feat(design-system): add Tailwind 4 @theme tokens, fonts, and completeness tests

- Load Fraunces, DM Sans, DM Mono via Google Fonts preconnect in app.html
- Define all design tokens in @theme block: neutrals, green/yellow/blue/
  purple/orange scales, spacing (--space-1..20), radii, shadows, button base
- Note --green-dark as button background (--green fails WCAG AA with white)
- Add @types/node for Node fs/path usage in design-system tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit was merged in pull request #31.
This commit is contained in:
2026-04-02 12:45:11 +02:00
parent 7c8d725fce
commit 0a2ef750c4
6 changed files with 201 additions and 0 deletions

View File

@@ -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",

View File

@@ -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
View 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
View 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>

View 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
View 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
}