# Briefwechsel — Thumbnail Rows · Final Design Spec · Familienarchiv

Reading this spec. Mockups in Section 02 are scaled to ~55 % of real pixel values so that multiple viewports fit on one page. Never copy pixel sizes from the mockups. Use the `impl-ref` tables for exact Tailwind class + pixel value. Close-ups in Section 03 are rendered at ~100 % scale for pixel-accurate reference.

## 01Page Anatomy — Default State at 1440 px (single-person)

The page is a single vertical column (`max-w-7xl`). Filter card sticks to the top of the content region; the row list starts immediately below, grouped by year dividers. All viewports render the same regions in the same order — they only adapt spacing and thumbnail size, never rearrange.

familienarchiv.de/briefwechsel?senderId=…
Familienarchiv
Person
Walter de Gruyter
Korrespondent — optional
Alle Korrespondenten
Newest ↓
▾ Filter
851 Briefe
📋 Alle Briefe von Walter de Gruyter — wähle einen Korrespondenten oben um einzugrenzen
19401 Brief
Demo leserlicher Brief
letzte Lebenstage von W. Dörpfeld in Griechenland — ausführlicher Bericht aus Belgard
← eingehendGertrud von Rofden·📍 Belgard·DörpfeldGriechenland
31. Mai 1940
vor 85 Jahren
19235 Briefe
W-0397 – 2. September 1923 – B.Lichterfelde
von Elsbeth geschriebener Kommentar, den Herbert zum Brief erzählte
→ ausgehendan Herbert Cram·📍 B.Lichterfelde·VerlagFamilie
2. September 1923
vor 102 Jahren
Ansichtskarte – 2. September 1923 – B.Lichterfelde
kurze Grüße aus B.Lichterfelde, Hinweis auf den kommenden Besuch
→ ausgehendan Herbert Cram·📍 B.Lichterfelde·✉ Postkarte
2. September 1923
vor 102 Jahren
4 S.
W-0524 – 31. Juli 1923 – Berlin
Glückwunsch zum 60. Geburtstag, Bericht über den Verlag und den Umzug
→ ausgehendan Walter Dieckmann·📍 Berlin·Geburtstag
31. Juli 1923
vor 102 Jahren
A · Filter card
Two inputs (person required, correspondent optional) + action row + hint bar. Uses `bg-surface` wrapper, not a card — the hint bar gives it closure.
B · Year divider
Sticky-looking band between year groups. Large navy numeral + brief count. Uses `bg-muted` and a 1 px rule above/below.
C · Row list
Single `<ul>` per year group. Each row is an `<a>` with `role="listitem"` ancestor. Border-left accent colors direction: navy = outgoing, mint-darker = incoming.
Row · Thumbnail cell
Fixed 104 × 120 px cell on desktop; portrait and landscape both centered in the same cell so row height stays consistent across mixed media.
Row · Body
Serif title · italic serif summary (with mint quote glyphs) · sans meta line with direction + counterpart + location + tags. Summary omitted entirely when empty.
Row · Right column
Date (serif, bold) + relative age ("vor 102 Jahren"). No status, no archive location — deliberately calm.
Implementation Reference — Page ShellTailwind 4 · tokens from layout.css

Element | Classes | Real | Note
--- | --- | --- | ---
Page container | `mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8` | max 80rem | Matches production /briefwechsel
Filter card wrapper | `mb-8 rounded-sm border border-line bg-surface p-6 shadow-sm` | padding 24 px | Existing `CorrespondenzPersonBar` container
Year divider | `flex items-baseline gap-3 border-y border-line bg-muted px-[14px] py-[8px]` | border 1 px both sides | Keep production styling — only row changes
Year numeral | `font-serif text-2xl font-black tracking-tight text-primary` | 24 px / 900 / -0.025em | Merriweather Black
Year count | `text-sm font-bold text-ink-3` | 14 px / 700 | "5 Briefe" / Paraglide plural
Row list wrapper | `overflow-hidden rounded-sm border border-line bg-surface` | 1 px border | Hides row borders at ends
Row | `group grid grid-cols-[104px_1fr_auto] gap-5 items-center px-5 py-[14px] border-b border-line-2 border-l-[3px] min-h-[128px] cursor-pointer transition-colors hover:bg-muted` | 128 px min · 20 × 14 padding | `border-l-primary` out · `border-l-accent` in
Touch target | Full row is clickable; row height 128 px > WCAG 44 px minimum × ~3 | 128 ≥ 44 | Senior audience: comfort over density

## 02Content States × 3 Viewports

Five states covering the combinations that matter. Every frame renders the full page shell (header → filter card → list). Reading order per state: 320 px (mobile S) → 768 px (tablet) → 1440 px (desktop). Watch for filter card wrap at 320, thumbnail shrinkage, and the right-column behaviour under content pressure.

01Default · Single person with mixed row types
The happy path. Four rows shown: incoming typed letter, outgoing handwritten letter, outgoing postcard (landscape thumbnail), outgoing multi-page letter (page badge). Summaries present on three of four — the fourth row shows the clean no-summary variant.
320 px · Mobile176 px @ 55%
9:41
FA
Person
Walter de Gruyter
Korrespondent
alle
↓
▾
851
19235
W-0397
Elsbeths Kommentar
→H. Cram
2. Sep
Ansichtskarte
→H. Cram✉
2. Sep
4
W-0524
Geburtstag & Umzug
→W. Dieckmann
31. Jul
W-0396
→H. Cram
2. Sep
768 px · Tablet422 px @ 55%
familienarchiv.de/briefwechsel
Familienarchiv
Person
Walter de Gruyter
Korrespondent — optional
Alle Korrespondenten
Newest ↓
▾ Filter
851 Briefe
📋 Alle Briefe von Walter de Gruyter
19235 Briefe
W-0397 – 2. September 1923
von Elsbeth geschriebener Kommentar, den Herbert zum Brief erzählte
→an Herbert CramVerlag
2. Sep 1923
vor 102 J.
Ansichtskarte – 2. September 1923
kurze Grüße aus B.Lichterfelde
→an Herbert Cram✉ Postkarte
2. Sep 1923
vor 102 J.
4 S.
W-0524 – 31. Juli 1923 – Berlin
Glückwunsch zum 60. Geburtstag, Bericht über den Verlag
→an Walter DieckmannGeburtstag
31. Jul 1923
vor 102 J.
W-0396 – 2. September 1923
→an Herbert Cram
2. Sep 1923
vor 102 J.
1440 px · Desktop720 px @ 55%
familienarchiv.de/briefwechsel?senderId=…
Familienarchiv
Person
Walter de Gruyter
Korrespondent — optional
Alle Korrespondenten
Newest ↓
▾ Filter
851 Briefe
📋 Alle Briefe von Walter de Gruyter — wähle einen Korrespondenten oben um einzugrenzen
19235 Briefe
W-0397 – 2. September 1923 – B.Lichterfelde
von Elsbeth geschriebener Kommentar, den Herbert zum Brief erzählte
→ ausgehendan Herbert Cram·📍 B.Lichterfelde·VerlagFamilie
2. September 1923
vor 102 Jahren
Ansichtskarte – 2. September 1923 – B.Lichterfelde
kurze Grüße aus B.Lichterfelde, Hinweis auf den kommenden Besuch
→ ausgehendan Herbert Cram·📍 B.Lichterfelde·✉ Postkarte
2. September 1923
vor 102 Jahren
4 S.
W-0524 – 31. Juli 1923 – Berlin
Glückwunsch zum 60. Geburtstag, Bericht über den Verlag und den Umzug
→ ausgehendan Walter Dieckmann·📍 Berlin·GeburtstagVerlag
31. Juli 1923
vor 102 Jahren
W-0396 – 2. September 1923 – B.Lichterfelde
→ ausgehendan Herbert Cram·📍 B.Lichterfelde
2. September 1923
vor 102 Jahren
Layout-Beobachtungen.

- 320 px: Filter card collapses to a single column. Title truncates with ellipsis (`W-0397`), summary keeps 1 line max; counterpart shortens to initials+last (`H. Cram`). Date format is `2. Sep` — no year (year dividers provide it).
- 768 px: Two-column filter returns. Title shows full label; summary gets 2 lines; date is `2. Sep 1923`; location meta omitted (kept to 2 items), tags trimmed to one.
- 1440 px: Full meta (direction word, counterpart, location, 2 tags). Relative date appears below the absolute date.
- Row 4 (no summary) retains the exact same row height as others — the row grid is `min-h-[128px]` at desktop so mixed-summary lists don't visually jump.

