mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:41:09 +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),
|
_siblingIds: locations.map((l) => l.id),
|
||||||
_siblingKey: 'locationId',
|
_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
|
<div
|
||||||
class="mt-0.5 h-2.5 w-2.5 shrink-0 rounded-full"
|
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>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<div class="flex items-center gap-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)}
|
{#if favoriteIds.has(location.id)}
|
||||||
<span class="text-xs text-yellow-400">★</span>
|
<span class="text-xs text-warning">★</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-white/40">
|
<p class="text-xs text-muted-foreground">
|
||||||
{categoryLabels[location.category] ?? location.category}
|
{categoryLabels[location.category] ?? location.category}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@
|
||||||
<input
|
<input
|
||||||
bind:value={query}
|
bind:value={query}
|
||||||
placeholder="Guides durchsuchen..."
|
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 -->
|
<!-- Category filter + create toggle -->
|
||||||
|
|
@ -97,8 +97,8 @@
|
||||||
onclick={() => (activeCategory = cat.id)}
|
onclick={() => (activeCategory = cat.id)}
|
||||||
class="rounded-full px-3 py-1 text-xs transition-colors
|
class="rounded-full px-3 py-1 text-xs transition-colors
|
||||||
{activeCategory === cat.id
|
{activeCategory === cat.id
|
||||||
? 'bg-white/15 text-white'
|
? 'bg-primary/20 text-primary'
|
||||||
: 'bg-white/5 text-white/50 hover:bg-white/10'}"
|
: 'bg-muted/30 text-muted-foreground hover:bg-muted'}"
|
||||||
>
|
>
|
||||||
{cat.label}
|
{cat.label}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -106,7 +106,7 @@
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="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)}
|
onclick={() => (creating = !creating)}
|
||||||
>
|
>
|
||||||
{creating ? 'Abbrechen' : '+ Neuer Guide'}
|
{creating ? 'Abbrechen' : '+ Neuer Guide'}
|
||||||
|
|
@ -114,17 +114,17 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if creating}
|
{#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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={newTitle}
|
bind:value={newTitle}
|
||||||
placeholder="Guide-Titel"
|
placeholder="Guide-Titel"
|
||||||
required
|
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
|
<select
|
||||||
bind:value={newCategory}
|
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)}
|
{#each Object.entries(GUIDE_CATEGORIES) as [key, info] (key)}
|
||||||
<option value={key}>{info.label}</option>
|
<option value={key}>{info.label}</option>
|
||||||
|
|
@ -132,7 +132,7 @@
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
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()}
|
disabled={!newTitle.trim()}
|
||||||
>
|
>
|
||||||
Guide erstellen
|
Guide erstellen
|
||||||
|
|
@ -152,28 +152,28 @@
|
||||||
{@const progress = getStepProgress(run ?? null, totalSteps)}
|
{@const progress = getStepProgress(run ?? null, totalSteps)}
|
||||||
<button
|
<button
|
||||||
onclick={() => openGuide(guide)}
|
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">
|
<div class="mb-1 flex items-center gap-2">
|
||||||
<span class="inline-block h-2 w-2 rounded-full {meta.color}"></span>
|
<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="text-xs text-muted-foreground">{meta.label}</span>
|
||||||
<span class="ml-auto text-xs text-white/30">{guide.estimatedMinutes} min</span>
|
<span class="ml-auto text-xs text-muted-foreground/70">{guide.estimatedMinutes} min</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-sm font-medium text-white/90">{guide.title}</h3>
|
<h3 class="text-sm font-medium text-foreground">{guide.title}</h3>
|
||||||
<p class="mt-0.5 text-xs text-white/50">{guide.description}</p>
|
<p class="mt-0.5 text-xs text-muted-foreground">{guide.description}</p>
|
||||||
<div class="mt-1.5 flex items-center gap-2">
|
<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]}
|
{DIFFICULTY_LABELS[guide.difficulty]}
|
||||||
</span>
|
</span>
|
||||||
{#if run}
|
{#if run}
|
||||||
{#if run.completedAt}
|
{#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}
|
{:else if totalSteps > 0}
|
||||||
<div class="ml-auto flex items-center gap-1.5">
|
<div class="ml-auto flex items-center gap-1.5">
|
||||||
<div class="h-1 w-12 rounded-full bg-white/10">
|
<div class="h-1 w-12 rounded-full bg-muted">
|
||||||
<div class="h-full rounded-full bg-teal-400" style="width: {progress}%"></div>
|
<div class="h-full rounded-full bg-primary" style="width: {progress}%"></div>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-[10px] text-white/30">{progress}%</span>
|
<span class="text-[10px] text-muted-foreground/70">{progress}%</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -108,17 +108,17 @@
|
||||||
_siblingIds: collections.map((c) => c.id),
|
_siblingIds: collections.map((c) => c.id),
|
||||||
_siblingKey: 'collectionId',
|
_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">
|
<div class="flex items-center gap-2">
|
||||||
{#if collection.icon}
|
{#if collection.icon}
|
||||||
<span class="text-sm">{collection.icon}</span>
|
<span class="text-sm">{collection.icon}</span>
|
||||||
{/if}
|
{/if}
|
||||||
<p class="flex-1 truncate text-sm font-medium text-white/80">{collection.name}</p>
|
<p class="flex-1 truncate text-sm font-medium text-foreground">{collection.name}</p>
|
||||||
<span class="text-xs text-white/40">{itemsInCollection(collection.id)}</span>
|
<span class="text-xs text-muted-foreground">{itemsInCollection(collection.id)}</span>
|
||||||
</div>
|
</div>
|
||||||
{#if collection.description}
|
{#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}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusColors: Record<string, string> = {
|
const statusColors: Record<string, string> = {
|
||||||
pending: 'bg-yellow-500/20 text-yellow-300',
|
pending: 'bg-warning/20 text-warning',
|
||||||
processing: 'bg-blue-500/20 text-blue-300',
|
processing: 'bg-primary/20 text-primary',
|
||||||
completed: 'bg-green-500/20 text-green-300',
|
completed: 'bg-success/20 text-success',
|
||||||
failed: 'bg-red-500/20 text-red-300',
|
failed: 'bg-error/20 text-error',
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -80,26 +80,26 @@
|
||||||
_siblingIds: sorted.map((m) => m.id),
|
_siblingIds: sorted.map((m) => m.id),
|
||||||
_siblingKey: 'memoId',
|
_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="flex items-start justify-between gap-2">
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
{#if memo.isPinned}
|
{#if memo.isPinned}
|
||||||
<span class="text-xs text-white/30">📌</span>
|
<span class="text-xs text-muted-foreground/70">📌</span>
|
||||||
{/if}
|
{/if}
|
||||||
<p class="truncate text-sm font-medium text-white/80">
|
<p class="truncate text-sm font-medium text-foreground">
|
||||||
{memo.title || 'Unbenanntes Memo'}
|
{memo.title || 'Unbenanntes Memo'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{#if memo.intro}
|
{#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}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1.5 shrink-0">
|
<div class="flex items-center gap-1.5 shrink-0">
|
||||||
{#if memo.transcriptModel && memo.processingStatus === 'completed'}
|
{#if memo.transcriptModel && memo.processingStatus === 'completed'}
|
||||||
<span
|
<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"
|
title="STT-Pipeline"
|
||||||
>
|
>
|
||||||
{memo.transcriptModel}
|
{memo.transcriptModel}
|
||||||
|
|
|
||||||
|
|
@ -242,7 +242,7 @@
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
{#snippet item(image)}
|
{#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}
|
{#if image.publicUrl}
|
||||||
<img
|
<img
|
||||||
src={image.publicUrl}
|
src={image.publicUrl}
|
||||||
|
|
@ -251,12 +251,12 @@
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
{:else}
|
{: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'}
|
{image.format ?? 'img'}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if image.isFavorite}
|
{#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}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
@ -334,7 +334,7 @@
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
.upload-thumb.success {
|
.upload-thumb.success {
|
||||||
outline: 2px solid #22c55e;
|
outline: 2px solid hsl(var(--color-success));
|
||||||
outline-offset: -2px;
|
outline-offset: -2px;
|
||||||
}
|
}
|
||||||
.upload-thumb.error {
|
.upload-thumb.error {
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@
|
||||||
<div class="flex items-center justify-end">
|
<div class="flex items-center justify-end">
|
||||||
<button
|
<button
|
||||||
type="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)}
|
onclick={() => (creating = !creating)}
|
||||||
>
|
>
|
||||||
{creating
|
{creating
|
||||||
|
|
@ -84,13 +84,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if creating}
|
{#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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={newName}
|
bind:value={newName}
|
||||||
placeholder={$_('plants.create.namePlaceholder', { default: 'Name (z. B. Monstera)' })}
|
placeholder={$_('plants.create.namePlaceholder', { default: 'Name (z. B. Monstera)' })}
|
||||||
required
|
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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -98,11 +98,11 @@
|
||||||
placeholder={$_('plants.create.scientificPlaceholder', {
|
placeholder={$_('plants.create.scientificPlaceholder', {
|
||||||
default: 'Botanischer Name (optional)',
|
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
|
<button
|
||||||
type="submit"
|
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()}
|
disabled={!newName.trim()}
|
||||||
>
|
>
|
||||||
{$_('plants.create.save', { default: 'Pflanze anlegen' })}
|
{$_('plants.create.save', { default: 'Pflanze anlegen' })}
|
||||||
|
|
@ -114,12 +114,12 @@
|
||||||
{#snippet header()}
|
{#snippet header()}
|
||||||
<span>{$_('plants.list.count', { values: { count: plants.length } })}</span>
|
<span>{$_('plants.list.count', { values: { count: plants.length } })}</span>
|
||||||
{#if dueForWatering.length > 0}
|
{#if dueForWatering.length > 0}
|
||||||
<span class="text-blue-400"
|
<span class="text-primary"
|
||||||
>{$_('plants.list.dueWatering', { values: { count: dueForWatering.length } })}</span
|
>{$_('plants.list.dueWatering', { values: { count: dueForWatering.length } })}</span
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{#if needsAttention.length > 0}
|
{#if needsAttention.length > 0}
|
||||||
<span class="text-amber-400"
|
<span class="text-warning"
|
||||||
>{$_('plants.list.needsCare', { values: { count: needsAttention.length } })}</span
|
>{$_('plants.list.needsCare', { values: { count: needsAttention.length } })}</span
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -135,24 +135,24 @@
|
||||||
_siblingIds: plants.map((p) => p.id),
|
_siblingIds: plants.map((p) => p.id),
|
||||||
_siblingKey: 'plantId',
|
_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">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-sm">
|
<span class="text-sm">
|
||||||
{@html healthIcons[plant.healthStatus ?? 'healthy'] ?? '🌱'}
|
{@html healthIcons[plant.healthStatus ?? 'healthy'] ?? '🌱'}
|
||||||
</span>
|
</span>
|
||||||
<div class="min-w-0 flex-1">
|
<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}
|
{#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}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if waterDue}
|
{#if waterDue}
|
||||||
<span class="text-xs text-blue-400">💧</span>
|
<span class="text-xs text-primary">💧</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if schedule}
|
{#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 } })}
|
{$_('plants.list.everyXDays', { values: { days: schedule.frequencyDays } })}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -91,10 +91,10 @@
|
||||||
<!-- Model selector -->
|
<!-- Model selector -->
|
||||||
<select
|
<select
|
||||||
bind:value={selectedModel}
|
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}
|
{#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}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|
@ -103,21 +103,23 @@
|
||||||
{#each messages as msg}
|
{#each messages as msg}
|
||||||
<div
|
<div
|
||||||
class="mb-2 min-h-[44px] rounded-md px-3 py-2 {msg.role === 'user'
|
class="mb-2 min-h-[44px] rounded-md px-3 py-2 {msg.role === 'user'
|
||||||
? 'bg-white/5'
|
? 'bg-muted/30'
|
||||||
: 'bg-blue-500/10'}"
|
: '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}
|
{#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}
|
{:else}
|
||||||
<p class="text-sm text-white/30">…</p>
|
<p class="text-sm text-muted-foreground/70">…</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
{#if messages.length === 0}
|
{#if messages.length === 0}
|
||||||
<div class="flex h-full items-center justify-center">
|
<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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -133,20 +135,20 @@
|
||||||
<input
|
<input
|
||||||
bind:value={prompt}
|
bind:value={prompt}
|
||||||
placeholder="Prompt eingeben..."
|
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}
|
{#if isLoading}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={stop}
|
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
|
>Stop</button
|
||||||
>
|
>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!prompt.trim()}
|
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
|
>▶</button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -57,10 +57,10 @@
|
||||||
<BaseListView items={decks} getKey={(d) => d.id} emptyTitle="Keine Präsentationen">
|
<BaseListView items={decks} getKey={(d) => d.id} emptyTitle="Keine Präsentationen">
|
||||||
{#snippet toolbar()}
|
{#snippet toolbar()}
|
||||||
<div class="flex items-center justify-between">
|
<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
|
<button
|
||||||
type="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)}
|
onclick={() => (creating = !creating)}
|
||||||
>
|
>
|
||||||
{creating ? 'Abbrechen' : '+ Neue Präsentation'}
|
{creating ? 'Abbrechen' : '+ Neue Präsentation'}
|
||||||
|
|
@ -68,23 +68,23 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if creating}
|
{#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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={newTitle}
|
bind:value={newTitle}
|
||||||
placeholder="Titel (z. B. Q2 Review)"
|
placeholder="Titel (z. B. Q2 Review)"
|
||||||
required
|
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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={newDescription}
|
bind:value={newDescription}
|
||||||
placeholder="Beschreibung (optional)"
|
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
|
<button
|
||||||
type="submit"
|
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()}
|
disabled={!newTitle.trim()}
|
||||||
>
|
>
|
||||||
Präsentation erstellen
|
Präsentation erstellen
|
||||||
|
|
@ -105,17 +105,17 @@
|
||||||
_siblingIds: decks.map((d) => d.id),
|
_siblingIds: decks.map((d) => d.id),
|
||||||
_siblingKey: 'deckId',
|
_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>
|
<p class="truncate text-sm font-medium text-foreground">{deck.title}</p>
|
||||||
<div class="mt-1 flex items-center gap-2 text-xs text-white/40">
|
<div class="mt-1 flex items-center gap-2 text-xs text-muted-foreground">
|
||||||
<span>{slideCount(deck.id)} Folien</span>
|
<span>{slideCount(deck.id)} Folien</span>
|
||||||
{#if deck.isPublic}
|
{#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}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if deck.description}
|
{#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}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
|
||||||
|
|
@ -59,10 +59,10 @@
|
||||||
const collections = $derived(collectionsQuery.value);
|
const collections = $derived(collectionsQuery.value);
|
||||||
|
|
||||||
const statusColors: Record<string, string> = {
|
const statusColors: Record<string, string> = {
|
||||||
open: 'bg-blue-500/20 text-blue-300',
|
open: 'bg-primary/20 text-primary',
|
||||||
researching: 'bg-amber-500/20 text-amber-300',
|
researching: 'bg-warning/20 text-warning',
|
||||||
answered: 'bg-green-500/20 text-green-300',
|
answered: 'bg-success/20 text-success',
|
||||||
archived: 'bg-white/10 text-white/40',
|
archived: 'bg-muted text-muted-foreground',
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusLabels: Record<string, string> = {
|
const statusLabels: Record<string, string> = {
|
||||||
|
|
@ -82,12 +82,12 @@
|
||||||
<BaseListView items={sorted} getKey={(q) => q.id} emptyTitle="Keine offenen Fragen">
|
<BaseListView items={sorted} getKey={(q) => q.id} emptyTitle="Keine offenen Fragen">
|
||||||
{#snippet toolbar()}
|
{#snippet toolbar()}
|
||||||
<div class="flex items-center justify-between">
|
<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
|
>{questions.length} Fragen · {collections.length} Sammlungen</span
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="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)}
|
onclick={() => (creating = !creating)}
|
||||||
>
|
>
|
||||||
{creating ? 'Abbrechen' : '+ Neue Frage'}
|
{creating ? 'Abbrechen' : '+ Neue Frage'}
|
||||||
|
|
@ -95,23 +95,23 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if creating}
|
{#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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={newTitle}
|
bind:value={newTitle}
|
||||||
placeholder="Was möchtest du herausfinden?"
|
placeholder="Was möchtest du herausfinden?"
|
||||||
required
|
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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={newDescription}
|
bind:value={newDescription}
|
||||||
placeholder="Kontext / Details (optional)"
|
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
|
<button
|
||||||
type="submit"
|
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()}
|
disabled={!newTitle.trim()}
|
||||||
>
|
>
|
||||||
Frage stellen
|
Frage stellen
|
||||||
|
|
@ -133,10 +133,10 @@
|
||||||
_siblingIds: sorted.map((q) => q.id),
|
_siblingIds: sorted.map((q) => q.id),
|
||||||
_siblingKey: 'questionId',
|
_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">
|
<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
|
<span
|
||||||
class="shrink-0 rounded px-1.5 py-0.5 text-[10px] {statusColors[question.status] ?? ''}"
|
class="shrink-0 rounded px-1.5 py-0.5 text-[10px] {statusColors[question.status] ?? ''}"
|
||||||
>
|
>
|
||||||
|
|
@ -144,12 +144,14 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{#if question.description}
|
{#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}
|
||||||
{#if question.tags.length > 0}
|
{#if question.tags.length > 0}
|
||||||
<div class="mt-1 flex gap-1">
|
<div class="mt-1 flex gap-1">
|
||||||
{#each question.tags.slice(0, 3) as tag}
|
{#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}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -101,8 +101,8 @@
|
||||||
<button
|
<button
|
||||||
onclick={handleStartStop}
|
onclick={handleStartStop}
|
||||||
class="flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors {timerStore.isRunning
|
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-error/80 text-white hover:bg-error'
|
||||||
: 'bg-white/10 text-white/50 hover:bg-green-500/80 hover:text-white'}"
|
: 'bg-muted text-muted-foreground hover:bg-success/80 hover:text-white'}"
|
||||||
>
|
>
|
||||||
{#if timerStore.isRunning}
|
{#if timerStore.isRunning}
|
||||||
<Stop size={14} weight="fill" />
|
<Stop size={14} weight="fill" />
|
||||||
|
|
@ -115,12 +115,12 @@
|
||||||
value={description}
|
value={description}
|
||||||
oninput={(e) => handleDescriptionInput((e.target as HTMLInputElement).value)}
|
oninput={(e) => handleDescriptionInput((e.target as HTMLInputElement).value)}
|
||||||
placeholder="Was trackst du?"
|
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}
|
{#if timerStore.isRunning}
|
||||||
<div class="flex h-7 items-center gap-1.5 rounded-full bg-green-500/10 px-2.5">
|
<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-green-400"></div>
|
<div class="h-1.5 w-1.5 animate-pulse rounded-full bg-success"></div>
|
||||||
<span class="font-mono text-xs text-green-400">
|
<span class="font-mono text-xs text-success">
|
||||||
{formatDuration(timerStore.elapsedSeconds)}
|
{formatDuration(timerStore.elapsedSeconds)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -132,21 +132,21 @@
|
||||||
<span class="flex-1"
|
<span class="flex-1"
|
||||||
>Heute: {todayEntries.length} Eintr{todayEntries.length === 1 ? 'ag' : 'age'}</span
|
>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}
|
||||||
|
|
||||||
{#snippet item(entry)}
|
{#snippet item(entry)}
|
||||||
<button
|
<button
|
||||||
onclick={() => navigate('detail', { entryId: entry.id })}
|
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">
|
<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'}
|
{entry.description || 'Ohne Beschreibung'}
|
||||||
</p>
|
</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>
|
</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>
|
</button>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</BaseListView>
|
</BaseListView>
|
||||||
|
|
|
||||||
|
|
@ -137,17 +137,17 @@
|
||||||
_siblingIds: sorted.map((l) => l.id),
|
_siblingIds: sorted.map((l) => l.id),
|
||||||
_siblingKey: 'linkId',
|
_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">
|
<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}
|
{link.title || link.shortCode}
|
||||||
</p>
|
</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>
|
</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}
|
{#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}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
|
||||||
|
|
@ -119,25 +119,25 @@
|
||||||
<div class="flex h-full flex-col gap-6 p-3 sm:p-4">
|
<div class="flex h-full flex-col gap-6 p-3 sm:p-4">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-bold text-white/90">Who?</h1>
|
<h1 class="text-2xl font-bold text-foreground">Who?</h1>
|
||||||
<p class="mt-1 text-sm text-white/60">
|
<p class="mt-1 text-sm text-muted-foreground">
|
||||||
Errate die historische Persönlichkeit. Eine KI verkörpert sie ohne den Namen zu verraten.
|
Errate die historische Persönlichkeit. Eine KI verkörpert sie ohne den Namen zu verraten.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Deck picker -->
|
<!-- Deck picker -->
|
||||||
<section>
|
<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
|
Neues Spiel starten
|
||||||
</h2>
|
</h2>
|
||||||
{#if loadingDecks}
|
{#if loadingDecks}
|
||||||
<div class="grid gap-3 sm:grid-cols-2">
|
<div class="grid gap-3 sm:grid-cols-2">
|
||||||
{#each Array(4) as _, i (i)}
|
{#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}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else if decks.length === 0}
|
{: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}
|
{:else}
|
||||||
<div class="grid gap-3 sm:grid-cols-2">
|
<div class="grid gap-3 sm:grid-cols-2">
|
||||||
{#each decks as deck (deck.id)}
|
{#each decks as deck (deck.id)}
|
||||||
|
|
@ -145,23 +145,23 @@
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => startGame(deck.id)}
|
onclick={() => startGame(deck.id)}
|
||||||
disabled={starting !== null}
|
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)}"
|
style="border-left: 3px solid {deckColor(deck.id)}"
|
||||||
>
|
>
|
||||||
<div class="flex w-full items-center justify-between">
|
<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
|
<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)}
|
{difficultyLabel(deck.difficulty)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-white/60">{deck.description.de}</p>
|
<p class="text-xs text-muted-foreground">{deck.description.de}</p>
|
||||||
<p class="text-[11px] text-white/40">
|
<p class="text-[11px] text-muted-foreground/70">
|
||||||
{deck.characterCount} Personen · {deck.categories.join(', ')}
|
{deck.characterCount} Personen · {deck.categories.join(', ')}
|
||||||
</p>
|
</p>
|
||||||
{#if starting === deck.id}
|
{#if starting === deck.id}
|
||||||
<p class="text-xs text-white/70">Starte…</p>
|
<p class="text-xs text-foreground/80">Starte…</p>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
@ -172,30 +172,30 @@
|
||||||
<!-- Past games -->
|
<!-- Past games -->
|
||||||
{#if games.length > 0}
|
{#if games.length > 0}
|
||||||
<section>
|
<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
|
Vergangene Spiele
|
||||||
</h2>
|
</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)}
|
{#each games as game (game.id)}
|
||||||
<li class="flex items-center gap-3 px-3 py-2.5">
|
<li class="flex items-center gap-3 px-3 py-2.5">
|
||||||
<span class="text-lg">{statusEmoji(game.status)}</span>
|
<span class="text-lg">{statusEmoji(game.status)}</span>
|
||||||
<button type="button" class="flex-1 text-left" onclick={() => openGame(game.id)}>
|
<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}
|
{#if game.revealedName}
|
||||||
<span class="font-medium">{game.revealedName}</span>
|
<span class="font-medium">{game.revealedName}</span>
|
||||||
{:else if game.status === 'playing'}
|
{:else if game.status === 'playing'}
|
||||||
<span class="text-white/60">Laufendes Spiel</span>
|
<span class="text-muted-foreground">Laufendes Spiel</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="text-white/60">Aufgegeben</span>
|
<span class="text-muted-foreground">Aufgegeben</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-[11px] text-white/40">
|
<div class="text-[11px] text-muted-foreground/70">
|
||||||
{game.deckId} · {game.messageCount} Nachrichten · {gameStatusLabel(game.status)}
|
{game.deckId} · {game.messageCount} Nachrichten · {gameStatusLabel(game.status)}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="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)}
|
onclick={() => deleteGame(game.id)}
|
||||||
title="Löschen"
|
title="Löschen"
|
||||||
>
|
>
|
||||||
|
|
@ -208,7 +208,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if error}
|
{#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}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
"validate:turbo": "node scripts/validate-no-recursive-turbo.mjs",
|
"validate:turbo": "node scripts/validate-no-recursive-turbo.mjs",
|
||||||
"validate:pg-schema": "node scripts/validate-pg-schema-isolation.mjs",
|
"validate:pg-schema": "node scripts/validate-pg-schema-isolation.mjs",
|
||||||
"validate:theme-tokens": "node scripts/validate-theme-tokens.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": "node scripts/audit-crypto-registry.mjs",
|
||||||
"check:crypto:seed": "node scripts/audit-crypto-registry.mjs --seed",
|
"check:crypto:seed": "node scripts/audit-crypto-registry.mjs --seed",
|
||||||
"audit:deps": "node scripts/audit-workspace-deps.mjs",
|
"audit:deps": "node scripts/audit-workspace-deps.mjs",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue