fix(ui): semantic turquoise tokens, badge styling, saved fade animation
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -111,7 +111,7 @@ let mobileMenuOpen = $state(false);
|
|||||||
aria-pressed={false}
|
aria-pressed={false}
|
||||||
class={mobile
|
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'
|
? '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'}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
class="h-5 w-5 shrink-0"
|
class="h-5 w-5 shrink-0"
|
||||||
@@ -139,8 +139,8 @@ let mobileMenuOpen = $state(false);
|
|||||||
aria-label={m.transcription_mode_stop()}
|
aria-label={m.transcription_mode_stop()}
|
||||||
aria-pressed={true}
|
aria-pressed={true}
|
||||||
class={mobile
|
class={mobile
|
||||||
? 'flex w-full items-center gap-2 rounded bg-[#00C7B1] px-3 py-2 text-left text-[16px] text-white transition focus-visible:ring-2 focus-visible:ring-primary'
|
? 'flex w-full items-center gap-2 rounded bg-turquoise px-3 py-2 text-left text-[16px] text-turquoise-fg transition focus-visible:ring-2 focus-visible:ring-primary'
|
||||||
: 'flex items-center gap-1.5 rounded bg-[#00C7B1] px-3 py-1.5 font-sans text-[16px] font-medium text-white transition focus-visible:ring-2 focus-visible:ring-primary'}
|
: 'flex items-center gap-1.5 rounded bg-turquoise px-3 py-1.5 font-sans text-[16px] font-medium text-white transition focus-visible:ring-2 focus-visible:ring-primary'}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
class="h-5 w-5 shrink-0"
|
class="h-5 w-5 shrink-0"
|
||||||
@@ -236,7 +236,7 @@ let mobileMenuOpen = $state(false);
|
|||||||
onclick={() => (detailsOpen = !detailsOpen)}
|
onclick={() => (detailsOpen = !detailsOpen)}
|
||||||
aria-expanded={detailsOpen}
|
aria-expanded={detailsOpen}
|
||||||
aria-label={m.doc_details_toggle()}
|
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()}
|
{m.doc_details_toggle()}
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { m } from '$lib/paraglide/messages.js';
|
import { m } from '$lib/paraglide/messages.js';
|
||||||
|
|
||||||
type SaveState = 'idle' | 'saving' | 'saved' | 'error';
|
type SaveState = 'idle' | 'saving' | 'saved' | 'fading' | 'error';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
blockId: string;
|
blockId: string;
|
||||||
@@ -30,7 +30,7 @@ let {
|
|||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
let leftBorderClass = $derived(
|
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) {
|
function autoresize(node: HTMLTextAreaElement) {
|
||||||
@@ -61,15 +61,19 @@ function handleDelete() {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="overflow-hidden rounded border border-line {leftBorderClass}" data-block-id={blockId}>
|
<div
|
||||||
<div class="p-4">
|
class="relative overflow-visible rounded border border-line {leftBorderClass}"
|
||||||
|
data-block-id={blockId}
|
||||||
|
>
|
||||||
|
<!-- Turquoise numbered badge — overlaps top-left of card -->
|
||||||
|
<span
|
||||||
|
class="absolute -top-2 -left-2 z-10 flex h-6 w-6 items-center justify-center rounded-full bg-turquoise text-xs font-bold text-turquoise-fg shadow-sm"
|
||||||
|
>
|
||||||
|
{blockNumber}
|
||||||
|
</span>
|
||||||
|
<div class="p-4 pl-6">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="mb-2 flex items-center gap-2">
|
<div class="mb-2 flex items-center gap-2">
|
||||||
<span
|
|
||||||
class="flex h-6 w-6 items-center justify-center rounded-full bg-[#002850] text-xs font-bold text-white"
|
|
||||||
>
|
|
||||||
{blockNumber}
|
|
||||||
</span>
|
|
||||||
{#if label}
|
{#if label}
|
||||||
<span class="text-xs font-medium tracking-wide text-ink-2 uppercase">
|
<span class="text-xs font-medium tracking-wide text-ink-2 uppercase">
|
||||||
{label}
|
{label}
|
||||||
@@ -96,8 +100,10 @@ function handleDelete() {
|
|||||||
<span class="animate-pulse text-xs text-ink-3">
|
<span class="animate-pulse text-xs text-ink-3">
|
||||||
{m.transcription_block_save_saving()}
|
{m.transcription_block_save_saving()}
|
||||||
</span>
|
</span>
|
||||||
{:else if saveState === 'saved'}
|
{:else if saveState === 'saved' || saveState === 'fading'}
|
||||||
<span class="text-xs text-green-600">
|
<span
|
||||||
|
class="text-xs text-green-600 transition-opacity duration-300 {saveState === 'fading' ? 'opacity-0' : 'opacity-100'}"
|
||||||
|
>
|
||||||
{m.transcription_block_save_saved()} <span class="inline-block">✓</span>
|
{m.transcription_block_save_saved()} <span class="inline-block">✓</span>
|
||||||
</span>
|
</span>
|
||||||
{:else if saveState === 'error'}
|
{:else if saveState === 'error'}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { SvelteMap } from 'svelte/reactivity';
|
|||||||
import TranscriptionBlock from './TranscriptionBlock.svelte';
|
import TranscriptionBlock from './TranscriptionBlock.svelte';
|
||||||
import type { TranscriptionBlockData } from '$lib/types';
|
import type { TranscriptionBlockData } from '$lib/types';
|
||||||
|
|
||||||
type SaveState = 'idle' | 'saving' | 'saved' | 'error';
|
type SaveState = 'idle' | 'saving' | 'saved' | 'fading' | 'error';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
blocks: TranscriptionBlockData[];
|
blocks: TranscriptionBlockData[];
|
||||||
@@ -50,7 +50,12 @@ async function executeSave(blockId: string) {
|
|||||||
function scheduleSavedFade(blockId: string) {
|
function scheduleSavedFade(blockId: string) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (getSaveState(blockId) === 'saved') {
|
if (getSaveState(blockId) === 'saved') {
|
||||||
setSaveState(blockId, 'idle');
|
setSaveState(blockId, 'fading');
|
||||||
|
setTimeout(() => {
|
||||||
|
if (getSaveState(blockId) === 'fading') {
|
||||||
|
setSaveState(blockId, 'idle');
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
}
|
}
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,10 @@
|
|||||||
/* Header surface — independent from canvas/surface for per-mode control */
|
/* Header surface — independent from canvas/surface for per-mode control */
|
||||||
--color-header: var(--c-header);
|
--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) */
|
/* Focus ring — keyboard focus indicator, mode-aware (navy in light, mint in dark) */
|
||||||
--color-focus-ring: var(--c-focus-ring);
|
--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 */
|
/* Header is brand-navy in light mode; same in dark mode for contrast compliance */
|
||||||
--c-header: #012851;
|
--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 */
|
/* Focus ring: brand-navy in light mode — 14:1 on white, ~11:1 on sand */
|
||||||
--c-focus-ring: #012851;
|
--c-focus-ring: #012851;
|
||||||
|
|
||||||
@@ -132,6 +139,9 @@
|
|||||||
/* Header at brand-navy: 4.99:1 with ink-3 (WCAG AA ✓), visually above canvas */
|
/* Header at brand-navy: 4.99:1 with ink-3 (WCAG AA ✓), visually above canvas */
|
||||||
--c-header: #012851;
|
--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 */
|
/* Focus ring: brand-mint in dark mode — 9.2:1 on canvas, 7.1:1 on surface */
|
||||||
--c-focus-ring: #a1dcd8;
|
--c-focus-ring: #a1dcd8;
|
||||||
|
|
||||||
@@ -167,6 +177,9 @@
|
|||||||
/* Header at brand-navy: 4.99:1 with ink-3 (WCAG AA ✓), visually above canvas */
|
/* Header at brand-navy: 4.99:1 with ink-3 (WCAG AA ✓), visually above canvas */
|
||||||
--c-header: #012851;
|
--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 */
|
/* Focus ring: brand-mint in dark mode — 9.2:1 on canvas, 7.1:1 on surface */
|
||||||
--c-focus-ring: #a1dcd8;
|
--c-focus-ring: #a1dcd8;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user