Files
familienarchiv/docs/specs/enrich-edit-unified.html
2026-05-05 12:39:20 +02:00

731 lines
41 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Enrich / Edit — Unified Design · Familienarchiv</title>
<link href="https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,300;0,400;0,700;1,300&family=Montserrat:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"/>
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Montserrat',system-ui,sans-serif;background:#ECEAE4;color:#1A1A1A;line-height:1.5;font-size:13px}
.doc{max-width:1300px;margin:0 auto;padding:48px 32px 120px}
/* ── Masthead ── */
.mh{padding-bottom:24px;border-bottom:3px solid #002850;margin-bottom:60px}
.mh h1{font-size:23px;font-weight:900;color:#002850;letter-spacing:-.4px}
.mh p{font-size:13px;color:#555;max-width:740px;line-height:1.75;margin-top:8px}
.mh .byline{font-size:9px;color:#999;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;margin-top:10px}
.tag-row{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}
.tag{background:#002850;color:#A6DAD8;padding:2px 8px;border-radius:2px;font-size:8px;font-weight:700;letter-spacing:.8px;text-transform:uppercase}
.tag.amber{background:#7c4a00;color:#fde68a}
.tag.green{background:#1e5e34;color:#d1fae5}
/* ── Section headers ── */
.sh{margin:0 0 28px}
.sh h2{font-size:16px;font-weight:900;color:#002850;letter-spacing:-.2px}
.sh p{font-size:12.5px;color:#666;max-width:720px;line-height:1.7;margin-top:5px}
.section{margin-bottom:80px;padding-bottom:80px;border-bottom:2px dashed #C8C4BE}
.section:last-of-type{border-bottom:none;margin-bottom:0;padding-bottom:0}
/* ── Field order comparison ── */
.compare{display:grid;grid-template-columns:1fr 40px 1fr;gap:0;align-items:start;margin-bottom:16px}
.compare-col{background:#F5F4EE;border:1.5px solid #E4E2D7;border-radius:4px;overflow:hidden}
.compare-head{padding:8px 14px;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:1px;border-bottom:1.5px solid #E4E2D7}
.compare-head.before{color:#888;background:#F0EEE9}
.compare-head.after{color:#002850;background:#EAF2FF}
.compare-arrow{display:flex;align-items:center;justify-content:center;font-size:20px;color:#B0ADA6;padding-top:40px}
.field-row{display:flex;align-items:center;gap:8px;padding:6px 14px;border-bottom:1px solid #E4E2D7;font-size:11px}
.field-row:last-child{border-bottom:none}
.field-name{flex:1;font-weight:600;color:#333}
.field-badge{font-size:7.5px;font-weight:800;padding:1px 6px;border-radius:10px;white-space:nowrap}
.badge-req{background:#FEE2E2;color:#991B1B}
.badge-opt{background:#F3F4F6;color:#555}
.badge-moved{background:#DBEAFE;color:#1E40AF}
.field-note{font-size:10px;color:#999;font-style:italic}
.compare-cap{font-size:10.5px;color:#777;line-height:1.65;font-style:italic;max-width:460px;margin-top:8px}
/* ── Progress bar annotation ── */
.annot-strip{background:#F0EEE9;border:1.5px solid #E4E2D7;border-radius:4px;padding:12px 16px;margin-bottom:24px;display:flex;align-items:flex-start;gap:14px}
.annot-num{width:22px;height:22px;border-radius:50%;background:#002850;color:#fff;font-size:9px;font-weight:900;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:1px}
.annot-body{}
.annot-title{font-size:11.5px;font-weight:800;color:#002850;margin-bottom:3px}
.annot-desc{font-size:11px;color:#555;line-height:1.65;max-width:680px}
/* ── Browser chrome ── */
.screen{max-width:940px;margin:0 auto}
.chrome{background:#F5F4EE;border:1.5px solid #C4C0BA;border-radius:8px;overflow:hidden;box-shadow:0 4px 20px rgba(0,0,0,.1)}
.chrome-bar{height:20px;background:#E8E6E0;border-bottom:1px solid #C4C0BA;display:flex;align-items:center;padding:0 8px;gap:4px;flex-shrink:0}
.chrome-dot{width:6px;height:6px;border-radius:50%;background:#BDB8B1}
.chrome-url{flex:1;height:9px;background:#CCC8C2;border-radius:5px;margin-left:6px}
/* ── App nav ── */
.app-nav{height:30px;background:#002850;display:flex;align-items:center;padding:0 12px;gap:10px;flex-shrink:0}
.app-logo{font-family:'Merriweather',Georgia,serif;font-size:7px;font-weight:700;color:#fff;border-bottom:2px solid #A6DAD8;padding-bottom:1px}
.app-link{font-size:5.5px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:rgba(255,255,255,.45);white-space:nowrap}
.app-link.on{color:rgba(255,255,255,.9)}
.app-nav-r{margin-left:auto;display:flex;gap:6px;align-items:center}
.app-avatar{width:16px;height:16px;border-radius:50%;background:rgba(255,255,255,.12);display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:rgba(255,255,255,.5)}
/* ── Enrich top bar ── */
.enrich-bar{height:36px;background:#F5F4EE;border-bottom:1px solid #E4E2D7;display:flex;align-items:center;padding:0 14px;gap:10px;flex-shrink:0}
.eb-back{font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;display:flex;align-items:center;gap:4px;white-space:nowrap}
.eb-title{flex:1;text-align:center;font-family:'Merriweather',Georgia,serif;font-size:8px;color:#002850;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:0 10px}
.eb-progress{font-size:6px;color:#AAA;white-space:nowrap;font-weight:600}
/* ── Required fields bar ── */
.req-strip{background:#F0EEE9;border-bottom:1px solid #E4E2D7;padding:5px 14px;display:flex;align-items:center;gap:8px;flex-shrink:0}
.req-label{font-size:5.5px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#B0ADA6;white-space:nowrap}
.req-track{flex:1;height:3px;background:#E0DDD6;border-radius:2px;overflow:hidden}
.req-fill{height:100%;background:#002850;border-radius:2px}
.req-count{font-size:6px;font-weight:800;color:#002850;white-space:nowrap}
/* ── Split ── */
.app-body{display:flex;flex-direction:column;overflow:hidden}
.split{display:flex;overflow:hidden}
/* ── PDF panel — with file ── */
.pdf-panel{background:#5E5C59;display:flex;flex-direction:column;overflow:hidden;border-right:1px solid #3A3836;flex:60}
.pdf-toolbar{height:26px;background:#3A3836;display:flex;align-items:center;padding:0 8px;gap:5px;flex-shrink:0}
.pdf-btn{width:14px;height:14px;border-radius:2px;background:rgba(255,255,255,.1);display:flex;align-items:center;justify-content:center;font-size:7px;color:rgba(255,255,255,.5)}
.pdf-btn.on{background:rgba(255,255,255,.18)}
.pdf-pg{font-size:6px;color:rgba(255,255,255,.4);margin:0 auto;font-weight:700;letter-spacing:.5px}
.pdf-view{flex:1;display:flex;justify-content:center;padding:16px;overflow-y:auto}
.pdf-paper{background:#FFFEF8;box-shadow:0 2px 10px rgba(0,0,0,.3);border-radius:1px;padding:14px 16px;display:flex;flex-direction:column;gap:0;width:190px;flex-shrink:0}
.pl{height:4px;background:#C4BDB0;border-radius:1px;opacity:.55;margin-bottom:3px}
.pl.h{height:5.5px;opacity:.75;margin-bottom:4px}
.pl.s{width:55%;opacity:.3}
.pl.m{width:80%}
.pl.sp{height:0;background:transparent}
/* ── PDF panel — no file ── */
.pdf-empty{background:#4A4846;display:flex;flex-direction:column;overflow:hidden;border-right:1px solid #3A3836;flex:60}
.pdf-empty-body{flex:1;display:flex;align-items:center;justify-content:center;padding:16px}
.upload-zone{border:1.5px dashed rgba(255,255,255,.2);border-radius:4px;padding:22px 20px;display:flex;flex-direction:column;align-items:center;text-align:center;gap:8px;width:180px}
.upload-icon{width:28px;height:28px;border-radius:50%;background:rgba(255,255,255,.08);display:flex;align-items:center;justify-content:center;font-size:13px;color:rgba(255,255,255,.35)}
.upload-filename{font-size:7px;font-weight:700;color:rgba(255,255,255,.5);letter-spacing:.2px;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.upload-sub{font-size:6px;color:rgba(255,255,255,.3);line-height:1.5}
.upload-btn{margin-top:4px;background:#002850;color:rgba(255,255,255,.9);font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.6px;padding:4px 10px;border-radius:2px;white-space:nowrap}
.upload-drag{font-size:5.5px;color:rgba(255,255,255,.22);margin-top:2px}
/* ── Form panel ── */
.form-panel{display:flex;flex-direction:column;overflow:hidden;background:#fff;flex:40}
.form-scroll{flex:1;overflow-y:auto;padding:14px}
/* ── Form elements ── */
.f-card{background:#F9F8F5;border:1px solid #E4E2D7;border-radius:3px;padding:11px 12px;margin-bottom:10px}
.f-card:last-child{margin-bottom:0}
.f-card-title{font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:#B0ADA6;margin-bottom:9px}
.f-row{display:grid;grid-template-columns:1fr 1fr;gap:7px;margin-bottom:7px}
.f-row.full{grid-template-columns:1fr}
.f-row:last-child{margin-bottom:0}
.f-field{display:flex;flex-direction:column;gap:2px}
.f-label{font-size:6px;font-weight:700;color:#666}
.f-req{color:#C0392B;margin-left:1px}
.f-input{height:18px;border:1px solid #D4D0CA;border-radius:2px;background:#fff;font-size:7px;padding:0 6px;color:#333;display:flex;align-items:center}
.f-input.focus{border-color:#002850;box-shadow:0 0 0 2px rgba(0,40,80,.12)}
.f-input.filled{color:#002850;font-weight:600;background:#FAFBFF}
.f-input.empty{color:#CCC;font-style:italic}
.f-input.tall{height:22px}
.f-textarea{height:38px;border:1px solid #D4D0CA;border-radius:2px;background:#fff;font-size:7px;padding:4px 6px;color:#555;display:flex;align-items:flex-start}
.f-tags{display:flex;gap:3px;flex-wrap:wrap;min-height:18px;border:1px solid #D4D0CA;border-radius:2px;padding:2px 4px;background:#fff;align-items:center}
.f-chip{background:#002850;color:#A6DAD8;border-radius:2px;font-size:5.5px;font-weight:700;padding:1px 4px 1px 5px;display:flex;align-items:center;gap:2px}
.f-chip-rm{color:rgba(166,218,216,.5)}
.f-add{font-size:6px;color:#B0ADA6;cursor:pointer}
/* ── Optional divider inside card ── */
.card-opt-divider{display:flex;align-items:center;gap:6px;margin:8px 0 7px}
.card-opt-line{flex:1;height:1px;background:#E4E2D7}
.card-opt-label{font-size:5.5px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#C8C4BE}
/* ── Action bar ── */
.action-bar{height:44px;background:#F5F4EE;border-top:1px solid #E4E2D7;display:flex;align-items:center;padding:0 12px;gap:6px;flex-shrink:0}
.btn-skip{font-size:6.5px;font-weight:700;color:#AAA}
.btn-spacer{flex:1}
.btn-outline{height:22px;padding:0 10px;border:1px solid #C0BDB6;border-radius:2px;font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#777;display:flex;align-items:center}
.btn-primary{height:22px;padding:0 10px;border-radius:2px;font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;background:#002850;color:#fff;display:flex;align-items:center}
/* ── Callout annotations ── */
.callouts{display:flex;flex-direction:column;gap:8px;margin-top:20px}
.callout{display:flex;align-items:flex-start;gap:10px;background:#fff;border:1px solid #E4E2D7;border-radius:3px;padding:9px 12px}
.callout-num{width:18px;height:18px;border-radius:50%;background:#002850;color:#A6DAD8;font-size:7.5px;font-weight:900;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.callout-text{font-size:11px;color:#444;line-height:1.6}
.callout-text strong{color:#002850;font-weight:700}
/* ── Two-state comparison header ── */
.state-label{font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:#888;margin-bottom:10px;display:flex;align-items:center;gap:6px}
.state-label::after{content:'';flex:1;height:1px;background:#D4D0CA}
.state-pair{display:grid;grid-template-columns:1fr 1fr;gap:24px;align-items:start}
.state-pair .screen{max-width:none}
/* ── Impl-ref table ── */
.impl-ref{margin-top:80px;padding-top:48px;border-top:3px solid #002850}
.impl-ref h2{font-size:17px;font-weight:900;color:#002850;margin-bottom:5px}
.impl-ref .subtitle{font-size:12px;color:#666;margin-bottom:24px;line-height:1.6;max-width:720px}
.impl-ref table{width:100%;border-collapse:collapse;font-size:11.5px}
.impl-ref thead th{text-align:left;padding:7px 12px;background:#002850;color:#A6DAD8;font-size:8.5px;font-weight:800;text-transform:uppercase;letter-spacing:.8px}
.impl-ref tbody td{padding:8px 12px;border-bottom:1px solid #E4E2D7;vertical-align:top;line-height:1.65;color:#333}
.impl-ref tbody tr:nth-child(even) td{background:#F5F4EE}
.impl-ref code{background:#E4E2D7;padding:1px 5px;border-radius:2px;font-family:monospace;font-size:10.5px;color:#1A1A1A}
</style>
</head>
<body>
<div class="doc">
<!-- ══════════════════════════════════════
MASTHEAD
══════════════════════════════════════ -->
<div class="mh">
<h1>Enrich / Edit — Unified Design Spec</h1>
<p>Keeps the current card-based layout. Applies three targeted changes to reduce friction: a required-fields progress bar above the form, required fields moved to the first row within each card, and a proper no-PDF upload state in the left panel.</p>
<div class="byline">Leonie Voss &nbsp;·&nbsp; UI/UX &nbsp;·&nbsp; 2026-04-17 &nbsp;·&nbsp; Familienarchiv</div>
<div class="tag-row">
<span class="tag">enrich</span>
<span class="tag">edit-document</span>
<span class="tag green">unified spec</span>
<span class="tag amber">field reordering</span>
</div>
</div>
<!-- ══════════════════════════════════════
SECTION 1 — FIELD REORDERING
══════════════════════════════════════ -->
<div class="section">
<div class="sh">
<h2>1 — Field Priority Within Cards</h2>
<p>Both cards keep their existing structure. Fields are reordered so a user can fill the essentials top-to-bottom without skipping around. Optional fields still live in the same card — they're just pushed below a thin divider.</p>
</div>
<!-- Annotations -->
<div style="margin-bottom:28px">
<div class="annot-strip">
<div class="annot-num">1</div>
<div class="annot-body">
<div class="annot-title">WhoWhenSection — swap rows</div>
<div class="annot-desc">Currently the grid is: Date | Location (row 1) → Sender | Receivers (row 2). Required fields are split across both rows. New order: Date | Sender (row 1, both required) → Receivers | Location (row 2, both optional). No code changes to the grid — just reorder the four <code>&lt;div&gt;</code> elements inside the existing <code>grid-cols-2</code>.</div>
</div>
</div>
<div class="annot-strip">
<div class="annot-num">2</div>
<div class="annot-body">
<div class="annot-title">DescriptionSection — promote Title, push Archive Location last</div>
<div class="annot-desc">Title is the most important field but currently sits below "Aufbewahrungsort" in the component's source order. New order: Title (full row, first) → Tags → Summary → Aufbewahrungsort (last, below an "Optional" divider). The divider signals that everything below it is supplementary — no need to fill it during enrichment.</div>
</div>
</div>
<div class="annot-strip">
<div class="annot-num">3</div>
<div class="annot-body">
<div class="annot-title">Required-fields progress bar — above the form scroll</div>
<div class="annot-desc">A 3 px strip between the top bar and the form panel's scroll area. Tracks how many required fields (Title, Date, Sender) are non-empty. Green fill grows left to right. Count badge (e.g. "2 / 3") to the right. Zero JS overhead — computed as a <code>$derived</code> value from existing bindable state.</div>
</div>
</div>
</div>
<!-- Before / After comparison -->
<div style="display:grid;grid-template-columns:1fr 1fr;gap:32px;align-items:start">
<!-- WhoWhenSection -->
<div>
<div style="font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:#888;margin-bottom:10px">WhoWhenSection</div>
<div class="compare">
<div class="compare-col">
<div class="compare-head before">Jetzt</div>
<div class="field-row"><span class="field-name">Datum</span><span class="field-badge badge-req">Pflicht</span></div>
<div class="field-row"><span class="field-name">Ort</span><span class="field-badge badge-opt">Optional</span></div>
<div class="field-row"><span class="field-name">Absender</span><span class="field-badge badge-req">Pflicht</span></div>
<div class="field-row"><span class="field-name">Empfänger</span><span class="field-badge badge-opt">Optional</span></div>
</div>
<div class="compare-arrow"></div>
<div class="compare-col">
<div class="compare-head after">Neu</div>
<div class="field-row"><span class="field-name">Datum</span><span class="field-badge badge-req">Pflicht</span></div>
<div class="field-row"><span class="field-name">Absender</span><span class="field-badge badge-moved">↑ hochgezogen</span></div>
<div class="field-row"><span class="field-name">Empfänger</span><span class="field-badge badge-opt">Optional</span></div>
<div class="field-row"><span class="field-name">Ort</span><span class="field-badge badge-opt">Optional</span></div>
</div>
</div>
<p class="compare-cap">Row 1 is now entirely required. A user who fills Date + Sender and moves on has completed all mandatory who/when data.</p>
</div>
<!-- DescriptionSection -->
<div>
<div style="font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:#888;margin-bottom:10px">DescriptionSection</div>
<div class="compare">
<div class="compare-col">
<div class="compare-head before">Jetzt</div>
<div class="field-row"><span class="field-name">Titel</span><span class="field-badge badge-req">Pflicht</span></div>
<div class="field-row"><span class="field-name">Aufbewahrungsort</span><span class="field-badge badge-opt">Optional</span></div>
<div class="field-row"><span class="field-name">Schlagworte</span><span class="field-badge badge-opt">Optional</span></div>
<div class="field-row"><span class="field-name">Kurzinhalt</span><span class="field-badge badge-opt">Optional</span></div>
</div>
<div class="compare-arrow"></div>
<div class="compare-col">
<div class="compare-head after">Neu</div>
<div class="field-row"><span class="field-name">Titel</span><span class="field-badge badge-req">Pflicht</span></div>
<div class="field-row"><span class="field-name">Schlagworte</span><span class="field-badge badge-moved">↑ hochgezogen</span></div>
<div class="field-row"><span class="field-name">Kurzinhalt</span><span class="field-badge badge-opt">Optional</span></div>
<div class="field-row" style="border-top:1.5px dashed #E0DDD6"><span class="field-name" style="color:#AAA">Aufbewahrungsort</span><span class="field-badge badge-opt">Optional</span></div>
</div>
</div>
<p class="compare-cap">Title stays first. Tags move up to row 2 (more useful than archive location during enrichment). Archive Location drops below the "Optional" divider — it's an administrative field, not enrichment work.</p>
</div>
</div>
</div><!-- /section 1 -->
<!-- ══════════════════════════════════════
SECTION 2 — FULL PAGE (WITH PDF)
══════════════════════════════════════ -->
<div class="section">
<div class="sh">
<h2>2 — Full Page with PDF</h2>
<p>The familiar split-pane layout with the reorganized form. Required-fields bar shows 2 of 3 filled; the one missing required field (Sender) is visible immediately at the top of the first card.</p>
</div>
<div class="screen">
<div class="chrome">
<div class="chrome-bar">
<div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div>
<div class="chrome-url"></div>
</div>
<div class="app-body">
<!-- App nav -->
<div class="app-nav">
<span class="app-logo">Familienarchiv</span>
<span class="app-link">Dokumente</span>
<span class="app-link">Personen</span>
<span class="app-link on">Anreicherung</span>
<div class="app-nav-r"><div class="app-avatar">MR</div></div>
</div>
<!-- Enrich top bar -->
<div class="enrich-bar">
<div class="eb-back">← Zur Liste</div>
<div class="eb-title">Brief an Tante Hilde, Sommer 1952</div>
<div class="eb-progress">12 verbleibend</div>
</div>
<!-- Split -->
<div class="split" style="height:490px">
<!-- PDF panel -->
<div class="pdf-panel">
<div class="pdf-toolbar">
<div class="pdf-btn on"></div>
<div class="pdf-btn"></div>
<div class="pdf-btn"></div>
<div class="pdf-pg">Seite 1 / 3</div>
<div class="pdf-btn">&#9783;</div>
</div>
<div class="pdf-view">
<div class="pdf-paper">
<div class="pl h"></div>
<div class="pl s"></div>
<div class="pl sp" style="margin-bottom:6px"></div>
<div class="pl m"></div>
<div class="pl"></div>
<div class="pl"></div>
<div class="pl s"></div>
<div class="pl sp" style="margin-bottom:4px"></div>
<div class="pl"></div>
<div class="pl m"></div>
<div class="pl"></div>
<div class="pl s"></div>
<div class="pl sp" style="margin-bottom:4px"></div>
<div class="pl"></div>
<div class="pl m"></div>
<div class="pl"></div>
<div class="pl"></div>
<div class="pl s"></div>
<div class="pl sp" style="margin-bottom:4px"></div>
<div class="pl m"></div>
<div class="pl"></div>
<div class="pl s"></div>
<div class="pl sp" style="margin-bottom:4px"></div>
<div class="pl"></div>
<div class="pl m"></div>
</div>
</div>
</div>
<!-- Form panel -->
<div class="form-panel">
<!-- Required-fields bar -->
<div class="req-strip">
<span class="req-label">Pflichtfelder</span>
<div class="req-track"><div class="req-fill" style="width:67%"></div></div>
<span class="req-count">2 / 3</span>
</div>
<div class="form-scroll">
<!-- Card 1: Wer & Wann — new field order -->
<div class="f-card">
<div class="f-card-title">Wer &amp; Wann</div>
<!-- Row 1: required fields -->
<div class="f-row">
<div class="f-field">
<span class="f-label">Datum <span class="f-req">*</span></span>
<div class="f-input filled">15.07.1952</div>
</div>
<div class="f-field">
<span class="f-label">Absender <span class="f-req">*</span></span>
<div class="f-input focus empty">Person suchen …</div>
</div>
</div>
<!-- Row 2: optional fields -->
<div class="f-row" style="margin-bottom:0">
<div class="f-field">
<span class="f-label">Empfänger</span>
<div class="f-tags">
<div class="f-chip">Hilde Brandt <span class="f-chip-rm">×</span></div>
<div class="f-add">+</div>
</div>
</div>
<div class="f-field">
<span class="f-label">Ort</span>
<div class="f-input">München</div>
</div>
</div>
</div>
<!-- Card 2: Beschreibung — new field order -->
<div class="f-card">
<div class="f-card-title">Beschreibung</div>
<!-- Title: full row, first -->
<div class="f-row full">
<div class="f-field">
<span class="f-label">Titel <span class="f-req">*</span></span>
<div class="f-input filled tall">Brief an Tante Hilde — München 1952</div>
</div>
</div>
<!-- Tags: second -->
<div class="f-row full">
<div class="f-field">
<span class="f-label">Schlagworte</span>
<div class="f-tags">
<div class="f-chip">Familie <span class="f-chip-rm">×</span></div>
<div class="f-chip">Krieg <span class="f-chip-rm">×</span></div>
<div class="f-add">+ Schlagwort</div>
</div>
</div>
</div>
<!-- Summary -->
<div class="f-row full">
<div class="f-field">
<span class="f-label">Kurzinhalt</span>
<div class="f-textarea"></div>
</div>
</div>
<!-- Optional divider -->
<div class="card-opt-divider">
<div class="card-opt-line"></div>
<span class="card-opt-label">Optional</span>
<div class="card-opt-line"></div>
</div>
<!-- Archive location — last -->
<div class="f-row full" style="margin-bottom:0">
<div class="f-field">
<span class="f-label">Aufbewahrungsort</span>
<div class="f-input empty">z. B. Karton 3, Regal B</div>
</div>
</div>
</div>
</div><!-- /form-scroll -->
<!-- Action bar -->
<div class="action-bar">
<span class="btn-skip">Überspringen</span>
<div class="btn-spacer"></div>
<div class="btn-outline">Speichern</div>
<div class="btn-primary">Speichern &amp; Geprüft</div>
</div>
</div>
</div><!-- /split -->
</div>
</div>
</div>
<!-- Callouts -->
<div class="callouts">
<div class="callout">
<div class="callout-num">1</div>
<div class="callout-text"><strong>Required bar at 2/3</strong> — Datum and Titel are filled; Absender is empty. The bar gives an at-a-glance completion signal without adding visual weight to individual fields.</div>
</div>
<div class="callout">
<div class="callout-num">2</div>
<div class="callout-text"><strong>Absender focused</strong> — it's the only required field still empty, so it receives focus on load via <code>autofocus</code> or a Svelte <code>$effect</code> that targets the first empty required input. No user action needed to know where to start.</div>
</div>
<div class="callout">
<div class="callout-num">3</div>
<div class="callout-text"><strong>Aufbewahrungsort below the "Optional" divider</strong> — visually de-prioritized without being removed. Editors doing bulk enrichment can ignore it entirely; archivists who need it will still find it.</div>
</div>
</div>
</div><!-- /section 2 -->
<!-- ══════════════════════════════════════
SECTION 3 — NO-PDF STATE
══════════════════════════════════════ -->
<div class="section">
<div class="sh">
<h2>3 — No-PDF State</h2>
<p>When a document has no file yet (status <code>PLACEHOLDER</code> from Excel import), the left panel shows the same dark background but with a centered upload zone. The original filename is shown if known. Drag-and-drop is supported; clicking the CTA opens the file picker. Once a file is selected it uploads and the panel transitions to the normal PDF viewer.</p>
</div>
<!-- Two states side by side at reduced width -->
<div style="display:grid;grid-template-columns:1fr 1fr;gap:28px;align-items:start;max-width:940px;margin:0 auto">
<!-- State A: No file -->
<div>
<div class="state-label">Kein Dokument hochgeladen</div>
<div class="chrome">
<div class="chrome-bar">
<div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div>
<div class="chrome-url"></div>
</div>
<div class="app-body">
<div class="app-nav">
<span class="app-logo">Familienarchiv</span>
<span class="app-link on">Anreicherung</span>
<div class="app-nav-r"><div class="app-avatar">MR</div></div>
</div>
<div class="enrich-bar">
<div class="eb-back">← Zur Liste</div>
<div class="eb-title">scan-2024-03-15-brief.pdf</div>
<div class="eb-progress">12 verbleibend</div>
</div>
<div class="split" style="height:300px">
<!-- No-file left panel -->
<div class="pdf-empty">
<div class="pdf-empty-body">
<div class="upload-zone">
<div class="upload-icon"></div>
<div class="upload-filename">scan-2024-03-15-brief.pdf</div>
<div class="upload-sub">Noch keine Datei hochgeladen</div>
<div class="upload-btn">Datei auswählen</div>
<div class="upload-drag">oder Datei hier ablegen</div>
</div>
</div>
</div>
<!-- Form panel (abbreviated) -->
<div class="form-panel">
<div class="req-strip">
<span class="req-label">Pflichtfelder</span>
<div class="req-track"><div class="req-fill" style="width:0%"></div></div>
<span class="req-count">0 / 3</span>
</div>
<div class="form-scroll">
<div class="f-card">
<div class="f-card-title">Wer &amp; Wann</div>
<div class="f-row">
<div class="f-field">
<span class="f-label">Datum <span class="f-req">*</span></span>
<div class="f-input focus empty">TT.MM.JJJJ</div>
</div>
<div class="f-field">
<span class="f-label">Absender <span class="f-req">*</span></span>
<div class="f-input empty">Person suchen …</div>
</div>
</div>
</div>
</div>
<div class="action-bar">
<span class="btn-skip">Überspringen</span>
<div class="btn-spacer"></div>
<div class="btn-outline">Speichern</div>
<div class="btn-primary">Speichern &amp; Geprüft</div>
</div>
</div>
</div>
</div>
</div>
</div><!-- /state A -->
<!-- State B: File uploading -->
<div>
<div class="state-label">Datei wird hochgeladen</div>
<div class="chrome">
<div class="chrome-bar">
<div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div>
<div class="chrome-url"></div>
</div>
<div class="app-body">
<div class="app-nav">
<span class="app-logo">Familienarchiv</span>
<span class="app-link on">Anreicherung</span>
<div class="app-nav-r"><div class="app-avatar">MR</div></div>
</div>
<div class="enrich-bar">
<div class="eb-back">← Zur Liste</div>
<div class="eb-title">scan-2024-03-15-brief.pdf</div>
<div class="eb-progress">12 verbleibend</div>
</div>
<div class="split" style="height:300px">
<!-- Uploading state — dark panel, progress indicator -->
<div class="pdf-empty">
<div class="pdf-empty-body">
<div class="upload-zone" style="border-color:rgba(166,218,216,.4);background:rgba(166,218,216,.05)">
<!-- Indeterminate progress bar -->
<div style="width:100%;height:2px;background:rgba(255,255,255,.1);border-radius:2px;overflow:hidden;position:relative">
<div style="position:absolute;left:-40%;width:40%;height:100%;background:#A6DAD8;border-radius:2px;animation:none;opacity:.7"></div>
</div>
<div class="upload-filename" style="color:rgba(166,218,216,.7)">scan-2024-03-15-brief.pdf</div>
<div class="upload-sub" style="color:rgba(255,255,255,.4)">Wird hochgeladen …</div>
<div style="font-size:5px;color:rgba(255,255,255,.2)">Abbrechen</div>
</div>
</div>
</div>
<!-- Form panel (same) -->
<div class="form-panel">
<div class="req-strip">
<span class="req-label">Pflichtfelder</span>
<div class="req-track"><div class="req-fill" style="width:0%"></div></div>
<span class="req-count">0 / 3</span>
</div>
<div class="form-scroll">
<div class="f-card">
<div class="f-card-title">Wer &amp; Wann</div>
<div class="f-row">
<div class="f-field">
<span class="f-label">Datum <span class="f-req">*</span></span>
<div class="f-input focus empty">TT.MM.JJJJ</div>
</div>
<div class="f-field">
<span class="f-label">Absender <span class="f-req">*</span></span>
<div class="f-input empty">Person suchen …</div>
</div>
</div>
</div>
</div>
<div class="action-bar">
<span class="btn-skip">Überspringen</span>
<div class="btn-spacer"></div>
<div class="btn-outline">Speichern</div>
<div class="btn-primary">Speichern &amp; Geprüft</div>
</div>
</div>
</div>
</div>
</div>
</div><!-- /state B -->
</div>
<!-- Callouts -->
<div class="callouts" style="max-width:940px;margin:20px auto 0">
<div class="callout">
<div class="callout-num">1</div>
<div class="callout-text"><strong>Dark background is consistent</strong> — the same dark panel color as the loading state prevents a jarring flash when the PDF loads. No separate empty-state design needed for the panel chrome.</div>
</div>
<div class="callout">
<div class="callout-num">2</div>
<div class="callout-text"><strong>Filename always visible</strong><code>doc.originalFilename</code> is always populated. Showing it in the upload zone reassures the user they're uploading the right file, and in the top bar title it gives context while the form is empty.</div>
</div>
<div class="callout">
<div class="callout-num">3</div>
<div class="callout-text"><strong>Form stays usable during upload</strong> — metadata can be filled while the file uploads in parallel. The upload and form save are independent operations; both are sent on "Speichern".</div>
</div>
<div class="callout">
<div class="callout-num">4</div>
<div class="callout-text"><strong>Replace file (edit mode)</strong> — when a file already exists and the user is on the edit page, the left panel shows the PDF normally. A "Datei ersetzen" ghost button sits in the PDF toolbar — same panel, just an additional toolbar action.</div>
</div>
</div>
</div><!-- /section 3 -->
<!-- ══════════════════════════════════════
IMPLEMENTATION REFERENCE
══════════════════════════════════════ -->
<div class="impl-ref">
<h2>Implementation Reference</h2>
<div class="subtitle">Exact Tailwind classes and component-level changes. All three improvements are additive — no existing card structure or component API needs to change.</div>
<table>
<thead>
<tr>
<th>Element</th>
<th>File</th>
<th>Change</th>
<th>Tailwind / Code</th>
</tr>
</thead>
<tbody>
<tr>
<td>WhoWhenSection field order</td>
<td><code>WhoWhenSection.svelte</code></td>
<td>Reorder the four <code>&lt;div&gt;</code> children of the <code>grid-cols-2</code> container: Date → Sender → Receivers → Location</td>
<td>No class changes. Grid order is source order.</td>
</tr>
<tr>
<td>DescriptionSection field order</td>
<td><code>DescriptionSection.svelte</code></td>
<td>Move <code>documentLocation</code> field to last. Move Tags before Summary.</td>
<td>No class changes to existing fields.</td>
</tr>
<tr>
<td>Optional divider inside DescriptionSection</td>
<td><code>DescriptionSection.svelte</code></td>
<td>Add divider element before <code>documentLocation</code> field.</td>
<td><code>&lt;div class="flex items-center gap-2 my-3"&gt;&lt;div class="flex-1 border-t border-line"&gt;&lt;/div&gt;&lt;span class="text-[9px] font-bold uppercase tracking-widest text-ink-3"&gt;Optional&lt;/span&gt;&lt;div class="flex-1 border-t border-line"&gt;&lt;/div&gt;&lt;/div&gt;</code></td>
</tr>
<tr>
<td>Required-fields progress bar</td>
<td><code>enrich/[id]/+page.svelte</code></td>
<td>Add strip between <code>.enrich-bar</code> and the split pane. Derive count from bindable state already in scope.</td>
<td>Strip: <code>flex items-center gap-3 border-b border-line bg-surface px-6 py-1.5</code>. Track: <code>h-0.5 flex-1 rounded-full bg-line</code>. Fill: <code>h-full rounded-full bg-brand-navy transition-all duration-300</code> with <code>style="width:{pct}%"</code>.</td>
</tr>
<tr>
<td>Required fields derivation</td>
<td><code>enrich/[id]/+page.svelte</code></td>
<td>Derive from existing state variables, no new props needed.</td>
<td><code>const requiredFilled = $derived([doc.title || titleValue, dateIso, senderId].filter(Boolean).length);</code> — 3 total required fields in enrich mode.</td>
</tr>
<tr>
<td>Auto-focus first empty required field</td>
<td><code>WhoWhenSection.svelte</code></td>
<td>Add <code>autofocus</code> on the Date input when <code>initialDateIso</code> is empty; on the Sender PersonTypeahead otherwise.</td>
<td><code>{#if !initialDateIso}&lt;input ... autofocus /&gt;{:else}&lt;PersonTypeahead ... autofocus /&gt;{/if}</code></td>
</tr>
<tr>
<td>No-PDF upload zone</td>
<td><code>enrich/[id]/+page.svelte</code> (new conditional block)</td>
<td>Conditionally render upload zone in the left panel when <code>!doc.filePath</code>.</td>
<td>Zone: <code>flex-1 flex items-center justify-center bg-[#4A4846]</code>. Inner: <code>border border-dashed border-white/20 rounded-sm p-8 flex flex-col items-center gap-3 text-center</code>.</td>
</tr>
<tr>
<td>Upload zone drag-and-drop</td>
<td><code>enrich/[id]/+page.svelte</code></td>
<td>Add <code>ondragover</code> / <code>ondrop</code> to zone element. On drop, trigger same upload action as the file input.</td>
<td><code>ondragover={(e) => { e.preventDefault(); isDragging = true; }}</code> + <code>ondrop={(e) => { e.preventDefault(); handleFile(e.dataTransfer?.files[0]); }}</code>. Dragging state: <code>border-brand-mint bg-mint/5</code> transition.</td>
</tr>
<tr>
<td>Upload zone — original filename</td>
<td><code>enrich/[id]/+page.svelte</code></td>
<td>Show <code>doc.originalFilename</code> inside the zone as a label. Always available from the load function.</td>
<td><code>&lt;p class="text-xs font-medium text-white/50 truncate max-w-[200px]"&gt;{doc.originalFilename}&lt;/p&gt;</code></td>
</tr>
<tr>
<td>Uploading / progress state</td>
<td><code>enrich/[id]/+page.svelte</code></td>
<td>Use existing <code>FileLoader</code> hook's <code>isLoading</code> state. Show indeterminate progress bar when loading.</td>
<td>Progress track: <code>w-full h-0.5 bg-white/10 rounded-full overflow-hidden</code>. Fill: <code>h-full bg-brand-mint/70 animate-[slide_1.4s_ease-in-out_infinite]</code>.</td>
</tr>
<tr>
<td>Replace file button (edit mode)</td>
<td><code>FileSectionEdit.svelte</code></td>
<td>Move "Datei ersetzen" from the card in the form scroll to a ghost button in the PDF toolbar. Toolbar is visible in both states.</td>
<td>Button: <code>ml-auto text-[10px] font-bold uppercase tracking-widest text-white/40 hover:text-white/70 transition-colors</code>. Label: "Datei ersetzen". Hidden input triggers file picker.</td>
</tr>
</tbody>
</table>
</div>
</div><!-- /doc -->
</body>
</html>