feat: [frontend] Add shadcn-svelte
This commit is contained in:
parent
517e2bf90e
commit
dd069c470e
9 changed files with 568 additions and 416 deletions
20
frontend/components.json
Normal file
20
frontend/components.json
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://shadcn-svelte.com/schema.json",
|
||||||
|
"tailwind": {
|
||||||
|
"css": "src/app.css",
|
||||||
|
"baseColor": "neutral"
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "$lib/components",
|
||||||
|
"utils": "$lib/utils",
|
||||||
|
"ui": "$lib/components/ui",
|
||||||
|
"hooks": "$lib/hooks",
|
||||||
|
"lib": "$lib"
|
||||||
|
},
|
||||||
|
"typescript": true,
|
||||||
|
"registry": "https://shadcn-svelte.com/registry",
|
||||||
|
"style": "vega",
|
||||||
|
"iconLibrary": "lucide",
|
||||||
|
"menuColor": "default",
|
||||||
|
"menuAccent": "subtle"
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,8 @@ Token and role checking is centralised into the `src/hooks.server.ts` file, whic
|
||||||
|
|
||||||
## Components
|
## Components
|
||||||
|
|
||||||
|
For "boring" screens (settings pages, admin pages, cms-like pages) use shadcn-svelte components to create sensible defaults and uninteresting User Interfaces.
|
||||||
|
|
||||||
It is bad practice to simply have a `+page.svelte` component contain all aspects of a page. When convenient, code should be split into smaller component files.
|
It is bad practice to simply have a `+page.svelte` component contain all aspects of a page. When convenient, code should be split into smaller component files.
|
||||||
|
|
||||||
Where components aren't shared outside of a single page, they live as siblings to the `+page.svelte` file.
|
Where components aren't shared outside of a single page, they live as siblings to the `+page.svelte` file.
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,17 @@
|
||||||
"test": "npm run test:unit -- --run"
|
"test": "npm run test:unit -- --run"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/compat": "^2.0.2",
|
"@eslint/compat": "latest",
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "latest",
|
||||||
|
"@fontsource-variable/inter": "^5.2.8",
|
||||||
"@hey-api/openapi-ts": "0.94.4",
|
"@hey-api/openapi-ts": "0.94.4",
|
||||||
|
"@lucide/svelte": "^1.8.0",
|
||||||
"@sveltejs/adapter-node": "^5.5.4",
|
"@sveltejs/adapter-node": "^5.5.4",
|
||||||
"@sveltejs/kit": "^2.50.2",
|
"@sveltejs/kit": "^2.50.2",
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||||
"@types/node": "^22",
|
"@types/node": "^22",
|
||||||
"@vitest/browser-playwright": "^4.1.0",
|
"@vitest/browser-playwright": "^4.1.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.2",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-svelte": "^3.14.0",
|
"eslint-plugin-svelte": "^3.14.0",
|
||||||
|
|
@ -32,8 +35,12 @@
|
||||||
"playwright": "^1.58.2",
|
"playwright": "^1.58.2",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.8.1",
|
||||||
"prettier-plugin-svelte": "^3.4.1",
|
"prettier-plugin-svelte": "^3.4.1",
|
||||||
|
"shadcn-svelte": "^1.2.7",
|
||||||
"svelte": "^5.51.0",
|
"svelte": "^5.51.0",
|
||||||
"svelte-check": "^4.4.2",
|
"svelte-check": "^4.4.2",
|
||||||
|
"tailwind-merge": "^3.5.0",
|
||||||
|
"tailwind-variants": "^3.2.2",
|
||||||
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.54.0",
|
"typescript-eslint": "^8.54.0",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
|
|
@ -42,6 +49,8 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"deepl-node": "^1.24.0",
|
"deepl-node": "^1.24.0",
|
||||||
|
"jose": "^6.2.2",
|
||||||
|
"tailwindcss": "^4.2.2",
|
||||||
"valibot": "^1.3.1"
|
"valibot": "^1.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,31 @@ importers:
|
||||||
deepl-node:
|
deepl-node:
|
||||||
specifier: ^1.24.0
|
specifier: ^1.24.0
|
||||||
version: 1.24.0
|
version: 1.24.0
|
||||||
|
jose:
|
||||||
|
specifier: ^6.2.2
|
||||||
|
version: 6.2.2
|
||||||
|
tailwindcss:
|
||||||
|
specifier: ^4.2.2
|
||||||
|
version: 4.2.2
|
||||||
valibot:
|
valibot:
|
||||||
specifier: ^1.3.1
|
specifier: ^1.3.1
|
||||||
version: 1.3.1(typescript@5.9.3)
|
version: 1.3.1(typescript@5.9.3)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@eslint/compat':
|
'@eslint/compat':
|
||||||
specifier: ^2.0.2
|
specifier: latest
|
||||||
version: 2.0.3(eslint@9.39.4(jiti@2.6.1))
|
version: 2.0.3(eslint@9.39.4(jiti@2.6.1))
|
||||||
'@eslint/js':
|
'@eslint/js':
|
||||||
specifier: ^9.39.2
|
specifier: latest
|
||||||
version: 9.39.4
|
version: 9.39.4
|
||||||
|
'@fontsource-variable/inter':
|
||||||
|
specifier: ^5.2.8
|
||||||
|
version: 5.2.8
|
||||||
'@hey-api/openapi-ts':
|
'@hey-api/openapi-ts':
|
||||||
specifier: 0.94.4
|
specifier: 0.94.4
|
||||||
version: 0.94.4(typescript@5.9.3)
|
version: 0.94.4(typescript@5.9.3)
|
||||||
|
'@lucide/svelte':
|
||||||
|
specifier: ^1.8.0
|
||||||
|
version: 1.8.0(svelte@5.54.1)
|
||||||
'@sveltejs/adapter-node':
|
'@sveltejs/adapter-node':
|
||||||
specifier: ^5.5.4
|
specifier: ^5.5.4
|
||||||
version: 5.5.4(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.54.1)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)))(svelte@5.54.1)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)))
|
version: 5.5.4(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.54.1)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)))(svelte@5.54.1)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)))
|
||||||
|
|
@ -39,6 +51,9 @@ importers:
|
||||||
'@vitest/browser-playwright':
|
'@vitest/browser-playwright':
|
||||||
specifier: ^4.1.0
|
specifier: ^4.1.0
|
||||||
version: 4.1.0(playwright@1.58.2)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1))(vitest@4.1.0)
|
version: 4.1.0(playwright@1.58.2)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1))(vitest@4.1.0)
|
||||||
|
clsx:
|
||||||
|
specifier: ^2.1.1
|
||||||
|
version: 2.1.1
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9.39.2
|
specifier: ^9.39.2
|
||||||
version: 9.39.4(jiti@2.6.1)
|
version: 9.39.4(jiti@2.6.1)
|
||||||
|
|
@ -60,12 +75,24 @@ importers:
|
||||||
prettier-plugin-svelte:
|
prettier-plugin-svelte:
|
||||||
specifier: ^3.4.1
|
specifier: ^3.4.1
|
||||||
version: 3.5.1(prettier@3.8.1)(svelte@5.54.1)
|
version: 3.5.1(prettier@3.8.1)(svelte@5.54.1)
|
||||||
|
shadcn-svelte:
|
||||||
|
specifier: ^1.2.7
|
||||||
|
version: 1.2.7(svelte@5.54.1)
|
||||||
svelte:
|
svelte:
|
||||||
specifier: ^5.51.0
|
specifier: ^5.51.0
|
||||||
version: 5.54.1
|
version: 5.54.1
|
||||||
svelte-check:
|
svelte-check:
|
||||||
specifier: ^4.4.2
|
specifier: ^4.4.2
|
||||||
version: 4.4.5(picomatch@4.0.3)(svelte@5.54.1)(typescript@5.9.3)
|
version: 4.4.5(picomatch@4.0.3)(svelte@5.54.1)(typescript@5.9.3)
|
||||||
|
tailwind-merge:
|
||||||
|
specifier: ^3.5.0
|
||||||
|
version: 3.5.0
|
||||||
|
tailwind-variants:
|
||||||
|
specifier: ^3.2.2
|
||||||
|
version: 3.2.2(tailwind-merge@3.5.0)(tailwindcss@4.2.2)
|
||||||
|
tw-animate-css:
|
||||||
|
specifier: ^1.4.0
|
||||||
|
version: 1.4.0
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.9.3
|
specifier: ^5.9.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
|
@ -294,6 +321,9 @@ packages:
|
||||||
resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
|
resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
|
'@fontsource-variable/inter@5.2.8':
|
||||||
|
resolution: {integrity: sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==}
|
||||||
|
|
||||||
'@hey-api/codegen-core@0.7.4':
|
'@hey-api/codegen-core@0.7.4':
|
||||||
resolution: {integrity: sha512-DGd9yeSQzflOWO3Y5mt1GRXkXH9O/yIMgbxPjwLI3jwu/3nAjoXXD26lEeFb6tclYlg0JAqTIs5d930G/qxHeA==}
|
resolution: {integrity: sha512-DGd9yeSQzflOWO3Y5mt1GRXkXH9O/yIMgbxPjwLI3jwu/3nAjoXXD26lEeFb6tclYlg0JAqTIs5d930G/qxHeA==}
|
||||||
engines: {node: '>=20.19.0'}
|
engines: {node: '>=20.19.0'}
|
||||||
|
|
@ -351,6 +381,11 @@ packages:
|
||||||
'@jsdevtools/ono@7.1.3':
|
'@jsdevtools/ono@7.1.3':
|
||||||
resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==}
|
resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==}
|
||||||
|
|
||||||
|
'@lucide/svelte@1.8.0':
|
||||||
|
resolution: {integrity: sha512-+zYQUKqEOVP5lxbGmxL1OVgGMQtRK91eIJ0bR+3Cr1ts4oQEsQfxyzzd5X47psJlblAuGFrl2xm4YuATjR9oaA==}
|
||||||
|
peerDependencies:
|
||||||
|
svelte: ^5
|
||||||
|
|
||||||
'@playwright/test@1.58.2':
|
'@playwright/test@1.58.2':
|
||||||
resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==}
|
resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
@ -1198,6 +1233,9 @@ packages:
|
||||||
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
|
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
jose@6.2.2:
|
||||||
|
resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==}
|
||||||
|
|
||||||
js-yaml@4.1.1:
|
js-yaml@4.1.1:
|
||||||
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
@ -1467,6 +1505,12 @@ packages:
|
||||||
set-cookie-parser@3.1.0:
|
set-cookie-parser@3.1.0:
|
||||||
resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==}
|
resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==}
|
||||||
|
|
||||||
|
shadcn-svelte@1.2.7:
|
||||||
|
resolution: {integrity: sha512-mWuQk4H4gtV+J2wJQ7nEPKNnB/v86AALFryZU0SSM7ChHmJJMZ1kH+qIuxYKrXm9vOOOcSWHRsWzPDB71DnjYA==}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
svelte: ^5.0.0
|
||||||
|
|
||||||
shebang-command@2.0.0:
|
shebang-command@2.0.0:
|
||||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
@ -1525,6 +1569,22 @@ packages:
|
||||||
resolution: {integrity: sha512-ow8tncN097Ty8U1H+C3bM1xNlsCbnO2UZeN0lWBnv8f3jKho7QTTQ2LWbMXrPQDodLjH91n4kpNnLolyRhVE6A==}
|
resolution: {integrity: sha512-ow8tncN097Ty8U1H+C3bM1xNlsCbnO2UZeN0lWBnv8f3jKho7QTTQ2LWbMXrPQDodLjH91n4kpNnLolyRhVE6A==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
tailwind-merge@3.5.0:
|
||||||
|
resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==}
|
||||||
|
|
||||||
|
tailwind-variants@3.2.2:
|
||||||
|
resolution: {integrity: sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==}
|
||||||
|
engines: {node: '>=16.x', pnpm: '>=7.x'}
|
||||||
|
peerDependencies:
|
||||||
|
tailwind-merge: '>=3.0.0'
|
||||||
|
tailwindcss: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
tailwind-merge:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
tailwindcss@4.2.2:
|
||||||
|
resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==}
|
||||||
|
|
||||||
tinybench@2.9.0:
|
tinybench@2.9.0:
|
||||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||||
|
|
||||||
|
|
@ -1550,6 +1610,9 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4'
|
typescript: '>=4.8.4'
|
||||||
|
|
||||||
|
tw-animate-css@1.4.0:
|
||||||
|
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
|
||||||
|
|
||||||
type-check@0.4.0:
|
type-check@0.4.0:
|
||||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
@ -1855,6 +1918,8 @@ snapshots:
|
||||||
'@eslint/core': 0.17.0
|
'@eslint/core': 0.17.0
|
||||||
levn: 0.4.1
|
levn: 0.4.1
|
||||||
|
|
||||||
|
'@fontsource-variable/inter@5.2.8': {}
|
||||||
|
|
||||||
'@hey-api/codegen-core@0.7.4':
|
'@hey-api/codegen-core@0.7.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@hey-api/types': 0.1.4
|
'@hey-api/types': 0.1.4
|
||||||
|
|
@ -1930,6 +1995,10 @@ snapshots:
|
||||||
|
|
||||||
'@jsdevtools/ono@7.1.3': {}
|
'@jsdevtools/ono@7.1.3': {}
|
||||||
|
|
||||||
|
'@lucide/svelte@1.8.0(svelte@5.54.1)':
|
||||||
|
dependencies:
|
||||||
|
svelte: 5.54.1
|
||||||
|
|
||||||
'@playwright/test@1.58.2':
|
'@playwright/test@1.58.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
playwright: 1.58.2
|
playwright: 1.58.2
|
||||||
|
|
@ -2775,6 +2844,8 @@ snapshots:
|
||||||
|
|
||||||
jiti@2.6.1: {}
|
jiti@2.6.1: {}
|
||||||
|
|
||||||
|
jose@6.2.2: {}
|
||||||
|
|
||||||
js-yaml@4.1.1:
|
js-yaml@4.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
argparse: 2.0.1
|
argparse: 2.0.1
|
||||||
|
|
@ -3015,6 +3086,14 @@ snapshots:
|
||||||
|
|
||||||
set-cookie-parser@3.1.0: {}
|
set-cookie-parser@3.1.0: {}
|
||||||
|
|
||||||
|
shadcn-svelte@1.2.7(svelte@5.54.1):
|
||||||
|
dependencies:
|
||||||
|
commander: 14.0.3
|
||||||
|
node-fetch-native: 1.6.7
|
||||||
|
postcss: 8.5.8
|
||||||
|
svelte: 5.54.1
|
||||||
|
tailwind-merge: 3.5.0
|
||||||
|
|
||||||
shebang-command@2.0.0:
|
shebang-command@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
shebang-regex: 3.0.0
|
shebang-regex: 3.0.0
|
||||||
|
|
@ -3086,6 +3165,16 @@ snapshots:
|
||||||
magic-string: 0.30.21
|
magic-string: 0.30.21
|
||||||
zimmerframe: 1.1.4
|
zimmerframe: 1.1.4
|
||||||
|
|
||||||
|
tailwind-merge@3.5.0: {}
|
||||||
|
|
||||||
|
tailwind-variants@3.2.2(tailwind-merge@3.5.0)(tailwindcss@4.2.2):
|
||||||
|
dependencies:
|
||||||
|
tailwindcss: 4.2.2
|
||||||
|
optionalDependencies:
|
||||||
|
tailwind-merge: 3.5.0
|
||||||
|
|
||||||
|
tailwindcss@4.2.2: {}
|
||||||
|
|
||||||
tinybench@2.9.0: {}
|
tinybench@2.9.0: {}
|
||||||
|
|
||||||
tinyexec@1.0.4: {}
|
tinyexec@1.0.4: {}
|
||||||
|
|
@ -3103,6 +3192,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
|
tw-animate-css@1.4.0: {}
|
||||||
|
|
||||||
type-check@0.4.0:
|
type-check@0.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
prelude-ls: 1.2.1
|
prelude-ls: 1.2.1
|
||||||
|
|
|
||||||
|
|
@ -1,405 +1,15 @@
|
||||||
/* ==========================================================================
|
@import "tw-animate-css";
|
||||||
Design System: The Editorial Stillness
|
@import "shadcn-svelte/tailwind.css";
|
||||||
========================================================================== */
|
@import "@fontsource-variable/inter";
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
@layer base {
|
||||||
Fonts
|
* {
|
||||||
-------------------------------------------------------------------------- */
|
@apply border-border outline-ring/50;
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: archivo; /* set name */
|
|
||||||
src: url('/fonts/Archivo-Medium.woff2'); /* url of the font */
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: archivo; /* set name */
|
|
||||||
src: url('/fonts/Archivo-Regular.woff2'); /* url of the font */
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
|
||||||
Design Tokens
|
|
||||||
-------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
:root {
|
|
||||||
/* --- Color: Primary --- */
|
|
||||||
--color-primary: #516356;
|
|
||||||
--color-on-primary: #e9fded;
|
|
||||||
--color-primary-container: #c8d8c8;
|
|
||||||
--color-on-primary-container: #2f342e;
|
|
||||||
|
|
||||||
/* --- Color: Secondary --- */
|
|
||||||
--color-secondary-container: #dde4de;
|
|
||||||
--color-on-secondary-container: #2f342e;
|
|
||||||
|
|
||||||
/* --- Color: Surface Hierarchy (light → dark = elevated → recessed) --- */
|
|
||||||
--color-surface-container-lowest: #ffffff; /* most elevated */
|
|
||||||
--color-surface-container-low: #f4f4ef;
|
|
||||||
--color-surface: #faf9f5; /* base */
|
|
||||||
--color-surface-container: #eeede9;
|
|
||||||
--color-surface-container-high: #e8e8e3;
|
|
||||||
--color-surface-container-highest: #e2e1dd;
|
|
||||||
--color-surface-dim: #d6dcd2; /* recessed utility */
|
|
||||||
|
|
||||||
/* --- Color: On-Surface --- */
|
|
||||||
--color-on-surface: #2f342e; /* replaces pure black */
|
|
||||||
--color-on-surface-variant: #5c605b;
|
|
||||||
|
|
||||||
/* --- Color: Outline --- */
|
|
||||||
--color-outline: #8c908b;
|
|
||||||
--color-outline-variant: #afb3ac; /* use at 20% opacity as "ghost border" */
|
|
||||||
|
|
||||||
/* --- Typography: Font Families --- */
|
|
||||||
--font-display: 'archivo', sans-serif;
|
|
||||||
--font-body: 'Newsreader', serif;
|
|
||||||
--font-label: 'Inter', sans-serif;
|
|
||||||
|
|
||||||
/* --- Typography: Scale --- */
|
|
||||||
--text-display-lg: 3.5rem; /* article titles */
|
|
||||||
--text-display-md: 2.75rem;
|
|
||||||
--text-display-sm: 2.25rem;
|
|
||||||
--text-headline-lg: 1.875rem;
|
|
||||||
--text-headline-md: 1.5rem;
|
|
||||||
--text-headline-sm: 1.25rem;
|
|
||||||
--text-title-lg: 1.125rem;
|
|
||||||
--text-title-md: 1rem;
|
|
||||||
--text-body-xl: 1.25rem; /* long-form reading standard */
|
|
||||||
--text-body-lg: 1rem;
|
|
||||||
--text-body-md: 0.9375rem;
|
|
||||||
--text-body-sm: 0.875rem;
|
|
||||||
--text-label-lg: 0.875rem;
|
|
||||||
--text-label-md: 0.75rem; /* metadata, all-caps */
|
|
||||||
--text-label-sm: 0.6875rem;
|
|
||||||
|
|
||||||
/* --- Typography: Weights --- */
|
|
||||||
--weight-light: 300;
|
|
||||||
--weight-regular: 400;
|
|
||||||
--weight-medium: 500;
|
|
||||||
--weight-semibold: 600;
|
|
||||||
--weight-bold: 700;
|
|
||||||
|
|
||||||
/* --- Typography: Line Height --- */
|
|
||||||
--leading-tight: 1.2;
|
|
||||||
--leading-snug: 1.375;
|
|
||||||
--leading-normal: 1.5;
|
|
||||||
--leading-relaxed: 1.6; /* "Digital Paper" body text minimum */
|
|
||||||
--leading-loose: 1.8;
|
|
||||||
|
|
||||||
/* --- Typography: Letter Spacing --- */
|
|
||||||
--tracking-tight: -0.025em;
|
|
||||||
--tracking-normal: 0em;
|
|
||||||
--tracking-wide: 0.05rem; /* label-md metadata */
|
|
||||||
--tracking-wider: 0.08rem;
|
|
||||||
|
|
||||||
/* --- Spacing Scale (base-4) --- */
|
|
||||||
--space-1: 0.25rem; /* 4px */
|
|
||||||
--space-2: 0.5rem; /* 8px */
|
|
||||||
--space-3: 1rem; /* 16px — list item separation */
|
|
||||||
--space-4: 1.4rem; /* ~22px — list item group separation */
|
|
||||||
--space-5: 1.75rem; /* 28px */
|
|
||||||
--space-6: 2rem; /* 32px */
|
|
||||||
--space-8: 3rem; /* 48px */
|
|
||||||
--space-10: 4rem; /* 64px */
|
|
||||||
--space-12: 4.5rem; /* 72px */
|
|
||||||
--space-16: 5.5rem; /* 88px — top-of-page breathability */
|
|
||||||
|
|
||||||
/* --- Border Radius --- */
|
|
||||||
--radius-xs: 0.125rem;
|
|
||||||
--radius-sm: 0.25rem;
|
|
||||||
--radius-md: 0.375rem; /* primary button */
|
|
||||||
--radius-lg: 0.75rem;
|
|
||||||
--radius-xl: 1.25rem;
|
|
||||||
--radius-full: 9999px;
|
|
||||||
|
|
||||||
/* --- Elevation: Ambient "Tonal Shadow" --- */
|
|
||||||
--shadow-tonal-sm: 0 4px 16px 0 color-mix(in srgb, var(--color-on-surface) 5%, transparent);
|
|
||||||
--shadow-tonal-md: 0 8px 32px 0 color-mix(in srgb, var(--color-on-surface) 5%, transparent);
|
|
||||||
|
|
||||||
/* --- Motion --- */
|
|
||||||
--duration-fast: 100ms;
|
|
||||||
--duration-normal: 200ms;
|
|
||||||
--duration-slow: 400ms;
|
|
||||||
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
|
||||||
|
|
||||||
/* --- Glass / Frosted Effect --- */
|
|
||||||
--glass-bg: color-mix(in srgb, var(--color-surface) 80%, transparent);
|
|
||||||
--glass-blur: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
|
||||||
Reset & Base
|
|
||||||
-------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-size: 16px;
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: var(--font-body);
|
@apply bg-background text-foreground;
|
||||||
font-size: var(--text-body-lg);
|
|
||||||
font-weight: var(--weight-regular);
|
|
||||||
line-height: var(--leading-relaxed);
|
|
||||||
color: var(--color-on-surface);
|
|
||||||
background-color: var(--color-surface);
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
}
|
||||||
|
html {
|
||||||
/* --------------------------------------------------------------------------
|
@apply font-sans;
|
||||||
Typography Utilities
|
|
||||||
-------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
.display-lg {
|
|
||||||
font-family: var(--font-display);
|
|
||||||
font-size: var(--text-display-lg);
|
|
||||||
font-weight: var(--weight-bold);
|
|
||||||
line-height: var(--leading-tight);
|
|
||||||
letter-spacing: var(--tracking-tight);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.headline-md {
|
|
||||||
font-family: var(--font-display);
|
|
||||||
font-size: var(--text-headline-md);
|
|
||||||
font-weight: var(--weight-semibold);
|
|
||||||
line-height: var(--leading-snug);
|
|
||||||
}
|
|
||||||
|
|
||||||
.label-md {
|
|
||||||
font-family: var(--font-label);
|
|
||||||
font-size: var(--text-label-md);
|
|
||||||
font-weight: var(--weight-medium);
|
|
||||||
letter-spacing: var(--tracking-wide);
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
|
||||||
color: var(--color-primary);
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: var(--weight-medium);
|
|
||||||
transition: opacity var(--duration-fast) var(--ease-standard);
|
|
||||||
}
|
|
||||||
|
|
||||||
.link:hover {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
|
||||||
Component: Form
|
|
||||||
-------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
.form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-header {
|
|
||||||
margin-bottom: var(--space-6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-eyebrow {
|
|
||||||
font-family: var(--font-label);
|
|
||||||
font-size: var(--text-label-md);
|
|
||||||
font-weight: var(--weight-medium);
|
|
||||||
letter-spacing: var(--tracking-wide);
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--color-on-surface-variant);
|
|
||||||
margin-bottom: var(--space-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-title {
|
|
||||||
font-family: var(--font-display);
|
|
||||||
font-size: var(--text-headline-lg);
|
|
||||||
font-weight: var(--weight-semibold);
|
|
||||||
line-height: var(--leading-snug);
|
|
||||||
color: var(--color-on-surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-footer {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-2);
|
|
||||||
margin-top: var(--space-6);
|
|
||||||
font-family: var(--font-label);
|
|
||||||
font-size: var(--text-body-sm);
|
|
||||||
color: var(--color-on-surface-variant);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
|
||||||
Component: Button
|
|
||||||
-------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: var(--space-2);
|
|
||||||
padding: var(--space-2) var(--space-4);
|
|
||||||
border: none;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
font-family: var(--font-display);
|
|
||||||
font-size: var(--text-label-lg);
|
|
||||||
font-weight: var(--weight-medium);
|
|
||||||
letter-spacing: var(--tracking-wide);
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none;
|
|
||||||
transition:
|
|
||||||
opacity var(--duration-normal) var(--ease-standard),
|
|
||||||
background-color var(--duration-normal) var(--ease-standard);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background-color: var(--color-primary);
|
|
||||||
color: var(--color-on-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover {
|
|
||||||
opacity: 0.88;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary {
|
|
||||||
background-color: var(--color-secondary-container);
|
|
||||||
color: var(--color-on-secondary-container);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-ghost {
|
|
||||||
background: none;
|
|
||||||
color: var(--color-primary);
|
|
||||||
padding-inline: var(--space-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
|
||||||
Component: Input
|
|
||||||
-------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
.field {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-label {
|
|
||||||
font-family: var(--font-label);
|
|
||||||
font-size: var(--text-label-md);
|
|
||||||
font-weight: var(--weight-medium);
|
|
||||||
letter-spacing: var(--tracking-wide);
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--color-on-surface-variant);
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-input {
|
|
||||||
background-color: var(--color-surface-container-high);
|
|
||||||
color: var(--color-on-surface);
|
|
||||||
border: none;
|
|
||||||
border-bottom: 1px solid color-mix(in srgb, var(--color-outline-variant) 20%, transparent);
|
|
||||||
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
|
||||||
padding: var(--space-2) var(--space-3);
|
|
||||||
font-family: var(--font-body);
|
|
||||||
font-size: var(--text-body-lg);
|
|
||||||
line-height: var(--leading-normal);
|
|
||||||
outline: none;
|
|
||||||
width: 100%;
|
|
||||||
transition: border-color var(--duration-fast) var(--ease-standard);
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-disabled {
|
|
||||||
font-family: var(--font-body);
|
|
||||||
font-size: var(--text-body-lg);
|
|
||||||
color: var(--color-on-surface-variant);
|
|
||||||
padding: var(--space-2) var(--space-3);
|
|
||||||
background-color: var(--color-surface-container);
|
|
||||||
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
|
||||||
border-bottom: 1px solid color-mix(in srgb, var(--color-outline-variant) 20%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-input::placeholder {
|
|
||||||
color: var(--color-outline-variant);
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-input:focus {
|
|
||||||
border-bottom: 2px solid var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-select {
|
|
||||||
background-color: var(--color-surface-container-high);
|
|
||||||
color: var(--color-on-surface);
|
|
||||||
border: none;
|
|
||||||
border-bottom: 1px solid color-mix(in srgb, var(--color-outline-variant) 20%, transparent);
|
|
||||||
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
|
||||||
padding: var(--space-2) var(--space-8) var(--space-2) var(--space-3);
|
|
||||||
font-family: var(--font-body);
|
|
||||||
font-size: var(--text-body-lg);
|
|
||||||
line-height: var(--leading-normal);
|
|
||||||
outline: none;
|
|
||||||
width: 100%;
|
|
||||||
cursor: pointer;
|
|
||||||
appearance: none;
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%235c605b' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: right var(--space-3) center;
|
|
||||||
transition: border-color var(--duration-fast) var(--ease-standard);
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-select:focus {
|
|
||||||
border-bottom: 2px solid var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-textarea {
|
|
||||||
background-color: var(--color-surface-container-high);
|
|
||||||
color: var(--color-on-surface);
|
|
||||||
border: none;
|
|
||||||
border-bottom: 1px solid color-mix(in srgb, var(--color-outline-variant) 20%, transparent);
|
|
||||||
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
|
||||||
padding: var(--space-2) var(--space-3);
|
|
||||||
font-family: var(--font-body);
|
|
||||||
font-size: var(--text-body-lg);
|
|
||||||
line-height: var(--leading-relaxed);
|
|
||||||
outline: none;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 14rem;
|
|
||||||
resize: vertical;
|
|
||||||
transition: border-color var(--duration-fast) var(--ease-standard);
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-textarea::placeholder {
|
|
||||||
color: var(--color-outline-variant);
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-textarea:focus {
|
|
||||||
border-bottom: 2px solid var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-hint {
|
|
||||||
font-family: var(--font-label);
|
|
||||||
font-size: var(--text-label-md);
|
|
||||||
color: var(--color-on-surface-variant);
|
|
||||||
margin-top: var(--space-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
|
||||||
Component: Alert
|
|
||||||
-------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
.alert {
|
|
||||||
padding: var(--space-3);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
font-family: var(--font-label);
|
|
||||||
font-size: var(--text-body-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-error {
|
|
||||||
background-color: color-mix(in srgb, #b3261e 10%, var(--color-surface));
|
|
||||||
color: #b3261e;
|
|
||||||
border-left: 3px solid #b3261e;
|
|
||||||
}
|
}
|
||||||
1
frontend/src/app.d.ts
vendored
1
frontend/src/app.d.ts
vendored
|
|
@ -8,6 +8,7 @@ declare global {
|
||||||
interface Locals {
|
interface Locals {
|
||||||
apiClient?: ApiClient;
|
apiClient?: ApiClient;
|
||||||
authToken: string | null;
|
authToken: string | null;
|
||||||
|
isAdmin: boolean;
|
||||||
}
|
}
|
||||||
// interface PageData {}
|
// interface PageData {}
|
||||||
// interface PageState {}
|
// interface PageState {}
|
||||||
|
|
|
||||||
13
frontend/src/lib/utils.ts
Normal file
13
frontend/src/lib/utils.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { clsx, type ClassValue } from "clsx";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, "children"> : T;
|
||||||
|
export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
|
||||||
|
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
|
||||||
405
frontend/src/styles.css
Normal file
405
frontend/src/styles.css
Normal file
|
|
@ -0,0 +1,405 @@
|
||||||
|
/* ==========================================================================
|
||||||
|
Design System: The Editorial Stillness
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------
|
||||||
|
Fonts
|
||||||
|
-------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: archivo; /* set name */
|
||||||
|
src: url('/fonts/Archivo-Medium.woff2'); /* url of the font */
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: archivo; /* set name */
|
||||||
|
src: url('/fonts/Archivo-Regular.woff2'); /* url of the font */
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------
|
||||||
|
Design Tokens
|
||||||
|
-------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* --- Color: Primary --- */
|
||||||
|
--color-primary: #516356;
|
||||||
|
--color-on-primary: #e9fded;
|
||||||
|
--color-primary-container: #c8d8c8;
|
||||||
|
--color-on-primary-container: #2f342e;
|
||||||
|
|
||||||
|
/* --- Color: Secondary --- */
|
||||||
|
--color-secondary-container: #dde4de;
|
||||||
|
--color-on-secondary-container: #2f342e;
|
||||||
|
|
||||||
|
/* --- Color: Surface Hierarchy (light → dark = elevated → recessed) --- */
|
||||||
|
--color-surface-container-lowest: #ffffff; /* most elevated */
|
||||||
|
--color-surface-container-low: #f4f4ef;
|
||||||
|
--color-surface: #faf9f5; /* base */
|
||||||
|
--color-surface-container: #eeede9;
|
||||||
|
--color-surface-container-high: #e8e8e3;
|
||||||
|
--color-surface-container-highest: #e2e1dd;
|
||||||
|
--color-surface-dim: #d6dcd2; /* recessed utility */
|
||||||
|
|
||||||
|
/* --- Color: On-Surface --- */
|
||||||
|
--color-on-surface: #2f342e; /* replaces pure black */
|
||||||
|
--color-on-surface-variant: #5c605b;
|
||||||
|
|
||||||
|
/* --- Color: Outline --- */
|
||||||
|
--color-outline: #8c908b;
|
||||||
|
--color-outline-variant: #afb3ac; /* use at 20% opacity as "ghost border" */
|
||||||
|
|
||||||
|
/* --- Typography: Font Families --- */
|
||||||
|
--font-display: 'archivo', sans-serif;
|
||||||
|
--font-body: 'Newsreader', serif;
|
||||||
|
--font-label: 'Inter', sans-serif;
|
||||||
|
|
||||||
|
/* --- Typography: Scale --- */
|
||||||
|
--text-display-lg: 3.5rem; /* article titles */
|
||||||
|
--text-display-md: 2.75rem;
|
||||||
|
--text-display-sm: 2.25rem;
|
||||||
|
--text-headline-lg: 1.875rem;
|
||||||
|
--text-headline-md: 1.5rem;
|
||||||
|
--text-headline-sm: 1.25rem;
|
||||||
|
--text-title-lg: 1.125rem;
|
||||||
|
--text-title-md: 1rem;
|
||||||
|
--text-body-xl: 1.25rem; /* long-form reading standard */
|
||||||
|
--text-body-lg: 1rem;
|
||||||
|
--text-body-md: 0.9375rem;
|
||||||
|
--text-body-sm: 0.875rem;
|
||||||
|
--text-label-lg: 0.875rem;
|
||||||
|
--text-label-md: 0.75rem; /* metadata, all-caps */
|
||||||
|
--text-label-sm: 0.6875rem;
|
||||||
|
|
||||||
|
/* --- Typography: Weights --- */
|
||||||
|
--weight-light: 300;
|
||||||
|
--weight-regular: 400;
|
||||||
|
--weight-medium: 500;
|
||||||
|
--weight-semibold: 600;
|
||||||
|
--weight-bold: 700;
|
||||||
|
|
||||||
|
/* --- Typography: Line Height --- */
|
||||||
|
--leading-tight: 1.2;
|
||||||
|
--leading-snug: 1.375;
|
||||||
|
--leading-normal: 1.5;
|
||||||
|
--leading-relaxed: 1.6; /* "Digital Paper" body text minimum */
|
||||||
|
--leading-loose: 1.8;
|
||||||
|
|
||||||
|
/* --- Typography: Letter Spacing --- */
|
||||||
|
--tracking-tight: -0.025em;
|
||||||
|
--tracking-normal: 0em;
|
||||||
|
--tracking-wide: 0.05rem; /* label-md metadata */
|
||||||
|
--tracking-wider: 0.08rem;
|
||||||
|
|
||||||
|
/* --- Spacing Scale (base-4) --- */
|
||||||
|
--space-1: 0.25rem; /* 4px */
|
||||||
|
--space-2: 0.5rem; /* 8px */
|
||||||
|
--space-3: 1rem; /* 16px — list item separation */
|
||||||
|
--space-4: 1.4rem; /* ~22px — list item group separation */
|
||||||
|
--space-5: 1.75rem; /* 28px */
|
||||||
|
--space-6: 2rem; /* 32px */
|
||||||
|
--space-8: 3rem; /* 48px */
|
||||||
|
--space-10: 4rem; /* 64px */
|
||||||
|
--space-12: 4.5rem; /* 72px */
|
||||||
|
--space-16: 5.5rem; /* 88px — top-of-page breathability */
|
||||||
|
|
||||||
|
/* --- Border Radius --- */
|
||||||
|
--radius-xs: 0.125rem;
|
||||||
|
--radius-sm: 0.25rem;
|
||||||
|
--radius-md: 0.375rem; /* primary button */
|
||||||
|
--radius-lg: 0.75rem;
|
||||||
|
--radius-xl: 1.25rem;
|
||||||
|
--radius-full: 9999px;
|
||||||
|
|
||||||
|
/* --- Elevation: Ambient "Tonal Shadow" --- */
|
||||||
|
--shadow-tonal-sm: 0 4px 16px 0 color-mix(in srgb, var(--color-on-surface) 5%, transparent);
|
||||||
|
--shadow-tonal-md: 0 8px 32px 0 color-mix(in srgb, var(--color-on-surface) 5%, transparent);
|
||||||
|
|
||||||
|
/* --- Motion --- */
|
||||||
|
--duration-fast: 100ms;
|
||||||
|
--duration-normal: 200ms;
|
||||||
|
--duration-slow: 400ms;
|
||||||
|
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
||||||
|
|
||||||
|
/* --- Glass / Frosted Effect --- */
|
||||||
|
--glass-bg: color-mix(in srgb, var(--color-surface) 80%, transparent);
|
||||||
|
--glass-blur: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------
|
||||||
|
Reset & Base
|
||||||
|
-------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 16px;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: var(--text-body-lg);
|
||||||
|
font-weight: var(--weight-regular);
|
||||||
|
line-height: var(--leading-relaxed);
|
||||||
|
color: var(--color-on-surface);
|
||||||
|
background-color: var(--color-surface);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------
|
||||||
|
Typography Utilities
|
||||||
|
-------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.display-lg {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: var(--text-display-lg);
|
||||||
|
font-weight: var(--weight-bold);
|
||||||
|
line-height: var(--leading-tight);
|
||||||
|
letter-spacing: var(--tracking-tight);
|
||||||
|
}
|
||||||
|
|
||||||
|
.headline-md {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: var(--text-headline-md);
|
||||||
|
font-weight: var(--weight-semibold);
|
||||||
|
line-height: var(--leading-snug);
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-md {
|
||||||
|
font-family: var(--font-label);
|
||||||
|
font-size: var(--text-label-md);
|
||||||
|
font-weight: var(--weight-medium);
|
||||||
|
letter-spacing: var(--tracking-wide);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: var(--color-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: var(--weight-medium);
|
||||||
|
transition: opacity var(--duration-fast) var(--ease-standard);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------
|
||||||
|
Component: Form
|
||||||
|
-------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-header {
|
||||||
|
margin-bottom: var(--space-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-eyebrow {
|
||||||
|
font-family: var(--font-label);
|
||||||
|
font-size: var(--text-label-md);
|
||||||
|
font-weight: var(--weight-medium);
|
||||||
|
letter-spacing: var(--tracking-wide);
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--color-on-surface-variant);
|
||||||
|
margin-bottom: var(--space-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-title {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: var(--text-headline-lg);
|
||||||
|
font-weight: var(--weight-semibold);
|
||||||
|
line-height: var(--leading-snug);
|
||||||
|
color: var(--color-on-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
margin-top: var(--space-6);
|
||||||
|
font-family: var(--font-label);
|
||||||
|
font-size: var(--text-body-sm);
|
||||||
|
color: var(--color-on-surface-variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------
|
||||||
|
Component: Button
|
||||||
|
-------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
padding: var(--space-2) var(--space-4);
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: var(--text-label-lg);
|
||||||
|
font-weight: var(--weight-medium);
|
||||||
|
letter-spacing: var(--tracking-wide);
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
transition:
|
||||||
|
opacity var(--duration-normal) var(--ease-standard),
|
||||||
|
background-color var(--duration-normal) var(--ease-standard);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: var(--color-on-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
opacity: 0.88;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background-color: var(--color-secondary-container);
|
||||||
|
color: var(--color-on-secondary-container);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ghost {
|
||||||
|
background: none;
|
||||||
|
color: var(--color-primary);
|
||||||
|
padding-inline: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------
|
||||||
|
Component: Input
|
||||||
|
-------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-label {
|
||||||
|
font-family: var(--font-label);
|
||||||
|
font-size: var(--text-label-md);
|
||||||
|
font-weight: var(--weight-medium);
|
||||||
|
letter-spacing: var(--tracking-wide);
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--color-on-surface-variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-input {
|
||||||
|
background-color: var(--color-surface-container-high);
|
||||||
|
color: var(--color-on-surface);
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid color-mix(in srgb, var(--color-outline-variant) 20%, transparent);
|
||||||
|
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
||||||
|
padding: var(--space-2) var(--space-3);
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: var(--text-body-lg);
|
||||||
|
line-height: var(--leading-normal);
|
||||||
|
outline: none;
|
||||||
|
width: 100%;
|
||||||
|
transition: border-color var(--duration-fast) var(--ease-standard);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-disabled {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: var(--text-body-lg);
|
||||||
|
color: var(--color-on-surface-variant);
|
||||||
|
padding: var(--space-2) var(--space-3);
|
||||||
|
background-color: var(--color-surface-container);
|
||||||
|
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
||||||
|
border-bottom: 1px solid color-mix(in srgb, var(--color-outline-variant) 20%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-input::placeholder {
|
||||||
|
color: var(--color-outline-variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-input:focus {
|
||||||
|
border-bottom: 2px solid var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-select {
|
||||||
|
background-color: var(--color-surface-container-high);
|
||||||
|
color: var(--color-on-surface);
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid color-mix(in srgb, var(--color-outline-variant) 20%, transparent);
|
||||||
|
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
||||||
|
padding: var(--space-2) var(--space-8) var(--space-2) var(--space-3);
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: var(--text-body-lg);
|
||||||
|
line-height: var(--leading-normal);
|
||||||
|
outline: none;
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%235c605b' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right var(--space-3) center;
|
||||||
|
transition: border-color var(--duration-fast) var(--ease-standard);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-select:focus {
|
||||||
|
border-bottom: 2px solid var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-textarea {
|
||||||
|
background-color: var(--color-surface-container-high);
|
||||||
|
color: var(--color-on-surface);
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid color-mix(in srgb, var(--color-outline-variant) 20%, transparent);
|
||||||
|
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
||||||
|
padding: var(--space-2) var(--space-3);
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: var(--text-body-lg);
|
||||||
|
line-height: var(--leading-relaxed);
|
||||||
|
outline: none;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 14rem;
|
||||||
|
resize: vertical;
|
||||||
|
transition: border-color var(--duration-fast) var(--ease-standard);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-textarea::placeholder {
|
||||||
|
color: var(--color-outline-variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-textarea:focus {
|
||||||
|
border-bottom: 2px solid var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-hint {
|
||||||
|
font-family: var(--font-label);
|
||||||
|
font-size: var(--text-label-md);
|
||||||
|
color: var(--color-on-surface-variant);
|
||||||
|
margin-top: var(--space-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------
|
||||||
|
Component: Alert
|
||||||
|
-------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
padding: var(--space-3);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-family: var(--font-label);
|
||||||
|
font-size: var(--text-body-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-error {
|
||||||
|
background-color: color-mix(in srgb, #b3261e 10%, var(--color-surface));
|
||||||
|
color: #b3261e;
|
||||||
|
border-left: 3px solid #b3261e;
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,8 @@ const config = {
|
||||||
remoteFunctions: true
|
remoteFunctions: true
|
||||||
},
|
},
|
||||||
alias: {
|
alias: {
|
||||||
'@client': 'src/client/client.gen.ts'
|
'@client': 'src/client/client.gen.ts',
|
||||||
|
'@/**': './src/lib/*',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue