Files
familienarchiv/docs/specs/briefwechsel-thumbnail-rows-spec.html
Marcel dd6cbe3a6f
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m41s
CI / OCR Service Tests (push) Successful in 33s
CI / Backend Unit Tests (push) Failing after 2m48s
docs(specs): add final specs for thumbnail rows + person dashboard
Two production-ready specs following the chronik-spec format
(scaled wireframes × 3 viewports + impl-ref tables with exact Tailwind
classes and pixel values + WCAG contrast verification):

- briefwechsel-thumbnail-rows-spec.html — /briefwechsel row redesign
  with PDF thumbnail, summary-as-quote, bilateral distribution bar;
  drops status lifecycle and script-type indicators.

- person-dashboard-spec.html — new Korrespondenz-Überblick block on
  /persons/[id] with stats, activity histogram, direction split, top
  correspondents/locations, tag cloud. Every tile deep-links to
  /briefwechsel with filters.

Both specs share the DistributionBar.svelte component.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 20:17:21 +02:00

1074 lines
97 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Briefwechsel — Thumbnail Rows · Final Design Spec · Familienarchiv</title>
<style>
/* ═══════════════════════════════════════════════════════════
BRIEFWECHSEL — Thumbnail Rows
/briefwechsel row redesign: PDF thumbnail + summary-as-quote.
4 row types × 3 viewports × light/dark.
By Leonie Voss (UX/Design). 2026-04-22.
═══════════════════════════════════════════════════════════ */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Helvetica Neue',Arial,sans-serif;background:#ECEAE4;color:#1A1A1A;line-height:1.5;-webkit-font-smoothing:antialiased}
.doc{max-width:1400px;margin:0 auto;padding:48px 28px}
/* ── Masthead ──────────────────────────────────────────── */
.mast{background:#012851;border-radius:10px;padding:32px 40px;margin-bottom:40px;color:#fff}
.mast-top{display:flex;align-items:flex-start;justify-content:space-between;gap:24px;margin-bottom:14px}
.mast h1{font-size:22px;font-weight:900;letter-spacing:-.4px;margin-bottom:6px}
.mast p{font-size:12px;color:rgba(255,255,255,.55);max-width:780px;line-height:1.7}
.mast p code{background:rgba(255,255,255,.1);padding:1px 5px;border-radius:2px;font-family:monospace}
.mast-badge{font-size:9px;font-weight:800;padding:3px 9px;border-radius:20px;text-transform:uppercase;letter-spacing:.8px;flex-shrink:0;margin-top:4px;background:#a1dcd8;color:#012851}
.decisions{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-top:18px;border-top:1px solid rgba(255,255,255,.1);padding-top:14px}
.dec{background:rgba(255,255,255,.06);border-radius:6px;padding:10px 12px}
.dec-label{font-size:7.5px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:rgba(255,255,255,.4);margin-bottom:5px}
.dec-value{font-size:10px;font-weight:700;color:#fff;line-height:1.5}
.dec-value s{color:rgba(255,255,255,.3);font-weight:400}
/* ── Disclaimer ──────────────────────────────────── */
.spec-disclaimer{background:#FFFBEB;border:1px solid #FCD34D;border-radius:6px;padding:14px 18px;font-size:11.5px;color:#78350F;line-height:1.6;margin-bottom:32px}
.spec-disclaimer strong{color:#92400E}
.spec-disclaimer code{background:#FEF3C7;padding:1px 5px;border-radius:2px;font-family:monospace;font-size:10.5px}
/* ── TOC ──────────────────────────────────────────── */
.toc{background:#fff;border:1px solid #DDD8CE;border-radius:8px;padding:18px 22px;margin-bottom:40px}
.toc-t{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:#666;margin-bottom:10px}
.toc ol{list-style:none;display:grid;grid-template-columns:repeat(2,1fr);gap:6px 24px}
.toc li{font-size:12px;color:#012851;display:flex;align-items:baseline;gap:8px}
.toc li b{background:#012851;color:#fff;padding:1px 6px;border-radius:3px;font-size:9px}
.toc li span{color:#888;font-size:10.5px;margin-left:auto}
/* ── Sections ──────────────────────────────────────── */
.sec{margin-bottom:56px}
.sec+.sec{border-top:2px dashed #C8C4BE;padding-top:48px}
.sec-h{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:1.2px;color:#666;margin-bottom:20px;display:flex;align-items:center;gap:10px}
.sec-h::after{content:'';flex:1;height:1px;background:#D8D4CE}
.sec-num{background:#012851;color:#fff;font-size:9px;font-weight:900;padding:2px 7px;border-radius:10px}
.sec-intro{font-size:12.5px;color:#555;line-height:1.65;max-width:780px;margin-bottom:24px}
/* ── Wireframe chrome ──────────────────────────────── */
.wf{background:#fff;border:2px solid #B8B4AE;border-radius:10px;overflow:hidden;box-shadow:0 4px 18px rgba(0,0,0,.08)}
.wf-bar{height:22px;background:#E8E4DF;border-bottom:1px solid #C8C4BE;display:flex;align-items:center;padding:0 8px;gap:4px}
.dot{width:6px;height:6px;border-radius:50%;background:#C8C4BE;display:inline-block}
.dot.r{background:#F87171}.dot.y{background:#FCD34D}.dot.g{background:#4ADE80}
.urlbar{flex:1;height:10px;background:#D8D4CE;border-radius:3px;margin-left:6px;display:flex;align-items:center;padding:0 5px}
.urlbar span{font-size:7px;color:#888;font-family:monospace}
.wf.dark{background:#010e1e;border-color:#0d3358}
.wf.dark .wf-bar{background:#0d2240;border-bottom-color:#081a30}
.wf.dark .urlbar{background:#012851}
.wf.dark .urlbar span{color:#7a8a9a}
.wf-m{width:176px;border-radius:14px}
.wf-m .wf-bar{display:none}
.wf-m-status{height:13px;background:#012851;display:flex;align-items:center;justify-content:space-between;padding:0 10px;border-top-left-radius:12px;border-top-right-radius:12px}
.wf-m-status span{font-size:6px;color:#fff;font-weight:700;letter-spacing:.3px}
.wf-m-status .dots{display:flex;gap:2px}
.wf-m-status .dots i{width:4px;height:4px;border-radius:50%;background:rgba(255,255,255,.6)}
.wf.dark.wf-m .wf-m-status{background:#001020}
.wf-t{width:422px}
.wf-d{width:720px}
/* ── State block ──────────────────────────────────── */
.state-block{background:#fff;border:1px solid #DDD8CE;border-radius:10px;padding:22px;margin-bottom:20px}
.state-hdr{display:flex;align-items:baseline;gap:12px;margin-bottom:4px}
.state-num{background:#012851;color:#fff;font-size:9px;font-weight:900;padding:3px 7px;border-radius:10px;flex-shrink:0}
.state-title{font-size:14px;font-weight:800;color:#012851}
.state-desc{font-size:11.5px;color:#555;line-height:1.6;margin-bottom:18px}
.state-vps{display:grid;gap:20px;grid-template-columns:min-content min-content 1fr;align-items:flex-start}
.state-vp-col{display:flex;flex-direction:column;gap:6px;align-items:center}
.vp-tag{font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:1.2px;color:#fff;background:#012851;padding:3px 8px;border-radius:3px}
.vp-tag.dark{background:#000}
.vp-dim{font-size:8px;color:#888;font-family:monospace;margin-top:-2px}
/* ── Page content (scaled ~55%) ─────────────────── */
.bw{background:#ECEAE4;font-family:'Helvetica Neue',Arial,sans-serif;padding:0;color:#012851}
.bw.dark{background:#010e1e;color:#f0efe9}
.bw-gh{height:18px;background:#012851;display:flex;align-items:center;padding:0 10px;gap:8px;border-bottom:1px solid #a1dcd8}
.bw-gh-logo{font-size:6px;font-weight:900;color:#fff;letter-spacing:.12em;text-transform:uppercase}
.bw-gh-nav{display:flex;gap:8px;margin-left:auto}
.bw-gh-nav span{font-size:5.5px;font-weight:600;color:rgba(255,255,255,.55);letter-spacing:.04em}
.bw-gh-nav span.on{color:#fff;border-bottom:1px solid #a1dcd8;padding-bottom:1px}
.bw-wrap{padding:10px 10px 14px;max-width:100%}
.wf-t .bw-wrap{padding:14px 18px 18px}
.wf-d .bw-wrap{padding:18px 28px 22px}
/* Filter card */
.bw-fc{background:#fff;border:1px solid #e4e2d7;border-radius:2px;padding:8px 10px;margin-bottom:10px}
.bw.dark .bw-fc{background:#011a30;border-color:#0d3358}
.wf-t .bw-fc{padding:12px 14px;margin-bottom:14px}
.wf-d .bw-fc{padding:14px 18px;margin-bottom:16px}
.bw-fc-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.wf-m .bw-fc-grid{grid-template-columns:1fr;gap:6px}
.bw-fl{font-size:5px;font-weight:800;color:#888;text-transform:uppercase;letter-spacing:.8px;margin-bottom:2px}
.wf-t .bw-fl{font-size:7px}
.wf-d .bw-fl{font-size:8px}
.bw-fi{height:16px;border:1px solid #e4e2d7;background:#fff;border-radius:2px;padding:0 6px;font-size:6px;color:#012851;display:flex;align-items:center}
.bw.dark .bw-fi{background:#011526;border-color:#0d3358;color:#f0efe9}
.bw-fi.empty{color:#b9b6ad;font-style:italic}
.bw.dark .bw-fi.empty{color:#5e6d7e}
.wf-t .bw-fi{height:24px;font-size:9px;padding:0 10px}
.wf-d .bw-fi{height:28px;font-size:10px;padding:0 10px}
.bw-fa{display:flex;align-items:center;gap:4px;margin-top:6px}
.wf-t .bw-fa{gap:6px;margin-top:8px}
.wf-d .bw-fa{gap:6px;margin-top:10px}
.bw-btn{height:14px;background:#f7f5f2;border:1px solid #e4e2d7;border-radius:2px;padding:0 5px;font-size:5px;font-weight:800;color:#444;text-transform:uppercase;letter-spacing:.4px;display:inline-flex;align-items:center;gap:2px}
.bw.dark .bw-btn{background:#0d2240;border-color:#0d3358;color:#d0d6de}
.wf-t .bw-btn{height:22px;font-size:8px;padding:0 8px}
.wf-d .bw-btn{height:26px;font-size:9px;padding:0 10px}
.bw-count{margin-left:auto;font-size:5.5px;color:#555}
.bw.dark .bw-count{color:#9ca3af}
.bw-count b{color:#012851;font-weight:800}
.bw.dark .bw-count b{color:#f0efe9}
.wf-t .bw-count{font-size:8.5px}
.wf-d .bw-count{font-size:10px}
/* Hint bar */
.bw-hint{margin-top:4px;background:#e9f5f4;border:1px solid #c8e6e4;border-radius:2px;padding:3px 5px;font-size:5.5px;color:#1a3e3c}
.bw.dark .bw-hint{background:rgba(166,218,216,.1);border-color:rgba(166,218,216,.2);color:#a1dcd8}
.wf-t .bw-hint{margin-top:8px;padding:6px 10px;font-size:8.5px}
.wf-d .bw-hint{margin-top:10px;padding:8px 12px;font-size:9.5px}
.bw-hint b{font-weight:800}
/* Distribution bar */
.bw-distbar{background:#F2F0E8;border:1px solid #e4e2d7;border-bottom:0;padding:5px 8px;display:flex;flex-direction:column;gap:3px;border-radius:2px 2px 0 0}
.bw.dark .bw-distbar{background:rgba(166,218,216,.06);border-color:#0d3358}
.wf-t .bw-distbar{padding:8px 14px;gap:5px}
.wf-d .bw-distbar{padding:10px 18px;gap:6px}
.bw-distbar-labels{display:flex;justify-content:space-between;font-size:6px;font-weight:700}
.wf-t .bw-distbar-labels{font-size:9px}
.wf-d .bw-distbar-labels{font-size:11px}
.bw-distbar-labels .out{color:#012851}
.bw-distbar-labels .in{color:#2F9E95}
.bw.dark .bw-distbar-labels .out{color:#a1dcd8}
.bw.dark .bw-distbar-labels .in{color:#f0efe9}
.bw-distbar-bar{height:3px;display:flex;border-radius:2px;overflow:hidden;background:#E4E2D7}
.bw.dark .bw-distbar-bar{background:#0d3358}
.wf-t .bw-distbar-bar{height:5px}
.wf-d .bw-distbar-bar{height:6px}
.bw-distbar-bar .out{background:#012851}
.bw-distbar-bar .in{background:#2F9E95}
.bw.dark .bw-distbar-bar .out{background:#a1dcd8}
.bw.dark .bw-distbar-bar .in{background:#00c7b1}
/* Year divider */
.bw-yd{background:#F2F0E8;border-top:1px solid #e4e2d7;border-bottom:1px solid #e4e2d7;padding:3px 8px;display:flex;align-items:baseline;gap:5px}
.bw.dark .bw-yd{background:rgba(255,255,255,.03);border-color:#0d3358}
.wf-t .bw-yd{padding:6px 14px;gap:8px}
.wf-d .bw-yd{padding:8px 18px;gap:10px}
.bw-yd-y{font-size:10px;font-weight:900;color:#012851;letter-spacing:-.3px}
.bw.dark .bw-yd-y{color:#f0efe9}
.wf-t .bw-yd-y{font-size:14px}
.wf-d .bw-yd-y{font-size:18px}
.bw-yd-n{font-size:5.5px;font-weight:800;color:#888}
.wf-t .bw-yd-n{font-size:8px}
.wf-d .bw-yd-n{font-size:10px}
/* List row */
.bw-rlist{background:#fff;border:1px solid #e4e2d7;border-radius:2px;overflow:hidden}
.bw-rlist.below-distbar{border-radius:0 0 2px 2px}
.bw.dark .bw-rlist{background:#011a30;border-color:#0d3358}
.bw-row{display:grid;grid-template-columns:36px 1fr auto;column-gap:6px;align-items:center;padding:5px 8px;border-bottom:1px solid #f1ede3;border-left:2px solid transparent}
.bw.dark .bw-row{border-bottom-color:#092843}
.bw-row.out{border-left-color:#012851}
.bw-row.in{border-left-color:#2F9E95}
.bw.dark .bw-row.in{border-left-color:#00c7b1}
.wf-t .bw-row{grid-template-columns:56px 1fr auto;column-gap:12px;padding:9px 14px}
.wf-d .bw-row{grid-template-columns:82px 1fr auto;column-gap:16px;padding:12px 20px}
/* Thumbnail */
.bw-thumb-cell{display:flex;align-items:center;justify-content:center;position:relative;width:100%;height:44px}
.wf-t .bw-thumb-cell{height:72px}
.wf-d .bw-thumb-cell{height:104px}
.bw-thumb{background:linear-gradient(180deg,#fdfcf7,#f4efdf);border:1px solid #d9d4c6;box-shadow:0 1px 2px rgba(0,0,0,.06),inset 0 0 0 1px #fff;position:relative;overflow:hidden;border-radius:1px}
.bw.dark .bw-thumb{background:linear-gradient(180deg,#1a2838,#0f1a28);border-color:#0d3358}
.bw-thumb.portrait{width:28px;height:38px}
.bw-thumb.landscape{width:40px;height:28px}
.wf-t .bw-thumb.portrait{width:48px;height:62px}
.wf-t .bw-thumb.landscape{width:62px;height:44px}
.wf-d .bw-thumb.portrait{width:72px;height:94px}
.wf-d .bw-thumb.landscape{width:94px;height:66px}
.bw-thumb-lines{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;gap:1.5px;padding:18% 10%}
.wf-t .bw-thumb-lines{gap:2.5px;padding:16% 10%}
.wf-d .bw-thumb-lines{gap:3.5px;padding:14% 10%}
.bw-thumb-lines i{display:block;height:.6px;background:rgba(24,40,70,.35)}
.wf-t .bw-thumb-lines i{height:1px}
.wf-d .bw-thumb-lines i{height:1.3px}
.bw.dark .bw-thumb-lines i{background:rgba(161,220,216,.5)}
.bw-thumb.kurrent .bw-thumb-lines i{transform:rotate(-.5deg)}
.bw-thumb.kurrent .bw-thumb-lines i:nth-child(3n){width:65%}
.bw-thumb.kurrent .bw-thumb-lines i:nth-child(4n){width:90%}
.bw-thumb.kurrent .bw-thumb-lines i:nth-child(5n){width:48%;transform:rotate(.4deg)}
.bw-thumb.typed .bw-thumb-lines i:nth-child(odd){width:92%}
.bw-thumb.typed .bw-thumb-lines i:nth-child(even){width:86%}
.bw-thumb.typed .bw-thumb-lines i:nth-child(7n){width:45%}
.bw-thumb.postcard::after{content:'';position:absolute;top:2px;right:2px;width:6px;height:6px;background:linear-gradient(135deg,#b6c9d3,#8ba9b6);border:.5px dashed rgba(0,0,0,.15)}
.wf-t .bw-thumb.postcard::after{width:10px;height:12px;top:4px;right:4px}
.wf-d .bw-thumb.postcard::after{width:14px;height:16px;top:5px;right:5px}
.bw-thumb.postcard::before{content:'';position:absolute;top:3px;right:10px;width:5px;height:5px;border:1px solid rgba(150,30,30,.4);border-radius:50%}
.wf-t .bw-thumb.postcard::before{width:9px;height:9px;top:6px;right:18px;border-width:1px}
.wf-d .bw-thumb.postcard::before{width:12px;height:12px;top:8px;right:24px;border-width:1.5px}
.bw-thumb-badge{position:absolute;top:-2px;right:-3px;background:#012851;color:#fff;font-size:5px;font-weight:800;padding:0 3px;border-radius:6px;box-shadow:0 0 0 1px #fff}
.bw.dark .bw-thumb-badge{background:#a1dcd8;color:#012851;box-shadow:0 0 0 1px #010e1e}
.wf-t .bw-thumb-badge{font-size:7px;padding:1px 4px;top:0;right:-4px}
.wf-d .bw-thumb-badge{font-size:9px;padding:1px 5px;top:2px;right:-4px}
/* Row body */
.bw-rb{min-width:0;display:flex;flex-direction:column;gap:1px}
.wf-t .bw-rb{gap:3px}
.wf-d .bw-rb{gap:4px}
.bw-title{font-family:Georgia,'Tinos',serif;font-size:7px;font-weight:700;color:#012851;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bw.dark .bw-title{color:#f0efe9}
.wf-t .bw-title{font-size:11px}
.wf-d .bw-title{font-size:14px}
.bw-summary{font-family:Georgia,serif;font-size:6px;color:#444;line-height:1.4;font-style:italic;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.bw.dark .bw-summary{color:#c5cbd4}
.wf-t .bw-summary{font-size:9.5px}
.wf-d .bw-summary{font-size:12px}
.bw-summary::before{content:'„';color:#a1dcd8;font-size:9px;font-weight:700;line-height:0;position:relative;top:3px;margin-right:1px}
.bw-summary::after{content:'"';color:#a1dcd8;font-size:9px;font-weight:700;line-height:0;position:relative;top:3px;margin-left:1px}
.wf-t .bw-summary::before, .wf-t .bw-summary::after{font-size:14px;top:5px}
.wf-d .bw-summary::before, .wf-d .bw-summary::after{font-size:18px;top:6px}
.bw-meta{display:flex;flex-wrap:wrap;gap:2px 5px;font-size:5.5px;color:#555;align-items:center}
.bw.dark .bw-meta{color:#9ca3af}
.wf-t .bw-meta{font-size:8px;gap:3px 8px}
.wf-d .bw-meta{font-size:10px;gap:3px 10px}
.bw-meta .sep{color:#ccc}
.bw.dark .bw-meta .sep{color:#3e4a5a}
.bw-meta .dir{color:#012851;font-weight:800}
.bw-meta .dir.in{color:#2F9E95}
.bw.dark .bw-meta .dir{color:#a1dcd8}
.bw.dark .bw-meta .dir.in{color:#00c7b1}
.bw-kind{background:#E4E2D7;color:#555;font-size:5px;font-weight:800;padding:.5px 3px;border-radius:5px;text-transform:uppercase;letter-spacing:.3px}
.bw.dark .bw-kind{background:#0d3358;color:#a1dcd8}
.wf-t .bw-kind{font-size:7px;padding:1px 5px}
.wf-d .bw-kind{font-size:9px;padding:1px 6px}
.bw-tag{background:#a1dcd8;color:#012851;font-size:5px;font-weight:700;padding:.5px 3px;border-radius:5px}
.bw.dark .bw-tag{background:rgba(0,199,177,.2);color:#00c7b1}
.wf-t .bw-tag{font-size:7px;padding:1px 5px}
.wf-d .bw-tag{font-size:9px;padding:1px 6px}
/* Row right */
.bw-rr{display:flex;flex-direction:column;align-items:flex-end;gap:1px;text-align:right}
.bw-date{font-family:Georgia,serif;font-size:6px;color:#444;font-weight:700;white-space:nowrap}
.bw.dark .bw-date{color:#d0d6de}
.wf-t .bw-date{font-size:9px}
.wf-d .bw-date{font-size:11px}
.bw-date-rel{font-size:5px;color:#888;font-weight:600}
.bw.dark .bw-date-rel{color:#6e7a8a}
.wf-t .bw-date-rel{font-size:7.5px}
.wf-d .bw-date-rel{font-size:9px}
/* Skeleton */
.sk{background:linear-gradient(90deg,#f5f4ef,#eceae4,#f5f4ef);border-radius:1px;animation:shimmer 1.4s infinite}
.bw.dark .sk{background:linear-gradient(90deg,#011a30,#011526,#011a30)}
@keyframes shimmer{0%{background-position:-200px 0}100%{background-position:200px 0}}
/* Empty state */
.bw-empty{background:#fff;border:1px solid #e4e2d7;border-radius:2px;padding:40px 14px;text-align:center;display:flex;flex-direction:column;align-items:center;gap:4px}
.bw.dark .bw-empty{background:#011a30;border-color:#0d3358}
.wf-t .bw-empty{padding:60px 20px;gap:6px}
.wf-d .bw-empty{padding:80px 40px;gap:10px}
.bw-empty-t{font-family:Georgia,serif;font-size:7px;font-weight:700;color:#012851}
.bw.dark .bw-empty-t{color:#f0efe9}
.wf-t .bw-empty-t{font-size:13px}
.wf-d .bw-empty-t{font-size:17px}
.bw-empty-b{font-size:5px;color:#555;line-height:1.5;max-width:140px}
.bw.dark .bw-empty-b{color:#9ca3af}
.wf-t .bw-empty-b{font-size:8.5px;max-width:260px}
.wf-d .bw-empty-b{font-size:11px;max-width:360px}
/* ── Close-ups at ~100% scale ──────────────────── */
.cu{background:#fff;border:1px solid #e4e2d7;border-radius:6px;padding:24px 28px;margin-bottom:20px}
.cu.dark{background:#011526;border-color:#0d3358}
.cu-t{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#666;margin-bottom:14px}
.cu.dark .cu-t{color:#8b97a5}
.cu-row{display:grid;grid-template-columns:104px 1fr auto;column-gap:20px;align-items:center;padding:14px 20px;background:#fff;border:1px solid #e4e2d7;border-left:3px solid #012851;border-radius:2px;position:relative}
.cu.dark .cu-row{background:#011526;border-color:#0d3358}
.cu-row.in{border-left-color:#2F9E95}
.cu.dark .cu-row.in{border-left-color:#00c7b1}
.cu-thumb-cell{width:104px;height:120px;display:flex;align-items:center;justify-content:center;position:relative}
.cu-thumb{background:linear-gradient(180deg,#fdfcf7,#f4efdf);border:1px solid #d9d4c6;box-shadow:0 1px 3px rgba(0,0,0,.08),inset 0 0 0 1px #fff;position:relative;overflow:hidden;border-radius:1px}
.cu.dark .cu-thumb{background:linear-gradient(180deg,#1a2838,#0f1a28);border-color:#0d3358}
.cu-thumb.portrait{width:82px;height:106px}
.cu-thumb.landscape{width:104px;height:72px}
.cu-thumb-lines{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;gap:3.5px;padding:14% 10%}
.cu-thumb-lines i{display:block;height:1.3px;background:rgba(24,40,70,.4)}
.cu.dark .cu-thumb-lines i{background:rgba(161,220,216,.5)}
.cu-thumb.kurrent .cu-thumb-lines i{transform:rotate(-.5deg)}
.cu-thumb.kurrent .cu-thumb-lines i:nth-child(3n){width:65%}
.cu-thumb.kurrent .cu-thumb-lines i:nth-child(4n){width:90%}
.cu-thumb.kurrent .cu-thumb-lines i:nth-child(5n){width:48%;transform:rotate(.4deg)}
.cu-thumb.typed .cu-thumb-lines i:nth-child(odd){width:92%}
.cu-thumb.typed .cu-thumb-lines i:nth-child(even){width:86%}
.cu-thumb.typed .cu-thumb-lines i:nth-child(7n){width:45%}
.cu-thumb.postcard::after{content:'';position:absolute;top:5px;right:5px;width:16px;height:18px;background:linear-gradient(135deg,#b6c9d3,#8ba9b6);border:1px dashed rgba(0,0,0,.15)}
.cu-thumb.postcard::before{content:'';position:absolute;top:8px;right:26px;width:14px;height:14px;border:1.5px solid rgba(150,30,30,.4);border-radius:50%}
.cu-thumb-badge{position:absolute;top:4px;right:-4px;background:#012851;color:#fff;font-size:10px;font-weight:800;padding:2px 7px;border-radius:10px;box-shadow:0 0 0 2px #fff}
.cu.dark .cu-thumb-badge{background:#a1dcd8;color:#012851;box-shadow:0 0 0 2px #010e1e}
.cu-body{min-width:0;display:flex;flex-direction:column;gap:4px}
.cu-title{font-family:Georgia,'Tinos',serif;font-size:16px;font-weight:700;color:#012851;line-height:1.35;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cu.dark .cu-title{color:#f0efe9}
.cu-summary{font-family:Georgia,serif;font-size:14px;color:#444;line-height:1.55;font-style:italic;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.cu.dark .cu-summary{color:#c5cbd4}
.cu-summary::before{content:'„';color:#a1dcd8;font-size:22px;font-weight:700;line-height:0;position:relative;top:6px;margin-right:2px}
.cu-summary::after{content:'"';color:#a1dcd8;font-size:22px;font-weight:700;line-height:0;position:relative;top:6px;margin-left:2px}
.cu-meta{display:flex;flex-wrap:wrap;gap:4px 12px;font-size:12px;color:#555;align-items:center;margin-top:2px}
.cu.dark .cu-meta{color:#9ca3af}
.cu-meta .sep{color:#ccc}
.cu-meta .dir{color:#012851;font-weight:800;font-size:13px}
.cu-meta .dir.in{color:#2F9E95}
.cu.dark .cu-meta .dir{color:#a1dcd8}
.cu.dark .cu-meta .dir.in{color:#00c7b1}
.cu-kind{background:#E4E2D7;color:#555;font-size:10px;font-weight:800;padding:2px 7px;border-radius:10px;text-transform:uppercase;letter-spacing:.3px}
.cu.dark .cu-kind{background:#0d3358;color:#a1dcd8}
.cu-tag{background:#a1dcd8;color:#012851;font-size:10px;font-weight:700;padding:2px 7px;border-radius:10px}
.cu.dark .cu-tag{background:rgba(0,199,177,.2);color:#00c7b1}
.cu-rr{display:flex;flex-direction:column;align-items:flex-end;gap:2px;text-align:right}
.cu-date{font-family:Georgia,serif;font-size:14px;color:#444;font-weight:700;white-space:nowrap}
.cu.dark .cu-date{color:#d0d6de}
.cu-date-rel{font-size:10px;color:#888;font-weight:600}
.cu.dark .cu-date-rel{color:#6e7a8a}
/* Callouts */
.ann{background:#EFF6FF;border:1px solid #BFDBFE;border-radius:6px;padding:14px 16px;margin-top:16px;font-size:11.5px;color:#1E3A5F;line-height:1.6}
.ann strong{color:#0D2240;display:block;margin-bottom:4px}
.ann ul{margin-top:4px;padding-left:18px}
.ann ul li{margin-top:3px}
.ann code{background:#DBEAFE;padding:1px 5px;border-radius:2px;font-family:monospace;font-size:10.5px}
.callout-grid{display:flex;flex-direction:column;gap:10px;padding-top:18px}
.callout-grid .cg{background:#fff;border:1px solid #DDD8CE;border-radius:6px;padding:12px 14px}
.callout-grid .cg .cg-t{font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#012851;margin-bottom:4px}
.callout-grid .cg .cg-b{font-size:10.5px;color:#444;line-height:1.55}
/* Impl-ref */
.impl-ref{background:#0d1117;border-radius:7px;margin-top:16px;overflow:hidden;border:1px solid #30363d}
.impl-ref-hdr{background:#161b22;padding:8px 16px;font-size:9.5px;font-weight:800;color:#f0883e;border-bottom:1px solid #30363d;display:flex;align-items:center;gap:8px;letter-spacing:.4px;text-transform:uppercase}
.impl-ref-hdr::before{content:'⚙';font-size:12px}
.impl-ref-hdr span{color:rgba(240,136,62,.55);font-weight:400;margin-left:auto;font-size:9px;text-transform:none;letter-spacing:0}
.impl-ref table{width:100%;border-collapse:collapse;font-size:10px}
.impl-ref th{text-align:left;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#8b949e;padding:7px 14px;border-bottom:1px solid #21262d}
.impl-ref td{padding:6px 14px;border-bottom:1px solid #161b22;vertical-align:top;line-height:1.5;color:#c9d1d9}
.impl-ref tr:last-child td{border-bottom:none}
.impl-ref td:first-child{color:#79c0ff;font-weight:700;white-space:nowrap;width:200px}
.impl-ref td code{font-family:'SFMono-Regular',Consolas,monospace;font-size:9.5px;background:#161b22;color:#a5d6ff;padding:1px 5px;border-radius:3px}
.impl-ref .ir-px{color:#7ee787;font-family:monospace;font-size:9.5px}
.impl-ref .ir-note{color:#8b949e;font-style:italic}
</style>
</head>
<body>
<div class="doc">
<!-- ═══════════════════════════════════════════════════════════
MASTHEAD
═══════════════════════════════════════════════════════════ -->
<header class="mast">
<div class="mast-top">
<div>
<h1>Briefwechsel — Thumbnail Rows</h1>
<p>Final row design for <code>/briefwechsel</code>. PDF thumbnail anchors each row; summary reads as a quote; no status lifecycle, no script-type indicator. Designed for <strong>fun discovery</strong>, not dense scanning. Scales from 320&#8239;px mobile to 1440&#8239;px desktop, light and dark. Serves both the millennial audience (2542) and the senior family audience (60&#8239;+) — the senior constraint drives touch targets, line height, and summary legibility.</p>
</div>
<div class="mast-badge">FINAL</div>
</div>
<div class="decisions">
<div class="dec"><div class="dec-label">Route</div><div class="dec-value">/briefwechsel · list surface</div></div>
<div class="dec"><div class="dec-label">Row height (desktop)</div><div class="dec-value">128&#8239;px · comfortable</div></div>
<div class="dec"><div class="dec-label">Thumbnail</div><div class="dec-value">82×106 portrait · 104×72 landscape</div></div>
<div class="dec"><div class="dec-label">Removed</div><div class="dec-value"><s>status dot</s> · <s>script type</s> · <s>archive box</s></div></div>
</div>
</header>
<div class="spec-disclaimer">
<strong>Reading this spec.</strong> Mockups in Section&#8239;02 are scaled to ~55&#8239;% of real pixel values so that multiple viewports fit on one page. <strong>Never copy pixel sizes from the mockups.</strong> Use the <code>impl-ref</code> tables for exact Tailwind class + pixel value. Close-ups in Section&#8239;03 are rendered at ~100&#8239;% scale for pixel-accurate reference.
</div>
<!-- ═══════════════════════════════════════════════════════════
TOC
═══════════════════════════════════════════════════════════ -->
<div class="toc">
<div class="toc-t">Inhalt</div>
<ol>
<li><b>01</b> Page anatomy <span>default · 1440&#8239;px</span></li>
<li><b>02</b> Content states × 3 viewports <span>5 states · 15 frames</span></li>
<li><b>03</b> Row anatomy close-ups <span>4 row types @ real size</span></li>
<li><b>04</b> Distribution bar <span>bilateral mode only</span></li>
<li><b>05</b> Accessibility contract <span>WCAG AA/AAA</span></li>
<li><b>06</b> Implementation notes <span>data · thumbnails · routing</span></li>
</ol>
</div>
<!-- ═══════════════════════════════════════════════════════════
SECTION 01 — PAGE ANATOMY
═══════════════════════════════════════════════════════════ -->
<section class="sec">
<h2 class="sec-h"><span class="sec-num">01</span>Page Anatomy — Default State at 1440&#8239;px (single-person)</h2>
<p class="sec-intro">The page is a single vertical column (<code>max-w-7xl</code>). 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.</p>
<div style="display:grid;grid-template-columns:1fr 300px;gap:32px;align-items:flex-start">
<div>
<div class="wf wf-d">
<div class="wf-bar"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><div class="urlbar"><span>familienarchiv.de/briefwechsel?senderId=…</span></div></div>
<div class="bw">
<div class="bw-gh">
<div class="bw-gh-logo">Familienarchiv</div>
<div class="bw-gh-nav"><span>Dokumente</span><span>Personen</span><span class="on">Briefwechsel</span><span>Chronik</span></div>
</div>
<div class="bw-wrap">
<!-- A · Filter card -->
<div class="bw-fc">
<div class="bw-fc-grid">
<div><div class="bw-fl">Person</div><div class="bw-fi">Walter de Gruyter</div></div>
<div><div class="bw-fl">Korrespondent — optional</div><div class="bw-fi empty">Alle Korrespondenten</div></div>
</div>
<div class="bw-fa">
<div class="bw-btn">Newest ↓</div>
<div class="bw-btn">▾ Filter</div>
<div class="bw-count"><b>851</b> Briefe</div>
</div>
<div class="bw-hint">📋 Alle Briefe von <b>Walter de Gruyter</b> — wähle einen Korrespondenten oben um einzugrenzen</div>
</div>
<!-- B · Year divider -->
<div class="bw-yd"><span class="bw-yd-y">1940</span><span class="bw-yd-n">1 Brief</span></div>
<!-- C · Row list -->
<div class="bw-rlist">
<div class="bw-row in">
<div class="bw-thumb-cell"><div class="bw-thumb portrait typed"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div></div>
<div class="bw-rb">
<div class="bw-title">Demo leserlicher Brief</div>
<div class="bw-summary">letzte Lebenstage von W. Dörpfeld in Griechenland — ausführlicher Bericht aus Belgard</div>
<div class="bw-meta"><span class="dir in">← eingehend</span><span>Gertrud von Rofden</span><span class="sep">·</span><span>📍 Belgard</span><span class="sep">·</span><span class="bw-tag">Dörpfeld</span><span class="bw-tag">Griechenland</span></div>
</div>
<div class="bw-rr"><div class="bw-date">31. Mai 1940</div><div class="bw-date-rel">vor 85 Jahren</div></div>
</div>
</div>
<!-- B · Year divider -->
<div class="bw-yd"><span class="bw-yd-y">1923</span><span class="bw-yd-n">5 Briefe</span></div>
<div class="bw-rlist">
<div class="bw-row out">
<div class="bw-thumb-cell"><div class="bw-thumb portrait kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div></div>
<div class="bw-rb">
<div class="bw-title">W-0397 2. September 1923 B.Lichterfelde</div>
<div class="bw-summary">von Elsbeth geschriebener Kommentar, den Herbert zum Brief erzählte</div>
<div class="bw-meta"><span class="dir">→ ausgehend</span><span>an Herbert Cram</span><span class="sep">·</span><span>📍 B.Lichterfelde</span><span class="sep">·</span><span class="bw-tag">Verlag</span><span class="bw-tag">Familie</span></div>
</div>
<div class="bw-rr"><div class="bw-date">2. September 1923</div><div class="bw-date-rel">vor 102 Jahren</div></div>
</div>
<div class="bw-row out">
<div class="bw-thumb-cell"><div class="bw-thumb landscape postcard kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i></div></div></div>
<div class="bw-rb">
<div class="bw-title">Ansichtskarte 2. September 1923 B.Lichterfelde</div>
<div class="bw-summary">kurze Grüße aus B.Lichterfelde, Hinweis auf den kommenden Besuch</div>
<div class="bw-meta"><span class="dir">→ ausgehend</span><span>an Herbert Cram</span><span class="sep">·</span><span>📍 B.Lichterfelde</span><span class="sep">·</span><span class="bw-kind">✉ Postkarte</span></div>
</div>
<div class="bw-rr"><div class="bw-date">2. September 1923</div><div class="bw-date-rel">vor 102 Jahren</div></div>
</div>
<div class="bw-row out">
<div class="bw-thumb-cell"><div class="bw-thumb portrait kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div><span class="bw-thumb-badge">4 S.</span></div>
<div class="bw-rb">
<div class="bw-title">W-0524 31. Juli 1923 Berlin</div>
<div class="bw-summary">Glückwunsch zum 60. Geburtstag, Bericht über den Verlag und den Umzug</div>
<div class="bw-meta"><span class="dir">→ ausgehend</span><span>an Walter Dieckmann</span><span class="sep">·</span><span>📍 Berlin</span><span class="sep">·</span><span class="bw-tag">Geburtstag</span></div>
</div>
<div class="bw-rr"><div class="bw-date">31. Juli 1923</div><div class="bw-date-rel">vor 102 Jahren</div></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="callout-grid">
<div class="cg"><div class="cg-t">A · Filter card</div><div class="cg-b">Two inputs (person required, correspondent optional) + action row + hint bar. Uses <code>bg-surface</code> wrapper, not a card — the hint bar gives it closure.</div></div>
<div class="cg"><div class="cg-t">B · Year divider</div><div class="cg-b">Sticky-looking band between year groups. Large navy numeral + brief count. Uses <code>bg-muted</code> and a 1&#8239;px rule above/below.</div></div>
<div class="cg"><div class="cg-t">C · Row list</div><div class="cg-b">Single <code>&lt;ul&gt;</code> per year group. Each row is an <code>&lt;a&gt;</code> with <code>role="listitem"</code> ancestor. Border-left accent colors direction: navy = outgoing, mint-darker = incoming.</div></div>
<div class="cg"><div class="cg-t">Row · Thumbnail cell</div><div class="cg-b">Fixed 104&#8239;× 120&#8239;px cell on desktop; portrait and landscape both centered in the same cell so row height stays consistent across mixed media.</div></div>
<div class="cg"><div class="cg-t">Row · Body</div><div class="cg-b">Serif title · italic serif summary (with mint quote glyphs) · sans meta line with direction + counterpart + location + tags. Summary omitted entirely when empty.</div></div>
<div class="cg"><div class="cg-t">Row · Right column</div><div class="cg-b">Date (serif, bold) + relative age ("vor 102 Jahren"). No status, no archive location — deliberately calm.</div></div>
</div>
</div>
<div class="impl-ref">
<div class="impl-ref-hdr">Implementation Reference — Page Shell<span>Tailwind 4 · tokens from layout.css</span></div>
<table>
<thead><tr><th>Element</th><th>Classes</th><th>Real</th><th>Note</th></tr></thead>
<tbody>
<tr><td>Page container</td><td><code>mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8</code></td><td class="ir-px">max 80rem</td><td>Matches production /briefwechsel</td></tr>
<tr><td>Filter card wrapper</td><td><code>mb-8 rounded-sm border border-line bg-surface p-6 shadow-sm</code></td><td class="ir-px">padding 24&#8239;px</td><td>Existing <code>CorrespondenzPersonBar</code> container</td></tr>
<tr><td>Year divider</td><td><code>flex items-baseline gap-3 border-y border-line bg-muted px-[14px] py-[8px]</code></td><td class="ir-px">border 1&#8239;px both sides</td><td>Keep production styling — only row changes</td></tr>
<tr><td>Year numeral</td><td><code>font-serif text-2xl font-black tracking-tight text-primary</code></td><td class="ir-px">24&#8239;px / 900 / -0.025em</td><td>Merriweather Black</td></tr>
<tr><td>Year count</td><td><code>text-sm font-bold text-ink-3</code></td><td class="ir-px">14&#8239;px / 700</td><td>"5 Briefe" / Paraglide plural</td></tr>
<tr><td>Row list wrapper</td><td><code>overflow-hidden rounded-sm border border-line bg-surface</code></td><td class="ir-px">1&#8239;px border</td><td>Hides row borders at ends</td></tr>
<tr><td>Row</td><td><code>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</code></td><td class="ir-px">128&#8239;px min · 20&#8239;× 14 padding</td><td><code>border-l-primary</code> out · <code>border-l-accent</code> in</td></tr>
<tr><td>Touch target</td><td>Full row is clickable; row height 128&#8239;px &gt; WCAG 44&#8239;px minimum × ~3</td><td class="ir-px">128 ≥ 44</td><td>Senior audience: comfort over density</td></tr>
</tbody>
</table>
</div>
</section>
<!-- ═══════════════════════════════════════════════════════════
SECTION 02 — CONTENT STATES × 3 VIEWPORTS
═══════════════════════════════════════════════════════════ -->
<section class="sec">
<h2 class="sec-h"><span class="sec-num">02</span>Content States &times; 3 Viewports</h2>
<p class="sec-intro">
Five states covering the combinations that matter. Every frame renders the full page shell (header → filter card → list). Reading order per state: <b>320&#8239;px</b> (mobile S) → <b>768&#8239;px</b> (tablet) → <b>1440&#8239;px</b> (desktop). Watch for filter card wrap at 320, thumbnail shrinkage, and the right-column behaviour under content pressure.
</p>
<!-- ════════════════════════════════════════════════════
STATE 01 · DEFAULT — single-person, mixed row types
═══════════════════════════════════════════════════════ -->
<div class="state-block">
<div class="state-hdr"><span class="state-num">01</span><span class="state-title">Default · Single person with mixed row types</span></div>
<div class="state-desc">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.</div>
<div class="state-vps">
<!-- 320 -->
<div class="state-vp-col">
<span class="vp-tag">320 px · Mobile</span><span class="vp-dim">176 px @ 55%</span>
<div class="wf wf-m">
<div class="wf-m-status"><span>9:41</span><div class="dots"><i></i><i></i><i></i></div></div>
<div class="bw">
<div class="bw-gh"><div class="bw-gh-logo">FA</div><div class="bw-gh-nav"><span class="on">Briefwechsel</span></div></div>
<div class="bw-wrap">
<div class="bw-fc"><div class="bw-fc-grid"><div><div class="bw-fl">Person</div><div class="bw-fi">Walter de Gruyter</div></div><div><div class="bw-fl">Korrespondent</div><div class="bw-fi empty">alle</div></div></div><div class="bw-fa"><div class="bw-btn"></div><div class="bw-btn"></div><div class="bw-count"><b>851</b></div></div></div>
<div class="bw-yd"><span class="bw-yd-y">1923</span><span class="bw-yd-n">5</span></div>
<div class="bw-rlist">
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb portrait kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i></div></div></div><div class="bw-rb"><div class="bw-title">W-0397</div><div class="bw-summary">Elsbeths Kommentar</div><div class="bw-meta"><span class="dir"></span><span>H. Cram</span></div></div><div class="bw-rr"><div class="bw-date">2. Sep</div></div></div>
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb landscape postcard kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i></div></div></div><div class="bw-rb"><div class="bw-title">Ansichtskarte</div><div class="bw-meta"><span class="dir"></span><span>H. Cram</span><span class="bw-kind"></span></div></div><div class="bw-rr"><div class="bw-date">2. Sep</div></div></div>
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb portrait kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i></div></div><span class="bw-thumb-badge">4</span></div><div class="bw-rb"><div class="bw-title">W-0524</div><div class="bw-summary">Geburtstag &amp; Umzug</div><div class="bw-meta"><span class="dir"></span><span>W. Dieckmann</span></div></div><div class="bw-rr"><div class="bw-date">31. Jul</div></div></div>
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb portrait kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i><i></i></div></div></div><div class="bw-rb"><div class="bw-title">W-0396</div><div class="bw-meta"><span class="dir"></span><span>H. Cram</span></div></div><div class="bw-rr"><div class="bw-date">2. Sep</div></div></div>
</div>
</div>
</div>
</div>
</div>
<!-- 768 -->
<div class="state-vp-col">
<span class="vp-tag">768 px · Tablet</span><span class="vp-dim">422 px @ 55%</span>
<div class="wf wf-t">
<div class="wf-bar"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><div class="urlbar"><span>familienarchiv.de/briefwechsel</span></div></div>
<div class="bw">
<div class="bw-gh"><div class="bw-gh-logo">Familienarchiv</div><div class="bw-gh-nav"><span>Dokumente</span><span class="on">Briefwechsel</span></div></div>
<div class="bw-wrap">
<div class="bw-fc"><div class="bw-fc-grid"><div><div class="bw-fl">Person</div><div class="bw-fi">Walter de Gruyter</div></div><div><div class="bw-fl">Korrespondent — optional</div><div class="bw-fi empty">Alle Korrespondenten</div></div></div><div class="bw-fa"><div class="bw-btn">Newest ↓</div><div class="bw-btn">▾ Filter</div><div class="bw-count"><b>851</b> Briefe</div></div><div class="bw-hint">📋 Alle Briefe von <b>Walter de Gruyter</b></div></div>
<div class="bw-yd"><span class="bw-yd-y">1923</span><span class="bw-yd-n">5 Briefe</span></div>
<div class="bw-rlist">
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb portrait kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div></div><div class="bw-rb"><div class="bw-title">W-0397 2. September 1923</div><div class="bw-summary">von Elsbeth geschriebener Kommentar, den Herbert zum Brief erzählte</div><div class="bw-meta"><span class="dir"></span><span>an Herbert Cram</span><span class="bw-tag">Verlag</span></div></div><div class="bw-rr"><div class="bw-date">2. Sep 1923</div><div class="bw-date-rel">vor 102 J.</div></div></div>
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb landscape postcard kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i></div></div></div><div class="bw-rb"><div class="bw-title">Ansichtskarte 2. September 1923</div><div class="bw-summary">kurze Grüße aus B.Lichterfelde</div><div class="bw-meta"><span class="dir"></span><span>an Herbert Cram</span><span class="bw-kind">✉ Postkarte</span></div></div><div class="bw-rr"><div class="bw-date">2. Sep 1923</div><div class="bw-date-rel">vor 102 J.</div></div></div>
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb portrait kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div><span class="bw-thumb-badge">4 S.</span></div><div class="bw-rb"><div class="bw-title">W-0524 31. Juli 1923 Berlin</div><div class="bw-summary">Glückwunsch zum 60. Geburtstag, Bericht über den Verlag</div><div class="bw-meta"><span class="dir"></span><span>an Walter Dieckmann</span><span class="bw-tag">Geburtstag</span></div></div><div class="bw-rr"><div class="bw-date">31. Jul 1923</div><div class="bw-date-rel">vor 102 J.</div></div></div>
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb portrait kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i></div></div></div><div class="bw-rb"><div class="bw-title">W-0396 2. September 1923</div><div class="bw-meta"><span class="dir"></span><span>an Herbert Cram</span></div></div><div class="bw-rr"><div class="bw-date">2. Sep 1923</div><div class="bw-date-rel">vor 102 J.</div></div></div>
</div>
</div>
</div>
</div>
</div>
<!-- 1440 -->
<div class="state-vp-col">
<span class="vp-tag">1440 px · Desktop</span><span class="vp-dim">720 px @ 55%</span>
<div class="wf wf-d">
<div class="wf-bar"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><div class="urlbar"><span>familienarchiv.de/briefwechsel?senderId=…</span></div></div>
<div class="bw">
<div class="bw-gh"><div class="bw-gh-logo">Familienarchiv</div><div class="bw-gh-nav"><span>Dokumente</span><span>Personen</span><span class="on">Briefwechsel</span><span>Chronik</span></div></div>
<div class="bw-wrap">
<div class="bw-fc"><div class="bw-fc-grid"><div><div class="bw-fl">Person</div><div class="bw-fi">Walter de Gruyter</div></div><div><div class="bw-fl">Korrespondent — optional</div><div class="bw-fi empty">Alle Korrespondenten</div></div></div><div class="bw-fa"><div class="bw-btn">Newest ↓</div><div class="bw-btn">▾ Filter</div><div class="bw-count"><b>851</b> Briefe</div></div><div class="bw-hint">📋 Alle Briefe von <b>Walter de Gruyter</b> — wähle einen Korrespondenten oben um einzugrenzen</div></div>
<div class="bw-yd"><span class="bw-yd-y">1923</span><span class="bw-yd-n">5 Briefe</span></div>
<div class="bw-rlist">
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb portrait kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div></div><div class="bw-rb"><div class="bw-title">W-0397 2. September 1923 B.Lichterfelde</div><div class="bw-summary">von Elsbeth geschriebener Kommentar, den Herbert zum Brief erzählte</div><div class="bw-meta"><span class="dir">→ ausgehend</span><span>an Herbert Cram</span><span class="sep">·</span><span>📍 B.Lichterfelde</span><span class="sep">·</span><span class="bw-tag">Verlag</span><span class="bw-tag">Familie</span></div></div><div class="bw-rr"><div class="bw-date">2. September 1923</div><div class="bw-date-rel">vor 102 Jahren</div></div></div>
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb landscape postcard kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i></div></div></div><div class="bw-rb"><div class="bw-title">Ansichtskarte 2. September 1923 B.Lichterfelde</div><div class="bw-summary">kurze Grüße aus B.Lichterfelde, Hinweis auf den kommenden Besuch</div><div class="bw-meta"><span class="dir">→ ausgehend</span><span>an Herbert Cram</span><span class="sep">·</span><span>📍 B.Lichterfelde</span><span class="sep">·</span><span class="bw-kind">✉ Postkarte</span></div></div><div class="bw-rr"><div class="bw-date">2. September 1923</div><div class="bw-date-rel">vor 102 Jahren</div></div></div>
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb portrait kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div><span class="bw-thumb-badge">4 S.</span></div><div class="bw-rb"><div class="bw-title">W-0524 31. Juli 1923 Berlin</div><div class="bw-summary">Glückwunsch zum 60. Geburtstag, Bericht über den Verlag und den Umzug</div><div class="bw-meta"><span class="dir">→ ausgehend</span><span>an Walter Dieckmann</span><span class="sep">·</span><span>📍 Berlin</span><span class="sep">·</span><span class="bw-tag">Geburtstag</span><span class="bw-tag">Verlag</span></div></div><div class="bw-rr"><div class="bw-date">31. Juli 1923</div><div class="bw-date-rel">vor 102 Jahren</div></div></div>
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb portrait kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i></div></div></div><div class="bw-rb"><div class="bw-title">W-0396 2. September 1923 B.Lichterfelde</div><div class="bw-meta"><span class="dir">→ ausgehend</span><span>an Herbert Cram</span><span class="sep">·</span><span>📍 B.Lichterfelde</span></div></div><div class="bw-rr"><div class="bw-date">2. September 1923</div><div class="bw-date-rel">vor 102 Jahren</div></div></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="ann"><strong>Layout-Beobachtungen.</strong>
<ul>
<li>320&#8239;px: Filter card collapses to a single column. Title truncates with ellipsis (<code>W-0397</code>), summary keeps 1 line max; counterpart shortens to initials+last (<code>H. Cram</code>). Date format is <code>2. Sep</code> — no year (year dividers provide it).</li>
<li>768&#8239;px: Two-column filter returns. Title shows full label; summary gets 2 lines; date is <code>2. Sep 1923</code>; location meta omitted (kept to 2 items), tags trimmed to one.</li>
<li>1440&#8239;px: Full meta (direction word, counterpart, location, 2 tags). Relative date appears below the absolute date.</li>
<li>Row 4 (no summary) retains the exact same row height as others — the row grid is <code>min-h-[128px]</code> at desktop so mixed-summary lists don't visually jump.</li>
</ul>
</div>
</div>
<!-- ════════════════════════════════════════════════════
STATE 02 · BILATERAL with distribution bar
═══════════════════════════════════════════════════════ -->
<div class="state-block">
<div class="state-hdr"><span class="state-num">02</span><span class="state-title">Bilateral · Both filters set + distribution bar</span></div>
<div class="state-desc">Sender and receiver both selected. A distribution bar appears above the row list, pattern lifted from production <code>ConversationTimeline</code>. Rows show compact direction glyph instead of the word — the bar above already established direction semantics.</div>
<div class="state-vps">
<div class="state-vp-col">
<span class="vp-tag">320 px · Mobile</span><span class="vp-dim">176 px @ 55%</span>
<div class="wf wf-m">
<div class="wf-m-status"><span>9:41</span><div class="dots"><i></i><i></i><i></i></div></div>
<div class="bw">
<div class="bw-gh"><div class="bw-gh-logo">FA</div><div class="bw-gh-nav"><span class="on">Briefwechsel</span></div></div>
<div class="bw-wrap">
<div class="bw-fc"><div class="bw-fc-grid"><div><div class="bw-fl">Person</div><div class="bw-fi">Walter</div></div><div><div class="bw-fl">Korrespondent</div><div class="bw-fi">Herbert</div></div></div><div class="bw-fa"><div class="bw-btn"></div><div class="bw-btn"></div><div class="bw-count"><b>143</b></div></div></div>
<div class="bw-distbar"><div class="bw-distbar-labels"><span class="out">87 Walter →</span><span class="in">← 56 Herbert</span></div><div class="bw-distbar-bar"><span class="out" style="width:60.8%"></span><span class="in" style="width:39.2%"></span></div></div>
<div class="bw-rlist below-distbar">
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb portrait kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i></div></div></div><div class="bw-rb"><div class="bw-title">W-0397</div><div class="bw-summary">Elsbeths Kommentar</div><div class="bw-meta"><span class="dir"></span><span>B.Lichterfelde</span></div></div><div class="bw-rr"><div class="bw-date">2. Sep</div></div></div>
<div class="bw-row in"><div class="bw-thumb-cell"><div class="bw-thumb portrait kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i><i></i></div></div></div><div class="bw-rb"><div class="bw-title">H-0213</div><div class="bw-summary">Antwort zur Herbstlieferung</div><div class="bw-meta"><span class="dir in"></span><span>Leipzig</span></div></div><div class="bw-rr"><div class="bw-date">29. Aug</div></div></div>
<div class="bw-row in"><div class="bw-thumb-cell"><div class="bw-thumb landscape postcard kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i></div></div></div><div class="bw-rb"><div class="bw-title">Ansichtskarte</div><div class="bw-meta"><span class="dir in"></span><span>Thür. Wald</span><span class="bw-kind"></span></div></div><div class="bw-rr"><div class="bw-date">20. Aug</div></div></div>
</div>
</div>
</div>
</div>
</div>
<div class="state-vp-col">
<span class="vp-tag">768 px · Tablet</span><span class="vp-dim">422 px @ 55%</span>
<div class="wf wf-t">
<div class="wf-bar"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><div class="urlbar"><span>…/briefwechsel?senderId=&amp;receiverId=</span></div></div>
<div class="bw">
<div class="bw-gh"><div class="bw-gh-logo">Familienarchiv</div><div class="bw-gh-nav"><span>Dokumente</span><span class="on">Briefwechsel</span></div></div>
<div class="bw-wrap">
<div class="bw-fc"><div class="bw-fc-grid"><div><div class="bw-fl">Person</div><div class="bw-fi">Walter de Gruyter</div></div><div><div class="bw-fl">Korrespondent</div><div class="bw-fi">Herbert Cram</div></div></div><div class="bw-fa"><div class="bw-btn">⇄ Tauschen</div><div class="bw-btn">Newest ↓</div><div class="bw-btn">▾ Filter</div><div class="bw-count"><b>143</b> Briefe</div></div></div>
<div class="bw-distbar"><div class="bw-distbar-labels"><span class="out">87 von Walter de Gruyter →</span><span class="in">← 56 von Herbert Cram</span></div><div class="bw-distbar-bar"><span class="out" style="width:60.8%"></span><span class="in" style="width:39.2%"></span></div></div>
<div class="bw-rlist below-distbar">
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb portrait kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div></div><div class="bw-rb"><div class="bw-title">W-0397 2. September 1923</div><div class="bw-summary">von Elsbeth geschriebener Kommentar</div><div class="bw-meta"><span class="dir"></span><span>Walter an Herbert</span><span class="bw-tag">Verlag</span></div></div><div class="bw-rr"><div class="bw-date">2. Sep 1923</div><div class="bw-date-rel">vor 102 J.</div></div></div>
<div class="bw-row in"><div class="bw-thumb-cell"><div class="bw-thumb portrait kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div></div><div class="bw-rb"><div class="bw-title">H-0213 29. August 1923 Leipzig</div><div class="bw-summary">Antwort auf Walters Anfrage zur Herbstauslieferung</div><div class="bw-meta"><span class="dir in"></span><span>Herbert an Walter</span><span class="bw-tag">Verlag</span></div></div><div class="bw-rr"><div class="bw-date">29. Aug 1923</div><div class="bw-date-rel">vor 102 J.</div></div></div>
<div class="bw-row in"><div class="bw-thumb-cell"><div class="bw-thumb landscape postcard kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i></div></div></div><div class="bw-rb"><div class="bw-title">Ansichtskarte 20. August 1923</div><div class="bw-summary">Urlaubsgruß aus Thüringen</div><div class="bw-meta"><span class="dir in"></span><span>Herbert an Walter</span><span class="bw-kind">✉ Postkarte</span></div></div><div class="bw-rr"><div class="bw-date">20. Aug 1923</div><div class="bw-date-rel">vor 102 J.</div></div></div>
</div>
</div>
</div>
</div>
</div>
<div class="state-vp-col">
<span class="vp-tag">1440 px · Desktop</span><span class="vp-dim">720 px @ 55%</span>
<div class="wf wf-d">
<div class="wf-bar"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><div class="urlbar"><span>familienarchiv.de/briefwechsel?senderId=…&amp;receiverId=…</span></div></div>
<div class="bw">
<div class="bw-gh"><div class="bw-gh-logo">Familienarchiv</div><div class="bw-gh-nav"><span>Dokumente</span><span>Personen</span><span class="on">Briefwechsel</span><span>Chronik</span></div></div>
<div class="bw-wrap">
<div class="bw-fc"><div class="bw-fc-grid"><div><div class="bw-fl">Person</div><div class="bw-fi">Walter de Gruyter</div></div><div><div class="bw-fl">Korrespondent</div><div class="bw-fi">Herbert Cram</div></div></div><div class="bw-fa"><div class="bw-btn">⇄ Tauschen</div><div class="bw-btn">Newest ↓</div><div class="bw-btn">▾ Filter</div><div class="bw-count"><b>143</b> Briefe im Zeitraum</div></div></div>
<div class="bw-distbar" role="img" aria-label="Briefverteilung: 87 von Walter, 56 von Herbert"><div class="bw-distbar-labels"><span class="out">87 von Walter de Gruyter →</span><span class="in">← 56 von Herbert Cram</span></div><div class="bw-distbar-bar"><span class="out" style="width:60.8%"></span><span class="in" style="width:39.2%"></span></div></div>
<div class="bw-rlist below-distbar">
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb portrait kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div></div><div class="bw-rb"><div class="bw-title">W-0397 2. September 1923 B.Lichterfelde</div><div class="bw-summary">von Elsbeth geschriebener Kommentar, den Herbert zum Brief erzählte</div><div class="bw-meta"><span class="dir"></span><span>Walter an Herbert</span><span class="sep">·</span><span>📍 B.Lichterfelde</span><span class="sep">·</span><span class="bw-tag">Verlag</span></div></div><div class="bw-rr"><div class="bw-date">2. September 1923</div><div class="bw-date-rel">vor 102 Jahren</div></div></div>
<div class="bw-row in"><div class="bw-thumb-cell"><div class="bw-thumb portrait kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div></div><div class="bw-rb"><div class="bw-title">H-0213 29. August 1923 Leipzig</div><div class="bw-summary">Antwort auf Walters Anfrage zur Herbstauslieferung</div><div class="bw-meta"><span class="dir in"></span><span>Herbert an Walter</span><span class="sep">·</span><span>📍 Leipzig</span><span class="sep">·</span><span class="bw-tag">Verlag</span></div></div><div class="bw-rr"><div class="bw-date">29. August 1923</div><div class="bw-date-rel">vor 102 Jahren</div></div></div>
<div class="bw-row in"><div class="bw-thumb-cell"><div class="bw-thumb landscape postcard kurrent"><div class="bw-thumb-lines"><i></i><i></i><i></i><i></i></div></div></div><div class="bw-rb"><div class="bw-title">Ansichtskarte 20. August 1923 Thüringer Wald</div><div class="bw-summary">Urlaubsgruß, kurze Notiz über Wetter und geplante Rückkehr</div><div class="bw-meta"><span class="dir in"></span><span>Herbert an Walter</span><span class="sep">·</span><span>📍 Thüringer Wald</span><span class="sep">·</span><span class="bw-kind">✉ Postkarte</span></div></div><div class="bw-rr"><div class="bw-date">20. August 1923</div><div class="bw-date-rel">vor 102 Jahren</div></div></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="ann"><strong>Distribution bar — only renders when both <code>senderId</code> and <code>receiverId</code> are set.</strong>
<ul>
<li>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.</li>
<li><code>role="img"</code> with a descriptive <code>aria-label</code> — screen readers hear the full distribution in one sentence.</li>
<li>Below 320&#8239;px: labels stack vertically with a 4&#8239;px gap. Never truncate a count.</li>
<li>In meta line, direction word collapses to glyph ("→ / ←") because the distribution bar above has already named the parties.</li>
</ul>
</div>
</div>
<!-- ════════════════════════════════════════════════════
STATE 03 · LOADING skeleton
═══════════════════════════════════════════════════════ -->
<div class="state-block">
<div class="state-hdr"><span class="state-num">03</span><span class="state-title">Loading · Skeleton (all three viewports render the same pattern)</span></div>
<div class="state-desc">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.</div>
<div class="state-vps">
<div class="state-vp-col">
<span class="vp-tag">320 px · Mobile</span><span class="vp-dim">176 px @ 55%</span>
<div class="wf wf-m">
<div class="wf-m-status"><span>9:41</span><div class="dots"><i></i><i></i><i></i></div></div>
<div class="bw">
<div class="bw-gh"><div class="bw-gh-logo">FA</div></div>
<div class="bw-wrap">
<div class="bw-fc"><div class="bw-fc-grid"><div><div class="bw-fl">Person</div><div class="bw-fi">Walter</div></div><div><div class="bw-fl">Korresp.</div><div class="bw-fi empty">alle</div></div></div><div class="bw-fa"><div class="bw-btn"></div><div class="bw-count"><b>851</b></div></div></div>
<div class="bw-yd"><span class="bw-yd-y">1923</span><span class="bw-yd-n">5</span></div>
<div class="bw-rlist">
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb portrait sk" style="width:28px;height:38px"></div></div><div class="bw-rb"><div class="bw-title">W-0397</div><div class="bw-summary">Elsbeths Kommentar</div><div class="bw-meta"><span class="dir"></span><span>H. Cram</span></div></div><div class="bw-rr"><div class="bw-date">2. Sep</div></div></div>
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb portrait sk" style="width:28px;height:38px"></div></div><div class="bw-rb"><div class="bw-title">W-0396</div><div class="bw-meta"><span class="dir"></span><span>H. Cram</span></div></div><div class="bw-rr"><div class="bw-date">2. Sep</div></div></div>
</div>
</div>
</div>
</div>
</div>
<div class="state-vp-col">
<span class="vp-tag">768 px · Tablet</span><span class="vp-dim">422 px @ 55%</span>
<div class="wf wf-t">
<div class="wf-bar"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><div class="urlbar"><span>…/briefwechsel</span></div></div>
<div class="bw">
<div class="bw-gh"><div class="bw-gh-logo">Familienarchiv</div></div>
<div class="bw-wrap">
<div class="bw-fc"><div class="bw-fc-grid"><div><div class="bw-fl">Person</div><div class="bw-fi">Walter de Gruyter</div></div><div><div class="bw-fl">Korresp.</div><div class="bw-fi empty">Alle</div></div></div><div class="bw-fa"><div class="bw-btn">Newest ↓</div><div class="bw-count"><b>851</b> Briefe</div></div></div>
<div class="bw-yd"><span class="bw-yd-y">1923</span><span class="bw-yd-n">5 Briefe</span></div>
<div class="bw-rlist">
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb portrait sk" style="width:48px;height:62px"></div></div><div class="bw-rb"><div class="bw-title">W-0397 2. September 1923</div><div class="bw-summary">Elsbeths Kommentar</div><div class="bw-meta"><span class="dir"></span><span>Herbert Cram</span></div></div><div class="bw-rr"><div class="bw-date">2. Sep 1923</div></div></div>
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb portrait sk" style="width:48px;height:62px"></div></div><div class="bw-rb"><div class="bw-title">W-0396 2. September 1923</div><div class="bw-meta"><span class="dir"></span><span>Herbert Cram</span></div></div><div class="bw-rr"><div class="bw-date">2. Sep 1923</div></div></div>
</div>
</div>
</div>
</div>
</div>
<div class="state-vp-col">
<span class="vp-tag">1440 px · Desktop</span><span class="vp-dim">720 px @ 55%</span>
<div class="wf wf-d">
<div class="wf-bar"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><div class="urlbar"><span>familienarchiv.de/briefwechsel</span></div></div>
<div class="bw">
<div class="bw-gh"><div class="bw-gh-logo">Familienarchiv</div></div>
<div class="bw-wrap">
<div class="bw-fc"><div class="bw-fc-grid"><div><div class="bw-fl">Person</div><div class="bw-fi">Walter de Gruyter</div></div><div><div class="bw-fl">Korrespondent</div><div class="bw-fi empty">Alle</div></div></div><div class="bw-fa"><div class="bw-btn">Newest ↓</div><div class="bw-btn">▾ Filter</div><div class="bw-count"><b>851</b> Briefe</div></div></div>
<div class="bw-yd"><span class="bw-yd-y">1923</span><span class="bw-yd-n">5 Briefe</span></div>
<div class="bw-rlist">
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb portrait sk" style="width:72px;height:94px"></div></div><div class="bw-rb"><div class="bw-title">W-0397 2. September 1923 B.Lichterfelde</div><div class="bw-summary">von Elsbeth geschriebener Kommentar, den Herbert zum Brief erzählte</div><div class="bw-meta"><span class="dir">→ ausgehend</span><span>an Herbert Cram</span></div></div><div class="bw-rr"><div class="bw-date">2. September 1923</div></div></div>
<div class="bw-row out"><div class="bw-thumb-cell"><div class="bw-thumb portrait sk" style="width:72px;height:94px"></div></div><div class="bw-rb"><div class="bw-title">W-0396 2. September 1923 B.Lichterfelde</div><div class="bw-meta"><span class="dir">→ ausgehend</span><span>an Herbert Cram</span></div></div><div class="bw-rr"><div class="bw-date">2. September 1923</div></div></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ════════════════════════════════════════════════════
STATE 04 · EMPTY (no results)
═══════════════════════════════════════════════════════ -->
<div class="state-block">
<div class="state-hdr"><span class="state-num">04</span><span class="state-title">Empty · No results matching current filters</span></div>
<div class="state-desc">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.</div>
<div class="state-vps">
<div class="state-vp-col">
<span class="vp-tag">320 px · Mobile</span><span class="vp-dim">176 px @ 55%</span>
<div class="wf wf-m">
<div class="wf-m-status"><span>9:41</span><div class="dots"><i></i><i></i><i></i></div></div>
<div class="bw">
<div class="bw-gh"><div class="bw-gh-logo">FA</div></div>
<div class="bw-wrap">
<div class="bw-fc"><div class="bw-fc-grid"><div><div class="bw-fl">Person</div><div class="bw-fi">Walter</div></div><div><div class="bw-fl">Korresp.</div><div class="bw-fi empty">alle</div></div></div><div class="bw-fa"><div class="bw-btn"></div><div class="bw-count"><b>0</b></div></div></div>
<div class="bw-empty"><div class="bw-empty-t">Keine Briefe</div><div class="bw-empty-b">Für diesen Filter gibt es keine Einträge. Zeitraum anpassen oder Filter zurücksetzen.</div></div>
</div>
</div>
</div>
</div>
<div class="state-vp-col">
<span class="vp-tag">768 px · Tablet</span><span class="vp-dim">422 px @ 55%</span>
<div class="wf wf-t">
<div class="wf-bar"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><div class="urlbar"><span>…/briefwechsel?from=1950&amp;to=1960</span></div></div>
<div class="bw">
<div class="bw-gh"><div class="bw-gh-logo">Familienarchiv</div></div>
<div class="bw-wrap">
<div class="bw-fc"><div class="bw-fc-grid"><div><div class="bw-fl">Person</div><div class="bw-fi">Walter de Gruyter</div></div><div><div class="bw-fl">Korresp.</div><div class="bw-fi empty">Alle</div></div></div><div class="bw-fa"><div class="bw-btn">Newest ↓</div><div class="bw-btn">▾ Filter</div><div class="bw-count"><b>0</b> Briefe</div></div></div>
<div class="bw-empty"><div class="bw-empty-t">Keine Briefe in diesem Zeitraum</div><div class="bw-empty-b">Von 1950 bis 1960 gibt es keine Korrespondenz. Zeitraum erweitern oder Filter zurücksetzen.</div></div>
</div>
</div>
</div>
</div>
<div class="state-vp-col">
<span class="vp-tag">1440 px · Desktop</span><span class="vp-dim">720 px @ 55%</span>
<div class="wf wf-d">
<div class="wf-bar"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><div class="urlbar"><span>familienarchiv.de/briefwechsel?from=1950&amp;to=1960</span></div></div>
<div class="bw">
<div class="bw-gh"><div class="bw-gh-logo">Familienarchiv</div></div>
<div class="bw-wrap">
<div class="bw-fc"><div class="bw-fc-grid"><div><div class="bw-fl">Person</div><div class="bw-fi">Walter de Gruyter</div></div><div><div class="bw-fl">Korrespondent</div><div class="bw-fi empty">Alle</div></div></div><div class="bw-fa"><div class="bw-btn">Newest ↓</div><div class="bw-btn">▾ Filter</div><div class="bw-count"><b>0</b> Briefe</div></div></div>
<div class="bw-empty"><div class="bw-empty-t">Keine Briefe in diesem Zeitraum</div><div class="bw-empty-b">Von 1950 bis 1960 gibt es keine Korrespondenz mit Walter de Gruyter. Passe den Zeitraum an oder setze die Filter zurück.</div></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ════════════════════════════════════════════════════
STATE 05 · SINGLE-PERSON HINT (no correspondent chosen)
═══════════════════════════════════════════════════════ -->
<div class="state-block">
<div class="state-hdr"><span class="state-num">05</span><span class="state-title">Single-person hint — reminder to narrow</span></div>
<div class="state-desc">Already shown in production. Stays exactly as is. Re-rendered here so developers confirm it still renders <em>above</em> the first year divider when only <code>senderId</code> is set. Not shown in bilateral mode.</div>
<div class="ann"><strong>Kein Redesign.</strong> Die bestehende <code>SinglePersonHintBar.svelte</code> bleibt unverändert und rendert zwischen Filter-Card und erster Jahres-Trennlinie. Nur in Single-Person-Modus, nicht bilateral.</div>
</div>
<div class="impl-ref">
<div class="impl-ref-hdr">Implementation Reference — Content States<span>list rendering + skeleton</span></div>
<table>
<thead><tr><th>Element</th><th>Classes</th><th>Real</th><th>Note</th></tr></thead>
<tbody>
<tr><td>Skeleton thumb</td><td><code>animate-pulse bg-gradient-to-r from-[#f5f4ef] via-[#eceae4] to-[#f5f4ef] rounded-[1px]</code></td><td class="ir-px">shimmer 1.4&#8239;s</td><td>Applied only to <code>.bw-thumb</code>, never to text</td></tr>
<tr><td>Empty card</td><td><code>flex flex-col items-center justify-center rounded-sm border border-line bg-muted py-24 text-center shadow-sm</code></td><td class="ir-px">padding 96&#8239;px y</td><td>Matches production empty state</td></tr>
<tr><td>Empty title</td><td><code>font-serif text-ink</code></td><td class="ir-px">18&#8239;px desktop</td><td>Paraglide: <code>m.conv_no_results_heading()</code></td></tr>
<tr><td>Empty body</td><td><code>mt-2 text-sm text-ink-3 max-w-prose mx-auto</code></td><td class="ir-px">14&#8239;px</td><td>Paraglide: <code>m.conv_no_results_text()</code></td></tr>
<tr><td>Distribution bar</td><td><code>flex flex-col gap-1 border-b border-line bg-muted px-[18px] py-2</code></td><td class="ir-px">role="img"</td><td>aria-label: "Briefverteilung: X von A, Y von B"</td></tr>
<tr><td>Distbar labels</td><td><code>flex justify-between text-sm font-bold</code> · <code>.out text-primary</code> · <code>.in text-accent</code></td><td class="ir-px">14&#8239;px / 700</td><td>Counts in <code>tabular-nums</code></td></tr>
<tr><td>Distbar bar</td><td><code>flex h-[5px] overflow-hidden rounded-full bg-line</code></td><td class="ir-px">5&#8239;px</td><td>Segments animated with <code>transition-[width]</code></td></tr>
</tbody>
</table>
</div>
</section>
<!-- ═══════════════════════════════════════════════════════════
SECTION 03 — ROW ANATOMY CLOSE-UPS
═══════════════════════════════════════════════════════════ -->
<section class="sec">
<h2 class="sec-h"><span class="sec-num">03</span>Row Anatomy · Close-Ups at ~100% Scale</h2>
<p class="sec-intro">Four row types at near-real pixel sizes. These are the reference renderings developers check against when implementing <code>ConversationTimeline.svelte</code> (or its successor <code>ThumbnailRow.svelte</code>).</p>
<!-- Type A: Portrait letter with summary + tags -->
<div class="cu">
<div class="cu-t">Type A · Portrait letter with summary + tags</div>
<div class="cu-row">
<div class="cu-thumb-cell"><div class="cu-thumb portrait kurrent"><div class="cu-thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div></div>
<div class="cu-body">
<div class="cu-title">W-0397 2. September 1923 B.Lichterfelde</div>
<div class="cu-summary">von Elsbeth geschriebener Kommentar, den Herbert zum Brief erzählte — Notiz auf der Rückseite</div>
<div class="cu-meta"><span class="dir">→ ausgehend</span><span>an Herbert Cram</span><span class="sep">·</span><span>📍 B.Lichterfelde</span><span class="sep">·</span><span class="cu-tag">Verlag</span><span class="cu-tag">Familie</span></div>
</div>
<div class="cu-rr"><div class="cu-date">2. September 1923</div><div class="cu-date-rel">vor 102 Jahren</div></div>
</div>
<div class="impl-ref">
<div class="impl-ref-hdr">Type A — Portrait Letter with Summary<span>rendered from Document + thumbnail URL</span></div>
<table>
<thead><tr><th>Part</th><th>Classes</th><th>Real</th><th>Note</th></tr></thead>
<tbody>
<tr><td>Row container</td><td><code>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</code></td><td class="ir-px">128&#8239;px min</td><td><code>&lt;a href="/documents/{id}"&gt;</code> · keyboard reachable</td></tr>
<tr><td>Thumbnail cell</td><td><code>w-[104px] h-[120px] flex items-center justify-center shrink-0</code></td><td class="ir-px">104 × 120</td><td>Centers any aspect ratio</td></tr>
<tr><td>Thumbnail img</td><td><code>w-[82px] h-[106px] rounded-[1px] shadow-sm ring-1 ring-white/80 transition-transform group-hover:-translate-y-[1px] group-hover:shadow-md</code></td><td class="ir-px">82 × 106 portrait</td><td><code>loading="lazy"</code> · <code>alt=""</code> (decorative, title covers meaning)</td></tr>
<tr><td>Title</td><td><code>font-serif text-base font-bold text-ink leading-[1.35] truncate</code></td><td class="ir-px">16&#8239;px / 700</td><td>Merriweather Bold</td></tr>
<tr><td>Summary</td><td><code>font-serif italic text-sm text-ink-2 leading-[1.55] line-clamp-2</code></td><td class="ir-px">14&#8239;px italic</td><td>Omit element entirely when <code>doc.summary</code> is empty — no placeholder</td></tr>
<tr><td>Summary quote marks</td><td><code>::before</code> &amp; <code>::after</code> pseudos, color <code>text-accent</code></td><td class="ir-px">22&#8239;px</td><td>„…" (German curly quotes)</td></tr>
<tr><td>Meta row</td><td><code>mt-0.5 flex flex-wrap gap-x-3 gap-y-1 text-xs text-ink-3 items-center</code></td><td class="ir-px">12&#8239;px</td><td>Separators use <code>·</code> with <code>text-line</code></td></tr>
<tr><td>Direction chip</td><td><code>text-[13px] font-extrabold text-primary</code> (out) · <code>text-accent</code> (in)</td><td class="ir-px">13&#8239;px / 800</td><td>"→ ausgehend" / "← eingehend" (word omitted in bilateral mode)</td></tr>
<tr><td>Tag chip</td><td><code>inline-flex items-center text-[10px] font-bold bg-accent/80 text-primary px-[7px] py-0.5 rounded-full</code></td><td class="ir-px">10&#8239;px / 700</td><td>Max 2 tags visible at 1440; 1 at 768; 0 at 320</td></tr>
<tr><td>Right column — date</td><td><code>font-serif text-sm font-bold text-ink-2 whitespace-nowrap text-right</code></td><td class="ir-px">14&#8239;px / 700</td><td>Intl.DateTimeFormat de-DE (see CLAUDE.md)</td></tr>
<tr><td>Right column — relative</td><td><code>text-[10px] text-ink-3 font-semibold</code></td><td class="ir-px">10&#8239;px</td><td>"vor X Jahren" — calculated client-side</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Type B: Portrait letter without summary -->
<div class="cu">
<div class="cu-t">Type B · Portrait letter without summary (clean variant)</div>
<div class="cu-row">
<div class="cu-thumb-cell"><div class="cu-thumb portrait kurrent"><div class="cu-thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div></div>
<div class="cu-body">
<div class="cu-title">W-0396 2. September 1923 B.Lichterfelde</div>
<div class="cu-meta"><span class="dir">→ ausgehend</span><span>an Herbert Cram</span><span class="sep">·</span><span>📍 B.Lichterfelde</span></div>
</div>
<div class="cu-rr"><div class="cu-date">2. September 1923</div><div class="cu-date-rel">vor 102 Jahren</div></div>
</div>
<div class="ann"><strong>No placeholder when summary is missing.</strong> The summary element is not rendered at all — row height still hits <code>min-h-[128px]</code> so the list stays rhythmic. Tags are also omitted when empty (no empty chip row).</div>
</div>
<!-- Type C: Postcard (landscape) -->
<div class="cu">
<div class="cu-t">Type C · Postcard · landscape thumbnail with stamp + postmark</div>
<div class="cu-row">
<div class="cu-thumb-cell"><div class="cu-thumb landscape postcard kurrent"><div class="cu-thumb-lines"><i></i><i></i><i></i><i></i></div></div></div>
<div class="cu-body">
<div class="cu-title">Ansichtskarte 20. August 1923 Thüringer Wald</div>
<div class="cu-summary">Urlaubsgruß, kurze Notiz über Wetter und geplante Rückkehr</div>
<div class="cu-meta"><span class="dir in">← eingehend</span><span>von Herbert Cram</span><span class="sep">·</span><span>📍 Thüringer Wald</span><span class="sep">·</span><span class="cu-kind">✉ Postkarte</span></div>
</div>
<div class="cu-rr"><div class="cu-date">20. August 1923</div><div class="cu-date-rel">vor 102 Jahren</div></div>
</div>
<div class="impl-ref">
<div class="impl-ref-hdr">Type C — Postcard (landscape)<span>aspect ratio detection + kind chip</span></div>
<table>
<thead><tr><th>Part</th><th>Classes</th><th>Real</th><th>Note</th></tr></thead>
<tbody>
<tr><td>Thumbnail</td><td><code>w-[104px] h-[72px] rounded-[1px] shadow-sm ring-1 ring-white/80</code></td><td class="ir-px">104 × 72 landscape</td><td>Aspect ratio detected server-side from PDF page 1 dimensions (w/h &gt; 1.1 → landscape)</td></tr>
<tr><td>Kind chip</td><td><code>inline-flex items-center text-[10px] font-bold uppercase tracking-wide bg-line text-ink-2 px-[7px] py-0.5 rounded-full</code></td><td class="ir-px">10&#8239;px / 700 uppercase</td><td>Paraglide: <code>m.doc_kind_postcard()</code> — shown only when thumbnail is landscape</td></tr>
<tr><td>Stamp corner</td><td>CSS pseudo-element on thumbnail — 16×18&#8239;px gradient square top-right 5&#8239;px</td><td class="ir-px">decorative</td><td>In production: rendered by the thumbnail service as part of the real scan; the CSS is only for spec rendering</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Type D: Multi-page letter with badge -->
<div class="cu">
<div class="cu-t">Type D · Multi-page letter with "N Seiten" badge</div>
<div class="cu-row">
<div class="cu-thumb-cell">
<div class="cu-thumb portrait kurrent"><div class="cu-thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div>
<span class="cu-thumb-badge">4 S.</span>
</div>
<div class="cu-body">
<div class="cu-title">W-0524 31. Juli 1923 Berlin</div>
<div class="cu-summary">Glückwunsch zum 60. Geburtstag, Bericht über den Verlag und den anstehenden Umzug nach B.Lichterfelde</div>
<div class="cu-meta"><span class="dir">→ ausgehend</span><span>an Walter Dieckmann</span><span class="sep">·</span><span>📍 Berlin</span><span class="sep">·</span><span class="cu-tag">Geburtstag</span><span class="cu-tag">Verlag</span></div>
</div>
<div class="cu-rr"><div class="cu-date">31. Juli 1923</div><div class="cu-date-rel">vor 102 Jahren</div></div>
</div>
<div class="impl-ref">
<div class="impl-ref-hdr">Type D — Page-count Badge<span>only when pages &gt; 1</span></div>
<table>
<thead><tr><th>Part</th><th>Classes</th><th>Real</th><th>Note</th></tr></thead>
<tbody>
<tr><td>Badge container</td><td><code>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</code></td><td class="ir-px">10&#8239;px / 700</td><td>Overlaps the thumbnail by 4&#8239;px right</td></tr>
<tr><td>Label</td><td>Paraglide: <code>m.doc_pages_count({ count })</code></td><td class="ir-px">"4 S."</td><td>Abbreviated form for the badge; full "4 Seiten" appears in the document detail page</td></tr>
<tr><td>Visibility rule</td><td>Render <code>{#if doc.pageCount &gt; 1}</code></td><td class="ir-px"></td><td>Never show "1 S."</td></tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════════════════════
SECTION 04 — DISTRIBUTION BAR CLOSE-UP
═══════════════════════════════════════════════════════════ -->
<section class="sec">
<h2 class="sec-h"><span class="sec-num">04</span>Distribution Bar · Close-Up</h2>
<p class="sec-intro">Only rendered in bilateral mode (both <code>senderId</code> and <code>receiverId</code> set). This component already exists in production as part of <code>ConversationTimeline.svelte</code> — 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.</p>
<div class="cu">
<div class="cu-t">Distribution bar · bilateral Walter ↔ Herbert</div>
<div style="background:#F2F0E8;border:1px solid #e4e2d7;border-radius:2px;padding:12px 18px;display:flex;flex-direction:column;gap:7px" role="img" aria-label="Briefverteilung: 87 von Walter de Gruyter, 56 von Herbert Cram">
<div style="display:flex;justify-content:space-between;font-size:14px;font-weight:700">
<span style="color:#012851">87 von Walter de Gruyter →</span>
<span style="color:#2F9E95">← 56 von Herbert Cram</span>
</div>
<div style="height:6px;display:flex;border-radius:4px;overflow:hidden;background:#E4E2D7">
<span style="background:#012851;width:60.8%"></span>
<span style="background:#2F9E95;width:39.2%"></span>
</div>
</div>
<div class="impl-ref">
<div class="impl-ref-hdr">Distribution Bar<span>role="img" + aria-label carries the data</span></div>
<table>
<thead><tr><th>Part</th><th>Classes</th><th>Real</th><th>Note</th></tr></thead>
<tbody>
<tr><td>Wrapper</td><td><code>flex flex-col gap-1 border-b border-line bg-muted px-[18px] py-2</code></td><td class="ir-px">8&#8239;px y padding</td><td><code>role="img"</code> · <code>aria-label</code> describes full distribution</td></tr>
<tr><td>Out label</td><td><code>inline-flex items-center gap-1 text-primary text-sm font-bold tabular-nums</code></td><td class="ir-px">14&#8239;px / 700</td><td>Format: "{count} von {sender} →"</td></tr>
<tr><td>In label</td><td><code>inline-flex items-center gap-1 text-accent text-sm font-bold tabular-nums</code></td><td class="ir-px">14&#8239;px / 700</td><td>Format: "← {count} von {receiver}"</td></tr>
<tr><td>Bar</td><td><code>flex h-[5px] overflow-hidden rounded-full bg-line</code></td><td class="ir-px">5&#8239;px tall</td><td>Segments use <code>transition-[width] duration-300 ease-out</code></td></tr>
<tr><td>Out segment</td><td><code>bg-primary h-full</code></td><td class="ir-px">width from API</td><td>Percentage computed backend-side from counts</td></tr>
<tr><td>In segment</td><td><code>bg-accent h-full</code></td><td class="ir-px">complementary</td><td>Never use <code>100% - out</code>; both come from the API separately</td></tr>
<tr><td>Mobile (320&#8239;px)</td><td>Labels stack with <code>flex-col gap-1</code>; bar stays full-width</td><td class="ir-px"></td><td>No truncation of counts — numbers must always be legible</td></tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════════════════════
SECTION 05 — ACCESSIBILITY CONTRACT
═══════════════════════════════════════════════════════════ -->
<section class="sec">
<h2 class="sec-h"><span class="sec-num">05</span>Accessibility Contract · WCAG AA/AAA</h2>
<p class="sec-intro">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 <code>tab</code>-through-list semantics.</p>
<div class="impl-ref">
<div class="impl-ref-hdr">Light Mode — Contrast Verification<span>layout.css tokens</span></div>
<table>
<thead><tr><th>Pair</th><th>Value</th><th>Ratio</th><th>WCAG</th></tr></thead>
<tbody>
<tr><td>Title (ink on surface)</td><td><code>#1A1A1A on #ffffff</code></td><td class="ir-px">19.6:1</td><td>AAA ✓</td></tr>
<tr><td>Summary (ink-2 on surface)</td><td><code>#444444 on #ffffff</code></td><td class="ir-px">9.7:1</td><td>AAA ✓ (body)</td></tr>
<tr><td>Meta (ink-3 on surface)</td><td><code>#666666 on #ffffff</code></td><td class="ir-px">5.7:1</td><td>AA ✓</td></tr>
<tr><td>Direction out (primary on surface)</td><td><code>#002850 on #ffffff</code></td><td class="ir-px">14.5:1</td><td>AAA ✓</td></tr>
<tr><td>Direction in (accent on surface)</td><td><code>#2F9E95 on #ffffff</code></td><td class="ir-px">4.6:1</td><td>AA ✓ (normal)</td></tr>
<tr><td>Tag chip (primary on mint)</td><td><code>#002850 on #a6dad8</code></td><td class="ir-px">8.1:1</td><td>AAA ✓</td></tr>
<tr><td>Quote marks (accent on surface)</td><td><code>#a6dad8 decorative</code></td><td class="ir-px">n/a</td><td>Decorative — summary text carries meaning</td></tr>
<tr><td>Focus ring (primary on surface)</td><td><code>#002850 on #ffffff, 2px offset</code></td><td class="ir-px">14.5:1</td><td>AAA ✓</td></tr>
</tbody>
</table>
</div>
<div class="impl-ref" style="margin-top:16px">
<div class="impl-ref-hdr">Dark Mode — Contrast Verification<span>remap via data-theme="dark"</span></div>
<table>
<thead><tr><th>Pair</th><th>Value</th><th>Ratio</th><th>WCAG</th></tr></thead>
<tbody>
<tr><td>Title (ink on surface-dark)</td><td><code>#f0efe9 on #011a30</code></td><td class="ir-px">15.1:1</td><td>AAA ✓</td></tr>
<tr><td>Summary (ink-2 on surface-dark)</td><td><code>#c5cbd4 on #011a30</code></td><td class="ir-px">11.2:1</td><td>AAA ✓</td></tr>
<tr><td>Meta (ink-3 on surface-dark)</td><td><code>#9ca3af on #011a30</code></td><td class="ir-px">7.8:1</td><td>AAA ✓ (body)</td></tr>
<tr><td>Direction out (mint on canvas)</td><td><code>#a1dcd8 on #010e1e</code></td><td class="ir-px">9.6:1</td><td>AAA ✓</td></tr>
<tr><td>Direction in (turquoise on canvas)</td><td><code>#00c7b1 on #010e1e</code></td><td class="ir-px">6.8:1</td><td>AA ✓</td></tr>
<tr><td>Tag chip (turquoise on tint)</td><td><code>#00c7b1 on rgba(0,199,177,.2)</code></td><td class="ir-px">6.3:1</td><td>AA ✓</td></tr>
</tbody>
</table>
</div>
<div class="ann"><strong>Non-negotiable accessibility rules.</strong>
<ul>
<li>Row is rendered as <code>&lt;a href="/documents/{id}"&gt;</code> — never <code>&lt;div onclick&gt;</code>. Keyboard Tab enters, Enter opens, Shift-Tab leaves.</li>
<li>Focus ring: <code>focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2</code> — always visible on keyboard focus, never on mouse click.</li>
<li>Thumbnail <code>&lt;img alt=""&gt;</code> — empty alt because the title next to it names the letter. A descriptive alt would be announced twice.</li>
<li>Direction glyph is color <em>and</em> shape (arrow direction). Never rely on color alone — the arrow "→" vs "←" carries meaning even in monochrome.</li>
<li>Distribution bar uses <code>role="img"</code> with a full-sentence aria-label. Screen readers hear the whole distribution in one announcement, not each half.</li>
<li>Minimum body text 14&#8239;px; minimum meta text 12&#8239;px. Never below 12&#8239;px for any visible text.</li>
<li>Touch target: 128&#8239;px row height ≫ 44&#8239;px WCAG minimum. Comfortable for senior users on phones.</li>
<li><code>prefers-reduced-motion</code>: hover lift on thumbnail collapses to <code>transition-duration: 0.01ms</code>. Required (project CLAUDE.md + WCAG 2.3.3).</li>
</ul>
</div>
</section>
<!-- ═══════════════════════════════════════════════════════════
SECTION 06 — IMPLEMENTATION NOTES
═══════════════════════════════════════════════════════════ -->
<section class="sec">
<h2 class="sec-h"><span class="sec-num">06</span>Implementation Notes — Data, Thumbnails, Routing</h2>
<div class="impl-ref">
<div class="impl-ref-hdr">Data contract — fields read per row<span>/api/documents/conversation</span></div>
<table>
<thead><tr><th>Field</th><th>From</th><th>Used for</th><th>Fallback</th></tr></thead>
<tbody>
<tr><td><code>id</code></td><td>Document</td><td>Row key, href</td><td>required</td></tr>
<tr><td><code>title</code></td><td>Document</td><td>Row title</td><td><code>originalFilename</code></td></tr>
<tr><td><code>summary</code></td><td>Document</td><td>Quote line (omit when empty)</td><td>element not rendered</td></tr>
<tr><td><code>documentDate</code></td><td>Document</td><td>Year group, right-column date, relative time</td><td>"—" placeholder, year group "Ohne Datum"</td></tr>
<tr><td><code>location</code></td><td>Document</td><td>Meta line</td><td>hidden</td></tr>
<tr><td><code>sender</code> / <code>receivers</code></td><td>Document</td><td>Direction + counterpart name</td><td>direction omitted, name = <code>m.conv_no_party()</code></td></tr>
<tr><td><code>tags[]</code></td><td>Document</td><td>Meta line (max 2 at 1440, 1 at 768, 0 at 320)</td><td>no chips rendered</td></tr>
<tr><td><code>pageCount</code></td><td>Document (new, from thumbnail service)</td><td>Badge when <code>&gt; 1</code></td><td>no badge</td></tr>
<tr><td><code>thumbnailUrl</code></td><td>Document (new, from thumbnail service)</td><td><code>&lt;img src&gt;</code></td><td>skeleton until fetched</td></tr>
<tr><td><code>thumbnailAspect</code></td><td>Document (new, from thumbnail service)</td><td>portrait / landscape class</td><td>defaults to portrait</td></tr>
</tbody>
</table>
</div>
<div class="impl-ref" style="margin-top:16px">
<div class="impl-ref-hdr">Thumbnail service — new endpoints<span>depends on open issue "thumbnail generation"</span></div>
<table>
<thead><tr><th>Concern</th><th>Decision</th><th>Note</th></tr></thead>
<tbody>
<tr><td>Storage</td><td>MinIO bucket <code>thumbnails</code></td><td>Mirrors document ID path; WEBP at 2× target resolution</td></tr>
<tr><td>URL</td><td><code>/api/documents/{id}/thumbnail</code></td><td>Redirects (302) to a presigned MinIO URL · <code>Cache-Control: public, max-age=2592000</code> (30 d)</td></tr>
<tr><td>Aspect</td><td>Computed once on generation, persisted as <code>Document.thumbnailAspect</code> enum <code>PORTRAIT \| LANDSCAPE</code></td><td>Threshold w/h &gt; 1.1 → LANDSCAPE</td></tr>
<tr><td>Page count</td><td>Persisted as <code>Document.pageCount</code> on upload / reprocess</td><td>Not computed client-side</td></tr>
<tr><td>Loading strategy</td><td><code>&lt;img loading="lazy" decoding="async"&gt;</code> with intersection observer for rows below the fold</td><td>Skeleton state until <code>onload</code> fires</td></tr>
<tr><td>Fallback</td><td>Paper-coloured placeholder (matches thumbnail gradient) with document icon</td><td>Never break the row layout</td></tr>
</tbody>
</table>
</div>
<div class="impl-ref" style="margin-top:16px">
<div class="impl-ref-hdr">Component structure<span>new files</span></div>
<table>
<thead><tr><th>File</th><th>Responsibility</th><th>Replaces</th></tr></thead>
<tbody>
<tr><td><code>ThumbnailRow.svelte</code></td><td>Single row with thumbnail, title, summary, meta, right column</td><td>Row rendering inside <code>ConversationTimeline.svelte</code></td></tr>
<tr><td><code>DistributionBar.svelte</code></td><td>The bilateral distribution bar</td><td>Lifts existing markup out of <code>ConversationTimeline.svelte</code></td></tr>
<tr><td><code>YearDivider.svelte</code></td><td>Year number + Briefe count</td><td>Already exists; no change required</td></tr>
<tr><td><code>ConversationTimeline.svelte</code></td><td>Orchestrator · renders distribution bar + year dividers + <code>ThumbnailRow</code>s</td><td>Simplified — no longer does row markup directly</td></tr>
<tr><td><code>DocumentThumbnail.svelte</code></td><td>Reusable thumbnail element with lazy-load + aspect + page badge</td><td>new · also usable on /documents list pages</td></tr>
</tbody>
</table>
</div>
<div class="ann"><strong>Shipping order.</strong>
<ul>
<li><b>Phase 1</b> — land <code>ThumbnailRow</code>, <code>DistributionBar</code> (extracted), and new typography/spacing without real thumbnails. Thumbnail cell renders the skeleton permanently. Ship and observe.</li>
<li><b>Phase 2</b> — wire up thumbnail service (open issue "PDF thumbnail generation"). Replace skeleton with real thumbnails. Add <code>thumbnailAspect</code> + <code>pageCount</code> to the <code>Document</code> entity and the <code>/api/documents/conversation</code> response.</li>
<li><b>Phase 3</b> — add lazy-loading + intersection observer for rows outside viewport. Measure perf on 851-letter lists.</li>
</ul>
</div>
</section>
</div>
</body>
</html>