02Bilateral · Both filters set + distribution bar
Sender and receiver both selected. A distribution bar appears above the row list, pattern lifted from production `ConversationTimeline`. Rows show compact direction glyph instead of the word — the bar above already established direction semantics.
320 px · Mobile176 px @ 55%
9:41
FA
Person
Walter
Korrespondent
Herbert
⇄
↓
143
87 Walter →← 56 Herbert
W-0397
Elsbeths Kommentar
→B.Lichterfelde
2. Sep
H-0213
Antwort zur Herbstlieferung
←Leipzig
29. Aug
Ansichtskarte
←Thür. Wald✉
20. Aug
768 px · Tablet422 px @ 55%
…/briefwechsel?senderId=&receiverId=
Familienarchiv
Person
Walter de Gruyter
Korrespondent
Herbert Cram
⇄ Tauschen
Newest ↓
▾ Filter
143 Briefe
87 von Walter de Gruyter →← 56 von Herbert Cram
W-0397 – 2. September 1923
von Elsbeth geschriebener Kommentar
→Walter an HerbertVerlag
2. Sep 1923
vor 102 J.
H-0213 – 29. August 1923 – Leipzig
Antwort auf Walters Anfrage zur Herbstauslieferung
←Herbert an WalterVerlag
29. Aug 1923
vor 102 J.
Ansichtskarte – 20. August 1923
Urlaubsgruß aus Thüringen
←Herbert an Walter✉ Postkarte
20. Aug 1923
vor 102 J.
1440 px · Desktop720 px @ 55%
familienarchiv.de/briefwechsel?senderId=…&receiverId=…
Familienarchiv
Person
Walter de Gruyter
Korrespondent
Herbert Cram
⇄ Tauschen
Newest ↓
▾ Filter
143 Briefe im Zeitraum
87 von Walter de Gruyter →← 56 von Herbert Cram
W-0397 – 2. September 1923 – B.Lichterfelde
von Elsbeth geschriebener Kommentar, den Herbert zum Brief erzählte
→Walter an Herbert·📍 B.Lichterfelde·Verlag
2. September 1923
vor 102 Jahren
H-0213 – 29. August 1923 – Leipzig
Antwort auf Walters Anfrage zur Herbstauslieferung
←Herbert an Walter·📍 Leipzig·Verlag
29. August 1923
vor 102 Jahren
Ansichtskarte – 20. August 1923 – Thüringer Wald
Urlaubsgruß, kurze Notiz über Wetter und geplante Rückkehr
←Herbert an Walter·📍 Thüringer Wald·✉ Postkarte
20. August 1923
vor 102 Jahren
Distribution bar — only renders when both `senderId` and `receiverId` are set.

- Labels are right/left-aligned matching the bar direction (out on left, in on right). Bar widths come from backend-calculated counts, not percentages on the client.
- `role="img"` with a descriptive `aria-label` — screen readers hear the full distribution in one sentence.
- Below 320 px: labels stack vertically with a 4 px gap. Never truncate a count.
- In meta line, direction word collapses to glyph ("→ / ←") because the distribution bar above has already named the parties.

03Loading · Skeleton (all three viewports render the same pattern)
SSR renders without thumbnails. While thumbnails are fetching, show a paper-coloured skeleton in the thumbnail cell. Title, summary and meta remain as normal text (the data is already present). No spinner, no pulse on the text — only the thumbnail shimmers.
320 px · Mobile176 px @ 55%
9:41
FA
Person
Walter
Korresp.
alle
↓
851
19235
W-0397
Elsbeths Kommentar
→H. Cram
2. Sep
W-0396
→H. Cram
2. Sep
768 px · Tablet422 px @ 55%
…/briefwechsel
Familienarchiv
Person
Walter de Gruyter
Korresp.
Alle
Newest ↓
851 Briefe
19235 Briefe
W-0397 – 2. September 1923
Elsbeths Kommentar
→Herbert Cram
2. Sep 1923
W-0396 – 2. September 1923
→Herbert Cram
2. Sep 1923
1440 px · Desktop720 px @ 55%
familienarchiv.de/briefwechsel
Familienarchiv
Person
Walter de Gruyter
Korrespondent
Alle
Newest ↓
▾ Filter
851 Briefe
19235 Briefe
W-0397 – 2. September 1923 – B.Lichterfelde
von Elsbeth geschriebener Kommentar, den Herbert zum Brief erzählte
→ ausgehendan Herbert Cram
2. September 1923
W-0396 – 2. September 1923 – B.Lichterfelde
→ ausgehendan Herbert Cram
2. September 1923
04Empty · No results matching current filters
Filter combination returns zero letters. Empty card sits below the filter card. Primary use case: date range that excludes all letters. Message gives the user a clear reset path.
320 px · Mobile176 px @ 55%
9:41
FA
Person
Walter
Korresp.
alle
↓
0
Keine Briefe
Für diesen Filter gibt es keine Einträge. Zeitraum anpassen oder Filter zurücksetzen.
768 px · Tablet422 px @ 55%
…/briefwechsel?from=1950&to=1960
Familienarchiv
Person
Walter de Gruyter
Korresp.
Alle
Newest ↓
▾ Filter
0 Briefe
Keine Briefe in diesem Zeitraum
Von 1950 bis 1960 gibt es keine Korrespondenz. Zeitraum erweitern oder Filter zurücksetzen.
1440 px · Desktop720 px @ 55%
familienarchiv.de/briefwechsel?from=1950&to=1960
Familienarchiv
Person
Walter de Gruyter
Korrespondent
Alle
Newest ↓
▾ Filter
0 Briefe
Keine Briefe in diesem Zeitraum
Von 1950 bis 1960 gibt es keine Korrespondenz mit Walter de Gruyter. Passe den Zeitraum an oder setze die Filter zurück.
05Single-person hint — reminder to narrow
Already shown in production. Stays exactly as is. Re-rendered here so developers confirm it still renders above the first year divider when only `senderId` is set. Not shown in bilateral mode.
Kein Redesign. Die bestehende `SinglePersonHintBar.svelte` bleibt unverändert und rendert zwischen Filter-Card und erster Jahres-Trennlinie. Nur in Single-Person-Modus, nicht bilateral.
Implementation Reference — Content Stateslist rendering + skeleton

Element | Classes | Real | Note
--- | --- | --- | ---
Skeleton thumb | `animate-pulse bg-gradient-to-r from-[#f5f4ef] via-[#eceae4] to-[#f5f4ef] rounded-[1px]` | shimmer 1.4 s | Applied only to `.bw-thumb`, never to text
Empty card | `flex flex-col items-center justify-center rounded-sm border border-line bg-muted py-24 text-center shadow-sm` | padding 96 px y | Matches production empty state
Empty title | `font-serif text-ink` | 18 px desktop | Paraglide: `m.conv_no_results_heading()`
Empty body | `mt-2 text-sm text-ink-3 max-w-prose mx-auto` | 14 px | Paraglide: `m.conv_no_results_text()`
Distribution bar | `flex flex-col gap-1 border-b border-line bg-muted px-[18px] py-2` | role="img" | aria-label: "Briefverteilung: X von A, Y von B"
Distbar labels | `flex justify-between text-sm font-bold` · `.out text-primary` · `.in text-accent` | 14 px / 700 | Counts in `tabular-nums`
Distbar bar | `flex h-[5px] overflow-hidden rounded-full bg-line` | 5 px | Segments animated with `transition-[width]`

## 03Row Anatomy · Close-Ups at ~100% Scale

Four row types at near-real pixel sizes. These are the reference renderings developers check against when implementing `ConversationTimeline.svelte` (or its successor `ThumbnailRow.svelte`).

Type A · Portrait letter with summary + tags
W-0397 – 2. September 1923 – B.Lichterfelde
von Elsbeth geschriebener Kommentar, den Herbert zum Brief erzählte — Notiz auf der Rückseite
→ ausgehendan Herbert Cram·📍 B.Lichterfelde·VerlagFamilie
2. September 1923
vor 102 Jahren
Type A — Portrait Letter with Summaryrendered from Document + thumbnail URL

