Files
familienarchiv/docs/specs/korrespondenz-redesign-spec.html
2026-04-14 23:21:15 +02:00

1008 lines
65 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Korrespondenz — Final Design Spec · Familienarchiv</title>
<style>
*,*::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}
.doc{max-width:1440px;margin:0 auto;padding:48px 32px}
/* ── Masthead ─── */
.mast{background:#0D2240;border-radius:10px;padding:32px 40px;margin-bottom:48px}
.mast-top{display:flex;align-items:flex-start;justify-content:space-between;gap:24px;margin-bottom:16px}
.mast h1{font-size:22px;font-weight:900;color:#fff;letter-spacing:-.4px;margin-bottom:6px}
.mast p{font-size:12px;color:rgba(255,255,255,.5);max-width:620px;line-height:1.7}
.mast-badge{font-size:9px;font-weight:800;padding:3px 9px;border-radius:20px;text-transform:uppercase;letter-spacing:.8px;white-space:nowrap;flex-shrink:0;margin-top:4px}
.mb-final{background:#A6DAD8;color:#002850}
.decisions{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-top:20px;border-top:1px solid rgba(255,255,255,.1);padding-top:16px}
.dec{background:rgba(255,255,255,.06);border-radius:6px;padding:10px 12px}
.dec-label{font-size:7px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:rgba(255,255,255,.35);margin-bottom:5px}
.dec-value{font-size:9.5px;font-weight:700;color:#fff;line-height:1.5}
.dec-value s{color:rgba(255,255,255,.3);font-weight:400}
/* ── Section headings ─── */
.sec{margin-bottom:64px}
.sec+.sec{border-top:2px dashed #C8C4BE;padding-top:56px}
.sec-h{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:1.2px;color:#888;margin-bottom:20px;display:flex;align-items:center;gap:10px}
.sec-h::after{content:'';flex:1;height:1px;background:#D8D4CE}
.sec-num{background:#0D2240;color:#fff;font-size:9px;font-weight:900;padding:2px 7px;border-radius:10px}
/* ── Screen grid ─── */
.sg{display:grid;gap:20px;align-items:start}
.sg-3{grid-template-columns:1fr 1fr 1fr}
.sg-2{grid-template-columns:1fr 1fr}
.sg-2a{grid-template-columns:1.3fr 1fr}
.sg-mob{grid-template-columns:1fr 220px}
.sb{display:flex;flex-direction:column}
.sl{font-size:9px;font-weight:800;color:#888;text-transform:uppercase;letter-spacing:1.5px;margin-bottom:6px;display:flex;align-items:center;gap:6px}
.sz{background:#E8E4DF;color:#666;padding:1px 5px;border-radius:3px;font-size:8px}
.state{padding:1px 6px;border-radius:3px;font-size:8px;font-weight:700}
.st-empty{background:#FEF3C7;color:#92400E}
.st-partial{background:#DBEAFE;color:#1E40AF}
.st-active{background:#DCFCE7;color:#166534}
.sc{font-size:8.5px;color:#888;margin-top:6px;font-style:italic;line-height:1.5}
/* ── Annotation callouts ─── */
.ann-wrap{position:relative}
.ann{display:inline-block;font-size:7.5px;font-weight:700;color:#C2410C;background:#FFF7ED;border:1px solid #FDBA74;border-radius:3px;padding:1px 5px;white-space:nowrap}
.ann-block{background:#FFF7ED;border:1px solid #FDBA74;border-radius:5px;padding:8px 10px;font-size:10px;color:#7C2D12;line-height:1.5;margin-top:10px}
.ann-block strong{font-weight:800}
.ann-block ul{padding-left:14px;display:flex;flex-direction:column;gap:3px;margin-top:5px}
/* ── 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:24px;background:#E8E4DF;border-bottom:1px solid #C8C4BE;display:flex;align-items:center;padding:0 9px;gap:4px}
.dot{width:7px;height:7px;border-radius:50%;background:#C8C4BE}
.dot.r{background:#F87171}.dot.y{background:#FCD34D}.dot.g{background:#4ADE80}
.urlbar{flex:1;height:11px;background:#D8D4CE;border-radius:3px;margin-left:6px;display:flex;align-items:center;padding:0 5px}
.urlbar span{font-size:7.5px;color:#888;font-family:monospace}
.N{height:42px;background:#0D2240;display:flex;align-items:center;padding:0 16px;gap:12px;flex-shrink:0}
.logo{font-size:9px;font-weight:900;color:#fff;letter-spacing:1px}
.nl{font-size:7.5px;color:rgba(255,255,255,.4);font-weight:700;text-transform:uppercase;letter-spacing:.5px}
.nl.on{color:#fff;border-bottom:2px solid #A6DAD8;padding-bottom:2px}
.nr{margin-left:auto;display:flex;gap:7px;align-items:center}
.nico{width:20px;height:20px;background:rgba(255,255,255,.1);border-radius:4px}
.MAIN{padding:14px 18px;display:flex;flex-direction:column;gap:10px;background:#ECEAE4}
/* ── Strip ─── */
.STRIP-R1{background:#fff;border-bottom:1px solid #EAE7E0;padding:9px 18px;display:flex;align-items:flex-end;gap:9px}
.STRIP-R2{background:#F7F5F2;border-bottom:1.5px solid #E0DDD6;padding:5px 18px;display:flex;align-items:center;gap:10px}
.FIELD{flex:1;min-width:0}
.FL{font-size:7px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#888;margin-bottom:3px}
.FL .opt{font-weight:400;font-style:italic;color:#C8C4BE;margin-left:3px}
.FI{height:30px;border:1.5px solid #D1D5DB;border-radius:3px;background:#fff;display:flex;align-items:center;padding:0 8px;font-size:9px;color:#333;gap:6px}
.FI.set{border-color:#002850}
.FI.focus{border-color:#002850;box-shadow:0 0 0 2px rgba(0,40,80,.07)}
.FI.placeholder{color:#C8C4BE;font-style:italic}
.SWAP{width:28px;height:28px;border:1.5px solid #C8C4BE;border-radius:3px;background:#F0EDE8;display:flex;align-items:center;justify-content:center;font-size:11px;color:#888;flex-shrink:0;align-self:flex-end}
.SWAP.on{border-color:#002850;color:#002850}
/* Row 2 tokens */
.R2-LABEL{font-size:7px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#AAA;white-space:nowrap}
.R2-DATE{height:22px;border:1.5px solid #D1D5DB;border-radius:3px;background:#fff;display:flex;align-items:center;padding:0 7px;font-size:8px;color:#AAA;font-style:italic;width:80px;flex-shrink:0}
.R2-DATE.set{color:#333;font-style:normal;border-color:#002850}
.R2-DASH{font-size:10px;color:#C8C4BE;flex-shrink:0}
.R2-COUNT{font-size:8px;font-weight:700;color:#888;white-space:nowrap;margin-left:auto}
.R2-COUNT.filtered{color:#002850}
.R2-SORT{height:22px;border:1.5px solid #D1D5DB;border-radius:3px;background:#fff;display:flex;align-items:center;padding:0 8px;font-size:8px;font-weight:700;color:#555;gap:3px;cursor:pointer;white-space:nowrap;flex-shrink:0}
.R2-SORT.active{border-color:#002850;color:#002850}
/* ── Suggestion dropdown ─── */
.SUGG{background:#fff;border:1.5px solid #002850;border-top:none;border-radius:0 0 4px 4px;box-shadow:0 5px 12px rgba(0,0,0,.1)}
.SUGG-H{font-size:7px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#AAA;padding:5px 9px 3px;border-bottom:1px solid #F0EDE8}
.SUGG-R{display:flex;align-items:center;gap:7px;padding:6px 9px;cursor:pointer;border-bottom:1px solid #F7F5F2}
.SUGG-R:hover{background:#F7F5F2}
.SUGG-R:last-child{border-bottom:none}
.SUGG-NAME{flex:1;font-size:8.5px;font-weight:700;color:#0D2240}
.SUGG-BAR{width:40px;height:3px;border-radius:2px;background:#E0DDD6;overflow:hidden}
.SUGG-FILL{height:100%;background:#002850}
.SUGG-CNT{font-size:7.5px;color:#AAA}
.SUGG-ALL{font-size:7.5px;color:#888;font-style:italic}
/* Divider row */
.SUGG-DIV{font-size:7px;color:#C8C4BE;padding:4px 9px;border-top:1px solid #F0EDE8}
.AV{border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:900;flex-shrink:0}
.AV-xs{width:16px;height:16px;font-size:5.5px}
.AV-sm{width:20px;height:20px;font-size:6.5px}
.AV-navy{background:#002850;color:#fff}
.AV-outline{background:#fff;color:#444;border:1.5px solid #C8C4BE}
.AV-sand{background:#E8E4DF;color:#666}
/* ── Asymmetry bar ─── */
.ASYM{padding:8px 18px;background:#F7F5F2;border-bottom:1px solid #E8E4DF;display:flex;flex-direction:column;gap:4px}
.ASYM-LABELS{display:flex;justify-content:space-between;font-size:7.5px;font-weight:700}
.ASYM-LABELS .s{color:#002850}
.ASYM-LABELS .r{color:#0F5755}
.ASYM-BAR{height:5px;border-radius:3px;background:#E0DDD6;overflow:hidden;display:flex}
.ASYM-OUT{height:100%;background:#002850}
.ASYM-IN{height:100%;background:#A6DAD8}
/* ── Log ─── */
.LOG{background:#fff;border:1.5px solid #E0DDD6;border-radius:4px;overflow:hidden}
.LOG-YEAR{padding:6px 14px;background:#F0EDE6;border-top:2px solid #C8C4BE;border-bottom:1px solid #D8D4CE;display:flex;align-items:baseline;gap:8px}
.LOG-YEAR-N{font-size:15px;font-weight:900;color:#002850;letter-spacing:-.5px}
.LOG-YEAR-C{font-size:7.5px;color:#AAA;font-weight:700}
.LOG-ROW{padding:10px 14px;border-left:3px solid transparent;border-bottom:1px solid #EDEBE4;display:flex;align-items:flex-start;gap:9px;cursor:pointer;text-decoration:none}
.LOG-ROW:hover{background:#F7F5F2}
.LOG-ROW:last-child{border-bottom:none}
.LOG-ROW.out{border-left-color:#002850}
.LOG-ROW.in{border-left-color:#A6DAD8}
.LOG-DIR{font-size:9px;font-weight:900;flex-shrink:0;padding-top:1px;width:14px}
.LOG-DIR.out{color:#002850}
.LOG-DIR.in{color:#0F5755}
.LOG-BODY{flex:1;min-width:0}
.LOG-TITLE{font-size:9px;font-weight:700;color:#0D2240;margin-bottom:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.LOG-META{font-size:7.5px;color:#888;display:flex;align-items:center;gap:5px}
.LOG-META-SEP{color:#D1CCC8}
.LOG-ACTION{flex-shrink:0;width:22px;height:22px;display:flex;align-items:center;justify-content:center;border-radius:3px;background:#F7F5F2;border:1px solid #E8E4DF;opacity:0}
.LOG-ROW:hover .LOG-ACTION{opacity:1}
/* Status dot */
.S-DOT{width:6px;height:6px;border-radius:50%;flex-shrink:0}
.S-UPLOADED{background:#22C55E}
.S-TRANSCRIBED{background:#3B82F6}
.S-REVIEWED{background:#A855F7}
.S-ARCHIVED{background:#6B7280}
.S-PLACEHOLDER{background:#F59E0B}
/* ── Empty state ─── */
.EMPTY{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:56px 24px;text-align:center}
.EMPTY-ICON{width:52px;height:52px;background:#F0EDE6;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:24px;margin-bottom:16px}
.EMPTY-H{font-size:14px;font-weight:800;color:#0D2240;font-family:Georgia,serif;margin-bottom:5px}
.EMPTY-S{font-size:10.5px;color:#888;max-width:280px;line-height:1.7;margin-bottom:22px}
.EMPTY-INPUT{width:260px;height:36px;border:2px solid #002850;border-radius:4px;display:flex;align-items:center;padding:0 12px;font-size:9px;color:#C8C4BE;font-style:italic;gap:8px;box-shadow:0 0 0 4px rgba(0,40,80,.06);margin-bottom:18px}
.EMPTY-OR{font-size:8px;text-transform:uppercase;letter-spacing:1px;color:#C8C4BE;font-weight:700;margin-bottom:10px}
.EMPTY-RECENT-L{font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#AAA;margin-bottom:8px}
.EMPTY-CHIPS{display:flex;gap:6px;flex-wrap:wrap;justify-content:center}
.EMPTY-CHIP{display:inline-flex;align-items:center;gap:5px;background:#fff;border:1.5px solid #E0DDD6;border-radius:20px;padding:5px 11px;font-size:8.5px;font-weight:700;color:#0D2240;cursor:pointer;transition:border-color .1s}
.EMPTY-CHIP:hover{border-color:#002850}
/* ── Single-person hint ─── */
.SPMODE{padding:6px 18px;background:#FFF7ED;border-bottom:1px solid #FDBA74;font-size:8px;color:#92400E;display:flex;align-items:center;gap:5px}
.SPMODE strong{font-weight:800}
/* ── Mobile chrome ─── */
.WF-M{background:#fff;border:2px solid #B8B4AE;border-radius:16px;overflow:hidden;box-shadow:0 4px 18px rgba(0,0,0,.08);width:220px}
.WF-M-STATUS{height:18px;background:#0D2240;display:flex;align-items:center;justify-content:space-between;padding:0 10px}
.WF-M-TIME{font-size:7px;color:#fff;font-weight:700}
.WF-M-ICONS{display:flex;gap:3px}
.WF-M-ICON{width:6px;height:6px;background:rgba(255,255,255,.5);border-radius:1px}
.N-M{height:40px;background:#0D2240;display:flex;align-items:center;padding:0 12px;justify-content:space-between}
.MAIN-SM{padding:10px 12px;display:flex;flex-direction:column;gap:8px;background:#ECEAE4}
.STRIP-R1-SM{background:#fff;border-bottom:1px solid #EAE7E0;padding:8px 12px;display:flex;flex-direction:column;gap:6px}
.STRIP-R2-SM{background:#F7F5F2;border-bottom:1.5px solid #E0DDD6;padding:5px 12px;display:flex;align-items:center;gap:7px}
.PH{height:7px;background:#E8E4DF;border-radius:2px;margin-bottom:4px}
.w80{width:80%}.w70{width:70%}.w60{width:60%}.w50{width:50%}.w40{width:40%}
/* ── Changelog / decision list ─── */
.CHANGES{background:#fff;border:1.5px solid #E0DDD6;border-radius:8px;padding:20px 24px;margin-bottom:40px}
.CHANGES h2{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid #E8E4DF}
.CHANGES-GRID{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.C-COL h3{font-size:10px;font-weight:800;color:#444;margin-bottom:8px}
.C-COL ul{list-style:none;display:flex;flex-direction:column;gap:5px}
.C-COL ul li{font-size:11px;color:#555;padding-left:16px;position:relative;line-height:1.5}
.C-COL.new li::before{content:'✦';position:absolute;left:0;color:#002850;font-size:8px}
.C-COL.remove li::before{content:'✗';position:absolute;left:0;color:#DC2626}
.C-COL.keep li::before{content:'→';position:absolute;left:0;color:#888}
/* ── Impl notes ─── */
.IMPL{background:#0D2240;border-radius:8px;padding:20px 24px;margin-top:48px}
.IMPL h2{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:rgba(255,255,255,.4);margin-bottom:16px;padding-bottom:10px;border-bottom:1px solid rgba(255,255,255,.08)}
.IMPL-GRID{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px}
.IMPL-COL h3{font-size:9.5px;font-weight:800;color:rgba(255,255,255,.6);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px}
.IMPL-COL ul{list-style:none;display:flex;flex-direction:column;gap:5px}
.IMPL-COL ul li{font-size:10.5px;color:rgba(255,255,255,.75);padding-left:14px;position:relative;line-height:1.5}
.IMPL-COL ul li::before{content:'';position:absolute;left:0;color:rgba(255,255,255,.3)}
.IMPL-COL code{font-family:monospace;font-size:9.5px;background:rgba(255,255,255,.08);padding:1px 4px;border-radius:3px;color:#A6DAD8}
/* ── Spec disclaimer ─── */
.spec-disclaimer{background:#FFF8E1;border:1.5px solid #FFC107;border-radius:6px;padding:11px 16px;font-size:11px;color:#6D4C00;margin-bottom:32px;line-height:1.6}
.spec-disclaimer strong{font-weight:800}
/* ── Agent Implementation Reference (impl-ref) ─────────────────────────
Appears after every visual section. Mockup CSS values are at ~55% scale
and must NOT be used as implementation values. Use these tables instead.
──────────────────────────────────────────────────────────────────────── */
.impl-ref{background:#0d1117;border-radius:8px;margin-top:20px;overflow:hidden;border:1px solid #30363d}
.impl-ref-hdr{background:#161b22;padding:9px 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:8px 14px;border-bottom:1px solid #21262d}
.impl-ref td{padding:6px 14px;border-bottom:1px solid #161b22;vertical-align:top;line-height:1.6;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:190px}
.impl-ref td code{font-family:'SFMono-Regular',Consolas,monospace;font-size:9.5px;background:#161b22;color:#a5d6ff;padding:1px 5px;border-radius:3px;white-space:nowrap}
.impl-ref .ir-px{color:#7ee787;font-family:monospace;font-size:9.5px}
</style>
</head>
<body>
<div class="doc">
<!-- ══════════════════════════════════
MASTHEAD
══════════════════════════════════ -->
<div class="mast">
<div class="mast-top">
<div>
<h1>Korrespondenz — Final Design Spec</h1>
<p>Correspondence Log with Compact Strip + Permanent Second Row. All design decisions from the exploration session locked in.</p>
</div>
<span class="mast-badge mb-final">Final · Ready for implementation</span>
</div>
<div class="decisions">
<div class="dec">
<div class="dec-label">Page name</div>
<div class="dec-value"><s>Gespräche</s><br>→ Korrespondenz</div>
</div>
<div class="dec">
<div class="dec-label">Timeline renderer</div>
<div class="dec-value"><s>Chat bubbles</s><br>→ Correspondence Log</div>
</div>
<div class="dec">
<div class="dec-label">Filter</div>
<div class="dec-value">Compact strip<br>2 rows · always visible</div>
</div>
<div class="dec">
<div class="dec-label">Receiver</div>
<div class="dec-value"><s>Required</s><br>→ Optional (single-person mode)</div>
</div>
<div class="dec">
<div class="dec-label">Discovery</div>
<div class="dec-value">Top correspondents<br>in person B dropdown</div>
</div>
<div class="dec">
<div class="dec-label">Ghost cards</div>
<div class="dec-value"><s>Considered</s><br>→ Dropped</div>
</div>
<div class="dec">
<div class="dec-label">Hub sidebar</div>
<div class="dec-value"><s>Considered</s><br>→ Dropped (person detail)</div>
</div>
<div class="dec">
<div class="dec-label">Secondary filters</div>
<div class="dec-value">Permanent row 2<br>Von / Bis / Sort / Count</div>
</div>
</div>
</div>
<div class="spec-disclaimer">
<strong>📐 Mockup scale notice —</strong> all font-size, height, padding, and spacing values in the mockup CSS below are scaled to ~55% of their real implementation values so they fit on screen. <strong>Do not copy sizes from the mockup HTML/CSS.</strong> Each section ends with an <strong>⚙ Implementation Reference</strong> table listing the exact Tailwind classes and real pixel values to use in code.
</div>
<!-- ══════════════════════════════════
CHANGES
══════════════════════════════════ -->
<div class="CHANGES">
<h2>What changes vs. current implementation</h2>
<div class="CHANGES-GRID">
<div class="C-COL new">
<h3>New / changed</h3>
<ul>
<li>Nav label: "Gespräche" → "Korrespondenz" (i18n key update)</li>
<li>Filter card → compact 2-row strip (redesign ConversationFilterBar)</li>
<li>Row 2 always visible: Von · Bis date inputs + live letter count + sort toggle</li>
<li>Receiver field: required → optional; single-person mode works without it</li>
<li>Person B input shows top correspondents as suggestions when person A is set (uses existing <code>restrictToCorrespondentsOf</code> — just surface the results)</li>
<li>Timeline renderer: chat bubbles → correspondence log cards (redesign ConversationTimeline)</li>
<li>Log card: direction arrow (→/←) + left border color + title + date + sender/recipient + status dot</li>
<li>Asymmetry bar visible when both persons selected</li>
<li>Empty state: centered prompt with person search + recent chips (remove "select both" gate)</li>
<li>Single-person mode hint strip: amber bar explaining scope</li>
</ul>
</div>
<div class="C-COL keep">
<h3>Kept unchanged</h3>
<ul>
<li>PersonTypeahead component — used in both fields as-is</li>
<li><code>restrictToCorrespondentsOf</code> prop — drives the suggestions</li>
<li>Swap button (⇄) — same behaviour</li>
<li><code>/api/documents/conversation</code> endpoint — no backend change for bilateral view</li>
<li>URL params: <code>?senderId=…&receiverId=…&from=…&to=…&dir=…</code></li>
<li>SvelteKit navigation with <code>goto()</code></li>
<li>Year divider logic from ConversationTimeline</li>
<li>canWrite / new document link</li>
</ul>
</div>
</div>
</div>
<!-- ══════════════════════════════════
SECTION 1 — EMPTY STATE
══════════════════════════════════ -->
<div class="sec">
<div class="sec-h"><span class="sec-num">1</span> Empty state — no person selected</div>
<div class="sg sg-2a">
<div class="sb">
<div class="sl">Desktop <span class="sz">≥768px</span> <span class="state st-empty">No selection</span></div>
<div class="wf">
<div class="wf-bar"><div class="dot r"></div><div class="dot y"></div><div class="dot g"></div><div class="urlbar"><span>/korrespondenz</span></div></div>
<div class="N">
<span class="logo">FAMILIENARCHIV</span>
<span class="nl">Dokumente</span><span class="nl">Personen</span>
<span class="nl on">Korrespondenz</span>
<div class="nr"><div class="nico"></div><div class="nico"></div></div>
</div>
<!-- Strip: row 1 — both empty -->
<div class="STRIP-R1">
<div class="FIELD">
<div class="FL">Person</div>
<div class="FI placeholder"><span style="font-size:11px;color:#DDD">🔍</span> Name eingeben…</div>
</div>
<div class="SWAP"></div>
<div class="FIELD">
<div class="FL">Korrespondent <span class="opt">— optional</span></div>
<div class="FI placeholder" style="background:#F9F8F6;border-style:dashed">Alle Korrespondenten</div>
</div>
</div>
<!-- Strip: row 2 — disabled -->
<div class="STRIP-R2" style="opacity:.4;pointer-events:none">
<div class="R2-LABEL">Zeitraum</div>
<div class="R2-DATE">Von…</div>
<div class="R2-DASH"></div>
<div class="R2-DATE">Bis…</div>
<div class="R2-COUNT" style="margin-left:auto"></div>
<div class="R2-SORT">Neueste ↓</div>
</div>
<!-- Empty body -->
<div class="EMPTY">
<div class="EMPTY-ICON"></div>
<div class="EMPTY-H">Korrespondenz durchsuchen</div>
<div class="EMPTY-S">Wähle eine Person aus dem Archiv um deren Briefe zu sehen — mit oder ohne Korrespondent.</div>
<div class="EMPTY-INPUT"><span style="font-size:13px;color:#DDD">🔍</span> <span>Person suchen…</span></div>
<div class="EMPTY-OR">oder</div>
<div class="EMPTY-RECENT-L">Zuletzt geöffnet</div>
<div class="EMPTY-CHIPS">
<div class="EMPTY-CHIP"><div class="AV AV-xs AV-navy" style="margin-right:2px">OF</div>Otto Familienname</div>
<div class="EMPTY-CHIP"><div class="AV AV-xs AV-outline" style="margin-right:2px">MW</div>Maria Weber</div>
<div class="EMPTY-CHIP"><div class="AV AV-xs AV-outline" style="margin-right:2px">KF</div>Klaus Fischer</div>
</div>
</div>
</div>
<div class="sc">Row 2 dimmed but visible — communicates the feature exists before it's usable. Clicking the "Person suchen" input focuses the strip's Person field. Recent chips use localStorage; shown only if history exists.</div>
</div>
<div class="sb">
<div class="sl">Mobile <span class="sz">375px</span></div>
<div style="display:flex;justify-content:center">
<div class="WF-M">
<div class="WF-M-STATUS"><span class="WF-M-TIME">09:41</span><div class="WF-M-ICONS"><div class="WF-M-ICON"></div><div class="WF-M-ICON"></div><div class="WF-M-ICON"></div></div></div>
<div class="N-M"><span class="logo" style="font-size:8.5px">FAMILIENARCHIV</span><span style="color:rgba(255,255,255,.7);font-size:17px"></span></div>
<!-- Mobile strip: stacked -->
<div class="STRIP-R1-SM">
<div>
<div class="FL">Person</div>
<div class="FI placeholder" style="height:36px;font-size:9px"><span style="font-size:11px;color:#DDD">🔍</span> Name eingeben…</div>
</div>
<div>
<div class="FL">Korrespondent <span class="opt">— optional</span></div>
<div class="FI placeholder" style="height:36px;font-size:9px;background:#F9F8F6;border-style:dashed">Alle Korrespondenten</div>
</div>
</div>
<div class="STRIP-R2-SM" style="opacity:.4">
<div class="R2-LABEL">Zeitraum</div>
<div class="R2-DATE" style="width:60px;font-size:7.5px">Von…</div>
<div class="R2-DASH"></div>
<div class="R2-DATE" style="width:60px;font-size:7.5px">Bis…</div>
<div class="R2-SORT" style="font-size:7.5px;height:20px;margin-left:auto">Neueste ↓</div>
</div>
<div class="EMPTY" style="padding:32px 16px">
<div class="EMPTY-ICON" style="width:40px;height:40px;font-size:18px;margin-bottom:12px"></div>
<div class="EMPTY-H" style="font-size:12px">Korrespondenz durchsuchen</div>
<div class="EMPTY-S" style="font-size:9.5px;margin-bottom:16px">Person oben eingeben um Briefe zu sehen.</div>
<div class="EMPTY-CHIPS" style="gap:5px">
<div class="EMPTY-CHIP" style="font-size:8px;padding:4px 9px"><div class="AV AV-xs AV-navy" style="margin-right:2px">OF</div>Otto F.</div>
<div class="EMPTY-CHIP" style="font-size:8px;padding:4px 9px"><div class="AV AV-xs AV-outline" style="margin-right:2px">MW</div>Maria W.</div>
</div>
</div>
</div>
</div>
<div class="sc">Mobile: both person fields stacked full-width (44px touch target). Row 2 stacked beneath. Recent chips condensed to initials + first name.</div>
</div>
</div>
</div>
<div class="impl-ref">
<div class="impl-ref-hdr">Implementation Reference — Filter Strip &amp; Empty State <span>Real values · mockup above is ~55% scale</span></div>
<table>
<thead><tr><th>Element</th><th>Tailwind classes</th><th>Real size</th><th>Notes</th></tr></thead>
<tbody>
<tr>
<td>Strip Row 1 container</td>
<td><code>flex items-end gap-4 px-4 sm:px-6 py-3 bg-surface border-b border-line</code></td>
<td><span class="ir-px">h ~56px, px 1624px, py 12px</span></td>
<td>White background, 1px bottom border</td>
</tr>
<tr>
<td>Field label (.FL)</td>
<td><code>text-xs font-bold uppercase tracking-widest text-ink-2 mb-1.5</code></td>
<td><span class="ir-px">12px / 700</span></td>
<td>"optional" suffix: <code>font-normal not-italic text-ink-3</code></td>
</tr>
<tr>
<td>Text input / typeahead</td>
<td><code>h-10 w-full px-3 text-sm border border-line bg-surface text-ink placeholder:text-ink-3 focus:border-ink focus:ring-1 focus:ring-ink focus:outline-none</code></td>
<td><span class="ir-px">40px tall, 14px text</span></td>
<td>Optional field: <code>border-dashed bg-muted</code> when empty</td>
</tr>
<tr>
<td>Swap button</td>
<td><code>h-10 w-10 flex shrink-0 items-center justify-center border border-line text-ink-2 hover:text-ink hover:border-ink transition-colors self-end</code></td>
<td><span class="ir-px">40×40px</span></td>
<td>Active (both set): <code>border-ink text-ink</code></td>
</tr>
<tr>
<td>Strip Row 2 container</td>
<td><code>flex items-center gap-3 px-4 sm:px-6 py-2 bg-canvas border-b border-line</code></td>
<td><span class="ir-px">h ~36px, py 8px</span></td>
<td>When no person selected: <code>opacity-40 pointer-events-none</code></td>
</tr>
<tr>
<td>Row 2 period label</td>
<td><code>text-xs font-bold uppercase tracking-widest text-ink-3 shrink-0</code></td>
<td><span class="ir-px">12px / 700</span></td>
<td>"Zeitraum" label before the date fields</td>
</tr>
<tr>
<td>Date input (Von / Bis)</td>
<td><code>h-8 w-24 px-2 text-xs border border-line bg-surface text-ink placeholder:text-ink-3 focus:border-ink focus:outline-none</code></td>
<td><span class="ir-px">32px tall, 12px text, 96px wide</span></td>
<td>When set: <code>border-ink</code>. Format: dd.mm.yyyy</td>
</tr>
<tr>
<td>Letter count</td>
<td><code>ml-auto text-sm font-bold text-ink-2</code></td>
<td><span class="ir-px">14px / 700</span></td>
<td>When filtered (≠ total): <code>text-primary</code></td>
</tr>
<tr>
<td>Sort toggle button</td>
<td><code>h-8 px-3 text-xs font-bold border border-line text-ink-2 hover:text-ink hover:border-ink transition-colors</code></td>
<td><span class="ir-px">32px tall, 12px text</span></td>
<td>Active: <code>border-primary text-primary</code></td>
</tr>
<tr>
<td>Empty state icon circle</td>
<td><code>w-16 h-16 rounded-full bg-muted flex items-center justify-center text-3xl mb-4</code></td>
<td><span class="ir-px">64×64px, 30px icon</span></td>
<td></td>
</tr>
<tr>
<td>Empty state heading</td>
<td><code>font-serif text-xl font-bold text-ink mb-2</code></td>
<td><span class="ir-px">20px / 700</span></td>
<td></td>
</tr>
<tr>
<td>Empty state sub-text</td>
<td><code>text-sm text-ink-2 max-w-xs text-center leading-relaxed mb-6</code></td>
<td><span class="ir-px">14px</span></td>
<td></td>
</tr>
<tr>
<td>Recent person chip</td>
<td><code>inline-flex items-center gap-2 px-3 py-1.5 border border-line bg-surface text-sm font-semibold text-ink hover:border-primary transition-colors</code></td>
<td><span class="ir-px">h ~32px, 14px text</span></td>
<td>Avatar: <code>w-5 h-5 rounded-full text-[10px]</code></td>
</tr>
</tbody>
</table>
</div>
<!-- ══════════════════════════════════
SECTION 2 — SINGLE-PERSON MODE
══════════════════════════════════ -->
<div class="sec">
<div class="sec-h"><span class="sec-num">2</span> Single-person mode — Person A set, picking correspondent</div>
<div class="sg sg-3">
<!-- Suggestions open -->
<div class="sb">
<div class="sl">Person B input focused <span class="state st-partial">Suggestions shown</span></div>
<div class="wf">
<div class="wf-bar"><div class="dot r"></div><div class="dot y"></div><div class="dot g"></div><div class="urlbar"><span>/korrespondenz?senderId=otto-id</span></div></div>
<div class="N"><span class="logo">FAMILIENARCHIV</span><span class="nl">Dokumente</span><span class="nl">Personen</span><span class="nl on">Korrespondenz</span><div class="nr"><div class="nico"></div></div></div>
<div class="STRIP-R1">
<div class="FIELD">
<div class="FL">Person</div>
<div class="FI set"><div class="AV AV-xs AV-navy">OF</div>Otto Familienname</div>
</div>
<div class="SWAP on"></div>
<!-- Person B: focused, suggestion dropdown -->
<div class="FIELD" style="position:relative">
<div class="FL">Korrespondent <span class="opt">— optional</span></div>
<div class="FI focus"><span style="font-size:10px;color:#C8C4BE">🔍</span><span style="color:#C8C4BE;font-style:italic;font-size:8.5px">Ottos Korrespondenten…</span></div>
<div class="SUGG">
<div class="SUGG-H">Häufigste Korrespondenten</div>
<div class="SUGG-R">
<div class="AV AV-xs AV-outline">MW</div>
<div class="SUGG-NAME">Maria Weber</div>
<div class="SUGG-BAR"><div class="SUGG-FILL" style="width:88%"></div></div>
<div class="SUGG-CNT">32 Briefe</div>
</div>
<div class="SUGG-R">
<div class="AV AV-xs AV-outline">KF</div>
<div class="SUGG-NAME">Klaus Fischer</div>
<div class="SUGG-BAR"><div class="SUGG-FILL" style="width:50%"></div></div>
<div class="SUGG-CNT">11 Briefe</div>
</div>
<div class="SUGG-R">
<div class="AV AV-xs AV-outline">HM</div>
<div class="SUGG-NAME">Helene Müller</div>
<div class="SUGG-BAR"><div class="SUGG-FILL" style="width:18%"></div></div>
<div class="SUGG-CNT">4 Briefe</div>
</div>
<div class="SUGG-DIV">oder ohne Einschränkung</div>
<div class="SUGG-R">
<div class="AV AV-xs AV-sand"></div>
<div class="SUGG-NAME" style="font-style:italic;color:#888">Alle Korrespondenten Ottos</div>
<div class="SUGG-CNT SUGG-ALL">47 Briefe</div>
</div>
</div>
</div>
</div>
<div class="STRIP-R2">
<div class="R2-LABEL">Zeitraum</div>
<div class="R2-DATE">Von…</div>
<div class="R2-DASH"></div>
<div class="R2-DATE">Bis…</div>
<div class="R2-COUNT" style="margin-left:auto">47 Briefe</div>
<div class="R2-SORT">Neueste ↓</div>
</div>
<!-- Hint bar -->
<div class="SPMODE">
<span style="font-size:11px">📋</span>
<span><strong>Alle Briefe Ottos</strong> — wähle einen Korrespondenten oben um einzugrenzen</span>
</div>
<div class="MAIN">
<div class="LOG">
<div class="LOG-YEAR"><span class="LOG-YEAR-N">1943</span><span class="LOG-YEAR-C">5 Briefe</span></div>
<div class="LOG-ROW out"><div class="LOG-DIR out"></div><div class="LOG-BODY"><div class="LOG-TITLE">Brief an Maria — letzte Nachrichten</div><div class="LOG-META">3. Sept. 1943<span class="LOG-META-SEP">·</span>Maria Weber<div class="S-DOT S-UPLOADED" style="margin-left:3px"></div></div></div></div>
<div class="LOG-ROW out"><div class="LOG-DIR out"></div><div class="LOG-BODY"><div class="LOG-TITLE">Brief an Klaus Fischer</div><div class="LOG-META">12. Sept. 1943<span class="LOG-META-SEP">·</span>Klaus Fischer<div class="S-DOT S-ARCHIVED" style="margin-left:3px"></div></div></div></div>
<div class="LOG-ROW in"><div class="LOG-DIR in"></div><div class="LOG-BODY"><div class="LOG-TITLE">Antwort von Klaus</div><div class="LOG-META">18. Sept. 1943<span class="LOG-META-SEP">·</span>Klaus Fischer<div class="S-DOT S-REVIEWED" style="margin-left:3px"></div></div></div></div>
</div>
</div>
</div>
<div class="sc">Dropdown uses <code>restrictToCorrespondentsOf</code> — only actual correspondents shown. "Alle Korrespondenten" row is the explicit opt-out. Log shows immediately (no blank screen), hint bar explains scope.</div>
</div>
<!-- Log showing (no correspondent) -->
<div class="sb">
<div class="sl">Single-person log <span class="state st-partial">No correspondent</span></div>
<div class="wf">
<div class="wf-bar"><div class="dot r"></div><div class="dot y"></div><div class="dot g"></div><div class="urlbar"><span>/korrespondenz?senderId=otto-id</span></div></div>
<div class="N"><span class="logo">FAMILIENARCHIV</span><span class="nl">Dokumente</span><span class="nl">Personen</span><span class="nl on">Korrespondenz</span><div class="nr"><div class="nico"></div></div></div>
<div class="STRIP-R1">
<div class="FIELD"><div class="FL">Person</div><div class="FI set"><div class="AV AV-xs AV-navy">OF</div>Otto Familienname</div></div>
<div class="SWAP"></div>
<div class="FIELD"><div class="FL">Korrespondent <span class="opt">— optional</span></div><div class="FI placeholder" style="border-style:dashed">Alle Korrespondenten</div></div>
</div>
<div class="STRIP-R2">
<div class="R2-LABEL">Zeitraum</div>
<div class="R2-DATE">Von…</div>
<div class="R2-DASH"></div>
<div class="R2-DATE">Bis…</div>
<div class="R2-COUNT" style="margin-left:auto">47 Briefe</div>
<div class="R2-SORT">Neueste ↓</div>
</div>
<div class="SPMODE">
<span style="font-size:11px">📋</span>
<span><strong>Alle Briefe Ottos</strong> — wähle einen Korrespondenten oben um einzugrenzen</span>
</div>
<div class="MAIN">
<div class="LOG">
<div class="LOG-YEAR"><span class="LOG-YEAR-N">1943</span><span class="LOG-YEAR-C">5 Briefe</span></div>
<div class="LOG-ROW out"><div class="LOG-DIR out"></div><div class="LOG-BODY"><div class="LOG-TITLE">Brief an Maria — letzte Nachrichten</div><div class="LOG-META">3. Sept. 1943<span class="LOG-META-SEP">·</span>Maria Weber<div class="S-DOT S-UPLOADED" style="margin-left:3px"></div></div></div></div>
<div class="LOG-ROW out"><div class="LOG-DIR out"></div><div class="LOG-BODY"><div class="LOG-TITLE">Brief an Klaus Fischer</div><div class="LOG-META">12. Sept. 1943<span class="LOG-META-SEP">·</span>Klaus Fischer<div class="S-DOT S-ARCHIVED" style="margin-left:3px"></div></div></div></div>
<div class="LOG-ROW in"><div class="LOG-DIR in"></div><div class="LOG-BODY"><div class="LOG-TITLE">Antwort von Klaus</div><div class="LOG-META">18. Sept. 1943<span class="LOG-META-SEP">·</span>Klaus Fischer<div class="S-DOT S-REVIEWED" style="margin-left:3px"></div></div></div></div>
<div class="LOG-YEAR"><span class="LOG-YEAR-N">1942</span><span class="LOG-YEAR-C">8 Briefe</span></div>
<div class="LOG-ROW out"><div class="LOG-DIR out"></div><div class="LOG-BODY"><div class="LOG-TITLE">Brief aus dem Feld</div><div class="LOG-META">14. März 1942<span class="LOG-META-SEP">·</span>Maria Weber<div class="S-DOT S-UPLOADED" style="margin-left:3px"></div></div></div></div>
<div class="LOG-ROW out"><div class="LOG-DIR out"></div><div class="LOG-BODY"><div class="LOG-TITLE">Neuigkeiten aus München</div><div class="LOG-META">22. Jan. 1942<span class="LOG-META-SEP">·</span>Maria Weber<div class="S-DOT S-TRANSCRIBED" style="margin-left:3px"></div></div></div></div>
</div>
</div>
</div>
<div class="sc">In single-person mode the recipient/sender name appears in the meta line — the only way to see who each letter is addressed to without filtering. Dashed border on correspondent field signals it's optional.</div>
</div>
<!-- Date range set in single-person mode -->
<div class="sb">
<div class="sl">Date range active <span class="state st-partial">Filtered + sorted</span></div>
<div class="wf">
<div class="wf-bar"><div class="dot r"></div><div class="dot y"></div><div class="dot g"></div><div class="urlbar"><span>/korrespondenz?senderId=otto-id&from=1940-01-01&to=1943-12-31&dir=ASC</span></div></div>
<div class="N"><span class="logo">FAMILIENARCHIV</span><span class="nl">Dokumente</span><span class="nl">Personen</span><span class="nl on">Korrespondenz</span><div class="nr"><div class="nico"></div></div></div>
<div class="STRIP-R1">
<div class="FIELD"><div class="FL">Person</div><div class="FI set"><div class="AV AV-xs AV-navy">OF</div>Otto Familienname</div></div>
<div class="SWAP"></div>
<div class="FIELD"><div class="FL">Korrespondent <span class="opt">— optional</span></div><div class="FI placeholder" style="border-style:dashed">Alle Korrespondenten</div></div>
</div>
<div class="STRIP-R2">
<div class="R2-LABEL">Zeitraum</div>
<div class="R2-DATE set">01.01.1940</div>
<div class="R2-DASH"></div>
<div class="R2-DATE set">31.12.1943</div>
<div class="R2-COUNT filtered" style="margin-left:auto">13 Briefe</div>
<div class="R2-SORT active">Älteste ↑</div>
</div>
<div class="SPMODE">
<span style="font-size:11px">📋</span>
<span><strong>Alle Briefe Ottos</strong> · 19401943 · Älteste zuerst</span>
</div>
<div class="MAIN">
<div class="LOG">
<div class="LOG-YEAR"><span class="LOG-YEAR-N">1940</span><span class="LOG-YEAR-C">3 Briefe</span></div>
<div class="LOG-ROW out"><div class="LOG-DIR out"></div><div class="LOG-BODY"><div class="LOG-TITLE">Erster Brief aus dem Krieg</div><div class="LOG-META">3. Sept. 1940<span class="LOG-META-SEP">·</span>Maria Weber<div class="S-DOT S-UPLOADED" style="margin-left:3px"></div></div></div></div>
<div class="LOG-ROW out"><div class="LOG-DIR out"></div><div class="LOG-BODY"><div class="LOG-TITLE">Brief über die Zustände</div><div class="LOG-META">15. Nov. 1940<span class="LOG-META-SEP">·</span>Maria Weber<div class="S-DOT S-TRANSCRIBED" style="margin-left:3px"></div></div></div></div>
<div class="LOG-YEAR"><span class="LOG-YEAR-N">1941</span><span class="LOG-YEAR-C">4 Briefe</span></div>
<div class="LOG-ROW in"><div class="LOG-DIR in"></div><div class="LOG-BODY"><div class="LOG-TITLE">Marias Antwort — Weihnachten</div><div class="LOG-META">22. Dez. 1941<span class="LOG-META-SEP">·</span>Maria Weber<div class="S-DOT S-ARCHIVED" style="margin-left:3px"></div></div></div></div>
</div>
</div>
</div>
<div class="sc">When a date range is set, the hint bar updates to summarise active filters. Count in row 2 turns navy and updates live. Both date inputs show navy border when set.</div>
</div>
</div>
</div>
<!-- ══════════════════════════════════
SECTION 3 — BILATERAL MODE (both selected)
══════════════════════════════════ -->
<div class="sec">
<div class="sec-h"><span class="sec-num">3</span> Bilateral mode — both persons selected</div>
<div class="sg sg-2a">
<!-- Full bilateral view -->
<div class="sb">
<div class="sl">Desktop <span class="sz">≥768px</span> <span class="state st-active">Both selected</span></div>
<div class="wf">
<div class="wf-bar"><div class="dot r"></div><div class="dot y"></div><div class="dot g"></div><div class="urlbar"><span>/korrespondenz?senderId=otto-id&receiverId=maria-id</span></div></div>
<div class="N"><span class="logo">FAMILIENARCHIV</span><span class="nl">Dokumente</span><span class="nl">Personen</span><span class="nl on">Korrespondenz</span><div class="nr"><div class="nico"></div></div></div>
<!-- Strip row 1 -->
<div class="STRIP-R1">
<div class="FIELD"><div class="FL">Person</div><div class="FI set"><div class="AV AV-xs AV-navy">OF</div>Otto Familienname</div></div>
<div class="SWAP on"></div>
<div class="FIELD"><div class="FL">Korrespondent</div><div class="FI set"><div class="AV AV-xs AV-outline">MW</div>Maria Weber</div></div>
</div>
<!-- Strip row 2 — active, count live -->
<div class="STRIP-R2">
<div class="R2-LABEL">Zeitraum</div>
<div class="R2-DATE">Von…</div>
<div class="R2-DASH"></div>
<div class="R2-DATE">Bis…</div>
<div class="R2-COUNT" style="margin-left:auto">32 Briefe</div>
<div class="R2-SORT">Neueste ↓</div>
</div>
<!-- Asymmetry bar — only in bilateral mode -->
<div class="ASYM">
<div class="ASYM-LABELS">
<span class="s">28 von Otto →</span>
<span class="r">4 von Maria ←</span>
</div>
<div class="ASYM-BAR">
<div class="ASYM-OUT" style="width:87.5%"></div>
<div class="ASYM-IN" style="width:12.5%"></div>
</div>
</div>
<!-- Log -->
<div class="MAIN">
<div class="LOG">
<div class="LOG-YEAR"><span class="LOG-YEAR-N">1943</span><span class="LOG-YEAR-C">3 Briefe</span></div>
<div class="LOG-ROW out">
<div class="LOG-DIR out"></div>
<div class="LOG-BODY">
<div class="LOG-TITLE">Brief an Maria — letzte Nachrichten</div>
<div class="LOG-META">3. Sept. 1943<span class="LOG-META-SEP">·</span>Prag<div class="S-DOT S-UPLOADED" style="margin-left:3px"></div></div>
</div>
<div class="LOG-ACTION"></div>
</div>
<div class="LOG-ROW in">
<div class="LOG-DIR in"></div>
<div class="LOG-BODY">
<div class="LOG-TITLE">Marias Antwort — September</div>
<div class="LOG-META">10. Sept. 1943<span class="LOG-META-SEP">·</span>Wien<div class="S-DOT S-ARCHIVED" style="margin-left:3px"></div></div>
</div>
<div class="LOG-ACTION"></div>
</div>
<div class="LOG-ROW out">
<div class="LOG-DIR out"></div>
<div class="LOG-BODY">
<div class="LOG-TITLE">Abschiedsbrief</div>
<div class="LOG-META">28. Sept. 1943<span class="LOG-META-SEP">·</span>Ostfront<div class="S-DOT S-TRANSCRIBED" style="margin-left:3px"></div></div>
</div>
<div class="LOG-ACTION"></div>
</div>
<div class="LOG-YEAR"><span class="LOG-YEAR-N">1942</span><span class="LOG-YEAR-C">7 Briefe</span></div>
<div class="LOG-ROW out"><div class="LOG-DIR out"></div><div class="LOG-BODY"><div class="LOG-TITLE">Brief aus dem Feld</div><div class="LOG-META">14. März 1942<span class="LOG-META-SEP">·</span>Frankreich<div class="S-DOT S-UPLOADED" style="margin-left:3px"></div></div></div><div class="LOG-ACTION"></div></div>
<div class="LOG-ROW out"><div class="LOG-DIR out"></div><div class="LOG-BODY"><div class="LOG-TITLE">Neuigkeiten aus München</div><div class="LOG-META">22. Jan. 1942<div class="S-DOT S-UPLOADED" style="margin-left:3px"></div></div></div><div class="LOG-ACTION"></div></div>
<div class="LOG-ROW in"><div class="LOG-DIR in"></div><div class="LOG-BODY"><div class="LOG-TITLE">Weihnachtsbrief von Maria</div><div class="LOG-META">20. Dez. 1942<span class="LOG-META-SEP">·</span>Wien<div class="S-DOT S-REVIEWED" style="margin-left:3px"></div></div></div><div class="LOG-ACTION"></div></div>
<div class="LOG-ROW out"><div class="LOG-DIR out"></div><div class="LOG-BODY"><div class="LOG-TITLE">Gedanken zu Weihnachten</div><div class="LOG-META">24. Dez. 1942<div class="S-DOT S-UPLOADED" style="margin-left:3px"></div></div></div><div class="LOG-ACTION"></div></div>
</div>
<!-- canWrite: new document link -->
<div style="display:flex;align-items:center;justify-content:flex-end;padding:2px 0">
<a href="#" style="display:inline-flex;align-items:center;gap:4px;font-size:8.5px;font-weight:700;color:rgba(0,40,80,.5);text-decoration:none">
<svg width="12" height="12" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
Neues Dokument in dieser Korrespondenz
</a>
</div>
</div>
</div>
<div class="sc">Asymmetry bar only appears in bilateral mode — hidden in single-person mode. Location shown in meta when present. Hover reveals "" action chevron per row. Sender/recipient omitted from meta in bilateral mode (implicit).</div>
</div>
<!-- Mobile bilateral -->
<div class="sb">
<div class="sl">Mobile <span class="sz">375px</span> <span class="state st-active">Both selected</span></div>
<div style="display:flex;justify-content:center">
<div class="WF-M">
<div class="WF-M-STATUS"><span class="WF-M-TIME">09:41</span><div class="WF-M-ICONS"><div class="WF-M-ICON"></div><div class="WF-M-ICON"></div><div class="WF-M-ICON"></div></div></div>
<div class="N-M"><span class="logo" style="font-size:8.5px">FAMILIENARCHIV</span><span style="color:rgba(255,255,255,.7);font-size:17px"></span></div>
<!-- Mobile strip: stacked persons + swap row -->
<div class="STRIP-R1-SM">
<div style="display:flex;align-items:center;gap:7px">
<div class="FI set" style="flex:1;height:34px;font-size:8.5px"><div class="AV AV-xs AV-navy" style="margin-right:3px">OF</div>Otto F.</div>
<div class="SWAP on" style="width:24px;height:24px;flex-shrink:0;font-size:10px"></div>
<div class="FI set" style="flex:1;height:34px;font-size:8.5px"><div class="AV AV-xs AV-outline" style="margin-right:3px">MW</div>Maria W.</div>
</div>
</div>
<div class="STRIP-R2-SM">
<div class="R2-DATE" style="width:55px;font-size:7.5px">Von…</div>
<div class="R2-DASH" style="font-size:9px"></div>
<div class="R2-DATE" style="width:55px;font-size:7.5px">Bis…</div>
<div style="font-size:7.5px;color:#888;margin-left:auto;white-space:nowrap">32 Br.</div>
<div class="R2-SORT" style="font-size:7px;height:20px;padding:0 6px">Neu ↓</div>
</div>
<!-- Asym bar -->
<div class="ASYM" style="padding:6px 12px">
<div class="ASYM-LABELS" style="font-size:7px"><span class="s">28 von Otto →</span><span class="r">4 von Maria ←</span></div>
<div class="ASYM-BAR" style="height:4px"><div class="ASYM-OUT" style="width:87.5%"></div><div class="ASYM-IN" style="width:12.5%"></div></div>
</div>
<div class="MAIN-SM">
<div class="LOG">
<div class="LOG-YEAR" style="padding:4px 10px"><span class="LOG-YEAR-N" style="font-size:12px">1943</span><span class="LOG-YEAR-C">3</span></div>
<div class="LOG-ROW out" style="padding:8px 10px"><div class="LOG-DIR out" style="font-size:8px"></div><div class="LOG-BODY"><div class="LOG-TITLE" style="font-size:8px">Brief — letzte Nachrichten</div><div class="LOG-META" style="font-size:7px">3. Sept. 1943<div class="S-DOT S-UPLOADED" style="margin-left:3px"></div></div></div></div>
<div class="LOG-ROW in" style="padding:8px 10px"><div class="LOG-DIR in" style="font-size:8px"></div><div class="LOG-BODY"><div class="LOG-TITLE" style="font-size:8px">Marias Antwort</div><div class="LOG-META" style="font-size:7px">10. Sept. 1943<div class="S-DOT S-ARCHIVED" style="margin-left:3px"></div></div></div></div>
<div class="LOG-ROW out" style="padding:8px 10px"><div class="LOG-DIR out" style="font-size:8px"></div><div class="LOG-BODY"><div class="LOG-TITLE" style="font-size:8px">Abschiedsbrief</div><div class="LOG-META" style="font-size:7px">28. Sept. 1943<div class="S-DOT S-TRANSCRIBED" style="margin-left:3px"></div></div></div></div>
<div class="LOG-YEAR" style="padding:4px 10px"><span class="LOG-YEAR-N" style="font-size:12px">1942</span><span class="LOG-YEAR-C">7</span></div>
<div class="LOG-ROW out" style="padding:8px 10px"><div class="LOG-DIR out" style="font-size:8px"></div><div class="LOG-BODY"><div class="LOG-TITLE" style="font-size:8px">Brief aus dem Feld</div><div class="LOG-META" style="font-size:7px">14. März 1942<div class="S-DOT S-UPLOADED" style="margin-left:3px"></div></div></div></div>
</div>
</div>
</div>
</div>
<div class="sc">Mobile: persons side-by-side (condensed names) with swap between. Row 2 compressed. Each log row ≥44px touch target. Full card tap → document detail.</div>
</div>
</div>
</div>
<!-- ══════════════════════════════════
SECTION 4 — BILATERAL WITH DATE FILTER
══════════════════════════════════ -->
<div class="sec">
<div class="sec-h"><span class="sec-num">4</span> Bilateral + date filter active</div>
<div class="sg sg-2">
<div class="sb">
<div class="sl">Date range set <span class="state st-active">Filtered bilateral</span></div>
<div class="wf">
<div class="wf-bar"><div class="dot r"></div><div class="dot y"></div><div class="dot g"></div><div class="urlbar"><span>/korrespondenz?senderId=otto-id&receiverId=maria-id&from=1921-01-01&to=1935-12-31&dir=ASC</span></div></div>
<div class="N"><span class="logo">FAMILIENARCHIV</span><span class="nl">Dokumente</span><span class="nl">Personen</span><span class="nl on">Korrespondenz</span><div class="nr"><div class="nico"></div></div></div>
<div class="STRIP-R1">
<div class="FIELD"><div class="FL">Person</div><div class="FI set"><div class="AV AV-xs AV-navy">OF</div>Otto Familienname</div></div>
<div class="SWAP on"></div>
<div class="FIELD"><div class="FL">Korrespondent</div><div class="FI set"><div class="AV AV-xs AV-outline">MW</div>Maria Weber</div></div>
</div>
<div class="STRIP-R2">
<div class="R2-LABEL">Zeitraum</div>
<div class="R2-DATE set">01.01.1921</div>
<div class="R2-DASH"></div>
<div class="R2-DATE set">31.12.1935</div>
<div class="R2-COUNT filtered" style="margin-left:auto">20 Briefe</div>
<div class="R2-SORT active">Älteste ↑</div>
</div>
<div class="ASYM">
<div class="ASYM-LABELS"><span class="s">18 von Otto →</span><span class="r">2 von Maria ←</span></div>
<div class="ASYM-BAR"><div class="ASYM-OUT" style="width:90%"></div><div class="ASYM-IN" style="width:10%"></div></div>
</div>
<div class="MAIN">
<div class="LOG">
<div class="LOG-YEAR"><span class="LOG-YEAR-N">1921</span><span class="LOG-YEAR-C">8 Briefe</span></div>
<div class="LOG-ROW out"><div class="LOG-DIR out"></div><div class="LOG-BODY"><div class="LOG-TITLE">Brief an Maria — Reise nach Wien</div><div class="LOG-META">12. März 1921<span class="LOG-META-SEP">·</span>Wien<div class="S-DOT S-TRANSCRIBED" style="margin-left:3px"></div></div></div><div class="LOG-ACTION"></div></div>
<div class="LOG-ROW out"><div class="LOG-DIR out"></div><div class="LOG-BODY"><div class="LOG-TITLE">Nachricht zum Geburtstag</div><div class="LOG-META">4. April 1921<div class="S-DOT S-ARCHIVED" style="margin-left:3px"></div></div></div><div class="LOG-ACTION"></div></div>
<div class="LOG-ROW in"><div class="LOG-DIR in"></div><div class="LOG-BODY"><div class="LOG-TITLE">Marias Antwort auf Geburtstag</div><div class="LOG-META">10. April 1921<span class="LOG-META-SEP">·</span>Wien<div class="S-DOT S-REVIEWED" style="margin-left:3px"></div></div></div><div class="LOG-ACTION"></div></div>
<div class="LOG-ROW out"><div class="LOG-DIR out"></div><div class="LOG-BODY"><div class="LOG-TITLE">Bericht aus dem Büro</div><div class="LOG-META">22. Mai 1921<div class="S-DOT S-UPLOADED" style="margin-left:3px"></div></div></div><div class="LOG-ACTION"></div></div>
<div class="LOG-YEAR"><span class="LOG-YEAR-N">1922</span><span class="LOG-YEAR-C">12 Briefe</span></div>
<div class="LOG-ROW out"><div class="LOG-DIR out"></div><div class="LOG-BODY"><div class="LOG-TITLE">Neujahrsgrüße 1922</div><div class="LOG-META">2. Jan. 1922<div class="S-DOT S-UPLOADED" style="margin-left:3px"></div></div></div><div class="LOG-ACTION"></div></div>
</div>
</div>
</div>
<div class="sc">Date inputs show the active range. Count updates to the filtered set (20, not 32). Sort label changes to "Älteste ↑". Asymmetry bar recalculates for the visible set.</div>
</div>
<!-- Annotation -->
<div class="sb" style="padding-top:40px">
<div class="ann-block">
<strong>Status dots — colour legend</strong>
<ul>
<li><span style="display:inline-block;width:7px;height:7px;border-radius:50%;background:#F59E0B;margin-right:4px;vertical-align:middle"></span> PLACEHOLDER — Platzhalter, keine Datei</li>
<li><span style="display:inline-block;width:7px;height:7px;border-radius:50%;background:#22C55E;margin-right:4px;vertical-align:middle"></span> UPLOADED — Datei vorhanden</li>
<li><span style="display:inline-block;width:7px;height:7px;border-radius:50%;background:#3B82F6;margin-right:4px;vertical-align:middle"></span> TRANSCRIBED — Transkribiert</li>
<li><span style="display:inline-block;width:7px;height:7px;border-radius:50%;background:#A855F7;margin-right:4px;vertical-align:middle"></span> REVIEWED — Überprüft</li>
<li><span style="display:inline-block;width:7px;height:7px;border-radius:50%;background:#6B7280;margin-right:4px;vertical-align:middle"></span> ARCHIVED — Archiviert</li>
</ul>
</div>
<div class="ann-block" style="margin-top:10px">
<strong>Direction border colours</strong>
<ul>
<li><span style="display:inline-block;width:4px;height:14px;background:#002850;margin-right:6px;vertical-align:middle;border-radius:1px"></span> Sent (→) — Navy #002850</li>
<li><span style="display:inline-block;width:4px;height:14px;background:#A6DAD8;margin-right:6px;vertical-align:middle;border-radius:1px"></span> Received (←) — Mint #A6DAD8</li>
</ul>
</div>
<div class="ann-block" style="margin-top:10px">
<strong>Row 2 behaviour rules</strong>
<ul>
<li>Always rendered in the DOM</li>
<li>Dimmed (opacity: 0.4) when no person is selected</li>
<li>Fully active as soon as any person is set</li>
<li>Count updates on every filter change via <code>documents.length</code></li>
<li>Sort button: click toggles <code>dir</code> param between ASC ↔ DESC</li>
<li>Date inputs: type or date picker; ISO sent in URL, German displayed</li>
</ul>
</div>
<div class="ann-block" style="margin-top:10px">
<strong>Asymmetry bar — when shown</strong>
<ul>
<li>Only rendered when both <code>senderId</code> and <code>receiverId</code> are set</li>
<li>Calculates from <code>documents</code> array: <code>filter(d => d.sender?.id === senderId).length</code></li>
<li>Hidden in single-person mode</li>
</ul>
</div>
</div>
</div>
</div>
<div class="impl-ref">
<div class="impl-ref-hdr">Implementation Reference — Correspondence Log <span>Real values · mockup above is ~55% scale</span></div>
<table>
<thead><tr><th>Element</th><th>Tailwind classes</th><th>Real size</th><th>Notes</th></tr></thead>
<tbody>
<tr>
<td>Log container</td>
<td><code>border border-line rounded-sm overflow-hidden bg-surface</code></td>
<td></td>
<td>White card wrapping year bands + rows</td>
</tr>
<tr>
<td>Year band (.LOG-YEAR)</td>
<td><code>flex items-baseline gap-3 px-4 py-2 bg-muted border-b border-line border-t border-t-line-2</code></td>
<td><span class="ir-px">h ~40px, py 8px, px 16px</span></td>
<td>First year band: omit <code>border-t</code></td>
</tr>
<tr>
<td>Year number</td>
<td><code>text-2xl font-bold text-ink leading-none</code></td>
<td><span class="ir-px">24px / 700</span></td>
<td>This is the most commonly undersized element — 24px minimum</td>
</tr>
<tr>
<td>Year letter count</td>
<td><code>text-xs text-ink-3 font-medium</code></td>
<td><span class="ir-px">12px</span></td>
<td>"5 Briefe" — always visible next to year</td>
</tr>
<tr>
<td>Log row (.LOG-ROW)</td>
<td><code>flex items-start gap-3 px-4 py-3 border-b border-line last:border-b-0 hover:bg-muted transition-colors cursor-pointer min-h-[44px] border-l-2</code></td>
<td><span class="ir-px">min 44px tall, py 12px, px 16px</span></td>
<td>Sent: <code>border-l-primary</code>. Received: <code>border-l-accent</code></td>
</tr>
<tr>
<td>Direction arrow</td>
<td><code>text-sm font-black shrink-0 pt-0.5 w-4 text-center</code></td>
<td><span class="ir-px">14px / 900, 16px wide</span></td>
<td>Sent: <code>text-primary</code> "→". Received: <code>text-[#0F5755]</code> "←"</td>
</tr>
<tr>
<td>Row body</td>
<td><code>flex-1 min-w-0</code></td>
<td></td>
<td>Flex column inside: title + meta</td>
</tr>
<tr>
<td>Document title</td>
<td><code>text-sm font-semibold text-ink leading-snug truncate</code></td>
<td><span class="ir-px">14px / 600</span></td>
<td>Single line, truncate with ellipsis on overflow</td>
</tr>
<tr>
<td>Meta line (date · sender · status)</td>
<td><code>text-xs text-ink-2 mt-0.5 flex items-center gap-1.5</code></td>
<td><span class="ir-px">12px</span></td>
<td>Separator: <code>text-line</code>. Status dot: <code>w-2 h-2 rounded-full shrink-0</code></td>
</tr>
<tr>
<td>Row action chevron</td>
<td><code>shrink-0 w-6 h-6 flex items-center justify-center border border-line bg-muted text-ink-3 opacity-0 group-hover:opacity-100 transition-opacity</code></td>
<td><span class="ir-px">24×24px</span></td>
<td>Add <code>group</code> to LOG-ROW; chevron fades in on hover</td>
</tr>
<tr>
<td>Asymmetry bar container</td>
<td><code>px-4 sm:px-6 py-2 bg-canvas border-b border-line</code></td>
<td><span class="ir-px">py 8px</span></td>
<td>Only rendered when both senderId and receiverId are set</td>
</tr>
<tr>
<td>Asymmetry bar track</td>
<td><code>h-1.5 rounded-full bg-line overflow-hidden flex mt-1</code></td>
<td><span class="ir-px">6px tall</span></td>
<td>Navy fill: <code>bg-primary</code>. Mint fill: <code>bg-accent</code></td>
</tr>
<tr>
<td>Single-person hint bar</td>
<td><code>px-4 sm:px-6 py-2 bg-amber-50 border-b border-amber-200 text-xs font-medium text-amber-800 flex items-center gap-2</code></td>
<td><span class="ir-px">h ~36px, 12px text</span></td>
<td>Amber warning tone. Only when senderId set but no receiverId.</td>
</tr>
</tbody>
</table>
</div>
<!-- ══════════════════════════════════
IMPLEMENTATION NOTES
══════════════════════════════════ -->
<div class="IMPL">
<h2>Implementation notes</h2>
<div class="IMPL-GRID">
<div class="IMPL-COL">
<h3>ConversationFilterBar.svelte</h3>
<ul>
<li>Replace the large <code>p-8</code> card with a two-row strip</li>
<li>Row 1: flex row, <code>border-bottom: 1px solid #EAE7E0</code>, <code>bg-white</code></li>
<li>Row 2: flex row, <code>bg-surface</code> (#F7F5F2), <code>border-bottom: 1.5px solid #E0DDD6</code></li>
<li>Row 2 always mounted; <code>opacity-40 pointer-events-none</code> when no person set</li>
<li>Receiver field: remove required validation, add dashed border style when empty</li>
<li>Suggestion dropdown: populate from <code>restrictToCorrespondentsOf</code> results when field focused + person A set</li>
<li>Add "Alle Korrespondenten" row at bottom of dropdown as explicit opt-out</li>
<li>Sort: click toggles <code>dir</code> via <code>ontoggleSort</code> — keep existing handler</li>
<li>Count: derived from <code>documents.length</code> passed as prop</li>
</ul>
</div>
<div class="IMPL-COL">
<h3>ConversationTimeline.svelte</h3>
<ul>
<li>Remove: central line div, chat bubble markup, left/right justify logic</li>
<li>Replace with: <code>LOG</code> container, <code>LOG-YEAR</code> bands, <code>LOG-ROW</code> cards</li>
<li>Direction: <code>isOut = doc.sender?.id === senderId</code> → navy border + "→" arrow</li>
<li>In bilateral mode: hide sender/recipient in meta (implicit). In single-person mode: show other party name</li>
<li>Asymmetry bar: new component or inline block, only when <code>senderId && receiverId</code></li>
<li>Summary bar: keep <code>data-testid="conv-summary"</code> but move count to Row 2 of filter strip</li>
<li>Keep <code>canWrite</code> new-document link — move to bottom of log list</li>
<li>Location in meta: show when <code>doc.location</code> is set</li>
<li>Status dot: map <code>doc.status</code> to dot colour (5 states)</li>
</ul>
</div>
<div class="IMPL-COL">
<h3>+page.svelte / +page.server.ts</h3>
<ul>
<li>Remove hard gate: <code>{#if !senderId || !receiverId}</code> block replaced with empty-state component + single-person mode support</li>
<li>Single-person API: when only <code>senderId</code> set, call <code>GET /api/documents</code> filtered by sender (or extend <code>/api/documents/conversation</code> to allow null receiver)</li>
<li>Recent persons: store last 3 visited person IDs in <code>localStorage</code>, resolve names on mount</li>
<li>Hint bar: show when <code>senderId && !receiverId</code></li>
<li>Page title: <code>m.conv_heading()</code> → update i18n key value to "Korrespondenz"</li>
<li>Nav label: update <code>+layout.svelte</code> nav link text key</li>
<li>Existing spec tests in <code>page.svelte.spec.ts</code>: update selectors for new log markup, add single-person mode tests</li>
</ul>
</div>
</div>
</div>
</div>
</body>
</html>