docs(spec): add /documents page design spec with mobile breakpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
665
docs/specs/documents-page-spec.html
Normal file
665
docs/specs/documents-page-spec.html
Normal file
@@ -0,0 +1,665 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Dokumente-Seite — Design Spec</title>
|
||||
<style>
|
||||
:root{
|
||||
--navy:#002850;--mint:#A6DAD8;--sand:#E4E2D7;
|
||||
--surface:#FAFAF7;--bg:#E8E7E2;--border:#D8D7D0;
|
||||
--text:#1C1C18;--muted:#6B6A63;--subtle:#9B9A93;
|
||||
--orange:#C26A00;--orange-bg:#FEF4E2;
|
||||
--green:#2E6E39;--green-bg:#EAF5EA;
|
||||
--purple:#5B5EA6;--purple-bg:#EEEDFE;
|
||||
--font:system-ui,sans-serif;--mono:'Courier New',monospace;
|
||||
}
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
|
||||
body{font-family:var(--font);background:var(--bg);color:var(--text);font-size:14px;line-height:1.6;}
|
||||
.doc{max-width:1100px;margin:0 auto;padding:48px 32px 96px;}
|
||||
hr{border:none;border-top:1px solid var(--border);margin:48px 0;}
|
||||
|
||||
/* Header */
|
||||
.hdr{background:var(--navy);color:#fff;padding:32px 32px 28px;border-radius:8px 8px 0 0;}
|
||||
.hdr h1{font-family:Georgia,serif;font-size:26px;font-weight:400;letter-spacing:-.02em;margin-bottom:8px;}
|
||||
.hdr-meta{font-family:var(--mono);font-size:11px;color:rgba(255,255,255,.45);margin-top:10px;}
|
||||
.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:4px;font-size:10px;font-weight:600;letter-spacing:.05em;background:var(--mint);color:var(--navy);}
|
||||
.badge-g{background:rgba(255,255,255,.15);color:rgba(255,255,255,.9);}
|
||||
.badges{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px;}
|
||||
.decision-box{background:#fff;border:1px solid var(--border);border-top:none;border-radius:0 0 6px 6px;padding:20px 28px 24px;margin-bottom:40px;}
|
||||
.decision-box h2{font-family:Georgia,serif;font-size:16px;font-weight:400;color:var(--navy);margin-bottom:8px;}
|
||||
.prose{font-size:13px;color:var(--muted);line-height:1.65;max-width:720px;margin-bottom:10px;}
|
||||
.prose:last-child{margin-bottom:0;}
|
||||
|
||||
/* Sections */
|
||||
.sec{margin-bottom:52px;}
|
||||
.sec-label{font-size:10px;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);padding-bottom:8px;border-bottom:1px solid var(--border);margin-bottom:22px;}
|
||||
.sec-title{font-family:Georgia,serif;font-size:20px;font-weight:400;color:var(--navy);margin-bottom:4px;}
|
||||
.sec-sub{font-size:13px;color:var(--muted);margin-bottom:16px;}
|
||||
|
||||
/* Callout */
|
||||
.callout{display:flex;gap:12px;padding:14px 16px;border-radius:4px;margin-bottom:16px;font-size:12px;line-height:1.55;}
|
||||
.callout.orange{background:var(--orange-bg);border-left:3px solid var(--orange);}
|
||||
.callout.green{background:var(--green-bg);border-left:3px solid var(--green);}
|
||||
.callout.navy{background:rgba(0,40,80,.05);border-left:3px solid var(--navy);}
|
||||
.callout.purple{background:var(--purple-bg);border-left:3px solid var(--purple);}
|
||||
.callout strong{font-weight:700;}
|
||||
.callout strong.o{color:var(--orange);}
|
||||
.callout strong.g{color:var(--green);}
|
||||
.callout strong.n{color:var(--navy);}
|
||||
|
||||
/* impl-ref */
|
||||
.impl-ref{margin-top:20px;}
|
||||
.impl-ref table{width:100%;border-collapse:collapse;font-size:12px;}
|
||||
.impl-ref th{background:var(--navy);color:#fff;padding:6px 10px;text-align:left;font-size:10px;font-weight:600;letter-spacing:.06em;}
|
||||
.impl-ref td{padding:7px 10px;border-bottom:1px solid var(--border);vertical-align:top;line-height:1.6;}
|
||||
.impl-ref tr:nth-child(even) td{background:var(--surface);}
|
||||
.impl-ref code{font-family:var(--mono);font-size:11px;background:rgba(0,40,80,.06);padding:1px 4px;border-radius:2px;}
|
||||
.impl-ref .new{color:var(--green);font-weight:600;}
|
||||
.impl-ref .changed{color:var(--orange);font-weight:600;}
|
||||
|
||||
/* caption */
|
||||
.caption{font-family:var(--mono);font-size:10px;color:var(--muted);display:block;margin-top:6px;}
|
||||
|
||||
/* Two-col layout */
|
||||
.two-col{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-bottom:24px;}
|
||||
.three-col{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-bottom:24px;}
|
||||
|
||||
/* ─── MOCKUP FRAME ───────────────────────────── */
|
||||
/* Scaled at ~56% (640px wide renders as ~1140px concept) */
|
||||
.frame-wrap{background:var(--surface);border:1px solid var(--border);border-radius:6px;overflow:hidden;box-shadow:0 4px 16px rgba(0,0,0,.08);margin-bottom:8px;}
|
||||
|
||||
/* Topbar */
|
||||
.f-topbar{background:var(--navy);height:26px;display:flex;align-items:center;padding:0 14px;gap:12px;}
|
||||
.f-logo{font-size:6.5px;font-weight:700;color:#fff;letter-spacing:.7px;}
|
||||
.f-navlinks{display:flex;gap:8px;margin-left:6px;}
|
||||
.f-navlink{font-size:5.5px;color:rgba(255,255,255,.4);font-weight:600;text-transform:uppercase;letter-spacing:.05em;}
|
||||
.f-navlink.on{color:rgba(255,255,255,.9);border-bottom:1.5px solid var(--mint);padding-bottom:1px;}
|
||||
.f-navr{margin-left:auto;display:flex;align-items:center;gap:4px;}
|
||||
.f-uname{font-size:5.5px;color:rgba(255,255,255,.4);text-transform:uppercase;font-weight:600;}
|
||||
.f-av{width:14px;height:14px;border-radius:50%;background:var(--mint);display:flex;align-items:center;justify-content:center;font-size:4.5px;font-weight:800;color:var(--navy);}
|
||||
|
||||
/* Search bar */
|
||||
.f-searchbar{background:#fff;border-bottom:1px solid var(--sand);padding:7px 14px;display:flex;align-items:center;gap:8px;}
|
||||
.f-search-input{flex:1;height:18px;border:1.5px solid var(--navy);border-radius:2px;padding:0 6px;display:flex;align-items:center;gap:4px;}
|
||||
.f-search-q{font-size:6.5px;color:var(--navy);font-weight:600;}
|
||||
.f-search-count{font-size:5.5px;font-weight:600;color:var(--subtle);text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;}
|
||||
.f-newbtn{height:18px;padding:0 7px;background:var(--navy);border-radius:2px;font-size:5.5px;font-weight:700;color:#fff;text-transform:uppercase;letter-spacing:.05em;display:flex;align-items:center;}
|
||||
|
||||
/* Sort bar */
|
||||
.f-sortbar{background:#fff;border-bottom:1px solid var(--sand);padding:5px 14px;display:flex;align-items:center;gap:8px;}
|
||||
.f-sortcount{flex:1;font-size:5.5px;color:var(--subtle);}
|
||||
.f-sortlabel{font-size:5px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--subtle);}
|
||||
.f-sortsel{height:14px;border:1px solid var(--sand);border-radius:2px;padding:0 5px;font-size:5.5px;color:var(--navy);background:var(--sand);display:flex;align-items:center;}
|
||||
.f-filterbtn{height:14px;padding:0 6px;background:var(--navy);border-radius:2px;font-size:5px;font-weight:700;color:#fff;text-transform:uppercase;display:flex;align-items:center;gap:3px;}
|
||||
.f-fbadge{background:var(--mint);color:var(--navy);font-size:4.5px;font-weight:800;border-radius:8px;padding:0 3px;}
|
||||
|
||||
/* Filter panel (open) */
|
||||
.f-filterpanel{background:#fff;border-bottom:1px solid var(--sand);padding:8px 14px;display:flex;gap:16px;}
|
||||
.f-fpgroup{flex:1;}
|
||||
.f-fplabel{font-size:4.5px;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:var(--subtle);margin-bottom:4px;display:block;}
|
||||
.f-fpinput{height:12px;border:1px solid var(--sand);border-radius:2px;padding:0 5px;font-size:5px;color:var(--subtle);background:var(--sand);width:100%;display:flex;align-items:center;}
|
||||
.f-fpdate{display:flex;gap:3px;}
|
||||
.f-fpdate .f-fpinput{flex:1;}
|
||||
.f-fptag{font-size:4.5px;font-weight:700;background:var(--mint);color:var(--navy);border-radius:2px;padding:1px 5px;display:inline-block;margin-right:2px;margin-bottom:2px;}
|
||||
.f-fptag-e{font-size:4.5px;font-weight:700;background:var(--sand);color:var(--subtle);border-radius:2px;padding:1px 5px;display:inline-block;margin-right:2px;margin-bottom:2px;}
|
||||
|
||||
/* List body */
|
||||
.f-body{padding:8px 14px;}
|
||||
|
||||
/* Year card */
|
||||
.f-yearcard{border:1px solid var(--border);background:#fff;margin-bottom:8px;overflow:hidden;box-shadow:0 1px 2px rgba(0,0,0,.04);}
|
||||
.f-yearhead{background:var(--sand);padding:3px 10px;font-size:5.5px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--subtle);border-bottom:1px solid var(--border);}
|
||||
|
||||
/* Document row */
|
||||
.f-docrow{display:flex;border-bottom:1px solid #ece9e0;}
|
||||
.f-docrow:last-child{border-bottom:none;}
|
||||
.f-docrow:hover{background:#fafaf8;}
|
||||
.f-docleft{flex:1;min-width:0;padding:8px 10px;border-right:1px solid #ece9e0;}
|
||||
.f-docright{width:110px;flex-shrink:0;padding:6px 9px;display:flex;flex-direction:column;justify-content:space-between;}
|
||||
.f-doctitle{font-family:Georgia,serif;font-size:7px;font-weight:700;color:var(--navy);margin-bottom:3px;line-height:1.3;}
|
||||
.f-doctitle .hl{border-bottom:1.5px solid var(--navy);}
|
||||
.f-docsnip{font-family:Georgia,serif;font-size:5.5px;color:#4b5563;font-style:italic;line-height:1.5;margin-bottom:4px;}
|
||||
.f-docsnip .hl{border-bottom:1.5px solid var(--navy);}
|
||||
.f-doctags{display:flex;gap:2px;flex-wrap:wrap;}
|
||||
.f-doctag{font-size:4.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;background:#dbeafe;color:var(--navy);border-radius:1px;padding:1px 4px;display:flex;align-items:center;gap:2px;}
|
||||
.f-doctag .dot{width:4px;height:4px;border-radius:50%;background:#3b82f6;flex-shrink:0;}
|
||||
.f-doctag.fam{background:#dcfce7;}.f-doctag.fam .dot{background:#16a34a;}
|
||||
.f-ml{display:flex;align-items:center;gap:3px;font-size:5px;color:var(--muted);margin-bottom:2px;}
|
||||
.f-ml strong{font-size:4.5px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--navy);}
|
||||
.f-meta-bottom{display:flex;align-items:center;justify-content:space-between;gap:4px;margin-top:4px;}
|
||||
/* Ring */
|
||||
.f-ring{position:relative;width:20px;height:20px;flex-shrink:0;}
|
||||
.f-ring svg{position:absolute;top:0;left:0;transform:rotate(-90deg);}
|
||||
.f-ring-label{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:4px;font-weight:800;}
|
||||
/* Contributors */
|
||||
.f-contribs{display:flex;}
|
||||
.f-cav{width:12px;height:12px;border-radius:50%;border:1.5px solid white;display:flex;align-items:center;justify-content:center;font-size:4px;font-weight:800;color:#fff;margin-left:-3px;}
|
||||
.f-contribs .f-cav:first-child{margin-left:0;}
|
||||
|
||||
/* annotation box */
|
||||
.anno-box{background:#fff;border:1px solid var(--border);border-radius:6px;padding:16px 20px;margin-bottom:16px;}
|
||||
.anno-box h4{font-size:12px;font-weight:700;color:var(--navy);margin-bottom:6px;}
|
||||
.anno-box p{font-size:12px;color:var(--muted);line-height:1.55;margin-bottom:8px;}
|
||||
.anno-box p:last-child{margin-bottom:0;}
|
||||
.anno-box code{font-family:var(--mono);font-size:10px;background:rgba(0,40,80,.06);padding:1px 4px;border-radius:2px;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="doc">
|
||||
|
||||
<!-- ══════════════════════════════════════
|
||||
HEADER
|
||||
══════════════════════════════════════ -->
|
||||
<div class="hdr">
|
||||
<div class="badges">
|
||||
<span class="badge">Neue Route</span>
|
||||
<span class="badge badge-g">Frontend</span>
|
||||
<span class="badge badge-g">Backend</span>
|
||||
</div>
|
||||
<h1>Dokumente-Seite — /documents</h1>
|
||||
<p style="font-size:13px;color:rgba(255,255,255,.6);margin-top:6px;max-width:680px;">
|
||||
Dedicated search and browse page for all documents. Separates the document list from the dashboard hub. Uses per-year group cards with flat divide-y rows, a horizontal split row (content left · metadata right), a circular progress ring, and contributor avatars.
|
||||
</p>
|
||||
<div class="hdr-meta">Spec · Leonie Voss · 2026-04-19 · Issue TBD</div>
|
||||
</div>
|
||||
|
||||
<div class="decision-box">
|
||||
<h2>Design decisions</h2>
|
||||
<p class="prose">The hub (<code>/</code>) becomes pure dashboard — no more dual-mode switching. The "Documents" nav tab points to <code>/documents</code>, a focused search/browse page.</p>
|
||||
<p class="prose">Row layout: two-column split — title and snippet occupy the full left column for maximum scan width; date, sender, receiver, archive location, progress ring and contributor avatars live in a fixed 240px right panel. This keeps metadata consistently positioned across all rows.</p>
|
||||
<p class="prose">List structure: one white card container per year group (matching the current <code>border border-line bg-surface shadow-sm</code> pattern), rows separated by <code>divide-y</code> dividers — no gaps, no individual row cards. The year label is an inset header row within each card.</p>
|
||||
<p class="prose">Progress ring shows work completion as a percentage (0–100%). It is driven by a new <code>completionPercentage</code> field on the search result DTO, computed server-side from annotation block counts. Contributor avatars require a new <code>contributors</code> array (initials + color) on the search DTO.</p>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<!-- ══════════════════════════════════════
|
||||
SECTION 1 — FULL MOCKUP
|
||||
══════════════════════════════════════ -->
|
||||
<div class="sec">
|
||||
<div class="sec-label">Section 1</div>
|
||||
<div class="sec-title">Full page mockup — filter panel open, search active</div>
|
||||
<div class="sec-sub">Scaled at ~56%. Desktop 1200px concept width.</div>
|
||||
|
||||
<div class="frame-wrap">
|
||||
<!-- Topbar -->
|
||||
<div class="f-topbar">
|
||||
<div class="f-logo">FAMILIENARCHIV</div>
|
||||
<div class="f-navlinks">
|
||||
<span class="f-navlink on">Documents</span>
|
||||
<span class="f-navlink">Persons</span>
|
||||
<span class="f-navlink">Letters</span>
|
||||
<span class="f-navlink">Admin</span>
|
||||
</div>
|
||||
<div class="f-navr"><span class="f-uname">Hochlader</span><div class="f-av">MR</div></div>
|
||||
</div>
|
||||
<!-- Search bar -->
|
||||
<div class="f-searchbar">
|
||||
<div class="f-search-input">
|
||||
<svg width="8" height="8" viewBox="0 0 20 20" fill="none"><circle cx="8.5" cy="8.5" r="5.75" stroke="#9ca3af" stroke-width="1.5"/><path d="M13 13l3.5 3.5" stroke="#9ca3af" stroke-width="1.5" stroke-linecap="round"/></svg>
|
||||
<span class="f-search-q">brief</span>
|
||||
</div>
|
||||
<span class="f-search-count">31 Dokumente</span>
|
||||
<div class="f-newbtn">+ New Document</div>
|
||||
</div>
|
||||
<!-- Sort bar -->
|
||||
<div class="f-sortbar">
|
||||
<span class="f-sortcount">31 documents</span>
|
||||
<span class="f-sortlabel">Sort</span>
|
||||
<div class="f-sortsel">Date ↓</div>
|
||||
<div class="f-filterbtn">
|
||||
<svg width="7" height="7" viewBox="0 0 16 16" fill="none"><path d="M2 4h12M4 8h8M6 12h4" stroke="white" stroke-width="1.5" stroke-linecap="round"/></svg>
|
||||
Filters <span class="f-fbadge">1</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Filter panel -->
|
||||
<div class="f-filterpanel">
|
||||
<div class="f-fpgroup">
|
||||
<span class="f-fplabel">Date range</span>
|
||||
<div class="f-fpdate"><div class="f-fpinput">From</div><div class="f-fpinput">To</div></div>
|
||||
</div>
|
||||
<div class="f-fpgroup">
|
||||
<span class="f-fplabel">Sender</span>
|
||||
<div class="f-fpinput">Search person…</div>
|
||||
</div>
|
||||
<div class="f-fpgroup">
|
||||
<span class="f-fplabel">Receiver</span>
|
||||
<div class="f-fpinput">Search person…</div>
|
||||
</div>
|
||||
<div class="f-fpgroup">
|
||||
<span class="f-fplabel">Tags</span>
|
||||
<span class="f-fptag">Brief</span><span class="f-fptag-e">Foto</span><span class="f-fptag-e">Postkarte</span><span class="f-fptag-e">Urkunde</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- List body -->
|
||||
<div class="f-body">
|
||||
|
||||
<!-- 1924 card -->
|
||||
<div class="f-yearcard">
|
||||
<div class="f-yearhead">1924</div>
|
||||
<div class="f-docrow">
|
||||
<div class="f-docleft">
|
||||
<div class="f-doctitle">Demo: Ierlicher <span class="hl">Brief</span> — Belgern</div>
|
||||
<div class="f-docsnip">… Hiermit übersende ich Ihnen den gewünschten <span class="hl">Brief</span> meines Vaters, welcher einige interessante Hinweise zur Familiengeschichte enthält …</div>
|
||||
<div class="f-doctags"><div class="f-doctag"><div class="dot"></div>Brief</div><div class="f-doctag fam"><div class="dot"></div>Familie</div></div>
|
||||
</div>
|
||||
<div class="f-docright">
|
||||
<div>
|
||||
<div class="f-ml"><strong>Date</strong> 31. Mai 1924</div>
|
||||
<div class="f-ml"><strong>From</strong> Louise Aon Boden</div>
|
||||
<div class="f-ml"><strong>To</strong> Marcel Raddatz</div>
|
||||
<div class="f-ml"><strong>Archive</strong> Box 3 · Folder A</div>
|
||||
</div>
|
||||
<div class="f-meta-bottom">
|
||||
<div class="f-ring">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20"><circle cx="10" cy="10" r="7" fill="none" stroke="#E4E2D7" stroke-width="2"/><circle cx="10" cy="10" r="7" fill="none" stroke="#A6DAD8" stroke-width="2" stroke-dasharray="44 44" stroke-linecap="round"/></svg>
|
||||
<div class="f-ring-label" style="color:#5bbab7">100%</div>
|
||||
</div>
|
||||
<div class="f-contribs"><div class="f-cav" style="background:#7c3aed">MR</div><div class="f-cav" style="background:#0891b2">LS</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 1923 card -->
|
||||
<div class="f-yearcard">
|
||||
<div class="f-yearhead">1923</div>
|
||||
<div class="f-docrow">
|
||||
<div class="f-docleft">
|
||||
<div class="f-doctitle">W-0614 – 8. September 1923 – Tölz</div>
|
||||
<div class="f-docsnip">… Clara schreibt über die Ankunft in Tölz und erwähnt den letzten <span class="hl">Brief</span> von Fauld Rupley, der noch keine Antwort erhalten hat …</div>
|
||||
<div class="f-doctags"><div class="f-doctag"><div class="dot"></div>Brief</div></div>
|
||||
</div>
|
||||
<div class="f-docright">
|
||||
<div>
|
||||
<div class="f-ml"><strong>Date</strong> 8. Sept. 1923</div>
|
||||
<div class="f-ml"><strong>From</strong> Clara Lam</div>
|
||||
<div class="f-ml"><strong>To</strong> Fauld Rupley</div>
|
||||
<div class="f-ml"><strong>Archive</strong> Box 1 · Folder C</div>
|
||||
</div>
|
||||
<div class="f-meta-bottom">
|
||||
<div class="f-ring">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20"><circle cx="10" cy="10" r="7" fill="none" stroke="#E4E2D7" stroke-width="2"/><circle cx="10" cy="10" r="7" fill="none" stroke="#A6DAD8" stroke-width="2" stroke-dasharray="33 44" stroke-linecap="round"/></svg>
|
||||
<div class="f-ring-label" style="color:#5bbab7">75%</div>
|
||||
</div>
|
||||
<div class="f-contribs"><div class="f-cav" style="background:#dc2626">AK</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="f-docrow">
|
||||
<div class="f-docleft">
|
||||
<div class="f-doctitle">W-0196 – 2. September 1923 – B. Lichterfelde</div>
|
||||
<div class="f-docsnip">… Prediger's Haushaltung enthält einen <span class="hl">Brief</span>; Zusammen mit der Vollmacht aus dem Vorjahr ergibt sich folgendes Bild …</div>
|
||||
<div class="f-doctags"><div class="f-doctag"><div class="dot"></div>Brief</div></div>
|
||||
</div>
|
||||
<div class="f-docright">
|
||||
<div>
|
||||
<div class="f-ml"><strong>Date</strong> 2. Sept. 1923</div>
|
||||
<div class="f-ml"><strong>From</strong> Müller de Gruym</div>
|
||||
<div class="f-ml"><strong>To</strong> Herbert Cram</div>
|
||||
</div>
|
||||
<div class="f-meta-bottom">
|
||||
<div class="f-ring">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20"><circle cx="10" cy="10" r="7" fill="none" stroke="#E4E2D7" stroke-width="2"/><circle cx="10" cy="10" r="7" fill="none" stroke="#A6DAD8" stroke-width="2" stroke-dasharray="18 44" stroke-linecap="round"/></svg>
|
||||
<div class="f-ring-label" style="color:#9ca3af">40%</div>
|
||||
</div>
|
||||
<div class="f-contribs"><div class="f-cav" style="background:#7c3aed">MR</div><div class="f-cav" style="background:#0891b2">LS</div><div class="f-cav" style="background:#dc2626">AK</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="f-docrow">
|
||||
<div class="f-docleft">
|
||||
<div class="f-doctitle">W-0397 – 2. September 1923 – B. Lichterfelde</div>
|
||||
<div class="f-docsnip">… zum einleitend Kommentar hieraus, den Herrn, zum <span class="hl">Brief</span> az sechzig und weitere Passagen …</div>
|
||||
<div class="f-doctags"><div class="f-doctag"><div class="dot"></div>Brief</div></div>
|
||||
</div>
|
||||
<div class="f-docright">
|
||||
<div>
|
||||
<div class="f-ml"><strong>Date</strong> 2. Sept. 1923</div>
|
||||
<div class="f-ml"><strong>From</strong> Müller de Gruym</div>
|
||||
</div>
|
||||
<div class="f-meta-bottom">
|
||||
<div class="f-ring">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20"><circle cx="10" cy="10" r="7" fill="none" stroke="#E4E2D7" stroke-width="2"/></svg>
|
||||
<div class="f-ring-label" style="color:#9ca3af">0%</div>
|
||||
</div>
|
||||
<span style="font-size:4.5px;color:#9ca3af;font-weight:600;text-transform:uppercase;letter-spacing:.08em;">No contributors</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<span class="caption">Fig 1 — /documents · 1200px · search: "brief" · filter panel open · sort: Date ↓</span>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<!-- ══════════════════════════════════════
|
||||
SECTION 2 — PAGE STRUCTURE
|
||||
══════════════════════════════════════ -->
|
||||
<div class="sec">
|
||||
<div class="sec-label">Section 2</div>
|
||||
<div class="sec-title">Page structure & zones</div>
|
||||
|
||||
<div class="three-col">
|
||||
<div class="anno-box">
|
||||
<h4>① Global search bar</h4>
|
||||
<p>Full-width row below the topbar. Contains the search input (flex-1), result count (right of input), and "+ New Document" button. Background white, bottom border <code>border-line</code>. Sticky — stays visible on scroll.</p>
|
||||
<p>Same search bar pattern as the current homepage. Debounce 500 ms on text input; immediate on clear.</p>
|
||||
</div>
|
||||
<div class="anno-box">
|
||||
<h4>② Sort / count bar</h4>
|
||||
<p>Slim bar below search. Shows result count (left), sort dropdown (right), and Filters toggle button (far right). Background white, bottom border <code>border-line</code>. Sticky — stacks below search bar on scroll.</p>
|
||||
<p>Filters button shows a mint badge with active filter count. When filters are open the button fills navy.</p>
|
||||
</div>
|
||||
<div class="anno-box">
|
||||
<h4>③ Collapsible filter panel</h4>
|
||||
<p>Drops open below the sort bar. Contains four groups: Date range (two inputs), Sender (PersonTypeahead), Receiver (PersonTypeahead), Tags (clickable pills). White background, bottom border <code>border-line</code>.</p>
|
||||
<p>Closed by default on page load unless URL already has active filter params. Animate open/close with <code>transition-all duration-200</code>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="callout navy">
|
||||
<div><strong class="n">Routing:</strong> New route <code>frontend/src/routes/documents/+page.svelte</code> and <code>+page.server.ts</code>. AppNav "Documents" tab <code>href</code> changes from <code>/</code> to <code>/documents</code>. Homepage <code>+page.svelte</code> loses dual-mode — always renders dashboard. No redirect from <code>/?q=…</code>.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<!-- ══════════════════════════════════════
|
||||
SECTION 2b — MOBILE BREAKPOINTS
|
||||
══════════════════════════════════════ -->
|
||||
<div class="sec">
|
||||
<div class="sec-label">Section 2b</div>
|
||||
<div class="sec-title">Mobile breakpoints</div>
|
||||
<div class="sec-sub">Three responsive tiers: <sm (mobile), sm–lg (tablet), lg+ (desktop).</div>
|
||||
|
||||
<div class="three-col">
|
||||
<div class="anno-box">
|
||||
<h4>< sm — < 640px (mobile)</h4>
|
||||
<p>Document row: single-column block. Left/right split collapses. Metadata (date, from, to, archive) moves below the tags row as a 2×2 compact grid. Progress ring and contributor stack appear in a bottom row directly below the grid.</p>
|
||||
<p>Filter panel: single-column stack (<code>flex-col</code>). Sort bar wraps if needed.</p>
|
||||
</div>
|
||||
<div class="anno-box">
|
||||
<h4>sm – lg — 640–1023px</h4>
|
||||
<p>Document row: two-column split restored. Metadata column narrower: <code>sm:w-48</code> (192px) instead of <code>w-60</code> to fit tablet viewports.</p>
|
||||
<p>Sticky bars span full width via negative margins. Filter panel: <code>flex-row flex-wrap</code>, groups can wrap.</p>
|
||||
</div>
|
||||
<div class="anno-box">
|
||||
<h4>lg+ — ≥ 1024px (desktop)</h4>
|
||||
<p>Full two-column split. Metadata column: <code>lg:w-60</code> (240px). Filter panel: four groups in a single row. Max content width <code>max-w-7xl</code> (1280px) — from app layout container, no extra padding on list body.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile mockup at ~375px concept width, rendered at ~220px -->
|
||||
<div class="two-col">
|
||||
<div>
|
||||
<div class="frame-wrap" style="width:220px;">
|
||||
<div class="f-topbar" style="height:20px;padding:0 8px;gap:6px;">
|
||||
<span class="f-logo" style="font-size:5px;">FAMILIENARCHIV</span>
|
||||
<div style="margin-left:auto;display:flex;gap:6px;align-items:center;">
|
||||
<span class="f-navlink on" style="font-size:4.5px;border-bottom-width:1px;">Dokumente</span>
|
||||
<div class="f-av" style="width:12px;height:12px;font-size:4px;">MR</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="f-searchbar" style="padding:5px 8px;gap:5px;">
|
||||
<div class="f-search-input" style="height:14px;padding:0 5px;">
|
||||
<svg width="6" height="6" viewBox="0 0 20 20" fill="none"><circle cx="8.5" cy="8.5" r="5.75" stroke="#9ca3af" stroke-width="1.5"/><path d="M13 13l3.5 3.5" stroke="#9ca3af" stroke-width="1.5" stroke-linecap="round"/></svg>
|
||||
<span style="font-size:5px;color:#002850;font-weight:600;">brief</span>
|
||||
</div>
|
||||
<span style="font-size:4.5px;color:#9B9A93;white-space:nowrap;font-weight:700;text-transform:uppercase;letter-spacing:.06em;">31 Dok.</span>
|
||||
</div>
|
||||
<div class="f-sortbar" style="padding:3px 8px;gap:5px;">
|
||||
<span class="f-sortcount">31 documents</span>
|
||||
<span class="f-sortlabel">Sort</span>
|
||||
<div class="f-sortsel" style="height:11px;font-size:4.5px;padding:0 4px;">Date ↓</div>
|
||||
<div class="f-filterbtn" style="height:11px;font-size:4px;padding:0 4px;">Filters</div>
|
||||
</div>
|
||||
<div style="padding:5px 8px;">
|
||||
<div class="f-yearcard">
|
||||
<div class="f-yearhead">1924</div>
|
||||
<!-- mobile stacked row 1 -->
|
||||
<div style="padding:7px 8px;border-bottom:1px solid #ece9e0;">
|
||||
<div class="f-doctitle" style="margin-bottom:2px;">Demo: Ierlicher <span class="hl">Brief</span> — Belgern</div>
|
||||
<div class="f-docsnip" style="margin-bottom:3px;">… Hiermit übersende ich Ihnen den gewünschten <span class="hl">Brief</span> …</div>
|
||||
<div class="f-doctags" style="margin-bottom:0;"><div class="f-doctag"><div class="dot"></div>Brief</div></div>
|
||||
<div style="border-top:1px solid #ece9e0;margin-top:4px;padding-top:3px;display:grid;grid-template-columns:1fr 1fr;gap:0 8px;">
|
||||
<div class="f-ml"><strong>Date</strong> 31. Mai 1924</div>
|
||||
<div class="f-ml"><strong>From</strong> L. von Boden</div>
|
||||
<div class="f-ml"><strong>Archive</strong> Box 3 · A</div>
|
||||
<div class="f-ml"><strong>To</strong> M. Raddatz</div>
|
||||
</div>
|
||||
<div class="f-meta-bottom" style="margin-top:4px;">
|
||||
<div class="f-ring">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20"><circle cx="10" cy="10" r="7" fill="none" stroke="#E4E2D7" stroke-width="2"/><circle cx="10" cy="10" r="7" fill="none" stroke="#A6DAD8" stroke-width="2" stroke-dasharray="44 44" stroke-linecap="round"/></svg>
|
||||
<div class="f-ring-label" style="color:#5bbab7">100%</div>
|
||||
</div>
|
||||
<div class="f-contribs"><div class="f-cav" style="background:#7c3aed">MR</div><div class="f-cav" style="background:#0891b2">LS</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- mobile stacked row 2 -->
|
||||
<div style="padding:7px 8px;">
|
||||
<div class="f-doctitle" style="margin-bottom:2px;">W-0614 – Sept. 1923 – Tölz</div>
|
||||
<div class="f-docsnip" style="margin-bottom:3px;">… Clara schreibt über den letzten <span class="hl">Brief</span> von Fauld Rupley …</div>
|
||||
<div class="f-doctags" style="margin-bottom:0;"><div class="f-doctag"><div class="dot"></div>Brief</div></div>
|
||||
<div style="border-top:1px solid #ece9e0;margin-top:4px;padding-top:3px;display:grid;grid-template-columns:1fr 1fr;gap:0 8px;">
|
||||
<div class="f-ml"><strong>Date</strong> 8. Sept. 1923</div>
|
||||
<div class="f-ml"><strong>From</strong> Clara Lam</div>
|
||||
<div class="f-ml"></div>
|
||||
<div class="f-ml"><strong>To</strong> F. Rupley</div>
|
||||
</div>
|
||||
<div class="f-meta-bottom" style="margin-top:4px;">
|
||||
<div class="f-ring">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20"><circle cx="10" cy="10" r="7" fill="none" stroke="#E4E2D7" stroke-width="2"/><circle cx="10" cy="10" r="7" fill="none" stroke="#A6DAD8" stroke-width="2" stroke-dasharray="33 44" stroke-linecap="round"/></svg>
|
||||
<div class="f-ring-label" style="color:#5bbab7">75%</div>
|
||||
</div>
|
||||
<div class="f-contribs"><div class="f-cav" style="background:#dc2626">AK</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="caption">Fig 2 — /documents · 375px mobile · search "brief" · filter closed</span>
|
||||
</div>
|
||||
|
||||
<div class="anno-box" style="align-self:start">
|
||||
<h4>Mobile row — CSS-only approach</h4>
|
||||
<p>No JS needed. The <code><a></code> link is always <code>block</code>. On <code>sm+</code> the inner element switches to <code>flex items-stretch</code>, showing the right metadata column (<code>hidden sm:flex</code>) and hiding the mobile compact grid (<code>sm:hidden</code>).</p>
|
||||
<p>This means the DOM contains both layouts simultaneously — the metadata grid inside the left column (mobile only) and the right metadata panel (sm+ only). Both share the same data, just rendered differently.</p>
|
||||
<p>Minimum touch target: the entire row is the <code><a></code>, guaranteed ≥44px on mobile given title + snippet + tags + metadata grid.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<!-- ══════════════════════════════════════
|
||||
SECTION 3 — YEAR GROUP CARD
|
||||
══════════════════════════════════════ -->
|
||||
<div class="sec">
|
||||
<div class="sec-label">Section 3</div>
|
||||
<div class="sec-title">Year group card</div>
|
||||
<div class="sec-sub">One card per year group. Rows inside use divide-y — no gaps between rows.</div>
|
||||
|
||||
<div class="two-col">
|
||||
<div class="anno-box">
|
||||
<h4>Card container</h4>
|
||||
<p>Matches current DocumentList outer container exactly: <code>border border-line bg-surface shadow-sm</code>. No border-radius (keeps it flush). Margin between consecutive year cards: <code>mb-4</code>.</p>
|
||||
<p>Rendered only when sort = DATE. For other sort modes (SENDER, RECEIVER, TITLE) the year header is replaced by the relevant group label using the same card pattern.</p>
|
||||
</div>
|
||||
<div class="anno-box">
|
||||
<h4>Year header row</h4>
|
||||
<p>First child of each card. Background <code>bg-sand</code>, text <code>text-xs font-bold uppercase tracking-widest text-ink-3</code>. Height <code>py-1.5 px-5</code>. Bottom border <code>border-b border-line</code>.</p>
|
||||
<p>Not a standalone divider — it is part of the card so the top border of the card frames the year label on three sides.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<!-- ══════════════════════════════════════
|
||||
SECTION 4 — DOCUMENT ROW
|
||||
══════════════════════════════════════ -->
|
||||
<div class="sec">
|
||||
<div class="sec-label">Section 4</div>
|
||||
<div class="sec-title">Document row — two-column split</div>
|
||||
|
||||
<div class="two-col">
|
||||
<div class="anno-box">
|
||||
<h4>Left column — content</h4>
|
||||
<p>Flex-1, min-width 0. Padding <code>p-4 pr-5</code>. Right border <code>border-r border-line-2</code>.</p>
|
||||
<p><strong>Title</strong> — <code>font-serif text-base font-bold text-ink</code> with search highlight underlines. <code>mb-1.5</code>.</p>
|
||||
<p><strong>Snippet</strong> — <code>font-serif text-sm italic text-ink-2 line-clamp-2 mb-2</code> with highlight underlines. Only rendered when a match snippet is present.</p>
|
||||
<p><strong>Tags</strong> — existing tag pill pattern <code>bg-muted text-ink text-[10px] font-bold uppercase tracking-widest rounded px-2 py-0.5</code>. Gap <code>gap-1.5 flex-wrap</code>.</p>
|
||||
</div>
|
||||
<div class="anno-box">
|
||||
<h4>Right column — metadata panel</h4>
|
||||
<p>Fixed width <code>w-60</code> (240px). Padding <code>p-3.5</code>. Flex column, <code>justify-between</code>.</p>
|
||||
<p><strong>Meta lines</strong> (top group) — <code>font-sans text-[11px] text-ink-2 mb-1</code>. Label: <code>font-bold uppercase tracking-wide text-[10px] text-ink-3 mr-1.5</code>. Lines: Date · From · To · Archive (Box · Folder). Archive only rendered when <code>archiveBox</code> is set.</p>
|
||||
<p><strong>Bottom row</strong> — flexbox, space-between. Left: progress ring. Right: ContributorStack.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="callout orange">
|
||||
<div><strong class="o">Accessibility:</strong> The ring conveys progress by both percentage text and arc fill — not colour alone. Contributors show initials as text inside the avatar. Both pass the redundant-cue requirement from the Leonie Voss persona. Minimum touch target for the row link: the full row is the <code><a></code> element, always ≥44px tall given the content. Row hover: <code>hover:bg-muted/50 transition-colors duration-200</code>.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<!-- ══════════════════════════════════════
|
||||
SECTION 5 — PROGRESS RING
|
||||
══════════════════════════════════════ -->
|
||||
<div class="sec">
|
||||
<div class="sec-label">Section 5</div>
|
||||
<div class="sec-title">Progress ring</div>
|
||||
|
||||
<div class="two-col">
|
||||
<div class="anno-box">
|
||||
<h4>Anatomy</h4>
|
||||
<p>SVG donut ring, 36×36px. Track circle: <code>stroke="#E4E2D7"</code> (<code>stroke-brand-sand</code>) width 3px. Fill arc: <code>stroke="#A6DAD8"</code> (<code>stroke-accent</code>) width 3px, <code>stroke-linecap="round"</code>. Rotated −90° so arc starts at 12 o'clock.</p>
|
||||
<p>Centre label: percentage text <code>font-sans text-[8px] font-bold</code>. Colour: mint (<code>text-accent-dark</code>) when >0%, gray-400 when 0%.</p>
|
||||
<p>Circumference of r=13: <code>2π×13 ≈ 81.7px</code>. Stroke-dasharray: <code>{pct * 81.7} 81.7</code>.</p>
|
||||
</div>
|
||||
<div class="anno-box">
|
||||
<h4>Data source — new API field</h4>
|
||||
<p>New field <code>completionPercentage: number</code> (0–100, integer) on the document search result DTO. Computed server-side:</p>
|
||||
<p><code>round((reviewedBlocks / max(totalBlocks, 1)) * 100)</code></p>
|
||||
<p>If a document has no annotation blocks yet (no transcription started), returns 0. Backend change: new subquery in the document search repository to COUNT annotation blocks (all vs. reviewed) per document, joined into the search projection.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<!-- ══════════════════════════════════════
|
||||
SECTION 6 — CONTRIBUTOR AVATARS
|
||||
══════════════════════════════════════ -->
|
||||
<div class="sec">
|
||||
<div class="sec-label">Section 6</div>
|
||||
<div class="sec-title">Contributor avatar stack</div>
|
||||
|
||||
<div class="two-col">
|
||||
<div class="anno-box">
|
||||
<h4>Anatomy</h4>
|
||||
<p>Reuse existing <code>ContributorStack.svelte</code> component (added in commit 031f6ea). Avatars 22×22px, <code>-ml-1.5</code> overlap, white 2px border.</p>
|
||||
<p>Show max 3 avatars. If more: <code>+N</code> text element in gray-400. When no contributors: render <code>text-[9px] text-ink-3 uppercase tracking-wide</code> label "No contributors".</p>
|
||||
</div>
|
||||
<div class="anno-box">
|
||||
<h4>Data source — new API field</h4>
|
||||
<p>New field <code>contributors: ActivityActorDTO[]</code> on the document search result DTO. <code>ActivityActorDTO</code> already exists (used in dashboard queue items): <code>{ initials: string, color: string, name?: string }</code>.</p>
|
||||
<p>Backend: join from document → annotation_blocks → created_by → users. Distinct by user. Order by most-recent contribution. Limit 4. New query in document search repository.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<!-- ══════════════════════════════════════
|
||||
SECTION 7 — BACKEND CHANGES
|
||||
══════════════════════════════════════ -->
|
||||
<div class="sec">
|
||||
<div class="sec-label">Section 7</div>
|
||||
<div class="sec-title">Backend changes required</div>
|
||||
|
||||
<div class="callout green">
|
||||
<div><strong class="g">New fields on document search DTO</strong> — Two new fields must be added to the object returned by <code>GET /api/documents/search</code>. These require a new projection or join in the repository layer. No schema migration needed — purely computed from existing annotation_block data.</div>
|
||||
</div>
|
||||
|
||||
<div class="impl-ref">
|
||||
<table>
|
||||
<thead><tr><th>Field</th><th>Type</th><th>Source</th><th>Notes</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>completionPercentage</code></td><td><code>int</code> (0–100)</td><td>COUNT(reviewed annotation blocks) / COUNT(all blocks)</td><td>0 when no blocks exist</td></tr>
|
||||
<tr><td><code>contributors</code></td><td><code>ActivityActorDTO[]</code></td><td>Distinct users with annotation_block contributions, ordered by recency</td><td>Max 4; reuse existing DTO</td></tr>
|
||||
<tr><td><code>archiveBox</code></td><td><code>String?</code></td><td>Already on Document entity — just not in search response</td><td><span class="changed">Expose existing field</span></td></tr>
|
||||
<tr><td><code>archiveFolder</code></td><td><code>String?</code></td><td>Already on Document entity — just not in search response</td><td><span class="changed">Expose existing field</span></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<!-- ══════════════════════════════════════
|
||||
SECTION 8 — IMPL-REF TABLE
|
||||
══════════════════════════════════════ -->
|
||||
<div class="sec">
|
||||
<div class="sec-label">Section 8 — Implementation Reference</div>
|
||||
<div class="sec-title">Exact Tailwind classes & pixel values</div>
|
||||
|
||||
<div class="impl-ref">
|
||||
<table>
|
||||
<thead><tr><th>Element</th><th>Tailwind classes</th><th>Pixels / value</th><th>Notes</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td colspan="4" style="background:rgba(0,40,80,.05);font-size:10px;font-weight:700;color:var(--navy);text-transform:uppercase;letter-spacing:.08em;">Page chrome</td></tr>
|
||||
<tr><td>Search bar wrapper</td><td><code>bg-white border-b border-line -mx-4 sm:-mx-6 lg:-mx-8 px-4 sm:px-6 lg:px-8 py-3.5 flex items-center gap-3 sticky top-[65px] z-20</code></td><td>padding 14px responsive</td><td>Topbar = 1px accent + 64px nav = 65px. Negative margins break out of container padding so bar spans full container width.</td></tr>
|
||||
<tr><td>Search input</td><td><code>flex-1 h-9 border border-ink rounded-sm px-3 font-sans text-sm text-ink bg-white</code></td><td>height 36px</td><td>Active: navy border</td></tr>
|
||||
<tr><td>Sort bar wrapper</td><td><code>bg-white border-b border-line -mx-4 sm:-mx-6 lg:-mx-8 px-4 sm:px-6 lg:px-8 py-2.5 flex items-center gap-3 sticky top-[113px] z-20</code></td><td>padding 10px responsive</td><td>Stacks below search bar (65 + 48 = 113px)</td></tr>
|
||||
<tr><td>Filters toggle (closed)</td><td><code>h-7 px-3 border border-line rounded-sm font-sans text-[10px] font-bold uppercase tracking-wide text-ink flex items-center gap-1.5</code></td><td>height 28px</td><td>—</td></tr>
|
||||
<tr><td>Filters toggle (open)</td><td><code>h-7 px-3 bg-ink text-white rounded-sm font-sans text-[10px] font-bold uppercase tracking-wide flex items-center gap-1.5</code></td><td>height 28px</td><td>Navy fill when active</td></tr>
|
||||
<tr><td>Filter panel wrapper</td><td><code>bg-white border-b border-line -mx-4 sm:-mx-6 lg:-mx-8 px-4 sm:px-6 lg:px-8 py-4 flex flex-col sm:flex-row sm:flex-wrap gap-4</code></td><td>padding 16px responsive</td><td>Use Svelte <code>slide</code> transition; stacks vertically on mobile</td></tr>
|
||||
<tr><td>List body</td><td><code>py-5</code></td><td>vertical padding only</td><td>No extra horizontal padding — app container handles it</td></tr>
|
||||
|
||||
<tr><td colspan="4" style="background:rgba(0,40,80,.05);font-size:10px;font-weight:700;color:var(--navy);text-transform:uppercase;letter-spacing:.08em;">Year group card</td></tr>
|
||||
<tr><td>Card container</td><td><code>border border-line bg-surface shadow-sm mb-4 overflow-hidden</code></td><td>—</td><td>Matches current DocumentList outer div exactly</td></tr>
|
||||
<tr><td>Year header</td><td><code>bg-sand border-b border-line px-5 py-1.5 font-sans text-[10px] font-bold uppercase tracking-widest text-ink-3</code></td><td>padding 6px 20px</td><td>—</td></tr>
|
||||
<tr><td>Row list</td><td><code>divide-y divide-line-2</code></td><td>—</td><td>Matches current <code><ul></code> pattern</td></tr>
|
||||
|
||||
<tr><td colspan="4" style="background:rgba(0,40,80,.05);font-size:10px;font-weight:700;color:var(--navy);text-transform:uppercase;letter-spacing:.08em;">Document row</td></tr>
|
||||
<tr><td>Row wrapper <code><li></code></td><td><code>group transition-colors duration-200 hover:bg-muted/50</code></td><td>—</td><td>Same hover pattern as current</td></tr>
|
||||
<tr><td>Row inner (link)</td><td><code>block sm:flex sm:items-stretch</code></td><td>—</td><td>Full-row <code><a href="/documents/{id}"></code>; flex only on sm+</td></tr>
|
||||
<tr><td>Left column</td><td><code>p-4 sm:flex-1 sm:min-w-0 sm:pr-5 sm:border-r sm:border-line-2</code></td><td>padding 16px</td><td>Right border only on sm+</td></tr>
|
||||
<tr><td>Right column (sm+)</td><td><code>hidden sm:flex sm:w-48 lg:w-60 flex-shrink-0 p-3.5 flex-col justify-between gap-2</code></td><td>sm: 192px · lg: 240px</td><td>Hidden on mobile; narrower on tablet</td></tr>
|
||||
<tr><td>Mobile metadata grid</td><td><code>sm:hidden border-t border-line-2 mt-3 pt-3 grid grid-cols-2 gap-x-4 gap-y-0.5</code></td><td>—</td><td>2×2 compact grid shown only on mobile, inside left col</td></tr>
|
||||
<tr><td>Mobile meta bottom row</td><td><code>sm:hidden flex items-center justify-between mt-3</code></td><td>—</td><td>Ring + contributors on mobile, shown only <sm</td></tr>
|
||||
<tr><td>Document title</td><td><code>font-serif text-base font-bold text-ink mb-1.5 leading-snug group-hover:underline</code></td><td>16px / 700</td><td>—</td></tr>
|
||||
<tr><td>Snippet text</td><td><code>font-serif text-sm italic text-ink-2 line-clamp-2 mb-2</code></td><td>14px</td><td>Only when snippet present</td></tr>
|
||||
<tr><td>Meta label</td><td><code>font-sans text-[10px] font-bold uppercase tracking-wide text-ink-3 mr-1.5</code></td><td>10px / 700</td><td>DATE · FROM · TO · ARCHIVE</td></tr>
|
||||
<tr><td>Meta value</td><td><code>font-sans text-[11px] text-ink-2</code></td><td>11px</td><td>—</td></tr>
|
||||
|
||||
<tr><td colspan="4" style="background:rgba(0,40,80,.05);font-size:10px;font-weight:700;color:var(--navy);text-transform:uppercase;letter-spacing:.08em;">Progress ring</td></tr>
|
||||
<tr><td>SVG container</td><td><code>relative w-9 h-9 flex-shrink-0</code></td><td>36×36px</td><td>—</td></tr>
|
||||
<tr><td>Track circle</td><td><code>stroke="var(--c-sand)"</code> stroke-width="3"</td><td>r=13, circumference 81.7px</td><td>—</td></tr>
|
||||
<tr><td>Fill arc</td><td><code>stroke="var(--c-accent)"</code> stroke-width="3" stroke-linecap="round"</td><td>dasharray = pct/100 × 81.7</td><td>rotate(−90deg)</td></tr>
|
||||
<tr><td>Percentage label</td><td><code>absolute inset-0 flex items-center justify-center font-sans text-[8px] font-bold</code></td><td>8px / 800</td><td>Mint when >0, gray-400 when 0</td></tr>
|
||||
|
||||
<tr><td colspan="4" style="background:rgba(0,40,80,.05);font-size:10px;font-weight:700;color:var(--navy);text-transform:uppercase;letter-spacing:.08em;">New files</td></tr>
|
||||
<tr><td><span class="new">NEW</span> <code>frontend/src/routes/documents/+page.svelte</code></td><td>—</td><td>—</td><td>Document list page (extract from homepage)</td></tr>
|
||||
<tr><td><span class="new">NEW</span> <code>frontend/src/routes/documents/+page.server.ts</code></td><td>—</td><td>—</td><td>Loads search results, same API call as current homepage</td></tr>
|
||||
<tr><td><span class="changed">CHANGED</span> <code>frontend/src/routes/AppNav.svelte</code></td><td>—</td><td>—</td><td>Documents tab href: <code>/</code> → <code>/documents</code></td></tr>
|
||||
<tr><td><span class="changed">CHANGED</span> <code>frontend/src/routes/+page.svelte</code></td><td>—</td><td>—</td><td>Remove dual-mode logic; always render dashboard</td></tr>
|
||||
<tr><td><span class="changed">CHANGED</span> <code>frontend/src/routes/+page.server.ts</code></td><td>—</td><td>—</td><td>Remove search branch; always fetch dashboard data</td></tr>
|
||||
<tr><td><span class="changed">CHANGED</span> <code>frontend/src/routes/DocumentList.svelte</code></td><td>—</td><td>—</td><td>Refactor to new two-column layout + year cards</td></tr>
|
||||
<tr><td><span class="new">NEW query</span> <code>backend/.../DocumentSearchRepository</code></td><td>—</td><td>—</td><td>Add completionPercentage + contributors to search projection</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /doc -->
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user