Part | Classes | Real | Note
--- | --- | --- | ---
Row container | `group grid grid-cols-[104px_1fr_auto] gap-5 items-center px-5 py-[14px] min-h-[128px] border-b border-line-2 border-l-[3px] border-l-primary transition-colors hover:bg-muted` | 128 px min | `<a href="/documents/{id}">` · keyboard reachable
Thumbnail cell | `w-[104px] h-[120px] flex items-center justify-center shrink-0` | 104 × 120 | Centers any aspect ratio
Thumbnail img | `w-[82px] h-[106px] rounded-[1px] shadow-sm ring-1 ring-white/80 transition-transform group-hover:-translate-y-[1px] group-hover:shadow-md` | 82 × 106 portrait | `loading="lazy"` · `alt=""` (decorative, title covers meaning)
Title | `font-serif text-base font-bold text-ink leading-[1.35] truncate` | 16 px / 700 | Merriweather Bold
Summary | `font-serif italic text-sm text-ink-2 leading-[1.55] line-clamp-2` | 14 px italic | Omit element entirely when `doc.summary` is empty — no placeholder
Summary quote marks | `::before` & `::after` pseudos, color `text-accent` | 22 px | „…" (German curly quotes)
Meta row | `mt-0.5 flex flex-wrap gap-x-3 gap-y-1 text-xs text-ink-3 items-center` | 12 px | Separators use `·` with `text-line`
Direction chip | `text-[13px] font-extrabold text-primary` (out) · `text-accent` (in) | 13 px / 800 | "→ ausgehend" / "← eingehend" (word omitted in bilateral mode)
Tag chip | `inline-flex items-center text-[10px] font-bold bg-accent/80 text-primary px-[7px] py-0.5 rounded-full` | 10 px / 700 | Max 2 tags visible at 1440; 1 at 768; 0 at 320
Right column — date | `font-serif text-sm font-bold text-ink-2 whitespace-nowrap text-right` | 14 px / 700 | Intl.DateTimeFormat de-DE (see CLAUDE.md)
Right column — relative | `text-[10px] text-ink-3 font-semibold` | 10 px | "vor X Jahren" — calculated client-side

Type B · Portrait letter without summary (clean variant)
W-0396 – 2. September 1923 – B.Lichterfelde
→ ausgehendan Herbert Cram·📍 B.Lichterfelde
2. September 1923
vor 102 Jahren
No placeholder when summary is missing. The summary element is not rendered at all — row height still hits `min-h-[128px]` so the list stays rhythmic. Tags are also omitted when empty (no empty chip row).
Type C · Postcard · landscape thumbnail with stamp + postmark
Ansichtskarte – 20. August 1923 – Thüringer Wald
Urlaubsgruß, kurze Notiz über Wetter und geplante Rückkehr
← eingehendvon Herbert Cram·📍 Thüringer Wald·✉ Postkarte
20. August 1923
vor 102 Jahren
Type C — Postcard (landscape)aspect ratio detection + kind chip

Part | Classes | Real | Note
--- | --- | --- | ---
Thumbnail | `w-[104px] h-[72px] rounded-[1px] shadow-sm ring-1 ring-white/80` | 104 × 72 landscape | Aspect ratio detected server-side from PDF page 1 dimensions (w/h > 1.1 → landscape)
Kind chip | `inline-flex items-center text-[10px] font-bold uppercase tracking-wide bg-line text-ink-2 px-[7px] py-0.5 rounded-full` | 10 px / 700 uppercase | Paraglide: `m.doc_kind_postcard()` — shown only when thumbnail is landscape
Stamp corner | CSS pseudo-element on thumbnail — 16×18 px gradient square top-right 5 px | decorative | In production: rendered by the thumbnail service as part of the real scan; the CSS is only for spec rendering

Type D · Multi-page letter with "N Seiten" badge
4 S.
W-0524 – 31. Juli 1923 – Berlin
Glückwunsch zum 60. Geburtstag, Bericht über den Verlag und den anstehenden Umzug nach B.Lichterfelde
→ ausgehendan Walter Dieckmann·📍 Berlin·GeburtstagVerlag
31. Juli 1923
vor 102 Jahren
Type D — Page-count Badgeonly when pages > 1

