refactor(deck-detail): Redesign mit Kategorie-Picker, Card-Menü, Markdown

- Deck-Header mit Farb-Dot, Titel, Aktions-Buttons (Lernen / Neue Karte)
- Kategorie-Picker (eingeklappt, inline wie /decks/new)
- Card-List mit kontextuellem 3-Punkte-Menü (Edit / Löschen)
- Karten-Vorschau mit Front/Back via marked
- NewDeckCard: Kategorie-Icon-Größe korrigiert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-05-10 15:59:56 +02:00
parent 03ec7e7b3e
commit 731481ffe3
2 changed files with 527 additions and 149 deletions

View file

@ -25,6 +25,7 @@
let imageFiles = $state<File[]>([]);
let imagePreviews = $state<string[]>([]);
let imageUrl = $state('');
let imageGenerating = $state(false);
let imageError = $state<string | null>(null);
let fileInput = $state<HTMLInputElement | null>(null);
@ -76,16 +77,21 @@
for (const url of imagePreviews) URL.revokeObjectURL(url);
imageFiles = [];
imagePreviews = [];
imageUrl = '';
imageGenerating = false;
imageError = null;
}
async function onFromImage() {
if (imageFiles.length === 0 || !devUser.id || imageGenerating) return;
if ((imageFiles.length === 0 && !imageUrl.trim()) || !devUser.id || imageGenerating) return;
imageError = null;
imageGenerating = true;
try {
const result = await generateDeckFromImage(imageFiles, { count, language });
const result = await generateDeckFromImage(imageFiles, {
count,
language,
url: imageUrl.trim() || undefined,
});
toasts.success(`🖼 "${result.deck.name}" mit ${result.cards_created} Karten erstellt`);
goto(`/decks/${result.deck.id}`);
} catch (err) {
@ -173,11 +179,12 @@
<div class="cat-grid">
{#each DECK_CATEGORY_IDS as id}
<button type="button" class="cat-btn" class:selected={category === id}
onclick={() => pickCategory(id)} title={DECK_CATEGORY_LABELS[id]}
onclick={() => pickCategory(id)}
aria-pressed={category === id}>
<DeckCategoryIcon category={id} size={16}
<DeckCategoryIcon category={id} size={13}
color={category === id ? color : null}
weight={category === id ? 'fill' : 'regular'} />
<span class="cat-label">{DECK_CATEGORY_LABELS[id]}</span>
</button>
{/each}
</div>
@ -248,6 +255,16 @@
</div>
</div>
<label class="field">
<span class="label">URL als Kontext (optional)</span>
<input
bind:value={imageUrl}
type="url"
placeholder="https://…"
class="input"
/>
</label>
{#if aiError}
<p class="ai-error" role="alert">{aiError}</p>
{/if}
@ -268,8 +285,16 @@
<button type="button" disabled={generating || saving || imageGenerating || !name.trim()} onclick={onAi} class="btn-ai">
{generating ? '✨ Generiere…' : '✨ Mit KI generieren'}
</button>
<button type="button" disabled={imageFiles.length === 0 || imageGenerating || saving || generating} onclick={onFromImage} class="btn-ai">
{imageGenerating ? '🖼 Analysiere…' : '🖼 Aus Bild'}
<button type="button" disabled={(imageFiles.length === 0 && !imageUrl.trim()) || imageGenerating || saving || generating} onclick={onFromImage} class="btn-ai">
{#if imageGenerating}
🖼 Analysiere…
{:else if imageFiles.length > 0 && imageUrl.trim()}
🖼 Bild + URL
{:else if imageUrl.trim()}
🖼 Aus URL
{:else}
🖼 Aus Bild
{/if}
</button>
<button type="button" onclick={close} class="btn-cancel">
{t('deck_new.cancel')}
@ -402,31 +427,39 @@
transform: rotate(180deg);
}
/* Kategorie-Picker — 4 Spalten Icon-Grid, erscheint direkt unter dem Trigger */
/* Kategorie-Picker — Flexwrap Pills mit Icon + Label */
.cat-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
padding-top: 0.25rem;
}
.cat-btn {
display: flex;
display: inline-flex;
align-items: center;
justify-content: center;
aspect-ratio: 1;
border-radius: 0.3125rem;
gap: 0.25rem;
padding: 0.25rem 0.5rem;
border-radius: 9999px;
border: 1px solid hsl(var(--color-border));
background: hsl(var(--color-surface));
color: hsl(var(--color-muted-foreground));
cursor: pointer;
transition: border-color 0.12s, background 0.12s;
transition: border-color 0.12s, background 0.12s, color 0.12s;
}
.cat-btn:hover {
border-color: hsl(var(--color-primary) / 0.5);
color: hsl(var(--color-foreground));
}
.cat-btn.selected {
border-color: hsl(var(--color-primary));
background: hsl(var(--color-primary) / 0.08);
color: hsl(var(--color-foreground));
}
.cat-label {
font-size: 0.75rem;
font-weight: 500;
}
/* Farbe — volle Breite, feste Höhe wie ein Input */