From ff31c28d384dfe1ace03bbb426cb91d3726b9e47 Mon Sep 17 00:00:00 2001 From: chris Date: Mon, 22 Sep 2025 14:25:07 +0000 Subject: [PATCH] too lazy to think of a message, so enjoy this pointless text. Good luck future me... --- bun.lock | 0 bunfig.toml | 12 +- flake.nix | 0 justfile | 6 + nix/devShells/flake-module.nix | 0 nix/modules/customer-portal/default.nix | 2 +- nix/modules/customer-portal/flake-module.nix | 0 nix/modules/flake-module.nix | 0 nix/packages/flake-module.nix | 0 src/app.css | 0 src/auth.client.ts | 10 +- src/auth.server.ts | 102 +++--- src/components/details/details.module.css | 66 ++-- src/components/details/details.tsx | 70 ++-- src/components/details/index.ts | 4 +- src/components/dropdown/dropdown.module.css | 190 +++++----- src/components/dropdown/dropdown.tsx | 102 +++--- src/components/dropdown/index.ts | 6 +- src/components/hero/hero.module.css | 348 +++++++++--------- src/components/hero/hero.tsx | 122 +++---- src/components/hero/index.ts | 2 +- src/components/list/index.ts | 2 +- src/components/list/list.module.css | 184 +++++----- src/components/list/list.tsx | 54 +-- src/components/select/index.ts | 4 +- src/components/select/select.module.css | 350 +++++++++---------- src/components/select/select.tsx | 132 +++---- src/features/shell/index.tsx | 2 +- src/features/shell/nav.module.css | 336 +++++++++--------- src/features/shell/nav.tsx | 50 +-- src/features/shell/shell.module.css | 102 +++--- src/features/shell/shell.tsx | 44 +-- src/features/shell/top.module.css | 74 ++-- src/features/shell/top.tsx | 118 +++---- src/features/theme/context.ts | 0 src/features/theme/index.ts | 0 src/features/theme/picker.module.css | 0 src/features/theme/picker.tsx | 0 src/features/user/avatar.module.css | 14 +- src/features/user/avatar.tsx | 54 +-- src/features/user/index.ts | 8 +- src/features/user/profile.module.css | 44 +-- src/features/user/profile.tsx | 36 +- src/features/user/user.ts | 12 +- src/routes/(shell).tsx | 0 src/utilities.ts | 138 ++++---- 46 files changed, 1403 insertions(+), 1397 deletions(-) mode change 100644 => 100755 bun.lock mode change 100644 => 100755 bunfig.toml mode change 100644 => 100755 flake.nix create mode 100755 justfile mode change 100644 => 100755 nix/devShells/flake-module.nix mode change 100644 => 100755 nix/modules/customer-portal/default.nix mode change 100644 => 100755 nix/modules/customer-portal/flake-module.nix mode change 100644 => 100755 nix/modules/flake-module.nix mode change 100644 => 100755 nix/packages/flake-module.nix mode change 100644 => 100755 src/app.css mode change 100644 => 100755 src/auth.client.ts mode change 100644 => 100755 src/auth.server.ts mode change 100644 => 100755 src/components/details/details.module.css mode change 100644 => 100755 src/components/details/details.tsx mode change 100644 => 100755 src/components/details/index.ts mode change 100644 => 100755 src/components/dropdown/dropdown.module.css mode change 100644 => 100755 src/components/dropdown/dropdown.tsx mode change 100644 => 100755 src/components/dropdown/index.ts mode change 100644 => 100755 src/components/hero/hero.module.css mode change 100644 => 100755 src/components/hero/hero.tsx mode change 100644 => 100755 src/components/hero/index.ts mode change 100644 => 100755 src/components/list/index.ts mode change 100644 => 100755 src/components/list/list.module.css mode change 100644 => 100755 src/components/list/list.tsx mode change 100644 => 100755 src/components/select/index.ts mode change 100644 => 100755 src/components/select/select.module.css mode change 100644 => 100755 src/components/select/select.tsx mode change 100644 => 100755 src/features/shell/index.tsx mode change 100644 => 100755 src/features/shell/nav.module.css mode change 100644 => 100755 src/features/shell/nav.tsx mode change 100644 => 100755 src/features/shell/shell.module.css mode change 100644 => 100755 src/features/shell/shell.tsx mode change 100644 => 100755 src/features/shell/top.module.css mode change 100644 => 100755 src/features/shell/top.tsx mode change 100644 => 100755 src/features/theme/context.ts mode change 100644 => 100755 src/features/theme/index.ts mode change 100644 => 100755 src/features/theme/picker.module.css mode change 100644 => 100755 src/features/theme/picker.tsx mode change 100644 => 100755 src/features/user/avatar.module.css mode change 100644 => 100755 src/features/user/avatar.tsx mode change 100644 => 100755 src/features/user/index.ts mode change 100644 => 100755 src/features/user/profile.module.css mode change 100644 => 100755 src/features/user/profile.tsx mode change 100644 => 100755 src/features/user/user.ts mode change 100644 => 100755 src/routes/(shell).tsx mode change 100644 => 100755 src/utilities.ts diff --git a/bun.lock b/bun.lock old mode 100644 new mode 100755 diff --git a/bunfig.toml b/bunfig.toml old mode 100644 new mode 100755 index 08c06e2..7eaa449 --- a/bunfig.toml +++ b/bunfig.toml @@ -1,6 +1,6 @@ -[test] -coverage = true -coverageSkipTestFiles = true -coverageReporter = ['text', 'lcov'] -coverageDir = './.coverage' -preload = "./test.config.ts" +[test] +coverage = true +coverageSkipTestFiles = true +coverageReporter = ['text', 'lcov'] +coverageDir = './.coverage' +preload = "./test.config.ts" diff --git a/flake.nix b/flake.nix old mode 100644 new mode 100755 diff --git a/justfile b/justfile new file mode 100755 index 0000000..21a5568 --- /dev/null +++ b/justfile @@ -0,0 +1,6 @@ + + +push: + git add . + git commit -m 'too lazy to think of a message, so enjoy this pointless text. Good luck future me...' + git push \ No newline at end of file diff --git a/nix/devShells/flake-module.nix b/nix/devShells/flake-module.nix old mode 100644 new mode 100755 diff --git a/nix/modules/customer-portal/default.nix b/nix/modules/customer-portal/default.nix old mode 100644 new mode 100755 index 4b34a6b..3958b76 --- a/nix/modules/customer-portal/default.nix +++ b/nix/modules/customer-portal/default.nix @@ -77,7 +77,7 @@ in config = mkIf cfg.enable { systemd = { - servces.amarthCustomerPortal = { + services.amarthCustomerPortal = { after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; diff --git a/nix/modules/customer-portal/flake-module.nix b/nix/modules/customer-portal/flake-module.nix old mode 100644 new mode 100755 diff --git a/nix/modules/flake-module.nix b/nix/modules/flake-module.nix old mode 100644 new mode 100755 diff --git a/nix/packages/flake-module.nix b/nix/packages/flake-module.nix old mode 100644 new mode 100755 diff --git a/src/app.css b/src/app.css old mode 100644 new mode 100755 diff --git a/src/auth.client.ts b/src/auth.client.ts old mode 100644 new mode 100755 index 7f0e2e6..57cb453 --- a/src/auth.client.ts +++ b/src/auth.client.ts @@ -1,6 +1,6 @@ -import { createAuthClient } from "better-auth/solid"; -import { genericOAuthClient } from "better-auth/client/plugins"; - -export const { signIn, signOut, useSession, ...client } = createAuthClient({ - plugins: [genericOAuthClient()], +import { createAuthClient } from "better-auth/solid"; +import { genericOAuthClient } from "better-auth/client/plugins"; + +export const { signIn, signOut, useSession, ...client } = createAuthClient({ + plugins: [genericOAuthClient()], }); \ No newline at end of file diff --git a/src/auth.server.ts b/src/auth.server.ts old mode 100644 new mode 100755 index f7482fb..79717bb --- a/src/auth.server.ts +++ b/src/auth.server.ts @@ -1,51 +1,51 @@ -import { betterAuth } from "better-auth"; -import { genericOAuth } from "better-auth/plugins"; -import { Database } from "bun:sqlite"; - -export const auth = betterAuth({ - appName: "Streamarr", - basePath: "/api/auth", - database: new Database('auth.sqlite', { create: true }), - logger: { - level: "debug", - log(level, message, ...args) { - console.log(level, message, {args}); - }, - }, - user: { - additionalFields: { - name: { - type: "string", - nullable: true, - }, - username: { - type: "string", - nullable: true, - }, - }, - }, - plugins: [ - genericOAuth({ - config: [ - { - providerId: "zitadel", - clientId: "", - clientSecret: "", - discoveryUrl: "https://auth.amarth.cloud/.well-known/openid-configuration", - scopes: [ - "offline_access", - "openid", - "email", - "picture", - "profile", - "groups", - ], - accessType: "offline", - pkce: true, - mapProfileToUser: ({ id, name, email, image, preferred_username, emailVerified }) => - ({ id, name, email, emailVerified, image, username: preferred_username }), - }, - ], - }), - ], -}); +import { betterAuth } from "better-auth"; +import { genericOAuth } from "better-auth/plugins"; +import { Database } from "bun:sqlite"; + +export const auth = betterAuth({ + appName: "Streamarr", + basePath: "/api/auth", + database: new Database('auth.sqlite', { create: true }), + logger: { + level: "debug", + log(level, message, ...args) { + console.log(level, message, {args}); + }, + }, + user: { + additionalFields: { + name: { + type: "string", + nullable: true, + }, + username: { + type: "string", + nullable: true, + }, + }, + }, + plugins: [ + genericOAuth({ + config: [ + { + providerId: "zitadel", + clientId: "", + clientSecret: "", + discoveryUrl: "https://auth.amarth.cloud/.well-known/openid-configuration", + scopes: [ + "offline_access", + "openid", + "email", + "picture", + "profile", + "groups", + ], + accessType: "offline", + pkce: true, + mapProfileToUser: ({ id, name, email, image, preferred_username, emailVerified }) => + ({ id, name, email, emailVerified, image, username: preferred_username }), + }, + ], + }), + ], +}); diff --git a/src/components/details/details.module.css b/src/components/details/details.module.css old mode 100644 new mode 100755 index 6166745..3d21b51 --- a/src/components/details/details.module.css +++ b/src/components/details/details.module.css @@ -1,33 +1,33 @@ -.container { - isolation: isolate; - display: block grid; - - container-type: inline-size; -} - -.header { - position: relative; - block-size: 80cqb; - - &::after { - content: ""; - position: absolute; - inset: 0; - display: block; - background: linear-gradient( - atan(var(--ratio, .2)), - var(--surface-2) 20em, - transparent 90% - ); - } - - & > .background { - position: absolute; - inset: 0; - block-size: 100%; - inline-size: 100%; - object-fit: cover; - object-position: center; - z-index: 0; - } -} +.container { + isolation: isolate; + display: block grid; + + container-type: inline-size; +} + +.header { + position: relative; + block-size: 80cqb; + + &::after { + content: ""; + position: absolute; + inset: 0; + display: block; + background: linear-gradient( + atan(var(--ratio, .2)), + var(--surface-2) 20em, + transparent 90% + ); + } + + & > .background { + position: absolute; + inset: 0; + block-size: 100%; + inline-size: 100%; + object-fit: cover; + object-position: center; + z-index: 0; + } +} diff --git a/src/components/details/details.tsx b/src/components/details/details.tsx old mode 100644 new mode 100755 index 3a0d797..c67bdc6 --- a/src/components/details/details.tsx +++ b/src/components/details/details.tsx @@ -1,35 +1,35 @@ -import { Component, createSignal, onCleanup, onMount } from "solid-js"; -import { Entry } from "~/features/content"; -import css from "./details.module.css"; - -interface DetailsProps { - entry: Entry; -} - -export const Details: Component = (props) => { - const [header, setHeader] = createSignal(); - - onMount(() => { - const observer = new ResizeObserver(([entry]) => { - const { inlineSize, blockSize } = entry.contentBoxSize[0]; - (entry.target as HTMLElement).style.setProperty( - "--ratio", - String((blockSize * 0.2) / inlineSize) - ); - }); - - observer.observe(header()!); - - onCleanup(() => observer.disconnect()); - }); - - return ( -
-
- - -

{props.entry.title}

-
-
- ); -}; +import { Component, createSignal, onCleanup, onMount } from "solid-js"; +import { Entry } from "~/features/content"; +import css from "./details.module.css"; + +interface DetailsProps { + entry: Entry; +} + +export const Details: Component = (props) => { + const [header, setHeader] = createSignal(); + + onMount(() => { + const observer = new ResizeObserver(([entry]) => { + const { inlineSize, blockSize } = entry.contentBoxSize[0]; + (entry.target as HTMLElement).style.setProperty( + "--ratio", + String((blockSize * 0.2) / inlineSize) + ); + }); + + observer.observe(header()!); + + onCleanup(() => observer.disconnect()); + }); + + return ( +
+
+ + +

{props.entry.title}

+
+
+ ); +}; diff --git a/src/components/details/index.ts b/src/components/details/index.ts old mode 100644 new mode 100755 index 5983876..541192e --- a/src/components/details/index.ts +++ b/src/components/details/index.ts @@ -1,3 +1,3 @@ - - + + export { Details } from './details'; \ No newline at end of file diff --git a/src/components/dropdown/dropdown.module.css b/src/components/dropdown/dropdown.module.css old mode 100644 new mode 100755 index 158052a..49e8155 --- a/src/components/dropdown/dropdown.module.css +++ b/src/components/dropdown/dropdown.module.css @@ -1,96 +1,96 @@ -.box { - display: contents; - - &:has(> :popover-open) > .button { - background-color: var(--surface-500); - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } -} - -.button { - position: relative; - display: grid; - grid-template-columns: inherit; - place-items: center start; - - /* Make sure the height of the button does not collapse when it is empty */ - block-size: 1em; - box-sizing: content-box; - - padding: var(--size-2); - background-color: transparent; - border: none; - border-radius: var(--radius-2); - font-size: 1rem; - line-height: 1; - - cursor: pointer; - - &:hover { - background-color: var(--surface-4); - } - - &:has(> .caret) { - padding-inline-end: calc(1em + (2 * var(--size-2))); - } - - & > .caret { - position: absolute; - inset-inline-end: var(--size-2); - inset-block-start: 50%; - translate: 0 -50%; - inline-size: 1em; - } -} - -.dialog { - display: none; - position: relative; - grid-template-columns: inherit; - - inset-inline-start: anchor(start); - inset-block-start: anchor(end); - position-try-fallbacks: flip-block, flip-inline; - - /* inline-size: anchor-size(self-inline); */ - background-color: var(--surface-4); - padding: var(--size-2); - border: none; - box-shadow: var(--shadow-2); - - &:popover-open { - display: grid; - } - - & > header { - display: grid; - grid-column: 1 / -1; - - gap: var(--size-1); - } - - & > main { - display: grid; - grid-template-columns: subgrid; - grid-column: 1 / -1; - row-gap: var(--size-1); - } -} - -.option { - display: grid; - grid-template-columns: subgrid; - grid-column: 1 / -1; - place-items: center start; - - border-radius: var(--radius-2); - padding: var(--size-1); - margin-inline: calc(-1 * var(--size-1)); - - cursor: pointer; - - &.selected { - background-color: color(from var(--cyan-4) srgb r g b / .1); - } +.box { + display: contents; + + &:has(> :popover-open) > .button { + background-color: var(--surface-500); + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } +} + +.button { + position: relative; + display: grid; + grid-template-columns: inherit; + place-items: center start; + + /* Make sure the height of the button does not collapse when it is empty */ + block-size: 1em; + box-sizing: content-box; + + padding: var(--size-2); + background-color: transparent; + border: none; + border-radius: var(--radius-2); + font-size: 1rem; + line-height: 1; + + cursor: pointer; + + &:hover { + background-color: var(--surface-4); + } + + &:has(> .caret) { + padding-inline-end: calc(1em + (2 * var(--size-2))); + } + + & > .caret { + position: absolute; + inset-inline-end: var(--size-2); + inset-block-start: 50%; + translate: 0 -50%; + inline-size: 1em; + } +} + +.dialog { + display: none; + position: relative; + grid-template-columns: inherit; + + inset-inline-start: anchor(start); + inset-block-start: anchor(end); + position-try-fallbacks: flip-block, flip-inline; + + /* inline-size: anchor-size(self-inline); */ + background-color: var(--surface-4); + padding: var(--size-2); + border: none; + box-shadow: var(--shadow-2); + + &:popover-open { + display: grid; + } + + & > header { + display: grid; + grid-column: 1 / -1; + + gap: var(--size-1); + } + + & > main { + display: grid; + grid-template-columns: subgrid; + grid-column: 1 / -1; + row-gap: var(--size-1); + } +} + +.option { + display: grid; + grid-template-columns: subgrid; + grid-column: 1 / -1; + place-items: center start; + + border-radius: var(--radius-2); + padding: var(--size-1); + margin-inline: calc(-1 * var(--size-1)); + + cursor: pointer; + + &.selected { + background-color: color(from var(--cyan-4) srgb r g b / .1); + } } \ No newline at end of file diff --git a/src/components/dropdown/dropdown.tsx b/src/components/dropdown/dropdown.tsx old mode 100644 new mode 100755 index d43add3..5b30988 --- a/src/components/dropdown/dropdown.tsx +++ b/src/components/dropdown/dropdown.tsx @@ -1,52 +1,52 @@ -import { createSignal, JSX, createEffect, Show } from "solid-js"; -import { FaSolidAngleDown } from "solid-icons/fa"; -import css from './dropdown.module.css'; - -export interface DropdownApi { - show(): void; - hide(): void; -} - -interface DropdownProps { - api?: (api: DropdownApi) => any, - id: string; - class?: string; - open?: boolean; - showCaret?: boolean; - text: JSX.Element; - children: JSX.Element; -} - -export function Dropdown(props: DropdownProps) { - const [dialog, setDialog] = createSignal(); - const [open, setOpen] = createSignal(props.open ?? false); - - createEffect(() => { - dialog()?.[open() ? 'showPopover' : 'hidePopover'](); - }); - - createEffect(() => { - props.api?.({ - show() { - dialog()?.showPopover(); - }, - hide() { - dialog()?.hidePopover(); - }, - }); - }); - - return
- - - setOpen(e.newState === 'open')}> - {props.children} - -
; +import { createSignal, JSX, createEffect, Show } from "solid-js"; +import { FaSolidAngleDown } from "solid-icons/fa"; +import css from './dropdown.module.css'; + +export interface DropdownApi { + show(): void; + hide(): void; +} + +interface DropdownProps { + api?: (api: DropdownApi) => any, + id: string; + class?: string; + open?: boolean; + showCaret?: boolean; + text: JSX.Element; + children: JSX.Element; +} + +export function Dropdown(props: DropdownProps) { + const [dialog, setDialog] = createSignal(); + const [open, setOpen] = createSignal(props.open ?? false); + + createEffect(() => { + dialog()?.[open() ? 'showPopover' : 'hidePopover'](); + }); + + createEffect(() => { + props.api?.({ + show() { + dialog()?.showPopover(); + }, + hide() { + dialog()?.hidePopover(); + }, + }); + }); + + return
+ + + setOpen(e.newState === 'open')}> + {props.children} + +
; } \ No newline at end of file diff --git a/src/components/dropdown/index.ts b/src/components/dropdown/index.ts old mode 100644 new mode 100755 index f3bcb0e..cedf75b --- a/src/components/dropdown/index.ts +++ b/src/components/dropdown/index.ts @@ -1,4 +1,4 @@ - - -export type { DropdownApi } from './dropdown'; + + +export type { DropdownApi } from './dropdown'; export { Dropdown } from './dropdown'; \ No newline at end of file diff --git a/src/components/hero/hero.module.css b/src/components/hero/hero.module.css old mode 100644 new mode 100755 index 12ac398..f555d1a --- a/src/components/hero/hero.module.css +++ b/src/components/hero/hero.module.css @@ -1,174 +1,174 @@ -@property --thumb-image { - syntax: ""; - inherits: true; -} - -.container { - isolation: isolate; - display: block grid; - grid-auto-flow: column; - grid-auto-columns: 100%; - - container-type: inline-size; - - overflow: hidden visible; - scroll-snap-type: inline mandatory; - overscroll-behavior-inline: contain; - - @media (prefers-reduced-motion: no-preference) { - scroll-behavior: smooth; - } - - scroll-marker-group: after; - - &::scroll-marker-group { - display: block grid; - - grid-auto-flow: column; - grid-auto-columns: 5em; - gap: 1rem; - justify-content: start; - - padding-inline: var(--size-6); - - inline-size: 100%; - block-size: 8.333333em; - - z-index: 1; - } -} - -.page { - --__i: var(--sibling-index); - --__c: var(--sibling-count); - scroll-snap-align: center; - position: relative; - display: grid; - grid: repeat(3, auto) / 15em 1fr; - grid-template-areas: - "thumbnail . ." - "thumbnail title cta" - "thumbnail detail detail" - "thumbnail summary summary"; - align-content: end; - align-items: center; - gap: 1rem; - padding: var(--size-6); - block-size: 80vh; - overflow: clip; - container-type: scroll-state; - - animation: - animate-in linear forwards, - animate-out linear forwards; - animation-timeline: view(inline); - animation-range: entry, exit; - - color: var(--gray-0); - - &::after { - content: ""; - position: absolute; - inset: 0; - display: block; - background: linear-gradient(182.5deg, transparent 20%, var(--surface-2) 90%), - linear-gradient(transparent 50%, #0007 75%); - } - - &::scroll-marker { - display: block; - content: " "; - - inline-size: 5rem; - aspect-ratio: 3 / 5; - - background: var(--thumb-image) center / cover no-repeat; - background-color: cornflowerblue; - border-radius: var(--radius-2); - - transform: scale(1); - transform-origin: top left; - transition: 0.3s; - } - - &::scroll-marker:target-current { - /* outline: 1px solid white; */ - transform: translate(calc(-0cqi - (6rem * (var(--__i) - 1))), -29rem) - scale(3); - } -} - -.title { - grid-area: title; - font-size: 2.5em; - z-index: 1; - filter: contrast(9); -} - -.cta { - grid-area: cta; - z-index: 1; - border-radius: var(--radius-2); - background-color: var(--gray-2); - color: var(--gray-8); - text-decoration-color: var(--gray-8); - padding: var(--size-3); - font-weight: var(--font-weight-9); - outline-offset: var(--size-1); - - &:focus-visible { - outline: 1px solid var(--gray-2); - } -} - -.thumbnail { - grid-area: thumbnail; - inline-size: 15em; - aspect-ratio: 3 / 5; - border-radius: var(--radius-3); - object-fit: cover; - object-position: center; - z-index: 1; - opacity: 0 !important; -} - -.background { - position: absolute; - inset: 0; - block-size: 100%; - inline-size: 100%; - object-fit: cover; - object-position: center; - z-index: 0; -} - -.detail { - grid-area: detail; - z-index: 1; -} - -.summary { - grid-area: summary; - text-wrap: balance; - z-index: 1; -} - -@keyframes animate-in { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} -@keyframes animate-out { - 0% { - opacity: 1; - } - 20% { - opacity: 0; - } - 100% { - opacity: 0; - } -} +@property --thumb-image { + syntax: ""; + inherits: true; +} + +.container { + isolation: isolate; + display: block grid; + grid-auto-flow: column; + grid-auto-columns: 100%; + + container-type: inline-size; + + overflow: hidden visible; + scroll-snap-type: inline mandatory; + overscroll-behavior-inline: contain; + + @media (prefers-reduced-motion: no-preference) { + scroll-behavior: smooth; + } + + scroll-marker-group: after; + + &::scroll-marker-group { + display: block grid; + + grid-auto-flow: column; + grid-auto-columns: 5em; + gap: 1rem; + justify-content: start; + + padding-inline: var(--size-6); + + inline-size: 100%; + block-size: 8.333333em; + + z-index: 1; + } +} + +.page { + --__i: var(--sibling-index); + --__c: var(--sibling-count); + scroll-snap-align: center; + position: relative; + display: grid; + grid: repeat(3, auto) / 15em 1fr; + grid-template-areas: + "thumbnail . ." + "thumbnail title cta" + "thumbnail detail detail" + "thumbnail summary summary"; + align-content: end; + align-items: center; + gap: 1rem; + padding: var(--size-6); + block-size: 80vh; + overflow: clip; + container-type: scroll-state; + + animation: + animate-in linear forwards, + animate-out linear forwards; + animation-timeline: view(inline); + animation-range: entry, exit; + + color: var(--gray-0); + + &::after { + content: ""; + position: absolute; + inset: 0; + display: block; + background: linear-gradient(182.5deg, transparent 20%, var(--surface-2) 90%), + linear-gradient(transparent 50%, #0007 75%); + } + + &::scroll-marker { + display: block; + content: " "; + + inline-size: 5rem; + aspect-ratio: 3 / 5; + + background: var(--thumb-image) center / cover no-repeat; + background-color: cornflowerblue; + border-radius: var(--radius-2); + + transform: scale(1); + transform-origin: top left; + transition: 0.3s; + } + + &::scroll-marker:target-current { + /* outline: 1px solid white; */ + transform: translate(calc(-0cqi - (6rem * (var(--__i) - 1))), -29rem) + scale(3); + } +} + +.title { + grid-area: title; + font-size: 2.5em; + z-index: 1; + filter: contrast(9); +} + +.cta { + grid-area: cta; + z-index: 1; + border-radius: var(--radius-2); + background-color: var(--gray-2); + color: var(--gray-8); + text-decoration-color: var(--gray-8); + padding: var(--size-3); + font-weight: var(--font-weight-9); + outline-offset: var(--size-1); + + &:focus-visible { + outline: 1px solid var(--gray-2); + } +} + +.thumbnail { + grid-area: thumbnail; + inline-size: 15em; + aspect-ratio: 3 / 5; + border-radius: var(--radius-3); + object-fit: cover; + object-position: center; + z-index: 1; + opacity: 0 !important; +} + +.background { + position: absolute; + inset: 0; + block-size: 100%; + inline-size: 100%; + object-fit: cover; + object-position: center; + z-index: 0; +} + +.detail { + grid-area: detail; + z-index: 1; +} + +.summary { + grid-area: summary; + text-wrap: balance; + z-index: 1; +} + +@keyframes animate-in { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} +@keyframes animate-out { + 0% { + opacity: 1; + } + 20% { + opacity: 0; + } + 100% { + opacity: 0; + } +} diff --git a/src/components/hero/hero.tsx b/src/components/hero/hero.tsx old mode 100644 new mode 100755 index e6ee70d..c3ef21f --- a/src/components/hero/hero.tsx +++ b/src/components/hero/hero.tsx @@ -1,61 +1,61 @@ -import { Component, createEffect, createMemo, For, Index } from "solid-js"; -import { createSlug, Entry } from "~/features/content"; -import css from "./hero.module.css"; - -type HeroProps = { - entries: Entry[]; - class?: string; -}; - -export function Hero(props: HeroProps) { - return ( -
- {(entry) => } -
- ); -} - -const Page: Component<{ entry: Entry }> = (props) => { - const slug = createMemo(() => createSlug(props.entry)); - - return ( -
-

{props.entry.title}

- - - Continue - - - - {/* */} - -
- ); -}; +import { Component, createEffect, createMemo, For, Index } from "solid-js"; +import { createSlug, Entry } from "~/features/content"; +import css from "./hero.module.css"; + +type HeroProps = { + entries: Entry[]; + class?: string; +}; + +export function Hero(props: HeroProps) { + return ( +
+ {(entry) => } +
+ ); +} + +const Page: Component<{ entry: Entry }> = (props) => { + const slug = createMemo(() => createSlug(props.entry)); + + return ( +
+

{props.entry.title}

+ + + Continue + + + + {/* */} + +
+ ); +}; diff --git a/src/components/hero/index.ts b/src/components/hero/index.ts old mode 100644 new mode 100755 index 9f31e41..6e85c17 --- a/src/components/hero/index.ts +++ b/src/components/hero/index.ts @@ -1 +1 @@ -export { Hero } from "./hero"; +export { Hero } from "./hero"; diff --git a/src/components/list/index.ts b/src/components/list/index.ts old mode 100644 new mode 100755 index f470c88..26d21c8 --- a/src/components/list/index.ts +++ b/src/components/list/index.ts @@ -1 +1 @@ -export { List } from "./list"; +export { List } from "./list"; diff --git a/src/components/list/list.module.css b/src/components/list/list.module.css old mode 100644 new mode 100755 index dcf474d..29754fa --- a/src/components/list/list.module.css +++ b/src/components/list/list.module.css @@ -1,92 +1,92 @@ -.container { - --_space: var(--size-6); - display: grid; - grid: auto auto / auto auto; - grid-template-areas: - "heading metadata" - "list list"; - justify-content: space-between; - inline-size: 100%; - - padding-inline: var(--_space); -} - -.heading { - grid-area: heading; - font-size: var(--size-7); - color: var(--text-1); - - padding-inline: var(--_space); -} - -.metadata { - grid-area: metadata; - color: var(--text-2); -} - -.list { - grid-area: list; - list-style-type: none; - - container-type: inline-size; - display: grid; - grid-auto-flow: column; - - gap: var(--_space); - padding: calc(8 * var(--_space)) calc(2 * var(--_space)) calc(2.5 * var(--_space)); - scroll-padding: calc(2 * var(--_space)); - margin: calc(-7 * var(--_space)) calc(-1 * var(--_space)) 0em; - - overflow: visible auto; - scroll-snap-type: inline mandatory; - overscroll-behavior-inline: contain; - - @media (prefers-reduced-motion: no-preference) { - scroll-behavior: smooth; - } - - /* the before and afters have unsnappable elements that create bouncy edges to the scroll */ - &::before, - &::after { - content: ""; - display: block; - } - - &::before { - inline-size: 15cqi; - } - - &::after { - inline-size: 100cqi; - } - - & > li { - scroll-snap-align: start; - container-type: scroll-state; - padding: 0; - position: relative; - isolation: isolate; - - z-index: calc(var(--sibling-count) - var(--sibling-index)); - - &:has(> :hover, > :focus-within) { - z-index: calc(var(--sibling-count) + 1); - } - - & > * { - @supports (animation-timeline: view()) { - @media (prefers-reduced-motion: no-preference) { - animation: slide-in linear both; - animation-timeline: view(inline); - animation-range: cover -100cqi contain 15cqi; - } - } - } - } -} - -@keyframes slide-in { - from { - transform: translateX(-100cqi) scale(0.5); - } -} +.container { + --_space: var(--size-6); + display: grid; + grid: auto auto / auto auto; + grid-template-areas: + "heading metadata" + "list list"; + justify-content: space-between; + inline-size: 100%; + + padding-inline: var(--_space); +} + +.heading { + grid-area: heading; + font-size: var(--size-7); + color: var(--text-1); + + padding-inline: var(--_space); +} + +.metadata { + grid-area: metadata; + color: var(--text-2); +} + +.list { + grid-area: list; + list-style-type: none; + + container-type: inline-size; + display: grid; + grid-auto-flow: column; + + gap: var(--_space); + padding: calc(8 * var(--_space)) calc(2 * var(--_space)) calc(2.5 * var(--_space)); + scroll-padding: calc(2 * var(--_space)); + margin: calc(-7 * var(--_space)) calc(-1 * var(--_space)) 0em; + + overflow: visible auto; + scroll-snap-type: inline mandatory; + overscroll-behavior-inline: contain; + + @media (prefers-reduced-motion: no-preference) { + scroll-behavior: smooth; + } + + /* the before and afters have unsnappable elements that create bouncy edges to the scroll */ + &::before, + &::after { + content: ""; + display: block; + } + + &::before { + inline-size: 15cqi; + } + + &::after { + inline-size: 100cqi; + } + + & > li { + scroll-snap-align: start; + container-type: scroll-state; + padding: 0; + position: relative; + isolation: isolate; + + z-index: calc(var(--sibling-count) - var(--sibling-index)); + + &:has(> :hover, > :focus-within) { + z-index: calc(var(--sibling-count) + 1); + } + + & > * { + @supports (animation-timeline: view()) { + @media (prefers-reduced-motion: no-preference) { + animation: slide-in linear both; + animation-timeline: view(inline); + animation-range: cover -100cqi contain 15cqi; + } + } + } + } +} + +@keyframes slide-in { + from { + transform: translateX(-100cqi) scale(0.5); + } +} diff --git a/src/components/list/list.tsx b/src/components/list/list.tsx old mode 100644 new mode 100755 index fb70fa3..8b518e7 --- a/src/components/list/list.tsx +++ b/src/components/list/list.tsx @@ -1,27 +1,27 @@ -import { Accessor, Index, JSX } from "solid-js"; -import css from "./list.module.css"; - -interface ListProps { - label: string; - items: T[]; - class?: string; - children: (item: Accessor) => JSX.Element; -} - -export function List(props: ListProps) { - return ( -
- - {props.label} - - - {props.items.length} result(s) - -
    - - {(item) =>
  • {props.children(item)}
  • } -
    -
-
- ); -} +import { Accessor, Index, JSX } from "solid-js"; +import css from "./list.module.css"; + +interface ListProps { + label: string; + items: T[]; + class?: string; + children: (item: Accessor) => JSX.Element; +} + +export function List(props: ListProps) { + return ( +
+ + {props.label} + + + {props.items.length} result(s) + +
    + + {(item) =>
  • {props.children(item)}
  • } +
    +
+
+ ); +} diff --git a/src/components/select/index.ts b/src/components/select/index.ts old mode 100644 new mode 100755 index 30079fc..f330df3 --- a/src/components/select/index.ts +++ b/src/components/select/index.ts @@ -1,3 +1,3 @@ - - + + export { Select } from './select'; \ No newline at end of file diff --git a/src/components/select/select.module.css b/src/components/select/select.module.css old mode 100644 new mode 100755 index 6886395..76e44b1 --- a/src/components/select/select.module.css +++ b/src/components/select/select.module.css @@ -1,176 +1,176 @@ -.box { - appearance: none; - - display: block grid; - place-items: center start; - - padding: var(--size-2); - background-color: transparent; - border: none; - border-radius: var(--radius-2); - font-size: 1rem; - - &:hover { - background-color: var(--surface-700); - } - - @supports (appearance: base-select) { - - &, - &::picker(select) { - appearance: base-select; - } - - &::picker(select) { - /* display: block grid; - row-gap: var(--size-2); */ - - background-color: var(--surface-3); - padding: var(--size-2) 0; - border: none; - box-shadow: var(--shadow-2); - - opacity: 0; - block-size: 0; - overflow: clip; - transition: - height 0.5s ease-out, - opacity 0.5s ease-out, - overlay 0.5s, - display 0.5s, - overflow 0.5s; - - transition-behavior: allow-discrete; - } - - &:open { - background-color: var(--surface-3); - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - - &::picker(select) { - opacity: 1; - block-size: calc-size(auto, size); - overflow: auto; - - @starting-style { - opacity: 0; - block-size: 0; - } - } - } - - & > option { - display: block grid; - grid-auto-flow: column; - place-items: center start; - - border-radius: var(--radius-2); - padding: var(--size-2); - - cursor: pointer; - - &:checked { - background-color: var(--surface-4); - } - - &::checkmark { - display: none; - } - } - } -} - -/* .box { - display: contents; - - &:has(> :popover-open) > .button { - background-color: var(--surface-500); - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } -} - -.button { - position: relative; - display: grid; - grid-template-columns: inherit; - place-items: center start; - - block-size: 1em; - box-sizing: content-box; - - padding: var(--size-2); - background-color: transparent; - border: none; - border-radius: var(--radius-2); - font-size: 1rem; - - cursor: pointer; - - &:hover { - background-color: var(--surface-700); - } - - &:has(> .caret) { - padding-inline-end: calc(1em + (2 * var(--size-2))); - } - - & > .caret { - position: absolute; - inset-inline-end: var(--size-2); - inset-block-start: 50%; - translate: 0 -50%; - inline-size: 1em; - } -} - -.dialog { - display: none; - position: relative; - grid-template-columns: inherit; - - inset-inline-start: anchor(start); - inset-block-start: anchor(end); - position-try-fallbacks: flip-block, flip-inline; - - background-color: var(--surface-3); - padding: var(--size-2); - border: none; - box-shadow: var(--shadow-2); - - &:popover-open { - display: grid; - } - - & > header { - display: grid; - grid-column: 1 / -1; - - gap: var(--padding-s); - } - - & > main { - display: grid; - grid-template-columns: subgrid; - grid-column: 1 / -1; - row-gap: var(--padding-s); - } -} - -.option { - display: grid; - grid-template-columns: subgrid; - grid-column: 1 / -1; - place-items: center start; - - border-radius: var(--radii-m); - padding: var(--padding-s); - margin-inline: calc(-1 * var(--padding-s)); - - cursor: pointer; - - &.selected { - background-color: oklch(from var(--info) l c h / .1); - } +.box { + appearance: none; + + display: block grid; + place-items: center start; + + padding: var(--size-2); + background-color: transparent; + border: none; + border-radius: var(--radius-2); + font-size: 1rem; + + &:hover { + background-color: var(--surface-700); + } + + @supports (appearance: base-select) { + + &, + &::picker(select) { + appearance: base-select; + } + + &::picker(select) { + /* display: block grid; + row-gap: var(--size-2); */ + + background-color: var(--surface-3); + padding: var(--size-2) 0; + border: none; + box-shadow: var(--shadow-2); + + opacity: 0; + block-size: 0; + overflow: clip; + transition: + height 0.5s ease-out, + opacity 0.5s ease-out, + overlay 0.5s, + display 0.5s, + overflow 0.5s; + + transition-behavior: allow-discrete; + } + + &:open { + background-color: var(--surface-3); + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + + &::picker(select) { + opacity: 1; + block-size: calc-size(auto, size); + overflow: auto; + + @starting-style { + opacity: 0; + block-size: 0; + } + } + } + + & > option { + display: block grid; + grid-auto-flow: column; + place-items: center start; + + border-radius: var(--radius-2); + padding: var(--size-2); + + cursor: pointer; + + &:checked { + background-color: var(--surface-4); + } + + &::checkmark { + display: none; + } + } + } +} + +/* .box { + display: contents; + + &:has(> :popover-open) > .button { + background-color: var(--surface-500); + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } +} + +.button { + position: relative; + display: grid; + grid-template-columns: inherit; + place-items: center start; + + block-size: 1em; + box-sizing: content-box; + + padding: var(--size-2); + background-color: transparent; + border: none; + border-radius: var(--radius-2); + font-size: 1rem; + + cursor: pointer; + + &:hover { + background-color: var(--surface-700); + } + + &:has(> .caret) { + padding-inline-end: calc(1em + (2 * var(--size-2))); + } + + & > .caret { + position: absolute; + inset-inline-end: var(--size-2); + inset-block-start: 50%; + translate: 0 -50%; + inline-size: 1em; + } +} + +.dialog { + display: none; + position: relative; + grid-template-columns: inherit; + + inset-inline-start: anchor(start); + inset-block-start: anchor(end); + position-try-fallbacks: flip-block, flip-inline; + + background-color: var(--surface-3); + padding: var(--size-2); + border: none; + box-shadow: var(--shadow-2); + + &:popover-open { + display: grid; + } + + & > header { + display: grid; + grid-column: 1 / -1; + + gap: var(--padding-s); + } + + & > main { + display: grid; + grid-template-columns: subgrid; + grid-column: 1 / -1; + row-gap: var(--padding-s); + } +} + +.option { + display: grid; + grid-template-columns: subgrid; + grid-column: 1 / -1; + place-items: center start; + + border-radius: var(--radii-m); + padding: var(--padding-s); + margin-inline: calc(-1 * var(--padding-s)); + + cursor: pointer; + + &.selected { + background-color: oklch(from var(--info) l c h / .1); + } } */ \ No newline at end of file diff --git a/src/components/select/select.tsx b/src/components/select/select.tsx old mode 100644 new mode 100755 index eba8db2..22ce703 --- a/src/components/select/select.tsx +++ b/src/components/select/select.tsx @@ -1,67 +1,67 @@ -import { createMemo, createSignal, For, JSX, Setter, createEffect, Show } from "solid-js"; -import { Dropdown, DropdownApi } from "../dropdown"; -import css from './select.module.css'; - -interface SelectProps { - id: string; - class?: string; - value: K; - setValue?: Setter; - values: Record; - open?: boolean; - showCaret?: boolean; - children: (key: K, value: T) => JSX.Element; - filter?: (query: string, key: K, value: T) => boolean; -} - -export function Select(props: SelectProps) { - const [dropdown, setDropdown] = createSignal(); - const [key, setKey] = createSignal(props.value); - const [query, setQuery] = createSignal(''); - - const showCaret = createMemo(() => props.showCaret ?? true); - const values = createMemo(() => { - let entries = Object.entries(props.values) as [K, T][]; - const filter = props.filter; - const q = query(); - - if (filter) { - entries = entries.filter(([k, v]) => filter(q, k, v)); - } - - return entries; - }); - - createEffect(() => { - props.setValue?.(() => key()); - }); - - const text = { - key => { - const value = createMemo(() => props.values[key()]); - - return <>{props.children(key(), value())}; - } - } - - return - -
- setQuery(e.target.value)} /> -
-
- -
- { - ([k, v]) => { - const selected = createMemo(() => key() === k); - - return { - setKey(() => k); - dropdown()?.hide(); - }}>{props.children(k, v)}; - } - } -
-
+import { createMemo, createSignal, For, JSX, Setter, createEffect, Show } from "solid-js"; +import { Dropdown, DropdownApi } from "../dropdown"; +import css from './select.module.css'; + +interface SelectProps { + id: string; + class?: string; + value: K; + setValue?: Setter; + values: Record; + open?: boolean; + showCaret?: boolean; + children: (key: K, value: T) => JSX.Element; + filter?: (query: string, key: K, value: T) => boolean; +} + +export function Select(props: SelectProps) { + const [dropdown, setDropdown] = createSignal(); + const [key, setKey] = createSignal(props.value); + const [query, setQuery] = createSignal(''); + + const showCaret = createMemo(() => props.showCaret ?? true); + const values = createMemo(() => { + let entries = Object.entries(props.values) as [K, T][]; + const filter = props.filter; + const q = query(); + + if (filter) { + entries = entries.filter(([k, v]) => filter(q, k, v)); + } + + return entries; + }); + + createEffect(() => { + props.setValue?.(() => key()); + }); + + const text = { + key => { + const value = createMemo(() => props.values[key()]); + + return <>{props.children(key(), value())}; + } + } + + return + +
+ setQuery(e.target.value)} /> +
+
+ +
+ { + ([k, v]) => { + const selected = createMemo(() => key() === k); + + return { + setKey(() => k); + dropdown()?.hide(); + }}>{props.children(k, v)}; + } + } +
+
} \ No newline at end of file diff --git a/src/features/shell/index.tsx b/src/features/shell/index.tsx old mode 100644 new mode 100755 index ba91e96..d58e900 --- a/src/features/shell/index.tsx +++ b/src/features/shell/index.tsx @@ -1 +1 @@ -export { Shell } from './shell'; +export { Shell } from './shell'; diff --git a/src/features/shell/nav.module.css b/src/features/shell/nav.module.css old mode 100644 new mode 100755 index bb325f9..e4a29cd --- a/src/features/shell/nav.module.css +++ b/src/features/shell/nav.module.css @@ -1,168 +1,168 @@ -.nav { - grid-area: 2 / 1 / 3 / 2; - display: block grid; - grid-auto-flow: row; - justify-content: space-between; - inline-size: 5em; - block-size: 100%; - padding: 1em; - background: inherit; - z-index: 0; - transition: z-index 0.3s step-end; - - & > ul { - position: relative; - display: block grid; - grid-template-columns: 2.5rem auto; - align-content: center; - inline-size: 4rem; - gap: 1rem; - transform-origin: left center; - padding: 0; - padding-inline-start: 0.5rem; - margin: 0; - - &::before { - content: ""; - position: absolute; - inset-inline-start: 100%; - inset-block: -1em; - inline-size: 40vw; - background-image: linear-gradient(to right, rgb(from var(--surface-1) r g b / .9) 50%, transparent); - mask: radial-gradient( - ellipse 40vw 100% at left center, - black 25%, - transparent - ); - backdrop-filter: blur(5px); - opacity: 0; - transition: opacity 0.3s var(--ease-3); - } - - & > a { - position: relative; - grid-column: span 2; - display: block grid; - grid-template-columns: subgrid; - align-items: center; - text-decoration: none; - transform-origin: center left; - transition: - transform 2s var(--ease-spring-5), - opacity 0.3s var(--ease-3); - color: var(--text-2); - font-size: 2rem; - line-height: 1.5; - - & > span { - opacity: 0; - transition: opacity 0.3s var(--ease-3); - text-shadow: 0 0 .5em var(--surface-1); - } - - & > svg { - fill: var(--text-2); - inline-size: 2.5rem; - block-size: 2.5rem; - } - - &.active { - color: var(--yellow-5); - list-style: disc; - - &::before { - content: "•"; - position: absolute; - inset-inline-start: -1rem; - } - - & > svg { - fill: var(--yellow-5); - } - } - } - - &:has(a:is(:hover, :focus)) { - &::before { - opacity: 1; - } - - & > a { - transform: scale(max(1, calc(1.5 - (0.2 * abs(var(--target) - var(--sibling-index)))))); - - & > span { - opacity: 1; - } - } - } - - &:has(a:is(:hover, :focus)) > a:not(:is(:hover, :focus)) { - opacity: 0.25; - } - - &:has(a:is(:hover, :focus):nth-child(1)) { - --target: 1; - } - - &:has(a:is(:hover, :focus):nth-child(2)) { - --target: 2; - } - - &:has(a:is(:hover, :focus):nth-child(3)) { - --target: 3; - } - - &:has(a:is(:hover, :focus):nth-child(4)) { - --target: 4; - } - - &:has(a:is(:hover, :focus):nth-child(5)) { - --target: 5; - } - - &:has(a:is(:hover, :focus):nth-child(6)) { - --target: 6; - } - - &:has(a:is(:hover, :focus):nth-child(7)) { - --target: 7; - } - - &:has(a:is(:hover, :focus):nth-child(8)) { - --target: 8; - } - - &:has(a:is(:hover, :focus):nth-child(9)) { - --target: 9; - } - - &:has(a:is(:hover, :focus):nth-child(10)) { - --target: 10; - } - - &:has(a:is(:hover, :focus):nth-child(11)) { - --target: 11; - } - - &:has(a:is(:hover, :focus):nth-child(12)) { - --target: 12; - } - - &:has(a:is(:hover, :focus):nth-child(13)) { - --target: 13; - } - - &:has(a:is(:hover, :focus):nth-child(14)) { - --target: 14; - } - - &:has(a:is(:hover, :focus):nth-child(15)) { - --target: 15; - } - } - - &:has(a:hover, :focus-within) { - z-index: 1; - transition: z-index 0.3s step-start; - } -} +.nav { + grid-area: 2 / 1 / 3 / 2; + display: block grid; + grid-auto-flow: row; + justify-content: space-between; + inline-size: 5em; + block-size: 100%; + padding: 1em; + background: inherit; + z-index: 0; + transition: z-index 0.3s step-end; + + & > ul { + position: relative; + display: block grid; + grid-template-columns: 2.5rem auto; + align-content: center; + inline-size: 4rem; + gap: 1rem; + transform-origin: left center; + padding: 0; + padding-inline-start: 0.5rem; + margin: 0; + + &::before { + content: ""; + position: absolute; + inset-inline-start: 100%; + inset-block: -1em; + inline-size: 40vw; + background-image: linear-gradient(to right, rgb(from var(--surface-1) r g b / .9) 50%, transparent); + mask: radial-gradient( + ellipse 40vw 100% at left center, + black 25%, + transparent + ); + backdrop-filter: blur(5px); + opacity: 0; + transition: opacity 0.3s var(--ease-3); + } + + & > a { + position: relative; + grid-column: span 2; + display: block grid; + grid-template-columns: subgrid; + align-items: center; + text-decoration: none; + transform-origin: center left; + transition: + transform 2s var(--ease-spring-5), + opacity 0.3s var(--ease-3); + color: var(--text-2); + font-size: 2rem; + line-height: 1.5; + + & > span { + opacity: 0; + transition: opacity 0.3s var(--ease-3); + text-shadow: 0 0 .5em var(--surface-1); + } + + & > svg { + fill: var(--text-2); + inline-size: 2.5rem; + block-size: 2.5rem; + } + + &.active { + color: var(--yellow-5); + list-style: disc; + + &::before { + content: "•"; + position: absolute; + inset-inline-start: -1rem; + } + + & > svg { + fill: var(--yellow-5); + } + } + } + + &:has(a:is(:hover, :focus)) { + &::before { + opacity: 1; + } + + & > a { + transform: scale(max(1, calc(1.5 - (0.2 * abs(var(--target) - var(--sibling-index)))))); + + & > span { + opacity: 1; + } + } + } + + &:has(a:is(:hover, :focus)) > a:not(:is(:hover, :focus)) { + opacity: 0.25; + } + + &:has(a:is(:hover, :focus):nth-child(1)) { + --target: 1; + } + + &:has(a:is(:hover, :focus):nth-child(2)) { + --target: 2; + } + + &:has(a:is(:hover, :focus):nth-child(3)) { + --target: 3; + } + + &:has(a:is(:hover, :focus):nth-child(4)) { + --target: 4; + } + + &:has(a:is(:hover, :focus):nth-child(5)) { + --target: 5; + } + + &:has(a:is(:hover, :focus):nth-child(6)) { + --target: 6; + } + + &:has(a:is(:hover, :focus):nth-child(7)) { + --target: 7; + } + + &:has(a:is(:hover, :focus):nth-child(8)) { + --target: 8; + } + + &:has(a:is(:hover, :focus):nth-child(9)) { + --target: 9; + } + + &:has(a:is(:hover, :focus):nth-child(10)) { + --target: 10; + } + + &:has(a:is(:hover, :focus):nth-child(11)) { + --target: 11; + } + + &:has(a:is(:hover, :focus):nth-child(12)) { + --target: 12; + } + + &:has(a:is(:hover, :focus):nth-child(13)) { + --target: 13; + } + + &:has(a:is(:hover, :focus):nth-child(14)) { + --target: 14; + } + + &:has(a:is(:hover, :focus):nth-child(15)) { + --target: 15; + } + } + + &:has(a:hover, :focus-within) { + z-index: 1; + transition: z-index 0.3s step-start; + } +} diff --git a/src/features/shell/nav.tsx b/src/features/shell/nav.tsx old mode 100644 new mode 100755 index 1b48c46..5c82a6c --- a/src/features/shell/nav.tsx +++ b/src/features/shell/nav.tsx @@ -1,25 +1,25 @@ -import { A } from "@solidjs/router"; -import { AiOutlineHome, AiOutlineStar, AiOutlineSearch } from "solid-icons/ai"; -import { Component } from "solid-js"; -import css from "./nav.module.css"; - -export const Nav: Component = (props) => { - return ( - - ); -}; +import { A } from "@solidjs/router"; +import { AiOutlineHome, AiOutlineStar, AiOutlineSearch } from "solid-icons/ai"; +import { Component } from "solid-js"; +import css from "./nav.module.css"; + +export const Nav: Component = (props) => { + return ( + + ); +}; diff --git a/src/features/shell/shell.module.css b/src/features/shell/shell.module.css old mode 100644 new mode 100755 index 5d318ab..4950e97 --- a/src/features/shell/shell.module.css +++ b/src/features/shell/shell.module.css @@ -1,52 +1,52 @@ -.container { - position: relative; - display: block grid; - grid: auto 1fr / 5em 1fr; - grid-template-areas: - "top top" - "nav content"; - inline-size: 100%; - block-size: 100%; - z-index: 0; - overflow: clip; - container-type: inline-size; - background-color: var(--surface-1); - contain: layout style paint; - - &::after { - content: ''; - grid-area: content; - display: block; - position: absolute; - inset-inline-start: 0; - inset-block-start: 0; - inline-size: var(--radius-4); - block-size: var(--radius-4); - background: radial-gradient(circle at bottom right, transparent var(--radius-4), var(--surface-1) var(--radius-4)); - pointer-events: none; - } -} - -.body { - grid-area: 2 / 1 / 3 / 3; - inline-size: 100%; - block-size: 100%; - background: linear-gradient(180deg, - transparent, - transparent 90vh, - var(--surface-500) 90vh, - var(--surface-500)); - overflow: clip auto; - padding-inline-start: 5em; - transition: filter var(--duration-moderate-1) var(--ease-3); - container-type: size; - - & > div { - background-color: var(--surface-2); - container-type: inline-size; - contain: layout style paint; - inline-size: 100%; - block-size: fit-content; - min-block-size: 100%; - } +.container { + position: relative; + display: block grid; + grid: auto 1fr / 5em 1fr; + grid-template-areas: + "top top" + "nav content"; + inline-size: 100%; + block-size: 100%; + z-index: 0; + overflow: clip; + container-type: inline-size; + background-color: var(--surface-1); + contain: layout style paint; + + &::after { + content: ''; + grid-area: content; + display: block; + position: absolute; + inset-inline-start: 0; + inset-block-start: 0; + inline-size: var(--radius-4); + block-size: var(--radius-4); + background: radial-gradient(circle at bottom right, transparent var(--radius-4), var(--surface-1) var(--radius-4)); + pointer-events: none; + } +} + +.body { + grid-area: 2 / 1 / 3 / 3; + inline-size: 100%; + block-size: 100%; + background: linear-gradient(180deg, + transparent, + transparent 90vh, + var(--surface-500) 90vh, + var(--surface-500)); + overflow: clip auto; + padding-inline-start: 5em; + transition: filter var(--duration-moderate-1) var(--ease-3); + container-type: size; + + & > div { + background-color: var(--surface-2); + container-type: inline-size; + contain: layout style paint; + inline-size: 100%; + block-size: fit-content; + min-block-size: 100%; + } } \ No newline at end of file diff --git a/src/features/shell/shell.tsx b/src/features/shell/shell.tsx old mode 100644 new mode 100755 index 47984b0..222e099 --- a/src/features/shell/shell.tsx +++ b/src/features/shell/shell.tsx @@ -1,22 +1,22 @@ -import { ParentComponent } from "solid-js"; -import { Top } from "./top"; -import { Nav } from "./nav"; -import css from "./shell.module.css"; -import { User } from "../user"; - -interface ShellProps { - user: User | undefined; -} - -export const Shell: ParentComponent = (props) => { - return ( -
- -
- ); -}; +import { ParentComponent } from "solid-js"; +import { Top } from "./top"; +import { Nav } from "./nav"; +import css from "./shell.module.css"; +import { User } from "../user"; + +interface ShellProps { + user: User | undefined; +} + +export const Shell: ParentComponent = (props) => { + return ( +
+ +
+ ); +}; diff --git a/src/features/shell/top.module.css b/src/features/shell/top.module.css old mode 100644 new mode 100755 index 269b56c..09c2d86 --- a/src/features/shell/top.module.css +++ b/src/features/shell/top.module.css @@ -1,37 +1,37 @@ -.top { - grid-area: 1 / 1 / 2 / 3; - display: block grid; - grid-auto-flow: column; - justify-content: end; - z-index: 1; - background-color: inherit; - padding: 0.5em; -} - -.accountTrigger { - anchor-name: --account-trigger; - background: transparent; - padding: 0; - margin: 0; - border-radius: var(--radius-round); -} - -.accountMenu { - position-anchor: --account-trigger; - position: absolute; - inset: auto; - inset-inline-end: anchor(end); - inset-block-start: anchor(start); - - display: block grid; - grid-auto-flow: row; - gap: var(--size-3); - padding: var(--size-3); - background-color: light-dark(var(--gray-1), var(--gray-9)); - border-radius: var(--radius-2); - box-shadow: var(--shadow-2); - - &:not(:popover-open) { - display: none; - } -} +.top { + grid-area: 1 / 1 / 2 / 3; + display: block grid; + grid-auto-flow: column; + justify-content: end; + z-index: 1; + background-color: inherit; + padding: 0.5em; +} + +.accountTrigger { + anchor-name: --account-trigger; + background: transparent; + padding: 0; + margin: 0; + border-radius: var(--radius-round); +} + +.accountMenu { + position-anchor: --account-trigger; + position: absolute; + inset: auto; + inset-inline-end: anchor(end); + inset-block-start: anchor(start); + + display: block grid; + grid-auto-flow: row; + gap: var(--size-3); + padding: var(--size-3); + background-color: light-dark(var(--gray-1), var(--gray-9)); + border-radius: var(--radius-2); + box-shadow: var(--shadow-2); + + &:not(:popover-open) { + display: none; + } +} diff --git a/src/features/shell/top.tsx b/src/features/shell/top.tsx old mode 100644 new mode 100755 index 6466391..ec12390 --- a/src/features/shell/top.tsx +++ b/src/features/shell/top.tsx @@ -1,59 +1,59 @@ -import { Component, Show } from "solid-js"; -import { signIn, signOut, client } from "~/auth.client"; -import { Avatar, Profile, User } from "../user"; -import { ColorSchemePicker } from "../theme"; -import css from "./top.module.css"; - -interface TopProps { - user: User | undefined; -} - -export const Top: Component = (props) => { - const login = async (e: SubmitEvent) => { - e.preventDefault(); - - await signIn.oauth2({ - providerId: "authelia", - callbackURL: "/", - }); - }; - - const logout = async (e: SubmitEvent) => { - e.preventDefault(); - - await signOut(); - }; - - return ( - - ); -}; +import { Component, Show } from "solid-js"; +import { signIn, signOut, client } from "~/auth.client"; +import { Avatar, Profile, User } from "../user"; +import { ColorSchemePicker } from "../theme"; +import css from "./top.module.css"; + +interface TopProps { + user: User | undefined; +} + +export const Top: Component = (props) => { + const login = async (e: SubmitEvent) => { + e.preventDefault(); + + await signIn.oauth2({ + providerId: "authelia", + callbackURL: "/", + }); + }; + + const logout = async (e: SubmitEvent) => { + e.preventDefault(); + + await signOut(); + }; + + return ( + + ); +}; diff --git a/src/features/theme/context.ts b/src/features/theme/context.ts old mode 100644 new mode 100755 diff --git a/src/features/theme/index.ts b/src/features/theme/index.ts old mode 100644 new mode 100755 diff --git a/src/features/theme/picker.module.css b/src/features/theme/picker.module.css old mode 100644 new mode 100755 diff --git a/src/features/theme/picker.tsx b/src/features/theme/picker.tsx old mode 100644 new mode 100755 diff --git a/src/features/user/avatar.module.css b/src/features/user/avatar.module.css old mode 100644 new mode 100755 index e420a47..06a7ef2 --- a/src/features/user/avatar.module.css +++ b/src/features/user/avatar.module.css @@ -1,7 +1,7 @@ -.avatar { - inline-size: var(--size-8); - border-radius: var(--radius-round); - aspect-ratio: 1; - object-fit: cover; - object-position: center; -} +.avatar { + inline-size: var(--size-8); + border-radius: var(--radius-round); + aspect-ratio: 1; + object-fit: cover; + object-position: center; +} diff --git a/src/features/user/avatar.tsx b/src/features/user/avatar.tsx old mode 100644 new mode 100755 index ba169f1..dd389b2 --- a/src/features/user/avatar.tsx +++ b/src/features/user/avatar.tsx @@ -1,27 +1,27 @@ -import { Component, createMemo, Show } from "solid-js"; -import { User } from "./user"; -import { hash } from "~/utilities"; -import css from "./avatar.module.css"; - -interface AvatarProps { - user: User | undefined; -} - -export const Avatar: Component = (props) => { - const hashedEmail = hash("SHA-256", () => props.user?.email); - const src = createMemo(() => { - const user = props.user; - - if (user === undefined) { - return ""; - } - - if (user.image === null) { - return `https://www.gravatar.com/avatar/${hashedEmail()}`; - } - - return user.image; - }); - - return ; -}; +import { Component, createMemo, Show } from "solid-js"; +import { User } from "./user"; +import { hash } from "~/utilities"; +import css from "./avatar.module.css"; + +interface AvatarProps { + user: User | undefined; +} + +export const Avatar: Component = (props) => { + const hashedEmail = hash("SHA-256", () => props.user?.email); + const src = createMemo(() => { + const user = props.user; + + if (user === undefined) { + return ""; + } + + if (user.image === null) { + return `https://www.gravatar.com/avatar/${hashedEmail()}`; + } + + return user.image; + }); + + return ; +}; diff --git a/src/features/user/index.ts b/src/features/user/index.ts old mode 100644 new mode 100755 index 836d10f..5b436a4 --- a/src/features/user/index.ts +++ b/src/features/user/index.ts @@ -1,4 +1,4 @@ -export type { User } from "./user"; - -export { Avatar } from "./avatar"; -export { Profile } from "./profile"; +export type { User } from "./user"; + +export { Avatar } from "./avatar"; +export { Profile } from "./profile"; diff --git a/src/features/user/profile.module.css b/src/features/user/profile.module.css old mode 100644 new mode 100755 index 326be27..23aea80 --- a/src/features/user/profile.module.css +++ b/src/features/user/profile.module.css @@ -1,22 +1,22 @@ -.profile { - display: block grid; - grid: auto 1fr / auto 1fr; - gap: var(--size-2); - place-content: start; - background-color: light-dark(var(--gray-1), var(--gray-9)); - - & > img { - grid-area: span 2 / 1; - } - - & > strong { - font-size: var(--size-4); - line-height: 1; - color: light-dark(var(--gray-7), var(--gray-3)); - } - - & > span { - line-height: 1; - color: light-dark(var(--gray-4), var(--gray-6)); - } -} +.profile { + display: block grid; + grid: auto 1fr / auto 1fr; + gap: var(--size-2); + place-content: start; + background-color: light-dark(var(--gray-1), var(--gray-9)); + + & > img { + grid-area: span 2 / 1; + } + + & > strong { + font-size: var(--size-4); + line-height: 1; + color: light-dark(var(--gray-7), var(--gray-3)); + } + + & > span { + line-height: 1; + color: light-dark(var(--gray-4), var(--gray-6)); + } +} diff --git a/src/features/user/profile.tsx b/src/features/user/profile.tsx old mode 100644 new mode 100755 index be64436..9f8bd32 --- a/src/features/user/profile.tsx +++ b/src/features/user/profile.tsx @@ -1,18 +1,18 @@ -import { Component } from "solid-js"; -import { User } from "./user"; -import { Avatar } from "./avatar"; -import css from "./profile.module.css"; - -interface ProfileProps { - user: User | undefined; -} - -export const Profile: Component = (props) => { - return ( -
- - {props.user?.name ?? ""} - {props.user?.email ?? ""} -
- ); -}; +import { Component } from "solid-js"; +import { User } from "./user"; +import { Avatar } from "./avatar"; +import css from "./profile.module.css"; + +interface ProfileProps { + user: User | undefined; +} + +export const Profile: Component = (props) => { + return ( +
+ + {props.user?.name ?? ""} + {props.user?.email ?? ""} +
+ ); +}; diff --git a/src/features/user/user.ts b/src/features/user/user.ts old mode 100644 new mode 100755 index ca822f5..ae82627 --- a/src/features/user/user.ts +++ b/src/features/user/user.ts @@ -1,6 +1,6 @@ -export interface User { - username: string; - name: string; - email: string; - image: string | null; -} +export interface User { + username: string; + name: string; + email: string; + image: string | null; +} diff --git a/src/routes/(shell).tsx b/src/routes/(shell).tsx old mode 100644 new mode 100755 diff --git a/src/utilities.ts b/src/utilities.ts old mode 100644 new mode 100755 index b5926b1..ff13210 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -1,69 +1,69 @@ -import { Accessor, createEffect, createSignal, on } from "solid-js"; - -export const splitAt = ( - subject: string, - index: number, -): readonly [string, string] => { - if (index < 0) { - return [subject, ""]; - } - - if (index > subject.length) { - return [subject, ""]; - } - - return [subject.slice(0, index), subject.slice(index + 1)]; -}; - -export const toSlug = (subject: string) => - subject.toLowerCase().replaceAll(" ", "-").replaceAll(/[^\w-]/gi, ""); -export const toHex = (subject: number) => subject.toString(16).padStart(2, "0"); - -const encoder = new TextEncoder(); -export const hash = ( - algorithm: AlgorithmIdentifier, - subject: Accessor, -) => { - const [hash, setHash] = createSignal(); - - createEffect( - on(subject, async (subject) => { - if (subject === null || subject === undefined || subject.length === 0) { - setHash(undefined); - - return; - } - - const buffer = new Uint8Array( - await crypto.subtle.digest(algorithm, encoder.encode(subject)), - ); - - setHash(Array.from(buffer).map(toHex).join("")); - }), - ); - - return hash; -}; - -export const merge = (...objects: Record[]): Record => { - if (objects.length === 0) { - return {}; - } - - const target = objects[0]; - - for (const key of new Set(objects.map(o => Object.keys(o)).flat())) { - const values = objects.filter(o => Object.hasOwn(o, key)).map(o => o[key]); - - target[key] = values.every(v => v && typeof v === 'object' && !Array.isArray(v)) ? merge(...values) : values.at(-1); - } - - return target; -}; - -type CamelCase = S extends `${infer First}${infer Rest}` ? `${Lowercase}${Rest}` : Lowercase; -export type CamelCased> = { - [ K in keyof T as CamelCase]: T[K]; -} & {}; - -export const mapKeysToCamelCase = >(subject: T): CamelCased => Object.fromEntries(Object.entries(subject).map(([k, v]) => [`${k[0].toLowerCase()}${k.slice(1)}`, v])) as CamelCased; +import { Accessor, createEffect, createSignal, on } from "solid-js"; + +export const splitAt = ( + subject: string, + index: number, +): readonly [string, string] => { + if (index < 0) { + return [subject, ""]; + } + + if (index > subject.length) { + return [subject, ""]; + } + + return [subject.slice(0, index), subject.slice(index + 1)]; +}; + +export const toSlug = (subject: string) => + subject.toLowerCase().replaceAll(" ", "-").replaceAll(/[^\w-]/gi, ""); +export const toHex = (subject: number) => subject.toString(16).padStart(2, "0"); + +const encoder = new TextEncoder(); +export const hash = ( + algorithm: AlgorithmIdentifier, + subject: Accessor, +) => { + const [hash, setHash] = createSignal(); + + createEffect( + on(subject, async (subject) => { + if (subject === null || subject === undefined || subject.length === 0) { + setHash(undefined); + + return; + } + + const buffer = new Uint8Array( + await crypto.subtle.digest(algorithm, encoder.encode(subject)), + ); + + setHash(Array.from(buffer).map(toHex).join("")); + }), + ); + + return hash; +}; + +export const merge = (...objects: Record[]): Record => { + if (objects.length === 0) { + return {}; + } + + const target = objects[0]; + + for (const key of new Set(objects.map(o => Object.keys(o)).flat())) { + const values = objects.filter(o => Object.hasOwn(o, key)).map(o => o[key]); + + target[key] = values.every(v => v && typeof v === 'object' && !Array.isArray(v)) ? merge(...values) : values.at(-1); + } + + return target; +}; + +type CamelCase = S extends `${infer First}${infer Rest}` ? `${Lowercase}${Rest}` : Lowercase; +export type CamelCased> = { + [ K in keyof T as CamelCase]: T[K]; +} & {}; + +export const mapKeysToCamelCase = >(subject: T): CamelCased => Object.fromEntries(Object.entries(subject).map(([k, v]) => [`${k[0].toLowerCase()}${k.slice(1)}`, v])) as CamelCased;