Files
familienarchiv/docs/specs/sort-integration-spec.html
Marcel e6f12e6d90
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
docs(design): add sort integration specs for issue #180
Exploration spec (sort-integration-spec.html) covers 4 placement variants
with comparison matrix. Final spec (sort-inline-final-spec.html) locks in
Variant A (inline sort in search bar row) with full desktop/mobile states,
dropdown interaction anatomy, loading/empty states, and backend wiring checklist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 12:09:00 +02:00

1292 lines
65 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sort Integration — 4 Exploration Variants · Familienarchiv #180</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:1400px;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:640px;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-exploration{background:#FCD34D;color:#713F12}
.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}
/* ── 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}
.sec-variant{background:#002850;color:#A6DAD8;font-size:9px;font-weight:900;padding:2px 9px;border-radius:10px;letter-spacing:.3px}
/* ── Variant cards ─── */
.variant-meta{background:#fff;border:1.5px solid #E0DDD6;border-radius:8px;padding:16px 20px;margin-bottom:20px;display:flex;gap:32px}
.vm-col h3{font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;margin-bottom:6px}
.vm-col ul{list-style:none;display:flex;flex-direction:column;gap:4px}
.vm-col ul li{font-size:10.5px;color:#555;padding-left:14px;position:relative;line-height:1.5}
.vm-col.pros li::before{content:'✓';position:absolute;left:0;color:#16A34A;font-weight:900}
.vm-col.cons li::before{content:'✗';position:absolute;left:0;color:#DC2626;font-weight:900}
.vm-col.when li::before{content:'→';position:absolute;left:0;color:#002850}
/* ── Mockup chrome ─── */
.wf{background:#fff;border:2px solid #B8B4AE;border-radius:10px;overflow:hidden;box-shadow:0 4px 18px rgba(0,0,0,.08);max-width:820px}
.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:40px;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}
/* ── Search card ─── */
.SCARD{background:#fff;border:1.5px solid #E0DDD6;border-radius:3px;padding:11px 14px;box-shadow:0 1px 3px rgba(0,0,0,.06)}
.SROW1{display:flex;align-items:center;gap:6px}
.SINPUT{flex:1;height:24px;border:1.5px solid #D1D5DB;border-radius:2px;display:flex;align-items:center;padding:0 8px;font-size:8.5px;color:#9CA3AF;font-style:italic;gap:5px;position:relative}
.SINPUT.active{color:#1A1A1A;font-style:normal}
.SINPUT-ICON{width:8px;height:8px;opacity:.35;flex-shrink:0}
.SINPUT-SPINNER{width:8px;height:8px;border:1.5px solid #E0DDD6;border-top-color:#002850;border-radius:50%;flex-shrink:0;animation:spin .6s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
.SBTN{height:24px;border:1.5px solid #D1D5DB;border-radius:2px;display:flex;align-items:center;padding:0 9px;font-size:8px;font-weight:700;color:#555;gap:4px;cursor:pointer;white-space:nowrap;background:#F7F5F2}
.SBTN.sort-active{border-color:#002850;color:#002850;background:#fff}
.SBTN.filter{background:#F7F5F2}
.SBTN.reset{border-color:transparent;background:transparent;color:#9CA3AF;padding:0 6px}
.SBTN.reset:hover{color:#DC2626}
.SCHEV{font-size:7px;opacity:.5}
.SCHEV.up{transform:rotate(180deg);display:inline-block}
/* ── Sort dropdown ─── */
.SDROP{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,.12);margin-top:-2px;position:relative;z-index:10}
.SDROP-ROW{display:flex;align-items:center;padding:5px 9px;border-bottom:1px solid #F0EDE8;cursor:pointer;gap:6px}
.SDROP-ROW:last-child{border-bottom:none}
.SDROP-ROW.active{background:#F0F5FF}
.SDROP-LABEL{flex:1;font-size:8px;color:#1A1A1A;font-weight:700}
.SDROP-ROW.active .SDROP-LABEL{color:#002850}
.SDROP-DIR{display:flex;gap:3px}
.SDROP-D{font-size:7.5px;color:#C8C4BE;font-weight:700;padding:1px 4px;border-radius:2px}
.SDROP-D.active{background:#002850;color:#fff}
.SDROP-CHECKMARK{width:8px;height:8px;color:#002850;font-size:8px;font-weight:900}
/* ── Sort pills strip ─── */
.SPILLS{background:#fff;border:1.5px solid #E0DDD6;border-radius:3px;padding:7px 12px;display:flex;align-items:center;gap:6px;overflow-x:auto;box-shadow:0 1px 2px rgba(0,0,0,.04)}
.SPILLS-LABEL{font-size:7px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#AAA;white-space:nowrap;flex-shrink:0;margin-right:2px}
.SPILL{height:17px;border:1.5px solid #D1D5DB;border-radius:20px;display:flex;align-items:center;padding:0 8px;font-size:7.5px;font-weight:700;color:#555;white-space:nowrap;cursor:pointer;gap:3px;flex-shrink:0}
.SPILL.active{background:#002850;color:#fff;border-color:#002850}
.SPILL-DIR{font-size:7px;opacity:.7}
.SPILLS-SEP{width:1px;height:14px;background:#E0DDD6;flex-shrink:0;margin-left:2px}
.SPILLS-DIRBTN{height:17px;border:1.5px solid #D1D5DB;border-radius:2px;display:flex;align-items:center;padding:0 6px;font-size:7.5px;font-weight:700;color:#555;cursor:pointer;gap:2px;flex-shrink:0;white-space:nowrap}
.SPILLS-DIRBTN.active{border-color:#002850;color:#002850}
/* ── Result header ─── */
.RESHEAD{display:flex;align-items:center;gap:8px;padding:0 2px;margin-bottom:4px}
.RESHEAD-COUNT{font-size:8px;font-weight:700;color:#555;flex:1}
.RESHEAD-COUNT strong{color:#002850}
.RESHEAD-SORT{height:22px;border:1.5px solid #D1D5DB;border-radius:2px;display:flex;align-items:center;padding:0 8px;font-size:7.5px;font-weight:700;color:#555;gap:5px;cursor:pointer;white-space:nowrap;background:#fff}
.RESHEAD-SORT.active{border-color:#002850;color:#002850}
.RESHEAD-SORTLABEL{font-size:7px;font-weight:400;color:#AAA;margin-right:2px}
/* ── Advanced filter with sort ─── */
.ADVSORT{border-bottom:1px solid #E0DDD6;padding-bottom:10px;margin-bottom:10px}
.ADVSORT-LABEL{font-size:7px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#888;margin-bottom:7px}
.ADVSORT-PILLS{display:flex;flex-wrap:wrap;gap:5px;margin-bottom:6px}
.ADVSORT-PILL{height:17px;border:1.5px solid #D1D5DB;border-radius:20px;display:flex;align-items:center;padding:0 8px;font-size:7.5px;font-weight:700;color:#555;cursor:pointer;white-space:nowrap}
.ADVSORT-PILL.active{background:#002850;color:#fff;border-color:#002850}
.ADVSORT-DIRS{display:flex;gap:5px}
.ADVSORT-DIR{height:17px;border:1.5px solid #D1D5DB;border-radius:2px;display:flex;align-items:center;padding:0 8px;font-size:7.5px;font-weight:700;color:#555;cursor:pointer;gap:3px;white-space:nowrap}
.ADVSORT-DIR.active{border-color:#002850;background:#002850;color:#fff}
/* ── Document list rows ─── */
.DOCLIST{display:flex;flex-direction:column;gap:0}
.DOCROW{background:#fff;border:1.5px solid #E0DDD6;border-radius:3px;padding:8px 12px;margin-bottom:5px;display:flex;align-items:flex-start;gap:8px;cursor:pointer}
.DOCROW:hover{border-color:#A6DAD8}
.DOCROW-THUMB{width:22px;height:28px;background:#F0EDE8;border:1px solid #E0DDD6;border-radius:2px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:7px;color:#AAA}
.DOCROW-BODY{flex:1;min-width:0}
.DOCROW-TITLE{font-size:8.5px;font-weight:700;color:#0D2240;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:2px}
.DOCROW-META{font-size:7.5px;color:#888;display:flex;align-items:center;gap:5px}
.DOCROW-META-SEP{color:#D1CCC8}
.DOCROW-TAG{display:inline-block;background:#F0EDE8;border-radius:20px;padding:1px 5px;font-size:7px;font-weight:700;color:#555}
.PH{height:6px;background:#E8E4DF;border-radius:2px}
.w80{width:80%}.w70{width:70%}.w60{width:60%}.w50{width:50%}.w40{width:40%}.w30{width:30%}
/* ── Grid ─── */
.sg{display:grid;gap:20px;align-items:start}
.sg-2{grid-template-columns:1fr 1fr}
.sg-3{grid-template-columns:1fr 1fr 1fr}
.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}
.sc{font-size:10px;color:#888;margin-top:8px;font-style:italic;line-height:1.5}
/* ── 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:200px}
.WF-M-STATUS{height:16px;background:#0D2240;display:flex;align-items:center;justify-content:space-between;padding:0 10px}
.WF-M-TIME{font-size:6px;color:#fff;font-weight:700}
.WF-M-ICONS{display:flex;gap:3px}
.WF-M-ICON{width:5px;height:5px;background:rgba(255,255,255,.5);border-radius:1px}
.N-M{height:34px;background:#0D2240;display:flex;align-items:center;padding:0 10px;justify-content:space-between}
.MAIN-SM{padding:8px 10px;display:flex;flex-direction:column;gap:7px;background:#ECEAE4}
.SCARD-SM{background:#fff;border:1.5px solid #E0DDD6;border-radius:3px;padding:8px 10px}
/* ── Annotation callouts ─── */
.ann-block{background:#FFF7ED;border:1px solid #FDBA74;border-radius:5px;padding:10px 14px;font-size:10.5px;color:#7C2D12;line-height:1.6;margin-top:12px}
.ann-block strong{font-weight:800}
.ann-block ul{padding-left:18px;display:flex;flex-direction:column;gap:3px;margin-top:6px}
/* ── Comparison matrix ─── */
.matrix{width:100%;border-collapse:collapse;border-radius:8px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,.06)}
.matrix th{background:#0D2240;color:#fff;font-size:10px;font-weight:800;padding:10px 14px;text-align:left;letter-spacing:.3px}
.matrix th:first-child{width:160px}
.matrix td{padding:9px 14px;font-size:11px;border-bottom:1px solid #E8E4DF;vertical-align:top;line-height:1.5}
.matrix tr:nth-child(even) td{background:#F7F5F2}
.matrix tr:last-child td{border-bottom:none}
.matrix td:first-child{font-weight:800;color:#0D2240;font-size:10.5px}
.matrix .good{color:#16A34A;font-weight:700}
.matrix .bad{color:#DC2626;font-weight:700}
.matrix .ok{color:#D97706;font-weight:700}
.matrix .best{background:#E0F2FE;font-weight:700;color:#0369A1}
/* ── 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}
</style>
</head>
<body>
<div class="doc">
<!-- ══════════════════════════════════════
MASTHEAD
══════════════════════════════════════ -->
<div class="mast">
<div class="mast-top">
<div>
<h1>Sort Integration — 4 Exploration Variants</h1>
<p>Four distinct approaches for adding sort controls to the document search. Issue #180 (Problem 1). Each variant has a different placement philosophy — choose based on the user model you want to reinforce. Not a final spec; pick one and lock it down.</p>
</div>
<span class="mast-badge mb-exploration">Exploration · Pick One</span>
</div>
<div class="decisions">
<div class="dec">
<div class="dec-label">Issue</div>
<div class="dec-value">#180 — Sort &amp; Search UX</div>
</div>
<div class="dec">
<div class="dec-label">Sort options (6)</div>
<div class="dec-value">Datum · Titel · Absender · Empfänger · Tag · Hochgeladen</div>
</div>
<div class="dec">
<div class="dec-label">Direction</div>
<div class="dec-value">Aufsteigend ↑ / Absteigend ↓ per option</div>
</div>
<div class="dec">
<div class="dec-label">New URL params</div>
<div class="dec-value"><code style="font-size:8.5px;color:#A6DAD8">?sort=date&amp;dir=desc</code></div>
</div>
</div>
</div>
<!-- ── spec disclaimer ── -->
<div class="spec-disclaimer">
<strong>📐 Mockup scale notice —</strong> all font-size, height, and padding values
in the mockup CSS 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.
</div>
<!-- ══════════════════════════════════════
VARIANT A — INLINE SORT IN SEARCH ROW
══════════════════════════════════════ -->
<div class="sec">
<div class="sec-h">
<span class="sec-num">A</span>
<span class="sec-variant">Inline sort in search bar row</span>
Sort dropdown sits directly in row 1 — always visible, no extra space
</div>
<div class="variant-meta">
<div class="vm-col pros">
<h3>Pros</h3>
<ul>
<li>Always visible — zero discovery friction</li>
<li>Sort and search feel like one cohesive control surface</li>
<li>Familiar pattern (GitHub Issues, Notion tables)</li>
<li>Active state is immediately legible without opening anything</li>
</ul>
</div>
<div class="vm-col cons">
<h3>Cons</h3>
<ul>
<li>Row 1 gets crowded at 320px — needs a wrapping strategy</li>
<li>Sort button competes visually with Filter button</li>
<li>Dropdown overlaps results on short viewports</li>
</ul>
</div>
<div class="vm-col when">
<h3>Best when</h3>
<ul>
<li>Sort is a primary, frequently changed action</li>
<li>Users already understand the search bar as a control hub</li>
<li>You want to minimize total UI surface area</li>
</ul>
</div>
</div>
<div class="sg sg-2" style="gap:24px;align-items:start">
<!-- Desktop mockup with dropdown open -->
<div class="sb">
<div class="sl">Desktop — dropdown open</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>localhost:3000/?q=brief&amp;sort=date&amp;dir=desc</span></div>
</div>
<div class="N">
<span class="logo">FAMILIENARCHIV</span>
<span class="nl on">Dokumente</span>
<span class="nl">Personen</span>
<div class="nr"><div class="nico"></div></div>
</div>
<div class="MAIN">
<div class="SCARD">
<div class="SROW1">
<!-- Search input with spinner (search in progress) -->
<div class="SINPUT active">
<div class="SINPUT-SPINNER"></div>
<span style="font-size:8px;color:#1a1a1a">brief</span>
</div>
<!-- Sort button — ACTIVE, dropdown open -->
<div style="position:relative">
<div class="SBTN sort-active" style="gap:5px">
<span style="font-size:7px;color:#888;font-weight:400">Sortieren:</span>
Datum ↓
<span class="SCHEV up"></span>
</div>
<!-- Dropdown -->
<div class="SDROP" style="position:absolute;top:24px;left:0;width:160px">
<div class="SDROP-ROW active">
<span class="SDROP-LABEL">Datum</span>
<div class="SDROP-DIR">
<span class="SDROP-D"></span>
<span class="SDROP-D active"></span>
</div>
</div>
<div class="SDROP-ROW">
<span class="SDROP-LABEL">Titel</span>
<div class="SDROP-DIR">
<span class="SDROP-D"></span>
<span class="SDROP-D"></span>
</div>
</div>
<div class="SDROP-ROW">
<span class="SDROP-LABEL">Absender</span>
<div class="SDROP-DIR">
<span class="SDROP-D"></span>
<span class="SDROP-D"></span>
</div>
</div>
<div class="SDROP-ROW">
<span class="SDROP-LABEL">Empfänger</span>
<div class="SDROP-DIR">
<span class="SDROP-D"></span>
<span class="SDROP-D"></span>
</div>
</div>
<div class="SDROP-ROW">
<span class="SDROP-LABEL">Tag</span>
<div class="SDROP-DIR">
<span class="SDROP-D"></span>
<span class="SDROP-D"></span>
</div>
</div>
<div class="SDROP-ROW">
<span class="SDROP-LABEL">Hochgeladen</span>
<div class="SDROP-DIR">
<span class="SDROP-D"></span>
<span class="SDROP-D"></span>
</div>
</div>
</div>
</div>
<!-- Filter button -->
<div class="SBTN filter">
<span></span> Filter
<span class="SCHEV"></span>
</div>
<!-- Reset -->
<div class="SBTN reset"></div>
</div>
</div>
<!-- Result count -->
<div class="RESHEAD" style="margin-top:2px">
<div class="RESHEAD-COUNT"><strong>12 Dokumente</strong> gefunden</div>
</div>
<!-- Doc list -->
<div class="DOCLIST">
<div class="DOCROW">
<div class="DOCROW-THUMB">PDF</div>
<div class="DOCROW-BODY">
<div class="DOCROW-TITLE">Brief an Tante Klara, Weihnachten 1954</div>
<div class="DOCROW-META">
<span>15. Dezember 1954</span>
<span class="DOCROW-META-SEP">·</span>
<span>Ernst Raddatz → Klara Meier</span>
<span class="DOCROW-META-SEP">·</span>
<span class="DOCROW-TAG">Briefe</span>
</div>
</div>
</div>
<div class="DOCROW">
<div class="DOCROW-THUMB">PDF</div>
<div class="DOCROW-BODY">
<div class="DOCROW-TITLE">Briefwechsel Sommer 1961</div>
<div class="DOCROW-META">
<span>3. Juli 1961</span>
<span class="DOCROW-META-SEP">·</span>
<span>Hildegard Raddatz → Stadtamt</span>
<span class="DOCROW-META-SEP">·</span>
<span class="DOCROW-TAG">Verwaltung</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="sc">Sort button sits between search input and Filter button. Dropdown shows all 6 options with per-option ↑/↓ direction toggle. Active option highlighted in navy.</div>
</div>
<!-- Mobile mockup -->
<div class="sb">
<div class="sl">Mobile 320px — collapsed (no open dropdown)</div>
<div class="WF-M">
<div class="WF-M-STATUS">
<span class="WF-M-TIME">9: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:7px">FAMILIENARCHIV</span>
<div class="nico"></div>
</div>
<div class="MAIN-SM">
<div class="SCARD-SM">
<!-- Mobile: row 1 has search + reset only -->
<div style="display:flex;gap:4px;margin-bottom:5px">
<div class="SINPUT active" style="height:22px">
<div class="SINPUT-ICON"></div>
<span style="font-size:7.5px;color:#1a1a1a">brief</span>
</div>
<div class="SBTN reset" style="height:22px"></div>
</div>
<!-- Mobile: row 2 has sort + filter as full-width buttons -->
<div style="display:flex;gap:4px">
<div class="SBTN sort-active" style="height:20px;flex:1;justify-content:center;font-size:7.5px">
Datum ↓ <span class="SCHEV"></span>
</div>
<div class="SBTN filter" style="height:20px;flex:1;justify-content:center;font-size:7.5px">
⊞ Filter
</div>
</div>
</div>
<div style="font-size:7px;font-weight:700;color:#555">12 Dokumente gefunden</div>
<div class="DOCROW" style="padding:6px 9px">
<div class="DOCROW-THUMB" style="width:18px;height:22px">PDF</div>
<div class="DOCROW-BODY">
<div class="PH w80" style="margin-bottom:4px"></div>
<div class="PH w50"></div>
</div>
</div>
<div class="DOCROW" style="padding:6px 9px">
<div class="DOCROW-THUMB" style="width:18px;height:22px">PDF</div>
<div class="DOCROW-BODY">
<div class="PH w70" style="margin-bottom:4px"></div>
<div class="PH w40"></div>
</div>
</div>
</div>
</div>
<div class="sc">On mobile, sort wraps to a second row inside the search card. Both sort and filter become equal-width buttons — same touch target, consistent layout.</div>
</div>
</div>
<div class="impl-ref">
<div class="impl-ref-hdr">Implementation Reference — Variant A: Inline Sort
<span>Real values · mockup above is ~55% scale · do not copy mockup CSS</span>
</div>
<table>
<thead><tr><th>Element</th><th>Tailwind classes</th><th>Real size</th><th>Notes</th></tr></thead>
<tbody>
<tr>
<td>Sort trigger button</td>
<td><code>h-10 flex items-center gap-2 border border-line px-3 text-sm font-bold text-ink-2 bg-muted hover:bg-surface transition whitespace-nowrap rounded-sm</code></td>
<td><span class="ir-px">h 40px, px 12px</span></td>
<td>Active state: <code>border-brand-navy text-brand-navy bg-surface</code></td>
</tr>
<tr>
<td>Sort prefix label</td>
<td><code>text-xs font-normal text-ink-3 mr-1</code></td>
<td><span class="ir-px">12px / 400</span></td>
<td>"Sortieren:" prefix — hide on mobile with <code>hidden sm:inline</code></td>
</tr>
<tr>
<td>Sort dropdown container</td>
<td><code>absolute top-full left-0 z-50 min-w-[180px] bg-surface border border-brand-navy border-t-0 shadow-lg rounded-b-sm</code></td>
<td><span class="ir-px">min-w 180px</span></td>
<td>Opens below button; close on outside click via <code>onpointerdown</code> listener</td>
</tr>
<tr>
<td>Dropdown option row</td>
<td><code>flex items-center px-3 py-2.5 gap-3 border-b border-line cursor-pointer hover:bg-muted last:border-b-0</code></td>
<td><span class="ir-px">h ~40px, py 10px</span></td>
<td>Touch target ≥ 44px — most commonly undersized element</td>
</tr>
<tr>
<td>Dropdown option label</td>
<td><code>flex-1 text-sm font-semibold text-ink</code></td>
<td><span class="ir-px">14px / 600</span></td>
<td>Active row: <code>text-brand-navy</code> + row background <code>bg-blue-50</code></td>
</tr>
<tr>
<td>Direction toggle (↑/↓)</td>
<td><code>flex gap-1</code> — each button: <code>text-xs font-bold px-1.5 py-0.5 rounded-sm border border-transparent text-ink-3</code></td>
<td><span class="ir-px">24px touch area min</span></td>
<td>Active direction: <code>bg-brand-navy text-white border-brand-navy</code></td>
</tr>
<tr>
<td>Mobile: sort + filter row</td>
<td><code>flex gap-2 mt-2</code> — each button adds <code>flex-1 justify-center</code></td>
<td><span class="ir-px">h 40px, flex-1</span></td>
<td>Wraps to second line inside <code>.SCARD</code> below sm. Both buttons equal width.</td>
</tr>
<tr>
<td>New URL params</td>
<td><code>sort</code> + <code>dir</code></td>
<td></td>
<td>Values: <code>sort=date|title|sender|receiver|tag|uploaded</code>, <code>dir=asc|desc</code>. Add to <code>triggerSearch()</code> in <code>+page.svelte</code> and to <code>/api/documents/search</code> query params.</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- ══════════════════════════════════════
VARIANT B — SORT PILL STRIP (BELOW SEARCH CARD)
══════════════════════════════════════ -->
<div class="sec">
<div class="sec-h">
<span class="sec-num">B</span>
<span class="sec-variant">Sort pill strip below search card</span>
A horizontally scrollable row of pills between search and results
</div>
<div class="variant-meta">
<div class="vm-col pros">
<h3>Pros</h3>
<ul>
<li>All 6 options visible at a glance — no dropdown needed</li>
<li>Active sort is unmistakable (navy pill, always in view)</li>
<li>Natural horizontal scroll on mobile — familiar to users</li>
<li>Search bar stays clean and single-purpose</li>
</ul>
</div>
<div class="vm-col cons">
<h3>Cons</h3>
<ul>
<li>Adds a full row of vertical space even when sort isn't used</li>
<li>6 options may overflow without scroll cue on narrow screens</li>
<li>Seniors may not discover horizontal scroll on mobile</li>
</ul>
</div>
<div class="vm-col when">
<h3>Best when</h3>
<ul>
<li>Sort is a secondary action but users switch it often</li>
<li>You want sort visible without modal/dropdown overhead</li>
<li>The team prefers a "tab strip" mental model over a select</li>
</ul>
</div>
</div>
<div class="sg sg-2" style="gap:24px;align-items:start">
<!-- Desktop -->
<div class="sb">
<div class="sl">Desktop — "Absender" active, ascending</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>localhost:3000/?q=brief&amp;sort=sender&amp;dir=asc</span></div>
</div>
<div class="N">
<span class="logo">FAMILIENARCHIV</span>
<span class="nl on">Dokumente</span>
<span class="nl">Personen</span>
<div class="nr"><div class="nico"></div></div>
</div>
<div class="MAIN">
<!-- Search card (clean, no sort inside) -->
<div class="SCARD">
<div class="SROW1">
<div class="SINPUT">
<div class="SINPUT-ICON"></div>
<span style="font-size:8px;color:#1a1a1a">brief</span>
</div>
<div class="SBTN filter">⊞ Filter <span class="SCHEV"></span></div>
<div class="SBTN reset"></div>
</div>
</div>
<!-- Sort pill strip (separate card) -->
<div class="SPILLS">
<span class="SPILLS-LABEL">Sortieren:</span>
<div class="SPILL">Datum <span class="SPILL-DIR"></span></div>
<div class="SPILL">Titel</div>
<div class="SPILL active">Absender ↑</div>
<div class="SPILL">Empfänger</div>
<div class="SPILL">Tag</div>
<div class="SPILL">Hochgeladen</div>
<!-- Direction toggle far right -->
<div class="SPILLS-SEP"></div>
<div class="SPILLS-DIRBTN active">↑ AZ</div>
<div class="SPILLS-DIRBTN">↓ ZA</div>
</div>
<!-- Result count -->
<div class="RESHEAD">
<div class="RESHEAD-COUNT"><strong>12 Dokumente</strong>, sortiert nach Absender ↑</div>
</div>
<!-- Doc list sorted by sender -->
<div class="DOCLIST">
<div class="DOCROW">
<div class="DOCROW-THUMB">PDF</div>
<div class="DOCROW-BODY">
<div class="DOCROW-TITLE">Brief an Tante Klara, Weihnachten 1954</div>
<div class="DOCROW-META">
<span>Ernst Raddatz</span>
<span class="DOCROW-META-SEP">·</span>
<span>15. Dez 1954</span>
<span class="DOCROW-META-SEP">·</span>
<span class="DOCROW-TAG">Briefe</span>
</div>
</div>
</div>
<div class="DOCROW">
<div class="DOCROW-THUMB">PDF</div>
<div class="DOCROW-BODY">
<div class="DOCROW-TITLE">Urlaubspostkarte 1962</div>
<div class="DOCROW-META">
<span>Hildegard Raddatz</span>
<span class="DOCROW-META-SEP">·</span>
<span>8. Aug 1962</span>
<span class="DOCROW-META-SEP">·</span>
<span class="DOCROW-TAG">Karten</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="sc">Pill strip lives between search card and results as its own card. Active pill is navy-filled. Direction buttons (↑/↓) are separate toggle at the right end. Result count confirms active sort.</div>
</div>
<!-- Mobile -->
<div class="sb">
<div class="sl">Mobile 320px — horizontal scroll</div>
<div class="WF-M">
<div class="WF-M-STATUS">
<span class="WF-M-TIME">9: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:7px">FAMILIENARCHIV</span>
<div class="nico"></div>
</div>
<div class="MAIN-SM">
<div class="SCARD-SM">
<div style="display:flex;gap:4px">
<div class="SINPUT" style="height:22px">
<span style="font-size:7.5px;color:#9CA3AF;font-style:italic">brief</span>
</div>
<div class="SBTN filter" style="height:22px;font-size:7px"></div>
<div class="SBTN reset" style="height:22px"></div>
</div>
</div>
<!-- Pill strip — shows partial pills to signal scroll -->
<div class="SPILLS" style="padding:5px 8px;gap:5px">
<div class="SPILL" style="font-size:7px;height:15px;padding:0 7px">Datum ↓</div>
<div class="SPILL active" style="font-size:7px;height:15px;padding:0 7px">Absender ↑</div>
<div class="SPILL" style="font-size:7px;height:15px;padding:0 7px">Empfänger</div>
<div class="SPILL" style="font-size:7px;height:15px;padding:0 7px;opacity:.4">Tag…</div>
</div>
<div style="font-size:7px;font-weight:700;color:#555">12 Dokumente, Absender ↑</div>
<div class="DOCROW" style="padding:6px 9px">
<div class="DOCROW-THUMB" style="width:18px;height:22px">PDF</div>
<div class="DOCROW-BODY"><div class="PH w70" style="margin-bottom:4px"></div><div class="PH w50"></div></div>
</div>
<div class="DOCROW" style="padding:6px 9px">
<div class="DOCROW-THUMB" style="width:18px;height:22px">PDF</div>
<div class="DOCROW-BODY"><div class="PH w80" style="margin-bottom:4px"></div><div class="PH w40"></div></div>
</div>
</div>
</div>
<div class="sc">On mobile the pill row clips at screen edge — the half-visible last pill cues horizontal scroll. Direction toggle moves inside the active pill label (↑/↓ suffix). Min touch target: 44px height enforced in implementation.</div>
</div>
</div>
<div class="ann-block" style="margin-top:16px">
<strong>Senior accessibility note:</strong> Horizontal scroll is invisible on desktop and may be missed by seniors on tablet. Consider adding a faint right-fade gradient mask (<code>-webkit-mask-image: linear-gradient(to right, #000 80%, transparent)</code>) to the pill container to signal overflow.
</div>
<div class="impl-ref">
<div class="impl-ref-hdr">Implementation Reference — Variant B: Sort Pill Strip
<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>Pill strip container</td>
<td><code>flex items-center gap-2 overflow-x-auto bg-surface border border-line rounded-sm px-4 py-3 shadow-sm scroll-smooth</code></td>
<td><span class="ir-px">h ~48px, py 12px</span></td>
<td>Add <code>scrollbar-hide</code> (Tailwind plugin) or <code>::-webkit-scrollbar { display: none }</code>. Right-fade mask on mobile.</td>
</tr>
<tr>
<td>"Sortieren:" label</td>
<td><code>text-xs font-bold uppercase tracking-widest text-ink-3 shrink-0 mr-1</code></td>
<td><span class="ir-px">12px / 700</span></td>
<td>Hide on mobile: <code>hidden sm:block</code></td>
</tr>
<tr>
<td>Inactive pill</td>
<td><code>h-8 shrink-0 flex items-center gap-1 px-3 rounded-full border border-line text-sm font-semibold text-ink-2 cursor-pointer hover:border-brand-navy hover:text-brand-navy transition whitespace-nowrap</code></td>
<td><span class="ir-px">h 32px, px 12px</span></td>
<td>Touch target: wrap in <code>min-h-[44px] flex items-center</code> on mobile. Most commonly undersized element.</td>
</tr>
<tr>
<td>Active pill</td>
<td>Inactive classes + <code>bg-brand-navy text-white border-brand-navy</code></td>
<td><span class="ir-px">h 32px</span></td>
<td>Shows direction suffix: "Absender ↑". Screen reader: <code>aria-pressed="true"</code></td>
</tr>
<tr>
<td>Direction toggles (↑/↓)</td>
<td><code>shrink-0 h-8 flex items-center gap-1 px-3 border border-line text-sm font-bold text-ink-2 rounded-sm cursor-pointer hover:border-brand-navy transition</code></td>
<td><span class="ir-px">h 32px, px 12px</span></td>
<td>Active: <code>border-brand-navy text-brand-navy</code>. <code>aria-label="Aufsteigend"</code> / <code>"Absteigend"</code></td>
</tr>
<tr>
<td>Separator</td>
<td><code>w-px h-5 bg-line shrink-0 mx-1</code></td>
<td><span class="ir-px">1px × 20px</span></td>
<td>Divides pills from direction buttons. <code>role="separator" aria-hidden="true"</code></td>
</tr>
<tr>
<td>Result count confirmation</td>
<td><code>text-sm font-medium text-ink-2 mb-2</code></td>
<td><span class="ir-px">14px</span></td>
<td>Text: "12 Dokumente, sortiert nach Absender ↑". Live region: <code>aria-live="polite"</code></td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- ══════════════════════════════════════
VARIANT C — RESULT-HEADER SORT (ABOVE DOCUMENT LIST)
══════════════════════════════════════ -->
<div class="sec">
<div class="sec-h">
<span class="sec-num">C</span>
<span class="sec-variant">Sort in the result-list header</span>
Sort lives above the document list, co-located with result count — zero search bar pollution
</div>
<div class="variant-meta">
<div class="vm-col pros">
<h3>Pros</h3>
<ul>
<li>Search bar stays completely clean — single responsibility</li>
<li>Sort sits next to what it affects — contextually obvious</li>
<li>Result count + sort in one bar = efficient use of space</li>
<li>Pattern familiar from e-commerce (Amazon, Zalando)</li>
</ul>
</div>
<div class="vm-col cons">
<h3>Cons</h3>
<ul>
<li>Sort is below the fold on mobile — users may not scroll to find it</li>
<li>Only visible in search mode (disappears on dashboard)</li>
<li>Seniors may not understand why sort isn't near the search input</li>
</ul>
</div>
<div class="vm-col when">
<h3>Best when</h3>
<ul>
<li>Sort is considered a result-manipulation tool, not a search param</li>
<li>The result list is long enough to justify a persistent header</li>
<li>You want a clean aesthetic in the search bar at all costs</li>
</ul>
</div>
</div>
<div class="sg sg-2" style="gap:24px;align-items:start">
<!-- Desktop -->
<div class="sb">
<div class="sl">Desktop — sort dropdown open</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>localhost:3000/?q=brief&amp;sort=date&amp;dir=desc</span></div>
</div>
<div class="N">
<span class="logo">FAMILIENARCHIV</span>
<span class="nl on">Dokumente</span>
<span class="nl">Personen</span>
<div class="nr"><div class="nico"></div></div>
</div>
<div class="MAIN">
<!-- Clean search card — no sort here -->
<div class="SCARD">
<div class="SROW1">
<div class="SINPUT active">
<div class="SINPUT-SPINNER"></div>
<span style="font-size:8px;color:#1a1a1a">brief</span>
</div>
<div class="SBTN filter">⊞ Filter <span class="SCHEV"></span></div>
<div class="SBTN reset"></div>
</div>
</div>
<!-- Result header with sort -->
<div class="RESHEAD" style="padding:8px 12px;background:#fff;border:1.5px solid #E0DDD6;border-radius:3px;margin-top:0">
<div class="RESHEAD-COUNT"><strong>12 Dokumente</strong> gefunden</div>
<div style="position:relative">
<!-- Sort trigger -->
<div class="RESHEAD-SORT active">
<span class="RESHEAD-SORTLABEL">Sortieren:</span>
Datum ↓
<span class="SCHEV up"></span>
</div>
<!-- Dropdown -->
<div class="SDROP" style="position:absolute;top:22px;right:0;width:160px">
<div class="SDROP-ROW active">
<span class="SDROP-LABEL">Datum</span>
<div class="SDROP-DIR">
<span class="SDROP-D"></span>
<span class="SDROP-D active"></span>
</div>
</div>
<div class="SDROP-ROW">
<span class="SDROP-LABEL">Titel</span>
<div class="SDROP-DIR"><span class="SDROP-D"></span><span class="SDROP-D"></span></div>
</div>
<div class="SDROP-ROW">
<span class="SDROP-LABEL">Absender</span>
<div class="SDROP-DIR"><span class="SDROP-D"></span><span class="SDROP-D"></span></div>
</div>
<div class="SDROP-ROW">
<span class="SDROP-LABEL">Empfänger</span>
<div class="SDROP-DIR"><span class="SDROP-D"></span><span class="SDROP-D"></span></div>
</div>
<div class="SDROP-ROW">
<span class="SDROP-LABEL">Tag</span>
<div class="SDROP-DIR"><span class="SDROP-D"></span><span class="SDROP-D"></span></div>
</div>
<div class="SDROP-ROW">
<span class="SDROP-LABEL">Hochgeladen</span>
<div class="SDROP-DIR"><span class="SDROP-D"></span><span class="SDROP-D"></span></div>
</div>
</div>
</div>
</div>
<!-- Doc list -->
<div class="DOCLIST" style="margin-top:-5px">
<div class="DOCROW">
<div class="DOCROW-THUMB">PDF</div>
<div class="DOCROW-BODY">
<div class="DOCROW-TITLE">Brief an Tante Klara, Weihnachten 1954</div>
<div class="DOCROW-META"><span>15. Dez 1954</span><span class="DOCROW-META-SEP">·</span><span>Ernst Raddatz</span><span class="DOCROW-META-SEP">·</span><span class="DOCROW-TAG">Briefe</span></div>
</div>
</div>
<div class="DOCROW">
<div class="DOCROW-THUMB">PDF</div>
<div class="DOCROW-BODY">
<div class="DOCROW-TITLE">Briefwechsel Sommer 1961</div>
<div class="DOCROW-META"><span>3. Jul 1961</span><span class="DOCROW-META-SEP">·</span><span>Hildegard Raddatz</span><span class="DOCROW-META-SEP">·</span><span class="DOCROW-TAG">Verwaltung</span></div>
</div>
</div>
</div>
</div>
</div>
<div class="sc">Result header is its own card above the document list. Left: result count. Right: sort dropdown trigger. Sort dropdown opens upward on mobile if needed. This variant only renders during search — not on dashboard.</div>
</div>
<!-- Mobile -->
<div class="sb">
<div class="sl">Mobile 320px — result header sticky</div>
<div class="WF-M">
<div class="WF-M-STATUS">
<span class="WF-M-TIME">9: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:7px">FAMILIENARCHIV</span>
<div class="nico"></div>
</div>
<div class="MAIN-SM">
<div class="SCARD-SM">
<div style="display:flex;gap:4px">
<div class="SINPUT" style="height:22px"><span style="font-size:7.5px;color:#1a1a1a">brief</span></div>
<div class="SBTN filter" style="height:22px;font-size:7px"></div>
<div class="SBTN reset" style="height:22px"></div>
</div>
</div>
<!-- Result header — sticky on mobile -->
<div style="background:#fff;border:1.5px solid #E0DDD6;border-radius:3px;padding:5px 9px;display:flex;align-items:center;gap:6px;position:sticky;top:0">
<span style="font-size:7px;font-weight:700;color:#555;flex:1"><strong style="color:#002850">12</strong> Dok.</span>
<div class="RESHEAD-SORT active" style="height:18px;font-size:7px;padding:0 7px">
<span style="font-size:6.5px;color:#AAA">Sort:</span>
Datum ↓ <span class="SCHEV up" style="font-size:6px"></span>
</div>
</div>
<div class="DOCROW" style="padding:6px 9px">
<div class="DOCROW-THUMB" style="width:18px;height:22px">PDF</div>
<div class="DOCROW-BODY"><div class="PH w80" style="margin-bottom:4px"></div><div class="PH w50"></div></div>
</div>
<div class="DOCROW" style="padding:6px 9px">
<div class="DOCROW-THUMB" style="width:18px;height:22px">PDF</div>
<div class="DOCROW-BODY"><div class="PH w60" style="margin-bottom:4px"></div><div class="PH w40"></div></div>
</div>
</div>
</div>
<div class="sc">Result header is <code>position: sticky; top: 0</code> on mobile so it stays in view while scrolling the document list. Sort dropdown opens upward (<code>bottom: 100%</code>) to avoid viewport overflow.</div>
</div>
</div>
<div class="impl-ref">
<div class="impl-ref-hdr">Implementation Reference — Variant C: Result-Header Sort
<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>Result header bar</td>
<td><code>flex items-center gap-3 px-4 py-3 bg-surface border border-line rounded-sm shadow-sm sticky top-0 z-20</code></td>
<td><span class="ir-px">h ~48px, py 12px</span></td>
<td>Sticky on mobile. Only rendered when <code>!isDashboard</code> — wrap in <code>{#if !data.isDashboard}</code> in <code>+page.svelte</code></td>
</tr>
<tr>
<td>Result count text</td>
<td><code>flex-1 text-sm font-medium text-ink-2</code></td>
<td><span class="ir-px">14px / 500</span></td>
<td><code>&lt;strong class="text-brand-navy"&gt;12&lt;/strong&gt; Dokumente gefunden</code>. <code>aria-live="polite"</code></td>
</tr>
<tr>
<td>Sort trigger (result header)</td>
<td><code>h-9 flex items-center gap-2 border border-line px-3 text-sm font-bold text-ink-2 bg-surface rounded-sm hover:border-brand-navy transition whitespace-nowrap</code></td>
<td><span class="ir-px">h 36px, px 12px</span></td>
<td>Active: <code>border-brand-navy text-brand-navy</code>. Touch target min 44px: add <code>min-h-[44px]</code> on mobile.</td>
</tr>
<tr>
<td>Sort dropdown (result header)</td>
<td><code>absolute bottom-full right-0 mb-1 z-50 min-w-[180px] bg-surface border border-brand-navy shadow-lg rounded-sm</code></td>
<td><span class="ir-px">min-w 180px</span></td>
<td>Opens upward (<code>bottom-full</code>) on mobile to stay in viewport. Same option rows as Variant A.</td>
</tr>
<tr>
<td>Dropdown option row</td>
<td><code>flex items-center px-3 py-2.5 gap-3 border-b border-line last:border-b-0 cursor-pointer hover:bg-muted</code></td>
<td><span class="ir-px">h ~40px</span></td>
<td>Touch target ≥ 44px — most commonly undersized</td>
</tr>
<tr>
<td>Direction toggle</td>
<td>Same as Variant A direction toggle</td>
<td></td>
<td>Reuse same component</td>
</tr>
<tr>
<td>Svelte placement</td>
<td></td>
<td></td>
<td>Add <code>ResultHeader.svelte</code> component. Render between <code>SearchFilterBar</code> and <code>DocumentList</code> in <code>+page.svelte</code>, only when <code>!data.isDashboard</code>.</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- ══════════════════════════════════════
VARIANT D — SORT INSIDE ADVANCED FILTER PANEL
══════════════════════════════════════ -->
<div class="sec">
<div class="sec-h">
<span class="sec-num">D</span>
<span class="sec-variant">Sort inside the advanced filter panel</span>
Sort lives at the top of the collapsible "Filter" section — hidden by default
</div>
<div class="variant-meta">
<div class="vm-col pros">
<h3>Pros</h3>
<ul>
<li>Zero additional chrome when filters are closed</li>
<li>Logical grouping: "filtering and sorting" as one concept</li>
<li>Reveals naturally when filter badge shows active sort</li>
<li>Works well if sort is rarely changed once set</li>
</ul>
</div>
<div class="vm-col cons">
<h3>Cons</h3>
<ul>
<li>Discoverability is low — users must click "Filter" to find sort</li>
<li>Seniors may not think to look inside "Filter" for sort</li>
<li>Active sort is invisible when filter panel is collapsed</li>
</ul>
</div>
<div class="vm-col when">
<h3>Best when</h3>
<ul>
<li>Sort is rarely changed (users set it once and leave it)</li>
<li>You want to keep the primary UI completely minimal</li>
<li>The Filter button shows an active-state badge when sort is non-default</li>
</ul>
</div>
</div>
<div class="sg sg-2" style="gap:24px;align-items:start">
<!-- Desktop: filter open showing sort at top -->
<div class="sb">
<div class="sl">Desktop — advanced filter open, sort section visible</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>localhost:3000/?q=brief&amp;sort=sender&amp;dir=asc</span></div>
</div>
<div class="N">
<span class="logo">FAMILIENARCHIV</span>
<span class="nl on">Dokumente</span>
<span class="nl">Personen</span>
<div class="nr"><div class="nico"></div></div>
</div>
<div class="MAIN">
<div class="SCARD">
<!-- Row 1: search bar — Filter button has a badge -->
<div class="SROW1">
<div class="SINPUT active">
<div class="SINPUT-ICON"></div>
<span style="font-size:8px;color:#1a1a1a">brief</span>
</div>
<!-- Filter button WITH active badge (sort is set) -->
<div class="SBTN sort-active" style="position:relative">
⊞ Filter
<span style="position:absolute;top:-5px;right:-5px;width:9px;height:9px;background:#002850;border-radius:50%;border:1.5px solid #fff;display:flex;align-items:center;justify-content:center;font-size:5px;color:#fff;font-weight:900">1</span>
<span class="SCHEV up"></span>
</div>
<div class="SBTN reset"></div>
</div>
<!-- Advanced filter panel — OPEN -->
<div style="margin-top:10px;border-top:1px solid #E0DDD6;padding-top:10px;display:flex;flex-direction:column;gap:10px">
<!-- Sort section at TOP of advanced panel -->
<div class="ADVSORT">
<div class="ADVSORT-LABEL">Sortieren nach</div>
<div class="ADVSORT-PILLS">
<div class="ADVSORT-PILL">Datum</div>
<div class="ADVSORT-PILL">Titel</div>
<div class="ADVSORT-PILL active">Absender</div>
<div class="ADVSORT-PILL">Empfänger</div>
<div class="ADVSORT-PILL">Tag</div>
<div class="ADVSORT-PILL">Hochgeladen</div>
</div>
<div class="ADVSORT-DIRS">
<div class="ADVSORT-DIR active">↑ Aufsteigend</div>
<div class="ADVSORT-DIR">↓ Absteigend</div>
</div>
</div>
<!-- Existing filter fields below sort -->
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;font-size:7.5px">
<div>
<div style="font-size:7px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#888;margin-bottom:4px">Absender</div>
<div style="height:20px;border:1.5px solid #D1D5DB;border-radius:2px;display:flex;align-items:center;padding:0 7px;color:#C8C4BE;font-style:italic;font-size:7.5px">Person wählen…</div>
</div>
<div>
<div style="font-size:7px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#888;margin-bottom:4px">Empfänger</div>
<div style="height:20px;border:1.5px solid #D1D5DB;border-radius:2px;display:flex;align-items:center;padding:0 7px;color:#C8C4BE;font-style:italic;font-size:7.5px">Person wählen…</div>
</div>
<div>
<div style="font-size:7px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#888;margin-bottom:4px">Von</div>
<div style="height:20px;border:1.5px solid #D1D5DB;border-radius:2px;display:flex;align-items:center;padding:0 7px;color:#C8C4BE;font-style:italic;font-size:7.5px">Datum…</div>
</div>
<div>
<div style="font-size:7px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#888;margin-bottom:4px">Bis</div>
<div style="height:20px;border:1.5px solid #D1D5DB;border-radius:2px;display:flex;align-items:center;padding:0 7px;color:#C8C4BE;font-style:italic;font-size:7.5px">Datum…</div>
</div>
</div>
</div>
</div>
<!-- Result count + doc list -->
<div class="RESHEAD"><div class="RESHEAD-COUNT"><strong>12 Dokumente</strong>, sortiert nach Absender ↑</div></div>
<div class="DOCLIST">
<div class="DOCROW">
<div class="DOCROW-THUMB">PDF</div>
<div class="DOCROW-BODY">
<div class="DOCROW-TITLE">Brief an Tante Klara, Weihnachten 1954</div>
<div class="DOCROW-META"><span>Ernst Raddatz</span><span class="DOCROW-META-SEP">·</span><span>15. Dez 1954</span><span class="DOCROW-META-SEP">·</span><span class="DOCROW-TAG">Briefe</span></div>
</div>
</div>
</div>
</div>
</div>
<div class="sc">Sort sits at the top of the advanced filter panel — above Absender, Empfänger, dates. When sort is non-default, the Filter button shows a navy badge with the count of active modifiers (sort + any active filters).</div>
</div>
<!-- Desktop: filter CLOSED, showing badge -->
<div class="sb">
<div class="sl">Desktop — filter closed, active badge visible</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>localhost:3000/?q=brief&amp;sort=sender&amp;dir=asc</span></div>
</div>
<div class="N">
<span class="logo">FAMILIENARCHIV</span>
<span class="nl on">Dokumente</span>
<span class="nl">Personen</span>
<div class="nr"><div class="nico"></div></div>
</div>
<div class="MAIN">
<div class="SCARD">
<div class="SROW1">
<div class="SINPUT active">
<div class="SINPUT-ICON"></div>
<span style="font-size:8px;color:#1a1a1a">brief</span>
</div>
<div class="SBTN sort-active" style="position:relative">
⊞ Filter (1)
<span class="SCHEV"></span>
</div>
<div class="SBTN reset"></div>
</div>
</div>
<div class="RESHEAD"><div class="RESHEAD-COUNT"><strong>12 Dokumente</strong>, sortiert nach Absender ↑</div></div>
<div class="DOCLIST">
<div class="DOCROW">
<div class="DOCROW-THUMB">PDF</div>
<div class="DOCROW-BODY">
<div class="DOCROW-TITLE">Brief an Tante Klara, Weihnachten 1954</div>
<div class="DOCROW-META"><span>Ernst Raddatz</span><span class="DOCROW-META-SEP">·</span><span>15. Dez 1954</span><span class="DOCROW-META-SEP">·</span><span class="DOCROW-TAG">Briefe</span></div>
</div>
</div>
<div class="DOCROW">
<div class="DOCROW-THUMB">PDF</div>
<div class="DOCROW-BODY">
<div class="DOCROW-TITLE">Briefwechsel Sommer 1961</div>
<div class="DOCROW-META"><span>Hildegard Raddatz</span><span class="DOCROW-META-SEP">·</span><span>3. Jul 1961</span><span class="DOCROW-META-SEP">·</span><span class="DOCROW-TAG">Verwaltung</span></div>
</div>
</div>
</div>
</div>
</div>
<div class="sc">Filter button shows "(1)" suffix when sort is non-default. The result count line confirms active sort. This is the only surface that signals sort is active when the panel is collapsed — the discoverability weakness of this variant.</div>
</div>
</div>
<div class="ann-block">
<strong>Discoverability mitigation:</strong> If choosing Variant D, the Filter button label should read "Filter (1)" (or show a dot badge) whenever sort is non-default. Additionally, the result-count line should always name the active sort: "12 Dokumente, Absender ↑". These two surfaces together are the only visual hints that a sort is active when the panel is closed.
</div>
<div class="impl-ref">
<div class="impl-ref-hdr">Implementation Reference — Variant D: Sort in Advanced Filter Panel
<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>Sort section in panel</td>
<td><code>border-b border-line pb-5 mb-5</code></td>
<td></td>
<td>First child inside the <code>transition:slide</code> panel in <code>SearchFilterBar.svelte</code></td>
</tr>
<tr>
<td>Sort section label</td>
<td><code>text-xs font-bold uppercase tracking-widest text-ink-2 mb-3 block</code></td>
<td><span class="ir-px">12px / 700</span></td>
<td>"SORTIEREN NACH"</td>
</tr>
<tr>
<td>Sort option pills row</td>
<td><code>flex flex-wrap gap-2 mb-3</code></td>
<td></td>
<td>Wraps on mobile. 6 pills total.</td>
</tr>
<tr>
<td>Inactive sort pill</td>
<td><code>h-9 flex items-center px-4 rounded-full border border-line text-sm font-semibold text-ink-2 cursor-pointer hover:border-brand-navy transition whitespace-nowrap</code></td>
<td><span class="ir-px">h 36px, px 16px</span></td>
<td>Touch target: <code>min-h-[44px]</code> on mobile. Most commonly undersized.</td>
</tr>
<tr>
<td>Active sort pill</td>
<td>Same + <code>bg-brand-navy text-white border-brand-navy</code></td>
<td></td>
<td><code>aria-pressed="true"</code></td>
</tr>
<tr>
<td>Direction row</td>
<td><code>flex gap-2</code></td>
<td></td>
<td>Two buttons: "↑ Aufsteigend" / "↓ Absteigend"</td>
</tr>
<tr>
<td>Direction button</td>
<td><code>h-9 flex items-center gap-1.5 px-4 border border-line text-sm font-bold text-ink-2 rounded-sm cursor-pointer hover:border-brand-navy transition whitespace-nowrap</code></td>
<td><span class="ir-px">h 36px</span></td>
<td>Active: <code>bg-brand-navy text-white border-brand-navy</code></td>
</tr>
<tr>
<td>Filter button badge count</td>
<td>Inline "(N)" text suffix or: <code>absolute -top-1.5 -right-1.5 w-4 h-4 rounded-full bg-brand-navy text-white text-[10px] font-bold flex items-center justify-center border-2 border-surface</code></td>
<td><span class="ir-px">16px badge</span></td>
<td>Count = number of active modifiers: active sort (if non-default) + active filter fields. Computed in <code>+page.svelte</code>.</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- ══════════════════════════════════════
COMPARISON MATRIX
══════════════════════════════════════ -->
<div class="sec">
<div class="sec-h">
<span class="sec-num"></span>
Variant comparison — pick one
</div>
<table class="matrix">
<thead>
<tr>
<th>Criterion</th>
<th>A — Inline</th>
<th>B — Pill strip</th>
<th>C — Result header</th>
<th>D — In filter panel</th>
</tr>
</thead>
<tbody>
<tr>
<td>Discoverability</td>
<td class="best">Excellent — always in view</td>
<td class="good">Good — visible strip</td>
<td class="ok">Medium — below search</td>
<td class="bad">Low — hidden by default</td>
</tr>
<tr>
<td>Senior usability</td>
<td class="good">Good — labeled button</td>
<td class="ok">Medium — scroll may confuse</td>
<td class="ok">Medium — must scroll to find</td>
<td class="bad">Low — requires two taps to find</td>
</tr>
<tr>
<td>Mobile footprint</td>
<td class="good">Good — wraps to row 2</td>
<td class="ok">Medium — extra full row</td>
<td class="best">Excellent — no extra row in search card</td>
<td class="best">Excellent — zero chrome when closed</td>
</tr>
<tr>
<td>Search bar clarity</td>
<td class="ok">Medium — adds sort button to row 1</td>
<td class="good">Good — search bar unchanged</td>
<td class="best">Excellent — search bar 100% clean</td>
<td class="best">Excellent — search bar 100% clean</td>
</tr>
<tr>
<td>Active state visibility</td>
<td class="best">Excellent — button label changes</td>
<td class="best">Excellent — active pill always visible</td>
<td class="good">Good — result header shows it</td>
<td class="bad">Low — only via badge + result count</td>
</tr>
<tr>
<td>Implementation complexity</td>
<td class="ok">Medium — dropdown with dir toggle</td>
<td class="good">Low — pills + 2 buttons, no dropdown</td>
<td class="ok">Medium — new ResultHeader component</td>
<td class="good">Low — extends existing filter panel</td>
</tr>
<tr>
<td>Keyboard / screen reader</td>
<td class="ok">Medium — dropdown needs focus trap</td>
<td class="best">Excellent — no dropdown, simple buttons</td>
<td class="ok">Medium — same dropdown focus trap</td>
<td class="best">Excellent — inside existing panel flow</td>
</tr>
<tr>
<td>Recommended for this project</td>
<td class="best" style="font-weight:900;color:#002850">★ Primary recommendation</td>
<td class="good">Strong alternative if row 1 feels crowded</td>
<td style="color:#888">Suitable only if sort is secondary</td>
<td style="color:#888">Not recommended — discoverability gap</td>
</tr>
</tbody>
</table>
<div class="ann-block" style="margin-top:20px">
<strong>Recommendation — Variant A (Inline) with Variant B (Pill strip) as fallback:</strong>
<ul>
<li>Variant A puts sort at 0-click distance, which matters most for seniors and first-time users</li>
<li>If the team finds row 1 too dense (especially at 375px), switch to Variant B — it keeps all 6 options visible without a dropdown and has the cleanest keyboard flow</li>
<li>Variant C is acceptable if the product decision is that sort is a result-manipulation tool (not a search param). Make the result header sticky so it stays accessible while scrolling</li>
<li>Variant D is not recommended: the discoverability deficit is too severe for the senior audience. The badge workaround is a patch, not a solution</li>
</ul>
</div>
</div>
</div><!-- /doc -->
</body>
</html>