731 lines
41 KiB
HTML
731 lines
41 KiB
HTML
<!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 · UI/UX · 2026-04-17 · 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><div></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">☷</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 & 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 & 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 & 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 & 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 & 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 & 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><div></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><div class="flex items-center gap-2 my-3"><div class="flex-1 border-t border-line"></div><span class="text-[9px] font-bold uppercase tracking-widest text-ink-3">Optional</span><div class="flex-1 border-t border-line"></div></div></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}<input ... autofocus />{:else}<PersonTypeahead ... autofocus />{/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><p class="text-xs font-medium text-white/50 truncate max-w-[200px]">{doc.originalFilename}</p></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>
|