refactor(theming): migrate remaining 738 token violations across routes + components

Expand validate-theme-tokens.mjs scope from ListViews only to all
lib/modules/**/*.svelte and routes/(app)/**/*.svelte. Add a second rule
banning the neutral Tailwind palette (gray/slate/zinc/neutral/stone-N)
— these should be theme tokens (bg-card, bg-muted, text-foreground,
text-muted-foreground, border-border) instead.

Apply one-shot codemod (scripts/migrate-theme-tokens.mjs) that
replaces:
  bg-gray-800/900        → bg-card
  bg-gray-600/700        → bg-muted (with opacity preserved)
  border-gray-600..900   → border-border
  text-gray-800/900      → text-foreground
  text-gray-300          → text-foreground/90
  text-gray-400/500/700  → text-muted-foreground
  placeholder-gray-*     → placeholder:text-muted-foreground/60
  bg/border-white/N      → bg-muted/N, border-border/N
  text-white/70-90       → text-foreground
  text-white/40-60       → text-muted-foreground
  text-white/10-30       → text-muted-foreground/70

42 files touched; biggest: presi/deck/[id] (91 subs), uload/analytics
(58), uload/+page (53), presi/+page (47), who/PlayView (35),
skilltree/Edit+AddXpModal (28 each), context/* (115 across 4 pages),
uload/links+tags (50 across 2).

Brand-literal overlays in moodlit/components/mood/{MoodFullscreen,
MoodCard,CreateMoodDialog}.svelte stay unmigrated — they render on
vivid colour gradients. Validator exempts these 3 files from the
white-alpha rule; they still obey the neutral-palette rule.

Result: 527 files pass validate:theme-tokens; svelte-check clean with
0 errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-22 15:42:55 +02:00
parent db2023a77f
commit 7d6a340b13
44 changed files with 747 additions and 460 deletions

View file

@ -143,8 +143,8 @@
<div class="meta"> <div class="meta">
<div class="meta-badges"> <div class="meta-badges">
<span class="badge {catInfo.color}">{catInfo.label}</span> <span class="badge {catInfo.color}">{catInfo.label}</span>
<span class="badge bg-white/10">{DIFFICULTY_LABELS[guide.difficulty]}</span> <span class="badge bg-muted/10">{DIFFICULTY_LABELS[guide.difficulty]}</span>
<span class="badge bg-white/10">{guide.estimatedMinutes} min</span> <span class="badge bg-muted/10">{guide.estimatedMinutes} min</span>
</div> </div>
<h1 class="title">{guide.title}</h1> <h1 class="title">{guide.title}</h1>
<p class="description">{guide.description}</p> <p class="description">{guide.description}</p>

View file

@ -21,7 +21,7 @@
lent: 'bg-amber-100 text-amber-800 dark:bg-amber-900/20 dark:text-amber-400', lent: 'bg-amber-100 text-amber-800 dark:bg-amber-900/20 dark:text-amber-400',
stored: 'bg-blue-100 text-blue-800 dark:bg-blue-900/20 dark:text-blue-400', stored: 'bg-blue-100 text-blue-800 dark:bg-blue-900/20 dark:text-blue-400',
for_sale: 'bg-purple-100 text-purple-800 dark:bg-purple-900/20 dark:text-purple-400', for_sale: 'bg-purple-100 text-purple-800 dark:bg-purple-900/20 dark:text-purple-400',
disposed: 'bg-gray-100 text-gray-800 dark:bg-gray-900/20 dark:text-gray-400', disposed: 'bg-muted text-foreground dark:bg-card/20 dark:text-muted-foreground',
}; };
</script> </script>

View file

@ -20,7 +20,7 @@
<div <div
class="relative rounded-xl border p-4 transition-all duration-200 {achievement.unlocked class="relative rounded-xl border p-4 transition-all duration-200 {achievement.unlocked
? `${rarity.bgColor} ${rarity.borderColor}` ? `${rarity.bgColor} ${rarity.borderColor}`
: 'border-gray-700/50 bg-gray-800/30'} {achievement.unlocked : 'border-border/50 bg-card/30'} {achievement.unlocked
? 'hover:-translate-y-0.5 hover:shadow-lg' ? 'hover:-translate-y-0.5 hover:shadow-lg'
: 'opacity-70'}" : 'opacity-70'}"
> >
@ -36,34 +36,38 @@
<div <div
class="flex h-12 w-12 shrink-0 items-center justify-center rounded-full {achievement.unlocked class="flex h-12 w-12 shrink-0 items-center justify-center rounded-full {achievement.unlocked
? 'bg-yellow-500/20' ? 'bg-yellow-500/20'
: 'bg-gray-700/50'}" : 'bg-muted/50'}"
> >
{#if achievement.unlocked} {#if achievement.unlocked}
<Trophy class="h-6 w-6 text-yellow-400" /> <Trophy class="h-6 w-6 text-yellow-400" />
{:else} {:else}
<Lock class="h-6 w-6 text-gray-500" /> <Lock class="h-6 w-6 text-muted-foreground" />
{/if} {/if}
</div> </div>
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<!-- Name --> <!-- Name -->
<h3 class="font-semibold {achievement.unlocked ? 'text-white' : 'text-gray-400'}"> <h3 class="font-semibold {achievement.unlocked ? 'text-white' : 'text-muted-foreground'}">
{achievement.name} {achievement.name}
</h3> </h3>
<!-- Description --> <!-- Description -->
<p class="mt-0.5 text-sm {achievement.unlocked ? 'text-gray-300' : 'text-gray-500'}"> <p
class="mt-0.5 text-sm {achievement.unlocked
? 'text-foreground/90'
: 'text-muted-foreground'}"
>
{achievement.description} {achievement.description}
</p> </p>
<!-- Progress bar (if not unlocked) --> <!-- Progress bar (if not unlocked) -->
{#if !achievement.unlocked} {#if !achievement.unlocked}
<div class="mt-2"> <div class="mt-2">
<div class="flex items-center justify-between text-xs text-gray-500"> <div class="flex items-center justify-between text-xs text-muted-foreground">
<span>{achievement.progress} / {achievement.condition.threshold}</span> <span>{achievement.progress} / {achievement.condition.threshold}</span>
<span>{progressPercent}%</span> <span>{progressPercent}%</span>
</div> </div>
<div class="mt-1 h-1.5 overflow-hidden rounded-full bg-gray-700"> <div class="mt-1 h-1.5 overflow-hidden rounded-full bg-muted">
<div <div
class="h-full rounded-full bg-gradient-to-r from-gray-500 to-gray-400 transition-all duration-300" class="h-full rounded-full bg-gradient-to-r from-gray-500 to-gray-400 transition-all duration-300"
style="width: {progressPercent}%" style="width: {progressPercent}%"
@ -77,13 +81,13 @@
<span <span
class="flex items-center gap-1 {achievement.unlocked class="flex items-center gap-1 {achievement.unlocked
? 'text-yellow-400' ? 'text-yellow-400'
: 'text-gray-500'}" : 'text-muted-foreground'}"
> >
<Star class="h-3 w-3" /> <Star class="h-3 w-3" />
+{achievement.xpReward} XP +{achievement.xpReward} XP
</span> </span>
{#if achievement.unlocked && achievement.unlockedAt} {#if achievement.unlocked && achievement.unlockedAt}
<span class="text-gray-500"> <span class="text-muted-foreground">
{new Date(achievement.unlockedAt).toLocaleDateString('de-DE')} {new Date(achievement.unlockedAt).toLocaleDateString('de-DE')}
</span> </span>
{/if} {/if}

View file

@ -70,7 +70,7 @@
<p class="mb-2 text-xl font-semibold text-white">{result.achievement.name}</p> <p class="mb-2 text-xl font-semibold text-white">{result.achievement.name}</p>
<!-- Description --> <!-- Description -->
<p class="mb-4 text-gray-400">{result.achievement.description}</p> <p class="mb-4 text-muted-foreground">{result.achievement.description}</p>
<!-- Rarity + XP reward --> <!-- Rarity + XP reward -->
<div class="inline-flex items-center gap-3"> <div class="inline-flex items-center gap-3">
@ -84,7 +84,7 @@
</div> </div>
<!-- Click to close --> <!-- Click to close -->
<p class="mt-6 text-sm text-gray-500">Klicken zum Schließen</p> <p class="mt-6 text-sm text-muted-foreground">Klicken zum Schließen</p>
</div> </div>
</div> </div>
</div> </div>

View file

@ -48,14 +48,14 @@
aria-modal="true" aria-modal="true"
> >
<div <div
class="w-full max-w-md rounded-t-2xl sm:rounded-2xl border border-gray-700 bg-gray-800 p-6 shadow-xl max-h-[95vh] sm:max-h-[90vh] sm:mx-4" class="w-full max-w-md rounded-t-2xl sm:rounded-2xl border border-border bg-card p-6 shadow-xl max-h-[95vh] sm:max-h-[90vh] sm:mx-4"
> >
<!-- Header --> <!-- Header -->
<div class="mb-6 flex items-center justify-between"> <div class="mb-6 flex items-center justify-between">
<h2 class="text-xl font-bold text-white">Neuer Skill</h2> <h2 class="text-xl font-bold text-white">Neuer Skill</h2>
<button <button
onclick={onClose} onclick={onClose}
class="rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-700 hover:text-white" class="rounded-lg p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-white"
> >
<X class="h-5 w-5" /> <X class="h-5 w-5" />
</button> </button>
@ -64,20 +64,20 @@
<form onsubmit={handleSubmit} class="space-y-4"> <form onsubmit={handleSubmit} class="space-y-4">
<!-- Name --> <!-- Name -->
<div> <div>
<label for="name" class="mb-1 block text-sm font-medium text-gray-300"> Name * </label> <label for="name" class="mb-1 block text-sm font-medium text-foreground/90"> Name * </label>
<input <input
id="name" id="name"
type="text" type="text"
bind:value={name} bind:value={name}
placeholder="z.B. TypeScript" placeholder="z.B. TypeScript"
class="w-full rounded-lg border border-gray-600 bg-gray-700 px-4 py-2 text-white placeholder-gray-400 focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500" class="w-full rounded-lg border border-border bg-muted px-4 py-2 text-white placeholder:text-muted-foreground/60 focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500"
required required
/> />
</div> </div>
<!-- Description --> <!-- Description -->
<div> <div>
<label for="description" class="mb-1 block text-sm font-medium text-gray-300"> <label for="description" class="mb-1 block text-sm font-medium text-foreground/90">
Beschreibung Beschreibung
</label> </label>
<textarea <textarea
@ -85,13 +85,15 @@
bind:value={description} bind:value={description}
placeholder="Worum geht es bei diesem Skill?" placeholder="Worum geht es bei diesem Skill?"
rows="3" rows="3"
class="w-full rounded-lg border border-gray-600 bg-gray-700 px-4 py-2 text-white placeholder-gray-400 focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500" class="w-full rounded-lg border border-border bg-muted px-4 py-2 text-white placeholder:text-muted-foreground/60 focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500"
></textarea> ></textarea>
</div> </div>
<!-- Branch --> <!-- Branch -->
<div> <div>
<label for="branch" class="mb-2 block text-sm font-medium text-gray-300"> Kategorie </label> <label for="branch" class="mb-2 block text-sm font-medium text-foreground/90">
Kategorie
</label>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
{#each Object.entries(BRANCH_INFO) as [key, info]} {#each Object.entries(BRANCH_INFO) as [key, info]}
<button <button
@ -100,7 +102,7 @@
class="flex items-center gap-2 rounded-lg border px-3 py-2 text-left text-sm transition-colors {branch === class="flex items-center gap-2 rounded-lg border px-3 py-2 text-left text-sm transition-colors {branch ===
key key
? 'border-emerald-500 bg-emerald-500/20 text-white' ? 'border-emerald-500 bg-emerald-500/20 text-white'
: 'border-gray-600 bg-gray-700/50 text-gray-300 hover:border-gray-500'}" : 'border-border bg-muted/50 text-foreground/90 hover:border-border-strong'}"
> >
<span class="h-3 w-3 rounded-full" style="background-color: {info.color}"></span> <span class="h-3 w-3 rounded-full" style="background-color: {info.color}"></span>
{info.name} {info.name}
@ -114,7 +116,7 @@
<button <button
type="button" type="button"
onclick={onClose} onclick={onClose}
class="flex-1 rounded-lg border border-gray-600 bg-transparent px-4 py-2 font-medium text-gray-300 transition-colors hover:bg-gray-700" class="flex-1 rounded-lg border border-border bg-transparent px-4 py-2 font-medium text-foreground/90 transition-colors hover:bg-muted"
> >
{$_('common.cancel')} {$_('common.cancel')}
</button> </button>

View file

@ -61,17 +61,17 @@
aria-modal="true" aria-modal="true"
> >
<div <div
class="w-full max-w-md rounded-t-2xl sm:rounded-2xl border border-gray-700 bg-gray-800 p-6 shadow-xl max-h-[95vh] sm:max-h-[90vh] sm:mx-4" class="w-full max-w-md rounded-t-2xl sm:rounded-2xl border border-border bg-card p-6 shadow-xl max-h-[95vh] sm:max-h-[90vh] sm:mx-4"
> >
<!-- Header --> <!-- Header -->
<div class="mb-6 flex items-center justify-between"> <div class="mb-6 flex items-center justify-between">
<div> <div>
<h2 class="text-xl font-bold text-white">XP hinzufügen</h2> <h2 class="text-xl font-bold text-white">XP hinzufügen</h2>
<p class="text-sm text-gray-400">{skill.name} (Lvl {skill.level})</p> <p class="text-sm text-muted-foreground">{skill.name} (Lvl {skill.level})</p>
</div> </div>
<button <button
onclick={onClose} onclick={onClose}
class="rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-700 hover:text-white" class="rounded-lg p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-white"
> >
<X class="h-5 w-5" /> <X class="h-5 w-5" />
</button> </button>
@ -80,7 +80,7 @@
<form onsubmit={handleSubmit} class="space-y-4"> <form onsubmit={handleSubmit} class="space-y-4">
<!-- Quick XP Presets --> <!-- Quick XP Presets -->
<div> <div>
<span class="mb-2 block text-sm font-medium text-gray-300"> Schnellauswahl </span> <span class="mb-2 block text-sm font-medium text-foreground/90"> Schnellauswahl </span>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
{#each xpPresets as preset} {#each xpPresets as preset}
<button <button
@ -89,7 +89,7 @@
class="rounded-lg border px-3 py-1.5 text-sm font-medium transition-colors {xp === class="rounded-lg border px-3 py-1.5 text-sm font-medium transition-colors {xp ===
preset.value preset.value
? 'border-emerald-500 bg-emerald-500/20 text-emerald-400' ? 'border-emerald-500 bg-emerald-500/20 text-emerald-400'
: 'border-gray-600 bg-gray-700/50 text-gray-300 hover:border-gray-500'}" : 'border-border bg-muted/50 text-foreground/90 hover:border-border-strong'}"
> >
{preset.label} {preset.label}
</button> </button>
@ -99,7 +99,7 @@
<!-- Custom XP --> <!-- Custom XP -->
<div> <div>
<label for="xp" class="mb-1 block text-sm font-medium text-gray-300"> <label for="xp" class="mb-1 block text-sm font-medium text-foreground/90">
<Lightning class="mr-1 inline h-4 w-4 text-yellow-500" /> <Lightning class="mr-1 inline h-4 w-4 text-yellow-500" />
XP Menge XP Menge
</label> </label>
@ -109,13 +109,13 @@
bind:value={xp} bind:value={xp}
min="1" min="1"
max="1000" max="1000"
class="w-full rounded-lg border border-gray-600 bg-gray-700 px-4 py-2 text-white placeholder-gray-400 focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500" class="w-full rounded-lg border border-border bg-muted px-4 py-2 text-white placeholder:text-muted-foreground/60 focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500"
/> />
</div> </div>
<!-- Description --> <!-- Description -->
<div> <div>
<label for="description" class="mb-1 block text-sm font-medium text-gray-300"> <label for="description" class="mb-1 block text-sm font-medium text-foreground/90">
Was hast du gemacht? Was hast du gemacht?
</label> </label>
<input <input
@ -123,14 +123,14 @@
type="text" type="text"
bind:value={description} bind:value={description}
placeholder="z.B. Tutorial abgeschlossen" placeholder="z.B. Tutorial abgeschlossen"
class="w-full rounded-lg border border-gray-600 bg-gray-700 px-4 py-2 text-white placeholder-gray-400 focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500" class="w-full rounded-lg border border-border bg-muted px-4 py-2 text-white placeholder:text-muted-foreground/60 focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500"
/> />
</div> </div>
<!-- Duration (optional) --> <!-- Duration (optional) -->
<div> <div>
<label for="duration" class="mb-1 block text-sm font-medium text-gray-300"> <label for="duration" class="mb-1 block text-sm font-medium text-foreground/90">
<Clock class="mr-1 inline h-4 w-4 text-gray-400" /> <Clock class="mr-1 inline h-4 w-4 text-muted-foreground" />
Dauer (optional, Minuten) Dauer (optional, Minuten)
</label> </label>
<input <input
@ -139,17 +139,17 @@
bind:value={duration} bind:value={duration}
min="1" min="1"
placeholder="z.B. 30" placeholder="z.B. 30"
class="w-full rounded-lg border border-gray-600 bg-gray-700 px-4 py-2 text-white placeholder-gray-400 focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500" class="w-full rounded-lg border border-border bg-muted px-4 py-2 text-white placeholder:text-muted-foreground/60 focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500"
/> />
</div> </div>
<!-- Preview --> <!-- Preview -->
<div class="rounded-lg bg-gray-700/50 p-3"> <div class="rounded-lg bg-muted/50 p-3">
<div class="flex items-center justify-between text-sm"> <div class="flex items-center justify-between text-sm">
<span class="text-gray-400">Vorschau</span> <span class="text-muted-foreground">Vorschau</span>
<span class="font-medium text-emerald-400">+{xp} XP</span> <span class="font-medium text-emerald-400">+{xp} XP</span>
</div> </div>
<div class="mt-1 text-xs text-gray-500"> <div class="mt-1 text-xs text-muted-foreground">
Neuer Stand: {(skill.totalXp + xp).toLocaleString()} XP Neuer Stand: {(skill.totalXp + xp).toLocaleString()} XP
</div> </div>
</div> </div>
@ -159,7 +159,7 @@
<button <button
type="button" type="button"
onclick={onClose} onclick={onClose}
class="flex-1 rounded-lg border border-gray-600 bg-transparent px-4 py-2 font-medium text-gray-300 transition-colors hover:bg-gray-700" class="flex-1 rounded-lg border border-border bg-transparent px-4 py-2 font-medium text-foreground/90 transition-colors hover:bg-muted"
> >
{$_('common.cancel')} {$_('common.cancel')}
</button> </button>

View file

@ -60,14 +60,14 @@
aria-modal="true" aria-modal="true"
> >
<div <div
class="w-full max-w-md rounded-t-2xl sm:rounded-2xl border border-gray-700 bg-gray-800 p-6 shadow-xl max-h-[95vh] sm:max-h-[90vh] sm:mx-4" class="w-full max-w-md rounded-t-2xl sm:rounded-2xl border border-border bg-card p-6 shadow-xl max-h-[95vh] sm:max-h-[90vh] sm:mx-4"
> >
<!-- Header --> <!-- Header -->
<div class="mb-6 flex items-center justify-between"> <div class="mb-6 flex items-center justify-between">
<h2 class="text-xl font-bold text-white">Skill bearbeiten</h2> <h2 class="text-xl font-bold text-white">Skill bearbeiten</h2>
<button <button
onclick={onClose} onclick={onClose}
class="rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-700 hover:text-white" class="rounded-lg p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-white"
> >
<X class="h-5 w-5" /> <X class="h-5 w-5" />
</button> </button>
@ -82,13 +82,13 @@
<Trash class="h-8 w-8 text-red-500" /> <Trash class="h-8 w-8 text-red-500" />
</div> </div>
<h3 class="mb-2 text-lg font-semibold text-white">Skill löschen?</h3> <h3 class="mb-2 text-lg font-semibold text-white">Skill löschen?</h3>
<p class="mb-6 text-gray-400"> <p class="mb-6 text-muted-foreground">
"{skill.name}" und alle zugehörigen Aktivitäten werden unwiderruflich gelöscht. "{skill.name}" und alle zugehörigen Aktivitäten werden unwiderruflich gelöscht.
</p> </p>
<div class="flex gap-3"> <div class="flex gap-3">
<button <button
onclick={() => (showDeleteConfirm = false)} onclick={() => (showDeleteConfirm = false)}
class="flex-1 rounded-lg border border-gray-600 bg-transparent px-4 py-2 font-medium text-gray-300 transition-colors hover:bg-gray-700" class="flex-1 rounded-lg border border-border bg-transparent px-4 py-2 font-medium text-foreground/90 transition-colors hover:bg-muted"
> >
{$_('common.cancel')} {$_('common.cancel')}
</button> </button>
@ -104,20 +104,22 @@
<form onsubmit={handleSubmit} class="space-y-4"> <form onsubmit={handleSubmit} class="space-y-4">
<!-- Name --> <!-- Name -->
<div> <div>
<label for="name" class="mb-1 block text-sm font-medium text-gray-300"> Name * </label> <label for="name" class="mb-1 block text-sm font-medium text-foreground/90">
Name *
</label>
<input <input
id="name" id="name"
type="text" type="text"
bind:value={name} bind:value={name}
placeholder="z.B. TypeScript" placeholder="z.B. TypeScript"
class="w-full rounded-lg border border-gray-600 bg-gray-700 px-4 py-2 text-white placeholder-gray-400 focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500" class="w-full rounded-lg border border-border bg-muted px-4 py-2 text-white placeholder:text-muted-foreground/60 focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500"
required required
/> />
</div> </div>
<!-- Description --> <!-- Description -->
<div> <div>
<label for="description" class="mb-1 block text-sm font-medium text-gray-300"> <label for="description" class="mb-1 block text-sm font-medium text-foreground/90">
Beschreibung Beschreibung
</label> </label>
<textarea <textarea
@ -125,13 +127,13 @@
bind:value={description} bind:value={description}
placeholder="Worum geht es bei diesem Skill?" placeholder="Worum geht es bei diesem Skill?"
rows="3" rows="3"
class="w-full rounded-lg border border-gray-600 bg-gray-700 px-4 py-2 text-white placeholder-gray-400 focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500" class="w-full rounded-lg border border-border bg-muted px-4 py-2 text-white placeholder:text-muted-foreground/60 focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500"
></textarea> ></textarea>
</div> </div>
<!-- Branch --> <!-- Branch -->
<div> <div>
<span class="mb-2 block text-sm font-medium text-gray-300"> Kategorie </span> <span class="mb-2 block text-sm font-medium text-foreground/90"> Kategorie </span>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
{#each Object.entries(BRANCH_INFO) as [key, info]} {#each Object.entries(BRANCH_INFO) as [key, info]}
<button <button
@ -140,7 +142,7 @@
class="flex items-center gap-2 rounded-lg border px-3 py-2 text-left text-sm transition-colors {branch === class="flex items-center gap-2 rounded-lg border px-3 py-2 text-left text-sm transition-colors {branch ===
key key
? 'border-emerald-500 bg-emerald-500/20 text-white' ? 'border-emerald-500 bg-emerald-500/20 text-white'
: 'border-gray-600 bg-gray-700/50 text-gray-300 hover:border-gray-500'}" : 'border-border bg-muted/50 text-foreground/90 hover:border-border-strong'}"
> >
<span class="h-3 w-3 rounded-full" style="background-color: {info.color}"></span> <span class="h-3 w-3 rounded-full" style="background-color: {info.color}"></span>
{info.name} {info.name}
@ -150,18 +152,18 @@
</div> </div>
<!-- Stats (read-only) --> <!-- Stats (read-only) -->
<div class="rounded-lg bg-gray-700/50 p-3"> <div class="rounded-lg bg-muted/50 p-3">
<div class="grid grid-cols-3 gap-4 text-center text-sm"> <div class="grid grid-cols-3 gap-4 text-center text-sm">
<div> <div>
<div class="text-gray-400">Level</div> <div class="text-muted-foreground">Level</div>
<div class="font-semibold text-white">{skill.level}</div> <div class="font-semibold text-white">{skill.level}</div>
</div> </div>
<div> <div>
<div class="text-gray-400">Total XP</div> <div class="text-muted-foreground">Total XP</div>
<div class="font-semibold text-white">{skill.totalXp.toLocaleString()}</div> <div class="font-semibold text-white">{skill.totalXp.toLocaleString()}</div>
</div> </div>
<div> <div>
<div class="text-gray-400">Erstellt</div> <div class="text-muted-foreground">Erstellt</div>
<div class="font-semibold text-white"> <div class="font-semibold text-white">
{new Date(skill.createdAt).toLocaleDateString('de-DE')} {new Date(skill.createdAt).toLocaleDateString('de-DE')}
</div> </div>
@ -182,7 +184,7 @@
<button <button
type="button" type="button"
onclick={onClose} onclick={onClose}
class="flex-1 rounded-lg border border-gray-600 bg-transparent px-4 py-2 font-medium text-gray-300 transition-colors hover:bg-gray-700" class="flex-1 rounded-lg border border-border bg-transparent px-4 py-2 font-medium text-foreground/90 transition-colors hover:bg-muted"
> >
{$_('common.cancel')} {$_('common.cancel')}
</button> </button>

View file

@ -66,7 +66,7 @@
<h2 class="mb-2 text-3xl font-bold text-white level-up-text">LEVEL UP!</h2> <h2 class="mb-2 text-3xl font-bold text-white level-up-text">LEVEL UP!</h2>
<!-- Skill name --> <!-- Skill name -->
<p class="mb-4 text-xl text-gray-300">{skillName}</p> <p class="mb-4 text-xl text-foreground/90">{skillName}</p>
<!-- New level badge --> <!-- New level badge -->
<div <div
@ -90,7 +90,7 @@
</div> </div>
<!-- Click to close --> <!-- Click to close -->
<p class="mt-6 text-sm text-gray-500">Klicken zum Schließen</p> <p class="mt-6 text-sm text-muted-foreground">Klicken zum Schließen</p>
</div> </div>
</div> </div>
</div> </div>

View file

@ -21,7 +21,7 @@
function getLevelColor(level: number): string { function getLevelColor(level: number): string {
const colors = [ const colors = [
'text-gray-400', 'text-muted-foreground',
'text-blue-400', 'text-blue-400',
'text-purple-400', 'text-purple-400',
'text-pink-400', 'text-pink-400',
@ -33,7 +33,7 @@
</script> </script>
<div <div
class="skill-card group relative overflow-hidden rounded-xl border border-gray-700 bg-gray-800/50 p-4" class="skill-card group relative overflow-hidden rounded-xl border border-border bg-card/50 p-4"
> >
<!-- Branch Indicator --> <!-- Branch Indicator -->
<div <div
@ -45,14 +45,14 @@
<div class="mb-3 flex items-start justify-between pl-3"> <div class="mb-3 flex items-start justify-between pl-3">
<div> <div>
<h3 class="text-lg font-semibold text-white">{skill.name}</h3> <h3 class="text-lg font-semibold text-white">{skill.name}</h3>
<p class="text-sm text-gray-400">{branchInfo.name}</p> <p class="text-sm text-muted-foreground">{branchInfo.name}</p>
</div> </div>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
{#each Array(skill.level) as _, i} {#each Array(skill.level) as _, i}
<Star class="h-4 w-4 fill-yellow-500 text-yellow-500" /> <Star class="h-4 w-4 fill-yellow-500 text-yellow-500" />
{/each} {/each}
{#each Array(5 - skill.level) as _, i} {#each Array(5 - skill.level) as _, i}
<Star class="h-4 w-4 text-gray-600" /> <Star class="h-4 w-4 text-muted-foreground/70" />
{/each} {/each}
</div> </div>
</div> </div>
@ -60,7 +60,7 @@
<!-- Level Badge --> <!-- Level Badge -->
<div class="mb-3 pl-3"> <div class="mb-3 pl-3">
<span <span
class="inline-flex items-center gap-1 rounded-full bg-gray-700/50 px-3 py-1 text-sm {getLevelColor( class="inline-flex items-center gap-1 rounded-full bg-muted/50 px-3 py-1 text-sm {getLevelColor(
skill.level skill.level
)}" )}"
> >
@ -71,8 +71,8 @@
<!-- XP Progress --> <!-- XP Progress -->
<div class="mb-4 pl-3"> <div class="mb-4 pl-3">
<div class="mb-1 flex justify-between text-sm"> <div class="mb-1 flex justify-between text-sm">
<span class="text-gray-400">XP</span> <span class="text-muted-foreground">XP</span>
<span class="text-gray-300"> <span class="text-foreground/90">
{skill.totalXp.toLocaleString()} {skill.totalXp.toLocaleString()}
{#if !isMaxLevel} {#if !isMaxLevel}
/ {nextLevelXp.toLocaleString()} / {nextLevelXp.toLocaleString()}
@ -86,7 +86,7 @@
<!-- Description --> <!-- Description -->
{#if skill.description} {#if skill.description}
<p class="mb-4 pl-3 text-sm text-gray-400 line-clamp-2">{skill.description}</p> <p class="mb-4 pl-3 text-sm text-muted-foreground line-clamp-2">{skill.description}</p>
{/if} {/if}
<!-- Actions --> <!-- Actions -->
@ -100,7 +100,7 @@
</button> </button>
<button <button
onclick={onEdit} onclick={onEdit}
class="rounded-lg bg-gray-600/20 p-2 text-gray-400 opacity-0 transition-all hover:bg-gray-600/30 hover:text-white group-hover:opacity-100" class="rounded-lg bg-muted/20 p-2 text-muted-foreground opacity-0 transition-all hover:bg-muted/30 hover:text-white group-hover:opacity-100"
title={$_('common.edit')} title={$_('common.edit')}
> >
<PencilSimple class="h-4 w-4" /> <PencilSimple class="h-4 w-4" />

View file

@ -112,7 +112,7 @@
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
> >
<div class="w-full max-w-2xl rounded-2xl border border-gray-700 bg-gray-800 p-6 shadow-xl my-8"> <div class="w-full max-w-2xl rounded-2xl border border-border bg-card p-6 shadow-xl my-8">
<!-- Header --> <!-- Header -->
<div class="mb-6 flex items-center justify-between"> <div class="mb-6 flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@ -121,13 +121,13 @@
</div> </div>
<button <button
onclick={onClose} onclick={onClose}
class="rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-700 hover:text-white" class="rounded-lg p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-white"
> >
<X class="h-5 w-5" /> <X class="h-5 w-5" />
</button> </button>
</div> </div>
<p class="mb-6 text-gray-400"> <p class="mb-6 text-muted-foreground">
Starte schnell mit vorgefertigten Skill-Sets. Wähle eine Vorlage und füge einzelne Skills oder Starte schnell mit vorgefertigten Skill-Sets. Wähle eine Vorlage und füge einzelne Skills oder
alle auf einmal hinzu. alle auf einmal hinzu.
</p> </p>
@ -135,15 +135,15 @@
<!-- Template List --> <!-- Template List -->
<div class="space-y-4 max-h-[60vh] overflow-y-auto pr-2"> <div class="space-y-4 max-h-[60vh] overflow-y-auto pr-2">
{#each Object.entries(templates) as [name, skills]} {#each Object.entries(templates) as [name, skills]}
<div class="rounded-xl border border-gray-700 bg-gray-900/50 overflow-hidden"> <div class="rounded-xl border border-border bg-card/50 overflow-hidden">
<!-- Template Header --> <!-- Template Header -->
<div class="flex items-center justify-between p-4 hover:bg-gray-800/50 transition-colors"> <div class="flex items-center justify-between p-4 hover:bg-card/50 transition-colors">
<button <button
onclick={() => (selectedTemplate = selectedTemplate === name ? null : name)} onclick={() => (selectedTemplate = selectedTemplate === name ? null : name)}
class="flex-1 text-left" class="flex-1 text-left"
> >
<h3 class="font-semibold text-white">{name}</h3> <h3 class="font-semibold text-white">{name}</h3>
<p class="text-sm text-gray-400">{skills.length} Skills</p> <p class="text-sm text-muted-foreground">{skills.length} Skills</p>
</button> </button>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button <button
@ -155,7 +155,7 @@
</button> </button>
<button <button
onclick={() => (selectedTemplate = selectedTemplate === name ? null : name)} onclick={() => (selectedTemplate = selectedTemplate === name ? null : name)}
class="text-gray-500 text-xl px-2" class="text-muted-foreground text-xl px-2"
> >
{selectedTemplate === name ? '' : '+'} {selectedTemplate === name ? '' : '+'}
</button> </button>
@ -164,10 +164,10 @@
<!-- Expanded Skills --> <!-- Expanded Skills -->
{#if selectedTemplate === name} {#if selectedTemplate === name}
<div class="border-t border-gray-700 p-4 space-y-2"> <div class="border-t border-border p-4 space-y-2">
{#each skills as skill} {#each skills as skill}
{@const isAdded = addedSkills.has(skill.name)} {@const isAdded = addedSkills.has(skill.name)}
<div class="flex items-center justify-between rounded-lg bg-gray-800/50 px-3 py-2"> <div class="flex items-center justify-between rounded-lg bg-card/50 px-3 py-2">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span <span
class="h-3 w-3 rounded-full" class="h-3 w-3 rounded-full"
@ -175,7 +175,7 @@
></span> ></span>
<div> <div>
<span class="font-medium text-white">{skill.name}</span> <span class="font-medium text-white">{skill.name}</span>
<span class="text-gray-400 text-sm"> - {skill.description}</span> <span class="text-muted-foreground text-sm"> - {skill.description}</span>
</div> </div>
</div> </div>
<button <button
@ -183,7 +183,7 @@
disabled={isAdded || adding} disabled={isAdded || adding}
class="rounded-lg p-1.5 transition-colors {isAdded class="rounded-lg p-1.5 transition-colors {isAdded
? 'bg-emerald-600/20 text-emerald-400' ? 'bg-emerald-600/20 text-emerald-400'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'}" : 'bg-muted text-foreground/90 hover:bg-muted'}"
> >
{#if isAdded} {#if isAdded}
<Check size={16} /> <Check size={16} />
@ -203,7 +203,7 @@
<div class="mt-6 flex justify-end"> <div class="mt-6 flex justify-end">
<button <button
onclick={onClose} onclick={onClose}
class="rounded-lg bg-gray-700 px-4 py-2 font-medium text-white transition-colors hover:bg-gray-600" class="rounded-lg bg-muted px-4 py-2 font-medium text-white transition-colors hover:bg-muted"
> >
Fertig Fertig
</button> </button>

View file

@ -18,13 +18,13 @@
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-5"> <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-5">
<!-- Total XP --> <!-- Total XP -->
<div class="rounded-xl border border-gray-700 bg-gray-800/50 p-4"> <div class="rounded-xl border border-border bg-card/50 p-4">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-yellow-500/20"> <div class="flex h-12 w-12 items-center justify-center rounded-full bg-yellow-500/20">
<Lightning class="h-6 w-6 text-yellow-500" /> <Lightning class="h-6 w-6 text-yellow-500" />
</div> </div>
<div> <div>
<p class="text-sm text-gray-400">Gesamt-XP</p> <p class="text-sm text-muted-foreground">Gesamt-XP</p>
<p class="text-2xl font-bold text-white"> <p class="text-2xl font-bold text-white">
{userStats.totalXp.toLocaleString()} {userStats.totalXp.toLocaleString()}
</p> </p>
@ -33,13 +33,13 @@
</div> </div>
<!-- Total Skills --> <!-- Total Skills -->
<div class="rounded-xl border border-gray-700 bg-gray-800/50 p-4"> <div class="rounded-xl border border-border bg-card/50 p-4">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-emerald-500/20"> <div class="flex h-12 w-12 items-center justify-center rounded-full bg-emerald-500/20">
<Target class="h-6 w-6 text-emerald-500" /> <Target class="h-6 w-6 text-emerald-500" />
</div> </div>
<div> <div>
<p class="text-sm text-gray-400">Skills</p> <p class="text-sm text-muted-foreground">Skills</p>
<p class="text-2xl font-bold text-white"> <p class="text-2xl font-bold text-white">
{userStats.totalSkills} {userStats.totalSkills}
</p> </p>
@ -48,13 +48,13 @@
</div> </div>
<!-- Highest Level --> <!-- Highest Level -->
<div class="rounded-xl border border-gray-700 bg-gray-800/50 p-4"> <div class="rounded-xl border border-border bg-card/50 p-4">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-purple-500/20"> <div class="flex h-12 w-12 items-center justify-center rounded-full bg-purple-500/20">
<Trophy class="h-6 w-6 text-purple-500" /> <Trophy class="h-6 w-6 text-purple-500" />
</div> </div>
<div> <div>
<p class="text-sm text-gray-400">Hochstes Level</p> <p class="text-sm text-muted-foreground">Hochstes Level</p>
<p class="text-2xl font-bold text-white"> <p class="text-2xl font-bold text-white">
{userStats.highestLevel} {userStats.highestLevel}
</p> </p>
@ -63,13 +63,13 @@
</div> </div>
<!-- Streak --> <!-- Streak -->
<div class="rounded-xl border border-gray-700 bg-gray-800/50 p-4"> <div class="rounded-xl border border-border bg-card/50 p-4">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-orange-500/20"> <div class="flex h-12 w-12 items-center justify-center rounded-full bg-orange-500/20">
<Fire class="h-6 w-6 text-orange-500" /> <Fire class="h-6 w-6 text-orange-500" />
</div> </div>
<div> <div>
<p class="text-sm text-gray-400">Streak</p> <p class="text-sm text-muted-foreground">Streak</p>
<p class="text-2xl font-bold text-white"> <p class="text-2xl font-bold text-white">
{userStats.streakDays} Tage {userStats.streakDays} Tage
</p> </p>
@ -80,16 +80,16 @@
<!-- Achievements --> <!-- Achievements -->
<a <a
href="/skilltree/achievements" href="/skilltree/achievements"
class="rounded-xl border border-gray-700 bg-gray-800/50 p-4 transition-colors hover:border-yellow-600/50 hover:bg-yellow-900/10" class="rounded-xl border border-border bg-card/50 p-4 transition-colors hover:border-yellow-600/50 hover:bg-yellow-900/10"
> >
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-yellow-500/20"> <div class="flex h-12 w-12 items-center justify-center rounded-full bg-yellow-500/20">
<Medal class="h-6 w-6 text-yellow-500" /> <Medal class="h-6 w-6 text-yellow-500" />
</div> </div>
<div> <div>
<p class="text-sm text-gray-400">Achievements</p> <p class="text-sm text-muted-foreground">Achievements</p>
<p class="text-2xl font-bold text-white"> <p class="text-2xl font-bold text-white">
{achievementStats.unlocked}<span class="text-sm font-normal text-gray-500" {achievementStats.unlocked}<span class="text-sm font-normal text-muted-foreground"
>/{achievementStats.total}</span >/{achievementStats.total}</span
> >
</p> </p>

View file

@ -116,17 +116,17 @@
<div class="flex h-full flex-col"> <div class="flex h-full flex-col">
<!-- Header --> <!-- Header -->
<header class="flex items-center gap-2 border-b border-white/5 px-3 py-2"> <header class="flex items-center gap-2 border-b border-border/5 px-3 py-2">
<button <button
type="button" type="button"
class="rounded p-1.5 text-white/60 hover:bg-white/5 hover:text-white/90" class="rounded p-1.5 text-muted-foreground hover:bg-muted/5 hover:text-foreground"
onclick={onBack} onclick={onBack}
aria-label="Zurück" aria-label="Zurück"
> >
</button> </button>
<div class="flex-1"> <div class="flex-1">
<div class="text-sm font-medium text-white/90"> <div class="text-sm font-medium text-foreground">
{#if game?.status === 'won'} {#if game?.status === 'won'}
{game.revealedName} {game.revealedName}
{:else if game?.status === 'surrendered'} {:else if game?.status === 'surrendered'}
@ -135,7 +135,7 @@
Wer bin ich? Wer bin ich?
{/if} {/if}
</div> </div>
<div class="text-[11px] text-white/40"> <div class="text-[11px] text-muted-foreground">
{#if game} {#if game}
{game.deckId} · {difficultyEmoji(game.difficulty)} · {game.messageCount} Fragen {game.deckId} · {difficultyEmoji(game.difficulty)} · {game.messageCount} Fragen
{/if} {/if}
@ -144,14 +144,14 @@
{#if game?.status === 'playing'} {#if game?.status === 'playing'}
<button <button
type="button" type="button"
class="rounded px-2 py-1 text-xs text-white/60 hover:bg-white/5 hover:text-white/90" class="rounded px-2 py-1 text-xs text-muted-foreground hover:bg-muted/5 hover:text-foreground"
onclick={() => (showGuessModal = true)} onclick={() => (showGuessModal = true)}
> >
Tippen Tippen
</button> </button>
<button <button
type="button" type="button"
class="rounded px-2 py-1 text-xs text-white/40 hover:bg-white/5 hover:text-white/70" class="rounded px-2 py-1 text-xs text-muted-foreground hover:bg-muted/5 hover:text-foreground"
onclick={surrender} onclick={surrender}
> >
Aufgeben Aufgeben
@ -162,7 +162,7 @@
<!-- Result banner (post-game only) --> <!-- Result banner (post-game only) -->
{#if game && game.status !== 'playing'} {#if game && game.status !== 'playing'}
<div <div
class="border-b border-white/5 px-4 py-3 {game.status === 'won' class="border-b border-border/5 px-4 py-3 {game.status === 'won'
? 'bg-emerald-500/10' ? 'bg-emerald-500/10'
: 'bg-amber-500/10'}" : 'bg-amber-500/10'}"
> >
@ -170,7 +170,7 @@
<p class="text-sm font-medium text-emerald-300"> <p class="text-sm font-medium text-emerald-300">
Erraten in {game.messageCount} Nachrichten! Erraten in {game.messageCount} Nachrichten!
</p> </p>
<p class="mt-0.5 text-xs text-white/60"> <p class="mt-0.5 text-xs text-muted-foreground">
Das war {game.revealedName}. Das war {game.revealedName}.
</p> </p>
{:else} {:else}
@ -182,7 +182,9 @@
<!-- Messages scroll --> <!-- Messages scroll -->
<div bind:this={scrollContainer} class="flex-1 overflow-y-auto px-3 py-4"> <div bind:this={scrollContainer} class="flex-1 overflow-y-auto px-3 py-4">
{#if messages.length === 0} {#if messages.length === 0}
<div class="flex h-full items-center justify-center text-center text-sm text-white/40"> <div
class="flex h-full items-center justify-center text-center text-sm text-muted-foreground"
>
<p> <p>
Stell die erste Frage.<br /> Stell die erste Frage.<br />
Versuche, die Persönlichkeit durch geschickte Fragen herauszufinden. Versuche, die Persönlichkeit durch geschickte Fragen herauszufinden.
@ -200,7 +202,7 @@
</div> </div>
{:else} {:else}
<div <div
class="max-w-[80%] whitespace-pre-wrap rounded-lg bg-white/5 px-3 py-2 text-sm leading-relaxed text-white/90" class="max-w-[80%] whitespace-pre-wrap rounded-lg bg-muted/5 px-3 py-2 text-sm leading-relaxed text-foreground"
> >
{msg.content} {msg.content}
</div> </div>
@ -219,14 +221,14 @@
<!-- Input or notes --> <!-- Input or notes -->
{#if game?.status === 'playing'} {#if game?.status === 'playing'}
<div class="border-t border-white/5 p-3"> <div class="border-t border-border/5 p-3">
<div class="mx-auto flex max-w-2xl items-end gap-2"> <div class="mx-auto flex max-w-2xl items-end gap-2">
<textarea <textarea
bind:value={inputText} bind:value={inputText}
onkeydown={onInputKeydown} onkeydown={onInputKeydown}
placeholder="Frag mich etwas…" placeholder="Frag mich etwas…"
rows="1" rows="1"
class="flex-1 resize-none rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm text-white/90 placeholder-white/30 focus:border-purple-400/50 focus:outline-none" class="flex-1 resize-none rounded-lg border border-border/10 bg-muted/5 px-3 py-2 text-sm text-foreground placeholder-white/30 focus:border-purple-400/50 focus:outline-none"
disabled={sending} disabled={sending}
></textarea> ></textarea>
<button <button
@ -240,9 +242,12 @@
</div> </div>
</div> </div>
{:else if game} {:else if game}
<div class="border-t border-white/5 p-3"> <div class="border-t border-border/5 p-3">
<div class="mx-auto max-w-2xl"> <div class="mx-auto max-w-2xl">
<label for="who-notes" class="mb-1 block text-[11px] uppercase tracking-wide text-white/40"> <label
for="who-notes"
class="mb-1 block text-[11px] uppercase tracking-wide text-muted-foreground"
>
Notiz {notesDirty ? '(speichert…)' : ''} Notiz {notesDirty ? '(speichert…)' : ''}
</label> </label>
<textarea <textarea
@ -251,7 +256,7 @@
oninput={onNotesInput} oninput={onNotesInput}
placeholder="Notiz zum Spiel…" placeholder="Notiz zum Spiel…"
rows="2" rows="2"
class="w-full resize-none rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm text-white/90 placeholder-white/30 focus:border-white/20 focus:outline-none" class="w-full resize-none rounded-lg border border-border/10 bg-muted/5 px-3 py-2 text-sm text-foreground placeholder-white/30 focus:border-border/20 focus:outline-none"
></textarea> ></textarea>
</div> </div>
</div> </div>
@ -266,9 +271,9 @@
onkeydown={(e) => e.key === 'Escape' && (showGuessModal = false)} onkeydown={(e) => e.key === 'Escape' && (showGuessModal = false)}
role="presentation" role="presentation"
> >
<div class="w-full max-w-md rounded-lg bg-zinc-900 p-5"> <div class="w-full max-w-md rounded-lg bg-card p-5">
<h3 class="mb-3 text-base font-medium text-white/90">Wer ist es?</h3> <h3 class="mb-3 text-base font-medium text-foreground">Wer ist es?</h3>
<p class="mb-3 text-xs text-white/50"> <p class="mb-3 text-xs text-muted-foreground">
Wenn die KI deine Vermutung nicht erkannt hat, kannst du den Namen hier direkt eintragen. Wenn die KI deine Vermutung nicht erkannt hat, kannst du den Namen hier direkt eintragen.
</p> </p>
<!-- svelte-ignore a11y_autofocus --> <!-- svelte-ignore a11y_autofocus -->
@ -277,13 +282,13 @@
bind:value={guessText} bind:value={guessText}
onkeydown={(e) => e.key === 'Enter' && submitGuess()} onkeydown={(e) => e.key === 'Enter' && submitGuess()}
placeholder="z.B. Marie Curie" placeholder="z.B. Marie Curie"
class="mb-3 w-full rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm text-white/90 placeholder-white/30 focus:border-purple-400/50 focus:outline-none" class="mb-3 w-full rounded-lg border border-border/10 bg-muted/5 px-3 py-2 text-sm text-foreground placeholder-white/30 focus:border-purple-400/50 focus:outline-none"
autofocus autofocus
/> />
<div class="flex justify-end gap-2"> <div class="flex justify-end gap-2">
<button <button
type="button" type="button"
class="rounded px-3 py-1.5 text-sm text-white/60 hover:bg-white/5" class="rounded px-3 py-1.5 text-sm text-muted-foreground hover:bg-muted/5"
onclick={() => { onclick={() => {
showGuessModal = false; showGuessModal = false;
guessText = ''; guessText = '';

View file

@ -143,7 +143,7 @@
class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium
{userData.user.role === 'admin' {userData.user.role === 'admin'
? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400' ? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'}" : 'bg-muted text-muted-foreground dark:bg-card dark:text-foreground/90'}"
> >
{userData.user.role} {userData.user.role}
</span> </span>

View file

@ -43,25 +43,25 @@
<!-- Stats --> <!-- Stats -->
<div class="mb-8 grid grid-cols-2 gap-4 md:grid-cols-4"> <div class="mb-8 grid grid-cols-2 gap-4 md:grid-cols-4">
<div <div
class="rounded-xl border border-gray-200 bg-white p-4 text-center dark:border-gray-700 dark:bg-gray-800" class="rounded-xl border border-border-strong bg-white p-4 text-center dark:border-border dark:bg-card"
> >
<div class="text-2xl font-bold">{spaces.length}</div> <div class="text-2xl font-bold">{spaces.length}</div>
<div class="mt-1 text-xs opacity-60">Spaces</div> <div class="mt-1 text-xs opacity-60">Spaces</div>
</div> </div>
<div <div
class="rounded-xl border border-gray-200 bg-white p-4 text-center dark:border-gray-700 dark:bg-gray-800" class="rounded-xl border border-border-strong bg-white p-4 text-center dark:border-border dark:bg-card"
> >
<div class="text-2xl font-bold">{stats.total}</div> <div class="text-2xl font-bold">{stats.total}</div>
<div class="mt-1 text-xs opacity-60">Dokumente</div> <div class="mt-1 text-xs opacity-60">Dokumente</div>
</div> </div>
<div <div
class="rounded-xl border border-gray-200 bg-white p-4 text-center dark:border-gray-700 dark:bg-gray-800" class="rounded-xl border border-border-strong bg-white p-4 text-center dark:border-border dark:bg-card"
> >
<div class="text-2xl font-bold">{stats.totalWords.toLocaleString()}</div> <div class="text-2xl font-bold">{stats.totalWords.toLocaleString()}</div>
<div class="mt-1 text-xs opacity-60">Woerter</div> <div class="mt-1 text-xs opacity-60">Woerter</div>
</div> </div>
<div <div
class="rounded-xl border border-gray-200 bg-white p-4 text-center dark:border-gray-700 dark:bg-gray-800" class="rounded-xl border border-border-strong bg-white p-4 text-center dark:border-border dark:bg-card"
> >
<div class="text-2xl font-bold">{stats.text}/{stats.context}/{stats.prompt}</div> <div class="text-2xl font-bold">{stats.text}/{stats.context}/{stats.prompt}</div>
<div class="mt-1 text-xs opacity-60">Text/Kontext/Prompt</div> <div class="mt-1 text-xs opacity-60">Text/Kontext/Prompt</div>
@ -79,7 +79,7 @@
</a> </a>
<a <a
href="/context/documents" href="/context/documents"
class="flex items-center gap-2 rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium transition-colors hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-700" class="flex items-center gap-2 rounded-lg border border-border-strong px-4 py-2 text-sm font-medium transition-colors hover:bg-muted dark:border-border dark:hover:bg-muted"
> >
<FileText size={16} /> <FileText size={16} />
Alle Dokumente Alle Dokumente
@ -94,7 +94,7 @@
{#each pinnedSpaces as space} {#each pinnedSpaces as space}
<a <a
href="/context/spaces/{space.id}" href="/context/spaces/{space.id}"
class="rounded-xl border border-gray-200 bg-white p-4 transition-all hover:shadow-md dark:border-gray-700 dark:bg-gray-800" class="rounded-xl border border-border-strong bg-white p-4 transition-all hover:shadow-md dark:border-border dark:bg-card"
> >
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span <span
@ -128,7 +128,7 @@
{#each recentDocs as doc} {#each recentDocs as doc}
<a <a
href="/context/documents/{doc.id}" href="/context/documents/{doc.id}"
class="group rounded-xl border border-gray-200 bg-white p-4 transition-all hover:shadow-md dark:border-gray-700 dark:bg-gray-800" class="group rounded-xl border border-border-strong bg-white p-4 transition-all hover:shadow-md dark:border-border dark:bg-card"
> >
<div class="flex items-start justify-between"> <div class="flex items-start justify-between">
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
@ -159,7 +159,7 @@
e.preventDefault(); e.preventDefault();
handleTogglePinDoc(doc.id); handleTogglePinDoc(doc.id);
}} }}
class="ml-2 rounded p-1 opacity-0 transition-opacity hover:bg-gray-100 group-hover:opacity-100 dark:hover:bg-gray-700" class="ml-2 rounded p-1 opacity-0 transition-opacity hover:bg-muted group-hover:opacity-100 dark:hover:bg-muted"
title={doc.pinned ? 'Loslassen' : 'Anheften'} title={doc.pinned ? 'Loslassen' : 'Anheften'}
> >
{doc.pinned ? '&#9733;' : '&#9734;'} {doc.pinned ? '&#9733;' : '&#9734;'}
@ -168,7 +168,7 @@
<div class="mt-2 flex items-center gap-3 text-xs opacity-40"> <div class="mt-2 flex items-center gap-3 text-xs opacity-40">
{#if doc.metadata?.tags && doc.metadata.tags.length > 0} {#if doc.metadata?.tags && doc.metadata.tags.length > 0}
{#each doc.metadata.tags.slice(0, 3) as tag} {#each doc.metadata.tags.slice(0, 3) as tag}
<span class="rounded bg-gray-100 px-1.5 py-0.5 dark:bg-gray-700">{tag}</span> <span class="rounded bg-muted px-1.5 py-0.5 dark:bg-muted">{tag}</span>
{/each} {/each}
{/if} {/if}
<span class="ml-auto"> <span class="ml-auto">
@ -181,7 +181,7 @@
</section> </section>
{:else} {:else}
<div <div
class="rounded-xl border-2 border-dashed border-gray-300 p-12 text-center dark:border-gray-600" class="rounded-xl border-2 border-dashed border-border-strong p-12 text-center dark:border-border"
> >
<FileText size={48} class="mx-auto mb-4 opacity-20" /> <FileText size={48} class="mx-auto mb-4 opacity-20" />
<h3 class="text-lg font-medium opacity-60">Noch keine Dokumente</h3> <h3 class="text-lg font-medium opacity-60">Noch keine Dokumente</h3>

View file

@ -106,7 +106,7 @@
<button <button
class="rounded-lg px-3 py-1.5 text-sm transition-colors {typeFilter === filter.value class="rounded-lg px-3 py-1.5 text-sm transition-colors {typeFilter === filter.value
? 'bg-indigo-600 text-white' ? 'bg-indigo-600 text-white'
: 'opacity-60 hover:bg-gray-100 dark:hover:bg-gray-700'}" : 'opacity-60 hover:bg-muted dark:hover:bg-muted'}"
onclick={() => (typeFilter = filter.value)} onclick={() => (typeFilter = filter.value)}
> >
{filter.label} {filter.label}
@ -129,7 +129,7 @@
type="text" type="text"
bind:value={searchQuery} bind:value={searchQuery}
placeholder="Dokumente durchsuchen..." placeholder="Dokumente durchsuchen..."
class="w-full rounded-lg border border-gray-300 bg-white py-2 pl-8 pr-3 text-sm focus:border-indigo-500 focus:outline-none dark:border-gray-600 dark:bg-gray-700" class="w-full rounded-lg border border-border-strong bg-white py-2 pl-8 pr-3 text-sm focus:border-indigo-500 focus:outline-none dark:border-border dark:bg-muted"
/> />
</div> </div>
</div> </div>
@ -141,7 +141,7 @@
<button <button
class="rounded-full px-2 py-1 text-xs transition-colors {tagFilter.includes(tag) class="rounded-full px-2 py-1 text-xs transition-colors {tagFilter.includes(tag)
? 'bg-indigo-600 text-white' ? 'bg-indigo-600 text-white'
: 'bg-gray-100 opacity-60 hover:opacity-100 dark:bg-gray-700'}" : 'bg-muted opacity-60 hover:opacity-100 dark:bg-muted'}"
onclick={() => toggleTag(tag)} onclick={() => toggleTag(tag)}
> >
{tag} {tag}
@ -155,7 +155,7 @@
<div class="grid grid-cols-1 gap-3 md:grid-cols-2"> <div class="grid grid-cols-1 gap-3 md:grid-cols-2">
{#each filteredDocuments as doc (doc.id)} {#each filteredDocuments as doc (doc.id)}
<div <div
class="group rounded-xl border border-gray-200 bg-white p-4 transition-all hover:shadow-md dark:border-gray-700 dark:bg-gray-800" class="group rounded-xl border border-border-strong bg-white p-4 transition-all hover:shadow-md dark:border-border dark:bg-card"
> >
<div class="flex items-start justify-between"> <div class="flex items-start justify-between">
<a href="/context/documents/{doc.id}" class="min-w-0 flex-1"> <a href="/context/documents/{doc.id}" class="min-w-0 flex-1">
@ -186,7 +186,7 @@
> >
<button <button
onclick={() => handleTogglePin(doc.id)} onclick={() => handleTogglePin(doc.id)}
class="rounded p-1 hover:bg-gray-100 dark:hover:bg-gray-700" class="rounded p-1 hover:bg-muted dark:hover:bg-muted"
title={doc.pinned ? 'Loslassen' : 'Anheften'} title={doc.pinned ? 'Loslassen' : 'Anheften'}
> >
{doc.pinned ? '&#9733;' : '&#9734;'} {doc.pinned ? '&#9733;' : '&#9734;'}
@ -203,7 +203,7 @@
<div class="mt-2 flex items-center gap-3 text-xs opacity-40"> <div class="mt-2 flex items-center gap-3 text-xs opacity-40">
{#if doc.metadata?.tags && doc.metadata.tags.length > 0} {#if doc.metadata?.tags && doc.metadata.tags.length > 0}
{#each doc.metadata.tags.slice(0, 3) as tag} {#each doc.metadata.tags.slice(0, 3) as tag}
<span class="rounded bg-gray-100 px-1.5 py-0.5 dark:bg-gray-700">{tag}</span> <span class="rounded bg-muted px-1.5 py-0.5 dark:bg-muted">{tag}</span>
{/each} {/each}
{/if} {/if}
<span class="ml-auto"> <span class="ml-auto">
@ -222,7 +222,7 @@
</div> </div>
{:else} {:else}
<div <div
class="flex flex-col items-center justify-center rounded-xl border-2 border-dashed border-gray-300 py-16 text-center dark:border-gray-600" class="flex flex-col items-center justify-center rounded-xl border-2 border-dashed border-border-strong py-16 text-center dark:border-border"
> >
<FileText size={48} class="mb-4 opacity-20" /> <FileText size={48} class="mb-4 opacity-20" />
<h2 class="text-lg font-medium opacity-60">Noch keine Dokumente</h2> <h2 class="text-lg font-medium opacity-60">Noch keine Dokumente</h2>
@ -250,7 +250,7 @@
role="presentation" role="presentation"
> >
<div <div
class="w-full max-w-sm rounded-xl bg-white p-6 shadow-2xl dark:bg-gray-800" class="w-full max-w-sm rounded-xl bg-white p-6 shadow-2xl dark:bg-card"
onclick={(e) => e.stopPropagation()} onclick={(e) => e.stopPropagation()}
role="none" role="none"
> >
@ -259,7 +259,7 @@
<div class="mt-4 flex justify-end gap-2"> <div class="mt-4 flex justify-end gap-2">
<button <button
onclick={() => (deleteTarget = null)} onclick={() => (deleteTarget = null)}
class="rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-700" class="rounded-lg border border-border-strong px-4 py-2 text-sm font-medium hover:bg-muted dark:border-border dark:hover:bg-muted"
> >
Abbrechen Abbrechen
</button> </button>

View file

@ -135,7 +135,7 @@
<!-- Editor --> <!-- Editor -->
<div <div
class="rounded-xl border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-800" class="rounded-xl border border-border-strong bg-white p-6 dark:border-border dark:bg-card"
> >
<!-- Title --> <!-- Title -->
<input <input
@ -148,7 +148,7 @@
<!-- Type + Tags bar --> <!-- Type + Tags bar -->
<div <div
class="mb-4 flex flex-wrap items-center gap-3 border-b border-gray-100 pb-4 dark:border-gray-700" class="mb-4 flex flex-wrap items-center gap-3 border-b border-border-strong pb-4 dark:border-border"
> >
<div class="flex gap-1"> <div class="flex gap-1">
{#each typeOptions as opt} {#each typeOptions as opt}
@ -156,7 +156,7 @@
class="rounded-md px-2.5 py-1 text-xs font-medium transition-colors {editType === class="rounded-md px-2.5 py-1 text-xs font-medium transition-colors {editType ===
opt.value opt.value
? 'bg-indigo-600 text-white' ? 'bg-indigo-600 text-white'
: 'bg-gray-100 opacity-60 hover:opacity-100 dark:bg-gray-700'}" : 'bg-muted opacity-60 hover:opacity-100 dark:bg-muted'}"
onclick={() => { onclick={() => {
editType = opt.value; editType = opt.value;
scheduleAutoSave(); scheduleAutoSave();
@ -166,7 +166,7 @@
</button> </button>
{/each} {/each}
</div> </div>
<div class="h-4 w-px bg-gray-200 dark:bg-gray-600"></div> <div class="h-4 w-px bg-muted dark:bg-muted"></div>
<input <input
type="text" type="text"
bind:value={editTags} bind:value={editTags}
@ -214,7 +214,7 @@
role="presentation" role="presentation"
> >
<div <div
class="w-full max-w-sm rounded-xl bg-white p-6 shadow-2xl dark:bg-gray-800" class="w-full max-w-sm rounded-xl bg-white p-6 shadow-2xl dark:bg-card"
onclick={(e) => e.stopPropagation()} onclick={(e) => e.stopPropagation()}
role="none" role="none"
> >
@ -223,7 +223,7 @@
<div class="mt-4 flex justify-end gap-2"> <div class="mt-4 flex justify-end gap-2">
<button <button
onclick={() => (showDeleteConfirm = false)} onclick={() => (showDeleteConfirm = false)}
class="rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-700" class="rounded-lg border border-border-strong px-4 py-2 text-sm font-medium hover:bg-muted dark:border-border dark:hover:bg-muted"
> >
Abbrechen Abbrechen
</button> </button>

View file

@ -53,7 +53,7 @@
} }
const inputClass = const inputClass =
'w-full rounded-lg border border-gray-300 bg-white px-4 py-3 focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-200 dark:border-gray-600 dark:bg-gray-700'; 'w-full rounded-lg border border-border-strong bg-white px-4 py-3 focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-200 dark:border-border dark:bg-muted';
</script> </script>
<svelte:head> <svelte:head>
@ -78,7 +78,7 @@
<!-- Create Form --> <!-- Create Form -->
{#if showCreateForm} {#if showCreateForm}
<div <div
class="mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm dark:border-gray-700 dark:bg-gray-800" class="mb-6 rounded-xl border border-border-strong bg-white p-6 shadow-sm dark:border-border dark:bg-card"
> >
<h3 class="mb-4 text-lg font-semibold">Neuen Space erstellen</h3> <h3 class="mb-4 text-lg font-semibold">Neuen Space erstellen</h3>
<div class="space-y-4"> <div class="space-y-4">
@ -119,7 +119,7 @@
<div class="flex justify-end gap-2"> <div class="flex justify-end gap-2">
<button <button
onclick={() => (showCreateForm = false)} onclick={() => (showCreateForm = false)}
class="rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-700" class="rounded-lg border border-border-strong px-4 py-2 text-sm font-medium hover:bg-muted dark:border-border dark:hover:bg-muted"
> >
Abbrechen Abbrechen
</button> </button>
@ -142,7 +142,7 @@
type="text" type="text"
bind:value={searchQuery} bind:value={searchQuery}
placeholder="Spaces durchsuchen..." placeholder="Spaces durchsuchen..."
class="w-full rounded-lg border border-gray-300 bg-white py-2.5 pl-9 pr-4 focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-200 dark:border-gray-600 dark:bg-gray-700" class="w-full rounded-lg border border-border-strong bg-white py-2.5 pl-9 pr-4 focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-200 dark:border-border dark:bg-muted"
/> />
</div> </div>
@ -150,7 +150,7 @@
<div class="grid grid-cols-1 gap-3 md:grid-cols-2"> <div class="grid grid-cols-1 gap-3 md:grid-cols-2">
{#each filteredSpaces as space (space.id)} {#each filteredSpaces as space (space.id)}
<div <div
class="group rounded-xl border border-gray-200 bg-white p-4 transition-all hover:shadow-md dark:border-gray-700 dark:bg-gray-800" class="group rounded-xl border border-border-strong bg-white p-4 transition-all hover:shadow-md dark:border-border dark:bg-card"
> >
<div class="flex items-start justify-between"> <div class="flex items-start justify-between">
<a href="/context/spaces/{space.id}" class="min-w-0 flex-1"> <a href="/context/spaces/{space.id}" class="min-w-0 flex-1">
@ -173,7 +173,7 @@
> >
<button <button
onclick={() => handleTogglePin(space.id)} onclick={() => handleTogglePin(space.id)}
class="rounded p-1.5 hover:bg-gray-100 dark:hover:bg-gray-700" class="rounded p-1.5 hover:bg-muted dark:hover:bg-muted"
title={space.pinned ? 'Loslassen' : 'Anheften'} title={space.pinned ? 'Loslassen' : 'Anheften'}
> >
{space.pinned ? '&#9733;' : '&#9734;'} {space.pinned ? '&#9733;' : '&#9734;'}
@ -199,7 +199,7 @@
</div> </div>
{:else} {:else}
<div <div
class="flex flex-col items-center justify-center rounded-xl border-2 border-dashed border-gray-300 py-16 text-center dark:border-gray-600" class="flex flex-col items-center justify-center rounded-xl border-2 border-dashed border-border-strong py-16 text-center dark:border-border"
> >
<Plus size={48} class="mb-4 opacity-20" /> <Plus size={48} class="mb-4 opacity-20" />
<h2 class="text-lg font-medium opacity-60">Noch keine Spaces</h2> <h2 class="text-lg font-medium opacity-60">Noch keine Spaces</h2>
@ -227,7 +227,7 @@
role="presentation" role="presentation"
> >
<div <div
class="w-full max-w-sm rounded-xl bg-white p-6 shadow-2xl dark:bg-gray-800" class="w-full max-w-sm rounded-xl bg-white p-6 shadow-2xl dark:bg-card"
onclick={(e) => e.stopPropagation()} onclick={(e) => e.stopPropagation()}
role="none" role="none"
> >
@ -239,7 +239,7 @@
<div class="mt-4 flex justify-end gap-2"> <div class="mt-4 flex justify-end gap-2">
<button <button
onclick={() => (deleteTarget = null)} onclick={() => (deleteTarget = null)}
class="rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-700" class="rounded-lg border border-border-strong px-4 py-2 text-sm font-medium hover:bg-muted dark:border-border dark:hover:bg-muted"
> >
Abbrechen Abbrechen
</button> </button>

View file

@ -116,20 +116,20 @@
{:else} {:else}
<!-- Space Header --> <!-- Space Header -->
<div <div
class="mb-6 rounded-xl border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-800" class="mb-6 rounded-xl border border-border-strong bg-white p-6 dark:border-border dark:bg-card"
> >
{#if editingName} {#if editingName}
<div class="space-y-3"> <div class="space-y-3">
<input <input
type="text" type="text"
bind:value={editName} bind:value={editName}
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-xl font-bold focus:border-indigo-500 focus:outline-none dark:border-gray-600 dark:bg-gray-700" class="w-full rounded-lg border border-border-strong bg-white px-3 py-2 text-xl font-bold focus:border-indigo-500 focus:outline-none dark:border-border dark:bg-muted"
/> />
<textarea <textarea
bind:value={editDescription} bind:value={editDescription}
rows="2" rows="2"
placeholder="Beschreibung..." placeholder="Beschreibung..."
class="w-full resize-none rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm focus:border-indigo-500 focus:outline-none dark:border-gray-600 dark:bg-gray-700" class="w-full resize-none rounded-lg border border-border-strong bg-white px-3 py-2 text-sm focus:border-indigo-500 focus:outline-none dark:border-border dark:bg-muted"
></textarea> ></textarea>
<div class="flex gap-2"> <div class="flex gap-2">
<button <button
@ -139,7 +139,7 @@
<Check size={14} /> Speichern <Check size={14} /> Speichern
</button> </button>
<button <button
class="flex items-center gap-1 rounded-lg border border-gray-300 px-3 py-1.5 text-sm font-medium hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-700" class="flex items-center gap-1 rounded-lg border border-border-strong px-3 py-1.5 text-sm font-medium hover:bg-muted dark:border-border dark:hover:bg-muted"
onclick={cancelEdit} onclick={cancelEdit}
> >
<X size={14} /> Abbrechen <X size={14} /> Abbrechen
@ -159,7 +159,7 @@
</div> </div>
</div> </div>
<button <button
class="rounded-lg p-2 opacity-60 transition-colors hover:bg-gray-100 hover:opacity-100 dark:hover:bg-gray-700" class="rounded-lg p-2 opacity-60 transition-colors hover:bg-muted hover:opacity-100 dark:hover:bg-muted"
onclick={startEdit} onclick={startEdit}
title={$_('common.edit')} title={$_('common.edit')}
> >
@ -176,7 +176,7 @@
<button <button
class="rounded-lg px-3 py-1.5 text-sm transition-colors {typeFilter === filter.value class="rounded-lg px-3 py-1.5 text-sm transition-colors {typeFilter === filter.value
? 'bg-indigo-600 text-white' ? 'bg-indigo-600 text-white'
: 'opacity-60 hover:bg-gray-100 dark:hover:bg-gray-700'}" : 'opacity-60 hover:bg-muted dark:hover:bg-muted'}"
onclick={() => (typeFilter = filter.value)} onclick={() => (typeFilter = filter.value)}
> >
{filter.label} {filter.label}
@ -194,7 +194,7 @@
type="text" type="text"
bind:value={searchQuery} bind:value={searchQuery}
placeholder={$_('common.search')} placeholder={$_('common.search')}
class="w-48 rounded-lg border border-gray-300 bg-white py-1.5 pl-8 pr-3 text-sm focus:border-indigo-500 focus:outline-none dark:border-gray-600 dark:bg-gray-700" class="w-48 rounded-lg border border-border-strong bg-white py-1.5 pl-8 pr-3 text-sm focus:border-indigo-500 focus:outline-none dark:border-border dark:bg-muted"
/> />
</div> </div>
<button <button
@ -212,7 +212,7 @@
<div class="grid grid-cols-1 gap-3 md:grid-cols-2"> <div class="grid grid-cols-1 gap-3 md:grid-cols-2">
{#each filteredDocuments as doc (doc.id)} {#each filteredDocuments as doc (doc.id)}
<div <div
class="group rounded-xl border border-gray-200 bg-white p-4 transition-all hover:shadow-md dark:border-gray-700 dark:bg-gray-800" class="group rounded-xl border border-border-strong bg-white p-4 transition-all hover:shadow-md dark:border-border dark:bg-card"
> >
<div class="flex items-start justify-between"> <div class="flex items-start justify-between">
<a href="/context/documents/{doc.id}" class="min-w-0 flex-1"> <a href="/context/documents/{doc.id}" class="min-w-0 flex-1">
@ -243,7 +243,7 @@
> >
<button <button
onclick={() => handleTogglePinDoc(doc.id)} onclick={() => handleTogglePinDoc(doc.id)}
class="rounded p-1 hover:bg-gray-100 dark:hover:bg-gray-700" class="rounded p-1 hover:bg-muted dark:hover:bg-muted"
title={doc.pinned ? 'Loslassen' : 'Anheften'} title={doc.pinned ? 'Loslassen' : 'Anheften'}
> >
{doc.pinned ? '&#9733;' : '&#9734;'} {doc.pinned ? '&#9733;' : '&#9734;'}
@ -265,7 +265,7 @@
</div> </div>
{:else} {:else}
<div <div
class="rounded-xl border-2 border-dashed border-gray-300 p-12 text-center dark:border-gray-600" class="rounded-xl border-2 border-dashed border-border-strong p-12 text-center dark:border-border"
> >
<p class="opacity-60">Keine Dokumente in diesem Space</p> <p class="opacity-60">Keine Dokumente in diesem Space</p>
<button <button

View file

@ -34,7 +34,7 @@
if (percentage >= 100) return 'bg-green-500'; if (percentage >= 100) return 'bg-green-500';
if (percentage >= 75) return 'bg-blue-500'; if (percentage >= 75) return 'bg-blue-500';
if (percentage >= 50) return 'bg-yellow-500'; if (percentage >= 50) return 'bg-yellow-500';
return 'bg-gray-400'; return 'bg-muted';
} }
</script> </script>

View file

@ -523,6 +523,6 @@
aria-label="Bild schließen" aria-label="Bild schließen"
> >
<img src={meal.photoUrl} alt={meal.description} class="max-h-full max-w-full object-contain" /> <img src={meal.photoUrl} alt={meal.description} class="max-h-full max-w-full object-contain" />
<span class="absolute right-4 top-4 text-3xl text-white/80">×</span> <span class="absolute right-4 top-4 text-3xl text-foreground">×</span>
</button> </button>
{/if} {/if}

View file

@ -201,7 +201,7 @@
{tag.name} {tag.name}
<button <button
onclick={() => handleRemoveTag(tag.id)} onclick={() => handleRemoveTag(tag.id)}
class="ml-0.5 rounded-full hover:bg-white/20" class="ml-0.5 rounded-full hover:bg-muted/20"
> >
<X size={12} /> <X size={12} />
</button> </button>

View file

@ -165,7 +165,7 @@
: `linear-gradient(135deg, ${mood.colors.join(', ')})`} : `linear-gradient(135deg, ${mood.colors.join(', ')})`}
<button <button
onclick={() => (fullscreenMood = mood)} onclick={() => (fullscreenMood = mood)}
class="mood-card group relative aspect-[16/10] w-full overflow-hidden rounded-2xl border-[3px] border-transparent transition-all duration-200 hover:border-white/40 focus:outline-none focus:ring-2 focus:ring-primary/50" class="mood-card group relative aspect-[16/10] w-full overflow-hidden rounded-2xl border-[3px] border-transparent transition-all duration-200 hover:border-border/40 focus:outline-none focus:ring-2 focus:ring-primary/50"
style="--mood-color: {mood.colors[0]}" style="--mood-color: {mood.colors[0]}"
> >
<div <div
@ -178,7 +178,7 @@
<div class="absolute inset-x-0 bottom-0 p-4 text-left"> <div class="absolute inset-x-0 bottom-0 p-4 text-left">
<h3 class="text-lg font-semibold text-white drop-shadow-md">{mood.name}</h3> <h3 class="text-lg font-semibold text-white drop-shadow-md">{mood.name}</h3>
<span <span
class="mt-1 inline-block rounded-full bg-white/20 px-2 py-0.5 text-[10px] font-medium text-white/80 backdrop-blur-sm capitalize" class="mt-1 inline-block rounded-full bg-muted/20 px-2 py-0.5 text-[10px] font-medium text-foreground backdrop-blur-sm capitalize"
>{mood.animation}</span >{mood.animation}</span
> >
</div> </div>
@ -197,7 +197,7 @@
deleteMood(mood); deleteMood(mood);
} }
}} }}
class="absolute right-2 top-2 rounded-full bg-black/20 p-1.5 text-white/70 opacity-0 backdrop-blur-sm transition-all hover:bg-black/40 hover:text-white group-hover:opacity-100 cursor-pointer" class="absolute right-2 top-2 rounded-full bg-black/20 p-1.5 text-foreground opacity-0 backdrop-blur-sm transition-all hover:bg-black/40 hover:text-white group-hover:opacity-100 cursor-pointer"
> >
<X size={14} /> <X size={14} />
</div> </div>

View file

@ -20,8 +20,10 @@
<Card> <Card>
<div class="py-12 text-center"> <div class="py-12 text-center">
<span class="mb-4 block text-6xl">🏢</span> <span class="mb-4 block text-6xl">🏢</span>
<h3 class="mb-2 text-lg font-semibold text-gray-900 dark:text-white">No organizations yet</h3> <h3 class="mb-2 text-lg font-semibold text-foreground dark:text-white">
<p class="mb-6 text-sm text-gray-500 dark:text-gray-400"> No organizations yet
</h3>
<p class="mb-6 text-sm text-muted-foreground dark:text-muted-foreground">
Create your first organization to get started Create your first organization to get started
</p> </p>
<Button variant="primary">Create Organization</Button> <Button variant="primary">Create Organization</Button>

View file

@ -58,7 +58,7 @@
{#snippet actions()} {#snippet actions()}
<a <a
href="/organizations" href="/organizations"
class="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" class="text-sm text-muted-foreground hover:text-muted-foreground dark:text-muted-foreground dark:hover:text-foreground"
> >
Back Back
</a> </a>
@ -94,21 +94,21 @@
{#if activeTab === 'overview'} {#if activeTab === 'overview'}
<div class="grid gap-6 md:grid-cols-2"> <div class="grid gap-6 md:grid-cols-2">
<Card> <Card>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Details</h3> <h3 class="text-lg font-semibold text-foreground dark:text-white mb-4">Details</h3>
<dl class="space-y-3 text-sm"> <dl class="space-y-3 text-sm">
<div> <div>
<dt class="text-gray-500 dark:text-gray-400">Name</dt> <dt class="text-muted-foreground dark:text-muted-foreground">Name</dt>
<dd class="font-medium text-gray-900 dark:text-white">{org.name}</dd> <dd class="font-medium text-foreground dark:text-white">{org.name}</dd>
</div> </div>
{#if org.slug} {#if org.slug}
<div> <div>
<dt class="text-gray-500 dark:text-gray-400">Slug</dt> <dt class="text-muted-foreground dark:text-muted-foreground">Slug</dt>
<dd class="font-medium text-gray-900 dark:text-white">{org.slug}</dd> <dd class="font-medium text-foreground dark:text-white">{org.slug}</dd>
</div> </div>
{/if} {/if}
<div> <div>
<dt class="text-gray-500 dark:text-gray-400">Created</dt> <dt class="text-muted-foreground dark:text-muted-foreground">Created</dt>
<dd class="font-medium text-gray-900 dark:text-white"> <dd class="font-medium text-foreground dark:text-white">
{new Date(org.createdAt).toLocaleDateString()} {new Date(org.createdAt).toLocaleDateString()}
</dd> </dd>
</div> </div>
@ -116,7 +116,7 @@
</Card> </Card>
<Card> <Card>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Landing Page</h3> <h3 class="text-lg font-semibold text-foreground dark:text-white mb-4">Landing Page</h3>
{#if org.metadata?.landingPage?.enabled} {#if org.metadata?.landingPage?.enabled}
<div class="space-y-2"> <div class="space-y-2">
<p class="text-sm text-green-600 dark:text-green-400 flex items-center gap-2"> <p class="text-sm text-green-600 dark:text-green-400 flex items-center gap-2">
@ -135,7 +135,9 @@
{/if} {/if}
</div> </div>
{:else} {:else}
<p class="text-sm text-gray-500 dark:text-gray-400">Not configured yet</p> <p class="text-sm text-muted-foreground dark:text-muted-foreground">
Not configured yet
</p>
{/if} {/if}
<div class="mt-4"> <div class="mt-4">
<a href="/organizations/{data.orgId}/landing"> <a href="/organizations/{data.orgId}/landing">
@ -146,7 +148,7 @@
</div> </div>
{:else if activeTab === 'members'} {:else if activeTab === 'members'}
<Card> <Card>
<div class="py-8 text-center text-sm text-gray-500 dark:text-gray-400"> <div class="py-8 text-center text-sm text-muted-foreground dark:text-muted-foreground">
Member management coming soon. Member management coming soon.
</div> </div>
</Card> </Card>

View file

@ -43,7 +43,7 @@
{#snippet actions()} {#snippet actions()}
<a <a
href="/organizations/{data.orgId}" href="/organizations/{data.orgId}"
class="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" class="text-sm text-muted-foreground hover:text-muted-foreground dark:text-muted-foreground dark:hover:text-foreground"
> >
Back to {org.name} Back to {org.name}
</a> </a>

View file

@ -66,7 +66,7 @@
> >
<button <button
onclick={() => handleRestore(img)} onclick={() => handleRestore(img)}
class="rounded-lg bg-white/90 p-2 text-foreground hover:bg-white transition-colors" class="rounded-lg bg-muted/90 p-2 text-foreground hover:bg-white transition-colors"
title="Wiederherstellen" title="Wiederherstellen"
> >
<ArrowCounterClockwise size={18} /> <ArrowCounterClockwise size={18} />

View file

@ -105,7 +105,7 @@
> >
<h3 class="font-semibold text-white truncate">{plant.name}</h3> <h3 class="font-semibold text-white truncate">{plant.name}</h3>
{#if plant.commonName} {#if plant.commonName}
<p class="text-xs text-white/70 truncate">{plant.commonName}</p> <p class="text-xs text-foreground truncate">{plant.commonName}</p>
{/if} {/if}
{#if getWateringText(plant.id)} {#if getWateringText(plant.id)}
<div class="water-status {getWateringClass(plant.id)} mt-1"> <div class="water-status {getWateringClass(plant.id)} mt-1">

View file

@ -61,8 +61,10 @@
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="flex items-center justify-between mb-8"> <div class="flex items-center justify-between mb-8">
<div> <div>
<h1 class="text-2xl font-bold text-slate-900 dark:text-white">My Presentations</h1> <h1 class="text-2xl font-bold text-foreground dark:text-white">My Presentations</h1>
<p class="text-slate-600 dark:text-slate-400 mt-1">Create and manage your slide decks</p> <p class="text-muted-foreground/70 dark:text-muted-foreground mt-1">
Create and manage your slide decks
</p>
</div> </div>
<button <button
onclick={() => (showCreateModal = true)} onclick={() => (showCreateModal = true)}
@ -76,12 +78,14 @@
{#if decks.length === 0} {#if decks.length === 0}
<div class="text-center py-16"> <div class="text-center py-16">
<div <div
class="mx-auto w-16 h-16 bg-slate-100 dark:bg-slate-800 rounded-full flex items-center justify-center mb-4" class="mx-auto w-16 h-16 bg-muted dark:bg-card rounded-full flex items-center justify-center mb-4"
> >
<Presentation class="w-8 h-8 text-slate-400" /> <Presentation class="w-8 h-8 text-muted-foreground" />
</div> </div>
<h2 class="text-lg font-medium text-slate-900 dark:text-white mb-2">No presentations yet</h2> <h2 class="text-lg font-medium text-foreground dark:text-white mb-2">No presentations yet</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4">Create your first deck to get started</p> <p class="text-muted-foreground/70 dark:text-muted-foreground mb-4">
Create your first deck to get started
</p>
<button <button
onclick={() => (showCreateModal = true)} onclick={() => (showCreateModal = true)}
class="inline-flex items-center gap-2 px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white font-medium rounded-lg transition-colors" class="inline-flex items-center gap-2 px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white font-medium rounded-lg transition-colors"
@ -94,22 +98,26 @@
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{#each decks as deck (deck.id)} {#each decks as deck (deck.id)}
<div <div
class="group bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 overflow-hidden hover:shadow-md transition-shadow" class="group bg-white dark:bg-card rounded-xl shadow-sm border border-border-strong dark:border-border overflow-hidden hover:shadow-md transition-shadow"
> >
<a href="/presi/deck/{deck.id}" class="block"> <a href="/presi/deck/{deck.id}" class="block">
<div <div
class="aspect-video bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center" class="aspect-video bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center"
> >
<Presentation class="w-12 h-12 text-white/80" /> <Presentation class="w-12 h-12 text-foreground" />
</div> </div>
<div class="p-4"> <div class="p-4">
<h3 class="font-semibold text-slate-900 dark:text-white truncate">{deck.title}</h3> <h3 class="font-semibold text-foreground dark:text-white truncate">{deck.title}</h3>
{#if deck.description} {#if deck.description}
<p class="text-sm text-slate-600 dark:text-slate-400 mt-1 line-clamp-2"> <p
class="text-sm text-muted-foreground/70 dark:text-muted-foreground mt-1 line-clamp-2"
>
{deck.description} {deck.description}
</p> </p>
{/if} {/if}
<div class="flex items-center gap-4 mt-3 text-xs text-slate-500 dark:text-slate-400"> <div
class="flex items-center gap-4 mt-3 text-xs text-muted-foreground dark:text-muted-foreground"
>
<span class="flex items-center gap-1"> <span class="flex items-center gap-1">
<Clock class="w-3.5 h-3.5" /> <Clock class="w-3.5 h-3.5" />
{formatDate(deck.updatedAt)} {formatDate(deck.updatedAt)}
@ -123,7 +131,7 @@
e.preventDefault(); e.preventDefault();
confirmDelete({ id: deck.id, title: deck.title }); confirmDelete({ id: deck.id, title: deck.title });
}} }}
class="p-2 text-slate-400 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/30 rounded-lg transition-colors opacity-0 group-hover:opacity-100" class="p-2 text-muted-foreground hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/30 rounded-lg transition-colors opacity-0 group-hover:opacity-100"
> >
<Trash class="w-4 h-4" /> <Trash class="w-4 h-4" />
</button> </button>
@ -137,15 +145,17 @@
<!-- Create Deck Modal --> <!-- Create Deck Modal -->
{#if showCreateModal} {#if showCreateModal}
<div class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50"> <div class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50">
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-xl w-full max-w-md"> <div class="bg-white dark:bg-card rounded-xl shadow-xl w-full max-w-md">
<form onsubmit={handleCreateDeck}> <form onsubmit={handleCreateDeck}>
<div class="p-6"> <div class="p-6">
<h2 class="text-xl font-semibold text-slate-900 dark:text-white mb-4">Create New Deck</h2> <h2 class="text-xl font-semibold text-foreground dark:text-white mb-4">
Create New Deck
</h2>
<div class="space-y-4"> <div class="space-y-4">
<div> <div>
<label <label
for="title" for="title"
class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1" class="block text-sm font-medium text-muted-foreground dark:text-foreground/90 mb-1"
>Title</label >Title</label
> >
<input <input
@ -153,31 +163,31 @@
id="title" id="title"
bind:value={newDeckTitle} bind:value={newDeckTitle}
required required
class="w-full px-4 py-2 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-700 text-slate-900 dark:text-white focus:ring-2 focus:ring-primary-500 focus:border-transparent" class="w-full px-4 py-2 border border-border-strong dark:border-border rounded-lg bg-white dark:bg-muted text-foreground dark:text-white focus:ring-2 focus:ring-primary-500 focus:border-transparent"
placeholder="My Presentation" placeholder="My Presentation"
/> />
</div> </div>
<div> <div>
<label <label
for="description" for="description"
class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1" class="block text-sm font-medium text-muted-foreground dark:text-foreground/90 mb-1"
>Description (optional)</label >Description (optional)</label
> >
<textarea <textarea
id="description" id="description"
bind:value={newDeckDescription} bind:value={newDeckDescription}
rows="3" rows="3"
class="w-full px-4 py-2 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-700 text-slate-900 dark:text-white focus:ring-2 focus:ring-primary-500 focus:border-transparent resize-none" class="w-full px-4 py-2 border border-border-strong dark:border-border rounded-lg bg-white dark:bg-muted text-foreground dark:text-white focus:ring-2 focus:ring-primary-500 focus:border-transparent resize-none"
placeholder="What is this presentation about?" placeholder="What is this presentation about?"
></textarea> ></textarea>
</div> </div>
</div> </div>
</div> </div>
<div class="px-6 py-4 bg-slate-50 dark:bg-slate-900/50 flex justify-end gap-3 rounded-b-xl"> <div class="px-6 py-4 bg-muted dark:bg-card/50 flex justify-end gap-3 rounded-b-xl">
<button <button
type="button" type="button"
onclick={() => (showCreateModal = false)} onclick={() => (showCreateModal = false)}
class="px-4 py-2 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700 rounded-lg transition-colors" class="px-4 py-2 text-muted-foreground dark:text-foreground/90 hover:bg-muted dark:hover:bg-muted rounded-lg transition-colors"
>Cancel</button >Cancel</button
> >
<button <button
@ -195,9 +205,9 @@
<!-- Delete Modal --> <!-- Delete Modal -->
{#if showDeleteModal} {#if showDeleteModal}
<div class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50"> <div class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50">
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-xl w-full max-w-md p-6"> <div class="bg-white dark:bg-card rounded-xl shadow-xl w-full max-w-md p-6">
<h2 class="text-xl font-semibold text-slate-900 dark:text-white mb-2">Delete Deck</h2> <h2 class="text-xl font-semibold text-foreground dark:text-white mb-2">Delete Deck</h2>
<p class="text-slate-600 dark:text-slate-400 mb-6"> <p class="text-muted-foreground/70 dark:text-muted-foreground mb-6">
Are you sure you want to delete "{deckToDelete?.title}"? Are you sure you want to delete "{deckToDelete?.title}"?
</p> </p>
<div class="flex justify-end gap-3"> <div class="flex justify-end gap-3">
@ -206,7 +216,7 @@
showDeleteModal = false; showDeleteModal = false;
deckToDelete = null; deckToDelete = null;
}} }}
class="px-4 py-2 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700 rounded-lg transition-colors" class="px-4 py-2 text-muted-foreground dark:text-foreground/90 hover:bg-muted dark:hover:bg-muted rounded-lg transition-colors"
>Cancel</button >Cancel</button
> >
<button <button

View file

@ -132,29 +132,28 @@
{#if currentDeck} {#if currentDeck}
<div class="flex items-center justify-between mb-8"> <div class="flex items-center justify-between mb-8">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<a <a href="/presi" class="p-2 hover:bg-muted dark:hover:bg-card rounded-lg transition-colors">
href="/presi" <ArrowLeft class="w-5 h-5 text-muted-foreground/70 dark:text-muted-foreground" />
class="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg transition-colors"
>
<ArrowLeft class="w-5 h-5 text-slate-600 dark:text-slate-400" />
</a> </a>
<div> <div>
<h1 class="text-2xl font-bold text-slate-900 dark:text-white">{currentDeck.title}</h1> <h1 class="text-2xl font-bold text-foreground dark:text-white">{currentDeck.title}</h1>
{#if currentDeck.description} {#if currentDeck.description}
<p class="text-slate-600 dark:text-slate-400 mt-1">{currentDeck.description}</p> <p class="text-muted-foreground/70 dark:text-muted-foreground mt-1">
{currentDeck.description}
</p>
{/if} {/if}
</div> </div>
</div> </div>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<button <button
onclick={openCreateSlide} onclick={openCreateSlide}
class="flex items-center gap-2 px-4 py-2 bg-slate-100 dark:bg-slate-700 hover:bg-slate-200 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-200 font-medium rounded-lg transition-colors" class="flex items-center gap-2 px-4 py-2 bg-muted dark:bg-muted hover:bg-muted dark:hover:bg-muted text-muted-foreground dark:text-foreground font-medium rounded-lg transition-colors"
> >
<Plus class="w-5 h-5" /> Add Slide <Plus class="w-5 h-5" /> Add Slide
</button> </button>
<button <button
onclick={() => (showShare = true)} onclick={() => (showShare = true)}
class="rounded-lg p-2 text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-slate-200 transition-colors" class="rounded-lg p-2 text-muted-foreground dark:text-muted-foreground hover:text-muted-foreground dark:hover:text-foreground transition-colors"
title="Kurzlink teilen" title="Kurzlink teilen"
> >
<ShareNetwork size={20} /> <ShareNetwork size={20} />
@ -173,11 +172,11 @@
{#if currentSlides.length === 0} {#if currentSlides.length === 0}
<div class="text-center py-16"> <div class="text-center py-16">
<div <div
class="mx-auto w-16 h-16 bg-slate-100 dark:bg-slate-800 rounded-full flex items-center justify-center mb-4" class="mx-auto w-16 h-16 bg-muted dark:bg-card rounded-full flex items-center justify-center mb-4"
> >
<TextT class="w-8 h-8 text-slate-400" /> <TextT class="w-8 h-8 text-muted-foreground" />
</div> </div>
<h2 class="text-lg font-medium text-slate-900 dark:text-white mb-2">No slides yet</h2> <h2 class="text-lg font-medium text-foreground dark:text-white mb-2">No slides yet</h2>
<button <button
onclick={openCreateSlide} onclick={openCreateSlide}
class="inline-flex items-center gap-2 px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white font-medium rounded-lg transition-colors" class="inline-flex items-center gap-2 px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white font-medium rounded-lg transition-colors"
@ -189,11 +188,11 @@
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{#each currentSlides as slide, index (slide.id)} {#each currentSlides as slide, index (slide.id)}
<div <div
class="group bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 overflow-hidden" class="group bg-white dark:bg-card rounded-xl shadow-sm border border-border-strong dark:border-border overflow-hidden"
> >
<button <button
onclick={() => openEditSlide(slide)} onclick={() => openEditSlide(slide)}
class="w-full aspect-video bg-slate-100 dark:bg-slate-700 p-4 flex flex-col items-center justify-center text-left" class="w-full aspect-video bg-muted dark:bg-muted p-4 flex flex-col items-center justify-center text-left"
> >
{#if slide.content.imageUrl} {#if slide.content.imageUrl}
<img <img
@ -204,12 +203,14 @@
{:else} {:else}
<div class="w-full h-full flex flex-col items-center justify-center p-4"> <div class="w-full h-full flex flex-col items-center justify-center p-4">
{#if slide.content.title}<h3 {#if slide.content.title}<h3
class="text-lg font-semibold text-slate-900 dark:text-white text-center line-clamp-2" class="text-lg font-semibold text-foreground dark:text-white text-center line-clamp-2"
> >
{slide.content.title} {slide.content.title}
</h3>{/if} </h3>{/if}
{#if slide.content.bulletPoints?.length} {#if slide.content.bulletPoints?.length}
<ul class="mt-2 text-sm text-slate-600 dark:text-slate-400 space-y-1"> <ul
class="mt-2 text-sm text-muted-foreground/70 dark:text-muted-foreground space-y-1"
>
{#each slide.content.bulletPoints.slice(0, 3) as point}<li class="truncate"> {#each slide.content.bulletPoints.slice(0, 3) as point}<li class="truncate">
{point} {point}
</li>{/each} </li>{/each}
@ -219,28 +220,34 @@
{/if} {/if}
</button> </button>
<div <div
class="p-3 flex items-center justify-between border-t border-slate-200 dark:border-slate-700" class="p-3 flex items-center justify-between border-t border-border-strong dark:border-border"
> >
<span class="text-sm text-slate-500">Slide {index + 1}</span> <span class="text-sm text-muted-foreground">Slide {index + 1}</span>
<div <div
class="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity" class="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity"
> >
<button <button
onclick={() => moveSlide(slide, 'up')} onclick={() => moveSlide(slide, 'up')}
disabled={index === 0} disabled={index === 0}
class="p-1.5 hover:bg-slate-100 dark:hover:bg-slate-700 rounded disabled:opacity-30" class="p-1.5 hover:bg-muted dark:hover:bg-muted rounded disabled:opacity-30"
><CaretUp class="w-4 h-4 text-slate-600 dark:text-slate-400" /></button ><CaretUp
class="w-4 h-4 text-muted-foreground/70 dark:text-muted-foreground"
/></button
> >
<button <button
onclick={() => moveSlide(slide, 'down')} onclick={() => moveSlide(slide, 'down')}
disabled={index === currentSlides.length - 1} disabled={index === currentSlides.length - 1}
class="p-1.5 hover:bg-slate-100 dark:hover:bg-slate-700 rounded disabled:opacity-30" class="p-1.5 hover:bg-muted dark:hover:bg-muted rounded disabled:opacity-30"
><CaretDown class="w-4 h-4 text-slate-600 dark:text-slate-400" /></button ><CaretDown
class="w-4 h-4 text-muted-foreground/70 dark:text-muted-foreground"
/></button
> >
<button <button
onclick={() => openEditSlide(slide)} onclick={() => openEditSlide(slide)}
class="p-1.5 hover:bg-slate-100 dark:hover:bg-slate-700 rounded" class="p-1.5 hover:bg-muted dark:hover:bg-muted rounded"
><PencilSimple class="w-4 h-4 text-slate-600 dark:text-slate-400" /></button ><PencilSimple
class="w-4 h-4 text-muted-foreground/70 dark:text-muted-foreground"
/></button
> >
<button <button
onclick={() => confirmDeleteSlide(slide)} onclick={() => confirmDeleteSlide(slide)}
@ -259,39 +266,40 @@
<!-- Slide Editor Modal --> <!-- Slide Editor Modal -->
{#if showSlideModal} {#if showSlideModal}
<div class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 overflow-y-auto"> <div class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 overflow-y-auto">
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-xl w-full max-w-2xl my-8"> <div class="bg-white dark:bg-card rounded-xl shadow-xl w-full max-w-2xl my-8">
<form onsubmit={handleSaveSlide}> <form onsubmit={handleSaveSlide}>
<div <div
class="p-6 border-b border-slate-200 dark:border-slate-700 flex items-center justify-between" class="p-6 border-b border-border-strong dark:border-border flex items-center justify-between"
> >
<h2 class="text-xl font-semibold text-slate-900 dark:text-white"> <h2 class="text-xl font-semibold text-foreground dark:text-white">
{editingSlide ? 'Edit Slide' : 'New Slide'} {editingSlide ? 'Edit Slide' : 'New Slide'}
</h2> </h2>
<button <button
type="button" type="button"
onclick={() => (showSlideModal = false)} onclick={() => (showSlideModal = false)}
class="p-2 hover:bg-slate-100 dark:hover:bg-slate-700 rounded-lg" class="p-2 hover:bg-muted dark:hover:bg-muted rounded-lg"
><X class="w-5 h-5 text-slate-600 dark:text-slate-400" /></button ><X class="w-5 h-5 text-muted-foreground/70 dark:text-muted-foreground" /></button
> >
</div> </div>
<div class="p-6 space-y-6 max-h-[60vh] overflow-y-auto"> <div class="p-6 space-y-6 max-h-[60vh] overflow-y-auto">
<div> <div>
<label <label
for="slideTitle" for="slideTitle"
class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">Title</label class="block text-sm font-medium text-muted-foreground dark:text-foreground/90 mb-1"
>Title</label
> >
<input <input
type="text" type="text"
id="slideTitle" id="slideTitle"
bind:value={slideTitle} bind:value={slideTitle}
class="w-full px-4 py-2 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-700 text-slate-900 dark:text-white focus:ring-2 focus:ring-primary-500" class="w-full px-4 py-2 border border-border-strong dark:border-border rounded-lg bg-white dark:bg-muted text-foreground dark:text-white focus:ring-2 focus:ring-primary-500"
placeholder="Slide title" placeholder="Slide title"
/> />
</div> </div>
<div> <div>
<label <label
for="slideImage" for="slideImage"
class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1" class="block text-sm font-medium text-muted-foreground dark:text-foreground/90 mb-1"
><span class="flex items-center gap-2" ><span class="flex items-center gap-2"
><Image class="w-4 h-4" /> Image URL (optional)</span ><Image class="w-4 h-4" /> Image URL (optional)</span
></label ></label
@ -300,38 +308,39 @@
type="url" type="url"
id="slideImage" id="slideImage"
bind:value={slideImageUrl} bind:value={slideImageUrl}
class="w-full px-4 py-2 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-700 text-slate-900 dark:text-white focus:ring-2 focus:ring-primary-500" class="w-full px-4 py-2 border border-border-strong dark:border-border rounded-lg bg-white dark:bg-muted text-foreground dark:text-white focus:ring-2 focus:ring-primary-500"
placeholder="https://example.com/image.jpg" placeholder="https://example.com/image.jpg"
/> />
</div> </div>
<div> <div>
<label <label
for="slideBody" for="slideBody"
class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1" class="block text-sm font-medium text-muted-foreground dark:text-foreground/90 mb-1"
>Body Text (optional)</label >Body Text (optional)</label
> >
<textarea <textarea
id="slideBody" id="slideBody"
bind:value={slideBody} bind:value={slideBody}
rows="3" rows="3"
class="w-full px-4 py-2 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-700 text-slate-900 dark:text-white focus:ring-2 focus:ring-primary-500 resize-none" class="w-full px-4 py-2 border border-border-strong dark:border-border rounded-lg bg-white dark:bg-muted text-foreground dark:text-white focus:ring-2 focus:ring-primary-500 resize-none"
placeholder="Main content text..." placeholder="Main content text..."
></textarea> ></textarea>
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2" <label
class="block text-sm font-medium text-muted-foreground dark:text-foreground/90 mb-2"
><span class="flex items-center gap-2"><List class="w-4 h-4" /> Bullet Points</span ><span class="flex items-center gap-2"><List class="w-4 h-4" /> Bullet Points</span
></label ></label
> >
<div class="space-y-2"> <div class="space-y-2">
{#each slideBulletPoints as point, index} {#each slideBulletPoints as point, index}
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-slate-400"></span> <span class="text-muted-foreground"></span>
<input <input
type="text" type="text"
value={point} value={point}
oninput={(e) => updateBulletPoint(index, (e.target as HTMLInputElement).value)} oninput={(e) => updateBulletPoint(index, (e.target as HTMLInputElement).value)}
class="flex-1 px-4 py-2 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-700 text-slate-900 dark:text-white focus:ring-2 focus:ring-primary-500" class="flex-1 px-4 py-2 border border-border-strong dark:border-border rounded-lg bg-white dark:bg-muted text-foreground dark:text-white focus:ring-2 focus:ring-primary-500"
placeholder="Add a point..." placeholder="Add a point..."
/> />
<button <button
@ -351,11 +360,11 @@
</div> </div>
</div> </div>
</div> </div>
<div class="px-6 py-4 bg-slate-50 dark:bg-slate-900/50 flex justify-end gap-3 rounded-b-xl"> <div class="px-6 py-4 bg-muted dark:bg-card/50 flex justify-end gap-3 rounded-b-xl">
<button <button
type="button" type="button"
onclick={() => (showSlideModal = false)} onclick={() => (showSlideModal = false)}
class="px-4 py-2 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700 rounded-lg transition-colors" class="px-4 py-2 text-muted-foreground dark:text-foreground/90 hover:bg-muted dark:hover:bg-muted rounded-lg transition-colors"
>Cancel</button >Cancel</button
> >
<button <button
@ -373,9 +382,9 @@
<!-- Delete Slide Modal --> <!-- Delete Slide Modal -->
{#if showDeleteModal} {#if showDeleteModal}
<div class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50"> <div class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50">
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-xl w-full max-w-md p-6"> <div class="bg-white dark:bg-card rounded-xl shadow-xl w-full max-w-md p-6">
<h2 class="text-xl font-semibold text-slate-900 dark:text-white mb-2">Delete Slide</h2> <h2 class="text-xl font-semibold text-foreground dark:text-white mb-2">Delete Slide</h2>
<p class="text-slate-600 dark:text-slate-400 mb-6"> <p class="text-muted-foreground/70 dark:text-muted-foreground mb-6">
Are you sure you want to delete this slide? Are you sure you want to delete this slide?
</p> </p>
<div class="flex justify-end gap-3"> <div class="flex justify-end gap-3">
@ -384,7 +393,7 @@
showDeleteModal = false; showDeleteModal = false;
slideToDelete = null; slideToDelete = null;
}} }}
class="px-4 py-2 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700 rounded-lg transition-colors" class="px-4 py-2 text-muted-foreground dark:text-foreground/90 hover:bg-muted dark:hover:bg-muted rounded-lg transition-colors"
>Cancel</button >Cancel</button
> >
<button <button

View file

@ -116,7 +116,7 @@
<svelte:head><title>Presenting: {currentDeck?.title || 'Loading...'}</title></svelte:head> <svelte:head><title>Presenting: {currentDeck?.title || 'Loading...'}</title></svelte:head>
<div class="fixed inset-0 bg-slate-900 text-white flex flex-col"> <div class="fixed inset-0 bg-card text-white flex flex-col">
{#if currentSlide} {#if currentSlide}
<div <div
class="absolute top-0 left-0 right-0 z-10 p-4 flex items-center justify-between bg-gradient-to-b from-black/50 to-transparent transition-opacity duration-300" class="absolute top-0 left-0 right-0 z-10 p-4 flex items-center justify-between bg-gradient-to-b from-black/50 to-transparent transition-opacity duration-300"
@ -125,18 +125,18 @@
> >
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<h1 class="text-lg font-medium truncate max-w-xs">{currentDeck?.title}</h1> <h1 class="text-lg font-medium truncate max-w-xs">{currentDeck?.title}</h1>
<span class="text-sm text-slate-400" <span class="text-sm text-muted-foreground"
>Slide {currentSlideIndex + 1} of {currentSlides.length}</span >Slide {currentSlideIndex + 1} of {currentSlides.length}</span
> >
</div> </div>
<button onclick={exitPresentation} class="p-2 hover:bg-white/10 rounded-lg transition-colors" <button onclick={exitPresentation} class="p-2 hover:bg-muted/10 rounded-lg transition-colors"
><X class="w-6 h-6" /></button ><X class="w-6 h-6" /></button
> >
</div> </div>
<div class="flex-1 flex items-center justify-center p-8 pt-20 pb-32"> <div class="flex-1 flex items-center justify-center p-8 pt-20 pb-32">
<div <div
class="w-full max-w-6xl aspect-video bg-slate-800 rounded-2xl shadow-2xl overflow-hidden flex flex-col items-center justify-center p-12" class="w-full max-w-6xl aspect-video bg-card rounded-2xl shadow-2xl overflow-hidden flex flex-col items-center justify-center p-12"
> >
{#if currentSlide.content.imageUrl} {#if currentSlide.content.imageUrl}
<img <img
@ -151,7 +151,7 @@
> >
{currentSlide.content.title} {currentSlide.content.title}
</h2>{/if} </h2>{/if}
{#if currentSlide.content.body}<p class="text-xl md:text-2xl text-slate-300 mb-8"> {#if currentSlide.content.body}<p class="text-xl md:text-2xl text-foreground/90 mb-8">
{currentSlide.content.body} {currentSlide.content.body}
</p>{/if} </p>{/if}
{#if currentSlide.content.bulletPoints?.length} {#if currentSlide.content.bulletPoints?.length}
@ -175,10 +175,10 @@
> >
<div class="max-w-4xl mx-auto flex items-center justify-between"> <div class="max-w-4xl mx-auto flex items-center justify-between">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<button onclick={toggleTimer} class="p-2 hover:bg-white/10 rounded-lg transition-colors"> <button onclick={toggleTimer} class="p-2 hover:bg-muted/10 rounded-lg transition-colors">
{#if isTimerRunning}<Pause class="w-5 h-5" />{:else}<Play class="w-5 h-5" />{/if} {#if isTimerRunning}<Pause class="w-5 h-5" />{:else}<Play class="w-5 h-5" />{/if}
</button> </button>
<div class="flex items-center gap-2 text-slate-300"> <div class="flex items-center gap-2 text-foreground/90">
<Clock class="w-4 h-4" /><span class="font-mono">{formatTime(elapsedSeconds)}</span> <Clock class="w-4 h-4" /><span class="font-mono">{formatTime(elapsedSeconds)}</span>
</div> </div>
</div> </div>
@ -186,7 +186,7 @@
<button <button
onclick={prevSlide} onclick={prevSlide}
disabled={currentSlideIndex === 0} disabled={currentSlideIndex === 0}
class="p-3 hover:bg-white/10 rounded-lg transition-colors disabled:opacity-30" class="p-3 hover:bg-muted/10 rounded-lg transition-colors disabled:opacity-30"
><CaretLeft class="w-6 h-6" /></button ><CaretLeft class="w-6 h-6" /></button
> >
<div class="flex items-center gap-2 px-4"> <div class="flex items-center gap-2 px-4">
@ -197,27 +197,27 @@
class="w-2 h-2 rounded-full transition-all" class="w-2 h-2 rounded-full transition-all"
class:bg-primary-500={index === currentSlideIndex} class:bg-primary-500={index === currentSlideIndex}
class:w-4={index === currentSlideIndex} class:w-4={index === currentSlideIndex}
class:bg-slate-500={index !== currentSlideIndex} class:bg-muted={index !== currentSlideIndex}
></button> ></button>
{/each} {/each}
</div> </div>
<button <button
onclick={nextSlide} onclick={nextSlide}
disabled={currentSlideIndex === currentSlides.length - 1} disabled={currentSlideIndex === currentSlides.length - 1}
class="p-3 hover:bg-white/10 rounded-lg transition-colors disabled:opacity-30" class="p-3 hover:bg-muted/10 rounded-lg transition-colors disabled:opacity-30"
><CaretRight class="w-6 h-6" /></button ><CaretRight class="w-6 h-6" /></button
> >
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button <button
onclick={() => (showNotes = !showNotes)} onclick={() => (showNotes = !showNotes)}
class="p-2 hover:bg-white/10 rounded-lg transition-colors" class="p-2 hover:bg-muted/10 rounded-lg transition-colors"
> >
{#if showNotes}<EyeSlash class="w-5 h-5" />{:else}<Eye class="w-5 h-5" />{/if} {#if showNotes}<EyeSlash class="w-5 h-5" />{:else}<Eye class="w-5 h-5" />{/if}
</button> </button>
<button <button
onclick={toggleFullscreen} onclick={toggleFullscreen}
class="p-2 hover:bg-white/10 rounded-lg transition-colors" class="p-2 hover:bg-muted/10 rounded-lg transition-colors"
> >
{#if isFullscreen}<ArrowsIn class="w-5 h-5" />{:else}<ArrowsOut class="w-5 h-5" />{/if} {#if isFullscreen}<ArrowsIn class="w-5 h-5" />{:else}<ArrowsOut class="w-5 h-5" />{/if}
</button> </button>
@ -226,7 +226,7 @@
</div> </div>
{:else} {:else}
<div class="flex-1 flex items-center justify-center"> <div class="flex-1 flex items-center justify-center">
<p class="text-slate-400">No slides in this deck</p> <p class="text-muted-foreground">No slides in this deck</p>
</div> </div>
{/if} {/if}
</div> </div>

View file

@ -31,10 +31,10 @@
); );
const statusIcons = { const statusIcons = {
open: { icon: Clock, color: 'text-gray-500' }, open: { icon: Clock, color: 'text-muted-foreground' },
researching: { icon: CircleNotch, color: 'text-blue-500' }, researching: { icon: CircleNotch, color: 'text-blue-500' },
answered: { icon: CheckCircle, color: 'text-green-500' }, answered: { icon: CheckCircle, color: 'text-green-500' },
archived: { icon: Archive, color: 'text-gray-400' }, archived: { icon: Archive, color: 'text-muted-foreground' },
}; };
const depthLabels: Record<ResearchDepth, string> = { const depthLabels: Record<ResearchDepth, string> = {
@ -151,7 +151,7 @@
<div class="space-y-3"> <div class="space-y-3">
{#each filteredQuestions as question (question.id)} {#each filteredQuestions as question (question.id)}
{@const StatusIcon = statusIcons[question.status]?.icon || Clock} {@const StatusIcon = statusIcons[question.status]?.icon || Clock}
{@const statusColor = statusIcons[question.status]?.color || 'text-gray-500'} {@const statusColor = statusIcons[question.status]?.color || 'text-muted-foreground'}
<a <a
href="/questions/{question.id}" href="/questions/{question.id}"
@ -214,7 +214,7 @@
? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400' ? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
: question.priority === 'high' : question.priority === 'high'
? 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400' ? 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
: 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400'}" : 'bg-muted text-muted-foreground/70 dark:bg-card dark:text-muted-foreground'}"
> >
{question.priority} {question.priority}
</span> </span>

View file

@ -130,7 +130,7 @@
const statusLabels: Record<string, { label: string; color: string }> = { const statusLabels: Record<string, { label: string; color: string }> = {
open: { open: {
label: 'Offen', label: 'Offen',
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300', color: 'bg-muted text-muted-foreground dark:bg-card dark:text-foreground/90',
}, },
researching: { researching: {
label: 'Recherche', label: 'Recherche',
@ -142,7 +142,7 @@
}, },
archived: { archived: {
label: 'Archiviert', label: 'Archiviert',
color: 'bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-400', color: 'bg-muted text-muted-foreground dark:bg-card dark:text-muted-foreground',
}, },
}; };

View file

@ -109,7 +109,7 @@
> >
<div class="text-4xl mb-3">{data.icon}</div> <div class="text-4xl mb-3">{data.icon}</div>
<h2 class="text-xl font-semibold mb-1">{$_(data.labelKey)}</h2> <h2 class="text-xl font-semibold mb-1">{$_(data.labelKey)}</h2>
<p class="text-white/80 text-sm"> <p class="text-foreground text-sm">
{$_('categories.quotes', { values: { count: data.count } })} {$_('categories.quotes', { values: { count: data.count } })}
</p> </p>
</button> </button>

View file

@ -125,9 +125,8 @@
} }
async function handleExport() { async function handleExport() {
const { skillTable, activityTable, achievementTable } = await import( const { skillTable, activityTable, achievementTable } =
'$lib/modules/skilltree/collections' await import('$lib/modules/skilltree/collections');
);
const [allSkillsData, allActivitiesData, allAchievementsData] = await Promise.all([ const [allSkillsData, allActivitiesData, allAchievementsData] = await Promise.all([
skillTable.toArray(), skillTable.toArray(),
activityTable.toArray(), activityTable.toArray(),
@ -159,9 +158,8 @@
try { try {
const text = await file.text(); const text = await file.text();
const data = JSON.parse(text); const data = JSON.parse(text);
const { skillTable, activityTable, achievementTable } = await import( const { skillTable, activityTable, achievementTable } =
'$lib/modules/skilltree/collections' await import('$lib/modules/skilltree/collections');
);
if (data.skills) await skillTable.bulkPut(data.skills); if (data.skills) await skillTable.bulkPut(data.skills);
if (data.activities) await activityTable.bulkPut(data.activities); if (data.activities) await activityTable.bulkPut(data.activities);
if (data.achievements) await achievementTable.bulkPut(data.achievements); if (data.achievements) await achievementTable.bulkPut(data.achievements);
@ -181,7 +179,7 @@
<div class="min-h-screen"> <div class="min-h-screen">
<!-- Header --> <!-- Header -->
<header class="border-b border-gray-800 bg-gray-900/80 backdrop-blur-sm sticky top-0 z-40"> <header class="border-b border-border bg-card/80 backdrop-blur-sm sticky top-0 z-40">
<div class="mx-auto max-w-7xl px-4 py-4"> <div class="mx-auto max-w-7xl px-4 py-4">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
@ -192,13 +190,13 @@
<!-- Achievements --> <!-- Achievements -->
<a <a
href="/skilltree/achievements" href="/skilltree/achievements"
class="relative rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-800 hover:text-yellow-400" class="relative rounded-lg p-2 text-muted-foreground transition-colors hover:bg-card hover:text-yellow-400"
title="Achievements" title="Achievements"
> >
<Trophy class="h-5 w-5" /> <Trophy class="h-5 w-5" />
{#if achievementStats.unlocked > 0} {#if achievementStats.unlocked > 0}
<span <span
class="absolute -right-0.5 -top-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-yellow-500 text-[10px] font-bold text-gray-900" class="absolute -right-0.5 -top-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-yellow-500 text-[10px] font-bold text-foreground"
> >
{achievementStats.unlocked} {achievementStats.unlocked}
</span> </span>
@ -207,7 +205,7 @@
<!-- Tree View --> <!-- Tree View -->
<a <a
href="/skilltree/tree" href="/skilltree/tree"
class="rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-800 hover:text-emerald-400" class="rounded-lg p-2 text-muted-foreground transition-colors hover:bg-card hover:text-emerald-400"
title="Skill-Tree Ansicht" title="Skill-Tree Ansicht"
> >
<Graph class="h-5 w-5" /> <Graph class="h-5 w-5" />
@ -215,7 +213,7 @@
<!-- Templates --> <!-- Templates -->
<button <button
onclick={() => (showTemplatesModal = true)} onclick={() => (showTemplatesModal = true)}
class="rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-800 hover:text-yellow-500" class="rounded-lg p-2 text-muted-foreground transition-colors hover:bg-card hover:text-yellow-500"
title="Skill-Vorlagen" title="Skill-Vorlagen"
> >
<Sparkle class="h-5 w-5" /> <Sparkle class="h-5 w-5" />
@ -223,14 +221,14 @@
<!-- Export/Import --> <!-- Export/Import -->
<button <button
onclick={handleExport} onclick={handleExport}
class="rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-800 hover:text-white" class="rounded-lg p-2 text-muted-foreground transition-colors hover:bg-card hover:text-white"
title="Daten exportieren" title="Daten exportieren"
> >
<DownloadSimple class="h-5 w-5" /> <DownloadSimple class="h-5 w-5" />
</button> </button>
<button <button
onclick={handleImport} onclick={handleImport}
class="rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-800 hover:text-white" class="rounded-lg p-2 text-muted-foreground transition-colors hover:bg-card hover:text-white"
title="Daten importieren" title="Daten importieren"
> >
<UploadSimple class="h-5 w-5" /> <UploadSimple class="h-5 w-5" />
@ -260,7 +258,7 @@
class="rounded-full px-4 py-2 text-sm font-medium transition-colors {selectedBranch === class="rounded-full px-4 py-2 text-sm font-medium transition-colors {selectedBranch ===
'all' 'all'
? 'bg-emerald-600 text-white' ? 'bg-emerald-600 text-white'
: 'bg-gray-800 text-gray-300 hover:bg-gray-700'}" : 'bg-card text-foreground/90 hover:bg-muted'}"
> >
Alle ({skills.length}) Alle ({skills.length})
</button> </button>
@ -272,7 +270,7 @@
class="rounded-full px-4 py-2 text-sm font-medium transition-colors {selectedBranch === class="rounded-full px-4 py-2 text-sm font-medium transition-colors {selectedBranch ===
branch branch
? 'bg-emerald-600 text-white' ? 'bg-emerald-600 text-white'
: 'bg-gray-800 text-gray-300 hover:bg-gray-700'}" : 'bg-card text-foreground/90 hover:bg-muted'}"
> >
{info.name} ({count}) {info.name} ({count})
</button> </button>
@ -284,13 +282,13 @@
<!-- Skills Grid --> <!-- Skills Grid -->
{#if filteredSkills.length === 0} {#if filteredSkills.length === 0}
<div class="mt-16 text-center"> <div class="mt-16 text-center">
<div <div class="mx-auto mb-6 flex h-24 w-24 items-center justify-center rounded-full bg-card">
class="mx-auto mb-6 flex h-24 w-24 items-center justify-center rounded-full bg-gray-800" <Tree class="h-12 w-12 text-muted-foreground/70" />
>
<Tree class="h-12 w-12 text-gray-600" />
</div> </div>
<h2 class="mb-2 text-xl font-semibold text-gray-300">Noch keine Skills</h2> <h2 class="mb-2 text-xl font-semibold text-foreground/90">Noch keine Skills</h2>
<p class="mb-6 text-gray-500">Füge deinen ersten Skill hinzu und beginne dein Abenteuer!</p> <p class="mb-6 text-muted-foreground">
Füge deinen ersten Skill hinzu und beginne dein Abenteuer!
</p>
<button <button
onclick={() => (showAddSkillModal = true)} onclick={() => (showAddSkillModal = true)}
class="inline-flex items-center gap-2 rounded-lg bg-emerald-600 px-6 py-3 font-medium text-white transition-colors hover:bg-emerald-500" class="inline-flex items-center gap-2 rounded-lg bg-emerald-600 px-6 py-3 font-medium text-white transition-colors hover:bg-emerald-500"
@ -323,7 +321,7 @@
{#each getRecentActivities(activities).slice(0, 5) as activity} {#each getRecentActivities(activities).slice(0, 5) as activity}
{@const skill = getSkillById(skills, activity.skillId)} {@const skill = getSkillById(skills, activity.skillId)}
{#if skill} {#if skill}
<div class="flex items-center justify-between rounded-lg bg-gray-800/50 px-4 py-3"> <div class="flex items-center justify-between rounded-lg bg-card/50 px-4 py-3">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div <div
class="flex h-8 w-8 items-center justify-center rounded-full bg-emerald-900/50 text-sm font-medium text-emerald-400" class="flex h-8 w-8 items-center justify-center rounded-full bg-emerald-900/50 text-sm font-medium text-emerald-400"
@ -332,10 +330,10 @@
</div> </div>
<div> <div>
<span class="font-medium text-white">{skill.name}</span> <span class="font-medium text-white">{skill.name}</span>
<span class="text-gray-400"> - {activity.description}</span> <span class="text-muted-foreground"> - {activity.description}</span>
</div> </div>
</div> </div>
<span class="text-sm text-gray-500"> <span class="text-sm text-muted-foreground">
{new Date(activity.timestamp).toLocaleDateString('de-DE')} {new Date(activity.timestamp).toLocaleDateString('de-DE')}
</span> </span>
</div> </div>

View file

@ -42,13 +42,13 @@
<div class="min-h-screen"> <div class="min-h-screen">
<!-- Header --> <!-- Header -->
<header class="border-b border-gray-800 bg-gray-900/80 backdrop-blur-sm sticky top-0 z-40"> <header class="border-b border-border bg-card/80 backdrop-blur-sm sticky top-0 z-40">
<div class="mx-auto max-w-7xl px-4 py-4"> <div class="mx-auto max-w-7xl px-4 py-4">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<a <a
href="/skilltree" href="/skilltree"
class="flex items-center gap-2 rounded-lg px-3 py-2 text-gray-400 transition-colors hover:bg-gray-800 hover:text-white" class="flex items-center gap-2 rounded-lg px-3 py-2 text-muted-foreground transition-colors hover:bg-card hover:text-white"
> >
<ArrowLeft class="h-5 w-5" /> <ArrowLeft class="h-5 w-5" />
</a> </a>
@ -71,12 +71,12 @@
<main class="mx-auto max-w-7xl px-4 py-8"> <main class="mx-auto max-w-7xl px-4 py-8">
<!-- Progress overview --> <!-- Progress overview -->
<div class="mb-8 rounded-xl border border-gray-700 bg-gray-800/50 p-6"> <div class="mb-8 rounded-xl border border-border bg-card/50 p-6">
<div class="flex items-center justify-between mb-3"> <div class="flex items-center justify-between mb-3">
<h2 class="text-lg font-semibold text-white">Fortschritt</h2> <h2 class="text-lg font-semibold text-white">Fortschritt</h2>
<span class="text-2xl font-bold text-yellow-400">{completion}%</span> <span class="text-2xl font-bold text-yellow-400">{completion}%</span>
</div> </div>
<div class="h-3 overflow-hidden rounded-full bg-gray-700"> <div class="h-3 overflow-hidden rounded-full bg-muted">
<div <div
class="h-full rounded-full bg-gradient-to-r from-yellow-500 to-yellow-400 transition-all duration-500" class="h-full rounded-full bg-gradient-to-r from-yellow-500 to-yellow-400 transition-all duration-500"
style="width: {completion}%" style="width: {completion}%"
@ -100,8 +100,8 @@
onclick={() => (selectedCategory = 'all')} onclick={() => (selectedCategory = 'all')}
class="rounded-full px-4 py-2 text-sm font-medium transition-colors {selectedCategory === class="rounded-full px-4 py-2 text-sm font-medium transition-colors {selectedCategory ===
'all' 'all'
? 'bg-yellow-500 text-gray-900' ? 'bg-yellow-500 text-foreground'
: 'bg-gray-800 text-gray-300 hover:bg-gray-700'}" : 'bg-card text-foreground/90 hover:bg-muted'}"
> >
Alle ({achievements.length}) Alle ({achievements.length})
</button> </button>
@ -111,8 +111,8 @@
onclick={() => (selectedCategory = category)} onclick={() => (selectedCategory = category)}
class="rounded-full px-4 py-2 text-sm font-medium transition-colors {selectedCategory === class="rounded-full px-4 py-2 text-sm font-medium transition-colors {selectedCategory ===
category category
? 'bg-yellow-500 text-gray-900' ? 'bg-yellow-500 text-foreground'
: 'bg-gray-800 text-gray-300 hover:bg-gray-700'}" : 'bg-card text-foreground/90 hover:bg-muted'}"
> >
{info.name} ({count}) {info.name} ({count})
</button> </button>
@ -123,7 +123,7 @@
onclick={() => (showOnlyUnlocked = !showOnlyUnlocked)} onclick={() => (showOnlyUnlocked = !showOnlyUnlocked)}
class="rounded-full px-4 py-2 text-sm font-medium transition-colors {showOnlyUnlocked class="rounded-full px-4 py-2 text-sm font-medium transition-colors {showOnlyUnlocked
? 'bg-yellow-500/20 text-yellow-400' ? 'bg-yellow-500/20 text-yellow-400'
: 'bg-gray-800 text-gray-300 hover:bg-gray-700'}" : 'bg-card text-foreground/90 hover:bg-muted'}"
> >
{showOnlyUnlocked ? 'Nur freigeschaltete' : 'Alle zeigen'} {showOnlyUnlocked ? 'Nur freigeschaltete' : 'Alle zeigen'}
</button> </button>
@ -133,13 +133,11 @@
<!-- Achievement grid --> <!-- Achievement grid -->
{#if filteredAchievements().length === 0} {#if filteredAchievements().length === 0}
<div class="mt-16 text-center"> <div class="mt-16 text-center">
<div <div class="mx-auto mb-6 flex h-24 w-24 items-center justify-center rounded-full bg-card">
class="mx-auto mb-6 flex h-24 w-24 items-center justify-center rounded-full bg-gray-800" <Trophy class="h-12 w-12 text-muted-foreground/70" />
>
<Trophy class="h-12 w-12 text-gray-600" />
</div> </div>
<h2 class="mb-2 text-xl font-semibold text-gray-300">Keine Achievements gefunden</h2> <h2 class="mb-2 text-xl font-semibold text-foreground/90">Keine Achievements gefunden</h2>
<p class="text-gray-500"> <p class="text-muted-foreground">
{showOnlyUnlocked {showOnlyUnlocked
? 'Du hast in dieser Kategorie noch keine Achievements freigeschaltet.' ? 'Du hast in dieser Kategorie noch keine Achievements freigeschaltet.'
: 'Keine Achievements in dieser Kategorie.'} : 'Keine Achievements in dieser Kategorie.'}

View file

@ -59,14 +59,14 @@
<title>Skill Tree View - SkillTree</title> <title>Skill Tree View - SkillTree</title>
</svelte:head> </svelte:head>
<div class="min-h-screen bg-gray-900 text-white"> <div class="min-h-screen bg-card text-white">
<!-- Header --> <!-- Header -->
<header class="border-b border-gray-800 bg-gray-900/80 backdrop-blur-sm sticky top-0 z-40"> <header class="border-b border-border bg-card/80 backdrop-blur-sm sticky top-0 z-40">
<div class="mx-auto max-w-7xl px-4 py-4"> <div class="mx-auto max-w-7xl px-4 py-4">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<a <a
href="/skilltree" href="/skilltree"
class="flex items-center gap-2 rounded-lg px-3 py-2 text-gray-400 transition-colors hover:bg-gray-800 hover:text-white" class="flex items-center gap-2 rounded-lg px-3 py-2 text-muted-foreground transition-colors hover:bg-card hover:text-white"
> >
<ArrowLeft class="h-5 w-5" /> <ArrowLeft class="h-5 w-5" />
Zurück Zurück
@ -79,7 +79,9 @@
<main class="p-4"> <main class="p-4">
{#if skills.length === 0} {#if skills.length === 0}
<div class="mt-16 text-center"> <div class="mt-16 text-center">
<p class="text-gray-400">Noch keine Skills vorhanden. Erstelle zuerst einige Skills!</p> <p class="text-muted-foreground">
Noch keine Skills vorhanden. Erstelle zuerst einige Skills!
</p>
<a <a
href="/skilltree" href="/skilltree"
class="mt-4 inline-block rounded-lg bg-emerald-600 px-4 py-2 font-medium text-white hover:bg-emerald-500" class="mt-4 inline-block rounded-lg bg-emerald-600 px-4 py-2 font-medium text-white hover:bg-emerald-500"
@ -93,7 +95,7 @@
{#each Object.entries(BRANCH_INFO) as [branch, info]} {#each Object.entries(BRANCH_INFO) as [branch, info]}
{@const count = skills.filter((s) => s.branch === branch).length} {@const count = skills.filter((s) => s.branch === branch).length}
{#if count > 0} {#if count > 0}
<div class="flex items-center gap-2 rounded-full bg-gray-800 px-3 py-1.5 text-sm"> <div class="flex items-center gap-2 rounded-full bg-card px-3 py-1.5 text-sm">
<span class="h-3 w-3 rounded-full" style="background-color: {info.color}"></span> <span class="h-3 w-3 rounded-full" style="background-color: {info.color}"></span>
{info.name} ({count}) {info.name} ({count})
</div> </div>
@ -255,7 +257,7 @@
> >
{level} {level}
</div> </div>
<span class="text-gray-400">{name}</span> <span class="text-muted-foreground">{name}</span>
</div> </div>
{/each} {/each}
</div> </div>

View file

@ -16,8 +16,8 @@
<Card> <Card>
<div class="py-12 text-center"> <div class="py-12 text-center">
<span class="mb-4 block text-6xl">👥</span> <span class="mb-4 block text-6xl">👥</span>
<h3 class="mb-2 text-lg font-semibold text-gray-900 dark:text-white">No teams yet</h3> <h3 class="mb-2 text-lg font-semibold text-foreground dark:text-white">No teams yet</h3>
<p class="mb-6 text-sm text-gray-500 dark:text-gray-400"> <p class="mb-6 text-sm text-muted-foreground dark:text-muted-foreground">
Create or join a team to start collaborating Create or join a team to start collaborating
</p> </p>
<Button variant="primary">Create Team</Button> <Button variant="primary">Create Team</Button>

View file

@ -157,7 +157,7 @@
style="background-color: {project.color}" style="background-color: {project.color}"
></div> ></div>
{:else} {:else}
<div class="h-3 w-3 shrink-0 rounded-full bg-gray-400"></div> <div class="h-3 w-3 shrink-0 rounded-full bg-muted"></div>
{/if} {/if}
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<p class="text-sm font-medium text-[hsl(var(--color-foreground))]">{template.name}</p> <p class="text-sm font-medium text-[hsl(var(--color-foreground))]">{template.name}</p>

View file

@ -248,9 +248,9 @@
} }
const inputClass = const inputClass =
'w-full rounded-lg border border-gray-300 bg-white px-4 py-3 focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-200 dark:border-gray-600 dark:bg-gray-700'; 'w-full rounded-lg border border-border-strong bg-white px-4 py-3 focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-200 dark:border-border dark:bg-muted';
const inputSmClass = const inputSmClass =
'w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm focus:border-indigo-500 focus:outline-none dark:border-gray-600 dark:bg-gray-700'; 'w-full rounded-lg border border-border-strong bg-white px-3 py-2 text-sm focus:border-indigo-500 focus:outline-none dark:border-border dark:bg-muted';
</script> </script>
<svelte:head> <svelte:head>
@ -273,7 +273,7 @@
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<a <a
href="/uload/links" href="/uload/links"
class="rounded-lg border border-gray-300 px-3 py-2 text-sm font-medium hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-700" class="rounded-lg border border-border-strong px-3 py-2 text-sm font-medium hover:bg-muted dark:border-border dark:hover:bg-muted"
> >
Alle Links Alle Links
</a> </a>
@ -289,7 +289,7 @@
<!-- Create Form --> <!-- Create Form -->
{#if showCreateForm} {#if showCreateForm}
<div <div
class="mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm dark:border-gray-700 dark:bg-gray-800" class="mb-6 rounded-xl border border-border-strong bg-white p-6 shadow-sm dark:border-border dark:bg-card"
> >
<div class="grid gap-4 md:grid-cols-2"> <div class="grid gap-4 md:grid-cols-2">
<div class="md:col-span-2"> <div class="md:col-span-2">
@ -447,7 +447,7 @@
type="text" type="text"
bind:value={searchQuery} bind:value={searchQuery}
placeholder="Links durchsuchen..." placeholder="Links durchsuchen..."
class="w-60 rounded-lg border border-gray-300 bg-white py-2 pl-8 pr-3 text-sm focus:border-indigo-500 focus:outline-none dark:border-gray-600 dark:bg-gray-700" class="w-60 rounded-lg border border-border-strong bg-white py-2 pl-8 pr-3 text-sm focus:border-indigo-500 focus:outline-none dark:border-border dark:bg-muted"
/> />
</div> </div>
<select bind:value={selectedStatus} class={inputSmClass} style="max-width: 140px"> <select bind:value={selectedStatus} class={inputSmClass} style="max-width: 140px">
@ -469,12 +469,12 @@
{#if allLinks.loading} {#if allLinks.loading}
<div class="space-y-3"> <div class="space-y-3">
{#each Array(3) as _} {#each Array(3) as _}
<div class="h-20 animate-pulse rounded-xl bg-gray-100 dark:bg-gray-800"></div> <div class="h-20 animate-pulse rounded-xl bg-muted dark:bg-card"></div>
{/each} {/each}
</div> </div>
{:else if filteredLinks.length === 0} {:else if filteredLinks.length === 0}
<div <div
class="rounded-xl border-2 border-dashed border-gray-300 p-12 text-center dark:border-gray-600" class="rounded-xl border-2 border-dashed border-border-strong p-12 text-center dark:border-border"
> >
<LinkIcon size={48} class="mx-auto mb-4 opacity-20" /> <LinkIcon size={48} class="mx-auto mb-4 opacity-20" />
<p class="text-lg font-medium opacity-60">Noch keine Links</p> <p class="text-lg font-medium opacity-60">Noch keine Links</p>
@ -490,7 +490,7 @@
<div class="space-y-3"> <div class="space-y-3">
{#each filteredLinks as link (link.id)} {#each filteredLinks as link (link.id)}
<div <div
class="group rounded-xl border border-gray-200 bg-white p-4 shadow-sm transition-all hover:shadow-md dark:border-gray-700 dark:bg-gray-800" class="group rounded-xl border border-border-strong bg-white p-4 shadow-sm transition-all hover:shadow-md dark:border-border dark:bg-card"
> >
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
@ -498,7 +498,7 @@
<span <span
class="inline-block h-2 w-2 shrink-0 rounded-full {link.isActive class="inline-block h-2 w-2 shrink-0 rounded-full {link.isActive
? 'bg-green-500' ? 'bg-green-500'
: 'bg-gray-400'}" : 'bg-muted'}"
></span> ></span>
<h3 class="truncate font-semibold">{link.title || link.shortCode}</h3> <h3 class="truncate font-semibold">{link.title || link.shortCode}</h3>
<span <span
@ -545,7 +545,7 @@
<div class="ml-4 flex items-center gap-1"> <div class="ml-4 flex items-center gap-1">
<a <a
href="/uload/analytics/{link.id}" href="/uload/analytics/{link.id}"
class="flex items-center gap-1 rounded-lg px-2 py-1.5 text-sm font-medium opacity-60 transition-all hover:bg-gray-100 hover:opacity-100 dark:hover:bg-gray-700" class="flex items-center gap-1 rounded-lg px-2 py-1.5 text-sm font-medium opacity-60 transition-all hover:bg-muted hover:opacity-100 dark:hover:bg-muted"
title="Analytics" title="Analytics"
> >
<ChartBar size={16} /> <ChartBar size={16} />
@ -553,31 +553,34 @@
</a> </a>
<button <button
onclick={() => copyShortUrl(link.shortCode)} onclick={() => copyShortUrl(link.shortCode)}
class="rounded-lg p-2 opacity-0 transition-all hover:bg-gray-100 group-hover:opacity-100 dark:hover:bg-gray-700" class="rounded-lg p-2 opacity-0 transition-all hover:bg-muted group-hover:opacity-100 dark:hover:bg-muted"
title="Link kopieren" title="Link kopieren"
> >
<Copy size={16} /> <Copy size={16} />
</button> </button>
<button <button
onclick={() => (qrLink = link)} onclick={() => (qrLink = link)}
class="rounded-lg p-2 opacity-0 transition-all hover:bg-gray-100 group-hover:opacity-100 dark:hover:bg-gray-700" class="rounded-lg p-2 opacity-0 transition-all hover:bg-muted group-hover:opacity-100 dark:hover:bg-muted"
title="QR-Code" title="QR-Code"
> >
<QrCode size={16} /> <QrCode size={16} />
</button> </button>
<button <button
onclick={() => openEdit(link)} onclick={() => openEdit(link)}
class="rounded-lg p-2 opacity-0 transition-all hover:bg-gray-100 group-hover:opacity-100 dark:hover:bg-gray-700" class="rounded-lg p-2 opacity-0 transition-all hover:bg-muted group-hover:opacity-100 dark:hover:bg-muted"
title={$_('common.edit')} title={$_('common.edit')}
> >
<PencilSimple size={16} /> <PencilSimple size={16} />
</button> </button>
<button <button
onclick={() => toggleActive(link)} onclick={() => toggleActive(link)}
class="rounded-lg p-2 opacity-0 transition-all hover:bg-gray-100 group-hover:opacity-100 dark:hover:bg-gray-700" class="rounded-lg p-2 opacity-0 transition-all hover:bg-muted group-hover:opacity-100 dark:hover:bg-muted"
title={link.isActive ? 'Deaktivieren' : 'Aktivieren'} title={link.isActive ? 'Deaktivieren' : 'Aktivieren'}
> >
<Lightning size={16} class={link.isActive ? 'text-green-500' : 'text-gray-400'} /> <Lightning
size={16}
class={link.isActive ? 'text-green-500' : 'text-muted-foreground'}
/>
</button> </button>
<button <button
onclick={() => deleteLink(link)} onclick={() => deleteLink(link)}
@ -605,7 +608,7 @@
role="presentation" role="presentation"
> >
<div <div
class="w-full max-w-lg rounded-xl bg-white p-6 shadow-2xl dark:bg-gray-800" class="w-full max-w-lg rounded-xl bg-white p-6 shadow-2xl dark:bg-card"
onclick={(e) => e.stopPropagation()} onclick={(e) => e.stopPropagation()}
role="none" role="none"
> >
@ -613,7 +616,7 @@
<h3 class="text-lg font-semibold">Link bearbeiten</h3> <h3 class="text-lg font-semibold">Link bearbeiten</h3>
<button <button
onclick={() => (editingLink = null)} onclick={() => (editingLink = null)}
class="rounded-lg p-1 hover:bg-gray-100 dark:hover:bg-gray-700" class="rounded-lg p-1 hover:bg-muted dark:hover:bg-muted"
> >
<X size={20} /> <X size={20} />
</button> </button>
@ -636,7 +639,7 @@
</div> </div>
</div> </div>
<div class="border-t border-gray-200 pt-4 dark:border-gray-700"> <div class="border-t border-border-strong pt-4 dark:border-border">
<p class="mb-2 text-sm font-medium opacity-70">UTM-Parameter</p> <p class="mb-2 text-sm font-medium opacity-70">UTM-Parameter</p>
<div class="grid gap-3 md:grid-cols-3"> <div class="grid gap-3 md:grid-cols-3">
<input <input
@ -660,7 +663,7 @@
</div> </div>
</div> </div>
<div class="border-t border-gray-200 pt-4 dark:border-gray-700"> <div class="border-t border-border-strong pt-4 dark:border-border">
<p class="mb-2 text-sm font-medium opacity-70">Erweitert</p> <p class="mb-2 text-sm font-medium opacity-70">Erweitert</p>
<div class="grid gap-3 md:grid-cols-3"> <div class="grid gap-3 md:grid-cols-3">
<div> <div>
@ -701,7 +704,7 @@
<div class="mt-6 flex justify-end gap-2"> <div class="mt-6 flex justify-end gap-2">
<button <button
onclick={() => (editingLink = null)} onclick={() => (editingLink = null)}
class="rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-700" class="rounded-lg border border-border-strong px-4 py-2 text-sm font-medium hover:bg-muted dark:border-border dark:hover:bg-muted"
> >
Abbrechen Abbrechen
</button> </button>
@ -727,7 +730,7 @@
role="presentation" role="presentation"
> >
<div <div
class="w-full max-w-sm rounded-xl bg-white p-6 shadow-2xl dark:bg-gray-800" class="w-full max-w-sm rounded-xl bg-white p-6 shadow-2xl dark:bg-card"
onclick={(e) => e.stopPropagation()} onclick={(e) => e.stopPropagation()}
role="none" role="none"
> >
@ -735,7 +738,7 @@
<h3 class="text-lg font-semibold">QR-Code</h3> <h3 class="text-lg font-semibold">QR-Code</h3>
<button <button
onclick={() => (qrLink = null)} onclick={() => (qrLink = null)}
class="rounded-lg p-1 hover:bg-gray-100 dark:hover:bg-gray-700" class="rounded-lg p-1 hover:bg-muted dark:hover:bg-muted"
> >
<X size={20} /> <X size={20} />
</button> </button>
@ -753,7 +756,7 @@
<div class="flex w-full gap-2"> <div class="flex w-full gap-2">
<button <button
onclick={() => copyShortUrl(qrLink!.shortCode)} onclick={() => copyShortUrl(qrLink!.shortCode)}
class="flex-1 rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-700" class="flex-1 rounded-lg border border-border-strong px-4 py-2 text-sm font-medium hover:bg-muted dark:border-border dark:hover:bg-muted"
> >
Link kopieren Link kopieren
</button> </button>

View file

@ -75,13 +75,13 @@
<div class="mx-auto max-w-4xl p-4"> <div class="mx-auto max-w-4xl p-4">
<!-- Header --> <!-- Header -->
<div class="mb-6 flex items-center gap-4"> <div class="mb-6 flex items-center gap-4">
<a href="/uload" class="rounded-lg p-2 transition-colors hover:bg-white/5" title="Zurueck"> <a href="/uload" class="rounded-lg p-2 transition-colors hover:bg-muted/5" title="Zurueck">
<CaretLeft size={20} class="text-white/60" /> <CaretLeft size={20} class="text-muted-foreground" />
</a> </a>
<div> <div>
<h1 class="text-2xl font-bold text-white">Analytics</h1> <h1 class="text-2xl font-bold text-white">Analytics</h1>
{#if link} {#if link}
<p class="mt-1 text-sm text-white/50"> <p class="mt-1 text-sm text-muted-foreground">
<span class="font-mono text-indigo-400">/{link.shortCode}</span> <span class="font-mono text-indigo-400">/{link.shortCode}</span>
&rarr; <span class="truncate">{link.originalUrl}</span> &rarr; <span class="truncate">{link.originalUrl}</span>
</p> </p>
@ -92,40 +92,40 @@
{#if loading} {#if loading}
<div class="space-y-4"> <div class="space-y-4">
{#each Array(4) as _} {#each Array(4) as _}
<div class="h-32 animate-pulse rounded-xl bg-white/5"></div> <div class="h-32 animate-pulse rounded-xl bg-muted/5"></div>
{/each} {/each}
</div> </div>
{:else if !link} {:else if !link}
<div class="rounded-xl border border-white/10 p-12 text-center"> <div class="rounded-xl border border-border/10 p-12 text-center">
<p class="text-white/50">Link nicht gefunden</p> <p class="text-muted-foreground">Link nicht gefunden</p>
</div> </div>
{:else} {:else}
<!-- Stats Overview --> <!-- Stats Overview -->
<div class="mb-6 grid gap-4 sm:grid-cols-4"> <div class="mb-6 grid gap-4 sm:grid-cols-4">
<div class="rounded-xl border border-white/10 bg-white/5 p-5"> <div class="rounded-xl border border-border/10 bg-muted/5 p-5">
<p class="text-xs font-medium uppercase tracking-wider text-white/40">Clicks</p> <p class="text-xs font-medium uppercase tracking-wider text-muted-foreground">Clicks</p>
<p class="mt-1 text-3xl font-bold text-white"> <p class="mt-1 text-3xl font-bold text-white">
{stats?.totalClicks ?? link.clickCount} {stats?.totalClicks ?? link.clickCount}
</p> </p>
</div> </div>
<div class="rounded-xl border border-white/10 bg-white/5 p-5"> <div class="rounded-xl border border-border/10 bg-muted/5 p-5">
<p class="text-xs font-medium uppercase tracking-wider text-white/40">Unique</p> <p class="text-xs font-medium uppercase tracking-wider text-muted-foreground">Unique</p>
<p class="mt-1 text-3xl font-bold text-white"> <p class="mt-1 text-3xl font-bold text-white">
{stats?.uniqueVisitors ?? '-'} {stats?.uniqueVisitors ?? '-'}
</p> </p>
</div> </div>
<div class="rounded-xl border border-white/10 bg-white/5 p-5"> <div class="rounded-xl border border-border/10 bg-muted/5 p-5">
<p class="text-xs font-medium uppercase tracking-wider text-white/40">Status</p> <p class="text-xs font-medium uppercase tracking-wider text-muted-foreground">Status</p>
<p class="mt-1 text-3xl font-bold"> <p class="mt-1 text-3xl font-bold">
{#if link.isActive} {#if link.isActive}
<span class="text-green-400">Aktiv</span> <span class="text-green-400">Aktiv</span>
{:else} {:else}
<span class="text-white/30">Inaktiv</span> <span class="text-muted-foreground/70">Inaktiv</span>
{/if} {/if}
</p> </p>
</div> </div>
<div class="rounded-xl border border-white/10 bg-white/5 p-5"> <div class="rounded-xl border border-border/10 bg-muted/5 p-5">
<p class="text-xs font-medium uppercase tracking-wider text-white/40">Erstellt</p> <p class="text-xs font-medium uppercase tracking-wider text-muted-foreground">Erstellt</p>
<p class="mt-1 text-lg font-bold text-white"> <p class="mt-1 text-lg font-bold text-white">
{new Date(link.createdAt).toLocaleDateString('de')} {new Date(link.createdAt).toLocaleDateString('de')}
</p> </p>
@ -133,11 +133,11 @@
</div> </div>
<!-- Link Details --> <!-- Link Details -->
<div class="mb-6 rounded-xl border border-white/10 bg-white/5 p-6"> <div class="mb-6 rounded-xl border border-border/10 bg-muted/5 p-6">
<h2 class="mb-4 text-lg font-semibold text-white">Link Details</h2> <h2 class="mb-4 text-lg font-semibold text-white">Link Details</h2>
<div class="space-y-3"> <div class="space-y-3">
<div class="flex items-center justify-between text-sm"> <div class="flex items-center justify-between text-sm">
<span class="text-white/50">Ziel-URL</span> <span class="text-muted-foreground">Ziel-URL</span>
<a <a
href={link.originalUrl} href={link.originalUrl}
target="_blank" target="_blank"
@ -149,31 +149,31 @@
</div> </div>
{#if link.title} {#if link.title}
<div class="flex items-center justify-between text-sm"> <div class="flex items-center justify-between text-sm">
<span class="text-white/50">Titel</span> <span class="text-muted-foreground">Titel</span>
<span class="text-white">{link.title}</span> <span class="text-white">{link.title}</span>
</div> </div>
{/if} {/if}
{#if link.utmSource || link.utmMedium || link.utmCampaign} {#if link.utmSource || link.utmMedium || link.utmCampaign}
<div class="border-t border-white/10 pt-3"> <div class="border-t border-border/10 pt-3">
<p class="mb-2 text-xs font-medium uppercase tracking-wider text-white/40"> <p class="mb-2 text-xs font-medium uppercase tracking-wider text-muted-foreground">
UTM-Parameter UTM-Parameter
</p> </p>
<div class="grid gap-2 sm:grid-cols-3"> <div class="grid gap-2 sm:grid-cols-3">
{#if link.utmSource} {#if link.utmSource}
<div class="text-sm text-white/70"> <div class="text-sm text-foreground">
<span class="text-white/40">Source:</span> <span class="text-muted-foreground">Source:</span>
{link.utmSource} {link.utmSource}
</div> </div>
{/if} {/if}
{#if link.utmMedium} {#if link.utmMedium}
<div class="text-sm text-white/70"> <div class="text-sm text-foreground">
<span class="text-white/40">Medium:</span> <span class="text-muted-foreground">Medium:</span>
{link.utmMedium} {link.utmMedium}
</div> </div>
{/if} {/if}
{#if link.utmCampaign} {#if link.utmCampaign}
<div class="text-sm text-white/70"> <div class="text-sm text-foreground">
<span class="text-white/40">Campaign:</span> <span class="text-muted-foreground">Campaign:</span>
{link.utmCampaign} {link.utmCampaign}
</div> </div>
{/if} {/if}
@ -182,19 +182,19 @@
{/if} {/if}
{#if link.expiresAt} {#if link.expiresAt}
<div class="flex items-center justify-between text-sm"> <div class="flex items-center justify-between text-sm">
<span class="text-white/50">Laeuft ab</span> <span class="text-muted-foreground">Laeuft ab</span>
<span class="text-white">{new Date(link.expiresAt).toLocaleDateString('de')}</span> <span class="text-white">{new Date(link.expiresAt).toLocaleDateString('de')}</span>
</div> </div>
{/if} {/if}
{#if link.maxClicks} {#if link.maxClicks}
<div class="flex items-center justify-between text-sm"> <div class="flex items-center justify-between text-sm">
<span class="text-white/50">Max Klicks</span> <span class="text-muted-foreground">Max Klicks</span>
<span class="text-white">{link.clickCount} / {link.maxClicks}</span> <span class="text-white">{link.clickCount} / {link.maxClicks}</span>
</div> </div>
{/if} {/if}
{#if link.password} {#if link.password}
<div class="flex items-center justify-between text-sm"> <div class="flex items-center justify-between text-sm">
<span class="text-white/50">Passwortgeschuetzt</span> <span class="text-muted-foreground">Passwortgeschuetzt</span>
<span class="text-white">Ja</span> <span class="text-white">Ja</span>
</div> </div>
{/if} {/if}
@ -202,7 +202,7 @@
</div> </div>
<!-- Timeline --> <!-- Timeline -->
<div class="mb-6 rounded-xl border border-white/10 bg-white/5 p-6"> <div class="mb-6 rounded-xl border border-border/10 bg-muted/5 p-6">
<div class="mb-4 flex items-center justify-between"> <div class="mb-4 flex items-center justify-between">
<h2 class="text-lg font-semibold text-white">Clicks ueber Zeit</h2> <h2 class="text-lg font-semibold text-white">Clicks ueber Zeit</h2>
<div class="flex gap-1"> <div class="flex gap-1">
@ -211,7 +211,7 @@
onclick={() => changeDays(d)} onclick={() => changeDays(d)}
class="rounded-md px-3 py-1 text-xs font-medium transition-colors {days === d class="rounded-md px-3 py-1 text-xs font-medium transition-colors {days === d
? 'bg-indigo-600 text-white' ? 'bg-indigo-600 text-white'
: 'bg-white/10 text-white/60 hover:bg-white/15'}" : 'bg-muted/10 text-muted-foreground hover:bg-muted/15'}"
> >
{d}T {d}T
</button> </button>
@ -227,28 +227,30 @@
style="height: {Math.max((day.count / maxTimelineCount) * 100, 2)}%" style="height: {Math.max((day.count / maxTimelineCount) * 100, 2)}%"
></div> ></div>
<div <div
class="pointer-events-none absolute -top-8 hidden rounded bg-white/90 px-2 py-1 text-xs text-gray-900 group-hover:block" class="pointer-events-none absolute -top-8 hidden rounded bg-muted/90 px-2 py-1 text-xs text-foreground group-hover:block"
> >
{day.count} {day.count}
</div> </div>
</div> </div>
{/each} {/each}
</div> </div>
<div class="mt-1 flex justify-between text-xs text-white/30"> <div class="mt-1 flex justify-between text-xs text-muted-foreground/70">
<span>{timeline[0]?.date}</span> <span>{timeline[0]?.date}</span>
<span>{timeline[timeline.length - 1]?.date}</span> <span>{timeline[timeline.length - 1]?.date}</span>
</div> </div>
{:else if !serverAvailable} {:else if !serverAvailable}
<div class="py-8 text-center"> <div class="py-8 text-center">
<p class="text-sm text-white/40"> <p class="text-sm text-muted-foreground">
Detaillierte Analytics sind verfuegbar, wenn der uLoad-Server verbunden ist. Detaillierte Analytics sind verfuegbar, wenn der uLoad-Server verbunden ist.
</p> </p>
<p class="mt-1 text-xs text-white/25"> <p class="mt-1 text-xs text-muted-foreground/70">
Lokaler Click-Count: {link.clickCount} Lokaler Click-Count: {link.clickCount}
</p> </p>
</div> </div>
{:else} {:else}
<p class="py-8 text-center text-sm text-white/40">Noch keine Daten fuer diesen Zeitraum</p> <p class="py-8 text-center text-sm text-muted-foreground">
Noch keine Daten fuer diesen Zeitraum
</p>
{/if} {/if}
</div> </div>
@ -256,19 +258,19 @@
{#if serverAvailable} {#if serverAvailable}
<div class="grid gap-6 md:grid-cols-3"> <div class="grid gap-6 md:grid-cols-3">
<!-- Devices --> <!-- Devices -->
<div class="rounded-xl border border-white/10 bg-white/5 p-6"> <div class="rounded-xl border border-border/10 bg-muted/5 p-6">
<h2 class="mb-4 text-lg font-semibold text-white">Geraete</h2> <h2 class="mb-4 text-lg font-semibold text-white">Geraete</h2>
{#if devices.length > 0} {#if devices.length > 0}
<div class="space-y-3"> <div class="space-y-3">
{#each devices as d} {#each devices as d}
<div> <div>
<div class="mb-1 flex items-center justify-between text-sm"> <div class="mb-1 flex items-center justify-between text-sm">
<span class="text-white/70">{d.deviceType || 'Unbekannt'}</span> <span class="text-foreground">{d.deviceType || 'Unbekannt'}</span>
<span class="font-medium text-white"> <span class="font-medium text-white">
{Math.round((d.count / totalDevices) * 100)}% {Math.round((d.count / totalDevices) * 100)}%
</span> </span>
</div> </div>
<div class="h-2 rounded-full bg-white/10"> <div class="h-2 rounded-full bg-muted/10">
<div <div
class="h-2 rounded-full bg-indigo-500" class="h-2 rounded-full bg-indigo-500"
style="width: {(d.count / totalDevices) * 100}%" style="width: {(d.count / totalDevices) * 100}%"
@ -278,18 +280,18 @@
{/each} {/each}
</div> </div>
{:else} {:else}
<p class="text-sm text-white/40">Keine Daten</p> <p class="text-sm text-muted-foreground">Keine Daten</p>
{/if} {/if}
</div> </div>
<!-- Referrers --> <!-- Referrers -->
<div class="rounded-xl border border-white/10 bg-white/5 p-6"> <div class="rounded-xl border border-border/10 bg-muted/5 p-6">
<h2 class="mb-4 text-lg font-semibold text-white">Referrer</h2> <h2 class="mb-4 text-lg font-semibold text-white">Referrer</h2>
{#if referrers.length > 0} {#if referrers.length > 0}
<div class="space-y-2"> <div class="space-y-2">
{#each referrers.slice(0, 8) as r} {#each referrers.slice(0, 8) as r}
<div class="flex items-center justify-between text-sm"> <div class="flex items-center justify-between text-sm">
<span class="max-w-[140px] truncate text-white/70"> <span class="max-w-[140px] truncate text-foreground">
{r.referer || 'Direkt'} {r.referer || 'Direkt'}
</span> </span>
<span class="font-medium tabular-nums text-white">{r.count}</span> <span class="font-medium tabular-nums text-white">{r.count}</span>
@ -297,24 +299,24 @@
{/each} {/each}
</div> </div>
{:else} {:else}
<p class="text-sm text-white/40">Keine Daten</p> <p class="text-sm text-muted-foreground">Keine Daten</p>
{/if} {/if}
</div> </div>
<!-- Countries --> <!-- Countries -->
<div class="rounded-xl border border-white/10 bg-white/5 p-6"> <div class="rounded-xl border border-border/10 bg-muted/5 p-6">
<h2 class="mb-4 text-lg font-semibold text-white">Laender</h2> <h2 class="mb-4 text-lg font-semibold text-white">Laender</h2>
{#if countries.length > 0} {#if countries.length > 0}
<div class="space-y-3"> <div class="space-y-3">
{#each countries.slice(0, 8) as c} {#each countries.slice(0, 8) as c}
<div> <div>
<div class="mb-1 flex items-center justify-between text-sm"> <div class="mb-1 flex items-center justify-between text-sm">
<span class="text-white/70">{c.country || 'Unbekannt'}</span> <span class="text-foreground">{c.country || 'Unbekannt'}</span>
<span class="font-medium text-white"> <span class="font-medium text-white">
{Math.round((c.count / totalCountries) * 100)}% {Math.round((c.count / totalCountries) * 100)}%
</span> </span>
</div> </div>
<div class="h-2 rounded-full bg-white/10"> <div class="h-2 rounded-full bg-muted/10">
<div <div
class="h-2 rounded-full bg-emerald-500" class="h-2 rounded-full bg-emerald-500"
style="width: {(c.count / totalCountries) * 100}%" style="width: {(c.count / totalCountries) * 100}%"
@ -324,7 +326,7 @@
{/each} {/each}
</div> </div>
{:else} {:else}
<p class="text-sm text-white/40">Keine Daten</p> <p class="text-sm text-muted-foreground">Keine Daten</p>
{/if} {/if}
</div> </div>
</div> </div>

View file

@ -122,7 +122,7 @@
} }
const inputSmClass = const inputSmClass =
'w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm focus:border-indigo-500 focus:outline-none dark:border-gray-600 dark:bg-gray-700'; 'w-full rounded-lg border border-border-strong bg-white px-3 py-2 text-sm focus:border-indigo-500 focus:outline-none dark:border-border dark:bg-muted';
</script> </script>
<svelte:head> <svelte:head>
@ -134,11 +134,7 @@
<!-- Header --> <!-- Header -->
<div class="mb-6 flex items-center justify-between"> <div class="mb-6 flex items-center justify-between">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<a <a href="/uload" class="rounded-lg p-2 hover:bg-muted dark:hover:bg-muted" title="Zurueck">
href="/uload"
class="rounded-lg p-2 hover:bg-gray-100 dark:hover:bg-gray-700"
title="Zurueck"
>
<ArrowLeft size={20} /> <ArrowLeft size={20} />
</a> </a>
<div> <div>
@ -159,9 +155,9 @@
selectedIds = selectedIds; selectedIds = selectedIds;
} }
}} }}
class="rounded-lg border border-gray-300 px-3 py-2 text-sm font-medium transition-colors {selectMode class="rounded-lg border border-border-strong px-3 py-2 text-sm font-medium transition-colors {selectMode
? 'bg-indigo-600 text-white' ? 'bg-indigo-600 text-white'
: 'hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-700'}" : 'hover:bg-muted dark:border-border dark:hover:bg-muted'}"
> >
{selectMode ? 'Fertig' : 'Auswaehlen'} {selectMode ? 'Fertig' : 'Auswaehlen'}
</button> </button>
@ -176,7 +172,7 @@
type="text" type="text"
bind:value={searchQuery} bind:value={searchQuery}
placeholder="Links durchsuchen..." placeholder="Links durchsuchen..."
class="w-60 rounded-lg border border-gray-300 bg-white py-2 pl-8 pr-3 text-sm focus:border-indigo-500 focus:outline-none dark:border-gray-600 dark:bg-gray-700" class="w-60 rounded-lg border border-border-strong bg-white py-2 pl-8 pr-3 text-sm focus:border-indigo-500 focus:outline-none dark:border-border dark:bg-muted"
/> />
</div> </div>
<select bind:value={selectedStatus} class={inputSmClass} style="max-width: 140px"> <select bind:value={selectedStatus} class={inputSmClass} style="max-width: 140px">
@ -226,12 +222,12 @@
{#if allLinks.loading} {#if allLinks.loading}
<div class="space-y-3"> <div class="space-y-3">
{#each Array(5) as _} {#each Array(5) as _}
<div class="h-20 animate-pulse rounded-xl bg-gray-100 dark:bg-gray-800"></div> <div class="h-20 animate-pulse rounded-xl bg-muted dark:bg-card"></div>
{/each} {/each}
</div> </div>
{:else if filteredLinks.length === 0} {:else if filteredLinks.length === 0}
<div <div
class="rounded-xl border-2 border-dashed border-gray-300 p-12 text-center dark:border-gray-600" class="rounded-xl border-2 border-dashed border-border-strong p-12 text-center dark:border-border"
> >
<LinkIcon size={48} class="mx-auto mb-4 opacity-20" /> <LinkIcon size={48} class="mx-auto mb-4 opacity-20" />
<p class="text-lg font-medium opacity-60">Keine Links gefunden</p> <p class="text-lg font-medium opacity-60">Keine Links gefunden</p>
@ -245,7 +241,7 @@
<div class="space-y-3"> <div class="space-y-3">
{#each filteredLinks as link (link.id)} {#each filteredLinks as link (link.id)}
<div <div
class="group rounded-xl border border-gray-200 bg-white p-4 shadow-sm transition-all hover:shadow-md dark:border-gray-700 dark:bg-gray-800" class="group rounded-xl border border-border-strong bg-white p-4 shadow-sm transition-all hover:shadow-md dark:border-border dark:bg-card"
> >
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
{#if selectMode} {#if selectMode}
@ -261,7 +257,7 @@
<span <span
class="inline-block h-2 w-2 shrink-0 rounded-full {link.isActive class="inline-block h-2 w-2 shrink-0 rounded-full {link.isActive
? 'bg-green-500' ? 'bg-green-500'
: 'bg-gray-400'}" : 'bg-muted'}"
></span> ></span>
<h3 class="truncate font-semibold">{link.title || link.shortCode}</h3> <h3 class="truncate font-semibold">{link.title || link.shortCode}</h3>
<span <span
@ -295,7 +291,7 @@
<div class="ml-4 flex items-center gap-1"> <div class="ml-4 flex items-center gap-1">
<a <a
href="/uload/analytics/{link.id}" href="/uload/analytics/{link.id}"
class="flex items-center gap-1 rounded-lg px-2 py-1.5 text-sm font-medium opacity-60 transition-all hover:bg-gray-100 hover:opacity-100 dark:hover:bg-gray-700" class="flex items-center gap-1 rounded-lg px-2 py-1.5 text-sm font-medium opacity-60 transition-all hover:bg-muted hover:opacity-100 dark:hover:bg-muted"
title="Analytics" title="Analytics"
> >
<ChartBar size={16} /> <ChartBar size={16} />
@ -303,17 +299,20 @@
</a> </a>
<button <button
onclick={() => copyShortUrl(link.shortCode)} onclick={() => copyShortUrl(link.shortCode)}
class="rounded-lg p-2 opacity-0 transition-all hover:bg-gray-100 group-hover:opacity-100 dark:hover:bg-gray-700" class="rounded-lg p-2 opacity-0 transition-all hover:bg-muted group-hover:opacity-100 dark:hover:bg-muted"
title="Link kopieren" title="Link kopieren"
> >
<Copy size={16} /> <Copy size={16} />
</button> </button>
<button <button
onclick={() => toggleActive(link)} onclick={() => toggleActive(link)}
class="rounded-lg p-2 opacity-0 transition-all hover:bg-gray-100 group-hover:opacity-100 dark:hover:bg-gray-700" class="rounded-lg p-2 opacity-0 transition-all hover:bg-muted group-hover:opacity-100 dark:hover:bg-muted"
title={link.isActive ? 'Deaktivieren' : 'Aktivieren'} title={link.isActive ? 'Deaktivieren' : 'Aktivieren'}
> >
<Lightning size={16} class={link.isActive ? 'text-green-500' : 'text-gray-400'} /> <Lightning
size={16}
class={link.isActive ? 'text-green-500' : 'text-muted-foreground'}
/>
</button> </button>
<button <button
onclick={() => deleteLink(link)} onclick={() => deleteLink(link)}

View file

@ -54,8 +54,8 @@
<div class="mx-auto max-w-4xl p-4"> <div class="mx-auto max-w-4xl p-4">
<div class="mb-6 flex items-center justify-between"> <div class="mb-6 flex items-center justify-between">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<a href="/uload" class="rounded-lg p-2 transition-colors hover:bg-white/5"> <a href="/uload" class="rounded-lg p-2 transition-colors hover:bg-muted/5">
<ArrowLeft size={20} class="text-white/60" /> <ArrowLeft size={20} class="text-muted-foreground" />
</a> </a>
<h1 class="text-2xl font-bold text-white">Tags</h1> <h1 class="text-2xl font-bold text-white">Tags</h1>
</div> </div>
@ -68,26 +68,30 @@
</div> </div>
{#if showCreateForm} {#if showCreateForm}
<div class="mb-6 rounded-xl border border-white/10 bg-white/5 p-5"> <div class="mb-6 rounded-xl border border-border/10 bg-muted/5 p-5">
<div class="flex items-end gap-4"> <div class="flex items-end gap-4">
<div class="flex-1"> <div class="flex-1">
<label for="tag-name" class="mb-1 block text-sm font-medium text-white/60">Name</label> <label for="tag-name" class="mb-1 block text-sm font-medium text-muted-foreground"
>Name</label
>
<input <input
id="tag-name" id="tag-name"
type="text" type="text"
bind:value={newName} bind:value={newName}
placeholder="z.B. Social Media" placeholder="z.B. Social Media"
class="w-full rounded-lg border border-white/10 bg-white/5 px-4 py-2 text-white placeholder-white/30 focus:border-indigo-500 focus:outline-none" class="w-full rounded-lg border border-border/10 bg-muted/5 px-4 py-2 text-white placeholder-white/30 focus:border-indigo-500 focus:outline-none"
onkeydown={(e) => e.key === 'Enter' && createTag()} onkeydown={(e) => e.key === 'Enter' && createTag()}
/> />
</div> </div>
<div> <div>
<label for="tag-color" class="mb-1 block text-sm font-medium text-white/60">Farbe</label> <label for="tag-color" class="mb-1 block text-sm font-medium text-muted-foreground"
>Farbe</label
>
<input <input
id="tag-color" id="tag-color"
type="color" type="color"
bind:value={newColor} bind:value={newColor}
class="h-10 w-16 cursor-pointer rounded-lg border border-white/10" class="h-10 w-16 cursor-pointer rounded-lg border border-border/10"
/> />
</div> </div>
<button <button
@ -102,22 +106,24 @@
{/if} {/if}
{#if !tags.value || tags.value.length === 0} {#if !tags.value || tags.value.length === 0}
<div class="rounded-xl border-2 border-dashed border-white/10 p-12 text-center"> <div class="rounded-xl border-2 border-dashed border-border/10 p-12 text-center">
<p class="text-lg font-medium text-white/60">Noch keine Tags</p> <p class="text-lg font-medium text-muted-foreground">Noch keine Tags</p>
<p class="mt-1 text-sm text-white/40">Erstelle Tags um deine Links zu organisieren.</p> <p class="mt-1 text-sm text-muted-foreground">
Erstelle Tags um deine Links zu organisieren.
</p>
</div> </div>
{:else} {:else}
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3"> <div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{#each tags.value as tag (tag.id)} {#each tags.value as tag (tag.id)}
<div <div
class="group rounded-xl border border-white/10 bg-white/5 p-4 transition-all hover:bg-white/8" class="group rounded-xl border border-border/10 bg-muted/5 p-4 transition-all hover:bg-muted/8"
> >
{#if editingTag?.id === tag.id} {#if editingTag?.id === tag.id}
<div class="space-y-3"> <div class="space-y-3">
<input <input
type="text" type="text"
bind:value={editingTag.name} bind:value={editingTag.name}
class="w-full rounded border border-white/10 bg-white/5 px-3 py-1.5 text-sm text-white" class="w-full rounded border border-border/10 bg-muted/5 px-3 py-1.5 text-sm text-white"
/> />
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<input type="color" bind:value={editingTag.color} class="h-8 w-12 rounded" /> <input type="color" bind:value={editingTag.color} class="h-8 w-12 rounded" />
@ -128,7 +134,7 @@
> >
<button <button
onclick={() => (editingTag = null)} onclick={() => (editingTag = null)}
class="rounded border border-white/10 px-3 py-1 text-sm text-white/60" class="rounded border border-border/10 px-3 py-1 text-sm text-muted-foreground"
>{$_('common.cancel')}</button >{$_('common.cancel')}</button
> >
</div> </div>
@ -143,16 +149,16 @@
<span class="font-medium text-white">{tag.name}</span> <span class="font-medium text-white">{tag.name}</span>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-sm text-white/40">{getUsageCount(tag.id)} Links</span> <span class="text-sm text-muted-foreground">{getUsageCount(tag.id)} Links</span>
<button <button
onclick={() => (editingTag = { id: tag.id, name: tag.name, color: tag.color })} onclick={() => (editingTag = { id: tag.id, name: tag.name, color: tag.color })}
class="rounded p-1 text-white/40 opacity-0 transition-all hover:bg-white/10 hover:text-white group-hover:opacity-100" class="rounded p-1 text-muted-foreground opacity-0 transition-all hover:bg-muted/10 hover:text-white group-hover:opacity-100"
> >
<PencilSimple size={16} /> <PencilSimple size={16} />
</button> </button>
<button <button
onclick={() => deleteTag(tag)} onclick={() => deleteTag(tag)}
class="rounded p-1 text-white/40 opacity-0 transition-all hover:bg-red-900/20 hover:text-red-400 group-hover:opacity-100" class="rounded p-1 text-muted-foreground opacity-0 transition-all hover:bg-red-900/20 hover:text-red-400 group-hover:opacity-100"
> >
<Trash size={16} /> <Trash size={16} />
</button> </button>

View file

@ -0,0 +1,184 @@
#!/usr/bin/env node
/**
* One-shot migration: replace raw Tailwind neutral-palette + white-alpha
* utilities with theme tokens across the unified Mana web app.
*
* This is a surgical codemod, not a general-purpose tool. The mappings
* encode a specific design decision: `bg-gray-800` = `bg-card`, etc.
* Re-running is a no-op once the codebase is clean.
*
* Usage:
* node scripts/migrate-theme-tokens.mjs [--dry-run]
*
* The mappings are ordered by specificity longer patterns first so
* `bg-gray-700/50` is tried before `bg-gray-700`.
*/
import { execSync } from 'node:child_process';
import { readFileSync, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = join(__dirname, '..');
const DRY_RUN = process.argv.includes('--dry-run');
const SCAN_GLOBS = [
'apps/mana/apps/web/src/lib/modules/**/*.svelte',
'apps/mana/apps/web/src/routes/(app)/**/*.svelte',
];
/**
* Files where `bg-white/N`, `text-white/N`, etc. are brand-literal overlays
* on vivid gradient backgrounds, not theme-intent. These stay untouched
* and are allowlisted in the validator via scoped <style> migration.
*/
const EXCLUDE_PATHS = new Set([
'apps/mana/apps/web/src/lib/modules/moodlit/components/mood/MoodFullscreen.svelte',
'apps/mana/apps/web/src/lib/modules/moodlit/components/mood/MoodCard.svelte',
'apps/mana/apps/web/src/lib/modules/moodlit/components/mood/CreateMoodDialog.svelte',
]);
/**
* Each entry: [regex, replacement]. Regex MUST use `\b` or class-boundary
* anchors so we don't accidentally rewrite inside longer identifiers.
* The boundary pattern `(?<=^|[\s:"'\`])` ensures the match starts at a
* class boundary (space, colon for variant prefixes, quote char).
*/
const B = String.raw`(?<=^|[\s:"'\`])`; // class-boundary lookbehind
const MAPPINGS = [
// ─── Backgrounds: surfaces ────────────────────────────────────
// Dark modals/cards use gray-800/900 as surface.
[new RegExp(`${B}bg-gray-900\\b`, 'g'), 'bg-card'],
[new RegExp(`${B}bg-gray-800\\b`, 'g'), 'bg-card'],
[new RegExp(`${B}bg-neutral-900\\b`, 'g'), 'bg-card'],
[new RegExp(`${B}bg-neutral-800\\b`, 'g'), 'bg-card'],
[new RegExp(`${B}bg-slate-900\\b`, 'g'), 'bg-card'],
[new RegExp(`${B}bg-slate-800\\b`, 'g'), 'bg-card'],
[new RegExp(`${B}bg-zinc-900\\b`, 'g'), 'bg-card'],
[new RegExp(`${B}bg-zinc-800\\b`, 'g'), 'bg-card'],
[new RegExp(`${B}bg-stone-900\\b`, 'g'), 'bg-card'],
[new RegExp(`${B}bg-stone-800\\b`, 'g'), 'bg-card'],
// Mid-grays are muted surfaces. Preserve opacity suffix.
[new RegExp(`${B}bg-gray-(?:700|600)\\/(\\d+)\\b`, 'g'), 'bg-muted/$1'],
[new RegExp(`${B}bg-gray-(?:700|600)\\b`, 'g'), 'bg-muted'],
[new RegExp(`${B}bg-neutral-(?:700|600)\\/(\\d+)\\b`, 'g'), 'bg-muted/$1'],
[new RegExp(`${B}bg-neutral-(?:700|600)\\b`, 'g'), 'bg-muted'],
[new RegExp(`${B}bg-slate-(?:700|600)\\/(\\d+)\\b`, 'g'), 'bg-muted/$1'],
[new RegExp(`${B}bg-slate-(?:700|600)\\b`, 'g'), 'bg-muted'],
[new RegExp(`${B}bg-zinc-(?:700|600)\\/(\\d+)\\b`, 'g'), 'bg-muted/$1'],
[new RegExp(`${B}bg-zinc-(?:700|600)\\b`, 'g'), 'bg-muted'],
[new RegExp(`${B}bg-stone-(?:700|600)\\/(\\d+)\\b`, 'g'), 'bg-muted/$1'],
[new RegExp(`${B}bg-stone-(?:700|600)\\b`, 'g'), 'bg-muted'],
// Light grays 100-500 — same bucket (rarely used in dark-first design).
[
new RegExp(`${B}bg-(?:gray|slate|zinc|neutral|stone)-(?:500|400|300|200|100|50)\\b`, 'g'),
'bg-muted',
],
// ─── Borders ─────────────────────────────────────────────────
[
new RegExp(`${B}border-(?:gray|slate|zinc|neutral|stone)-(?:700|800|900)\\/(\\d+)\\b`, 'g'),
'border-border/$1',
],
[
new RegExp(`${B}border-(?:gray|slate|zinc|neutral|stone)-(?:700|800|900)\\b`, 'g'),
'border-border',
],
[new RegExp(`${B}border-(?:gray|slate|zinc|neutral|stone)-(?:600)\\b`, 'g'), 'border-border'],
[
new RegExp(`${B}border-(?:gray|slate|zinc|neutral|stone)-(?:500|400|300|200|100)\\b`, 'g'),
'border-border-strong',
],
// ─── Text ─────────────────────────────────────────────────────
// Dark text on light bg (gray-900/800) = foreground.
[new RegExp(`${B}text-(?:gray|slate|zinc|neutral|stone)-(?:900|800)\\b`, 'g'), 'text-foreground'],
// text-gray-200/100 on dark bg = foreground
[new RegExp(`${B}text-(?:gray|slate|zinc|neutral|stone)-(?:200|100)\\b`, 'g'), 'text-foreground'],
// text-gray-300 = foreground/90 (slightly muted primary text)
[new RegExp(`${B}text-(?:gray|slate|zinc|neutral|stone)-300\\b`, 'g'), 'text-foreground/90'],
// text-gray-400/500/600/700 = muted-foreground (labels, captions)
[
new RegExp(`${B}text-(?:gray|slate|zinc|neutral|stone)-(?:400|500|700)\\b`, 'g'),
'text-muted-foreground',
],
[
new RegExp(`${B}text-(?:gray|slate|zinc|neutral|stone)-600\\b`, 'g'),
'text-muted-foreground/70',
],
// ─── Placeholders ────────────────────────────────────────────
[
new RegExp(`${B}placeholder-(?:gray|slate|zinc|neutral|stone)-(?:400|500|600)\\b`, 'g'),
'placeholder:text-muted-foreground/60',
],
[
new RegExp(`${B}placeholder:text-(?:gray|slate|zinc|neutral|stone)-(?:400|500|600)\\b`, 'g'),
'placeholder:text-muted-foreground/60',
],
// ─── Hover/Focus variants ────────────────────────────────────
// Handled uniformly because the boundary lookbehind already matches
// after `hover:`, `focus:`, `active:`, `group-hover:`, etc.
// ─── White-alpha utilities ───────────────────────────────────
// Preserve opacity modifier as given.
[new RegExp(`${B}bg-white\\/(\\d+)\\b`, 'g'), 'bg-muted/$1'],
[new RegExp(`${B}border-white\\/(\\d+)\\b`, 'g'), 'border-border/$1'],
// Text: /70+ = foreground, /30-60 = muted-foreground
[new RegExp(`${B}text-white\\/(?:90|80|70)\\b`, 'g'), 'text-foreground'],
[new RegExp(`${B}text-white\\/(?:60|50|40)\\b`, 'g'), 'text-muted-foreground'],
[new RegExp(`${B}text-white\\/(?:30|20|10)\\b`, 'g'), 'text-muted-foreground/70'],
];
function listFiles() {
const args = SCAN_GLOBS.map((g) => `"${g}"`).join(' ');
const out = execSync(`git ls-files ${args}`, {
cwd: REPO_ROOT,
encoding: 'utf8',
});
return out
.split('\n')
.map((p) => p.trim())
.filter(Boolean);
}
function migrate() {
const paths = listFiles();
let changedFiles = 0;
let totalSubs = 0;
for (const rel of paths) {
if (EXCLUDE_PATHS.has(rel)) continue;
const abs = join(REPO_ROOT, rel);
let src = readFileSync(abs, 'utf8');
let fileSubs = 0;
for (const [pattern, replacement] of MAPPINGS) {
const before = src;
src = src.replace(pattern, replacement);
if (src !== before) {
// Count replacements this rule made.
const matches = before.match(pattern);
fileSubs += matches ? matches.length : 0;
}
}
if (fileSubs > 0) {
changedFiles++;
totalSubs += fileSubs;
if (!DRY_RUN) writeFileSync(abs, src, 'utf8');
console.log(` ${fileSubs.toString().padStart(4)} subs ${rel}`);
}
}
const verb = DRY_RUN ? 'Would migrate' : 'Migrated';
console.log(`\n${verb} ${totalSubs} token(s) across ${changedFiles} file(s).`);
if (DRY_RUN) console.log('Run without --dry-run to apply.');
}
migrate();

View file

@ -1,30 +1,42 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* Validate that module ListView.svelte files use theme tokens instead of * Validate that the unified Mana web app uses theme tokens instead of raw
* raw white-alpha Tailwind utilities. * Tailwind utilities that bypass the theme system.
* *
* Background: the unified Mana app supports multiple theme variants (Lume, * Background: the unified Mana app supports multiple theme variants (Lume,
* Nature, Stone, Ocean, plus dark). Utilities like `text-white/80`, * Nature, Stone, Ocean, plus dark). Utilities like `text-white/80`,
* `bg-white/5`, `border-white/10` ignore the active theme and can render * `bg-gray-800`, `border-neutral-700` ignore the active theme and can
* white-on-white in light variants. The theme tokens (`text-foreground`, * render white-on-white (or dark-on-dark) under the wrong variant. The
* `bg-muted`, `border-border`, etc.) are the canonical replacements * theme tokens (`text-foreground`, `bg-muted`, `border-border`, etc.) are
* they're generated by Tailwind v4 from `packages/shared-tailwind/src/themes.css` * generated by Tailwind v4 from `packages/shared-tailwind/src/themes.css`
* and resolve per-theme automatically. * and resolve per-theme automatically.
* *
* Rule: `src/lib/modules/**\/ListView.svelte` must not contain * Rules (enforced across `apps/mana/apps/web/src/lib/modules/**` and
* - `bg-white/` (e.g. bg-white/5, bg-white/10, hover:bg-white/5) * `apps/mana/apps/web/src/routes/(app)/**`):
* - `text-white/` (e.g. text-white/80, hover:text-white/90)
* - `border-white/` (e.g. border-white/10, focus:border-white/20)
* *
* Suggested replacements: * 1. No white-alpha utilities:
* bg-white/N bg-muted/N or bg-card * `bg-white/N`, `text-white/N`, `border-white/N` (and hover:/focus:/etc. variants)
* text-white text-foreground
* text-white/40-60 text-muted-foreground
* border-white/N border-border
* *
* `text-white` without an opacity modifier is allowed when it sits on a * 2. No neutral-palette utilities:
* guaranteed-dark surface (mood gradient overlays, photo viewer) those * `(bg|text|border)-(gray|slate|zinc|neutral|stone)-N` (with or without /N)
* are brand literals per the themes.css policy. * these should be theme tokens instead.
*
* Replacement cheat-sheet:
* bg-white/N bg-muted/N or bg-card
* text-white text-foreground
* text-white/40..60 text-muted-foreground
* border-white/N border-border
* bg-gray-800 bg-card or bg-muted
* bg-neutral-900 bg-card
* text-gray-400 text-muted-foreground
* text-slate-300 text-foreground/90
* border-gray-700 border-border
*
* Brand-literal escape hatch: if a color must stay literal (overlay on a
* vivid gradient, photo viewer backdrop, domain-semantic palette), move
* it into a scoped <style> block the validator only scans class
* attributes. See `lib/modules/moodlit/ListView.svelte:.mood-card:hover`
* for the pattern.
* *
* Zero deps runs as plain Node ESM. Uses `git ls-files` to respect * Zero deps runs as plain Node ESM. Uses `git ls-files` to respect
* .gitignore. * .gitignore.
@ -38,14 +50,41 @@ import { dirname, join } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = join(__dirname, '..'); const REPO_ROOT = join(__dirname, '..');
const LIST_VIEW_GLOB = 'apps/mana/apps/web/src/lib/modules/*/ListView.svelte'; const SCAN_GLOBS = [
'apps/mana/apps/web/src/lib/modules/**/*.svelte',
'apps/mana/apps/web/src/routes/(app)/**/*.svelte',
];
// `\b` before ensures we catch `hover:bg-white/`, `focus:border-white/`, /**
// etc., without matching unrelated class names like `off-white`. * Files where white-alpha utilities are intentional brand literals
const FORBIDDEN = /(?:^|[\s:"'`])(bg|text|border)-white\//g; * they render overlays on vivid mood-gradient backgrounds, not theme
* surfaces. Exempt from the `white/` rule but still must follow the
* neutral-palette rule.
*/
const BRAND_OVERLAY_FILES = new Set([
'apps/mana/apps/web/src/lib/modules/moodlit/components/mood/MoodFullscreen.svelte',
'apps/mana/apps/web/src/lib/modules/moodlit/components/mood/MoodCard.svelte',
'apps/mana/apps/web/src/lib/modules/moodlit/components/mood/CreateMoodDialog.svelte',
]);
function listListViews() { // `(?:^|[\s:"'`])` anchors so `hover:bg-white/5` and class=`bg-white/10`
const out = execSync(`git ls-files "${LIST_VIEW_GLOB}"`, { // both match, without tripping on unrelated substrings like `off-white`.
const RULES = [
{
name: 'white-alpha',
pattern: /(?:^|[\s:"'`])(bg|text|border)-white\/\d/g,
describe: (m) => `${m[1]}-white/`,
},
{
name: 'neutral-palette',
pattern: /(?:^|[\s:"'`])(bg|text|border)-(gray|slate|zinc|neutral|stone)-\d/g,
describe: (m) => `${m[1]}-${m[2]}-`,
},
];
function listFiles() {
const args = SCAN_GLOBS.map((g) => `"${g}"`).join(' ');
const out = execSync(`git ls-files ${args}`, {
cwd: REPO_ROOT, cwd: REPO_ROOT,
encoding: 'utf8', encoding: 'utf8',
}); });
@ -56,7 +95,7 @@ function listListViews() {
} }
function validate() { function validate() {
const paths = listListViews(); const paths = listFiles();
const violations = []; const violations = [];
for (const rel of paths) { for (const rel of paths) {
@ -64,40 +103,60 @@ function validate() {
const src = readFileSync(abs, 'utf8'); const src = readFileSync(abs, 'utf8');
const lines = src.split('\n'); const lines = src.split('\n');
const brandOverlay = BRAND_OVERLAY_FILES.has(rel);
lines.forEach((line, i) => { lines.forEach((line, i) => {
FORBIDDEN.lastIndex = 0; for (const rule of RULES) {
let match; if (brandOverlay && rule.name === 'white-alpha') continue;
while ((match = FORBIDDEN.exec(line)) !== null) { rule.pattern.lastIndex = 0;
violations.push({ let match;
file: rel, while ((match = rule.pattern.exec(line)) !== null) {
line: i + 1, violations.push({
token: `${match[1]}-white/`, file: rel,
snippet: line.trim().slice(0, 120), line: i + 1,
}); rule: rule.name,
token: rule.describe(match),
snippet: line.trim().slice(0, 120),
});
}
} }
}); });
} }
if (violations.length > 0) { if (violations.length > 0) {
console.error(`\n✗ Theme-token check FAILED (${violations.length} violation(s)):\n`); // Group by file for readability.
const byFile = new Map();
for (const v of violations) { for (const v of violations) {
console.error(`${v.file}:${v.line} [${v.token}]`); if (!byFile.has(v.file)) byFile.set(v.file, []);
console.error(` ${v.snippet}`); byFile.get(v.file).push(v);
} }
console.error( console.error(
`\nReplace raw white-alpha utilities with theme tokens:\n` + `\n✗ Theme-token check FAILED (${violations.length} violation(s) in ${byFile.size} file(s)):\n`
` bg-white/N → bg-muted/N or bg-card\n` + );
` text-white → text-foreground\n` + for (const [file, vs] of byFile) {
` text-white/40-60 → text-muted-foreground\n` + console.error(` ${file} (${vs.length})`);
` border-white/N → border-border\n\n` + for (const v of vs.slice(0, 5)) {
`Rationale: raw white utilities ignore theme variants (Lume, Nature, ...)\n` + console.error(` :${v.line} [${v.rule}: ${v.token}] ${v.snippet.slice(0, 80)}`);
`and can render white-on-white under light themes. See\n` + }
`packages/shared-tailwind/src/themes.css for the full token set.\n` if (vs.length > 5) console.error(` … +${vs.length - 5} more`);
}
console.error(
`\nReplace with theme tokens:\n` +
` bg-white/N → bg-muted/N or bg-card\n` +
` text-white → text-foreground\n` +
` text-white/40..60 → text-muted-foreground\n` +
` border-white/N → border-border\n` +
` bg-gray/slate/neutral-N → bg-muted / bg-card\n` +
` text-gray/slate-N → text-foreground / text-muted-foreground\n` +
` border-gray/slate-N → border-border\n\n` +
`Rationale: raw color utilities ignore theme variants (Lume, Nature, ...)\n` +
`and can render invisible under other themes. See\n` +
`packages/shared-tailwind/src/themes.css for the full token set.\n` +
`Brand-literal overlays may move into scoped <style> blocks.\n`
); );
process.exit(1); process.exit(1);
} }
console.log(`✓ Theme-token check: ${paths.length} ListView(s) use theme tokens correctly.`); console.log(`✓ Theme-token check: ${paths.length} files use theme tokens correctly.`);
} }
validate(); validate();