dark mode & transitions

This commit is contained in:
Georgi Gardev
2023-11-19 17:31:55 +02:00
parent 09f0365955
commit fef851e7e7
15 changed files with 147 additions and 62 deletions

View File

@@ -2,9 +2,9 @@
position: relative;
overflow: hidden;
overflow-clip-margin: -10px;
background-color: rgba(255, 255, 255, 0.75);
background-color: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(10px);
color: #333;
color: #eee;
border-radius: 1rem;
transition: background-color 0.2s ease-in-out;
@@ -21,8 +21,8 @@
justify-content: space-between;
gap: 12px;
width: 140px;
height: 140px;
width: 150px;
height: 150px;
padding: 1rem;
text-decoration: none;
}
@@ -34,7 +34,7 @@
.Url {
font-size: .85rem;
color: var(--color-secondary);
color: #ddd
}
.Text {
@@ -54,7 +54,7 @@
bottom: -5px;
right: -5px;
font-size: 100px;
color: rgba(90, 90, 90, 0.3);
color: rgba(255, 255, 255, 0.3);
letter-spacing: -4px;
font-family: "Nova Mono";
}

View File

@@ -8,8 +8,8 @@
gap: max(1vw, 1rem);
margin-bottom: 1rem;
padding-bottom: 8px;
font-size: max(1.8vw, 1.3rem);
text-shadow: 1px 1px 5px #000;
font-size: clamp(1.3rem, 1.8vw, 3rem);
text-shadow: 1px 1px 7px #000;
}
.Close {

View File

@@ -14,6 +14,28 @@
}
.Overlay {
backdrop-filter: blur(10px);
pointer-events: none;
opacity: 0;
transition: opacity 1s ease;
}
.Overlay.Pending {
opacity: 1;
}
.Overlay img {
position: absolute;
object-fit: cover;
width: 100vw;
height: 100vh;
}
.Overlay .Black {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
backdrop-filter: blur(20px);
background-color: rgba(0, 0, 0, 0.4);
}

View File

@@ -1,34 +1,33 @@
import { Show, createEffect, createSignal } from 'solid-js';
import { Ref, Show, createEffect, createSignal, useTransition } from 'solid-js';
import { useSettings } from '~/state/settings-provider';
import style from './Background.module.css';
import { loadImage } from '~/lib/load-image';
export function Background() {
const { currentBackground } = useSettings();
const [fullLoaded, setFullLoaded] = createSignal(false);
const [isLoading, setIsLoading] = createSignal(true);
createEffect(() => {
if (!currentBackground()) {
return null;
}
var curImg = new Image();
curImg.src = currentBackground()!.urls.full;
curImg.onload = () => setFullLoaded(true);
if (currentBackground()?.urls.full) {
loadImage(currentBackground()!.urls.full).then(() => {
setTimeout(() => setIsLoading(false), 300);
});
}
});
return (
<Show
when={fullLoaded()}
fallback={
<>
<img class={style.Background} src={currentBackground()?.urls.thumb} />
<div class={style.Overlay}></div>
</>
}
>
<>
<img class={style.Background} src={currentBackground()?.urls.full} />
</Show>
<div classList={{ [style.Overlay]: true, [style.Pending]: isLoading() }}>
<img src={currentBackground()?.urls.small} />
<div class={style.Black} />
</div>
</>
);
}

View File

