dark mode & transitions
This commit is contained in:
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) => (
|
||||
<>
|
||||
|
||||
@@ -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
8
src/lib/load-image.ts
Normal 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;
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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'}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user