refactor(theming): migrate remaining 12 ListViews to theme tokens

Replace raw white-alpha Tailwind utilities across the last 12 module
ListViews that were flagged by validate-theme-tokens: citycorners,
guides, inventory, memoro, picture, plants, playground, presi,
questions, times, uload, who. Also replace semantic color hex/names
(bg-yellow-500/20, bg-green-400, text-blue-400, bg-teal-600, etc.)
with success/warning/error/primary tokens.

Per-deck brand colors in who/ListView (#a855f7 purple/historical,
#ec4899 pink/women, #f59e0b amber/antiquity, #0ea5e9 blue/inventors)
stay as hex — those are domain semantics, not theme intent.

Wire validate:theme-tokens into validate:all so future regressions
fail the local pre-push gate. All 76 module ListViews now pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-22 15:29:43 +02:00
parent a2a43b1d5a
commit 86c205ffc5
13 changed files with 129 additions and 124 deletions

View file

@ -56,20 +56,21 @@
_siblingIds: locations.map((l) => l.id),
_siblingKey: 'locationId',
})}
class="flex w-full min-h-[44px] items-start gap-2 rounded-md px-2 py-2 transition-colors hover:bg-white/5 cursor-pointer text-left"
class="flex w-full min-h-[44px] items-start gap-2 rounded-md px-2 py-2 transition-colors hover:bg-muted/50 cursor-pointer text-left"
>
<div
class="mt-0.5 h-2.5 w-2.5 shrink-0 rounded-full"
style="background: {CATEGORY_COLORS[location.category] ?? '#666'}"
style="background: {CATEGORY_COLORS[location.category] ??
'hsl(var(--color-muted-foreground))'}"
></div>
<div class="min-w-0 flex-1">
<div class="flex items-center gap-1">
<p class="truncate text-sm text-white/80">{location.name}</p>
<p class="truncate text-sm text-foreground">{location.name}</p>
{#if favoriteIds.has(location.id)}
<span class="text-xs text-yellow-400">&#9733;</span>
<span class="text-xs text-warning">&#9733;</span>
{/if}
</div>
<p class="text-xs text-white/40">
<p class="text-xs text-muted-foreground">
{categoryLabels[location.category] ?? location.category}
</p>
</div>

View file

@ -85,7 +85,7 @@
<input
bind:value={query}
placeholder="Guides durchsuchen..."
class="rounded-md border border-white/10 bg-white/5 px-3 py-2 text-sm text-white placeholder:text-white/30 focus:border-white/20 focus:outline-none"
class="rounded-md border border-border bg-muted/30 px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground/60 focus:border-ring focus:outline-none"
/>
<!-- Category filter + create toggle -->
@ -97,8 +97,8 @@
onclick={() => (activeCategory = cat.id)}
class="rounded-full px-3 py-1 text-xs transition-colors
{activeCategory === cat.id
? 'bg-white/15 text-white'
: 'bg-white/5 text-white/50 hover:bg-white/10'}"
? 'bg-primary/20 text-primary'
: 'bg-muted/30 text-muted-foreground hover:bg-muted'}"
>
{cat.label}
</button>
@ -106,7 +106,7 @@
</div>
<button
type="button"
class="shrink-0 text-xs text-white/50 transition-colors hover:text-white/80"
class="shrink-0 text-xs text-muted-foreground transition-colors hover:text-foreground"
onclick={() => (creating = !creating)}
>
{creating ? 'Abbrechen' : '+ Neuer Guide'}
@ -114,17 +114,17 @@
</div>
{#if creating}
<form class="flex flex-col gap-2 rounded-lg bg-white/5 p-3" onsubmit={handleCreate}>
<form class="flex flex-col gap-2 rounded-lg bg-muted/30 p-3" onsubmit={handleCreate}>
<input
type="text"
bind:value={newTitle}
placeholder="Guide-Titel"
required
class="rounded-md border border-white/10 bg-white/5 px-3 py-1.5 text-sm text-white placeholder:text-white/30 focus:border-white/20 focus:outline-none"
class="rounded-md border border-border bg-muted/30 px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground/60 focus:border-ring focus:outline-none"
/>
<select
bind:value={newCategory}
class="rounded-md border border-white/10 bg-white/5 px-3 py-1.5 text-sm text-white focus:border-white/20 focus:outline-none"
class="rounded-md border border-border bg-muted/30 px-3 py-1.5 text-sm text-foreground focus:border-ring focus:outline-none"
>
{#each Object.entries(GUIDE_CATEGORIES) as [key, info] (key)}
<option value={key}>{info.label}</option>
@ -132,7 +132,7 @@
</select>
<button
type="submit"
class="rounded-md bg-teal-600 px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-teal-700 disabled:cursor-not-allowed disabled:opacity-50"
class="rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50"
disabled={!newTitle.trim()}
>
Guide erstellen
@ -152,28 +152,28 @@
{@const progress = getStepProgress(run ?? null, totalSteps)}
<button
onclick={() => openGuide(guide)}
class="mb-2 w-full rounded-md border border-white/5 bg-white/5 p-3 text-left transition-colors hover:bg-white/10"
class="mb-2 w-full rounded-md border border-border bg-muted/30 p-3 text-left transition-colors hover:bg-muted"
>
<div class="mb-1 flex items-center gap-2">
<span class="inline-block h-2 w-2 rounded-full {meta.color}"></span>
<span class="text-xs text-white/40">{meta.label}</span>
<span class="ml-auto text-xs text-white/30">{guide.estimatedMinutes} min</span>
<span class="text-xs text-muted-foreground">{meta.label}</span>
<span class="ml-auto text-xs text-muted-foreground/70">{guide.estimatedMinutes} min</span>
</div>
<h3 class="text-sm font-medium text-white/90">{guide.title}</h3>
<p class="mt-0.5 text-xs text-white/50">{guide.description}</p>
<h3 class="text-sm font-medium text-foreground">{guide.title}</h3>
<p class="mt-0.5 text-xs text-muted-foreground">{guide.description}</p>
<div class="mt-1.5 flex items-center gap-2">
<span class="text-[10px] uppercase tracking-wide text-white/30">
<span class="text-[10px] uppercase tracking-wide text-muted-foreground/70">
{DIFFICULTY_LABELS[guide.difficulty]}
</span>
{#if run}
{#if run.completedAt}
<span class="ml-auto text-[10px] font-medium text-green-400">Abgeschlossen</span>
<span class="ml-auto text-[10px] font-medium text-success">Abgeschlossen</span>
{:else if totalSteps > 0}
<div class="ml-auto flex items-center gap-1.5">
<div class="h-1 w-12 rounded-full bg-white/10">
<div class="h-full rounded-full bg-teal-400" style="width: {progress}%"></div>
<div class="h-1 w-12 rounded-full bg-muted">
<div class="h-full rounded-full bg-primary" style="width: {progress}%"></div>
</div>
<span class="text-[10px] text-white/30">{progress}%</span>
<span class="text-[10px] text-muted-foreground/70">{progress}%</span>
</div>
{/if}
{/if}

View file

@ -108,17 +108,17 @@
_siblingIds: collections.map((c) => c.id),
_siblingKey: 'collectionId',
})}
class="mb-2 w-full rounded-md border border-white/10 px-3 py-2.5 text-left transition-colors hover:bg-white/5 min-h-[44px]"
class="mb-2 w-full rounded-md border border-border px-3 py-2.5 text-left transition-colors hover:bg-muted/50 min-h-[44px]"
>
<div class="flex items-center gap-2">
{#if collection.icon}
<span class="text-sm">{collection.icon}</span>
{/if}
<p class="flex-1 truncate text-sm font-medium text-white/80">{collection.name}</p>
<span class="text-xs text-white/40">{itemsInCollection(collection.id)}</span>
<p class="flex-1 truncate text-sm font-medium text-foreground">{collection.name}</p>
<span class="text-xs text-muted-foreground">{itemsInCollection(collection.id)}</span>
</div>
{#if collection.description}
<p class="mt-1 truncate text-xs text-white/30">{collection.description}</p>
<p class="mt-1 truncate text-xs text-muted-foreground/70">{collection.description}</p>
{/if}
</button>
{/snippet}

View file

@ -58,10 +58,10 @@
}
const statusColors: Record<string, string> = {
pending: 'bg-yellow-500/20 text-yellow-300',
processing: 'bg-blue-500/20 text-blue-300',
completed: 'bg-green-500/20 text-green-300',
failed: 'bg-red-500/20 text-red-300',
pending: 'bg-warning/20 text-warning',
processing: 'bg-primary/20 text-primary',
completed: 'bg-success/20 text-success',
failed: 'bg-error/20 text-error',
};
</script>
@ -80,26 +80,26 @@
_siblingIds: sorted.map((m) => m.id),
_siblingKey: 'memoId',
})}
class="mb-2 w-full rounded-md border border-white/10 px-3 py-2.5 text-left transition-colors hover:bg-white/5 min-h-[44px]"
class="mb-2 w-full rounded-md border border-border px-3 py-2.5 text-left transition-colors hover:bg-muted/50 min-h-[44px]"
>
<div class="flex items-start justify-between gap-2">
<div class="min-w-0 flex-1">
<div class="flex items-center gap-1">
{#if memo.isPinned}
<span class="text-xs text-white/30">&#128204;</span>
<span class="text-xs text-muted-foreground/70">&#128204;</span>
{/if}
<p class="truncate text-sm font-medium text-white/80">
<p class="truncate text-sm font-medium text-foreground">
{memo.title || 'Unbenanntes Memo'}
</p>
</div>
{#if memo.intro}
<p class="mt-0.5 truncate text-xs text-white/40">{memo.intro}</p>
<p class="mt-0.5 truncate text-xs text-muted-foreground">{memo.intro}</p>
{/if}
</div>
<div class="flex items-center gap-1.5 shrink-0">
{#if memo.transcriptModel && memo.processingStatus === 'completed'}
<span
class="rounded px-1 py-0.5 text-[9px] bg-white/5 text-white/30"
class="rounded px-1 py-0.5 text-[9px] bg-muted text-muted-foreground"
title="STT-Pipeline"
>
{memo.transcriptModel}

View file

@ -242,7 +242,7 @@
{/snippet}
{#snippet item(image)}
<div class="group relative aspect-square overflow-hidden rounded-md bg-white/5">
<div class="group relative aspect-square overflow-hidden rounded-md bg-muted/50">
{#if image.publicUrl}
<img
src={image.publicUrl}
@ -251,12 +251,12 @@
loading="lazy"
/>
{:else}
<div class="flex h-full items-center justify-center text-white/20 text-xs">
<div class="flex h-full items-center justify-center text-muted-foreground/60 text-xs">
{image.format ?? 'img'}
</div>
{/if}
{#if image.isFavorite}
<span class="absolute right-1 top-1 text-xs text-yellow-400">&#9733;</span>
<span class="absolute right-1 top-1 text-xs text-warning">&#9733;</span>
{/if}
</div>
{/snippet}
@ -334,7 +334,7 @@
object-fit: cover;
}
.upload-thumb.success {
outline: 2px solid #22c55e;
outline: 2px solid hsl(var(--color-success));
outline-offset: -2px;
}
.upload-thumb.error {

View file

@ -74,7 +74,7 @@
<div class="flex items-center justify-end">
<button
type="button"
class="text-xs text-white/50 transition-colors hover:text-white/80"
class="text-xs text-muted-foreground transition-colors hover:text-foreground"
onclick={() => (creating = !creating)}
>
{creating
@ -84,13 +84,13 @@
</div>
{#if creating}
<form class="flex flex-col gap-2 rounded-lg bg-white/5 p-3" onsubmit={handleCreate}>
<form class="flex flex-col gap-2 rounded-lg bg-muted/30 p-3" onsubmit={handleCreate}>
<input
type="text"
bind:value={newName}
placeholder={$_('plants.create.namePlaceholder', { default: 'Name (z. B. Monstera)' })}
required
class="rounded-md border border-white/10 bg-white/5 px-3 py-1.5 text-sm text-white placeholder:text-white/30 focus:border-white/20 focus:outline-none"
class="rounded-md border border-border bg-muted/30 px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground/60 focus:border-ring focus:outline-none"
/>
<input
type="text"
@ -98,11 +98,11 @@
placeholder={$_('plants.create.scientificPlaceholder', {
default: 'Botanischer Name (optional)',
})}
class="rounded-md border border-white/10 bg-white/5 px-3 py-1.5 text-sm text-white placeholder:text-white/30 focus:border-white/20 focus:outline-none"
class="rounded-md border border-border bg-muted/30 px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground/60 focus:border-ring focus:outline-none"
/>
<button
type="submit"
class="rounded-md bg-green-600 px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-green-700 disabled:cursor-not-allowed disabled:opacity-50"
class="rounded-md bg-success px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-success/90 disabled:cursor-not-allowed disabled:opacity-50"
disabled={!newName.trim()}
>
{$_('plants.create.save', { default: 'Pflanze anlegen' })}
@ -114,12 +114,12 @@
{#snippet header()}
<span>{$_('plants.list.count', { values: { count: plants.length } })}</span>
{#if dueForWatering.length > 0}
<span class="text-blue-400"
<span class="text-primary"
>{$_('plants.list.dueWatering', { values: { count: dueForWatering.length } })}</span
>
{/if}
{#if needsAttention.length > 0}
<span class="text-amber-400"
<span class="text-warning"
>{$_('plants.list.needsCare', { values: { count: needsAttention.length } })}</span
>
{/if}
@ -135,24 +135,24 @@
_siblingIds: plants.map((p) => p.id),
_siblingKey: 'plantId',
})}
class="mb-2 w-full rounded-md border border-white/10 px-3 py-2.5 text-left transition-colors hover:bg-white/5 min-h-[44px]"
class="mb-2 w-full rounded-md border border-border px-3 py-2.5 text-left transition-colors hover:bg-muted/50 min-h-[44px]"
>
<div class="flex items-center gap-2">
<span class="text-sm">
{@html healthIcons[plant.healthStatus ?? 'healthy'] ?? '&#127793;'}
</span>
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium text-white/80">{plant.name}</p>
<p class="truncate text-sm font-medium text-foreground">{plant.name}</p>
{#if plant.scientificName}
<p class="truncate text-xs italic text-white/30">{plant.scientificName}</p>
<p class="truncate text-xs italic text-muted-foreground/70">{plant.scientificName}</p>
{/if}
</div>
{#if waterDue}
<span class="text-xs text-blue-400">&#128167;</span>
<span class="text-xs text-primary">&#128167;</span>
{/if}
</div>
{#if schedule}
<p class="mt-1 text-xs text-white/30">
<p class="mt-1 text-xs text-muted-foreground/70">
{$_('plants.list.everyXDays', { values: { days: schedule.frequencyDays } })}
</p>
{/if}

View file

@ -91,10 +91,10 @@
<!-- Model selector -->
<select
bind:value={selectedModel}
class="rounded-md border border-white/10 bg-white/5 px-3 py-1.5 text-sm text-white/70 focus:border-white/20 focus:outline-none"
class="rounded-md border border-border bg-muted/30 px-3 py-1.5 text-sm text-foreground/90 focus:border-ring focus:outline-none"
>
{#each modelOptions as model}
<option value={model.id} class="bg-neutral-900">{model.label} ({model.provider})</option>
<option value={model.id} class="bg-card">{model.label} ({model.provider})</option>
{/each}
</select>
@ -103,21 +103,23 @@
{#each messages as msg}
<div
class="mb-2 min-h-[44px] rounded-md px-3 py-2 {msg.role === 'user'
? 'bg-white/5'
: 'bg-blue-500/10'}"
? 'bg-muted/30'
: 'bg-primary/10'}"
>
<p class="text-[10px] text-white/30">{msg.role === 'user' ? 'Du' : modelLabel}</p>
<p class="text-[10px] text-muted-foreground/70">
{msg.role === 'user' ? 'Du' : modelLabel}
</p>
{#if msg.content}
<p class="whitespace-pre-wrap text-sm text-white/70">{msg.content}</p>
<p class="whitespace-pre-wrap text-sm text-foreground/90">{msg.content}</p>
{:else}
<p class="text-sm text-white/30"></p>
<p class="text-sm text-muted-foreground/70"></p>
{/if}
</div>
{/each}
{#if messages.length === 0}
<div class="flex h-full items-center justify-center">
<p class="text-sm text-white/30">Schreib einen Prompt...</p>
<p class="text-sm text-muted-foreground/70">Schreib einen Prompt...</p>
</div>
{/if}
</div>
@ -133,20 +135,20 @@
<input
bind:value={prompt}
placeholder="Prompt eingeben..."
class="flex-1 rounded-md border border-white/10 bg-white/5 px-3 py-1.5 text-sm text-white placeholder:text-white/30 focus:border-white/20 focus:outline-none"
class="flex-1 rounded-md border border-border bg-muted/30 px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground/60 focus:border-ring focus:outline-none"
/>
{#if isLoading}
<button
type="button"
onclick={stop}
class="rounded-md bg-red-500/20 px-3 py-1.5 text-sm text-red-200 transition-colors hover:bg-red-500/30"
class="rounded-md bg-error/20 px-3 py-1.5 text-sm text-error transition-colors hover:bg-error/30"
>Stop</button
>
{:else}
<button
type="submit"
disabled={!prompt.trim()}
class="rounded-md bg-white/10 px-3 py-1.5 text-sm text-white/70 transition-colors hover:bg-white/15 disabled:opacity-50"
class="rounded-md bg-muted px-3 py-1.5 text-sm text-foreground/80 transition-colors hover:bg-muted/80 disabled:opacity-50"
>&#9654;</button
>
{/if}

View file

@ -57,10 +57,10 @@
<BaseListView items={decks} getKey={(d) => d.id} emptyTitle="Keine Präsentationen">
{#snippet toolbar()}
<div class="flex items-center justify-between">
<span class="text-xs text-white/40">{decks.length} Präsentationen</span>
<span class="text-xs text-muted-foreground">{decks.length} Präsentationen</span>
<button
type="button"
class="text-xs text-white/50 transition-colors hover:text-white/80"
class="text-xs text-muted-foreground transition-colors hover:text-foreground"
onclick={() => (creating = !creating)}
>
{creating ? 'Abbrechen' : '+ Neue Präsentation'}
@ -68,23 +68,23 @@
</div>
{#if creating}
<form class="flex flex-col gap-2 rounded-lg bg-white/5 p-3" onsubmit={handleCreate}>
<form class="flex flex-col gap-2 rounded-lg bg-muted/30 p-3" onsubmit={handleCreate}>
<input
type="text"
bind:value={newTitle}
placeholder="Titel (z. B. Q2 Review)"
required
class="rounded-md border border-white/10 bg-white/5 px-3 py-1.5 text-sm text-white placeholder:text-white/30 focus:border-white/20 focus:outline-none"
class="rounded-md border border-border bg-muted/30 px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground/60 focus:border-ring focus:outline-none"
/>
<input
type="text"
bind:value={newDescription}
placeholder="Beschreibung (optional)"
class="rounded-md border border-white/10 bg-white/5 px-3 py-1.5 text-sm text-white placeholder:text-white/30 focus:border-white/20 focus:outline-none"
class="rounded-md border border-border bg-muted/30 px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground/60 focus:border-ring focus:outline-none"
/>
<button
type="submit"
class="rounded-md bg-indigo-600 px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-indigo-700 disabled:cursor-not-allowed disabled:opacity-50"
class="rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50"
disabled={!newTitle.trim()}
>
Präsentation erstellen
@ -105,17 +105,17 @@
_siblingIds: decks.map((d) => d.id),
_siblingKey: 'deckId',
})}
class="mb-2 w-full rounded-md border border-white/10 px-3 py-2.5 text-left transition-colors hover:bg-white/5 min-h-[44px]"
class="mb-2 w-full rounded-md border border-border px-3 py-2.5 text-left transition-colors hover:bg-muted/50 min-h-[44px]"
>
<p class="truncate text-sm font-medium text-white/80">{deck.title}</p>
<div class="mt-1 flex items-center gap-2 text-xs text-white/40">
<p class="truncate text-sm font-medium text-foreground">{deck.title}</p>
<div class="mt-1 flex items-center gap-2 text-xs text-muted-foreground">
<span>{slideCount(deck.id)} Folien</span>
{#if deck.isPublic}
<span class="rounded bg-white/10 px-1.5 py-0.5 text-[10px]">Öffentlich</span>
<span class="rounded bg-muted px-1.5 py-0.5 text-[10px]">Öffentlich</span>
{/if}
</div>
{#if deck.description}
<p class="mt-1 truncate text-xs text-white/30">{deck.description}</p>
<p class="mt-1 truncate text-xs text-muted-foreground/70">{deck.description}</p>
{/if}
</button>
{/snippet}

View file

@ -59,10 +59,10 @@
const collections = $derived(collectionsQuery.value);
const statusColors: Record<string, string> = {
open: 'bg-blue-500/20 text-blue-300',
researching: 'bg-amber-500/20 text-amber-300',
answered: 'bg-green-500/20 text-green-300',
archived: 'bg-white/10 text-white/40',
open: 'bg-primary/20 text-primary',
researching: 'bg-warning/20 text-warning',
answered: 'bg-success/20 text-success',
archived: 'bg-muted text-muted-foreground',
};
const statusLabels: Record<string, string> = {
@ -82,12 +82,12 @@
<BaseListView items={sorted} getKey={(q) => q.id} emptyTitle="Keine offenen Fragen">
{#snippet toolbar()}
<div class="flex items-center justify-between">
<span class="text-xs text-white/40"
<span class="text-xs text-muted-foreground"
>{questions.length} Fragen · {collections.length} Sammlungen</span
>
<button
type="button"
class="text-xs text-white/50 transition-colors hover:text-white/80"
class="text-xs text-muted-foreground transition-colors hover:text-foreground"
onclick={() => (creating = !creating)}
>
{creating ? 'Abbrechen' : '+ Neue Frage'}
@ -95,23 +95,23 @@
</div>
{#if creating}
<form class="flex flex-col gap-2 rounded-lg bg-white/5 p-3" onsubmit={handleCreate}>
<form class="flex flex-col gap-2 rounded-lg bg-muted/30 p-3" onsubmit={handleCreate}>
<input
type="text"
bind:value={newTitle}
placeholder="Was möchtest du herausfinden?"
required
class="rounded-md border border-white/10 bg-white/5 px-3 py-1.5 text-sm text-white placeholder:text-white/30 focus:border-white/20 focus:outline-none"
class="rounded-md border border-border bg-muted/30 px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground/60 focus:border-ring focus:outline-none"
/>
<input
type="text"
bind:value={newDescription}
placeholder="Kontext / Details (optional)"
class="rounded-md border border-white/10 bg-white/5 px-3 py-1.5 text-sm text-white placeholder:text-white/30 focus:border-white/20 focus:outline-none"
class="rounded-md border border-border bg-muted/30 px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground/60 focus:border-ring focus:outline-none"
/>
<button
type="submit"
class="rounded-md bg-indigo-600 px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-indigo-700 disabled:cursor-not-allowed disabled:opacity-50"
class="rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50"
disabled={!newTitle.trim()}
>
Frage stellen
@ -133,10 +133,10 @@
_siblingIds: sorted.map((q) => q.id),
_siblingKey: 'questionId',
})}
class="mb-2 w-full text-left rounded-md border border-white/10 px-3 py-2.5 transition-colors hover:bg-white/5 cursor-pointer min-h-[44px]"
class="mb-2 w-full text-left rounded-md border border-border px-3 py-2.5 transition-colors hover:bg-muted/50 cursor-pointer min-h-[44px]"
>
<div class="flex items-start justify-between gap-2">
<p class="text-sm font-medium text-white/80">{question.title}</p>
<p class="text-sm font-medium text-foreground">{question.title}</p>
<span
class="shrink-0 rounded px-1.5 py-0.5 text-[10px] {statusColors[question.status] ?? ''}"
>
@ -144,12 +144,14 @@
</span>
</div>
{#if question.description}
<p class="mt-1 truncate text-xs text-white/30">{question.description}</p>
<p class="mt-1 truncate text-xs text-muted-foreground/70">{question.description}</p>
{/if}
{#if question.tags.length > 0}
<div class="mt-1 flex gap-1">
{#each question.tags.slice(0, 3) as tag}
<span class="rounded bg-white/5 px-1.5 py-0.5 text-[10px] text-white/40">{tag}</span>
<span class="rounded bg-muted/50 px-1.5 py-0.5 text-[10px] text-muted-foreground"
>{tag}</span
>
{/each}
</div>
{/if}

View file

@ -101,8 +101,8 @@
<button
onclick={handleStartStop}
class="flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors {timerStore.isRunning
? 'bg-red-500/80 text-white hover:bg-red-500'
: 'bg-white/10 text-white/50 hover:bg-green-500/80 hover:text-white'}"
? 'bg-error/80 text-white hover:bg-error'
: 'bg-muted text-muted-foreground hover:bg-success/80 hover:text-white'}"
>
{#if timerStore.isRunning}
<Stop size={14} weight="fill" />
@ -115,12 +115,12 @@
value={description}
oninput={(e) => handleDescriptionInput((e.target as HTMLInputElement).value)}
placeholder="Was trackst du?"
class="min-w-0 flex-1 rounded-md border border-white/10 bg-white/5 px-2.5 py-1.5 text-xs text-white/90 placeholder:text-white/30 focus:border-white/20 focus:outline-none"
class="min-w-0 flex-1 rounded-md border border-border bg-muted/30 px-2.5 py-1.5 text-xs text-foreground placeholder:text-muted-foreground/60 focus:border-ring focus:outline-none"
/>
{#if timerStore.isRunning}
<div class="flex h-7 items-center gap-1.5 rounded-full bg-green-500/10 px-2.5">
<div class="h-1.5 w-1.5 animate-pulse rounded-full bg-green-400"></div>
<span class="font-mono text-xs text-green-400">
<div class="flex h-7 items-center gap-1.5 rounded-full bg-success/10 px-2.5">
<div class="h-1.5 w-1.5 animate-pulse rounded-full bg-success"></div>
<span class="font-mono text-xs text-success">
{formatDuration(timerStore.elapsedSeconds)}
</span>
</div>
@ -132,21 +132,21 @@
<span class="flex-1"
>Heute: {todayEntries.length} Eintr{todayEntries.length === 1 ? 'ag' : 'age'}</span
>
<span class="font-medium text-white/60">{fmtCompact(totalToday)}</span>
<span class="font-medium text-foreground/80">{fmtCompact(totalToday)}</span>
{/snippet}
{#snippet item(entry)}
<button
onclick={() => navigate('detail', { entryId: entry.id })}
class="mb-1 w-full min-h-[44px] rounded-md px-3 py-2 text-left transition-colors hover:bg-white/5"
class="mb-1 w-full min-h-[44px] rounded-md px-3 py-2 text-left transition-colors hover:bg-muted/50"
>
<div class="flex items-center justify-between">
<p class="truncate text-sm text-white/80">
<p class="truncate text-sm text-foreground">
{entry.description || 'Ohne Beschreibung'}
</p>
<span class="shrink-0 text-xs text-white/50">{fmtCompact(entry.duration)}</span>
<span class="shrink-0 text-xs text-muted-foreground">{fmtCompact(entry.duration)}</span>
</div>
<p class="text-xs text-white/30">{projectName(entry.projectId)}</p>
<p class="text-xs text-muted-foreground/70">{projectName(entry.projectId)}</p>
</button>
{/snippet}
</BaseListView>

View file

@ -137,17 +137,17 @@
_siblingIds: sorted.map((l) => l.id),
_siblingKey: 'linkId',
})}
class="mb-1 w-full min-h-[44px] text-left rounded-md px-3 py-2 transition-colors hover:bg-white/5 cursor-pointer"
class="mb-1 w-full min-h-[44px] text-left rounded-md px-3 py-2 transition-colors hover:bg-muted/50 cursor-pointer"
>
<div class="flex items-center justify-between">
<p class="truncate text-sm font-medium text-white/80">
<p class="truncate text-sm font-medium text-foreground">
{link.title || link.shortCode}
</p>
<span class="shrink-0 text-xs text-white/40">{link.clickCount}</span>
<span class="shrink-0 text-xs text-muted-foreground">{link.clickCount}</span>
</div>
<p class="truncate text-xs text-white/30">{hostname(link.originalUrl)}</p>
<p class="truncate text-xs text-muted-foreground/70">{hostname(link.originalUrl)}</p>
{#if link.customCode}
<p class="text-xs text-blue-400/60">/{link.customCode}</p>
<p class="text-xs text-primary/70">/{link.customCode}</p>
{/if}
</button>
{/snippet}

View file

@ -119,25 +119,25 @@
<div class="flex h-full flex-col gap-6 p-3 sm:p-4">
<!-- Header -->
<div>
<h1 class="text-2xl font-bold text-white/90">Who?</h1>
<p class="mt-1 text-sm text-white/60">
<h1 class="text-2xl font-bold text-foreground">Who?</h1>
<p class="mt-1 text-sm text-muted-foreground">
Errate die historische Persönlichkeit. Eine KI verkörpert sie ohne den Namen zu verraten.
</p>
</div>
<!-- Deck picker -->
<section>
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wide text-white/50">
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground">
Neues Spiel starten
</h2>
{#if loadingDecks}
<div class="grid gap-3 sm:grid-cols-2">
{#each Array(4) as _, i (i)}
<div class="h-24 animate-pulse rounded-lg bg-white/5"></div>
<div class="h-24 animate-pulse rounded-lg bg-muted/30"></div>
{/each}
</div>
{:else if decks.length === 0}
<p class="text-sm text-white/40">Keine Decks verfügbar.</p>
<p class="text-sm text-muted-foreground">Keine Decks verfügbar.</p>
{:else}
<div class="grid gap-3 sm:grid-cols-2">
{#each decks as deck (deck.id)}
@ -145,23 +145,23 @@
type="button"
onclick={() => startGame(deck.id)}
disabled={starting !== null}
class="group flex flex-col items-start gap-2 rounded-lg border border-white/10 bg-white/[0.02] p-4 text-left transition hover:border-white/20 hover:bg-white/[0.05] disabled:cursor-wait disabled:opacity-50"
class="group flex flex-col items-start gap-2 rounded-lg border border-border bg-muted/20 p-4 text-left transition hover:border-border-strong hover:bg-muted/40 disabled:cursor-wait disabled:opacity-50"
style="border-left: 3px solid {deckColor(deck.id)}"
>
<div class="flex w-full items-center justify-between">
<span class="text-base font-medium text-white/90">{deck.name.de}</span>
<span class="text-base font-medium text-foreground">{deck.name.de}</span>
<span
class="rounded-full bg-white/5 px-2 py-0.5 text-[10px] uppercase tracking-wide text-white/50"
class="rounded-full bg-muted px-2 py-0.5 text-[10px] uppercase tracking-wide text-muted-foreground"
>
{difficultyLabel(deck.difficulty)}
</span>
</div>
<p class="text-xs text-white/60">{deck.description.de}</p>
<p class="text-[11px] text-white/40">
<p class="text-xs text-muted-foreground">{deck.description.de}</p>
<p class="text-[11px] text-muted-foreground/70">
{deck.characterCount} Personen · {deck.categories.join(', ')}
</p>
{#if starting === deck.id}
<p class="text-xs text-white/70">Starte…</p>
<p class="text-xs text-foreground/80">Starte…</p>
{/if}
</button>
{/each}
@ -172,30 +172,30 @@
<!-- Past games -->
{#if games.length > 0}
<section>
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wide text-white/50">
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground">
Vergangene Spiele
</h2>
<ul class="divide-y divide-white/5 rounded-lg border border-white/10 bg-white/[0.02]">
<ul class="divide-y divide-border rounded-lg border border-border bg-muted/20">
{#each games as game (game.id)}
<li class="flex items-center gap-3 px-3 py-2.5">
<span class="text-lg">{statusEmoji(game.status)}</span>
<button type="button" class="flex-1 text-left" onclick={() => openGame(game.id)}>
<div class="text-sm text-white/90">
<div class="text-sm text-foreground">
{#if game.revealedName}
<span class="font-medium">{game.revealedName}</span>
{:else if game.status === 'playing'}
<span class="text-white/60">Laufendes Spiel</span>
<span class="text-muted-foreground">Laufendes Spiel</span>
{:else}
<span class="text-white/60">Aufgegeben</span>
<span class="text-muted-foreground">Aufgegeben</span>
{/if}
</div>
<div class="text-[11px] text-white/40">
<div class="text-[11px] text-muted-foreground/70">
{game.deckId} · {game.messageCount} Nachrichten · {gameStatusLabel(game.status)}
</div>
</button>
<button
type="button"
class="rounded p-1 text-white/30 hover:bg-white/5 hover:text-white/60"
class="rounded p-1 text-muted-foreground/70 hover:bg-muted hover:text-foreground"
onclick={() => deleteGame(game.id)}
title="Löschen"
>
@ -208,7 +208,7 @@
{/if}
{#if error}
<div class="rounded-lg border border-red-500/30 bg-red-500/10 p-3 text-sm text-red-300">
<div class="rounded-lg border border-error/30 bg-error/10 p-3 text-sm text-error">
{error}
</div>
{/if}

View file

@ -22,7 +22,7 @@
"validate:turbo": "node scripts/validate-no-recursive-turbo.mjs",
"validate:pg-schema": "node scripts/validate-pg-schema-isolation.mjs",
"validate:theme-tokens": "node scripts/validate-theme-tokens.mjs",
"validate:all": "pnpm run validate:turbo && pnpm run validate:pg-schema && pnpm run check:crypto",
"validate:all": "pnpm run validate:turbo && pnpm run validate:pg-schema && pnpm run validate:theme-tokens && pnpm run check:crypto",
"check:crypto": "node scripts/audit-crypto-registry.mjs",
"check:crypto:seed": "node scripts/audit-crypto-registry.mjs --seed",
"audit:deps": "node scripts/audit-workspace-deps.mjs",