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>
1074 lines
97 KiB
HTML
1074 lines
97 KiB
HTML
<!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 px mobile to 1440 px desktop, light and dark. Serves both the millennial audience (25–42) and the senior family audience (60 +) — 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 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 02 are scaled to ~55 % 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 03 are rendered at ~100 % 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 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 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 px rule above/below.</div></div>
|
||
<div class="cg"><div class="cg-t">C · Row list</div><div class="cg-b">Single <code><ul></code> per year group. Each row is an <code><a></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 × 120 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 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 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 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 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 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 px min · 20 × 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 px > WCAG 44 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 × 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 px</b> (mobile S) → <b>768 px</b> (tablet) → <b>1440 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 & 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 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 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 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=&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=…&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 px: labels stack vertically with a 4 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&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&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 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 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 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 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 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 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 px min</td><td><code><a href="/documents/{id}"></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 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 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> & <code>::after</code> pseudos, color <code>text-accent</code></td><td class="ir-px">22 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 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 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 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 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 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 > 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 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 px gradient square top-right 5 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 > 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 px / 700</td><td>Overlaps the thumbnail by 4 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 > 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 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 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 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 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 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><a href="/documents/{id}"></code> — never <code><div onclick></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><img alt=""></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 px; minimum meta text 12 px. Never below 12 px for any visible text.</li>
|
||
<li>Touch target: 128 px row height ≫ 44 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>> 1</code></td><td>no badge</td></tr>
|
||
<tr><td><code>thumbnailUrl</code></td><td>Document (new, from thumbnail service)</td><td><code><img src></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 > 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><img loading="lazy" decoding="async"></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>
|