@@ -2,11 +2,19 @@
height: 100vh;
width: max(25vw, 180px);
backdrop-filter: blur(20px);
background-color: rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
flex-shrink: 0;
opacity: 1;
transition: opacity 0.5s ease, transform 0.5s ease;
}
.Sidebar.Loading {
opacity: 0;
}
.Menu {
@@ -20,7 +28,7 @@
.Menu li {
position: relative;
cursor: pointer;
text-shadow: 1px 1px 5px #000;
text-shadow: 1px 1px 7px #000;
padding: clamp(.5rem, 1vw, 1.5rem) 1.5rem;
}

View File

@@ -1,5 +1,5 @@
import { Tooltip } from '@hope-ui/solid';
import { Accessor, For, createSelector } from 'solid-js';
import { Accessor, For, createSelector, createSignal, onMount } from 'solid-js';
import { Settings } from '../widgets/Settings';
import { FavoriteBackground } from '../widgets/FavoriteBackground';
@@ -14,8 +14,14 @@ type SidebarProps = {
export function Sidebar(props: SidebarProps) {
const isActive = createSelector(props.activeCategory);
const [isInitialLoading, setIsInitialLoading] = createSignal(true);
onMount(() => {
setTimeout(() => setIsInitialLoading(false), 200);
});
return (
<div class={style.Sidebar}>
<div classList={{ [style.Sidebar]: true, [style.Loading]: isInitialLoading() }}>
<ul class={style.Menu}>
<For each={props.categories()}>
{(category) => (

View File

@@ -4,18 +4,19 @@
height: 100%;
font-family: nova mono;
letter-spacing: -2px;
text-shadow: 1px 1px 7px #000;
text-shadow: 1px 1px 12px #000;
font-weight: bold;
}
.Date {
font-size: clamp(2.5rem, 3vw, 6rem);
font-size: clamp(2.5rem, 3.5vw, 5.5rem);
margin-bottom: max(1vw, 1rem);
line-height: 1.1;
}
.Time {
font-size: clamp(1.5rem, 1.5vw, 3.5rem);
text-shadow: 1px 1px 4px #000;
font-size: clamp(1.5rem, 2vw, 2.8rem);
text-shadow: 1px 1px 5px #000;
}
.Logo {

View File

@@ -16,9 +16,11 @@
backdrop-filter: blur(10px);
padding: 11px 20px;
text-align: center;
background-color: rgba(0, 0, 0, 0.4);
border-bottom: 2px solid rgba(0, 0, 0, 0.4);
background-color: rgba(0, 0, 0, 0.6);
border-bottom: 2px solid rgba(0, 0, 0, 0.6);
font-family: Nova Mono;
border-top-right-radius: .5rem;
border-bottom-right-radius: .5rem;
}
.Search {
@@ -27,26 +29,21 @@
border: 0;
font-size: 1rem;
backdrop-filter: blur(10px);
background-color: rgba(0, 0, 0, 0.4);
border-bottom: 2px solid rgba(0, 0, 0, 0.4);
background-color: rgba(0, 0, 0, 0.6);
border-bottom: 2px solid rgba(0, 0, 0, 0.6);
color: var(--color-text);
outline: 0;
transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out;
}
.Search::placeholder {
color: var(--color-text);
color: rgba(255, 255, 255, 0.4);
user-select: none;
}
.Search:focus-within {
outline: 0;
}
.Search:focus-within, .Search:hover {
.Search:focus-within{
border-color: var(--color-text);
background-color: rgba(0, 0, 0, 0.6);
}
.Search::placeholder {
color: #eee;
user-select: none;
}
.Hint {

View File

@@ -7,9 +7,18 @@
display: flex;
flex-direction: column;
gap: .5rem;
border-radius: .75rem;
backdrop-filter: blur(10px);
background-color: rgba(0, 0, 0, 0.4);
background-color: rgba(0, 0, 0, 0.6);
opacity: 1;
transform: translateY(0);
transition: opacity 0.5s ease, transform 0.5s ease;
}
.Weather.Loading {
opacity: 0;
transform: translateY(-1rem);
}
.FeelsLike {
@@ -18,12 +27,12 @@
gap: .5rem;
margin-left: .5rem;
font-size: 1.3rem;
color: #ddd;
color: #eee;
}
.FeelsLike :last-child {
font-size: 1rem;
color: #bbb;
color: #ddd;
}
.Delimiter {

View File

@@ -1,6 +1,6 @@
import { HiSolidArrowDown, HiSolidArrowUp } from 'solid-icons/hi';
import { WiHumidity, WiSunrise, WiSunset } from 'solid-icons/wi';
import { Show } from 'solid-js';
import { Show, createEffect, createSignal } from 'solid-js';
import { createWeather } from '~/api/open-weather';
import { getTime } from '~/lib/date';
@@ -9,11 +9,18 @@ import style from './Weather.module.css';
export function Weather() {
const weather = createWeather();
const [isLoading, setIsLoading] = createSignal(true);
createEffect(() => {
if (weather() && isLoading() === true) {
setTimeout(() => setIsLoading(false), 200);
}
});
const sunrise = () => getTime((weather()?.sunrise ?? 0) * 1000);
const sunset = () => getTime((weather()?.sunset ?? 0) * 1000);
return (
<div class={style.Weather}>
<div classList={{ [style.Weather]: true, [style.Loading]: isLoading() }}>
<Show when={weather()} fallback="Loading">
{(weather) => (
<>

View File

@@ -1,6 +1,10 @@
import { random } from './math';
export function sample<T>(list: T[]): T {
export function sample<T>(list: T[]): T | undefined {
if (list.length === 0) {
return undefined;
}
if (list.length === 1) {
return list[0];
}

8
src/lib/load-image.ts Normal file
View File

@@ -0,0 +1,8 @@
export function loadImage(url: string): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject();
img.src = url;
});
}

View File

@@ -25,3 +25,18 @@
width: 100%;
margin-top: calc(50vh - 240px);
}
.MainWidgets {
display: flex;
flex-direction: column;
gap: 2rem;
opacity: 1;
transform: translateY(0);
transition: opacity 0.5s ease, transform 0.5s ease;
}
.MainWidgets.Loading {
opacity: 0;
transform: translateY(-1rem);
}

View File

@@ -67,6 +67,12 @@ export default function Home() {
const activeCategoryDef = () => categories()?.find((c) => c.name === activeCategory());
const [isInitialLoading, setIsInitialLoading] = createSignal(true);
onMount(() => {
setTimeout(() => setIsInitialLoading(false), 200);
});
return (
<main class={style.SidebarPage}>
<Sidebar
@@ -80,14 +86,16 @@ export default function Home() {
when={activeCategory()}
fallback={
<>
<DateView />
<Search
term={searchTerm}
setTerm={setSearchTerm}
providers={searchProviders}
canOpenApp={() => searchResults().length > 0}
onOpenApp={() => window.open(searchResults()[0].url)}
/>
<div classList={{ [style.MainWidgets]: true, [style.Loading]: isInitialLoading() }}>
<DateView />
<Search
term={searchTerm}
setTerm={setSearchTerm}
providers={searchProviders}
canOpenApp={() => searchResults().length > 0}
onOpenApp={() => window.open(searchResults()[0].url)}
/>
</div>
<Show when={shouldSearchLocally()} fallback={<Weather />}>
<AppList
category={() => 'search results'}

View File

@@ -46,7 +46,8 @@ export function SettingsProvider(props: { children: JSX.Element }) {
appSettings.useSavedBackgrounds ? sample(favoriteBackgrounds) : currentRandomBackground()
);
const isCurrentBackgroundSaved = () => isBackgroundSaved(currentBackground().id);
const isCurrentBackgroundSaved = () =>
Boolean(currentBackground() && isBackgroundSaved(currentBackground()!.id));
const onUnfavoriteCurrentBackground = () =>
currentBackground() && onUnfavoriteBackground(currentBackground()!.id);