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'} + i18n.set(id as 'de' | 'en')} + sectionLabel={t('common.language_switcher')} + primaryColor="hsl(var(--color-primary))" + /> {#if devUser.id} {devUser.user?.email ?? devUser.user?.name ?? devUser.id} @@ -76,7 +84,7 @@ {:else} Login diff --git a/apps/web/src/lib/components/ImageOcclusionEditor.svelte b/apps/web/src/lib/components/ImageOcclusionEditor.svelte index a349e6f..0c2dbe0 100644 --- a/apps/web/src/lib/components/ImageOcclusionEditor.svelte +++ b/apps/web/src/lib/components/ImageOcclusionEditor.svelte @@ -134,7 +134,7 @@ /> {#if uploading} -

{t('image_occlusion.uploading')}

+

{t('image_occlusion.uploading')}

{/if} {#if imageRef} @@ -186,24 +186,24 @@
-

{t('image_occlusion.draw_hint')}

+

{t('image_occlusion.draw_hint')}

{#if masks.length > 0}
    {#each masks as m, i (m.id)} -
  • - {i + 1} +
  • + {i + 1} setLabel(m.id, (e.currentTarget as HTMLInputElement).value)} - class="flex-1 rounded border bg-[var(--color-card)] border-[var(--color-border)] px-2 py-1 text-sm" + class="flex-1 rounded border bg-[hsl(var(--color-card))] border-[hsl(var(--color-border))] px-2 py-1 text-sm" /> -

    +

    Mana-Account auf

    {t('account.title')}

    -
    +
    {#if devUser.user}
    -
    E-Mail
    +
    E-Mail
    {devUser.user.email}
    {#if devUser.user.name}
    -
    Name
    +
    Name
    {devUser.user.name}
    {/if}
    -
    Tier
    +
    Tier
    {devUser.user.tier}
    -
    {t('account.user_id_label')}
    - {devUser.user.id} +
    {t('account.user_id_label')}
    + {devUser.user.id}
    {:else}
    -
    {t('account.user_id_label')} (Stub)
    +
    {t('account.user_id_label')} (Stub)
    {devUser.id ?? '—'}
    {/if} @@ -104,34 +104,34 @@
-
+

{t('account.export_title')}

-

{t('account.export_intro')}

+

{t('account.export_intro')}

-
-

{t('account.delete_title')}

-

{t('account.delete_intro')}

+
+

{t('account.delete_title')}

+

{t('account.delete_intro')}

diff --git a/apps/web/src/routes/cards/[id]/edit/+page.svelte b/apps/web/src/routes/cards/[id]/edit/+page.svelte index b4aed2a..8a4db98 100644 --- a/apps/web/src/routes/cards/[id]/edit/+page.svelte +++ b/apps/web/src/routes/cards/[id]/edit/+page.svelte @@ -122,21 +122,21 @@
{#if loading} -

{t('decks.loading')}

+

{t('decks.loading')}

{:else if error} -

{t('decks.error', { msg: error })}

+

{t('decks.error', { msg: error })}

{:else if card}
{t('card_edit.back')}{t('card_edit.back')}

{t('card_edit.title')}

- + {cardType}
-

{t('card_edit.type_locked_help')}

+

{t('card_edit.type_locked_help')}

{#if cardType === 'image-occlusion'} @@ -150,14 +150,14 @@ required rows="6" placeholder={t('card_new.cloze_text_placeholder')} - class="mt-1 block w-full rounded border bg-[var(--color-card)] border-[var(--color-border)] px-3 py-2 font-mono text-sm" + class="mt-1 block w-full rounded border bg-[hsl(var(--color-card))] border-[hsl(var(--color-border))] px-3 py-2 font-mono text-sm" > -

{t('card_new.cloze_help')}

+

{t('card_new.cloze_help')}

{#if text.trim() && clusterIds.length === 0} -

{t('card_new.cloze_no_clusters')}

+

{t('card_new.cloze_no_clusters')}

{:else if clusterIds.length > 0} -

+

{t('card_new.cloze_clusters_detected', { n: clusterIds.length, ids: clusterIds.join(', c'), @@ -167,8 +167,8 @@

{#if clozePreviewHtml} -
-
+
+
{t('card_new.cloze_preview_label', { first: clusterIds[0] ?? 1 })}
{@html clozePreviewHtml}
@@ -181,7 +181,7 @@ bind:value={extra} rows="3" placeholder={t('card_new.cloze_extra_placeholder')} - class="mt-1 block w-full rounded border bg-[var(--color-card)] border-[var(--color-border)] px-3 py-2 font-mono text-sm" + class="mt-1 block w-full rounded border bg-[hsl(var(--color-card))] border-[hsl(var(--color-border))] px-3 py-2 font-mono text-sm" > {:else} @@ -193,12 +193,12 @@ bind:value={front} required rows="8" - class="mt-1 block w-full rounded border bg-[var(--color-card)] border-[var(--color-border)] px-3 py-2 font-mono text-sm" + class="mt-1 block w-full rounded border bg-[hsl(var(--color-card))] border-[hsl(var(--color-border))] px-3 py-2 font-mono text-sm" > {#if front.trim()} -
-
{t('card_new.preview_label')}
+
+
{t('card_new.preview_label')}
{@html frontHtml}
{/if} @@ -210,12 +210,12 @@ bind:value={back} required rows="8" - class="mt-1 block w-full rounded border bg-[var(--color-card)] border-[var(--color-border)] px-3 py-2 font-mono text-sm" + class="mt-1 block w-full rounded border bg-[hsl(var(--color-card))] border-[hsl(var(--color-border))] px-3 py-2 font-mono text-sm" > {#if back.trim()} -
-
{t('card_new.preview_label')}
+
+
{t('card_new.preview_label')}
{@html backHtml}
{/if} @@ -228,19 +228,19 @@ {t('card_edit.cancel')}{t('card_edit.cancel')}
diff --git a/apps/web/src/routes/cards/new/+page.svelte b/apps/web/src/routes/cards/new/+page.svelte index f75fba2..614f90e 100644 --- a/apps/web/src/routes/cards/new/+page.svelte +++ b/apps/web/src/routes/cards/new/+page.svelte @@ -107,7 +107,7 @@ } -{t('card_new.back')}

{t('card_new.title')}

@@ -119,7 +119,7 @@ @@ -152,14 +152,14 @@ required rows="6" placeholder={t('card_new.cloze_text_placeholder')} - class="mt-1 block w-full rounded border bg-[var(--color-card)] border-[var(--color-border)] px-3 py-2 font-mono text-sm" + class="mt-1 block w-full rounded border bg-[hsl(var(--color-card))] border-[hsl(var(--color-border))] px-3 py-2 font-mono text-sm" > -

{t('card_new.cloze_help')}

+

{t('card_new.cloze_help')}

{#if text.trim() && clusterIds.length === 0} -

{t('card_new.cloze_no_clusters')}

+

{t('card_new.cloze_no_clusters')}

{:else if clusterIds.length > 0} -

+

{t('card_new.cloze_clusters_detected', { n: clusterIds.length, ids: clusterIds.join(', c'), @@ -169,8 +169,8 @@

{#if clozePreviewHtml} -
-
+
+
{t('card_new.cloze_preview_label', { first: clusterIds[0] ?? 1 })}
{@html clozePreviewHtml}
@@ -183,7 +183,7 @@ bind:value={extra} rows="3" placeholder={t('card_new.cloze_extra_placeholder')} - class="mt-1 block w-full rounded border bg-[var(--color-card)] border-[var(--color-border)] px-3 py-2 font-mono text-sm" + class="mt-1 block w-full rounded border bg-[hsl(var(--color-card))] border-[hsl(var(--color-border))] px-3 py-2 font-mono text-sm" > {:else} @@ -196,12 +196,12 @@ required rows="8" placeholder={t('card_new.front_placeholder')} - class="mt-1 block w-full rounded border bg-[var(--color-card)] border-[var(--color-border)] px-3 py-2 font-mono text-sm" + class="mt-1 block w-full rounded border bg-[hsl(var(--color-card))] border-[hsl(var(--color-border))] px-3 py-2 font-mono text-sm" > {#if front.trim()} -
-
{t('card_new.preview_label')}
+
+
{t('card_new.preview_label')}
{@html frontHtml}
{/if} @@ -215,12 +215,12 @@ required rows="8" placeholder={t('card_new.back_placeholder')} - class="mt-1 block w-full rounded border bg-[var(--color-card)] border-[var(--color-border)] px-3 py-2 font-mono text-sm" + class="mt-1 block w-full rounded border bg-[hsl(var(--color-card))] border-[hsl(var(--color-border))] px-3 py-2 font-mono text-sm" > {#if back.trim()} -
-
{t('card_new.preview_label')}
+
+
{t('card_new.preview_label')}
{@html backHtml}
{/if} @@ -232,13 +232,13 @@ {t('card_new.cancel')}{t('card_new.cancel')}
diff --git a/apps/web/src/routes/decks/new-ai/+page.svelte b/apps/web/src/routes/decks/new-ai/+page.svelte index 0223360..914a850 100644 --- a/apps/web/src/routes/decks/new-ai/+page.svelte +++ b/apps/web/src/routes/decks/new-ai/+page.svelte @@ -50,11 +50,11 @@
- + ← {t('nav.decks')}

✨ Deck mit KI erstellen

-

+

Beschreibe ein Thema, und mana-llm baut ein Deck mit Karteikarten daraus. Du kannst die Karten danach jederzeit editieren oder ergänzen.

@@ -68,9 +68,9 @@ rows="4" maxlength="500" placeholder="z.B. Deutsche Hunderassen mit ihren wichtigsten Charaktermerkmalen" - class="mt-1 block w-full rounded border bg-[var(--color-card)] border-[var(--color-border)] px-3 py-2 text-sm" + class="mt-1 block w-full rounded border bg-[hsl(var(--color-card))] border-[hsl(var(--color-border))] px-3 py-2 text-sm" > -

+

Klar formulierte, abgegrenzte Themen funktionieren besser als zu breite („alles über Geschichte" → eher: „französische Revolution: Schlüsselereignisse 1789-1799").

@@ -84,16 +84,16 @@ bind:value={count} min="3" max="40" - class="mt-1 block w-full rounded border bg-[var(--color-card)] border-[var(--color-border)] px-3 py-2 text-sm" + class="mt-1 block w-full rounded border bg-[hsl(var(--color-card))] border-[hsl(var(--color-border))] px-3 py-2 text-sm" /> -

3–40 (Server-Cap).

+

3–40 (Server-Cap).

@@ -68,11 +68,11 @@ - {t('deck_new.cancel')}
diff --git a/apps/web/src/routes/import/+page.svelte b/apps/web/src/routes/import/+page.svelte index 9749e14..6a2c1a1 100644 --- a/apps/web/src/routes/import/+page.svelte +++ b/apps/web/src/routes/import/+page.svelte @@ -18,21 +18,21 @@

{t('import.title')}

-

{t('import.intro')}

+

{t('import.intro')}

-
{/each}
-
+

{t('stats.fsrs_title')}

-

{t('stats.fsrs_intro')}

+

{t('stats.fsrs_intro')}

-
{t('stats.fsrs_new')}
+
{t('stats.fsrs_new')}
{stats.state_counts.new}
-
{t('stats.fsrs_learning')}
+
{t('stats.fsrs_learning')}
{stats.state_counts.learning}
-
{t('stats.fsrs_review')}
+
{t('stats.fsrs_review')}
{stats.state_counts.review}
-
{t('stats.fsrs_relearning')}
+
{t('stats.fsrs_relearning')}
{stats.state_counts.relearning}
diff --git a/apps/web/src/routes/study/+page.svelte b/apps/web/src/routes/study/+page.svelte index 9e23d56..51a5234 100644 --- a/apps/web/src/routes/study/+page.svelte +++ b/apps/web/src/routes/study/+page.svelte @@ -44,12 +44,12 @@
{#if loading} -

{t('study_session.loading')}

+

{t('study_session.loading')}

{:else}
    {#each items as it (it.deck.id)}
  • {#if it.deck.color} @@ -60,19 +60,19 @@ > {/if} {it.deck.name} - + {t('study.due_count', { n: it.due })}
    {#if it.due > 0} {t('study.study_now')} {:else} - + {/if}
  • {/each} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e67b0c..f6f02e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,6 +75,9 @@ importers: '@mana/shared-ui': specifier: ^0.1.1 version: 0.1.1(svelte@5.55.5)(vite@5.4.21(@types/node@22.19.18)(lightningcss@1.32.0)) + '@mana/themes': + specifier: ^0.1.0 + version: 0.1.0 dompurify: specifier: ^3.4.2 version: 3.4.2 @@ -605,6 +608,9 @@ packages: peerDependencies: svelte: ^5.0.0 + '@mana/themes@0.1.0': + resolution: {integrity: sha512-YI3UU4Y1s+V1DRy5SDzkuA76Z/dSaf5VKf6zm5IofEhiQUn96/XoKwythRGemKiJz+kqnO7Kv9BJNCODU4y/Vg==} + '@nodable/entities@2.1.0': resolution: {integrity: sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==} @@ -2229,6 +2235,8 @@ snapshots: transitivePeerDependencies: - vite + '@mana/themes@0.1.0': {} + '@nodable/entities@2.1.0': {} '@petamoriken/float16@3.9.3': {}