From a3fbcf346b4743600567ad9c0dfa4c02c0d1ab73 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 5 Apr 2026 20:26:41 +0200 Subject: [PATCH] fix(ui): semantic turquoise tokens, badge styling, saved fade animation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add turquoise/turquoise-fg semantic color tokens to layout.css (light + dark mode), replacing all hardcoded #00C7B1 in components - Bump Details toggle from text-xs to text-sm for visual hierarchy - Block badge: navy → turquoise, overlapping top-left card border with absolute positioning to visually link PDF annotation badges - Saved indicator: smooth 300ms opacity fade before removal (new 'fading' state in SaveState type) - Transcribe buttons: use border-turquoise/bg-turquoise/text-turquoise-fg Fixes @Leonie concerns: toggle visual weight, semantic tokens, badge styling, saved fade animation Co-Authored-By: Claude Sonnet 4.6 --- .../src/lib/components/DocumentTopBar.svelte | 8 +++--- .../lib/components/TranscriptionBlock.svelte | 28 +++++++++++-------- .../components/TranscriptionEditView.svelte | 9 ++++-- frontend/src/routes/layout.css | 13 +++++++++ 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/frontend/src/lib/components/DocumentTopBar.svelte b/frontend/src/lib/components/DocumentTopBar.svelte index a58eb3bd..b7b06b89 100644 --- a/frontend/src/lib/components/DocumentTopBar.svelte +++ b/frontend/src/lib/components/DocumentTopBar.svelte @@ -111,7 +111,7 @@ let mobileMenuOpen = $state(false); aria-pressed={false} class={mobile ? 'flex w-full items-center gap-2 rounded px-3 py-2 text-left text-[16px] text-ink transition hover:bg-muted focus-visible:ring-2 focus-visible:ring-primary' - : 'hidden items-center gap-1.5 rounded border border-[#00C7B1] px-3 py-1.5 font-sans text-[16px] font-medium text-ink transition hover:bg-[#00C7B1] hover:text-white focus-visible:ring-2 focus-visible:ring-primary md:flex'} + : 'hidden items-center gap-1.5 rounded border border-turquoise px-3 py-1.5 font-sans text-[16px] font-medium text-ink transition hover:bg-turquoise hover:text-turquoise-fg focus-visible:ring-2 focus-visible:ring-primary md:flex'} > (detailsOpen = !detailsOpen)} aria-expanded={detailsOpen} aria-label={m.doc_details_toggle()} - class="ml-2 inline-flex min-h-[44px] shrink-0 items-center gap-1 rounded border px-2 py-1 font-sans text-xs font-semibold transition-colors {detailsOpen ? 'border-primary bg-primary text-primary-fg' : 'border-line text-ink-2 hover:bg-muted hover:text-ink'}" + class="ml-2 inline-flex min-h-[44px] shrink-0 items-center gap-1.5 rounded border px-3 py-1 font-sans text-sm font-semibold transition-colors {detailsOpen ? 'border-primary bg-primary text-primary-fg' : 'border-line text-ink-2 hover:bg-muted hover:text-ink'}" > {m.doc_details_toggle()} import { m } from '$lib/paraglide/messages.js'; -type SaveState = 'idle' | 'saving' | 'saved' | 'error'; +type SaveState = 'idle' | 'saving' | 'saved' | 'fading' | 'error'; type Props = { blockId: string; @@ -30,7 +30,7 @@ let { }: Props = $props(); let leftBorderClass = $derived( - saveState === 'error' ? 'border-l-2 border-error' : active ? 'border-l-2 border-[#00C7B1]' : '' + saveState === 'error' ? 'border-l-2 border-error' : active ? 'border-l-2 border-turquoise' : '' ); function autoresize(node: HTMLTextAreaElement) { @@ -61,15 +61,19 @@ function handleDelete() { } -
-
+
+ + + {blockNumber} + +
- - {blockNumber} - {#if label} {label} @@ -96,8 +100,10 @@ function handleDelete() { {m.transcription_block_save_saving()} - {:else if saveState === 'saved'} - + {:else if saveState === 'saved' || saveState === 'fading'} + {m.transcription_block_save_saved()} {:else if saveState === 'error'} diff --git a/frontend/src/lib/components/TranscriptionEditView.svelte b/frontend/src/lib/components/TranscriptionEditView.svelte index 08d2652f..f4f57c51 100644 --- a/frontend/src/lib/components/TranscriptionEditView.svelte +++ b/frontend/src/lib/components/TranscriptionEditView.svelte @@ -4,7 +4,7 @@ import { SvelteMap } from 'svelte/reactivity'; import TranscriptionBlock from './TranscriptionBlock.svelte'; import type { TranscriptionBlockData } from '$lib/types'; -type SaveState = 'idle' | 'saving' | 'saved' | 'error'; +type SaveState = 'idle' | 'saving' | 'saved' | 'fading' | 'error'; type Props = { blocks: TranscriptionBlockData[]; @@ -50,7 +50,12 @@ async function executeSave(blockId: string) { function scheduleSavedFade(blockId: string) { setTimeout(() => { if (getSaveState(blockId) === 'saved') { - setSaveState(blockId, 'idle'); + setSaveState(blockId, 'fading'); + setTimeout(() => { + if (getSaveState(blockId) === 'fading') { + setSaveState(blockId, 'idle'); + } + }, 300); } }, 2000); } diff --git a/frontend/src/routes/layout.css b/frontend/src/routes/layout.css index 4f7bfe0e..197aef4e 100644 --- a/frontend/src/routes/layout.css +++ b/frontend/src/routes/layout.css @@ -59,6 +59,10 @@ /* Header surface — independent from canvas/surface for per-mode control */ --color-header: var(--c-header); + /* Turquoise — transcription mode accent */ + --color-turquoise: var(--c-turquoise); + --color-turquoise-fg: var(--c-turquoise-fg); + /* Focus ring — keyboard focus indicator, mode-aware (navy in light, mint in dark) */ --color-focus-ring: var(--c-focus-ring); @@ -93,6 +97,9 @@ /* Header is brand-navy in light mode; same in dark mode for contrast compliance */ --c-header: #012851; + --c-turquoise: #00c7b1; + --c-turquoise-fg: #ffffff; + /* Focus ring: brand-navy in light mode — 14:1 on white, ~11:1 on sand */ --c-focus-ring: #012851; @@ -132,6 +139,9 @@ /* Header at brand-navy: 4.99:1 with ink-3 (WCAG AA ✓), visually above canvas */ --c-header: #012851; + --c-turquoise: #00c7b1; + --c-turquoise-fg: #012851; + /* Focus ring: brand-mint in dark mode — 9.2:1 on canvas, 7.1:1 on surface */ --c-focus-ring: #a1dcd8; @@ -167,6 +177,9 @@ /* Header at brand-navy: 4.99:1 with ink-3 (WCAG AA ✓), visually above canvas */ --c-header: #012851; + --c-turquoise: #00c7b1; + --c-turquoise-fg: #012851; + /* Focus ring: brand-mint in dark mode — 9.2:1 on canvas, 7.1:1 on surface */ --c-focus-ring: #a1dcd8;