731 lines
70 KiB
HTML
731 lines
70 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>DocumentTopBar — Final Implementation Spec</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}
|
||
.page{max-width:1360px;margin:0 auto;padding:48px 32px}
|
||
.mh{padding-bottom:24px;border-bottom:3px solid #012851;margin-bottom:32px}
|
||
.mh h1{font-size:22px;font-weight:900;color:#012851;letter-spacing:-.4px}
|
||
.mh p{font-size:12.5px;color:#555;max-width:680px;line-height:1.7;margin-top:6px}
|
||
.mh .byline{font-size:9px;color:#AAA;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;margin-top:8px}
|
||
.sh{margin:56px 0 28px;padding-bottom:12px;border-bottom:2px solid #E0DDD6}
|
||
.sh h2{font-size:16px;font-weight:900;color:#012851}
|
||
.sh p{font-size:12px;color:#666;margin-top:4px;max-width:700px;line-height:1.6}
|
||
.grid{display:flex;gap:20px;flex-wrap:wrap;margin-bottom:32px;align-items:flex-start}
|
||
.col{display:flex;flex-direction:column;gap:6px}
|
||
.lbl{font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:1.2px;color:#888;display:flex;align-items:center;gap:5px}
|
||
.tag{background:#E4E0DA;color:#666;padding:1px 5px;border-radius:2px;font-size:7px;font-weight:700}
|
||
.cap{font-size:9.5px;color:#999;font-style:italic;line-height:1.55;max-width:460px}
|
||
.chrome{background:#F0EFE9;border:1.5px solid #C4C0BA;border-radius:7px;overflow:hidden;box-shadow:0 3px 14px rgba(0,0,0,.09)}
|
||
.chrome.dark{background:#060C12;border-color:#0A1520}
|
||
.bar{height:20px;background:#E0DDD6;border-bottom:1px solid #C4C0BA;display:flex;align-items:center;padding:0 7px;gap:3px}
|
||
.chrome.dark .bar{background:#0A1218;border-bottom-color:#0A1520}
|
||
.dot{width:5px;height:5px;border-radius:50%;background:#BDB8B1}
|
||
.chrome.dark .dot{background:#1A2A3A}
|
||
.url{flex:1;height:8px;background:#CCC8C2;border-radius:5px;margin-left:4px}
|
||
.chrome.dark .url{background:#1A2A3A}
|
||
.nav{height:32px;background:#012851;display:flex;align-items:center;padding:0 12px;gap:8px;flex-shrink:0}
|
||
.nav.dark{background:#060C12}
|
||
.nav-logo{font-size:7px;font-weight:900;color:#fff;letter-spacing:.8px;border-bottom:2px solid #A1DCD8;padding-bottom:1px}
|
||
.nav-link{font-size:5.5px;color:rgba(255,255,255,.4);font-weight:700;text-transform:uppercase}
|
||
.nav-r{margin-left:auto;display:flex;gap:5px;align-items:center}
|
||
.nav-av{width:16px;height:16px;background:rgba(255,255,255,.1);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:rgba(255,255,255,.5)}
|
||
.pdf{background:#D4D0C8;display:flex;align-items:center;justify-content:center;flex-shrink:0}
|
||
.pdf.dark{background:#08121C}
|
||
.paper{background:#FFFEF8;width:55%;box-shadow:0 2px 8px rgba(0,0,0,.14);border-radius:1px;padding:9px 11px;display:flex;flex-direction:column;gap:2px}
|
||
.paper.dark{background:#0D1820}
|
||
.pl{height:3px;background:#C4BDB0;border-radius:1px;opacity:.5;margin-bottom:2px}
|
||
.ps{height:2px;background:#C4BDB0;border-radius:1px;opacity:.28;margin-bottom:1.5px}
|
||
.paper.dark .pl{background:#1E2D3D}
|
||
.paper.dark .ps{background:#162230}
|
||
.chip{display:inline-flex;align-items:center;gap:2px;padding:2px 6px 2px 3px;background:#F0EFE9;border:1px solid #DDD9D0;border-radius:10px;white-space:nowrap;flex-shrink:0}
|
||
.chip.dk{background:#0A1218;border-color:#1E2D3D}
|
||
.av{border-radius:50%;background:#012851;display:flex;align-items:center;justify-content:center;font-weight:800;color:#A1DCD8;flex-shrink:0}
|
||
.av.purple{background:#5A3080;color:#fff}
|
||
.av.teal{background:#007596;color:#fff}
|
||
.av.moss{background:#2A6040;color:#fff}
|
||
.av.rust{background:#803020;color:#fff}
|
||
.cn{font-weight:600;color:#333;white-space:nowrap}
|
||
.cn.dk{color:#8AAABB}
|
||
.ov{display:inline-flex;align-items:center;padding:2px 6px;background:#E8E4DC;border:1px solid #DDD9D0;border-radius:10px;font-weight:700;color:#666;white-space:nowrap;flex-shrink:0}
|
||
.ov.dk{background:#0A1218;border-color:#1E2D3D;color:#4E6070}
|
||
.arr{color:#C4C0B8;flex-shrink:0}
|
||
.arr.dk{color:#1E2D3D}
|
||
.btn-p{height:26px;padding:0 10px;background:#012851;color:#A1DCD8;font-size:6.5px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;border-radius:3px;display:inline-flex;align-items:center;gap:4px;white-space:nowrap;flex-shrink:0}
|
||
.btn-p.dk{background:#A1DCD8;color:#012851}
|
||
.btn-g{height:26px;padding:0 9px;border:1.5px solid #C8C4BE;color:#444;font-size:6.5px;font-weight:700;text-transform:uppercase;letter-spacing:.4px;border-radius:3px;display:inline-flex;align-items:center;gap:4px;white-space:nowrap;flex-shrink:0}
|
||
.btn-g.dk{border-color:#1E2D3D;color:#6080A0}
|
||
.btn-g.on{background:#012851;border-color:#012851;color:#A1DCD8}
|
||
.btn-g.on.dk{background:#A1DCD8;border-color:#A1DCD8;color:#012851}
|
||
.ico{width:26px;height:26px;border:1.5px solid #C8C4BE;border-radius:3px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;color:#888}
|
||
.ico.dk{border-color:#1E2D3D;color:#4E6070}
|
||
.dv{width:1px;background:#E4E2D8;flex-shrink:0}
|
||
.dv.dk{background:#1E2D3D}
|
||
.dl::before{content:'';display:block;width:2px;height:5px;background:currentColor;margin:0 auto}
|
||
.dl{width:9px;height:6px;border-bottom:2px solid currentColor;border-left:2px solid transparent;border-right:2px solid transparent}
|
||
.hint{height:18px;background:rgba(1,40,81,.05);border-top:1px dashed rgba(1,40,81,.12);display:flex;align-items:center;padding:0 14px;gap:8px}
|
||
.hint.dk{background:rgba(161,220,216,.04);border-top-color:rgba(161,220,216,.1)}
|
||
.hint-lbl{font-size:5.5px;font-weight:800;color:#012851;text-transform:uppercase;letter-spacing:.5px}
|
||
.hint-lbl.dk{color:#A1DCD8}
|
||
.hint-txt{font-size:5.5px;color:#888}
|
||
.st-up{display:inline-flex;align-items:center;gap:2px;padding:1px 5px;border-radius:3px;font-size:5.5px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;background:#D1FAE5;border:1px solid #6EE7B7;color:#065F46;flex-shrink:0}
|
||
.st-up.dk{background:rgba(209,250,229,.07);color:#6EE7B7;border-color:rgba(110,231,183,.2)}
|
||
.st-dot{width:4px;height:4px;border-radius:50%;background:#10B981}
|
||
hr{border:none;border-top:2px dashed #C8C4BE;margin:60px 0}
|
||
.rules{background:#fff;border:1px solid #E0DDD6;border-radius:6px;overflow:hidden;margin-top:28px}
|
||
.rules table{width:100%;border-collapse:collapse}
|
||
.rules th{background:#F4F2EC;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;padding:8px 12px;text-align:left;border-bottom:1px solid #E0DDD6}
|
||
.rules td{font-size:11px;color:#444;padding:8px 12px;border-bottom:1px solid #F0EEE8;vertical-align:top;line-height:1.55}
|
||
.rules tr:last-child td{border-bottom:none}
|
||
.rules td:first-child{font-size:9px;font-weight:700;color:#012851;white-space:nowrap;width:110px}
|
||
.rules td code{font-size:9px;background:#F0EFE9;padding:1px 4px;border-radius:2px;color:#555}
|
||
/* ── 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}
|
||
/* ── impl-ref ── */
|
||
.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: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;white-space:nowrap}
|
||
.impl-ref .ir-px{color:#7ee787;font-family:monospace;font-size:9.5px}
|
||
.impl-ref .ir-warn{color:#f0883e;font-style:italic}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="page">
|
||
|
||
<div class="mh">
|
||
<h1>DocumentTopBar — Final Implementation Spec</h1>
|
||
<p>Authoritative implementation reference for the responsive DocumentTopBar component. Incorporates all resolutions from the issue #161 team review (Felix Brandt, Markus Keller, Sara Holt, Nora Steiner, Tobias Wendt, Leonie Voss). Supersedes <code>document-topbar-b1-responsive.html</code> — refer to that file for additional visual mockup detail.</p>
|
||
<div class="byline">Familienarchiv · 2026-03-31 · Leonie Voss, UX Lead — Final spec after review</div>
|
||
</div>
|
||
|
||
<div class="spec-disclaimer">
|
||
<strong>📐 Mockup scale notice —</strong> all font-size, height, and padding values in the mockup CSS below are scaled to ~55% of actual implementation values.
|
||
<strong>Do not copy sizes from mockup CSS.</strong> Use the ⚙ Implementation Reference tables after each section. Mockup CSS is for visual preview only.
|
||
<br><strong>⚠ This spec overrides the B1 spec</strong> — font sizes, heights, status chip, overflow pill, and touch targets have all changed. Key corrections: title min <code>text-[11px]</code>, chip names <code>text-[9px]</code>, topbar heights <code>h-12/h-14</code>, status chip dot-only, edit button icon-only on mobile.
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════════════
|
||
SECTION 0 — COMPONENT ARCHITECTURE
|
||
══════════════════════════════════════════ -->
|
||
<div class="sh">
|
||
<h2>0 · Component architecture</h2>
|
||
<p>Decompose into these components. Never merge into a single monolith — each has a clear single visual responsibility and must be independently testable.</p>
|
||
</div>
|
||
|
||
<div class="rules">
|
||
<table>
|
||
<thead><tr><th>Component file</th><th>Props</th><th>Responsibility</th><th>Notes</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>DocumentTopBar.svelte</td><td><code>doc, canWrite, canAnnotate, fileUrl, annotateMode (bindable)</code></td><td>Orchestrator. Owns <code>overflowOpen: $state(false)</code>. Passes props down. Contains back link, title, action buttons.</td><td>Parent layout must wrap in <code><header></code>. No direct DOM measurement.</td></tr>
|
||
<tr><td>PersonChipRow.svelte</td><td><code>sender, receivers, abbreviated: boolean</code></td><td>Chip row with arrow. Visible at ≥375px. Hidden at XS via <code>hidden xs:flex</code>.</td><td>Renders plain-text fallback slot at XS via parent.</td></tr>
|
||
<tr><td>PersonChip.svelte</td><td><code>person, abbreviated: boolean</code></td><td>Single chip: avatar initials + name. Abbreviated = first initial + last name.</td><td>Avatar colour from <code>personAvatarColor(person.id)</code>.</td></tr>
|
||
<tr><td>OverflowPill.svelte</td><td><code>extraCount, persons (for tooltip), open (bindable)</code></td><td>At ≥768px: interactive <code><button></code> with tooltip. At <768px: <code><span aria-hidden="true"></code> — non-interactive.</td><td><code>aria-haspopup="listbox"</code>, <code>aria-expanded</code>, <code>aria-label</code>. See tooltip rules.</td></tr>
|
||
<tr><td>DocumentStatusChip.svelte</td><td><code>status: DocumentStatus</code></td><td>Dot-only indicator. Hidden below 768px. <code>title</code> + <code>aria-label</code> carry the label text.</td><td>No text label — removes i18n requirement.</td></tr>
|
||
<tr><td>AnnotateHintStrip.svelte</td><td><code>annotateMode: boolean</code></td><td>18px strip below main row. Only rendered when <code>annotateMode === true</code> AND viewport ≥768px.</td><td>Use <code>{#if annotateMode}</code> — no CSS height animation. Hidden via parent responsive class.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="impl-ref">
|
||
<div class="impl-ref-hdr">Implementation Reference — Svelte state & derived values <span>Svelte 5 runes</span></div>
|
||
<table>
|
||
<thead><tr><th>Value</th><th>Type</th><th>Implementation</th><th>Notes</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>overflowOpen</td><td><code>$state(false)</code></td><td><code>let overflowOpen = $state(false)</code></td><td>In DocumentTopBar. Passed as bindable to OverflowPill.</td></tr>
|
||
<tr><td>visibleReceivers</td><td><code>$derived</code></td><td><code>$derived(doc.receivers.slice(0, viewportGe768 ? 2 : 1))</code></td><td>CSS-only: at <768px always 1 shown. At ≥768px show 2 if count==2, else 1. Use CSS to hide — no JS.</td></tr>
|
||
<tr><td>extraCount</td><td><code>$derived</code></td><td><code>$derived(doc.receivers.length - visibleReceivers.length)</code></td><td>0 = no pill needed.</td></tr>
|
||
<tr><td>formattedDate</td><td><code>$derived</code></td><td>See utility module — <code>formatDate(doc.documentDate, format)</code></td><td>Format switches via CSS responsive classes, not JS viewport check.</td></tr>
|
||
<tr><td>xsMetaLine</td><td><code>$derived</code></td><td><code>$derived(formatXsMeta(doc))</code></td><td>Used only at XS. Import from <code>$lib/utils/personFormat</code>.</td></tr>
|
||
<tr><td>annotateMode</td><td>bindable prop</td><td><code>let { annotateMode = $bindable(false) } = $props()</code></td><td>Parent page owns state. TopBar toggles it.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════════════
|
||
SECTION 1 — DESIGN TOKENS
|
||
══════════════════════════════════════════ -->
|
||
<div class="sh"><h2>1 · Design tokens</h2><p>All CSS custom properties used by the topbar. No hardcoded colours in any component — all must reference these tokens.</p></div>
|
||
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:20px">
|
||
<div style="background:#fff;border:1px solid #E0DDD6;border-radius:6px;overflow:hidden">
|
||
<div style="background:#F4F2EC;padding:8px 14px;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;border-bottom:1px solid #E0DDD6">Light theme</div>
|
||
<table style="width:100%;border-collapse:collapse;font-size:11px">
|
||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700;width:160px">bg-surface</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#fff;border:1px solid #DDD;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#FFFFFF — topbar bg</td></tr>
|
||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">border-line</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#E4E2D8;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#E4E2D8 — bottom border, dividers, chip borders</td></tr>
|
||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">bg-primary</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#012851;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#012851 — accent bar (light), primary btn, avatars</td></tr>
|
||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">text-primary-fg</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#A1DCD8;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#A1DCD8 — text on navy bg</td></tr>
|
||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">text-ink</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#1A1A1A;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#1A1A1A — title, chip names</td></tr>
|
||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">text-ink-2</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#AAA;border:1px solid #EEE;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#AAAAAA — date, meta (spec ink-3 mapped here)</td></tr>
|
||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">bg-muted</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#F0EFE9;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#F0EFE9 — chip bg, back btn bg at XS</td></tr>
|
||
<tr><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">Accent bar</td><td style="padding:6px 14px"><code style="font-size:9px;background:#F0EFE9;padding:1px 4px;border-radius:2px">border-l-[3px] border-primary</code> — always present, all breakpoints</td></tr>
|
||
</table>
|
||
</div>
|
||
<div style="background:#0F1923;border:1px solid #1E2D3D;border-radius:6px;overflow:hidden">
|
||
<div style="background:#0A1218;padding:8px 14px;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#4E6070;border-bottom:1px solid #1E2D3D">Dark theme</div>
|
||
<table style="width:100%;border-collapse:collapse;font-size:11px">
|
||
<tr style="border-bottom:1px solid #1A2830"><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700;width:160px">bg-surface</td><td style="padding:6px 14px;color:#8AAABB"><span style="display:inline-block;width:12px;height:12px;background:#0F1923;border:1px solid #1E2D3D;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#0F1923</td></tr>
|
||
<tr style="border-bottom:1px solid #1A2830"><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700">border-line</td><td style="padding:6px 14px;color:#8AAABB"><span style="display:inline-block;width:12px;height:12px;background:#1E2D3D;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#1E2D3D</td></tr>
|
||
<tr style="border-bottom:1px solid #1A2830"><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700">Accent bar (dark)</td><td style="padding:6px 14px;color:#8AAABB"><span style="display:inline-block;width:12px;height:12px;background:#A1DCD8;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#A1DCD8 — teal replaces navy for accent bar</td></tr>
|
||
<tr style="border-bottom:1px solid #1A2830"><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700">text-ink</td><td style="padding:6px 14px;color:#8AAABB"><span style="display:inline-block;width:12px;height:12px;background:#EAE8E2;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#EAE8E2 — title</td></tr>
|
||
<tr style="border-bottom:1px solid #1A2830"><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700">text-ink-2 (meta)</td><td style="padding:6px 14px;color:#8AAABB"><span style="display:inline-block;width:12px;height:12px;background:#3E5065;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#3E5065 — date, meta</td></tr>
|
||
<tr style="border-bottom:1px solid #1A2830"><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700">Chip bg</td><td style="padding:6px 14px;color:#8AAABB"><span style="display:inline-block;width:12px;height:12px;background:#0A1218;border:1px solid #1E2D3D;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#0A1218 · border #1E2D3D</td></tr>
|
||
<tr><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700">Primary btn (dark)</td><td style="padding:6px 14px;color:#8AAABB">bg #A1DCD8 · text #012851 — inverted</td></tr>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="impl-ref">
|
||
<div class="impl-ref-hdr">Implementation Reference — Design Tokens <span>Real values · CSS custom properties</span></div>
|
||
<table>
|
||
<thead><tr><th>Token / concern</th><th>Tailwind class</th><th>CSS var</th><th>Notes</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Accent bar</td><td><code>border-l-[3px] border-primary</code></td><td><code>var(--color-primary)</code></td><td>Light: #012851. Dark: resolves to #A1DCD8 via theme. Never hardcode.</td></tr>
|
||
<tr><td>Topbar bg</td><td><code>bg-surface</code></td><td><code>var(--color-surface)</code></td><td>Auto light/dark via CSS custom property.</td></tr>
|
||
<tr><td>Bottom border</td><td><code>border-b border-line</code></td><td><code>var(--color-line)</code></td><td>1px, both themes.</td></tr>
|
||
<tr><td>Chip bg</td><td><code>bg-muted</code></td><td><code>var(--color-muted)</code></td><td>Light #F0EFE9, dark #0A1218.</td></tr>
|
||
<tr><td>Chip border</td><td><code>border-line</code></td><td>—</td><td>Same token as bottom border.</td></tr>
|
||
<tr><td>Hint strip bg (light)</td><td><code>bg-[rgba(1,40,81,0.05)]</code></td><td>—</td><td class="ir-warn">⚠ --color-primary must be RGB format (1 40 81) for bg-primary/5 to work. If hex, use explicit rgba fallback.</td></tr>
|
||
<tr><td>Hint strip bg (dark)</td><td><code>dark:bg-[rgba(161,220,216,0.04)]</code></td><td>—</td><td>Use explicit rgba. Verify --color-primary-fg is also RGB.</td></tr>
|
||
<tr><td>Avatar palette</td><td>inline style only</td><td>—</td><td>5 values: <code>['#012851','#5A3080','#007596','#2A6040','#803020']</code>. Index = <code>hash(id) % 5</code>.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════════════
|
||
SECTION 2 — OVERFLOW PATTERNS (visual)
|
||
══════════════════════════════════════════ -->
|
||
<div class="sh"><h2>2 · Receiver overflow patterns</h2><p>Rule: always show sender + 1st receiver, collapse remaining. At <768px: max 1 receiver shown, overflow pill is a non-interactive span. At ≥768px: show 2 receivers if count==2 (no pill); show 1 + pill if count≥3.</p></div>
|
||
|
||
<div style="background:#fff;border:1.5px solid #E0DDD6;border-radius:6px;overflow:hidden;margin-bottom:20px">
|
||
<div style="background:#F4F2EC;border-bottom:1px solid #E0DDD6;padding:7px 14px;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#012851">Light theme — all receiver counts</div>
|
||
<div style="padding:16px 14px;display:flex;flex-direction:column;gap:14px">
|
||
<div style="display:flex;align-items:center;gap:10px">
|
||
<div style="font-size:8px;font-weight:700;color:#AAA;text-transform:uppercase;letter-spacing:.5px;width:90px;flex-shrink:0">0 receivers</div>
|
||
<div style="display:flex;align-items:center;gap:5px;padding:6px 10px;background:#FAFAF8;border:1px dashed #E0DDD6;border-radius:4px">
|
||
<div class="chip"><div class="av" style="width:14px;height:14px;font-size:5px">KR</div><div class="cn" style="font-size:6.5px">Karl Raddatz</div></div>
|
||
</div>
|
||
<div style="font-size:9.5px;color:#999;font-style:italic">Sender only. No arrow. Diary entries, certificates.</div>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:10px">
|
||
<div style="font-size:8px;font-weight:700;color:#AAA;text-transform:uppercase;letter-spacing:.5px;width:90px;flex-shrink:0">1 receiver</div>
|
||
<div style="display:flex;align-items:center;gap:5px;padding:6px 10px;background:#FAFAF8;border:1px dashed #E0DDD6;border-radius:4px">
|
||
<div class="chip"><div class="av" style="width:14px;height:14px;font-size:5px">KR</div><div class="cn" style="font-size:6.5px">Karl Raddatz</div></div>
|
||
<span class="arr" style="font-size:9px">→</span>
|
||
<div class="chip"><div class="av purple" style="width:14px;height:14px;font-size:5px">ER</div><div class="cn" style="font-size:6.5px">Elfriede Raddatz</div></div>
|
||
</div>
|
||
<div style="font-size:9.5px;color:#999;font-style:italic">Both shown. No pill.</div>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:10px">
|
||
<div style="font-size:8px;font-weight:700;color:#AAA;text-transform:uppercase;letter-spacing:.5px;width:90px;flex-shrink:0">2 receivers</div>
|
||
<div style="display:flex;align-items:center;gap:5px;padding:6px 10px;background:#FAFAF8;border:1px dashed #E0DDD6;border-radius:4px">
|
||
<div class="chip"><div class="av" style="width:14px;height:14px;font-size:5px">KR</div><div class="cn" style="font-size:6.5px">Karl Raddatz</div></div>
|
||
<span class="arr" style="font-size:9px">→</span>
|
||
<div class="chip"><div class="av purple" style="width:14px;height:14px;font-size:5px">ER</div><div class="cn" style="font-size:6.5px">Elfriede Raddatz</div></div>
|
||
<span style="font-size:8px;color:#DDD">·</span>
|
||
<div class="chip"><div class="av teal" style="width:14px;height:14px;font-size:5px">HR</div><div class="cn" style="font-size:6.5px">Hans Raddatz</div></div>
|
||
</div>
|
||
<div style="font-size:9.5px;color:#999;font-style:italic">≥768px only: both shown, no pill. At <768px: collapse to 1st + "+1" span.</div>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:10px">
|
||
<div style="font-size:8px;font-weight:700;color:#AAA;text-transform:uppercase;letter-spacing:.5px;width:90px;flex-shrink:0">3 receivers</div>
|
||
<div style="display:flex;align-items:center;gap:5px;padding:6px 10px;background:#FAFAF8;border:1px dashed #E0DDD6;border-radius:4px">
|
||
<div class="chip"><div class="av" style="width:14px;height:14px;font-size:5px">KR</div><div class="cn" style="font-size:6.5px">Karl Raddatz</div></div>
|
||
<span class="arr" style="font-size:9px">→</span>
|
||
<div class="chip"><div class="av purple" style="width:14px;height:14px;font-size:5px">ER</div><div class="cn" style="font-size:6.5px">Elfriede Raddatz</div></div>
|
||
<div class="ov" style="font-size:6.5px">+2 weitere</div>
|
||
</div>
|
||
<div style="font-size:9.5px;color:#999;font-style:italic">≥768px: "+2 weitere" interactive button. <768px: "+2" non-interactive span, aria-hidden.</div>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:10px">
|
||
<div style="font-size:8px;font-weight:700;color:#AAA;text-transform:uppercase;letter-spacing:.5px;width:90px;flex-shrink:0">No sender</div>
|
||
<div style="display:flex;align-items:center;gap:5px;padding:6px 10px;background:#FAFAF8;border:1px dashed #E0DDD6;border-radius:4px">
|
||
<div class="chip"><div class="av purple" style="width:14px;height:14px;font-size:5px">ER</div><div class="cn" style="font-size:6.5px">Elfriede Raddatz</div></div>
|
||
</div>
|
||
<div style="font-size:9.5px;color:#999;font-style:italic">First available person shown. No arrow. Photos, undated documents.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="impl-ref">
|
||
<div class="impl-ref-hdr">Implementation Reference — Chip & Overflow Logic <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>PersonChip container</td><td><code>inline-flex items-center gap-1 rounded-full border border-line bg-muted px-2 py-1 whitespace-nowrap shrink-0</code></td><td><span class="ir-px">h ~28px, px 8px, py 4px</span></td><td>Not interactive — no hover/focus styles needed.</td></tr>
|
||
<tr><td>Avatar circle</td><td><code>flex w-[18px] h-[18px] shrink-0 items-center justify-center rounded-full text-[7px] font-bold</code></td><td><span class="ir-px">18×18px</span></td><td>bg from <code>personAvatarColor(id)</code> — inline style. text-primary-fg for navy bg, white for others.</td></tr>
|
||
<tr><td>Chip name (full)</td><td><code>text-[9px] font-semibold text-ink</code></td><td><span class="ir-px">9px / 600</span></td><td class="ir-warn">⚠ Most commonly undersized — minimum 9px. Original spec said 6.5px — corrected.</td></tr>
|
||
<tr><td>Chip name (abbreviated)</td><td><code>text-[9px] font-semibold text-ink</code></td><td><span class="ir-px">9px / 600</span></td><td>"K. Raddatz" format at <768px. Same styling as full name.</td></tr>
|
||
<tr><td>Arrow between chips</td><td><code>text-ink-2 shrink-0 text-[11px]</code> aria-hidden="true"</td><td><span class="ir-px">11px</span></td><td>Unicode → (U+2192). aria-hidden always present.</td></tr>
|
||
<tr><td>Overflow pill (≥768px)</td><td><code>inline-flex items-center rounded-full border border-line bg-muted px-2 py-1 text-[9px] font-bold text-ink-2 whitespace-nowrap shrink-0 min-h-[44px] md:min-h-0</code></td><td><span class="ir-px">9px / 700</span></td><td>Interactive button at ≥768px. Active state: <code>bg-primary border-primary text-primary-fg</code>.</td></tr>
|
||
<tr><td>Overflow pill (<768px)</td><td><code>inline-flex items-center rounded-full border border-line bg-muted px-2 py-1 text-[9px] font-bold text-ink-2 whitespace-nowrap shrink-0</code></td><td><span class="ir-px">9px / 700</span></td><td><code><span aria-hidden="true"></code> — not a button. No tap behaviour.</td></tr>
|
||
<tr><td>PersonChipRow wrapper</td><td><code>hidden xs:flex items-center gap-1.5 min-w-0 overflow-hidden</code></td><td>—</td><td>Hidden at XS (<375px). flex at ≥375px.</td></tr>
|
||
<tr><td>XS plain-text meta</td><td><code>block xs:hidden text-[9px] text-ink-2 truncate mt-0.5</code></td><td><span class="ir-px">9px</span></td><td>Format: "K.Raddatz → E.Raddatz +4 · 24.12.1943". From <code>formatXsMeta(doc)</code>.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════════════
|
||
SECTION 3 — VIEWPORT MOCKUPS
|
||
══════════════════════════════════════════ -->
|
||
<div class="sh"><h2>3 · Viewport-by-viewport</h2><p>Visual reference for each breakpoint. See <a href="document-topbar-b1-responsive.html" style="color:#012851">B1 spec</a> for full set. Key states shown below.</p></div>
|
||
|
||
<!-- 320px -->
|
||
<div style="margin-bottom:10px;font-size:11px;font-weight:700;color:#012851;border-left:3px solid #012851;padding-left:10px">320 px · XS Mobile</div>
|
||
<div class="grid">
|
||
<div class="col">
|
||
<div class="lbl"><span class="tag">320px</span>Light</div>
|
||
<div class="chrome" style="width:200px">
|
||
<div class="bar"><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="url"></div></div>
|
||
<div class="nav"><div class="nav-logo">FA</div><div class="nav-r"><div class="nav-av">KL</div></div></div>
|
||
<div style="background:#fff;border-bottom:1.5px solid #E4E2D8;border-left:3px solid #012851">
|
||
<div style="display:flex;align-items:center;padding:0 9px;height:26px;gap:7px">
|
||
<div style="width:24px;height:24px;border-radius:3px;background:#F0EFE9;display:flex;align-items:center;justify-content:center;flex-shrink:0"><div style="width:5px;height:5px;border-left:1.5px solid #666;border-bottom:1.5px solid #666;transform:rotate(45deg);margin-left:1px"></div></div>
|
||
<div style="flex:1;min-width:0">
|
||
<div style="font-size:10px;font-weight:800;color:#012851;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:Georgia,serif">Brief a. Großmutter</div>
|
||
<div style="font-size:5.5px;color:#AAA;margin-top:1px">K.Raddatz → E.Raddatz · 24.12.1943</div>
|
||
</div>
|
||
<div style="width:14px;height:14px;border:1px solid #C8C4BE;border-radius:2px;display:flex;align-items:center;justify-content:center;flex-shrink:0"><div style="width:6px;height:6px;border-left:1.5px solid #888;border-bottom:1.5px solid #888;position:relative"><div style="position:absolute;width:1.5px;height:5px;background:#888;top:-4px;left:1.5px"></div></div></div>
|
||
</div>
|
||
</div>
|
||
<div class="pdf" style="height:120px"><div class="paper" style="height:95px;width:80%"><div class="pl" style="width:65%"></div><div class="ps"></div><div class="ps" style="width:88%"></div><div class="ps" style="width:70%"></div></div></div>
|
||
</div>
|
||
<div class="cap">XS: square back btn, plain-text meta below title, icon-only edit button. No chips, no annotate, no download.</div>
|
||
</div>
|
||
<div class="col">
|
||
<div class="lbl"><span class="tag">320px</span>Dark</div>
|
||
<div class="chrome dark" style="width:200px">
|
||
<div class="bar"><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="url"></div></div>
|
||
<div class="nav dark"><div class="nav-logo">FA</div><div class="nav-r"><div class="nav-av">KL</div></div></div>
|
||
<div style="background:#0F1923;border-bottom:1.5px solid #1E2D3D;border-left:3px solid #A1DCD8">
|
||
<div style="display:flex;align-items:center;padding:0 9px;height:26px;gap:7px">
|
||
<div style="width:24px;height:24px;border-radius:3px;background:#0A1218;display:flex;align-items:center;justify-content:center;flex-shrink:0"><div style="width:5px;height:5px;border-left:1.5px solid #4E6070;border-bottom:1.5px solid #4E6070;transform:rotate(45deg);margin-left:1px"></div></div>
|
||
<div style="flex:1;min-width:0">
|
||
<div style="font-size:10px;font-weight:800;color:#EAE8E2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:Georgia,serif">Brief a. Großmutter</div>
|
||
<div style="font-size:5.5px;color:#3E5065;margin-top:1px">K.Raddatz → E.Raddatz · 24.12.1943</div>
|
||
</div>
|
||
<div style="width:14px;height:14px;border:1px solid #1E2D3D;border-radius:2px;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#4E6070"><div style="width:6px;height:6px;border-left:1.5px solid currentColor;border-bottom:1.5px solid currentColor;position:relative"><div style="position:absolute;width:1.5px;height:5px;background:currentColor;top:-4px;left:1.5px"></div></div></div>
|
||
</div>
|
||
</div>
|
||
<div class="pdf dark" style="height:120px"><div class="paper dark" style="height:95px;width:80%"><div class="pl"></div><div class="ps"></div><div class="ps" style="width:88%"></div></div></div>
|
||
</div>
|
||
<div class="cap">Dark XS: teal accent bar, icon-only edit, dark chip-less meta.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 375px -->
|
||
<div style="margin:24px 0 10px;font-size:11px;font-weight:700;color:#012851;border-left:3px solid #012851;padding-left:10px">375 px · Standard Mobile</div>
|
||
<div class="grid">
|
||
<div class="col">
|
||
<div class="lbl"><span class="tag">375px</span>Light · 1 receiver</div>
|
||
<div class="chrome" style="width:230px">
|
||
<div class="bar"><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="url"></div></div>
|
||
<div class="nav"><div class="nav-logo">FA</div><div class="nav-r"><div class="nav-av">KL</div></div></div>
|
||
<div style="background:#fff;border-bottom:1.5px solid #E4E2D8;border-left:3px solid #012851">
|
||
<div style="display:flex;align-items:center;padding:0 10px;height:30px;gap:8px">
|
||
<div style="width:26px;height:26px;border-radius:50%;border:1.5px solid #E0DDD6;display:flex;align-items:center;justify-content:center;flex-shrink:0"><div style="width:5px;height:5px;border-left:1.5px solid #666;border-bottom:1.5px solid #666;transform:rotate(45deg);margin-left:1px"></div></div>
|
||
<div style="flex:1;min-width:0;display:flex;flex-direction:column;gap:2px">
|
||
<div style="font-size:10.5px;font-weight:800;color:#012851;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:Georgia,serif">Brief a. Großmutter, 1943</div>
|
||
<div style="display:flex;align-items:center;gap:3px">
|
||
<div class="chip" style="padding:1.5px 5px 1.5px 2px;gap:2px"><div class="av" style="width:12px;height:12px;font-size:4.5px">KR</div><div class="cn" style="font-size:6px">K. Raddatz</div></div>
|
||
<span class="arr" style="font-size:8px">→</span>
|
||
<div class="chip" style="padding:1.5px 5px 1.5px 2px;gap:2px"><div class="av purple" style="width:12px;height:12px;font-size:4.5px">ER</div><div class="cn" style="font-size:6px">E. Raddatz</div></div>
|
||
</div>
|
||
</div>
|
||
<div style="width:14px;height:14px;border:1px solid #C8C4BE;border-radius:2px;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#888"><div style="width:6px;height:6px;border-left:1.5px solid currentColor;border-bottom:1.5px solid currentColor;position:relative"><div style="position:absolute;width:1.5px;height:5px;background:currentColor;top:-4px;left:1.5px"></div></div></div>
|
||
</div>
|
||
</div>
|
||
<div class="pdf" style="height:130px"><div class="paper" style="height:103px;width:78%"><div class="pl" style="width:65%"></div><div class="ps"></div><div class="ps" style="width:88%"></div><div class="ps" style="width:70%"></div></div></div>
|
||
</div>
|
||
<div class="cap">375px: circle back btn, chip row with abbreviated names, icon-only edit. Annotate hidden.</div>
|
||
</div>
|
||
<div class="col">
|
||
<div class="lbl"><span class="tag">375px</span>Light · 3+ receivers</div>
|
||
<div class="chrome" style="width:230px">
|
||
<div class="bar"><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="url"></div></div>
|
||
<div class="nav"><div class="nav-logo">FA</div><div class="nav-r"><div class="nav-av">KL</div></div></div>
|
||
<div style="background:#fff;border-bottom:1.5px solid #E4E2D8;border-left:3px solid #012851">
|
||
<div style="display:flex;align-items:center;padding:0 10px;height:30px;gap:8px">
|
||
<div style="width:26px;height:26px;border-radius:50%;border:1.5px solid #E0DDD6;display:flex;align-items:center;justify-content:center;flex-shrink:0"><div style="width:5px;height:5px;border-left:1.5px solid #666;border-bottom:1.5px solid #666;transform:rotate(45deg);margin-left:1px"></div></div>
|
||
<div style="flex:1;min-width:0;display:flex;flex-direction:column;gap:2px">
|
||
<div style="font-size:10.5px;font-weight:800;color:#012851;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:Georgia,serif">Rundbrief, 1951</div>
|
||
<div style="display:flex;align-items:center;gap:3px">
|
||
<div class="chip" style="padding:1.5px 5px 1.5px 2px;gap:2px"><div class="av" style="width:12px;height:12px;font-size:4.5px">KR</div><div class="cn" style="font-size:6px">K. Raddatz</div></div>
|
||
<span class="arr" style="font-size:8px">→</span>
|
||
<div class="chip" style="padding:1.5px 5px 1.5px 2px;gap:2px"><div class="av purple" style="width:12px;height:12px;font-size:4.5px">ER</div><div class="cn" style="font-size:6px">E. Raddatz</div></div>
|
||
<div class="ov" style="font-size:6px;padding:1.5px 5px">+2</div>
|
||
</div>
|
||
</div>
|
||
<div style="width:14px;height:14px;border:1px solid #C8C4BE;border-radius:2px;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#888"><div style="width:6px;height:6px;border-left:1.5px solid currentColor;border-bottom:1.5px solid currentColor;position:relative"><div style="position:absolute;width:1.5px;height:5px;background:currentColor;top:-4px;left:1.5px"></div></div></div>
|
||
</div>
|
||
</div>
|
||
<div class="pdf" style="height:130px"><div class="paper" style="height:103px;width:78%"><div class="pl" style="width:65%"></div><div class="ps"></div></div></div>
|
||
</div>
|
||
<div class="cap">375px overflow: "+2" is a non-interactive <span aria-hidden>. No "weitere" — no tooltip on mobile.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 768px Tablet -->
|
||
<div style="margin:24px 0 10px;font-size:11px;font-weight:700;color:#012851;border-left:3px solid #012851;padding-left:10px">768 px · Tablet</div>
|
||
<div class="grid">
|
||
<div class="col">
|
||
<div class="lbl"><span class="tag">768px</span>Light · 1 receiver</div>
|
||
<div class="chrome" style="width:420px">
|
||
<div class="bar"><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="url"></div></div>
|
||
<div class="nav"><div class="nav-logo">FAMILIENARCHIV</div><div class="nav-link">Dokumente</div><div class="nav-r"><div class="nav-av">KL</div></div></div>
|
||
<div style="background:#fff;border-bottom:1.5px solid #E4E2D8;border-left:3px solid #012851;display:flex;align-items:center;padding:0 12px;height:30px;gap:10px">
|
||
<div style="width:28px;height:28px;border-radius:50%;border:1.5px solid #E0DDD6;display:flex;align-items:center;justify-content:center;flex-shrink:0"><div style="width:6px;height:6px;border-left:1.5px solid #666;border-bottom:1.5px solid #666;transform:rotate(45deg);margin-left:2px"></div></div>
|
||
<div style="flex:1;min-width:0;display:flex;flex-direction:column;gap:3px">
|
||
<div style="display:flex;align-items:center;gap:5px">
|
||
<div style="font-size:11.5px;font-weight:800;color:#012851;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:Georgia,serif">Brief an Großmutter, Weihnachten 1943</div>
|
||
<div class="st-dot" style="width:6px;height:6px;flex-shrink:0" title="Hochgeladen"></div>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:4px">
|
||
<div style="font-size:6.5px;color:#AAA">24.12.1943</div>
|
||
<span style="font-size:8px;color:#DDD">·</span>
|
||
<div class="chip" style="padding:2px 6px 2px 3px"><div class="av" style="width:14px;height:14px;font-size:5px">KR</div><div class="cn" style="font-size:6.5px">Karl Raddatz</div></div>
|
||
<span class="arr" style="font-size:9px">→</span>
|
||
<div class="chip" style="padding:2px 6px 2px 3px"><div class="av purple" style="width:14px;height:14px;font-size:5px">ER</div><div class="cn" style="font-size:6.5px">Elfriede Raddatz</div></div>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:4px;flex-shrink:0">
|
||
<div class="btn-g" style="display:inline-flex;align-items:center;gap:3px"><div style="width:7px;height:7px;border:1.5px solid #888;border-radius:50%"></div>Annotieren</div>
|
||
<div class="btn-p" style="display:inline-flex;align-items:center;gap:3px"><div style="width:6px;height:6px;border-left:1.5px solid #A1DCD8;border-bottom:1.5px solid #A1DCD8;position:relative"><div style="position:absolute;width:1.5px;height:5px;background:#A1DCD8;top:-4px;left:1.5px"></div></div>Bearbeiten</div>
|
||
<div class="dv" style="height:16px"></div>
|
||
<div class="ico"><div class="dl" style="color:#888"></div></div>
|
||
</div>
|
||
</div>
|
||
<div class="pdf" style="height:150px"><div class="paper" style="height:118px"><div class="pl" style="width:65%"></div><div class="ps"></div><div class="ps" style="width:88%"></div><div class="ps" style="width:72%"></div></div></div>
|
||
</div>
|
||
<div class="cap">768px: full names, dot-only status indicator, Annotate + Bearbeiten + download icon. Status dot replaces text chip.</div>
|
||
</div>
|
||
<div class="col">
|
||
<div class="lbl"><span class="tag">768px</span>Dark · annotate active</div>
|
||
<div class="chrome dark" style="width:380px">
|
||
<div class="bar"><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="url"></div></div>
|
||
<div class="nav dark"><div class="nav-logo">FAMILIENARCHIV</div><div class="nav-r"><div class="nav-av">KL</div></div></div>
|
||
<div style="background:#0F1923;border-bottom:1.5px solid #1E2D3D;border-left:3px solid #A1DCD8">
|
||
<div style="display:flex;align-items:center;padding:0 12px;height:30px;gap:10px">
|
||
<div style="width:28px;height:28px;border-radius:50%;border:1.5px solid #1E2D3D;display:flex;align-items:center;justify-content:center;flex-shrink:0"><div style="width:6px;height:6px;border-left:1.5px solid #4E6070;border-bottom:1.5px solid #4E6070;transform:rotate(45deg);margin-left:2px"></div></div>
|
||
<div style="flex:1;min-width:0;display:flex;flex-direction:column;gap:3px">
|
||
<div style="font-size:11.5px;font-weight:800;color:#EAE8E2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:Georgia,serif">Brief an Großmutter</div>
|
||
<div style="display:flex;align-items:center;gap:4px">
|
||
<div class="chip dk" style="padding:2px 6px 2px 3px"><div class="av" style="width:14px;height:14px;font-size:5px">KR</div><div class="cn dk" style="font-size:6.5px">Karl Raddatz</div></div>
|
||
<span class="arr dk" style="font-size:9px">→</span>
|
||
<div class="chip dk" style="padding:2px 6px 2px 3px"><div class="av purple" style="width:14px;height:14px;font-size:5px">ER</div><div class="cn dk" style="font-size:6.5px">Elfriede Raddatz</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="btn-g on dk" style="display:inline-flex;align-items:center;gap:3px"><div style="width:7px;height:7px;border:1.5px solid #012851;border-radius:50%"></div>Beenden</div>
|
||
</div>
|
||
<div class="hint dk"><div class="hint-lbl dk">Annotierungsmodus aktiv</div><div class="hint-txt">Klicken Sie auf eine Textstelle.</div></div>
|
||
</div>
|
||
<div class="pdf dark" style="height:142px"><div class="paper dark" style="height:112px;outline:2px solid rgba(161,220,216,.15)"><div class="pl"></div><div class="ps"></div><div class="ps" style="width:88%"></div></div></div>
|
||
</div>
|
||
<div class="cap">Annotate active: Edit + Download removed. "Beenden" fills teal. Hint strip visible. PDF outline teal at 15%.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 1024px -->
|
||
<div style="margin:24px 0 10px;font-size:11px;font-weight:700;color:#012851;border-left:3px solid #012851;padding-left:10px">1024 px · Laptop / 1440 px · Desktop</div>
|
||
<div class="grid">
|
||
<div class="col">
|
||
<div class="lbl"><span class="tag">1024px</span>Light · 5 receivers (overflow open)</div>
|
||
<div class="chrome" style="width:560px">
|
||
<div class="bar"><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="url"></div></div>
|
||
<div class="nav"><div class="nav-logo">FAMILIENARCHIV</div><div class="nav-link">Dokumente</div><div class="nav-link">Personen</div><div class="nav-r"><div class="nav-av">KL</div></div></div>
|
||
<div style="background:#fff;border-bottom:1.5px solid #E4E2D8;border-left:3px solid #012851;display:flex;align-items:center;padding:0 14px;height:30px;gap:10px;position:relative">
|
||
<div style="width:28px;height:28px;border-radius:50%;border:1.5px solid #E0DDD6;display:flex;align-items:center;justify-content:center;flex-shrink:0"><div style="width:6px;height:6px;border-left:1.5px solid #666;border-bottom:1.5px solid #666;transform:rotate(45deg);margin-left:2px"></div></div>
|
||
<div style="flex:1;min-width:0;display:flex;flex-direction:column;gap:3px">
|
||
<div style="display:flex;align-items:center;gap:6px">
|
||
<div style="font-size:12px;font-weight:800;color:#012851;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:Georgia,serif">Rundbrief an die Familie, Sommer 1951</div>
|
||
<div class="st-dot" style="width:6px;height:6px;flex-shrink:0" title="Archiviert"></div>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:5px;position:relative">
|
||
<div style="font-size:6.5px;color:#AAA">18.07.1951 · Berlin</div>
|
||
<span style="font-size:8px;color:#DDD">·</span>
|
||
<div class="chip"><div class="av" style="width:14px;height:14px;font-size:5px">KR</div><div class="cn" style="font-size:6.5px">Karl Raddatz</div></div>
|
||
<span class="arr" style="font-size:9px">→</span>
|
||
<div class="chip"><div class="av purple" style="width:14px;height:14px;font-size:5px">ER</div><div class="cn" style="font-size:6.5px">Elfriede Raddatz</div></div>
|
||
<div style="display:inline-flex;align-items:center;padding:2px 6px;background:#012851;border:1px solid #012851;border-radius:10px;font-size:6.5px;font-weight:700;color:#A1DCD8;position:relative">
|
||
+4 weitere
|
||
<div style="position:absolute;top:18px;left:0;background:#fff;border:1.5px solid #E0DDD6;border-radius:5px;padding:10px 12px;min-width:155px;z-index:10;box-shadow:0 4px 16px rgba(0,0,0,.12)">
|
||
<div style="font-size:7px;font-weight:800;color:#AAA;text-transform:uppercase;letter-spacing:.6px;margin-bottom:7px;border-bottom:1px solid #F0EEE8;padding-bottom:5px">Weitere Empfänger</div>
|
||
<div style="display:flex;align-items:center;gap:6px;margin-bottom:5px"><div class="av teal" style="width:16px;height:16px;font-size:5.5px">HR</div><div style="font-size:9.5px;color:#333;font-weight:500">Hans Raddatz</div></div>
|
||
<div style="display:flex;align-items:center;gap:6px;margin-bottom:5px"><div class="av moss" style="width:16px;height:16px;font-size:5.5px">MR</div><div style="font-size:9.5px;color:#333;font-weight:500">Maria Raddatz</div></div>
|
||
<div style="display:flex;align-items:center;gap:6px"><div class="av rust" style="width:16px;height:16px;font-size:5.5px">GR</div><div style="font-size:9.5px;color:#333;font-weight:500">Gerhard Raddatz</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:5px;flex-shrink:0">
|
||
<div class="btn-g" style="display:inline-flex;align-items:center;gap:3px"><div style="width:7px;height:7px;border:1.5px solid #888;border-radius:50%"></div>Annotieren</div>
|
||
<div class="btn-p">Bearbeiten</div>
|
||
<div class="dv" style="height:16px"></div>
|
||
<div class="ico"><div class="dl" style="color:#888"></div></div>
|
||
</div>
|
||
</div>
|
||
<div class="pdf" style="height:155px"><div class="paper" style="height:122px"><div class="pl" style="width:65%"></div><div class="ps"></div><div class="ps" style="width:88%"></div></div></div>
|
||
</div>
|
||
<div class="cap">Active overflow pill fills navy. Tooltip drops below: "Weitere Empfänger" + avatar + name link per person.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="impl-ref">
|
||
<div class="impl-ref-hdr">Implementation Reference — Topbar Layout per Breakpoint <span>Real values · mockup above is ~55% scale</span></div>
|
||
<table>
|
||
<thead><tr><th>Element</th><th>XS <375px</th><th>Mobile 375–767px</th><th>Tablet/Desktop ≥768px</th><th>Notes</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Topbar height</td><td><code>h-12</code> <span class="ir-px">48px</span></td><td><code>h-14</code> <span class="ir-px">56px</span></td><td><code>h-14</code> <span class="ir-px">56px</span></td><td class="ir-warn">⚠ Increased from B1 spec (was 44/50/52px) to fit text-[11px]+ chip row.</td></tr>
|
||
<tr><td>Back button wrapper</td><td colspan="3"><code>a href="/documents" w-11 h-11 -ml-2 flex items-center justify-center shrink-0 focus-visible:ring-2</code></td><td><code>w-11 h-11</code> = 44×44px touch area. -ml-2 = visual alignment. aria-label="Zurück zur Dokumentenliste" always.</td></tr>
|
||
<tr><td>Back button visual</td><td><code>w-6 h-6 rounded-sm bg-muted flex items-center justify-center</code></td><td><code>w-6 h-6 rounded-full border border-line flex items-center justify-center</code></td><td><code>w-7 h-7 rounded-full border border-line flex items-center justify-center</code></td><td>Shape changes at xs breakpoint. Inner chevron SVG 10×10px.</td></tr>
|
||
<tr><td>Title</td><td><code>font-serif font-extrabold text-[11px] text-ink truncate</code></td><td><code>font-serif font-extrabold text-[11px] text-ink truncate</code></td><td><code>font-serif font-extrabold text-[12px] lg:text-[13px] text-ink truncate</code></td><td class="ir-warn">⚠ Minimum 11px — original spec said 10px, corrected.</td></tr>
|
||
<tr><td>Status indicator</td><td>Hidden <code>hidden</code></td><td>Hidden <code>hidden</code></td><td><code>hidden md:block w-2.5 h-2.5 rounded-full shrink-0</code></td><td>Dot only. title + aria-label carry label. See statusDotClass() for colours.</td></tr>
|
||
<tr><td>Date text</td><td>In xsMetaLine string</td><td><code>text-xs text-ink-2 shrink-0</code> format: "24.12.1943"</td><td><code>text-xs text-ink-2 shrink-0</code> format: ≥1024px long ("24. Dezember 1943")</td><td>Date format switches via Tailwind: <code><span class="lg:hidden">24.12.1943</span><span class="hidden lg:inline">{longDate}</span></code></td></tr>
|
||
<tr><td>Location in meta</td><td>Hidden</td><td>Hidden</td><td>Shown if doc.location present at ≥768px: <code>hidden md:inline</code></td><td>// TODO: show location when doc.location field available on DTO</td></tr>
|
||
<tr><td>Edit button (XS/mobile)</td><td colspan="2"><code>inline-flex items-center justify-center w-11 h-11 -mr-2 shrink-0</code> with pencil SVG 18×18px. aria-label="Bearbeiten".</td><td>—</td><td class="ir-warn">⚠ Icon-only on mobile. "Bearbeiten" text hidden below 768px.</td></tr>
|
||
<tr><td>Edit button (tablet+)</td><td>—</td><td>—</td><td><code>hidden md:inline-flex h-10 items-center gap-2 px-4 bg-primary text-primary-fg text-[11px] font-bold uppercase tracking-wide rounded-sm</code></td><td>Shows pencil icon + "Bearbeiten" label at ≥768px.</td></tr>
|
||
<tr><td>Annotate button</td><td>Hidden <code>hidden</code></td><td>Hidden <code>hidden</code></td><td><code>hidden md:inline-flex h-10 items-center gap-2 px-3 border border-line text-ink-2 text-[11px] font-bold uppercase tracking-wide rounded-sm</code></td><td>Active: <code>bg-primary border-primary text-primary-fg</code>. Label "Annotieren" → "Beenden". aria-pressed={annotateMode}.</td></tr>
|
||
<tr><td>Download button</td><td>Hidden</td><td>Hidden</td><td><code>hidden md:inline-flex w-10 h-10 items-center justify-center border border-line rounded-sm text-ink-2</code></td><td>Icon only — download SVG 18×18px.</td></tr>
|
||
<tr><td>Divider</td><td>Hidden</td><td>Hidden</td><td><code>hidden md:block w-px h-4 bg-line shrink-0</code></td><td>Between download icon and Bearbeiten button.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════════════
|
||
SECTION 4 — RESPONSIVE RULES TABLE (updated)
|
||
══════════════════════════════════════════ -->
|
||
<div class="sh"><h2>4 · Responsive rules (authoritative)</h2><p>All heights and font sizes updated from resolved review. These values override the B1 spec.</p></div>
|
||
|
||
<div class="rules">
|
||
<table>
|
||
<thead><tr><th>Element</th><th>≤ 374px (XS)</th><th>375–767px (mobile)</th><th>768–1023px (tablet)</th><th>≥ 1024px (desktop)</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Topbar height</td><td>h-12 (48px)</td><td>h-14 (56px)</td><td>h-14 (56px)</td><td>h-14 (56px)</td></tr>
|
||
<tr><td>Back button</td><td>Square 24×24 <code>rounded-sm bg-muted</code></td><td>Circle 24×24 <code>rounded-full border-line</code></td><td>Circle 28×28 <code>rounded-full border-line</code></td><td>Circle 28×28</td></tr>
|
||
<tr><td>Touch target</td><td colspan="4"><code>w-11 h-11 -ml-2</code> wrapper around all back button variants. Always 44×44px.</td></tr>
|
||
<tr><td>Title size</td><td>text-[11px] / 800</td><td>text-[11px] / 800</td><td>text-[12px] / 800</td><td>text-[13px] / 800</td></tr>
|
||
<tr><td>Chip row</td><td>Hidden → plain-text xsMetaLine</td><td>Shown — abbreviated names</td><td>Shown — full names</td><td>Shown — full names + location</td></tr>
|
||
<tr><td>Chip name text</td><td>N/A</td><td>text-[9px] / 600</td><td>text-[9px] / 600</td><td>text-[9px] / 600</td></tr>
|
||
<tr><td>Date format</td><td><code>dd.mm.yyyy</code> in xsMetaLine</td><td><code>24.12.1943</code></td><td><code>24.12.1943</code></td><td><code>24. Dezember 1943</code></td></tr>
|
||
<tr><td>Status indicator</td><td>Hidden</td><td>Hidden</td><td>Dot only — <code>w-2.5 h-2.5 rounded-full</code></td><td>Dot only</td></tr>
|
||
<tr><td>Annotate button</td><td>Hidden</td><td>Hidden</td><td>Shown — "Annotieren"</td><td>Shown — "Annotieren"</td></tr>
|
||
<tr><td>Edit button</td><td>Icon only (pencil SVG)</td><td>Icon only</td><td>Icon + "Bearbeiten"</td><td>Icon + "Bearbeiten"</td></tr>
|
||
<tr><td>Download button</td><td>Hidden</td><td>Hidden</td><td>Icon only</td><td>Icon only</td></tr>
|
||
<tr><td>Overflow pill</td><td>N/A (chips hidden)</td><td><code><span aria-hidden></code> "+N"</td><td><code><button></code> "+N weitere" → tooltip</td><td><code><button></code> "+N weitere" → tooltip</td></tr>
|
||
<tr><td>Hint strip</td><td>Hidden</td><td>Hidden</td><td>18px strip when annotateMode</td><td>18px strip when annotateMode</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="impl-ref">
|
||
<div class="impl-ref-hdr">Implementation Reference — Tailwind breakpoint setup <span>tailwind.config.ts</span></div>
|
||
<table>
|
||
<thead><tr><th>Item</th><th>Value</th><th>Notes</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Custom xs breakpoint</td><td><code>xs: '375px'</code> in <code>theme.extend.screens</code></td><td class="ir-warn">⚠ Tailwind 4 may use different config syntax — verify before using <code>xs:</code> prefix classes. Without this, <code>xs:flex</code> silently does nothing.</td></tr>
|
||
<tr><td>Usage for chip row</td><td><code>hidden xs:flex</code></td><td>Hidden at <375px, flex at ≥375px.</td></tr>
|
||
<tr><td>Usage for XS meta</td><td><code>block xs:hidden</code></td><td>Only visible below 375px.</td></tr>
|
||
<tr><td>Usage for overflow pill type</td><td>CSS only: <code>hidden xs:inline-flex</code> for button, always-rendered span for mobile with <code>md:hidden</code></td><td>Prefer CSS over JS viewport check — no SSR issues.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════════════
|
||
SECTION 5 — OVERFLOW TOOLTIP
|
||
══════════════════════════════════════════ -->
|
||
<div class="sh"><h2>5 · Overflow tooltip</h2><p>Clicking "+N weitere" opens a floating panel. Only at ≥768px. Mobile overflow pill is a non-interactive span.</p></div>
|
||
|
||
<div class="impl-ref">
|
||
<div class="impl-ref-hdr">Implementation Reference — OverflowPill component <span>Real values · Svelte 5</span></div>
|
||
<table>
|
||
<thead><tr><th>Element</th><th>Tailwind classes</th><th>Real size</th><th>Notes</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Pill (interactive ≥768px)</td><td><code>inline-flex items-center rounded-full border border-line bg-muted px-2 py-1 text-[9px] font-bold text-ink-2 whitespace-nowrap shrink-0 cursor-pointer</code></td><td><span class="ir-px">h ~28px, 9px text</span></td><td>Active state: <code>bg-primary border-primary text-primary-fg</code>. aria-haspopup="listbox" aria-expanded={open} aria-label="{count} weitere Empfänger".</td></tr>
|
||
<tr><td>Pill (non-interactive <768px)</td><td>same visual classes as above on <code><span aria-hidden="true"></code></td><td>—</td><td>No button, no onclick, no tooltip. Users access full receiver list via document edit page.</td></tr>
|
||
<tr><td>Tooltip panel</td><td><code>absolute top-full left-0 mt-1 bg-surface border border-line rounded-md shadow-lg p-3 min-w-[160px] z-50</code></td><td><span class="ir-px">min 160px wide, p 12px</span></td><td>position:relative on chip row container. role="listbox".</td></tr>
|
||
<tr><td>Tooltip header</td><td><code>text-[9px] font-bold uppercase tracking-wide text-ink-2 border-b border-line pb-2 mb-2</code></td><td><span class="ir-px">9px / 700</span></td><td>Text: "Weitere Empfänger".</td></tr>
|
||
<tr><td>Person row</td><td><code>flex items-center gap-2 py-1 rounded hover:bg-muted</code></td><td><span class="ir-px">min 32px tall</span></td><td>role="option". Each row is an <a href="/persons/{id}">. Avatar 16×16px.</td></tr>
|
||
<tr><td>Person name in tooltip</td><td><code>text-[11px] font-medium text-ink</code></td><td><span class="ir-px">11px / 500</span></td><td>Full name always shown in tooltip.</td></tr>
|
||
<tr><td>Keyboard: open</td><td>—</td><td>—</td><td>Enter/Space on pill → open tooltip → focus first role="option".</td></tr>
|
||
<tr><td>Keyboard: navigate</td><td>—</td><td>—</td><td>Tab/Shift+Tab between options inside tooltip.</td></tr>
|
||
<tr><td>Keyboard: close</td><td>—</td><td>—</td><td>Escape → close + return focus to pill. Click-outside → close. Second click on pill → close.</td></tr>
|
||
<tr><td>Click-outside</td><td>—</td><td>—</td><td>Use <code>use:clickOutside</code> Svelte action — do not use document.addEventListener directly in $effect.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="impl-ref">
|
||
<div class="impl-ref-hdr">Implementation Reference — Annotate hint strip <span>AnnotateHintStrip.svelte</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 wrapper</td><td><code>hidden md:flex h-[18px] items-center gap-2 border-t border-dashed px-3.5</code></td><td><span class="ir-px">h 18px</span></td><td>Only rendered when annotateMode===true via Svelte {#if}. hidden below md.</td></tr>
|
||
<tr><td>Strip bg (light)</td><td><code>bg-[rgba(1,40,81,0.05)]</code></td><td>—</td><td class="ir-warn">⚠ Use explicit rgba — bg-primary/5 requires RGB --color-primary. Fallback if not converted.</td></tr>
|
||
<tr><td>Strip border (light)</td><td><code>border-[rgba(1,40,81,0.20)]</code></td><td>—</td><td>Same caveat — explicit rgba.</td></tr>
|
||
<tr><td>Strip bg (dark)</td><td><code>dark:bg-[rgba(161,220,216,0.04)]</code></td><td>—</td><td>Explicit rgba.</td></tr>
|
||
<tr><td>Label text</td><td><code>text-[9px] font-bold uppercase tracking-wide text-primary</code></td><td><span class="ir-px">9px / 700</span></td><td class="ir-warn">⚠ Corrected from spec's 5.5px — minimum 9px. Dark: text-primary-fg.</td></tr>
|
||
<tr><td>Body text</td><td><code>text-[9px] text-ink-2</code></td><td><span class="ir-px">9px</span></td><td>"Klicken Sie auf eine Textstelle im Dokument, um eine Anmerkung hinzuzufügen."</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════════════
|
||
SECTION 6 — UTILITY FUNCTIONS
|
||
══════════════════════════════════════════ -->
|
||
<div class="sh"><h2>6 · Utility functions — <code>src/lib/utils/personFormat.ts</code></h2><p>Pure functions. Write Vitest unit tests for each before implementing. No DOM, no side effects.</p></div>
|
||
|
||
<div class="rules">
|
||
<table>
|
||
<thead><tr><th>Function</th><th>Signature</th><th>Behaviour & edge cases</th></tr></thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>abbreviateName</td>
|
||
<td><code>abbreviateName(person: Person): string</code></td>
|
||
<td>
|
||
"Karl Raddatz" → "K. Raddatz"<br>
|
||
"Elfriede" (single name) → "Elfriede" (no initial, return as-is)<br>
|
||
"Karl Müller-Schmidt" → "K. Müller-Schmidt" (preserve hyphenated last name)<br>
|
||
Split on first space only. First character of first word + ". " + rest.
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>formatXsMeta</td>
|
||
<td><code>formatXsMeta(doc: Document): string</code></td>
|
||
<td>
|
||
0 receivers: "K.Raddatz · 24.12.1943"<br>
|
||
1 receiver: "K.Raddatz → E.Raddatz · 24.12.1943"<br>
|
||
3 receivers: "K.Raddatz → E.Raddatz +2 · 24.12.1943"<br>
|
||
No sender: "E.Raddatz · 24.12.1943"<br>
|
||
Abbreviated format: first initial + dot + last name, no space (e.g. "K.Raddatz").<br>
|
||
Date: <code>dd.mm.yyyy</code> format, no spaces.
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>personAvatarColor</td>
|
||
<td><code>personAvatarColor(personId: string): string</code></td>
|
||
<td>
|
||
Returns one of: <code>['#012851','#5A3080','#007596','#2A6040','#803020']</code><br>
|
||
Must be deterministic: same ID always returns same colour.<br>
|
||
Implementation: <code>PALETTE[simpleHash(id) % PALETTE.length]</code><br>
|
||
<strong>simpleHash</strong>: sum of char codes, or djb2. Never interpolate the ID string into CSS directly.<br>
|
||
Test: 1000 random UUIDs → all map to valid palette entry.
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>formatDate</td>
|
||
<td><code>formatDate(isoDate: string, format: 'short' | 'long'): string</code></td>
|
||
<td>
|
||
short: "24.12.1943" — use <code>Intl.DateTimeFormat('de-DE', {day:'2-digit', month:'2-digit', year:'numeric'})</code><br>
|
||
long: "24. Dezember 1943" — use <code>month: 'long'</code><br>
|
||
Always parse with <code>new Date(isoDate + 'T12:00:00')</code> to avoid UTC off-by-one.
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>statusDotClass</td>
|
||
<td><code>statusDotClass(status: DocumentStatus): string</code></td>
|
||
<td>
|
||
PLACEHOLDER → <code>'bg-gray-400'</code><br>
|
||
UPLOADED → <code>'bg-emerald-500'</code><br>
|
||
TRANSCRIBED → <code>'bg-blue-400'</code><br>
|
||
REVIEWED → <code>'bg-amber-400'</code><br>
|
||
ARCHIVED → <code>'bg-emerald-600'</code>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>statusLabel (for title/aria)</td>
|
||
<td><code>statusLabel(status: DocumentStatus): string</code></td>
|
||
<td>
|
||
German labels (not shown as text, used in title + aria-label only):<br>
|
||
PLACEHOLDER → "Platzhalter" · UPLOADED → "Hochgeladen" · TRANSCRIBED → "Transkribiert"<br>
|
||
REVIEWED → "Geprüft" · ARCHIVED → "Archiviert"
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════════════
|
||
SECTION 7 — ACCESSIBILITY
|
||
══════════════════════════════════════════ -->
|
||
<div class="sh"><h2>7 · Accessibility requirements (WCAG 2.2 AA)</h2></div>
|
||
|
||
<div class="rules">
|
||
<table>
|
||
<thead><tr><th>Element</th><th>Requirement</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Landmark</td><td>Parent page must wrap topbar in <code><header role="banner"></code> or the topbar must itself be in <code><header></code>. Verify parent layout.</td></tr>
|
||
<tr><td>Back link</td><td><code>aria-label="Zurück zur Dokumentenliste"</code> always present — icon is the only visible element at XS.</td></tr>
|
||
<tr><td>Edit button (icon-only mobile)</td><td><code>aria-label="Bearbeiten"</code> always present, even at mobile where it renders icon-only.</td></tr>
|
||
<tr><td>Annotate button</td><td><code>aria-pressed={annotateMode}</code>. Label changes: "Annotieren" → "Beenden".</td></tr>
|
||
<tr><td>Overflow pill</td><td><code>aria-haspopup="listbox"</code> (not "true"), <code>aria-expanded={overflowOpen}</code>, <code>aria-label="{extraCount} weitere Empfänger anzeigen"</code>.</td></tr>
|
||
<tr><td>Overflow tooltip</td><td><code>role="listbox"</code> on panel. <code>role="option"</code> on each person row. Not a modal — no focus trap needed.</td></tr>
|
||
<tr><td>Tooltip focus flow</td><td>Opening tooltip moves focus to first <code>role="option"</code>. Tab/Shift+Tab navigates within. Escape closes + returns focus to pill.</td></tr>
|
||
<tr><td>Arrow between chips</td><td><code>aria-hidden="true"</code> on the → character. Directionality conveyed by order, not just the arrow.</td></tr>
|
||
<tr><td>Status dot</td><td><code>title={statusLabel(doc.status)}</code> for hover tooltip. <code>aria-label={statusLabel(doc.status)}</code>. No text label rendered.</td></tr>
|
||
<tr><td>Touch targets</td><td>Back button: 44×44px via wrapper. Edit (mobile): 44×44px via wrapper. Overflow pill: naturally ≥44px wide at ≥375px. All verified.</td></tr>
|
||
<tr><td>Focus rings</td><td>Never <code>outline:none</code> without a replacement. All interactive elements: <code>focus-visible:ring-2 focus-visible:ring-primary</code>.</td></tr>
|
||
<tr><td>Colour alone</td><td>Status uses colour + aria-label. Never colour as only signal. Annotate mode uses label change "Beenden" + visual fill + hint strip.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════════════
|
||
SECTION 8 — ACCEPTANCE CRITERIA
|
||
══════════════════════════════════════════ -->
|
||
<div class="sh"><h2>8 · Acceptance criteria</h2></div>
|
||
|
||
<div class="rules">
|
||
<table>
|
||
<thead><tr><th>ID</th><th>Criterion</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>AC-01</td><td>Topbar renders correctly at 320px, 375px, 768px, 1024px, 1440px — matches spec screenshots. Verified by /proofshot at all 5 widths.</td></tr>
|
||
<tr><td>AC-02</td><td>Light and dark themes match token table — no hardcoded hex values in any component file.</td></tr>
|
||
<tr><td>AC-03</td><td>0-receiver, 1-receiver, 2-receiver, 3-receiver, and 5-receiver cases all render correctly per Section 2.</td></tr>
|
||
<tr><td>AC-04</td><td>No-sender case: chip row shows receivers only (or first person if no sender), no arrow rendered.</td></tr>
|
||
<tr><td>AC-05</td><td>Overflow tooltip opens/closes on click, Enter/Space, Escape. Escape returns focus to pill. Click-outside closes.</td></tr>
|
||
<tr><td>AC-06</td><td>Opening tooltip moves focus to first person link inside tooltip.</td></tr>
|
||
<tr><td>AC-07</td><td>Overflow tooltip links navigate to correct /persons/{id} URL.</td></tr>
|
||
<tr><td>AC-08</td><td>Overflow pill at <768px is a non-interactive span — no tooltip, no tap action.</td></tr>
|
||
<tr><td>AC-09</td><td>Annotate mode: Edit + Download hidden; hint strip visible; button label "Beenden"; aria-pressed=true.</td></tr>
|
||
<tr><td>AC-10</td><td>Annotate hint strip NOT rendered below 768px even when annotateMode===true.</td></tr>
|
||
<tr><td>AC-11</td><td>Status dot visible at ≥768px with correct colour per status. Hidden below.</td></tr>
|
||
<tr><td>AC-12</td><td>All touch targets ≥44×44px at mobile (back button, edit button, overflow pill where interactive).</td></tr>
|
||
<tr><td>AC-13</td><td>aria-pressed, aria-haspopup="listbox", aria-expanded, aria-label on all interactive elements.</td></tr>
|
||
<tr><td>AC-14</td><td>svelte-check passes with no new type errors.</td></tr>
|
||
<tr><td>AC-15</td><td>Unit tests pass for: abbreviateName (full, single, hyphenated), formatXsMeta (0/1/3+ receivers, no sender), personAvatarColor (deterministic, palette-only), statusDotClass (all 5 values), statusLabel (all 5 values), formatDate (short, long, UTC boundary).</td></tr>
|
||
<tr><td>AC-16</td><td>Visual proof: /proofshot against document detail page at all 5 viewport widths, both light and dark themes (10 screenshots).</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<hr>
|
||
|
||
<div style="font-size:10px;color:#AAA;text-align:center;padding:16px 0">
|
||
DocumentTopBar Final Spec · Familienarchiv · 2026-03-31 · Leonie Voss<br>
|
||
Supersedes <code>document-topbar-b1-responsive.html</code> · All resolutions from issue #161 review incorporated
|
||
</div>
|
||
|
||
</div><!-- /page -->
|
||
</body>
|
||
</html>
|