Compare commits
2 Commits
refactor/i
...
docs/issue
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bd7f0d486 | ||
| 4b8da0024f |
1122
docs/specs/dashboard-expansion-patterns.html
Normal file
1122
docs/specs/dashboard-expansion-patterns.html
Normal file
File diff suppressed because it is too large
Load Diff
814
docs/specs/mission-control-strip-final.html
Normal file
814
docs/specs/mission-control-strip-final.html
Normal file
@@ -0,0 +1,814 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Mission-Control-Streifen — Finale Spec (Issue #240)</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;}
|
||||
|
||||
/* Tags */
|
||||
.tag-list{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:14px;}
|
||||
.tag{display:inline-block;padding:2px 8px;border-radius:4px;font-size:10px;font-weight:600;letter-spacing:.04em;}
|
||||
.t-g{background:var(--green-bg);color:var(--green);}
|
||||
.t-o{background:var(--orange-bg);color:var(--orange);}
|
||||
.t-n{background:rgba(0,40,80,.08);color:var(--navy);}
|
||||
.t-p{background:var(--purple-bg);color:var(--purple);}
|
||||
|
||||
/* Pipeline diagram */
|
||||
.pipeline{display:flex;align-items:center;gap:6px;flex-wrap:wrap;padding:14px 18px;background:#fff;border:1px solid var(--border);border-radius:6px;margin-bottom:24px;}
|
||||
.pipe-node{text-align:center;}
|
||||
.pipe-badge{display:inline-block;padding:3px 10px;border-radius:4px;font-size:11px;font-weight:600;margin-bottom:4px;}
|
||||
.pipe-badge.n1{background:rgba(0,40,80,.08);color:var(--navy);}
|
||||
.pipe-badge.n2{background:rgba(0,40,80,.08);color:var(--navy);}
|
||||
.pipe-badge.n3{background:rgba(0,40,80,.08);color:var(--navy);}
|
||||
.pipe-badge.done{background:var(--green-bg);color:var(--green);}
|
||||
.pipe-sub{font-size:10px;color:var(--muted);}
|
||||
.pipe-arrow{font-size:16px;color:var(--border);flex-shrink:0;}
|
||||
.pipe-col-label{font-size:9px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;margin-top:4px;}
|
||||
.pipe-col-label.s{color:var(--navy);}
|
||||
.pipe-col-label.t{color:var(--navy);}
|
||||
.pipe-col-label.l{color:var(--green);}
|
||||
|
||||
/* Column definition grid */
|
||||
.col-defs{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;margin-bottom:28px;}
|
||||
.col-def{background:#fff;border:1px solid var(--border);border-radius:6px;padding:14px;}
|
||||
.col-def-title{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;margin-bottom:6px;}
|
||||
.col-def-title.n{color:var(--navy);}
|
||||
.col-def-title.g{color:var(--green);}
|
||||
.col-def p{font-size:12px;color:var(--muted);line-height:1.5;margin-bottom:8px;}
|
||||
.col-def code{font-family:var(--mono);font-size:10px;background:rgba(0,40,80,.06);padding:1px 4px;border-radius:2px;}
|
||||
|
||||
/* 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);}
|
||||
.callout strong.p{color:var(--purple);}
|
||||
|
||||
/* Sorting options */
|
||||
.sort-options{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;margin-bottom:20px;}
|
||||
.sort-opt{background:#fff;border:1px solid var(--border);border-radius:6px;padding:14px;position:relative;}
|
||||
.sort-opt.rec{border-color:var(--navy);box-shadow:0 0 0 1px var(--navy);}
|
||||
.sort-opt-rec-badge{position:absolute;top:-8px;right:10px;background:var(--navy);color:#fff;font-size:9px;font-weight:700;padding:2px 8px;border-radius:4px;letter-spacing:.05em;}
|
||||
.sort-opt h4{font-size:12px;font-weight:700;color:var(--navy);margin-bottom:6px;}
|
||||
.sort-opt p{font-size:11px;color:var(--muted);line-height:1.5;margin-bottom:8px;}
|
||||
.sort-opt code{font-family:var(--mono);font-size:10px;background:rgba(0,40,80,.06);padding:1px 4px;border-radius:2px;display:block;margin-top:6px;line-height:1.6;}
|
||||
|
||||
/* Frames */
|
||||
.frames-row{display:flex;gap:24px;flex-wrap:wrap;align-items:flex-start;margin-bottom:16px;}
|
||||
.caption{font-family:var(--mono);font-size:10px;color:var(--muted);display:block;margin-top:6px;}
|
||||
|
||||
/* Desktop frame */
|
||||
.frame-desktop{background:var(--surface);border-radius:8px;overflow:hidden;border:1px solid var(--border);box-shadow:0 4px 16px rgba(0,0,0,.08);}
|
||||
.f-nav{height:26px;background:var(--navy);display:flex;align-items:center;padding:0 8px;gap:5px;}
|
||||
.f-logo{font-size:6.5px;font-weight:700;color:#fff;letter-spacing:.7px;border-bottom:1px solid var(--mint);padding-bottom:1px;}
|
||||
.f-navlinks{display:flex;gap:5px;margin-left:8px;}
|
||||
.f-navlink{font-size:5.5px;color:rgba(255,255,255,.4);font-weight:600;text-transform:uppercase;}
|
||||
.f-navlink.on{color:rgba(255,255,255,.9);}
|
||||
.f-navr{margin-left:auto;}
|
||||
.f-av{width:14px;height:14px;border-radius:50%;background:rgba(255,255,255,.12);display:flex;align-items:center;justify-content:center;font-size:4.5px;font-weight:800;color:rgba(255,255,255,.5);}
|
||||
.f-body{padding:10px;}
|
||||
.f-search{background:#fff;border:1px solid var(--border);border-radius:4px;height:24px;display:flex;align-items:center;padding:0 8px;gap:5px;margin-bottom:5px;}
|
||||
.f-si{font-size:9px;color:var(--muted);}
|
||||
.f-st{font-size:7.5px;color:var(--subtle);flex:1;}
|
||||
.f-resume{background:var(--mint);opacity:.2;height:7px;border-radius:3px;margin-bottom:8px;}
|
||||
.f-grid-2{display:grid;grid-template-columns:1fr 155px;gap:7px;margin-bottom:7px;}
|
||||
.f-grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px;}
|
||||
.f-card{background:#fff;border:1px solid var(--sand);border-radius:3px;padding:7px;}
|
||||
.f-ht{font-size:6px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-bottom:5px;}
|
||||
.f-ht.o{color:var(--orange);}
|
||||
.f-ht.g{color:var(--green);}
|
||||
.f-ht.n{color:var(--navy);}
|
||||
.f-row{border-bottom:1px solid var(--sand);padding:3px 0;}
|
||||
.f-row:last-of-type{border-bottom:none;}
|
||||
.f-dn{font-family:Georgia,serif;font-size:7.5px;color:var(--navy);line-height:1.3;}
|
||||
.f-ds{font-size:6px;color:var(--muted);margin-top:1px;}
|
||||
.f-dd{font-size:5.5px;color:var(--subtle);margin-left:auto;white-space:nowrap;flex-shrink:0;padding-top:1px;}
|
||||
.f-lnk{font-size:6px;color:var(--navy);display:block;margin-top:5px;}
|
||||
.f-lnk.g{color:var(--green);}
|
||||
.f-stat{font-size:5.5px;color:var(--muted);margin-top:5px;}
|
||||
.f-dz{border:1.5px dashed var(--mint);background:rgba(166,218,216,.07);border-radius:3px;padding:7px;text-align:center;}
|
||||
.f-dz-i{font-size:12px;color:var(--navy);opacity:.35;margin-bottom:2px;}
|
||||
.f-dz-t{font-size:6px;font-weight:700;color:var(--navy);}
|
||||
.f-dz-s{font-size:5px;color:var(--muted);}
|
||||
.rhs{display:flex;flex-direction:column;gap:6px;}
|
||||
|
||||
/* Strip columns */
|
||||
.strip-col{border-radius:3px;padding:6px;display:flex;flex-direction:column;gap:4px;}
|
||||
.strip-col.seg{background:rgba(0,40,80,.03);border:1px solid var(--sand);}
|
||||
.strip-col.trans{background:rgba(0,40,80,.03);border:1px solid var(--sand);}
|
||||
.strip-col.done{background:rgba(166,218,216,.10);border:1px solid var(--mint);}
|
||||
.strip-col.done-empty{background:rgba(166,218,216,.06);border:1.5px dashed var(--mint);align-items:center;justify-content:center;text-align:center;min-height:100px;}
|
||||
|
||||
/* Skill pill */
|
||||
.skill-pill{display:inline-flex;align-items:center;padding:1px 5px;border-radius:8px;font-size:5px;font-weight:700;margin-bottom:3px;}
|
||||
.skill-pill.easy{background:var(--green-bg);border:1px solid rgba(46,110,57,.2);color:var(--green);}
|
||||
.skill-pill.kurrent{background:rgba(0,40,80,.08);border:1px solid rgba(0,40,80,.15);color:var(--navy);}
|
||||
|
||||
/* Pulse */
|
||||
.pulse{display:flex;align-items:center;gap:4px;margin-bottom:3px;}
|
||||
.pulse-num{font-size:5.5px;font-weight:700;}
|
||||
.pulse-num.g{color:var(--green);}
|
||||
.pulse-num.n{color:var(--navy);}
|
||||
.pulse-open{font-size:5px;color:var(--muted);}
|
||||
|
||||
/* Avatars */
|
||||
.avatars{display:flex;gap:2px;margin-bottom:4px;}
|
||||
.av-sm{width:10px;height:10px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:4px;font-weight:700;color:#fff;}
|
||||
.av-more{font-size:5px;color:var(--muted);line-height:10px;margin-left:2px;}
|
||||
|
||||
/* Per-doc bar */
|
||||
.doc-bar-row{display:flex;flex-direction:column;gap:2px;border-bottom:1px solid var(--sand);padding-bottom:4px;}
|
||||
.doc-bar-row:last-child{border-bottom:none;}
|
||||
.bar-track{flex:1;height:3px;background:rgba(0,40,80,.12);border-radius:2px;overflow:hidden;}
|
||||
.bar-fill{height:100%;background:var(--navy);border-radius:2px;}
|
||||
.bar-label{font-size:5px;color:var(--muted);white-space:nowrap;}
|
||||
|
||||
/* CTA button */
|
||||
.cta-btn{display:block;font-size:6px;font-weight:700;color:#fff;background:var(--navy);border-radius:2px;padding:3px 6px;text-align:center;margin-top:3px;}
|
||||
.cta-btn.ghost{background:transparent;color:var(--navy);border:1px solid var(--navy);}
|
||||
|
||||
/* Expert badge */
|
||||
.expert-badge{display:inline-flex;align-items:center;gap:2px;padding:1px 4px;border-radius:3px;font-size:5px;font-weight:700;background:var(--purple-bg);color:var(--purple);border:1px solid rgba(91,94,166,.2);margin-left:3px;}
|
||||
|
||||
/* Phone frame */
|
||||
.frame-phone{width:200px;flex-shrink:0;background:var(--surface);border-radius:24px;overflow:hidden;box-shadow:0 4px 20px rgba(0,0,0,.12),0 0 0 1px rgba(0,0,0,.06);display:flex;flex-direction:column;border:4px solid #1C1C18;}
|
||||
.ph-nav{height:20px;background:var(--navy);display:flex;align-items:center;padding:0 6px;}
|
||||
.ph-logo{font-size:5.5px;font-weight:700;color:#fff;letter-spacing:.6px;border-bottom:1px solid var(--mint);padding-bottom:1px;}
|
||||
.ph-body{flex:1;overflow:hidden;padding:6px;display:flex;flex-direction:column;gap:4px;}
|
||||
.ph-search{background:#fff;border:1px solid var(--border);border-radius:3px;height:18px;display:flex;align-items:center;padding:0 6px;}
|
||||
.ph-st{font-size:6.5px;color:var(--subtle);flex:1;}
|
||||
|
||||
/* 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;}
|
||||
.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;}
|
||||
|
||||
/* Component list */
|
||||
.comp-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:20px;}
|
||||
.comp-card{background:#fff;border:1px solid var(--border);border-radius:6px;padding:14px;}
|
||||
.comp-card h4{font-size:12px;font-weight:700;color:var(--navy);margin-bottom:4px;}
|
||||
.comp-card p{font-size:11px;color:var(--muted);line-height:1.5;}
|
||||
.comp-card 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">
|
||||
<h1>Mission-Control-Streifen — Finale Spec</h1>
|
||||
<div class="badges">
|
||||
<span class="badge">Issue #240</span>
|
||||
<span class="badge badge-g">Leonie Voss — UX & Accessibility</span>
|
||||
<span class="badge badge-g">15. April 2026</span>
|
||||
<span class="badge badge-g">v3 — Final</span>
|
||||
</div>
|
||||
<div class="hdr-meta">src/routes/+page.svelte · src/lib/components/DashboardMissionControl.svelte · +page.server.ts</div>
|
||||
</div>
|
||||
<div class="decision-box">
|
||||
<h2>Entscheidung</h2>
|
||||
<p class="prose">
|
||||
Der bestehende Dashboard-Aufbau (Neueste Aktivität links, DropZone + Metadaten-Widget rechts) bleibt unverändert.
|
||||
Unterhalb des Zwei-Spalten-Gitters erscheint ein neuer vollbreiter <strong>Mission-Control-Streifen</strong> mit drei
|
||||
gleichwertigen Spalten: <em>Rahmen einzeichnen</em> (Segmentierung, kein Vorwissen nötig),
|
||||
<em>Text eintippen</em> (Transkription, Kurrent hilfreich), <em>Lesefertig ✓</em> (Belohnungsbereich).
|
||||
</p>
|
||||
<p class="prose">
|
||||
Die „Transkription fehlt"-Spalte aus Issue #240 wird in Segmentierung + Transkription aufgeteilt, um
|
||||
eine klare Beitragspyramide zu schaffen: Jeder kann Rahmen einzeichnen — nicht jeder kann Kurrent lesen.
|
||||
Ein wöchentlich rotierender Sort mit <em>Experten-gesucht</em>-Escape-Hatch verhindert, dass schwer lesbare
|
||||
Dokumente dauerhaft die Spalte blockieren.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- ── PIPELINE ─────────────────────────────────────────────────────── -->
|
||||
<div class="sec">
|
||||
<div class="sec-label">Dokument-Lebenszyklus</div>
|
||||
<div class="pipeline">
|
||||
<div class="pipe-node">
|
||||
<div class="pipe-badge n1">Kein Segment</div>
|
||||
<div class="pipe-sub">0 Annotationen</div>
|
||||
<div class="pipe-col-label s">→ Spalte 1</div>
|
||||
</div>
|
||||
<div class="pipe-arrow">→</div>
|
||||
<div class="pipe-node">
|
||||
<div class="pipe-badge n2">Segmentiert</div>
|
||||
<div class="pipe-sub">Rahmen da, wenig Text</div>
|
||||
<div class="pipe-col-label t">→ Spalte 2</div>
|
||||
</div>
|
||||
<div class="pipe-arrow">→</div>
|
||||
<div class="pipe-node">
|
||||
<div class="pipe-badge n3">In Review</div>
|
||||
<div class="pipe-sub">Text da, reviewed < 90 %</div>
|
||||
<div class="pipe-col-label t">→ Spalte 2</div>
|
||||
</div>
|
||||
<div class="pipe-arrow">→</div>
|
||||
<div class="pipe-node">
|
||||
<div class="pipe-badge done">Lesefertig ✓</div>
|
||||
<div class="pipe-sub">reviewed ≥ 90 %</div>
|
||||
<div class="pipe-col-label l">→ Spalte 3</div>
|
||||
</div>
|
||||
<div style="margin-left:auto;font-size:11px;color:var(--muted);max-width:200px;line-height:1.4;">
|
||||
„Segmentiert" und „In Review" landen beide in Spalte 2 —
|
||||
unterschieden durch den per-Dokument-Balken (0 Blöcke vs. N Blöcke).
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Column definitions -->
|
||||
<div class="col-defs">
|
||||
<div class="col-def">
|
||||
<div class="col-def-title n">Spalte 1 — Rahmen einzeichnen</div>
|
||||
<p>Dokumente ohne Annotationsrahmen. Kein Kurrent nötig — Textblöcke markieren reicht.</p>
|
||||
<p><strong>Bedingung:</strong> <code>annotation_count = 0</code></p>
|
||||
<p><strong>Sort:</strong> Wöchentliche Rotation (seeded shuffle, s. u.)</p>
|
||||
<p><strong>Fortschritt:</strong> Wochenpuls „↑ +5 diese Woche", kein globaler Balken</p>
|
||||
</div>
|
||||
<div class="col-def">
|
||||
<div class="col-def-title n">Spalte 2 — Text eintippen</div>
|
||||
<p>Annotationen vorhanden, aber Text fehlt oder reviewed < 90 %. Kurrent-Kenntnisse hilfreich.</p>
|
||||
<p><strong>Bedingung:</strong> <code>annotation_count > 0 AND reviewed_pct < 0.90</code></p>
|
||||
<p><strong>Sort:</strong> Teilfortschritt zuerst, dann wöchentliche Rotation; <code>needsExpert</code>-Flagge schiebt nach hinten</p>
|
||||
<p><strong>Fortschritt:</strong> Per-Dokument-Balken „3 / 8 Blöcke"</p>
|
||||
</div>
|
||||
<div class="col-def" style="background:rgba(166,218,216,.06);border-color:var(--mint);">
|
||||
<div class="col-def-title g">Spalte 3 — Lesefertig ✓</div>
|
||||
<p>Reviewed ≥ 90 %. Keine Aufgabe — Einladung zum Lesen.</p>
|
||||
<p><strong>Bedingung:</strong> <code>reviewed_pct >= 0.90</code></p>
|
||||
<p><strong>Sort:</strong> Neueste zuerst</p>
|
||||
<p><strong>Fortschritt:</strong> „94 % geprüft" als Text — kein Balken, die mint-Spalte ist das Signal</p>
|
||||
<p><strong>Leerstand:</strong> Cross-Column-Redirect zu Spalte 1</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<!-- ── HARD DOCUMENTS PROBLEM ─────────────────────────────────────────── -->
|
||||
<div class="sec">
|
||||
<div class="sec-label">Sortierstrategie — Das „zu schwer"-Problem</div>
|
||||
<div class="sec-title">Schwer lesbare Dokumente blockieren die Spalte</div>
|
||||
<div class="sec-sub">Wenn dieselben 3 Dokumente immer oben stehen und niemand sie lesen kann, stoppt die Transkription komplett.</div>
|
||||
|
||||
<div class="callout orange">
|
||||
<div><strong class="o">Problem:</strong> Bei 1 500 Dokumenten ohne Transkription und sortiert nach <code>updated_at</code>
|
||||
können dieselben 3 besonders schwer lesbaren Dokumente dauerhaft die Spalte blockieren.
|
||||
Jeder öffnet sie, gibt auf, und die Spalte wird zur Sackgasse.</div>
|
||||
</div>
|
||||
|
||||
<div class="sort-options">
|
||||
<!-- Option 1 -->
|
||||
<div class="sort-opt">
|
||||
<h4>Option 1 — Zufällig pro Seitenaufruf</h4>
|
||||
<p><code>ORDER BY RANDOM()</code></p>
|
||||
<p>Jeder Besuch zeigt andere Dokumente. Kein Aufwand, aber chaotisch — kein Nutzer sieht ein Dokument zweimal,
|
||||
kann nicht gezielt zurückkehren.</p>
|
||||
<div class="tag-list"><span class="tag t-g">+ Null Aufwand</span><span class="tag t-o">− Chaotisch</span><span class="tag t-o">− Kein stabiles Lesezeichen</span></div>
|
||||
</div>
|
||||
<!-- Option 2 — RECOMMENDED -->
|
||||
<div class="sort-opt rec">
|
||||
<div class="sort-opt-rec-badge">★ Empfohlen</div>
|
||||
<h4>Option 2 — Teilfortschritt + wöchentliche Rotation</h4>
|
||||
<p>Dokumente mit Teilfortschritt (3/8 Blöcke) erscheinen zuerst — am ehesten abschließbar. Dokumente mit 0 Blöcken rotieren wöchentlich durch einen deterministischen Shuffle.</p>
|
||||
<code>ORDER BY textedBlocks DESC,
|
||||
HASHTEXT(id || EXTRACT(WEEK FROM NOW())::text)</code>
|
||||
<div class="tag-list" style="margin-top:8px;"><span class="tag t-g">+ Konsistent innerhalb einer Woche</span><span class="tag t-g">+ Bringt leichte Dokumente an die Oberfläche</span><span class="tag t-g">+ Kein neues Datenbankfeld</span></div>
|
||||
</div>
|
||||
<!-- Option 3 -->
|
||||
<div class="sort-opt">
|
||||
<h4>Option 3 — Manuelle Schwierigkeitsbewertung</h4>
|
||||
<p>Beitragende bewerten Dokumente 1–3 nach Versuch. Einfache Dokumente erscheinen zuerst.</p>
|
||||
<p>Beste Langzeitlösung — braucht aber Bewertungs-UI auf der Enrich-Seite und Signalakkumulation.</p>
|
||||
<div class="tag-list"><span class="tag t-g">+ Selbstverbessernd</span><span class="tag t-o">− UI-Aufwand</span><span class="tag t-o">− Braucht Zeit bis Signal</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Escape hatch -->
|
||||
<div class="callout navy">
|
||||
<div>
|
||||
<strong class="n">Escape-Hatch: „Experten gesucht"-Flagge (Option 2 ergänzen)</strong><br/>
|
||||
Im Enrich-Bereich: ein einzelner Button „Zu schwer — Hilfe gesucht".
|
||||
Setzt <code>Document.needsExpert = true</code> (1 Boolean, keine Migration wenn Flyway-Migration V{n} hinzugefügt wird).
|
||||
In der Transkriptions-Spalte zeigen flagged Dokumente einen lila Badge und werden hinter unflagged Dokumenten einsortiert.
|
||||
Kein Leaderboard, keine Scham — nur ein ehrliches Signal an die Community.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Expert badge mockup -->
|
||||
<div style="background:#fff;border:1px solid var(--border);border-radius:6px;padding:16px;margin-bottom:16px;">
|
||||
<div style="font-size:10px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-bottom:10px;">Mockup: Experten-gesucht-Badge in der Transkriptions-Zeile</div>
|
||||
<div style="display:flex;flex-direction:column;gap:4px;max-width:380px;">
|
||||
<!-- Normal doc -->
|
||||
<div style="display:flex;flex-direction:column;gap:3px;padding:8px;border:1px solid var(--sand);border-radius:3px;">
|
||||
<div style="font-family:Georgia,serif;font-size:13px;color:var(--navy);">Reisepass Opa Heinrich <span style="font-family:system-ui;font-size:10px;font-weight:600;background:rgba(0,40,80,.07);color:var(--navy);padding:1px 6px;border-radius:4px;">3 / 8 Blöcke</span></div>
|
||||
<div style="display:flex;align-items:center;gap:6px;">
|
||||
<div style="flex:1;height:4px;background:rgba(0,40,80,.12);border-radius:2px;overflow:hidden;"><div style="width:37%;height:100%;background:var(--navy);border-radius:2px;"></div></div>
|
||||
<div style="font-size:11px;color:var(--muted);">37 %</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Expert-needed doc -->
|
||||
<div style="display:flex;flex-direction:column;gap:3px;padding:8px;border:1px solid rgba(91,94,166,.25);background:rgba(91,94,166,.03);border-radius:3px;">
|
||||
<div style="font-family:Georgia,serif;font-size:13px;color:var(--navy);">Standesamt Breslau 1872
|
||||
<span style="font-family:system-ui;font-size:10px;font-weight:600;background:var(--purple-bg);color:var(--purple);padding:1px 6px;border-radius:4px;border:1px solid rgba(91,94,166,.2);">Experten gesucht</span>
|
||||
</div>
|
||||
<div style="font-size:11px;color:var(--muted);">Schrift besonders schwer lesbar — Hilfe willkommen</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="impl-ref">
|
||||
<table>
|
||||
<thead><tr><th>Element</th><th>SQL / Tailwind</th><th>Wert</th><th>Hinweis</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Sort Transkription</td><td><code>ORDER BY textedBlocks DESC, HASHTEXT(id::text || EXTRACT(WEEK FROM NOW())::int::text)</code></td><td>—</td><td>Kein neues Feld nötig; ändert sich automatisch jede Woche</td></tr>
|
||||
<tr><td><code>needsExpert</code>-Flag</td><td><code>ALTER TABLE documents ADD COLUMN needs_expert BOOLEAN NOT NULL DEFAULT FALSE</code></td><td>Flyway <code>V{n}__add_needs_expert.sql</code></td><td>Flagged Docs ans Ende: <code>ORDER BY needs_expert ASC, ...</code></td></tr>
|
||||
<tr><td>Experten-Badge</td><td><code>inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-purple-50 border border-purple-200 text-purple-700</code></td><td>Kontrast 6,8:1 ✓</td><td>Nur wenn <code>doc.needsExpert === true</code></td></tr>
|
||||
<tr><td>„Zu schwer"-Button (Enrich)</td><td><code>text-xs text-gray-400 hover:text-gray-600 underline underline-offset-2</code></td><td>—</td><td>Unscheinbar — kein roter Knopf, keine Scham</td></tr>
|
||||
<tr><td>Endpoint (Flagge setzen)</td><td><code>PATCH /api/documents/{id}/needs-expert</code></td><td><code>@RequirePermission(READ_ALL)</code></td><td>Jeder angemeldete Nutzer darf flaggen</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<!-- ── DESKTOP MOCKUP — FILLED STATE ─────────────────────────────────── -->
|
||||
<div class="sec">
|
||||
<div class="sec-label">Mockup — Desktop, normaler Zustand</div>
|
||||
|
||||
<div class="frames-row">
|
||||
<div style="flex:1;min-width:0;">
|
||||
<div class="frame-desktop">
|
||||
<div class="f-nav">
|
||||
<div class="f-logo">FAMILIENARCHIV</div>
|
||||
<div class="f-navlinks"><div class="f-navlink on">Archiv</div><div class="f-navlink">Personen</div><div class="f-navlink">Gespräche</div></div>
|
||||
<div class="f-navr"><div class="f-av">MR</div></div>
|
||||
</div>
|
||||
<div class="f-body">
|
||||
<div class="f-search"><div class="f-si">⌕</div><div class="f-st">Dokumente durchsuchen…</div></div>
|
||||
<div class="f-resume"></div>
|
||||
|
||||
<!-- Existing grid — unchanged -->
|
||||
<div class="f-grid-2">
|
||||
<div class="f-card">
|
||||
<div class="f-ht">Neueste Aktivität</div>
|
||||
<div class="f-row" style="display:flex;"><div><div class="f-dn">Brief von Oma Martha, 1943</div><div class="f-ds">Karl Raddatz</div></div><div class="f-dd">12. Apr</div></div>
|
||||
<div class="f-row" style="display:flex;"><div><div class="f-dn">Taufurkunde Karl Raddatz</div><div class="f-ds">Standesamt</div></div><div class="f-dd">9. Apr</div></div>
|
||||
<div class="f-row" style="display:flex;"><div><div class="f-dn">Postkarte aus Breslau</div><div class="f-ds">Martha Raddatz</div></div><div class="f-dd">7. Apr</div></div>
|
||||
<div class="f-row" style="display:flex;"><div><div class="f-dn">Familienfoto Sommer 1952</div><div class="f-ds">Unbekannt</div></div><div class="f-dd">3. Apr</div></div>
|
||||
<div class="f-stat">47 Dokumente · 12 Personen</div>
|
||||
</div>
|
||||
<div class="rhs">
|
||||
<div class="f-dz"><div class="f-dz-i">↑</div><div class="f-dz-t">Datei hochladen</div><div class="f-dz-s">Drag & Drop</div></div>
|
||||
<div class="f-card" style="flex:1;">
|
||||
<div class="f-ht o">Metadaten fehlen</div>
|
||||
<div class="f-row"><div class="f-dn">Familienfoto 1952</div><div class="f-ds">Titel fehlt</div></div>
|
||||
<div class="f-row"><div class="f-dn">Standesamtsurkunde</div><div class="f-ds">Datum fehlt</div></div>
|
||||
<a class="f-lnk">Alle 5 anzeigen →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ★ Mission Control Strip -->
|
||||
<div style="background:#fff;border:1px solid var(--sand);border-radius:3px;padding:8px;">
|
||||
<div class="f-ht" style="margin-bottom:7px;">Was braucht Aufmerksamkeit?</div>
|
||||
<div class="f-grid-3">
|
||||
|
||||
<!-- Col 1: SEGMENTIERUNG -->
|
||||
<div class="strip-col seg">
|
||||
<div>
|
||||
<div class="f-ht n" style="margin-bottom:2px;">Rahmen einzeichnen</div>
|
||||
<div class="skill-pill easy">✓ Ohne Vorkenntnisse</div>
|
||||
<div class="pulse"><span class="pulse-num g">↑ +5 diese Woche</span><span class="pulse-open">· 1 480 offen</span></div>
|
||||
<div class="avatars">
|
||||
<div class="av-sm" style="background:var(--navy);">MR</div>
|
||||
<div class="av-sm" style="background:var(--purple);">TG</div>
|
||||
<div class="av-sm" style="background:#8C6E3F;">AS</div>
|
||||
<div class="av-more">+ 2</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="f-row"><div class="f-dn">Taufurkunde Karl R.</div><div class="f-ds">Noch keine Rahmen</div></div>
|
||||
<div class="f-row"><div class="f-dn">Standesamt 1889</div><div class="f-ds">Noch keine Rahmen</div></div>
|
||||
<div class="f-row"><div class="f-dn">Heiratsurkunde 1921</div><div class="f-ds">Noch keine Rahmen</div></div>
|
||||
<a class="cta-btn">Jetzt einzeichnen →</a>
|
||||
</div>
|
||||
|
||||
<!-- Col 2: TRANSKRIPTION with per-doc bar + expert badge -->
|
||||
<div class="strip-col trans">
|
||||
<div>
|
||||
<div class="f-ht n" style="margin-bottom:2px;">Text eintippen</div>
|
||||
<div class="skill-pill kurrent">Kurrent hilfreich</div>
|
||||
<div class="pulse"><span class="pulse-num n">↑ +2 diese Woche</span><span class="pulse-open">· 8 offen</span></div>
|
||||
<div class="avatars">
|
||||
<div class="av-sm" style="background:var(--navy);">MR</div>
|
||||
<div class="av-more">1 Person</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Per-document bar — partial progress first -->
|
||||
<div class="doc-bar-row">
|
||||
<div class="f-dn">Reisepass Opa Heinrich</div>
|
||||
<div style="display:flex;align-items:center;gap:3px;"><div class="bar-track"><div class="bar-fill" style="width:37%;"></div></div><div class="bar-label">3 / 8 Blöcke</div></div>
|
||||
</div>
|
||||
<div class="doc-bar-row">
|
||||
<div class="f-dn">Brief v. Oma Martha 1943</div>
|
||||
<div style="display:flex;align-items:center;gap:3px;"><div class="bar-track"><div class="bar-fill" style="width:0%;"></div></div><div class="bar-label">0 / 6 Blöcke</div></div>
|
||||
</div>
|
||||
<!-- Expert-needed doc — sorted last -->
|
||||
<div class="doc-bar-row" style="border-color:rgba(91,94,166,.2);background:rgba(91,94,166,.03);padding:2px 3px;">
|
||||
<div style="display:flex;align-items:center;flex-wrap:wrap;gap:2px;"><div class="f-dn">Standesamt Breslau 1872</div><span class="expert-badge">Experten gesucht</span></div>
|
||||
<div class="f-ds">Schrift besonders schwer lesbar</div>
|
||||
</div>
|
||||
<a class="cta-btn">Jetzt tippen →</a>
|
||||
</div>
|
||||
|
||||
<!-- Col 3: LESEFERTIG — filled -->
|
||||
<div class="strip-col done">
|
||||
<div>
|
||||
<div class="f-ht g" style="margin-bottom:2px;">Lesefertig ✓</div>
|
||||
<div style="font-size:5.5px;color:var(--green);font-weight:600;margin-bottom:4px;">3 Dokumente bereit</div>
|
||||
<div class="avatars">
|
||||
<div class="av-sm" style="background:var(--green);">MR</div>
|
||||
<div class="av-sm" style="background:var(--purple);">TG</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="doc-bar-row" style="border-color:rgba(166,218,216,.4);">
|
||||
<div class="f-dn">Postkarte aus Breslau 1943</div>
|
||||
<div style="font-size:5.5px;color:var(--green);font-weight:600;">100 % geprüft</div>
|
||||
</div>
|
||||
<div class="doc-bar-row" style="border-color:rgba(166,218,216,.4);">
|
||||
<div class="f-dn">Brief Oma Martha 1938</div>
|
||||
<div style="font-size:5.5px;color:var(--green);font-weight:600;">95 % geprüft</div>
|
||||
</div>
|
||||
<div class="doc-bar-row" style="border-color:rgba(166,218,216,.4);">
|
||||
<div class="f-dn">Heiratsurkunde 1921</div>
|
||||
<div style="font-size:5.5px;color:var(--green);font-weight:600;">91 % geprüft</div>
|
||||
</div>
|
||||
<a class="f-lnk g" style="margin-top:3px;">Alle 3 lesen →</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="caption">Desktop (55 %) — normaler Zustand: Teilfortschritt oben, Experten-gesucht-Dokument unten in Spalte 2</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── DESKTOP MOCKUP — EARLY STATE (Lesefertig leer) ───────────────── -->
|
||||
<div class="sec">
|
||||
<div class="sec-label">Mockup — Desktop, frühe Projektphase (Lesefertig leer)</div>
|
||||
|
||||
<div class="frames-row">
|
||||
<div style="flex:1;min-width:0;">
|
||||
<div class="frame-desktop">
|
||||
<div class="f-nav">
|
||||
<div class="f-logo">FAMILIENARCHIV</div>
|
||||
<div class="f-navlinks"><div class="f-navlink on">Archiv</div><div class="f-navlink">Personen</div></div>
|
||||
<div class="f-navr"><div class="f-av">MR</div></div>
|
||||
</div>
|
||||
<div class="f-body">
|
||||
<div class="f-search"><div class="f-si">⌕</div><div class="f-st">Dokumente durchsuchen…</div></div>
|
||||
<div class="f-resume"></div>
|
||||
<div class="f-grid-2">
|
||||
<div class="f-card">
|
||||
<div class="f-ht">Neueste Aktivität</div>
|
||||
<div class="f-row" style="display:flex;"><div><div class="f-dn">Brief von Oma Martha, 1943</div></div><div class="f-dd">12. Apr</div></div>
|
||||
<div class="f-row" style="display:flex;"><div><div class="f-dn">Taufurkunde Karl Raddatz</div></div><div class="f-dd">9. Apr</div></div>
|
||||
<div class="f-stat">1 500 Dokumente · 12 Personen</div>
|
||||
</div>
|
||||
<div class="rhs">
|
||||
<div class="f-dz"><div class="f-dz-i">↑</div><div class="f-dz-t">Datei hochladen</div><div class="f-dz-s">Drag & Drop</div></div>
|
||||
<div class="f-card" style="flex:1;">
|
||||
<div class="f-ht o">Metadaten fehlen</div>
|
||||
<div class="f-row"><div class="f-dn">Familienfoto 1952</div></div>
|
||||
<div class="f-row"><div class="f-dn">Standesamtsurkunde</div></div>
|
||||
<a class="f-lnk">Alle anzeigen →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="background:#fff;border:1px solid var(--sand);border-radius:3px;padding:8px;">
|
||||
<div class="f-ht" style="margin-bottom:7px;">Was braucht Aufmerksamkeit?</div>
|
||||
<div class="f-grid-3">
|
||||
<div class="strip-col seg">
|
||||
<div>
|
||||
<div class="f-ht n" style="margin-bottom:2px;">Rahmen einzeichnen</div>
|
||||
<div class="skill-pill easy">✓ Ohne Vorkenntnisse</div>
|
||||
<div class="pulse"><span class="pulse-num g">↑ +3 diese Woche</span><span class="pulse-open">· 1 498 offen</span></div>
|
||||
<div class="avatars"><div class="av-sm" style="background:var(--navy);">MR</div><div class="av-more">1 Person</div></div>
|
||||
</div>
|
||||
<div class="f-row"><div class="f-dn">Taufurkunde Karl R.</div></div>
|
||||
<div class="f-row"><div class="f-dn">Standesamt 1889</div></div>
|
||||
<div class="f-row"><div class="f-dn">Heiratsurkunde 1921</div></div>
|
||||
<a class="cta-btn">Jetzt einzeichnen →</a>
|
||||
</div>
|
||||
<div class="strip-col trans">
|
||||
<div>
|
||||
<div class="f-ht n" style="margin-bottom:2px;">Text eintippen</div>
|
||||
<div class="skill-pill kurrent">Kurrent hilfreich</div>
|
||||
<div class="pulse"><span class="pulse-num n">↑ +1 diese Woche</span><span class="pulse-open">· 2 offen</span></div>
|
||||
<div class="avatars"><div class="av-sm" style="background:var(--navy);">MR</div><div class="av-more">1 Person</div></div>
|
||||
</div>
|
||||
<div class="doc-bar-row">
|
||||
<div class="f-dn">Brief v. Oma Martha 1943</div>
|
||||
<div style="display:flex;align-items:center;gap:3px;"><div class="bar-track"><div class="bar-fill" style="width:0%;"></div></div><div class="bar-label">0 / 6 Blöcke</div></div>
|
||||
</div>
|
||||
<div class="doc-bar-row">
|
||||
<div class="f-dn">Reisepass Opa Heinrich</div>
|
||||
<div style="display:flex;align-items:center;gap:3px;"><div class="bar-track"><div class="bar-fill" style="width:0%;"></div></div><div class="bar-label">0 / 4 Blöcke</div></div>
|
||||
</div>
|
||||
<a class="cta-btn">Jetzt tippen →</a>
|
||||
</div>
|
||||
<!-- Lesefertig EMPTY — cross-column redirect -->
|
||||
<div class="strip-col done-empty">
|
||||
<div style="font-size:11px;color:var(--mint);margin-bottom:3px;">✦</div>
|
||||
<div style="font-size:6.5px;font-weight:700;color:var(--navy);margin-bottom:3px;">Noch kein Dokument lesefertig</div>
|
||||
<div style="font-size:5.5px;color:var(--muted);line-height:1.5;max-width:105px;margin-bottom:5px;">Erscheint hier sobald die Transkription abgeschlossen ist.</div>
|
||||
<a class="cta-btn ghost" style="font-size:5.5px;padding:2px 7px;">Jetzt mithelfen →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="caption">Desktop (55 %) — frühe Phase: 1 500 Dokumente ohne Transkription, Wochenpuls zeigt Schwung statt Berg</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<!-- ── MOBILE MOCKUP ─────────────────────────────────────────────────── -->
|
||||
<div class="sec">
|
||||
<div class="sec-label">Mockup — Mobil 320 px</div>
|
||||
<p class="prose" style="margin-bottom:16px;">
|
||||
Die rechte Spalte (DropZone + Metadaten) erscheint auf Mobil zuerst im DOM (<code>lg:order-last</code> schiebt sie auf Desktop nach rechts).
|
||||
Der Streifen stapelt seine drei Spalten vertikal. Jede Spalte hat volle Breite — keine Overflow-Probleme.
|
||||
</p>
|
||||
|
||||
<div class="frames-row">
|
||||
<!-- Phone: filled state -->
|
||||
<div>
|
||||
<div class="frame-phone" style="height:620px;">
|
||||
<div class="ph-nav"><div class="ph-logo">FAMILIENARCHIV</div></div>
|
||||
<div class="ph-body" style="overflow-y:auto;">
|
||||
<div class="ph-search"><div class="ph-st">⌕ Dokumente…</div></div>
|
||||
<!-- Right col first on mobile -->
|
||||
<div class="f-dz" style="padding:5px;"><div class="f-dz-i" style="font-size:10px;">↑</div><div class="f-dz-t">Hochladen</div></div>
|
||||
<div class="f-card" style="padding:5px;">
|
||||
<div class="f-ht o">Metadaten fehlen</div>
|
||||
<div class="f-row"><div class="f-dn">Familienfoto 1952</div></div>
|
||||
<div class="f-row"><div class="f-dn">Standesamtsurkunde</div></div>
|
||||
</div>
|
||||
<!-- Left col (recent) -->
|
||||
<div class="f-card" style="padding:5px;">
|
||||
<div class="f-ht">Neueste Aktivität</div>
|
||||
<div class="f-row"><div class="f-dn">Brief von Oma Martha</div></div>
|
||||
<div class="f-row"><div class="f-dn">Taufurkunde Karl R.</div></div>
|
||||
<div class="f-stat">1 500 Dok. · 12 Pers.</div>
|
||||
</div>
|
||||
<!-- Strip — stacked on mobile -->
|
||||
<div style="background:#fff;border:1px solid var(--sand);border-radius:3px;padding:5px;display:flex;flex-direction:column;gap:4px;">
|
||||
<div class="f-ht" style="margin-bottom:3px;">Was braucht Aufmerksamkeit?</div>
|
||||
<!-- Seg -->
|
||||
<div class="strip-col seg" style="padding:5px;">
|
||||
<div class="f-ht n" style="margin-bottom:1px;">Rahmen einzeichnen</div>
|
||||
<div class="skill-pill easy">✓ Ohne Vorkenntnisse</div>
|
||||
<div class="pulse" style="margin-bottom:2px;"><span class="pulse-num g">↑ +5 diese Woche</span><span class="pulse-open">· 1 480 offen</span></div>
|
||||
<div class="f-row"><div class="f-dn">Taufurkunde Karl R.</div></div>
|
||||
<div class="f-row"><div class="f-dn">Standesamt 1889</div></div>
|
||||
<a class="cta-btn" style="font-size:6px;">Jetzt einzeichnen →</a>
|
||||
</div>
|
||||
<!-- Trans -->
|
||||
<div class="strip-col trans" style="padding:5px;">
|
||||
<div class="f-ht n" style="margin-bottom:1px;">Text eintippen</div>
|
||||
<div class="skill-pill kurrent">Kurrent hilfreich</div>
|
||||
<div class="pulse" style="margin-bottom:2px;"><span class="pulse-num n">↑ +2 diese Woche</span><span class="pulse-open">· 8 offen</span></div>
|
||||
<div class="doc-bar-row">
|
||||
<div class="f-dn">Reisepass Opa Heinrich</div>
|
||||
<div style="display:flex;align-items:center;gap:3px;"><div class="bar-track"><div class="bar-fill" style="width:37%;"></div></div><div class="bar-label">3 / 8 Blöcke</div></div>
|
||||
</div>
|
||||
<div class="doc-bar-row">
|
||||
<div class="f-dn">Brief v. Oma Martha 1943</div>
|
||||
<div style="display:flex;align-items:center;gap:3px;"><div class="bar-track"><div class="bar-fill" style="width:0%;"></div></div><div class="bar-label">0 / 6 Blöcke</div></div>
|
||||
</div>
|
||||
<a class="cta-btn" style="font-size:6px;">Jetzt tippen →</a>
|
||||
</div>
|
||||
<!-- Lesefertig -->
|
||||
<div class="strip-col done" style="padding:5px;">
|
||||
<div class="f-ht g" style="margin-bottom:1px;">Lesefertig ✓</div>
|
||||
<div style="font-size:5.5px;color:var(--green);font-weight:600;margin-bottom:3px;">3 bereit</div>
|
||||
<div class="doc-bar-row" style="border-color:rgba(166,218,216,.4);"><div class="f-dn">Postkarte 1943</div><div style="font-size:5.5px;color:var(--green);font-weight:600;">100 %</div></div>
|
||||
<div class="doc-bar-row" style="border-color:rgba(166,218,216,.4);"><div class="f-dn">Brief Oma 1938</div><div style="font-size:5.5px;color:var(--green);font-weight:600;">95 %</div></div>
|
||||
<a class="f-lnk g">Alle lesen →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="caption">Mobil 320 px — Streifen stapelt vertikal, volle Breite je Spalte</span>
|
||||
</div>
|
||||
|
||||
<!-- Mobile layout notes -->
|
||||
<div style="flex:1;min-width:220px;">
|
||||
<div style="background:#fff;border:1px solid var(--border);border-radius:6px;padding:16px;margin-bottom:12px;">
|
||||
<div style="font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--navy);margin-bottom:8px;">Mobile-Reihenfolge (DOM)</div>
|
||||
<ol style="font-size:12px;color:var(--muted);line-height:1.8;margin-left:16px;">
|
||||
<li>Suchleiste</li>
|
||||
<li>DropZone (write users only)</li>
|
||||
<li>Metadaten fehlen</li>
|
||||
<li>Neueste Aktivität</li>
|
||||
<li>Was braucht Aufmerksamkeit?
|
||||
<ol style="margin-left:16px;">
|
||||
<li>Rahmen einzeichnen</li>
|
||||
<li>Text eintippen</li>
|
||||
<li>Lesefertig ✓</li>
|
||||
</ol>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="callout navy">
|
||||
<div>
|
||||
<strong class="n">Touch targets:</strong> Alle CTA-Buttons: <code>min-h-[44px]</code> (WCAG 2.2).
|
||||
Dokument-Zeilen in den Spalten: <code>min-h-[44px] py-2</code>.
|
||||
Der „Zu schwer"-Button auf der Enrich-Seite: <code>min-h-[44px]</code> als Icon-Button mit <code>aria-label</code>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<!-- ── ENGAGEMENT FEATURES SUMMARY ──────────────────────────────────── -->
|
||||
<div class="sec">
|
||||
<div class="sec-label">Engagement-Elemente — Zusammenfassung</div>
|
||||
<div class="comp-grid">
|
||||
<div class="comp-card">
|
||||
<h4>① Skill-Pill</h4>
|
||||
<p>Unter jedem Spaltentitel. „Ohne Vorkenntnisse" (grün) vs. „Kurrent hilfreich" (navy-neutral).
|
||||
Senkt die Hemmschwelle — Neueinsteiger sehen sofort, was ohne Kurrent-Kenntnisse möglich ist.</p>
|
||||
<p style="margin-top:6px;"><code>bg-green-50 border-green-200 text-green-800</code> / <code>bg-surface border-line text-ink</code></p>
|
||||
</div>
|
||||
<div class="comp-card">
|
||||
<h4>② Wochenpuls</h4>
|
||||
<p>„↑ +5 diese Woche · 1 480 offen" statt globalem Fortschrittsbalken.
|
||||
Zeigt Schwung, nicht den Berg. Psychologisch: 0,8 %-Balken ist demotivierender als kein Balken.</p>
|
||||
<p style="margin-top:6px;"><code>SELECT COUNT(*) WHERE created_at > NOW() - INTERVAL '7 days'</code></p>
|
||||
</div>
|
||||
<div class="comp-card">
|
||||
<h4>③ Per-Dokument-Balken</h4>
|
||||
<p>Nur in Spalte 2, nur wenn <code>annotation_count > 0</code>. Richtiger Maßstab:
|
||||
8 Blöcke sind in einer Sitzung abschließbar. Zeigt auch, welche Dokumente „fast fertig" sind.</p>
|
||||
<p style="margin-top:6px;"><code>width: {textedBlocks / totalBlocks * 100}%</code>; Guard: <code>totalBlocks === 0 → width: 0</code></p>
|
||||
</div>
|
||||
<div class="comp-card">
|
||||
<h4>④ Contributor-Avatare</h4>
|
||||
<p>Max. 3 Initialen-Bubbles der letzten Beitragenden pro Spalte. Kein Leaderboard (Wettbewerb) —
|
||||
soziale Sichtbarkeit (Zugehörigkeit). Farbe deterministisch aus User-ID-Hash.</p>
|
||||
<p style="margin-top:6px;">DTO: <code>lastContributors: [{initials, colorIndex}]</code> — nur Initialen, keine Namen (Nora)</p>
|
||||
</div>
|
||||
<div class="comp-card">
|
||||
<h4>⑤ „Starte hier →"-CTA</h4>
|
||||
<p>Ein einziger opinionated Button je Aufgaben-Spalte, der direkt zum nächsten Dokument springt.
|
||||
Entscheidungslähmung ist der Hauptgrund für Non-Participation bei Familienprojekten.</p>
|
||||
<p style="margin-top:6px;"><code>/enrich?filter=NEEDS_SEGMENTATION&next=1</code> (Segmentierung)<br/><code>/enrich?filter=NEEDS_TRANSCRIPTION&next=1</code> (Transkription)</p>
|
||||
</div>
|
||||
<div class="comp-card">
|
||||
<h4>⑥ Lesefertig-Leerstand → Redirect</h4>
|
||||
<p>Wenn Spalte 3 leer ist (frühe Phase), erscheint kein toter Endpunkt sondern:
|
||||
„Erscheint hier, sobald die Transkription abgeschlossen ist — jetzt mithelfen →".
|
||||
Der Link springt zu Spalte 1.</p>
|
||||
<p style="margin-top:6px;"><code>{#if readyToRead.length === 0}</code> → <code>DashboardReadyToReadEmpty.svelte</code></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<!-- ── IMPL-REF TABLE ────────────────────────────────────────────────── -->
|
||||
<div class="sec">
|
||||
<div class="sec-label">Implementation Reference</div>
|
||||
|
||||
<div class="impl-ref">
|
||||
<table>
|
||||
<thead><tr><th>Element</th><th>Tailwind-Klassen</th><th>Pixel / Wert</th><th>Hinweis</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><strong>Streifen-Wrapper</strong></td><td><code>mt-4 bg-white border border-line rounded-sm p-6</code></td><td>padding 24 px</td><td>Direkt nach bestehendem <code>div.mt-4.grid</code></td></tr>
|
||||
<tr><td>Streifen-Titel</td><td><code>text-xs font-bold uppercase tracking-widest text-gray-400 mb-4</code></td><td>12 px / 700</td><td>Standard-Section-Title-Muster</td></tr>
|
||||
<tr><td>3-Spalten-Grid</td><td><code>grid grid-cols-1 gap-4 sm:grid-cols-3</code></td><td>gap 16 px</td><td>sm = 640 px; darunter stapeln</td></tr>
|
||||
<tr><td>Segmentierung-Spalte</td><td><code>bg-surface rounded-sm border border-line p-4 flex flex-col gap-3</code></td><td>—</td><td>Neutral</td></tr>
|
||||
<tr><td>Transkription-Spalte</td><td><code>bg-surface rounded-sm border border-line p-4 flex flex-col gap-3</code></td><td>—</td><td>Neutral — es ist eine Aufgabe</td></tr>
|
||||
<tr><td>Lesefertig-Spalte (gefüllt)</td><td><code>bg-mint/10 rounded-sm border border-mint p-4 flex flex-col gap-3</code></td><td>—</td><td>Mint-Ton = Erfolg</td></tr>
|
||||
<tr><td>Lesefertig-Spalte (leer)</td><td><code>flex flex-col items-center justify-center text-center bg-mint/5 border border-dashed border-mint rounded-sm p-6 min-h-[120px]</code></td><td>min-h 120 px</td><td>Kein toter Endpunkt</td></tr>
|
||||
<tr><td>Skill-Pill easy</td><td><code>inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-semibold bg-green-50 border border-green-200 text-green-800</code></td><td>Kontrast 9,7:1 ✓ AAA</td><td>—</td></tr>
|
||||
<tr><td>Skill-Pill kurrent</td><td><code>inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-semibold bg-surface border border-line text-ink</code></td><td>Kontrast 14,5:1 ✓ AAA</td><td>Neutral — kein Abschreck-Signal</td></tr>
|
||||
<tr><td>Wochenpuls-Zahl</td><td><code>text-xs font-semibold text-green-700</code> (Seg.) / <code>text-ink</code> (Trans.)</td><td>12 px</td><td>Kein globaler Balken</td></tr>
|
||||
<tr><td>Per-Dokument-Track</td><td><code>flex-1 h-1 bg-navy/20 rounded-full overflow-hidden</code></td><td>h 4 px</td><td>Nur wenn <code>annotation_count > 0</code></td></tr>
|
||||
<tr><td>Per-Dokument-Fill</td><td><code>h-full bg-ink rounded-full transition-all</code> + <code>style="width:{pct}%"</code></td><td>—</td><td>Guard: <code>totalBlocks === 0 → 0%</code></td></tr>
|
||||
<tr><td>Lesefertig-Prozent</td><td><code>text-xs font-semibold text-green-800</code></td><td>12 px</td><td>Kein Balken — mint-Spalte ist das Signal</td></tr>
|
||||
<tr><td>Contributor-Avatar</td><td><code>w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-bold text-white shrink-0</code></td><td>24 × 24 px</td><td>Farbe: 6 Werte, Index = <code>userIdHash % 6</code></td></tr>
|
||||
<tr><td>CTA-Button (primär)</td><td><code>block w-full text-center text-xs font-semibold text-white bg-ink rounded-sm py-2 mt-2 hover:bg-ink-2 transition-colors focus-visible:ring-2 focus-visible:ring-ink focus-visible:ring-offset-1</code></td><td>min-h 36 px</td><td><code>aria-label</code> mit Dokumenttitel falls nötig</td></tr>
|
||||
<tr><td>CTA-Button (ghost, Leerstand)</td><td><code>inline-flex items-center text-xs font-semibold text-ink border border-ink rounded-sm px-3 py-2 hover:bg-ink hover:text-white transition-colors</code></td><td>min-h 36 px</td><td>—</td></tr>
|
||||
<tr><td>Experten-gesucht-Badge</td><td><code>inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-purple-50 border border-purple-200 text-purple-700</code></td><td>Kontrast 6,8:1 ✓ AA</td><td>Nur wenn <code>doc.needsExpert === true</code></td></tr>
|
||||
<tr><td>Sichtbarkeit Streifen</td><td><code>{#if needsSegmentation.length > 0 || needsTranscription.length > 0 || readyToRead.length > 0}</code></td><td>—</td><td>Streifen verschwindet wenn alle drei Buckets leer</td></tr>
|
||||
<tr><td>Dokument-Zeile Mindesthöhe</td><td><code>min-h-[44px] flex items-start py-2</code></td><td>44 px ✓ WCAG 2.2</td><td>Gilt für alle klickbaren Zeilen</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<!-- ── BACKEND CONTRACTS ─────────────────────────────────────────────── -->
|
||||
<div class="sec">
|
||||
<div class="sec-label">Backend — neue Endpoints & Queries</div>
|
||||
<div class="impl-ref">
|
||||
<table>
|
||||
<thead><tr><th>Endpoint / Query</th><th>Bedingung</th><th>Sort</th><th>Auth</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>GET /api/documents/needs-segmentation?size=3</code></td><td><code>NOT EXISTS (SELECT 1 FROM document_annotations WHERE document_id = d.id)</code></td><td><code>HASHTEXT(id::text || week::text)</code></td><td><code>READ_ALL</code></td></tr>
|
||||
<tr><td><code>GET /api/documents/needs-transcription?size=3</code></td><td><code>EXISTS annotation AND (no blocks OR reviewed_pct < 0.90)</code></td><td><code>textedBlocks DESC, needs_expert ASC, HASHTEXT(...)</code></td><td><code>READ_ALL</code></td></tr>
|
||||
<tr><td><code>GET /api/documents/ready-to-read?size=3</code></td><td><code>reviewed_pct >= 0.90</code></td><td><code>updated_at DESC</code></td><td><code>READ_ALL</code></td></tr>
|
||||
<tr><td><code>PATCH /api/documents/{id}/needs-expert</code></td><td>Setzt <code>needs_expert = true</code></td><td>—</td><td><code>READ_ALL</code> (jeder Nutzer darf flaggen)</td></tr>
|
||||
<tr><td><code>GET /api/stats/strip-activity</code></td><td>Wochenpuls: <code>COUNT(*) WHERE created_at > NOW() - INTERVAL '7 days'</code> pro Bucket</td><td>—</td><td><code>READ_ALL</code></td></tr>
|
||||
<tr><td>Flyway-Migration</td><td><code>ALTER TABLE documents ADD COLUMN needs_expert BOOLEAN NOT NULL DEFAULT FALSE</code></td><td>—</td><td>V{n}__add_needs_expert_flag.sql</td></tr>
|
||||
<tr><td>Index prüfen (Tobias)</td><td><code>document_annotations(document_id)</code>, <code>transcription_blocks(document_id, reviewed)</code></td><td>—</td><td>EXPLAIN ANALYZE vor Merge</td></tr>
|
||||
<tr><td>Division durch 0 (Sara)</td><td>Alle reviewed_pct-Queries: <code>CASE WHEN COUNT(*) = 0 THEN 0 ELSE SUM(...)::float / COUNT(*) END</code></td><td>—</td><td>—</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<!-- ── NEW COMPONENTS ────────────────────────────────────────────────── -->
|
||||
<div class="sec">
|
||||
<div class="sec-label">Neue Svelte-Komponenten</div>
|
||||
<div class="comp-grid">
|
||||
<div class="comp-card">
|
||||
<h4><code>DashboardMissionControl.svelte</code></h4>
|
||||
<p>Wrapper für den vollbreiten Streifen. Props: <code>needsSegmentation</code>, <code>needsTranscription</code>,
|
||||
<code>readyToRead</code>, <code>weeklyActivity</code>. Rendert die drei Spalten und ist komplett unsichtbar wenn alle Arrays leer sind.</p>
|
||||
</div>
|
||||
<div class="comp-card">
|
||||
<h4><code>DashboardSegmentationCol.svelte</code></h4>
|
||||
<p>Spalte 1: Skill-Pill, Wochenpuls, Avatare, Dokumentliste, CTA. Keine Balken — keine Dokument-Metadaten vorhanden.</p>
|
||||
</div>
|
||||
<div class="comp-card">
|
||||
<h4><code>DashboardTranscriptionCol.svelte</code></h4>
|
||||
<p>Spalte 2: Skill-Pill, Wochenpuls, Avatare, per-Dokument-Balken, Experten-Badge bei <code>needsExpert</code>, CTA.</p>
|
||||
</div>
|
||||
<div class="comp-card">
|
||||
<h4><code>DashboardReadyToReadCol.svelte</code></h4>
|
||||
<p>Spalte 3: Zeigt gefüllten Zustand (Liste mit %-Text) oder leeren Zustand (Cross-Column-Redirect zu Segmentierung).</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="callout green">
|
||||
<div>
|
||||
<strong class="g">Bestehende Komponente bleibt:</strong> <code>DashboardNeedsMetadata.svelte</code> ist unverändert —
|
||||
sie lebt weiterhin in der rechten Spalte. Der Mission-Control-Streifen ist vollständig additiv und ändert nichts am bestehenden Layout.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /doc -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -56,20 +56,21 @@ onMount(async () => {
|
||||
await renderer.init();
|
||||
});
|
||||
|
||||
// Wire DOM elements to the renderer after they mount
|
||||
$effect(() => {
|
||||
if (canvasEl && textLayerEl) {
|
||||
renderer.setElements(canvasEl, textLayerEl);
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (renderer.pdfjsReady && url) {
|
||||
renderer.loadDocument(url);
|
||||
}
|
||||
});
|
||||
|
||||
// Wire DOM elements to the renderer and trigger rendering.
|
||||
// canvasEl is read synchronously so Svelte tracks it as a dependency:
|
||||
// when the canvas reappears after the loading spinner (loading → false),
|
||||
// this effect re-fires and renders the already-loaded PDF.
|
||||
$effect(() => {
|
||||
if (!canvasEl || !textLayerEl) return;
|
||||
renderer.setElements(canvasEl, textLayerEl);
|
||||
// Also track currentPage and scale so page-nav / zoom re-renders work.
|
||||
// Read scale and currentPage synchronously so Svelte tracks them as dependencies.
|
||||
if (renderer.isLoaded && renderer.currentPage && renderer.scale > 0) {
|
||||
renderer.renderCurrentPage().then(() => renderer.prerender());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user