refactor(components): replace all hardcoded colors with semantic tokens

Replaces bg-white, text-brand-navy, border-brand-sand, text-gray-*, bg-[#2A2A2A],
bg-brand-purple/15, hover:bg-brand-sand, etc. across all 35 .svelte files with
semantic token utilities (bg-surface, text-ink, border-line, bg-pdf-bg, bg-nav-active,
bg-muted, text-accent, bg-primary, ...).

Also adds CSS filter: invert(1) in layout.css for De Gruyter <img> icons in dark mode,
excluding icons that carry .invert already (to prevent double-inversion).

Closes #64
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-03-25 12:22:50 +01:00
parent caba89dacc
commit e4539ed0f0
32 changed files with 569 additions and 613 deletions

View File

@@ -98,7 +98,7 @@ $effect(() => {
<div class="mb-6">
<a
href="/persons"
class="group inline-flex items-center text-xs font-bold tracking-widest text-gray-500 uppercase transition-colors hover:text-brand-navy"
class="group inline-flex items-center text-xs font-bold tracking-widest text-ink-2 uppercase transition-colors hover:text-ink"
>
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Arrow/Arrow-Left-MD.svg"
@@ -111,15 +111,15 @@ $effect(() => {
</div>
<!-- Header / Metadata Card -->
<div class="border-brand-sand mb-10 overflow-hidden rounded-sm border bg-white shadow-sm">
<div class="h-2 w-full bg-brand-navy"></div>
<div class="mb-10 overflow-hidden rounded-sm border border-line bg-surface shadow-sm">
<div class="h-2 w-full bg-primary"></div>
<div class="p-8 md:p-10">
{#if editMode && data.canWrite}
<!-- Edit Form -->
<form method="POST" action="?/update" use:enhance>
<div class="flex flex-col gap-6">
<h2 class="border-b border-gray-100 pb-3 font-serif text-xl text-brand-navy">
<h2 class="border-b border-line-2 pb-3 font-serif text-xl text-ink">
{m.person_edit_heading()}
</h2>
@@ -133,7 +133,7 @@ $effect(() => {
<div>
<label
for="firstName"
class="mb-1 block text-xs font-bold tracking-widest text-gray-400 uppercase"
class="mb-1 block text-xs font-bold tracking-widest text-ink-3 uppercase"
>{m.form_label_first_name()} *</label
>
<input
@@ -142,13 +142,13 @@ $effect(() => {
type="text"
required
value={person.firstName}
class="block w-full rounded border border-gray-300 px-3 py-2 font-serif text-brand-navy focus:border-brand-navy focus:outline-none"
class="block w-full rounded border border-gray-300 px-3 py-2 font-serif text-ink focus:border-ink focus:outline-none"
/>
</div>
<div>
<label
for="lastName"
class="mb-1 block text-xs font-bold tracking-widest text-gray-400 uppercase"
class="mb-1 block text-xs font-bold tracking-widest text-ink-3 uppercase"
>{m.form_label_last_name()} *</label
>
<input
@@ -157,13 +157,13 @@ $effect(() => {
type="text"
required
value={person.lastName}
class="block w-full rounded border border-gray-300 px-3 py-2 font-serif text-brand-navy focus:border-brand-navy focus:outline-none"
class="block w-full rounded border border-gray-300 px-3 py-2 font-serif text-ink focus:border-ink focus:outline-none"
/>
</div>
<div class="md:col-span-2">
<label
for="alias"
class="mb-1 block text-xs font-bold tracking-widest text-gray-400 uppercase"
class="mb-1 block text-xs font-bold tracking-widest text-ink-3 uppercase"
>{m.form_label_alias()}</label
>
<input
@@ -171,13 +171,13 @@ $effect(() => {
name="alias"
type="text"
value={person.alias ?? ''}
class="block w-full rounded border border-gray-300 px-3 py-2 font-serif text-brand-navy focus:border-brand-navy focus:outline-none"
class="block w-full rounded border border-gray-300 px-3 py-2 font-serif text-ink focus:border-ink focus:outline-none"
/>
</div>
<div>
<label
for="birthYear"
class="mb-1 block text-xs font-bold tracking-widest text-gray-400 uppercase"
class="mb-1 block text-xs font-bold tracking-widest text-ink-3 uppercase"
>{m.person_label_birth_year()}</label
>
<input
@@ -188,13 +188,13 @@ $effect(() => {
max="2100"
placeholder={m.person_placeholder_year()}
value={person.birthYear ?? ''}
class="block w-full rounded border border-gray-300 px-3 py-2 font-serif text-brand-navy focus:border-brand-navy focus:outline-none"
class="block w-full rounded border border-gray-300 px-3 py-2 font-serif text-ink focus:border-ink focus:outline-none"
/>
</div>
<div>
<label
for="deathYear"
class="mb-1 block text-xs font-bold tracking-widest text-gray-400 uppercase"
class="mb-1 block text-xs font-bold tracking-widest text-ink-3 uppercase"
>{m.person_label_death_year()}</label
>
<input
@@ -205,13 +205,13 @@ $effect(() => {
max="2100"
placeholder={m.person_placeholder_year()}
value={person.deathYear ?? ''}
class="block w-full rounded border border-gray-300 px-3 py-2 font-serif text-brand-navy focus:border-brand-navy focus:outline-none"
class="block w-full rounded border border-gray-300 px-3 py-2 font-serif text-ink focus:border-ink focus:outline-none"
/>
</div>
<div class="md:col-span-2">
<label
for="notes"
class="mb-1 block text-xs font-bold tracking-widest text-gray-400 uppercase"
class="mb-1 block text-xs font-bold tracking-widest text-ink-3 uppercase"
>{m.person_label_notes()}</label
>
<textarea
@@ -219,7 +219,7 @@ $effect(() => {
name="notes"
rows="4"
placeholder={m.person_placeholder_notes()}
class="block w-full resize-y rounded border border-gray-300 px-3 py-2 font-serif text-brand-navy focus:border-brand-navy focus:outline-none"
class="block w-full resize-y rounded border border-gray-300 px-3 py-2 font-serif text-ink focus:border-ink focus:outline-none"
>{person.notes ?? ''}</textarea
>
</div>
@@ -228,14 +228,14 @@ $effect(() => {
<div class="flex gap-3">
<button
type="submit"
class="rounded bg-brand-navy px-5 py-2 text-sm font-bold tracking-widest text-white uppercase transition-colors hover:bg-brand-navy/80"
class="rounded bg-primary px-5 py-2 text-sm font-bold tracking-widest text-white uppercase transition-colors hover:bg-primary/80"
>
{m.btn_save()}
</button>
<button
type="button"
onclick={() => (editMode = false)}
class="rounded border border-gray-300 px-5 py-2 text-sm font-bold tracking-widest text-gray-600 uppercase transition-colors hover:bg-gray-50"
class="rounded border border-gray-300 px-5 py-2 text-sm font-bold tracking-widest text-ink-2 uppercase transition-colors hover:bg-gray-50"
>
{m.btn_cancel()}
</button>
@@ -247,15 +247,15 @@ $effect(() => {
<div class="flex flex-col items-start gap-8 md:flex-row">
<div class="flex-shrink-0">
<div
class="border-brand-sand bg-brand-sand/30 flex h-24 w-24 items-center justify-center rounded-full border text-brand-navy"
class="flex h-24 w-24 items-center justify-center rounded-full border border-line bg-muted text-ink"
>
<span class="font-serif text-3xl">{person.firstName[0]}{person.lastName[0]}</span>
</div>
</div>
<div class="w-full flex-1">
<div class="mb-8 flex items-start justify-between border-b border-gray-100 pb-4">
<h1 class="font-serif text-4xl text-brand-navy">
<div class="mb-8 flex items-start justify-between border-b border-line-2 pb-4">
<h1 class="font-serif text-4xl text-ink">
{person.firstName}
{person.lastName}
</h1>
@@ -263,7 +263,7 @@ $effect(() => {
{#if data.canWrite}
<button
onclick={() => (editMode = true)}
class="inline-flex items-center gap-1.5 rounded border border-gray-300 px-3 py-1.5 text-xs font-bold tracking-widest text-gray-500 uppercase transition-colors hover:border-brand-navy hover:text-brand-navy"
class="inline-flex items-center gap-1.5 rounded border border-gray-300 px-3 py-1.5 text-xs font-bold tracking-widest text-ink-2 uppercase transition-colors hover:border-primary hover:text-ink"
>
<img
src="/degruyter-icons/Simple/Small-16px/SVG/Action/Edit-Content-SM.svg"
@@ -280,10 +280,10 @@ $effect(() => {
<div class="grid grid-cols-1 gap-8 md:grid-cols-2">
<div>
<span
class="mb-1 block font-sans text-xs font-bold tracking-widest text-gray-400 uppercase"
class="mb-1 block font-sans text-xs font-bold tracking-widest text-ink-3 uppercase"
>{m.person_label_full_name()}</span
>
<span class="block font-serif text-lg text-brand-navy"
<span class="block font-serif text-lg text-ink"
>{person.firstName} {person.lastName}</span
>
</div>
@@ -291,23 +291,21 @@ $effect(() => {
{#if person.alias}
<div>
<span
class="mb-1 block font-sans text-xs font-bold tracking-widest text-gray-400 uppercase"
class="mb-1 block font-sans text-xs font-bold tracking-widest text-ink-3 uppercase"
>{m.form_label_alias()}</span
>
<span class="block font-serif text-lg text-brand-navy italic"
>"{person.alias}"</span
>
<span class="block font-serif text-lg text-ink italic">"{person.alias}"</span>
</div>
{/if}
{#if person.birthYear || person.deathYear}
<div>
<span
class="mb-1 block font-sans text-xs font-bold tracking-widest text-gray-400 uppercase"
class="mb-1 block font-sans text-xs font-bold tracking-widest text-ink-3 uppercase"
>
{#if person.birthYear && person.deathYear}{m.person_label_birth_year()} / {m.person_label_death_year()}{:else if person.birthYear}{m.person_label_birth_year()}{:else}{m.person_label_death_year()}{/if}
</span>
<span class="block font-serif text-lg text-brand-navy">
<span class="block font-serif text-lg text-ink">
{#if person.birthYear}* {person.birthYear}{/if}{#if person.birthYear && person.deathYear}
&nbsp;{/if}{#if person.deathYear}{person.deathYear}{/if}
</span>
@@ -317,10 +315,10 @@ $effect(() => {
{#if person.notes}
<div class="md:col-span-2">
<span
class="mb-1 block font-sans text-xs font-bold tracking-widest text-gray-400 uppercase"
class="mb-1 block font-sans text-xs font-bold tracking-widest text-ink-3 uppercase"
>{m.person_label_notes()}</span
>
<p class="font-serif text-base whitespace-pre-wrap text-brand-navy">
<p class="font-serif text-base whitespace-pre-wrap text-ink">
{person.notes}
</p>
</div>
@@ -335,10 +333,10 @@ $effect(() => {
<!-- Merge Section -->
{#if data.canWrite}
{#key person.id}
<div class="border-brand-sand mb-10 overflow-hidden rounded-sm border bg-white shadow-sm">
<div class="mb-10 overflow-hidden rounded-sm border border-line bg-surface shadow-sm">
<div class="p-6 md:p-8">
<h2 class="mb-1 font-serif text-lg text-brand-navy">{m.person_merge_heading()}</h2>
<p class="mb-5 font-sans text-sm text-gray-500">
<h2 class="mb-1 font-serif text-lg text-ink">{m.person_merge_heading()}</h2>
<p class="mb-5 font-sans text-sm text-ink-2">
{m.person_merge_description()}
</p>
@@ -381,7 +379,7 @@ $effect(() => {
<button
type="button"
onclick={() => (showMergeConfirm = false)}
class="rounded border border-gray-300 px-4 py-2 text-sm font-bold tracking-widest text-gray-500 uppercase transition-colors hover:bg-gray-50"
class="rounded border border-gray-300 px-4 py-2 text-sm font-bold tracking-widest text-ink-2 uppercase transition-colors hover:bg-gray-50"
>
{m.btn_cancel()}
</button>
@@ -406,17 +404,17 @@ $effect(() => {
<!-- Co-Correspondents Section -->
{#if coCorrespondents.length > 0}
<div class="mb-6">
<h3 class="mb-3 text-xs font-bold tracking-widest text-gray-400 uppercase">
<h3 class="mb-3 text-xs font-bold tracking-widest text-ink-3 uppercase">
{m.person_co_correspondents_heading()}
</h3>
<div class="flex flex-wrap gap-2">
{#each coCorrespondents as c (c.id)}
<a
href="/conversations?senderId={person.id}&receiverId={c.id}"
class="border-brand-sand inline-flex items-center gap-1.5 rounded-full border px-3 py-1 font-serif text-sm text-brand-navy transition-colors hover:border-brand-navy"
class="inline-flex items-center gap-1.5 rounded-full border border-line px-3 py-1 font-serif text-sm text-ink transition-colors hover:border-primary"
>
{c.name}
<span class="font-sans text-xs text-gray-400">({c.count})</span>
<span class="font-sans text-xs text-ink-3">({c.count})</span>
</a>
{/each}
</div>
@@ -425,18 +423,18 @@ $effect(() => {
<!-- Sent Documents Section -->
<div class="mb-10">
<div class="mb-6 flex items-center gap-3 border-b border-brand-navy/10 pb-2">
<h2 class="font-serif text-xl text-brand-navy">{m.person_docs_heading()}</h2>
<span class="rounded-full bg-brand-navy px-2 py-1 text-xs font-bold text-white">
<div class="mb-6 flex items-center gap-3 border-b border-ink/10 pb-2">
<h2 class="font-serif text-xl text-ink">{m.person_docs_heading()}</h2>
<span class="rounded-full bg-primary px-2 py-1 text-xs font-bold text-white">
{sentDocuments.length}
</span>
{#if sentYearRange}
<span class="font-sans text-xs text-gray-400">{sentYearRange}</span>
<span class="font-sans text-xs text-ink-3">{sentYearRange}</span>
{/if}
{#if sentDocuments.length > 1}
<button
onclick={() => (sortDirSent = sortDirSent === 'DESC' ? 'ASC' : 'DESC')}
class="ml-auto text-xs font-bold tracking-widest text-gray-400 uppercase transition-colors hover:text-brand-navy"
class="ml-auto text-xs font-bold tracking-widest text-ink-3 uppercase transition-colors hover:text-ink"
>
{sortDirSent === 'DESC' ? m.conv_sort_newest() : m.conv_sort_oldest()}
</button>
@@ -444,8 +442,8 @@ $effect(() => {
</div>
{#if sentDocuments.length === 0}
<div class="border-brand-sand rounded-sm border border-dashed bg-white p-12 text-center">
<p class="font-sans text-gray-500">{m.person_no_docs()}</p>
<div class="rounded-sm border border-dashed border-line bg-surface p-12 text-center">
<p class="font-sans text-ink-2">{m.person_no_docs()}</p>
</div>
{:else}
<ul class="space-y-3">
@@ -453,12 +451,12 @@ $effect(() => {
<li class="group">
<a
href="/documents/{doc.id}"
class="border-brand-sand block border bg-white p-4 transition-all duration-200 hover:border-brand-navy hover:shadow-md"
class="block border border-line bg-surface p-4 transition-all duration-200 hover:border-primary hover:shadow-md"
>
<div class="flex items-center justify-between">
<div class="flex items-center gap-4 overflow-hidden">
<div
class="bg-brand-sand/20 flex h-10 w-10 flex-shrink-0 items-center justify-center rounded text-brand-navy transition-colors group-hover:bg-brand-mint group-hover:text-brand-navy"
class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded bg-muted text-ink transition-colors group-hover:bg-accent group-hover:text-ink"
>
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/PDF-Document-MD.svg"
@@ -469,16 +467,16 @@ $effect(() => {
</div>
<div class="min-w-0">
<div
class="truncate font-serif text-base font-medium text-brand-navy decoration-brand-mint decoration-2 underline-offset-2 group-hover:underline"
class="truncate font-serif text-base font-medium text-ink decoration-brand-mint decoration-2 underline-offset-2 group-hover:underline"
>
{doc.title || doc.originalFilename}
</div>
<div class="mt-0.5 flex items-center space-x-2 font-sans text-xs text-gray-500">
<div class="mt-0.5 flex items-center space-x-2 font-sans text-xs text-ink-2">
<span
>{doc.documentDate ? formatDate(doc.documentDate) : m.doc_no_date()}</span
>
{#if doc.location}
<span class="text-brand-mint"></span>
<span class="text-accent"></span>
<span>{doc.location}</span>
{/if}
</div>
@@ -488,7 +486,7 @@ $effect(() => {
<span
class="hidden items-center rounded-full border px-2 py-0.5 text-[10px] font-bold tracking-wide uppercase sm:inline-flex
{doc.status === 'UPLOADED'
? 'border-brand-mint/50 bg-brand-mint/20 text-brand-navy'
? 'border-accent/50 bg-accent/20 text-ink'
: 'border-yellow-200 bg-yellow-50 text-yellow-800'}"
>
{doc.status}
@@ -508,7 +506,7 @@ $effect(() => {
{#if sentDocuments.length > DOCS_PREVIEW_LIMIT && !showAllSent}
<button
onclick={() => (showAllSent = true)}
class="mt-3 text-xs font-bold tracking-widest text-brand-navy/50 uppercase transition-colors hover:text-brand-navy"
class="mt-3 text-xs font-bold tracking-widest text-ink/50 uppercase transition-colors hover:text-ink"
>
{m.person_show_more({ count: sentDocuments.length - DOCS_PREVIEW_LIMIT })}
</button>
@@ -518,18 +516,18 @@ $effect(() => {
<!-- Received Documents Section -->
<div>
<div class="mb-6 flex items-center gap-3 border-b border-brand-navy/10 pb-2">
<h2 class="font-serif text-xl text-brand-navy">{m.person_received_docs_heading()}</h2>
<span class="rounded-full bg-brand-navy px-2 py-1 text-xs font-bold text-white">
<div class="mb-6 flex items-center gap-3 border-b border-ink/10 pb-2">
<h2 class="font-serif text-xl text-ink">{m.person_received_docs_heading()}</h2>
<span class="rounded-full bg-primary px-2 py-1 text-xs font-bold text-white">
{receivedDocuments.length}
</span>
{#if receivedYearRange}
<span class="font-sans text-xs text-gray-400">{receivedYearRange}</span>
<span class="font-sans text-xs text-ink-3">{receivedYearRange}</span>
{/if}
{#if receivedDocuments.length > 1}
<button
onclick={() => (sortDirReceived = sortDirReceived === 'DESC' ? 'ASC' : 'DESC')}
class="ml-auto text-xs font-bold tracking-widest text-gray-400 uppercase transition-colors hover:text-brand-navy"
class="ml-auto text-xs font-bold tracking-widest text-ink-3 uppercase transition-colors hover:text-ink"
>
{sortDirReceived === 'DESC' ? m.conv_sort_newest() : m.conv_sort_oldest()}
</button>
@@ -537,8 +535,8 @@ $effect(() => {
</div>
{#if receivedDocuments.length === 0}
<div class="border-brand-sand rounded-sm border border-dashed bg-white p-12 text-center">
<p class="font-sans text-gray-500">{m.person_no_received_docs()}</p>
<div class="rounded-sm border border-dashed border-line bg-surface p-12 text-center">
<p class="font-sans text-ink-2">{m.person_no_received_docs()}</p>
</div>
{:else}
<ul class="space-y-3">
@@ -546,12 +544,12 @@ $effect(() => {
<li class="group">
<a
href="/documents/{doc.id}"
class="border-brand-sand block border bg-white p-4 transition-all duration-200 hover:border-brand-navy hover:shadow-md"
class="block border border-line bg-surface p-4 transition-all duration-200 hover:border-primary hover:shadow-md"
>
<div class="flex items-center justify-between">
<div class="flex items-center gap-4 overflow-hidden">
<div
class="bg-brand-sand/20 flex h-10 w-10 flex-shrink-0 items-center justify-center rounded text-brand-navy transition-colors group-hover:bg-brand-mint group-hover:text-brand-navy"
class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded bg-muted text-ink transition-colors group-hover:bg-accent group-hover:text-ink"
>
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/PDF-Document-MD.svg"
@@ -562,16 +560,16 @@ $effect(() => {
</div>
<div class="min-w-0">
<div
class="truncate font-serif text-base font-medium text-brand-navy decoration-brand-mint decoration-2 underline-offset-2 group-hover:underline"
class="truncate font-serif text-base font-medium text-ink decoration-brand-mint decoration-2 underline-offset-2 group-hover:underline"
>
{doc.title || doc.originalFilename}
</div>
<div class="mt-0.5 flex items-center space-x-2 font-sans text-xs text-gray-500">
<div class="mt-0.5 flex items-center space-x-2 font-sans text-xs text-ink-2">
<span
>{doc.documentDate ? formatDate(doc.documentDate) : m.doc_no_date()}</span
>
{#if doc.location}
<span class="text-brand-mint"></span>
<span class="text-accent"></span>
<span>{doc.location}</span>
{/if}
</div>
@@ -581,7 +579,7 @@ $effect(() => {
<span
class="hidden items-center rounded-full border px-2 py-0.5 text-[10px] font-bold tracking-wide uppercase sm:inline-flex
{doc.status === 'UPLOADED'
? 'border-brand-mint/50 bg-brand-mint/20 text-brand-navy'
? 'border-accent/50 bg-accent/20 text-ink'
: 'border-yellow-200 bg-yellow-50 text-yellow-800'}"
>
{doc.status}
@@ -601,7 +599,7 @@ $effect(() => {
{#if receivedDocuments.length > DOCS_PREVIEW_LIMIT && !showAllReceived}
<button
onclick={() => (showAllReceived = true)}
class="mt-3 text-xs font-bold tracking-widest text-brand-navy/50 uppercase transition-colors hover:text-brand-navy"
class="mt-3 text-xs font-bold tracking-widest text-ink/50 uppercase transition-colors hover:text-ink"
>
{m.person_show_more({ count: receivedDocuments.length - DOCS_PREVIEW_LIMIT })}
</button>