feat(timeline): frame /zeitstrahl in a canvas with a meta line (REQ-001/002)

The timeline now sits inside a bordered, rounded bg-canvas sheet. Below the
heading a sub-line composes the year range, the letter and event counts
(from timelineMeta), and the static "Gruppierung: Datum" — joined by " · "
so the range drops out when there are no year bands and the whole line is
absent for an empty timeline. Semantic tokens only; AA-legible text-xs.

Refs #833
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-14 11:00:54 +02:00
parent a1e57ff8cf
commit e4da28d795
2 changed files with 94 additions and 2 deletions

View File

@@ -1,9 +1,28 @@
<script lang="ts">
import * as m from '$lib/paraglide/messages.js';
import TimelineView from '$lib/timeline/TimelineView.svelte';
import { timelineMeta } from '$lib/timeline/timelineMeta';
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
const meta = $derived(timelineMeta(data.timeline));
const hasContent = $derived(data.timeline.years.length > 0 || data.timeline.undated.length > 0);
// Compose the sub-line from segments joined by " · " so the range drops out
// cleanly when there are no year bands; the whole line is absent when the
// timeline is empty (REQ-002). Counts come from the route alone, never from
// TimelineView.
const metaLine = $derived.by(() => {
const segments: string[] = [];
if (meta.firstYear !== null && meta.lastYear !== null) {
segments.push(`${meta.firstYear}${meta.lastYear}`);
}
segments.push(m.timeline_letters_count({ count: meta.letterCount }));
segments.push(m.timeline_events_count({ count: meta.eventCount }));
segments.push(m.timeline_grouping_date());
return segments.join(' · ');
});
</script>
<svelte:head>
@@ -11,6 +30,13 @@ let { data }: { data: PageData } = $props();
</svelte:head>
<div class="mx-auto max-w-5xl px-4 py-8">
<h1 class="mb-8 font-serif text-2xl font-bold text-brand-navy">{m.timeline_heading()}</h1>
<TimelineView timeline={data.timeline} />
<!-- The .tl-canvas sheet: a framed surface the timeline reads as a finished
life-thread rather than bare page chrome (REQ-001). -->
<div data-testid="timeline-canvas" class="rounded-[10px] border border-line bg-canvas p-6">
<h1 class="font-serif text-2xl font-bold text-brand-navy">{m.timeline_heading()}</h1>
{#if hasContent}
<p data-testid="timeline-meta" class="mt-1 mb-6 font-sans text-xs text-ink-3">{metaLine}</p>
{/if}
<TimelineView timeline={data.timeline} />
</div>
</div>