Part | Classes | Real | Note
--- | --- | --- | ---
Badge container | `absolute top-1 -right-1 bg-primary text-primary-fg text-[10px] font-bold px-[7px] py-0.5 rounded-full ring-2 ring-white` | 10 px / 700 | Overlaps the thumbnail by 4 px right
Label | Paraglide: `m.doc_pages_count({ count })` | "4 S." | Abbreviated form for the badge; full "4 Seiten" appears in the document detail page
Visibility rule | Render `{#if doc.pageCount > 1}` | — | Never show "1 S."

## 04Distribution Bar · Close-Up

Only rendered in bilateral mode (both `senderId` and `receiverId` set). This component already exists in production as part of `ConversationTimeline.svelte` — this spec keeps its API and visual treatment identical but moves it out of the timeline header into a standalone component above the row list, so it can sit between the filter card and the year dividers.

Distribution bar · bilateral Walter ↔ Herbert
87 von Walter de Gruyter → ← 56 von Herbert Cram
Distribution Barrole="img" + aria-label carries the data

Part | Classes | Real | Note
--- | --- | --- | ---
Wrapper | `flex flex-col gap-1 border-b border-line bg-muted px-[18px] py-2` | 8 px y padding | `role="img"` · `aria-label` describes full distribution
Out label | `inline-flex items-center gap-1 text-primary text-sm font-bold tabular-nums` | 14 px / 700 | Format: "{count} von {sender} →"
In label | `inline-flex items-center gap-1 text-accent text-sm font-bold tabular-nums` | 14 px / 700 | Format: "← {count} von {receiver}"
Bar | `flex h-[5px] overflow-hidden rounded-full bg-line` | 5 px tall | Segments use `transition-[width] duration-300 ease-out`
Out segment | `bg-primary h-full` | width from API | Percentage computed backend-side from counts
In segment | `bg-accent h-full` | complementary | Never use `100% - out`; both come from the API separately
Mobile (320 px) | Labels stack with `flex-col gap-1`; bar stays full-width | — | No truncation of counts — numbers must always be legible

## 05Accessibility Contract · WCAG AA/AAA

Every colour pair on the rendered row has been measured. AAA where reasonably achievable; AA is the floor. The row is a link, not a button — keyboard navigation is native `tab`-through-list semantics.

Light Mode — Contrast Verificationlayout.css tokens

Pair | Value | Ratio | WCAG
--- | --- | --- | ---
Title (ink on surface) | `#1A1A1A on #ffffff` | 19.6:1 | AAA ✓
Summary (ink-2 on surface) | `#444444 on #ffffff` | 9.7:1 | AAA ✓ (body)
Meta (ink-3 on surface) | `#666666 on #ffffff` | 5.7:1 | AA ✓
Direction out (primary on surface) | `#002850 on #ffffff` | 14.5:1 | AAA ✓
Direction in (accent on surface) | `#2F9E95 on #ffffff` | 4.6:1 | AA ✓ (normal)
Tag chip (primary on mint) | `#002850 on #a6dad8` | 8.1:1 | AAA ✓
Quote marks (accent on surface) | `#a6dad8 decorative` | n/a | Decorative — summary text carries meaning
Focus ring (primary on surface) | `#002850 on #ffffff, 2px offset` | 14.5:1 | AAA ✓

Dark Mode — Contrast Verificationremap via data-theme="dark"

Pair | Value | Ratio | WCAG
--- | --- | --- | ---
Title (ink on surface-dark) | `#f0efe9 on #011a30` | 15.1:1 | AAA ✓
Summary (ink-2 on surface-dark) | `#c5cbd4 on #011a30` | 11.2:1 | AAA ✓
Meta (ink-3 on surface-dark) | `#9ca3af on #011a30` | 7.8:1 | AAA ✓ (body)
Direction out (mint on canvas) | `#a1dcd8 on #010e1e` | 9.6:1 | AAA ✓
Direction in (turquoise on canvas) | `#00c7b1 on #010e1e` | 6.8:1 | AA ✓
Tag chip (turquoise on tint) | `#00c7b1 on rgba(0,199,177,.2)` | 6.3:1 | AA ✓

