diff --git a/apps/web/package.json b/apps/web/package.json
index fa8d802..2d5a55a 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -1,38 +1,41 @@
{
- "name": "@cards/web",
- "version": "0.0.0",
- "private": true,
- "type": "module",
- "description": "Cards-Web — SvelteKit 2 + Svelte 5 Frontend für cardecky.mana.how.",
- "scripts": {
- "dev": "vite dev --port 3082 --host",
- "build": "vite build",
- "preview": "vite preview",
- "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
- "type-check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
- "test": "vitest run --passWithNoTests",
- "lint": "echo 'lint configured later (eslint flat-config)'",
- "clean": "rm -rf .svelte-kit build .turbo"
- },
- "dependencies": {
- "@cards/domain": "workspace:*",
- "dompurify": "^3.4.2",
- "jszip": "^3.10.1",
- "marked": "^18.0.3",
- "sql.js": "^1.14.1"
- },
- "devDependencies": {
- "@sveltejs/adapter-node": "^5.2.0",
- "@sveltejs/kit": "^2.8.0",
- "@sveltejs/vite-plugin-svelte": "^4.0.0",
- "@tailwindcss/vite": "^4.2.4",
- "@types/dompurify": "^3.2.0",
- "@types/jszip": "^3.4.1",
- "@types/sql.js": "^1.4.11",
- "svelte": "^5.0.0",
- "svelte-check": "^4.0.0",
- "tailwindcss": "^4.2.4",
- "vite": "^5.4.0",
- "vitest": "^2.1.0"
- }
+ "name": "@cards/web",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "description": "Cards-Web — SvelteKit 2 + Svelte 5 Frontend für cardecky.mana.how.",
+ "scripts": {
+ "dev": "vite dev --port 3082 --host",
+ "build": "vite build",
+ "preview": "vite preview",
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
+ "type-check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
+ "test": "vitest run --passWithNoTests",
+ "lint": "echo 'lint configured later (eslint flat-config)'",
+ "clean": "rm -rf .svelte-kit build .turbo"
+ },
+ "dependencies": {
+ "@cards/domain": "workspace:*",
+ "@mana/shared-icons": "^1.0.0",
+ "@mana/shared-ui": "^0.1.1",
+ "dompurify": "^3.4.2",
+ "jszip": "^3.10.1",
+ "marked": "^18.0.3",
+ "sql.js": "^1.14.1",
+ "@mana/themes": "^0.1.0"
+ },
+ "devDependencies": {
+ "@sveltejs/adapter-node": "^5.2.0",
+ "@sveltejs/kit": "^2.8.0",
+ "@sveltejs/vite-plugin-svelte": "^4.0.0",
+ "@tailwindcss/vite": "^4.2.4",
+ "@types/dompurify": "^3.2.0",
+ "@types/jszip": "^3.4.1",
+ "@types/sql.js": "^1.4.11",
+ "svelte": "^5.0.0",
+ "svelte-check": "^4.0.0",
+ "tailwindcss": "^4.2.4",
+ "vite": "^5.4.0",
+ "vitest": "^2.1.0"
+ }
}
diff --git a/apps/web/src/app.css b/apps/web/src/app.css
index b3f7ed9..9b1bc5a 100644
--- a/apps/web/src/app.css
+++ b/apps/web/src/app.css
@@ -1,17 +1,53 @@
@import 'tailwindcss';
+@import '@mana/themes/variants/forest.css';
+
+/**
+ * Cards — Theming-Setup.
+ *
+ * 12-Token-Vokabular kommt aus `@mana/themes/variants/forest.css`.
+ * `data-theme="forest"` ist in `app.html` gesetzt; Light/Dark via
+ * Class `.dark` auf . Konvention: `hsl(var(--color-X))`. Bare
+ * `var(--color-X)` ist verboten (silent inherit-fallback). Detail in
+ * mana/docs/THEMING.md.
+ *
+ * Diese Datei trägt nur Cards-spezifische Ergänzungen:
+ * 1. Bridge-Aliase für shared-ui@0.1.x — bis shared-ui@2.0 entfällt
+ * 2. Brand-Literals (--brand-cards-forest)
+ * 3. Layer-Base-Regeln (html, focus-ring, sr-only, skip-link)
+ * 4. Reduced-Motion-Override
+ */
@theme {
- --color-bg: oklch(0.99 0.005 240);
- --color-fg: oklch(0.20 0.02 240);
- --color-muted: oklch(0.55 0.02 240);
- --color-border: oklch(0.92 0.01 240);
- --color-card: oklch(1 0 0);
- --color-primary: oklch(0.55 0.15 250);
- --color-primary-fg: oklch(1 0 0);
- --color-success: oklch(0.6 0.15 145);
- --color-warning: oklch(0.75 0.15 75);
- --color-danger: oklch(0.55 0.18 25);
+ /* ===== Bridge-Aliase für shared-ui@0.1.x =====
+ * shared-ui@0.1.x erwartet ein 30-Token-Vokabular aus dem alten
+ * managarten-System. Damit Komponenten wie PillTabGroup nicht
+ * silent-fallback brechen, mappen wir die fehlenden Tokens auf
+ * unser 12er-Set. Mit shared-ui@2.0 entfällt diese Sektion.
+ */
+ --color-card: var(--color-surface);
+ --color-card-foreground: var(--color-foreground);
+ --color-popover: var(--color-surface);
+ --color-popover-foreground: var(--color-foreground);
+ --color-secondary: var(--color-surface);
+ --color-secondary-foreground: var(--color-foreground);
+ --color-accent: var(--color-primary);
+ --color-accent-foreground: var(--color-primary-foreground);
+ --color-input: var(--color-border);
+ --color-ring: var(--color-primary);
+ --color-border-strong: var(--color-foreground);
+ --color-surface-elevated: var(--color-surface);
+ --color-surface-elevated-1: var(--color-surface);
+ --color-surface-elevated-2: var(--color-surface);
+ --color-surface-elevated-3: var(--color-surface);
+ --color-app-accent: var(--color-primary);
+ --color-mana: var(--color-primary);
+ --color-destructive: var(--color-error);
+ --color-destructive-foreground: var(--color-primary-foreground);
+ /* ===== Brand-Literals (NICHT theme-aware) ===== */
+ --brand-cards-forest: #16a34a;
+
+ /* ===== Fonts ===== */
--font-sans:
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
@@ -19,19 +55,20 @@
@layer base {
html {
- background-color: var(--color-bg);
- color: var(--color-fg);
+ background-color: hsl(var(--color-background));
+ color: hsl(var(--color-foreground));
font-family: var(--font-sans);
}
+
body {
min-height: 100dvh;
}
/* Sichtbarer Fokus-Ring für Tastatur-Nutzer. Tailwind 4 strippt
- die Browser-Defaults; wir setzen einen expliziten Outline.
- Nur :focus-visible, damit Maus-Klicks visuell sauber bleiben. */
+ die Browser-Defaults; expliziter Outline. Nur :focus-visible,
+ damit Maus-Klicks visuell sauber bleiben. */
:focus-visible {
- outline: 2px solid var(--color-primary);
+ outline: 2px solid hsl(var(--color-primary));
outline-offset: 2px;
border-radius: 2px;
}
@@ -56,8 +93,8 @@
top: 0;
left: 0;
padding: 0.5rem 0.75rem;
- background: var(--color-primary);
- color: var(--color-primary-fg);
+ background: hsl(var(--color-primary));
+ color: hsl(var(--color-primary-foreground));
z-index: 50;
transform: translateY(-200%);
transition: transform 0.15s;
@@ -79,15 +116,3 @@
scroll-behavior: auto !important;
}
}
-
-@media (prefers-color-scheme: dark) {
- @theme {
- --color-bg: oklch(0.18 0.02 240);
- --color-fg: oklch(0.95 0.01 240);
- --color-muted: oklch(0.65 0.02 240);
- --color-border: oklch(0.30 0.02 240);
- --color-card: oklch(0.22 0.02 240);
- --color-primary: oklch(0.70 0.18 250);
- --color-primary-fg: oklch(0.18 0.02 240);
- }
-}
diff --git a/apps/web/src/app.html b/apps/web/src/app.html
index da72501..549c039 100644
--- a/apps/web/src/app.html
+++ b/apps/web/src/app.html
@@ -1,5 +1,5 @@
-
+
diff --git a/apps/web/src/lib/components/AnkiImport.svelte b/apps/web/src/lib/components/AnkiImport.svelte
index e76e358..7cb5e30 100644
--- a/apps/web/src/lib/components/AnkiImport.svelte
+++ b/apps/web/src/lib/components/AnkiImport.svelte
@@ -84,14 +84,14 @@
}
-
+
{t('import.anki_label')}
{#if stage === 'idle'}
e.preventDefault()}
ondrop={onDrop}
onclick={() => fileInput?.click()}
@@ -115,21 +115,21 @@
onchange={onPick}
/>
{:else if stage === 'parsing'}
-
+
{t('import.parsing', { file: fileName })}
{:else if stage === 'preview' && parsed}
- {t('import.preview_found')}
- {fileName}:
+ {t('import.preview_found')}
+ {fileName}:
- {tn('import.preview_decks', parsed.decks.length)}
-
{tn('import.preview_cards', parsed.cards.length)}
{#if parsed.cards.length > 0}
-
+
{t('import.preview_breakdown', {
basic: typeBreakdown.basic,
basic_reverse: typeBreakdown.basicReverse,
@@ -139,7 +139,7 @@
{/if}
{#if parsed.mediaByFilename.size > 0}
- -
+
-
{t('import.preview_media', { n: parsed.mediaByFilename.size })}
{/if}
@@ -148,7 +148,7 @@
{/if}
{#if parsed.warnings.length > 0}
-
+
{t('import.preview_warnings', { n: parsed.warnings.length })}
{#each parsed.warnings.slice(0, 10) as w (w)}- {w}
{/each}
@@ -157,13 +157,13 @@
{/if}
{:else if stage === 'importing'}
-
+
{#if progress.stage === 'media'}
{t('import.stage_media', { current: progress.current, total: progress.total })}
{:else if progress.stage === 'decks'}
@@ -182,32 +182,32 @@
{t('import.stage_done')}
{/if}
{:else if stage === 'done' && result}
-
+
{result.decksCreated === 1
? t('import.done_summary_one', { cards: result.cardsCreated })
: t('import.done_summary', { cards: result.cardsCreated, decks: result.decksCreated })}
{#if result.cardsSkippedDuplicate > 0}
-
+
{t('import.done_dupes', { n: result.cardsSkippedDuplicate })}
{/if}
{#if result.mediaUploaded > 0 || result.mediaFailed > 0}
-
+
{t('import.done_media', {
uploaded: result.mediaUploaded,
failed: result.mediaFailed,
@@ -215,7 +215,7 @@
{/if}
{#if result.failed > 0}
-
+
{t('import.done_failures', { n: result.failed })}
{#each result.failures.slice(0, 20) as msg (msg)}- {msg}
{/each}
@@ -223,7 +223,7 @@
{/if}
{:else if stage === 'error'}
-
{t('import.error_label', { msg: error ?? '?' })}
+
{t('import.error_label', { msg: error ?? '?' })}