style(mana/web): migrate photos/times/contacts module helpers to theme tokens (P5)

photos/PhotoCard + PhotoDetailModal: bare var() refs → hsl(var()), broken
fallbacks dropped. The lightbox backdrop stays explicit near-black — photo
viewing chrome is intentionally theme-neutral.

times/FocusCard: phase color (focus=red, break=green, idle=muted) reads
theme tokens via wrapped hsl() strings so the SVG ring tracks variants.
The bogus --color-input fallback is gone.

times/EntryItem: was referencing the long-removed shadcn aliases without
the --color- prefix (--border, --card, --foreground, --muted-foreground,
--primary, --input). Re-prefixed; --input → --background since we have
no separate input token. The delete button's text-red-500 / hover bg are
now --color-error so they track theme variants.

contacts/ContactPage: avatar + self-badge color-mix fallback chains
collapse to plain hsl(var(--color-primary) / 0.12).

The cycles pink #ec4899 birthday accent on the contact row stays literal
— it's a deliberate brand color, not theme intent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-09 12:37:29 +02:00
parent d2e44c8b65
commit a7fbd29a67
5 changed files with 58 additions and 53 deletions

View file

@ -422,8 +422,8 @@
width: 2rem;
height: 2rem;
border-radius: 9999px;
background: color-mix(in srgb, var(--color-primary, #8b5cf6) 12%, transparent);
color: var(--color-primary, #8b5cf6);
background: hsl(var(--color-primary) / 0.12);
color: hsl(var(--color-primary));
display: flex;
align-items: center;
justify-content: center;
@ -499,8 +499,8 @@
font-weight: 600;
padding: 0.0625rem 0.375rem;
border-radius: 9999px;
background: color-mix(in srgb, var(--color-primary, #8b5cf6) 12%, transparent);
color: var(--color-primary, #8b5cf6);
background: hsl(var(--color-primary) / 0.12);
color: hsl(var(--color-primary));
flex-shrink: 0;
}
@ -521,8 +521,8 @@
width: 4.5rem;
height: 4.5rem;
border-radius: 9999px;
background: color-mix(in srgb, var(--color-primary, #8b5cf6) 12%, transparent);
color: var(--color-primary, #8b5cf6);
background: hsl(var(--color-primary) / 0.12);
color: hsl(var(--color-primary));
display: flex;
align-items: center;
justify-content: center;

View file

@ -64,8 +64,8 @@
position: relative;
aspect-ratio: 1;
overflow: hidden;
border-radius: var(--radius-md, 0.5rem);
background-color: var(--color-muted, #f1f5f9);
border-radius: 0.5rem;
background-color: hsl(var(--color-muted));
cursor: pointer;
border: none;
padding: 0;
@ -76,18 +76,14 @@
.photo-card:hover {
transform: scale(1.02);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
box-shadow: 0 10px 20px hsl(0 0% 0% / 0.2);
z-index: 10;
}
.placeholder {
position: absolute;
inset: 0;
background: linear-gradient(
135deg,
var(--color-muted, #f1f5f9) 0%,
var(--color-accent, #e2e8f0) 100%
);
background: linear-gradient(135deg, hsl(var(--color-muted)) 0%, hsl(var(--color-accent)) 100%);
animation: pulse 2s ease-in-out infinite;
}
@ -119,13 +115,13 @@
display: flex;
align-items: center;
justify-content: center;
color: var(--color-muted-foreground, #64748b);
color: hsl(var(--color-muted-foreground));
}
.photo-overlay {
position: absolute;
inset: 0;
background: linear-gradient(to top, rgba(0, 0, 0, 0.5) 0%, transparent 40%);
background: linear-gradient(to top, hsl(0 0% 0% / 0.5) 0%, transparent 40%);
opacity: 0;
transition: opacity 150ms;
display: flex;
@ -141,8 +137,8 @@
.favorite-btn {
padding: 0.5rem;
border-radius: 50%;
background: rgba(255, 255, 255, 0.9);
color: var(--color-muted-foreground, #64748b);
background: hsl(0 0% 100% / 0.9);
color: hsl(var(--color-muted-foreground));
border: none;
cursor: pointer;
transition: all 150ms;
@ -152,6 +148,6 @@
transform: scale(1.1);
}
.favorite-btn.favorited {
color: #ef4444;
color: hsl(var(--color-error));
}
</style>

View file

@ -157,10 +157,12 @@
</div>
<style>
/* Lightbox backdrop is intentionally near-black regardless of theme — photos
need a neutral viewing chrome. */
.lightbox-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.95);
background: hsl(0 0% 0% / 0.95);
z-index: 50;
display: flex;
align-items: center;
@ -180,7 +182,7 @@
left: 1rem;
padding: 0.5rem;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
background: hsl(0 0% 100% / 0.1);
color: white;
border: none;
cursor: pointer;
@ -189,7 +191,7 @@
}
.close-btn:hover {
background: rgba(255, 255, 255, 0.2);
background: hsl(0 0% 100% / 0.2);
}
.lightbox-main {
@ -208,8 +210,8 @@
.info-panel {
width: 320px;
background: var(--color-card, #ffffff);
color: var(--color-foreground, #0f172a);
background: hsl(var(--color-card));
color: hsl(var(--color-foreground));
overflow-y: auto;
padding: 1.5rem;
}
@ -220,7 +222,7 @@
align-items: center;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--color-border, #e2e8f0);
border-bottom: 1px solid hsl(var(--color-border));
}
.info-actions {
@ -235,10 +237,10 @@
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
border: 1px solid var(--color-border, #e2e8f0);
border: 1px solid hsl(var(--color-border));
border-radius: 0.5rem;
background: var(--color-background, #ffffff);
color: var(--color-foreground, #0f172a);
background: hsl(var(--color-background));
color: hsl(var(--color-foreground));
cursor: pointer;
transition: all 150ms;
text-decoration: none;
@ -246,11 +248,11 @@
}
.action-btn:hover {
background: var(--color-accent, #f1f5f9);
background: hsl(var(--color-accent));
}
.action-btn.favorited {
color: #ef4444;
border-color: #ef4444;
color: hsl(var(--color-error));
border-color: hsl(var(--color-error));
}
.info-section {
@ -259,7 +261,7 @@
.info-label {
font-size: 0.75rem;
color: var(--color-muted-foreground, #64748b);
color: hsl(var(--color-muted-foreground));
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.25rem;
@ -273,7 +275,7 @@
padding: 0.25rem;
border-radius: 50%;
background: transparent;
color: var(--color-foreground, #0f172a);
color: hsl(var(--color-foreground));
border: none;
cursor: pointer;
}

View file

@ -103,8 +103,8 @@
</script>
<div
class="entry-item rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] transition-all {isExpanded
? 'ring-1 ring-[hsl(var(--primary)/0.3)]'
class="entry-item rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] transition-all {isExpanded
? 'ring-1 ring-[hsl(var(--color-primary)/0.3)]'
: ''}"
>
<!-- Compact row (always visible) -->
@ -115,13 +115,13 @@
{#if project}
<div class="project-dot" style="background-color: {project.color}"></div>
{:else}
<div class="project-dot" style="background-color: #9ca3af"></div>
<div class="project-dot" style="background-color: hsl(var(--color-muted-foreground))"></div>
{/if}
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium text-[hsl(var(--foreground))]">
<p class="truncate text-sm font-medium text-[hsl(var(--color-foreground))]">
{entry.description || $_('timer.noDescription')}
</p>
<p class="text-xs text-[hsl(var(--muted-foreground))]">
<p class="text-xs text-[hsl(var(--color-muted-foreground))]">
{project?.name || $_('project.internal')}
{#if client}· {client.name}{/if}
{#if startTimeStr && endTimeStr}
@ -130,25 +130,25 @@
</p>
</div>
<div class="text-right shrink-0">
<p class="duration-display text-sm font-medium text-[hsl(var(--foreground))]">
<p class="duration-display text-sm font-medium text-[hsl(var(--color-foreground))]">
{formatDurationCompact(entry.duration)}
</p>
{#if entry.isBillable}
<span class="text-xs text-[hsl(var(--primary))]">$</span>
<span class="text-xs text-[hsl(var(--color-primary))]">$</span>
{/if}
</div>
</button>
<!-- Expanded edit form -->
{#if isExpanded}
<div class="border-t border-[hsl(var(--border))] px-4 py-3 space-y-3">
<div class="border-t border-[hsl(var(--color-border))] px-4 py-3 space-y-3">
<!-- Description -->
<input
type="text"
value={editDescription}
oninput={(e) => handleDescriptionChange((e.target as HTMLInputElement).value)}
placeholder={$_('entry.description')}
class="w-full rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--input))] px-3 py-2 text-sm text-[hsl(var(--foreground))] focus:border-[hsl(var(--primary))] focus:outline-none"
class="w-full rounded-lg border border-[hsl(var(--color-border))] bg-[hsl(var(--color-background))] px-3 py-2 text-sm text-[hsl(var(--color-foreground))] focus:border-[hsl(var(--color-primary))] focus:outline-none"
/>
<!-- Project + Duration row -->
@ -156,7 +156,7 @@
<select
value={editProjectId}
onchange={(e) => handleProjectChange((e.target as HTMLSelectElement).value)}
class="flex-1 rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--input))] px-3 py-2 text-sm text-[hsl(var(--foreground))]"
class="flex-1 rounded-lg border border-[hsl(var(--color-border))] bg-[hsl(var(--color-background))] px-3 py-2 text-sm text-[hsl(var(--color-foreground))]"
>
<option value="">{$_('project.internal')}</option>
{#each allProjects.value.filter((p) => !p.isArchived) as proj}
@ -171,9 +171,9 @@
oninput={(e) =>
handleDurationChange(parseInt((e.target as HTMLInputElement).value) || 0)}
min="0"
class="w-20 rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--input))] px-3 py-2 text-center text-sm text-[hsl(var(--foreground))]"
class="w-20 rounded-lg border border-[hsl(var(--color-border))] bg-[hsl(var(--color-background))] px-3 py-2 text-center text-sm text-[hsl(var(--color-foreground))]"
/>
<span class="text-xs text-[hsl(var(--muted-foreground))]">min</span>
<span class="text-xs text-[hsl(var(--color-muted-foreground))]">min</span>
</div>
</div>
@ -182,8 +182,8 @@
<button
onclick={handleBillableToggle}
class="flex items-center gap-2 rounded-lg px-3 py-1.5 text-xs transition-colors {editIsBillable
? 'bg-[hsl(var(--primary)/0.1)] text-[hsl(var(--primary))]'
: 'text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]'}"
? 'bg-[hsl(var(--color-primary)/0.1)] text-[hsl(var(--color-primary))]'
: 'text-[hsl(var(--color-muted-foreground))] hover:text-[hsl(var(--color-foreground))]'}"
>
<CurrencyDollar size={14} />
{editIsBillable ? $_('entry.billable') : $_('entry.notBillable')}
@ -191,7 +191,7 @@
<button
onclick={handleDelete}
class="rounded-lg px-3 py-1.5 text-xs text-red-500 transition-colors hover:bg-red-500/10"
class="rounded-lg px-3 py-1.5 text-xs text-[hsl(var(--color-error))] transition-colors hover:bg-[hsl(var(--color-error)/0.1)]"
>
{$_('common.delete')}
</button>

View file

@ -32,8 +32,15 @@
focusStore.phase === 'focus' ? 'Fokus' : focusStore.phase === 'break' ? 'Pause' : 'Bereit'
);
// Focus = error/red, break = success/green, idle = muted — read from theme tokens
// so the ring tracks light/dark + variants. SVG strokes need a real color value,
// so we wrap with hsl().
const phaseColor = $derived(
focusStore.phase === 'focus' ? '#ef4444' : focusStore.phase === 'break' ? '#22c55e' : '#6b7280'
focusStore.phase === 'focus'
? 'hsl(var(--color-error))'
: focusStore.phase === 'break'
? 'hsl(var(--color-success))'
: 'hsl(var(--color-muted-foreground))'
);
// SVG ring
@ -179,7 +186,7 @@
padding: 0.625rem 0.75rem;
border: 1px solid hsl(var(--color-border));
border-radius: 0.5rem;
background: hsl(var(--color-input, var(--color-background)));
background: hsl(var(--color-background));
color: hsl(var(--color-foreground));
font-size: 0.875rem;
outline: none;
@ -206,7 +213,7 @@
padding: 0.625rem;
border: none;
border-radius: 0.75rem;
background: #ef4444;
background: hsl(var(--color-error));
color: white;
font-size: 0.9375rem;
font-weight: 600;
@ -322,8 +329,8 @@
opacity: 0.9;
}
.control-btn.danger:hover {
color: #ef4444;
border-color: #ef4444;
color: hsl(var(--color-error));
border-color: hsl(var(--color-error));
}
/* Compact mode */