Non-negotiable accessibility rules.

- Row is rendered as `<a href="/documents/{id}">` — never `<div onclick>`. Keyboard Tab enters, Enter opens, Shift-Tab leaves.
- Focus ring: `focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2` — always visible on keyboard focus, never on mouse click.
- Thumbnail `<img alt="">` — empty alt because the title next to it names the letter. A descriptive alt would be announced twice.
- Direction glyph is color and shape (arrow direction). Never rely on color alone — the arrow "→" vs "←" carries meaning even in monochrome.
- Distribution bar uses `role="img"` with a full-sentence aria-label. Screen readers hear the whole distribution in one announcement, not each half.
- Minimum body text 14 px; minimum meta text 12 px. Never below 12 px for any visible text.
- Touch target: 128 px row height ≫ 44 px WCAG minimum. Comfortable for senior users on phones.
- `prefers-reduced-motion`: hover lift on thumbnail collapses to `transition-duration: 0.01ms`. Required (project CLAUDE.md + WCAG 2.3.3).

## 06Implementation Notes — Data, Thumbnails, Routing

Data contract — fields read per row/api/documents/conversation

Field | From | Used for | Fallback
--- | --- | --- | ---
`id` | Document | Row key, href | required
`title` | Document | Row title | `originalFilename`
`summary` | Document | Quote line (omit when empty) | element not rendered
`documentDate` | Document | Year group, right-column date, relative time | "—" placeholder, year group "Ohne Datum"
`location` | Document | Meta line | hidden
`sender` / `receivers` | Document | Direction + counterpart name | direction omitted, name = `m.conv_no_party()`
`tags[]` | Document | Meta line (max 2 at 1440, 1 at 768, 0 at 320) | no chips rendered
`pageCount` | Document (new, from thumbnail service) | Badge when `> 1` | no badge
`thumbnailUrl` | Document (new, from thumbnail service) | `<img src>` | skeleton until fetched
`thumbnailAspect` | Document (new, from thumbnail service) | portrait / landscape class | defaults to portrait

Thumbnail service — new endpointsdepends on open issue "thumbnail generation"

Concern | Decision | Note
--- | --- | ---
Storage | MinIO bucket `thumbnails` | Mirrors document ID path; WEBP at 2× target resolution
URL | `/api/documents/{id}/thumbnail` | Redirects (302) to a presigned MinIO URL · `Cache-Control: public, max-age=2592000` (30 d)
Aspect | Computed once on generation, persisted as `Document.thumbnailAspect` enum `PORTRAIT \| LANDSCAPE` | Threshold w/h > 1.1 → LANDSCAPE
Page count | Persisted as `Document.pageCount` on upload / reprocess | Not computed client-side
Loading strategy | `<img loading="lazy" decoding="async">` with intersection observer for rows below the fold | Skeleton state until `onload` fires
Fallback | Paper-coloured placeholder (matches thumbnail gradient) with document icon | Never break the row layout

Component structurenew files

File | Responsibility | Replaces
--- | --- | ---
`ThumbnailRow.svelte` | Single row with thumbnail, title, summary, meta, right column | Row rendering inside `ConversationTimeline.svelte`
`DistributionBar.svelte` | The bilateral distribution bar | Lifts existing markup out of `ConversationTimeline.svelte`
`YearDivider.svelte` | Year number + Briefe count | Already exists; no change required
`ConversationTimeline.svelte` | Orchestrator · renders distribution bar + year dividers + `ThumbnailRow`s | Simplified — no longer does row markup directly
`DocumentThumbnail.svelte` | Reusable thumbnail element with lazy-load + aspect + page badge | new · also usable on /documents list pages

Shipping order.

- Phase 1 — land `ThumbnailRow`, `DistributionBar` (extracted), and new typography/spacing without real thumbnails. Thumbnail cell renders the skeleton permanently. Ship and observe.
- Phase 2 — wire up thumbnail service (open issue "PDF thumbnail generation"). Replace skeleton with real thumbnails. Add `thumbnailAspect` + `pageCount` to the `Document` entity and the `/api/documents/conversation` response.
- Phase 3 — add lazy-loading + intersection observer for rows outside viewport. Measure perf on 851-letter lists.