mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 18:41:08 +02:00
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:
parent
a2a43b1d5a
commit
86c205ffc5
13 changed files with 129 additions and 124 deletions
|
|
@ -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">★</span>
|
||||
<span class="text-xs text-warning">★</span>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="text-xs text-white/40">
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{categoryLabels[location.category] ?? location.category}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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">📌</span>
|
||||
<span class="text-xs text-muted-foreground/70">📌</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}
|
||||
|
|
|
|||
|
|
@ -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">★</span>
|
||||
<span class="absolute right-1 top-1 text-xs text-warning">★</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 {
|
||||
|
|
|
|||
|
|
@ -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'] ?? '🌱'}
|
||||
</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">💧</span>
|
||||
<span class="text-xs text-primary">💧</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}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>▶</button
|
||||
>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue