Design system foundation — Tailwind 4 theme, CSS tokens, fonts #31
18
frontend/package-lock.json
generated
18
frontend/package-lock.json
generated
@@ -19,6 +19,7 @@
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/svelte": "^5.3.1",
|
||||
"@types/node": "^25.5.0",
|
||||
"@vitest/ui": "^4.1.2",
|
||||
"jsdom": "^29.0.1",
|
||||
"openapi-typescript": "^7.13.0",
|
||||
@@ -1847,6 +1848,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
|
||||
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
|
||||
@@ -3737,6 +3748,13 @@
|
||||
"node": ">=20.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/uri-js-replace": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz",
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/svelte": "^5.3.1",
|
||||
"@types/node": "^25.5.0",
|
||||
"@vitest/ui": "^4.1.2",
|
||||
"jsdom": "^29.0.1",
|
||||
"openapi-typescript": "^7.13.0",
|
||||
|
||||
89
frontend/src/app.css
Normal file
89
frontend/src/app.css
Normal file
@@ -0,0 +1,89 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
@theme {
|
||||
/* ── Fonts ─────────────────────────────────────────────────────── */
|
||||
--font-display: 'Fraunces', Georgia, serif;
|
||||
--font-sans: 'DM Sans', system-ui, sans-serif;
|
||||
--font-mono: 'DM Mono', monospace;
|
||||
|
||||
/* ── Neutrals ───────────────────────────────────────────────────── */
|
||||
--color-page: #fafaf7;
|
||||
--color-surface: #f5f4ee;
|
||||
--color-subtle: #edecea;
|
||||
--color-border: #d8d7d0;
|
||||
--color-text: #1c1c18;
|
||||
--color-text-muted: #6b6a63;
|
||||
|
||||
/* ── Green scale ────────────────────────────────────────────────── */
|
||||
--green-tint: #e8f5ea;
|
||||
--green-light: #aedcb0;
|
||||
--green: #3d8c4a;
|
||||
--green-dark: #2e6e39; /* button backgrounds with white text — #3D8C4A gives 4.16:1, fails AA */
|
||||
--green-deeper: #1e4a26;
|
||||
|
||||
/* ── Yellow scale ───────────────────────────────────────────────── */
|
||||
--yellow-tint: #fdf6d8;
|
||||
--yellow-light: #f9e08a;
|
||||
--yellow: #f2c12e;
|
||||
--yellow-dark: #c49610;
|
||||
--yellow-text: #8a6800;
|
||||
|
||||
/* ── Blue scale ─────────────────────────────────────────────────── */
|
||||
--blue-tint: #e6f1fb;
|
||||
--blue-light: #a4cff4;
|
||||
--blue: #2d7dd2;
|
||||
--blue-dark: #185fa5;
|
||||
|
||||
/* ── Purple scale ───────────────────────────────────────────────── */
|
||||
--purple-tint: #eeedfe;
|
||||
--purple: #534ab7;
|
||||
--purple-dark: #3c3489;
|
||||
|
||||
/* ── Orange scale ───────────────────────────────────────────────── */
|
||||
--orange-tint: #fef0e6;
|
||||
--orange: #e8862a;
|
||||
--orange-dark: #b46820;
|
||||
|
||||
/* ── Status ─────────────────────────────────────────────────────── */
|
||||
--color-error: #dc4c3e;
|
||||
|
||||
/* ── Spacing (8px base grid, 4px half-step) ─────────────────────── */
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 20px;
|
||||
--space-6: 24px;
|
||||
--space-7: 28px;
|
||||
--space-8: 32px;
|
||||
--space-9: 36px;
|
||||
--space-10: 40px;
|
||||
--space-11: 44px;
|
||||
--space-12: 48px;
|
||||
--space-13: 52px;
|
||||
--space-14: 56px;
|
||||
--space-15: 60px;
|
||||
--space-16: 64px;
|
||||
--space-17: 68px;
|
||||
--space-18: 72px;
|
||||
--space-19: 76px;
|
||||
--space-20: 80px;
|
||||
|
||||
/* ── Radii ──────────────────────────────────────────────────────── */
|
||||
--radius-xs: 2px;
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 6px; /* default */
|
||||
--radius-lg: 10px;
|
||||
--radius-xl: 16px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* ── Elevation ──────────────────────────────────────────────────── */
|
||||
--shadow-card: 0 1px 3px rgba(28, 28, 24, 0.06), 0 1px 2px rgba(28, 28, 24, 0.04);
|
||||
--shadow-raised: 0 4px 12px rgba(28, 28, 24, 0.08), 0 2px 4px rgba(28, 28, 24, 0.04);
|
||||
--shadow-overlay: 0 8px 32px rgba(28, 28, 24, 0.12), 0 2px 8px rgba(28, 28, 24, 0.06);
|
||||
|
||||
/* ── Button base tokens ─────────────────────────────────────────── */
|
||||
--btn-font-size: 13px;
|
||||
--btn-font-weight: 500;
|
||||
--btn-letter-spacing: 0.04em;
|
||||
}
|
||||
15
frontend/src/app.html
Normal file
15
frontend/src/app.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="text-scale" content="scale" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300;9..144,400;9..144,500&family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
57
frontend/src/lib/design-system/tokens.test.ts
Normal file
57
frontend/src/lib/design-system/tokens.test.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
const css = readFileSync(resolve(__dirname, '../../app.css'), 'utf-8');
|
||||
|
||||
const requiredTokens = [
|
||||
// Fonts
|
||||
'--font-display',
|
||||
'--font-sans',
|
||||
'--font-mono',
|
||||
// Neutrals
|
||||
'--color-page',
|
||||
'--color-surface',
|
||||
'--color-subtle',
|
||||
'--color-border',
|
||||
'--color-text',
|
||||
'--color-text-muted',
|
||||
// Green scale
|
||||
'--green-tint',
|
||||
'--green-light',
|
||||
'--green',
|
||||
'--green-dark',
|
||||
'--green-deeper',
|
||||
// Yellow scale
|
||||
'--yellow-tint',
|
||||
'--yellow-light',
|
||||
'--yellow',
|
||||
'--yellow-dark',
|
||||
'--yellow-text',
|
||||
// Status
|
||||
'--color-error',
|
||||
// Spacing
|
||||
'--space-1',
|
||||
'--space-4',
|
||||
'--space-8',
|
||||
'--space-12',
|
||||
'--space-16',
|
||||
'--space-20',
|
||||
// Radii
|
||||
'--radius-xs',
|
||||
'--radius-sm',
|
||||
'--radius-md',
|
||||
'--radius-lg',
|
||||
'--radius-xl',
|
||||
'--radius-full',
|
||||
// Shadows
|
||||
'--shadow-card',
|
||||
'--shadow-raised',
|
||||
'--shadow-overlay'
|
||||
];
|
||||
|
||||
describe('design token completeness', () => {
|
||||
it.each(requiredTokens)('%s is defined in app.css', (token) => {
|
||||
expect(css).toContain(token);
|
||||
});
|
||||
});
|
||||
21
frontend/tsconfig.json
Normal file
21
frontend/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["@types/node"]
|
||||
}
|
||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||
//
|
||||
// To make changes to top-level options such as include and exclude, we recommend extending
|
||||
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
|
||||
}
|
||||
Reference in New Issue
Block a user