Covers segmented type control, title input, conditional field visibility, PersonCard title display, mobile layout, and a11y. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1021 lines
59 KiB
HTML
1021 lines
59 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Person Title & Type Fields — Design Spec · Familienarchiv</title>
|
||
<style>
|
||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||
body{font-family:'Helvetica Neue',Arial,sans-serif;background:#ECEAE4;color:#1A1A1A;line-height:1.5}
|
||
.doc{max-width:1440px;margin:0 auto;padding:48px 32px}
|
||
|
||
/* ── Masthead ─── */
|
||
.mast{background:#0D2240;border-radius:10px;padding:32px 40px;margin-bottom:48px}
|
||
.mast-top{display:flex;align-items:flex-start;justify-content:space-between;gap:24px;margin-bottom:16px}
|
||
.mast h1{font-size:22px;font-weight:900;color:#fff;letter-spacing:-.4px;margin-bottom:6px}
|
||
.mast p{font-size:12px;color:rgba(255,255,255,.5);max-width:620px;line-height:1.7}
|
||
.mast-badge{font-size:9px;font-weight:800;padding:3px 9px;border-radius:20px;text-transform:uppercase;letter-spacing:.8px;white-space:nowrap;flex-shrink:0;margin-top:4px}
|
||
.mb-final{background:#A6DAD8;color:#002850}
|
||
.decisions{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-top:20px;border-top:1px solid rgba(255,255,255,.1);padding-top:16px}
|
||
.dec{background:rgba(255,255,255,.06);border-radius:6px;padding:10px 12px}
|
||
.dec-label{font-size:7px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:rgba(255,255,255,.35);margin-bottom:5px}
|
||
.dec-value{font-size:9.5px;font-weight:700;color:#fff;line-height:1.5}
|
||
.dec-value s{color:rgba(255,255,255,.3);font-weight:400}
|
||
|
||
/* ── Section headings ─── */
|
||
.sec{margin-bottom:64px}
|
||
.sec+.sec{border-top:2px dashed #C8C4BE;padding-top:56px}
|
||
.sec-h{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:1.2px;color:#888;margin-bottom:20px;display:flex;align-items:center;gap:10px}
|
||
.sec-h::after{content:'';flex:1;height:1px;background:#D8D4CE}
|
||
.sec-num{background:#0D2240;color:#fff;font-size:9px;font-weight:900;padding:2px 7px;border-radius:10px}
|
||
|
||
/* ── Screen grid ─── */
|
||
.sg{display:grid;gap:20px;align-items:start}
|
||
.sg-2{grid-template-columns:1fr 1fr}
|
||
.sg-2a{grid-template-columns:1.3fr 1fr}
|
||
.sg-3{grid-template-columns:1fr 1fr 1fr}
|
||
.sg-4{grid-template-columns:1fr 1fr 1fr 1fr}
|
||
.sb{display:flex;flex-direction:column}
|
||
.sl{font-size:9px;font-weight:800;color:#888;text-transform:uppercase;letter-spacing:1.5px;margin-bottom:6px;display:flex;align-items:center;gap:6px}
|
||
.sz{background:#E8E4DF;color:#666;padding:1px 5px;border-radius:3px;font-size:8px}
|
||
.state{padding:1px 6px;border-radius:3px;font-size:8px;font-weight:700}
|
||
.st-new{background:#DCFCE7;color:#166534}
|
||
.st-changed{background:#DBEAFE;color:#1E40AF}
|
||
.sc{font-size:8.5px;color:#888;margin-top:6px;font-style:italic;line-height:1.5}
|
||
|
||
/* ── Annotation callouts ─── */
|
||
.ann{display:inline-block;font-size:7.5px;font-weight:700;color:#C2410C;background:#FFF7ED;border:1px solid #FDBA74;border-radius:3px;padding:1px 5px;white-space:nowrap}
|
||
.ann-block{background:#FFF7ED;border:1px solid #FDBA74;border-radius:5px;padding:8px 10px;font-size:10px;color:#7C2D12;line-height:1.5;margin-top:10px}
|
||
.ann-block strong{font-weight:800}
|
||
.ann-block ul{padding-left:14px;display:flex;flex-direction:column;gap:3px;margin-top:5px}
|
||
|
||
/* ── Chrome (browser frame) ─── */
|
||
.wf{background:#fff;border:2px solid #B8B4AE;border-radius:10px;overflow:hidden;box-shadow:0 4px 18px rgba(0,0,0,.08)}
|
||
.wf-bar{height:24px;background:#E8E4DF;border-bottom:1px solid #C8C4BE;display:flex;align-items:center;padding:0 9px;gap:4px}
|
||
.dot{width:7px;height:7px;border-radius:50%;background:#C8C4BE}
|
||
.dot.r{background:#F87171}.dot.y{background:#FCD34D}.dot.g{background:#4ADE80}
|
||
.urlbar{flex:1;height:11px;background:#D8D4CE;border-radius:3px;margin-left:6px;display:flex;align-items:center;padding:0 5px}
|
||
.urlbar span{font-size:7.5px;color:#888;font-family:monospace}
|
||
.N{height:42px;background:#0D2240;display:flex;align-items:center;padding:0 16px;gap:12px;flex-shrink:0}
|
||
.logo{font-size:9px;font-weight:900;color:#fff;letter-spacing:1px}
|
||
.nl{font-size:7.5px;color:rgba(255,255,255,.4);font-weight:700;text-transform:uppercase;letter-spacing:.5px}
|
||
.nl.on{color:#fff;border-bottom:2px solid #A6DAD8;padding-bottom:2px}
|
||
.nr{margin-left:auto;display:flex;gap:7px;align-items:center}
|
||
.nico{width:20px;height:20px;background:rgba(255,255,255,.1);border-radius:4px}
|
||
.MAIN{padding:14px 18px;display:flex;flex-direction:column;gap:10px;background:#ECEAE4}
|
||
|
||
/* ── Form elements ─── */
|
||
.CARD{background:#fff;border:1.5px solid #E0DDD6;border-radius:4px;overflow:hidden;padding:14px 18px}
|
||
.CARD-H{font-size:7px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:#AAA;margin-bottom:10px}
|
||
.FL{font-size:7px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#888;margin-bottom:3px}
|
||
.FI{height:24px;border:1.5px solid #D1D5DB;border-radius:3px;background:#fff;display:flex;align-items:center;padding:0 8px;font-size:9px;color:#333;font-family:Georgia,serif}
|
||
.FI.placeholder{color:#C8C4BE;font-style:italic}
|
||
.FI.sm{width:60px}
|
||
.FI.md{width:120px}
|
||
.FTA{height:48px;border:1.5px solid #D1D5DB;border-radius:3px;background:#fff;padding:5px 8px;font-size:9px;color:#C8C4BE;font-style:italic;font-family:Georgia,serif;resize:none}
|
||
.FORM-ROW{display:flex;gap:8px;margin-bottom:8px}
|
||
.FORM-COL{flex:1}
|
||
.FORM-COL.narrow{flex:0 0 60px}
|
||
.FORM-COL.full{flex:0 0 100%}
|
||
|
||
/* ── Segmented control ─── */
|
||
.SEG{display:flex;border:1.5px solid #D1D5DB;border-radius:4px;overflow:hidden;margin-bottom:12px}
|
||
.SEG-BTN{flex:1;text-align:center;font-size:8px;font-weight:700;padding:6px 4px;color:#888;background:#F7F5F2;border-right:1px solid #D1D5DB;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:3px}
|
||
.SEG-BTN:last-child{border-right:none}
|
||
.SEG-BTN.active{background:#002850;color:#fff}
|
||
.SEG-BTN svg{width:10px;height:10px}
|
||
|
||
/* ── PersonCard (detail view) ─── */
|
||
.PC{background:#fff;border:1.5px solid #E0DDD6;border-radius:4px;overflow:hidden}
|
||
.PC-BAR{height:5px;background:#002850}
|
||
.PC-BODY{padding:14px;text-align:center}
|
||
.AV{border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:900;flex-shrink:0;margin:0 auto 8px}
|
||
.AV-lg{width:36px;height:36px;font-size:11px}
|
||
.AV-navy{background:#002850;color:#fff}
|
||
.PC-TITLE{font-size:7px;font-weight:700;text-transform:uppercase;letter-spacing:1px;color:#AAA;margin-bottom:2px}
|
||
.PC-NAME{font-size:11px;font-weight:700;color:#002850;font-family:Georgia,serif;margin-bottom:2px}
|
||
.PC-ALIAS{font-size:8px;color:#888;font-style:italic;margin-bottom:2px}
|
||
.PC-DATES{font-size:8px;color:#AAA;margin-bottom:8px}
|
||
.PC-BADGE{display:inline-flex;align-items:center;gap:3px;font-size:7px;font-weight:600;padding:2px 7px;border-radius:10px;margin-bottom:2px}
|
||
.PC-BADGE.inst{background:#e8eff7;color:#1a4971;border:1px solid #c4d5e8}
|
||
.PC-NOTES{background:#F7F5F2;border:1px solid #E8E4DF;border-radius:3px;padding:6px 8px;font-size:8px;color:#888;text-align:left;margin-bottom:8px;line-height:1.5}
|
||
.PC-NOTES-LABEL{font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#AAA;margin-bottom:3px}
|
||
.PC-BTN{display:flex;align-items:center;justify-content:center;gap:4px;width:100%;border:1.5px solid #E0DDD6;border-radius:3px;padding:5px;font-size:7px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;cursor:pointer}
|
||
|
||
/* ── List card (grid view) ─── */
|
||
.LC{background:#fff;border:1.5px solid #E0DDD6;border-radius:4px;padding:10px;text-align:center;display:flex;flex-direction:column;align-items:center;gap:3px}
|
||
.LC:hover{border-left:3px solid #A6DAD8;box-shadow:0 2px 8px rgba(0,0,0,.08)}
|
||
.AV-sm{width:24px;height:24px;font-size:7px}
|
||
.LC-NAME{font-size:8px;font-weight:700;color:#002850;font-family:Georgia,serif}
|
||
.LC-ALIAS{font-size:7px;color:#888;font-style:italic}
|
||
.LC-DATES{font-size:6.5px;color:#AAA}
|
||
.LC-BADGE{font-size:6px;font-weight:600;padding:1px 5px;border-radius:8px}
|
||
.LC-COUNT{font-size:6.5px;font-weight:600;color:#888;border:1px solid #E0DDD6;border-radius:8px;padding:1px 5px;background:#F7F5F2}
|
||
|
||
/* ── Save bar ─── */
|
||
.SAVE{margin-top:8px;border:1.5px solid #E0DDD6;border-radius:4px;background:#fff;padding:8px 18px;display:flex;align-items:center;justify-content:space-between}
|
||
.SAVE-DISCARD{font-size:8px;color:#888;font-weight:600}
|
||
.SAVE-BTN{background:#002850;color:#fff;font-size:7px;font-weight:800;padding:5px 16px;border-radius:3px;text-transform:uppercase;letter-spacing:.8px}
|
||
|
||
/* ── Back link ─── */
|
||
.BACK{font-size:7px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:#AAA;margin-bottom:4px;display:flex;align-items:center;gap:3px}
|
||
.BACK-ARROW{font-size:9px}
|
||
.PAGE-TITLE{font-size:15px;font-weight:700;color:#002850;font-family:Georgia,serif;margin-bottom:10px}
|
||
|
||
/* ── Changelog / decision list ─── */
|
||
.CHANGES{background:#fff;border:1.5px solid #E0DDD6;border-radius:8px;padding:20px 24px;margin-bottom:40px}
|
||
.CHANGES h2{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid #E8E4DF}
|
||
.CHANGES-GRID{display:grid;grid-template-columns:1fr 1fr;gap:16px}
|
||
.C-COL h3{font-size:10px;font-weight:800;color:#444;margin-bottom:8px}
|
||
.C-COL ul{list-style:none;display:flex;flex-direction:column;gap:5px}
|
||
.C-COL ul li{font-size:11px;color:#555;padding-left:16px;position:relative;line-height:1.5}
|
||
.C-COL.new li::before{content:'✦';position:absolute;left:0;color:#002850;font-size:8px}
|
||
.C-COL.keep li::before{content:'→';position:absolute;left:0;color:#888}
|
||
|
||
/* ── Impl notes ─── */
|
||
.IMPL{background:#0D2240;border-radius:8px;padding:20px 24px;margin-top:48px}
|
||
.IMPL h2{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:rgba(255,255,255,.4);margin-bottom:16px;padding-bottom:10px;border-bottom:1px solid rgba(255,255,255,.08)}
|
||
.IMPL-GRID{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px}
|
||
.IMPL-COL h3{font-size:9.5px;font-weight:800;color:rgba(255,255,255,.6);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px}
|
||
.IMPL-COL ul{list-style:none;display:flex;flex-direction:column;gap:5px}
|
||
.IMPL-COL ul li{font-size:10.5px;color:rgba(255,255,255,.75);padding-left:14px;position:relative;line-height:1.5}
|
||
.IMPL-COL ul li::before{content:'›';position:absolute;left:0;color:rgba(255,255,255,.3)}
|
||
.IMPL-COL code{font-family:monospace;font-size:9.5px;background:rgba(255,255,255,.08);padding:1px 4px;border-radius:3px;color:#A6DAD8}
|
||
|
||
/* ── Spec disclaimer ─── */
|
||
.spec-disclaimer{background:#FFF8E1;border:1.5px solid #FFC107;border-radius:6px;padding:11px 16px;font-size:11px;color:#6D4C00;margin-bottom:32px;line-height:1.6}
|
||
.spec-disclaimer strong{font-weight:800}
|
||
|
||
/* ── Agent Implementation Reference (impl-ref) ─────────────────────────
|
||
Appears after every visual section. Mockup CSS values are at ~55% scale
|
||
and must NOT be used as implementation values. Use these tables instead.
|
||
──────────────────────────────────────────────────────────────────────── */
|
||
.impl-ref{background:#0d1117;border-radius:8px;margin-top:20px;overflow:hidden;border:1px solid #30363d}
|
||
.impl-ref-hdr{background:#161b22;padding:9px 16px;font-size:9.5px;font-weight:800;color:#f0883e;border-bottom:1px solid #30363d;display:flex;align-items:center;gap:8px;letter-spacing:.4px;text-transform:uppercase}
|
||
.impl-ref-hdr::before{content:'⚙';font-size:12px}
|
||
.impl-ref-hdr span{color:rgba(240,136,62,.55);font-weight:400;margin-left:auto;font-size:9px;text-transform:none;letter-spacing:0}
|
||
.impl-ref table{width:100%;border-collapse:collapse;font-size:10px}
|
||
.impl-ref th{text-align:left;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#8b949e;padding:8px 14px;border-bottom:1px solid #21262d}
|
||
.impl-ref td{padding:6px 14px;border-bottom:1px solid #161b22;vertical-align:top;line-height:1.6;color:#c9d1d9}
|
||
.impl-ref tr:last-child td{border-bottom:none}
|
||
.impl-ref td:first-child{color:#79c0ff;font-weight:700;white-space:nowrap;width:190px}
|
||
.impl-ref td code{font-family:'SFMono-Regular',Consolas,monospace;font-size:9.5px;background:#161b22;color:#a5d6ff;padding:1px 5px;border-radius:3px;white-space:nowrap}
|
||
.impl-ref .ir-px{color:#7ee787;font-family:monospace;font-size:9.5px}
|
||
|
||
/* ── Conditional fields hint ─── */
|
||
.COND{background:#EFF6FF;border:1px solid #93C5FD;border-radius:4px;padding:6px 10px;font-size:8px;color:#1E40AF;margin-top:6px;line-height:1.5}
|
||
.COND strong{font-weight:800}
|
||
|
||
/* ── Mobile chrome ─── */
|
||
.WF-M{background:#fff;border:2px solid #B8B4AE;border-radius:16px;overflow:hidden;box-shadow:0 4px 18px rgba(0,0,0,.08);width:220px}
|
||
.WF-M-STATUS{height:18px;background:#0D2240;display:flex;align-items:center;justify-content:space-between;padding:0 10px}
|
||
.WF-M-TIME{font-size:7px;color:#fff;font-weight:700}
|
||
.WF-M-ICONS{display:flex;gap:3px}
|
||
.WF-M-ICON{width:6px;height:6px;background:rgba(255,255,255,.5);border-radius:1px}
|
||
.N-M{height:40px;background:#0D2240;display:flex;align-items:center;padding:0 12px;justify-content:space-between}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="doc">
|
||
|
||
<!-- ══════════════════════════════════
|
||
MASTHEAD
|
||
══════════════════════════════════ -->
|
||
<div class="mast">
|
||
<div class="mast-top">
|
||
<div>
|
||
<h1>Person Title & Type Fields — Design Spec</h1>
|
||
<p>Surface the existing <code style="background:rgba(255,255,255,.12);padding:1px 4px;border-radius:3px;color:#A6DAD8;font-size:11px">title</code> and <code style="background:rgba(255,255,255,.12);padding:1px 4px;border-radius:3px;color:#A6DAD8;font-size:11px">personType</code> fields in the person edit/create forms, detail card, and list cards. Adds conditional field visibility based on entity type.</p>
|
||
</div>
|
||
<span class="mast-badge mb-final">Final · Ready for implementation</span>
|
||
</div>
|
||
<div class="decisions">
|
||
<div class="dec">
|
||
<div class="dec-label">Title field</div>
|
||
<div class="dec-value"><s>Backend only</s><br>→ Editable in forms</div>
|
||
</div>
|
||
<div class="dec">
|
||
<div class="dec-label">Type selector</div>
|
||
<div class="dec-value"><s>Import only</s><br>→ Segmented control</div>
|
||
</div>
|
||
<div class="dec">
|
||
<div class="dec-label">SKIP value</div>
|
||
<div class="dec-value">Hidden from UI<br>(import-only type)</div>
|
||
</div>
|
||
<div class="dec">
|
||
<div class="dec-label">Conditional fields</div>
|
||
<div class="dec-value">Institution/Group hide<br>title, firstName, birth/death</div>
|
||
</div>
|
||
<div class="dec">
|
||
<div class="dec-label">Detail card</div>
|
||
<div class="dec-value">Title above name<br>small-caps label</div>
|
||
</div>
|
||
<div class="dec">
|
||
<div class="dec-label">List card</div>
|
||
<div class="dec-value">No change needed<br>displayName includes title</div>
|
||
</div>
|
||
<div class="dec">
|
||
<div class="dec-label">Mobile</div>
|
||
<div class="dec-value">Title + firstName share row<br>Type stacks vertically</div>
|
||
</div>
|
||
<div class="dec">
|
||
<div class="dec-label">Backend DTO</div>
|
||
<div class="dec-value">Add <code style="color:#A6DAD8;font-size:8px">personType</code><br>to PersonUpdateDTO</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="spec-disclaimer">
|
||
<strong>📐 Mockup scale notice —</strong> all font-size, height, padding, and spacing values in the mockup CSS below are scaled to ~55% of their real implementation values so they fit on screen. <strong>Do not copy sizes from the mockup HTML/CSS.</strong> Each section ends with an <strong>⚙ Implementation Reference</strong> table listing the exact Tailwind classes and real pixel values to use in code.
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════
|
||
CHANGES
|
||
══════════════════════════════════ -->
|
||
<div class="CHANGES">
|
||
<h2>What changes vs. current implementation</h2>
|
||
<div class="CHANGES-GRID">
|
||
<div class="C-COL new">
|
||
<h3>New / changed</h3>
|
||
<ul>
|
||
<li>PersonEditForm: segmented type control added above name fields</li>
|
||
<li>PersonEditForm: <code>title</code> input (narrow column) added before firstName</li>
|
||
<li>PersonEditForm: conditional visibility — INSTITUTION/GROUP hide title, firstName, birthYear, deathYear; lastName relabeled to "Name"</li>
|
||
<li>PersonEditForm: UNKNOWN shows lastName + notes only</li>
|
||
<li>New person form: same type selector + title field + conditional logic</li>
|
||
<li>PersonCard (detail): title rendered above displayName as small-caps label</li>
|
||
<li>PersonUpdateDTO (backend): add <code>personType</code> field</li>
|
||
<li>+page.server.ts (edit & new): read and submit <code>title</code> + <code>personType</code></li>
|
||
</ul>
|
||
</div>
|
||
<div class="C-COL keep">
|
||
<h3>Kept unchanged</h3>
|
||
<ul>
|
||
<li>PersonTypeBadge component — still used as-is in detail + list</li>
|
||
<li>List card layout — displayName already includes title via backend formatter</li>
|
||
<li>Avatar logic — type-based icons vs. initials unchanged</li>
|
||
<li>NameHistoryEditCard — no changes</li>
|
||
<li>PersonDangerZone — no changes</li>
|
||
<li>PersonEditSaveBar — no changes</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════
|
||
SECTION 1 — EDIT FORM: PERSON TYPE
|
||
══════════════════════════════════ -->
|
||
<div class="sec">
|
||
<div class="sec-h"><span class="sec-num">1</span> Edit Form — Type Selector & Title Field</div>
|
||
|
||
<div class="sg sg-2">
|
||
<!-- ── Desktop: PERSON type selected ── -->
|
||
<div class="sb">
|
||
<div class="sl">Person selected <span class="sz">max-w-2xl</span> <span class="state st-changed">changed</span></div>
|
||
<div class="wf">
|
||
<div class="wf-bar"><div class="dot r"></div><div class="dot y"></div><div class="dot g"></div><div class="urlbar"><span>/persons/abc-123/edit</span></div></div>
|
||
<div class="N"><span class="logo">FAMILIENARCHIV</span><span class="nl">Dokumente</span><span class="nl on">Personen</span><span class="nr"><div class="nico"></div></span></div>
|
||
<div class="MAIN">
|
||
<!-- Back link -->
|
||
<div class="BACK"><span class="BACK-ARROW">←</span> Prof. Dr. Heinrich Raddatz</div>
|
||
<div class="PAGE-TITLE">Person bearbeiten</div>
|
||
|
||
<div class="CARD">
|
||
<div class="CARD-H">Details</div>
|
||
|
||
<!-- Type selector — segmented control -->
|
||
<div class="SEG">
|
||
<div class="SEG-BTN active">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
|
||
Person
|
||
</div>
|
||
<div class="SEG-BTN">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0H5m14 0h2m-2 0v-2M5 21H3m2 0v-2m4-12h2m-2 4h2m4-4h2m-2 4h2"/></svg>
|
||
Institution
|
||
</div>
|
||
<div class="SEG-BTN">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
||
Gruppe
|
||
</div>
|
||
<div class="SEG-BTN">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01"/></svg>
|
||
Unbekannt
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Name row: title (narrow) + firstName + lastName -->
|
||
<div class="FORM-ROW">
|
||
<div class="FORM-COL narrow">
|
||
<div class="FL">Titel</div>
|
||
<div class="FI sm">Prof. Dr.</div>
|
||
</div>
|
||
<div class="FORM-COL">
|
||
<div class="FL">Vorname *</div>
|
||
<div class="FI">Heinrich</div>
|
||
</div>
|
||
<div class="FORM-COL">
|
||
<div class="FL">Nachname *</div>
|
||
<div class="FI">Raddatz</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Alias row -->
|
||
<div class="FORM-ROW">
|
||
<div class="FORM-COL">
|
||
<div class="FL">Alias</div>
|
||
<div class="FI placeholder">z.B. Spitzname, Geburtsname…</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Year row -->
|
||
<div class="FORM-ROW">
|
||
<div class="FORM-COL">
|
||
<div class="FL">Geburtsjahr</div>
|
||
<div class="FI">1878</div>
|
||
</div>
|
||
<div class="FORM-COL">
|
||
<div class="FL">Sterbejahr</div>
|
||
<div class="FI">1944</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Notes -->
|
||
<div class="FORM-ROW">
|
||
<div class="FORM-COL">
|
||
<div class="FL">Notizen</div>
|
||
<div class="FTA">Biografische Notizen…</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="SAVE">
|
||
<span class="SAVE-DISCARD">Verwerfen</span>
|
||
<span class="SAVE-BTN">Speichern</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="sc">PERSON type: all fields visible. Title field is a narrow input (max-w-[120px]) before firstName. Three columns on md+.</div>
|
||
</div>
|
||
|
||
<!-- ── Desktop: INSTITUTION type selected ── -->
|
||
<div class="sb">
|
||
<div class="sl">Institution selected <span class="sz">max-w-2xl</span> <span class="state st-new">new behavior</span></div>
|
||
<div class="wf">
|
||
<div class="wf-bar"><div class="dot r"></div><div class="dot y"></div><div class="dot g"></div><div class="urlbar"><span>/persons/def-456/edit</span></div></div>
|
||
<div class="N"><span class="logo">FAMILIENARCHIV</span><span class="nl">Dokumente</span><span class="nl on">Personen</span><span class="nr"><div class="nico"></div></span></div>
|
||
<div class="MAIN">
|
||
<div class="BACK"><span class="BACK-ARROW">←</span> Reichsfechtschule Leipzig</div>
|
||
<div class="PAGE-TITLE">Person bearbeiten</div>
|
||
|
||
<div class="CARD">
|
||
<div class="CARD-H">Details</div>
|
||
|
||
<!-- Type selector — INSTITUTION active -->
|
||
<div class="SEG">
|
||
<div class="SEG-BTN">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
|
||
Person
|
||
</div>
|
||
<div class="SEG-BTN active">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0H5m14 0h2m-2 0v-2M5 21H3m2 0v-2m4-12h2m-2 4h2m4-4h2m-2 4h2"/></svg>
|
||
Institution
|
||
</div>
|
||
<div class="SEG-BTN">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
||
Gruppe
|
||
</div>
|
||
<div class="SEG-BTN">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01"/></svg>
|
||
Unbekannt
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Name row: only "Name" (full width) — title + firstName hidden -->
|
||
<div class="FORM-ROW">
|
||
<div class="FORM-COL">
|
||
<div class="FL">Name *</div>
|
||
<div class="FI">Reichsfechtschule Leipzig</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Alias row -->
|
||
<div class="FORM-ROW">
|
||
<div class="FORM-COL">
|
||
<div class="FL">Alias</div>
|
||
<div class="FI placeholder">z.B. Kurzname, früherer Name…</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Notes (no birth/death year) -->
|
||
<div class="FORM-ROW">
|
||
<div class="FORM-COL">
|
||
<div class="FL">Notizen</div>
|
||
<div class="FTA">Historische Anmerkungen…</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="COND">
|
||
<strong>Conditional fields:</strong> Title, Vorname, Geburtsjahr, and Sterbejahr are hidden when type is INSTITUTION or GROUP. The "Nachname" label changes to "Name".
|
||
</div>
|
||
</div>
|
||
|
||
<div class="SAVE">
|
||
<span class="SAVE-DISCARD">Verwerfen</span>
|
||
<span class="SAVE-BTN">Speichern</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="sc">INSTITUTION type: title, firstName, birthYear, deathYear hidden. "Nachname" relabeled to "Name". Same layout for GROUP type.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- impl-ref -->
|
||
<div class="impl-ref">
|
||
<div class="impl-ref-hdr">Implementation Reference — Type Selector & Form Fields
|
||
<span>Real values · mockup above is ~55% scale</span>
|
||
</div>
|
||
<table>
|
||
<thead><tr><th>Element</th><th>Tailwind classes</th><th>Real size</th><th>Notes</th></tr></thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Type selector container</td>
|
||
<td><code>flex rounded-lg border border-line overflow-hidden mb-5</code></td>
|
||
<td><span class="ir-px">h ~44px per button</span></td>
|
||
<td>Use <code>role="radiogroup"</code>, each button <code>role="radio"</code> with <code>aria-checked</code>. Arrow key navigation. Hidden <code><input type="hidden" name="personType"></code> holds the value.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Type button (inactive)</td>
|
||
<td><code>flex-1 flex items-center justify-center gap-1.5 text-sm font-bold text-ink-3 bg-muted border-r border-line cursor-pointer py-2.5</code></td>
|
||
<td><span class="ir-px">14px / 700, py 10px</span></td>
|
||
<td>Last button: no <code>border-r</code>. Hover: <code>bg-surface</code>.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Type button (active)</td>
|
||
<td><code>flex-1 flex items-center justify-center gap-1.5 text-sm font-bold text-primary-fg bg-primary py-2.5</code></td>
|
||
<td><span class="ir-px">14px / 700</span></td>
|
||
<td>Active state replaces bg + text color. Icon inherits <code>currentColor</code>.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Type button icon</td>
|
||
<td><code>w-4 h-4</code></td>
|
||
<td><span class="ir-px">16px</span></td>
|
||
<td>Same SVG paths as PersonTypeBadge + person silhouette for PERSON.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Title input</td>
|
||
<td><code>block w-full max-w-[120px] rounded border border-line px-3 py-2 font-serif text-ink focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring</code></td>
|
||
<td><span class="ir-px">max-w 120px, py 8px</span></td>
|
||
<td><code>aria-label</code> = i18n key for "Akademischer Titel". Only visible when type = PERSON.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Title label</td>
|
||
<td><code>mb-1 block text-xs font-bold tracking-widest text-ink-3 uppercase</code></td>
|
||
<td><span class="ir-px">12px / 700</span></td>
|
||
<td>Matches existing label pattern. Label text: paraglide key <code>form_label_title</code>.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Name row (PERSON)</td>
|
||
<td><code>grid grid-cols-[120px_1fr_1fr] gap-4 md:grid-cols-[120px_1fr_1fr]</code></td>
|
||
<td><span class="ir-px">gap 16px</span></td>
|
||
<td>Mobile (<md): <code>grid-cols-[80px_1fr]</code> — title + firstName share row, lastName goes below full-width.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Name row (INSTITUTION/GROUP)</td>
|
||
<td><code>grid grid-cols-1 gap-4</code></td>
|
||
<td><span class="ir-px">full width</span></td>
|
||
<td>Single "Name" field using the <code>lastName</code> input. Label changes via conditional: <code>form_label_name</code> i18n key.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Conditional visibility</td>
|
||
<td colspan="2">Svelte <code>{#if selectedType === 'PERSON'}</code></td>
|
||
<td>Hide: title, firstName, birthYear, deathYear for INSTITUTION/GROUP. Hide: title, firstName, birthYear, deathYear, alias for UNKNOWN. Use <code>$state</code> for reactive type.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════
|
||
SECTION 2 — NEW PERSON FORM
|
||
══════════════════════════════════ -->
|
||
<div class="sec">
|
||
<div class="sec-h"><span class="sec-num">2</span> New Person Form</div>
|
||
|
||
<div class="sg sg-2">
|
||
<!-- ── Desktop: default (PERSON) ── -->
|
||
<div class="sb">
|
||
<div class="sl">Default state — Person <span class="sz">max-w-2xl</span> <span class="state st-changed">changed</span></div>
|
||
<div class="wf">
|
||
<div class="wf-bar"><div class="dot r"></div><div class="dot y"></div><div class="dot g"></div><div class="urlbar"><span>/persons/new</span></div></div>
|
||
<div class="N"><span class="logo">FAMILIENARCHIV</span><span class="nl">Dokumente</span><span class="nl on">Personen</span><span class="nr"><div class="nico"></div></span></div>
|
||
<div class="MAIN">
|
||
<div class="BACK"><span class="BACK-ARROW">←</span> Zurück zur Übersicht</div>
|
||
<div class="PAGE-TITLE">Neue Person</div>
|
||
|
||
<div class="CARD">
|
||
<div class="CARD-H">Details</div>
|
||
|
||
<!-- Type selector — PERSON active (default) -->
|
||
<div class="SEG">
|
||
<div class="SEG-BTN active">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
|
||
Person
|
||
</div>
|
||
<div class="SEG-BTN">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0H5m14 0h2m-2 0v-2M5 21H3m2 0v-2m4-12h2m-2 4h2m4-4h2m-2 4h2"/></svg>
|
||
Institution
|
||
</div>
|
||
<div class="SEG-BTN">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
||
Gruppe
|
||
</div>
|
||
<div class="SEG-BTN">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01"/></svg>
|
||
Unbekannt
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Name row with title -->
|
||
<div class="FORM-ROW">
|
||
<div class="FORM-COL narrow">
|
||
<div class="FL">Titel</div>
|
||
<div class="FI sm placeholder">Dr.</div>
|
||
</div>
|
||
<div class="FORM-COL">
|
||
<div class="FL">Vorname *</div>
|
||
<div class="FI placeholder">Vorname</div>
|
||
</div>
|
||
<div class="FORM-COL">
|
||
<div class="FL">Nachname *</div>
|
||
<div class="FI placeholder">Nachname</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="FORM-ROW">
|
||
<div class="FORM-COL">
|
||
<div class="FL">Alias</div>
|
||
<div class="FI placeholder">z.B. Spitzname, Geburtsname…</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="FORM-ROW">
|
||
<div class="FORM-COL">
|
||
<div class="FL">Geburtsjahr</div>
|
||
<div class="FI placeholder">z.B. 1920</div>
|
||
</div>
|
||
<div class="FORM-COL">
|
||
<div class="FL">Sterbejahr</div>
|
||
<div class="FI placeholder">z.B. 1995</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="FORM-ROW">
|
||
<div class="FORM-COL">
|
||
<div class="FL">Notizen</div>
|
||
<div class="FTA">Biografische Notizen…</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="SAVE">
|
||
<span class="SAVE-DISCARD">Abbrechen</span>
|
||
<span class="SAVE-BTN">Erstellen</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="sc">New person form defaults to PERSON type. Same segmented control + title field as edit form. All fields empty with placeholders.</div>
|
||
</div>
|
||
|
||
<!-- ── Mobile: PERSON type ── -->
|
||
<div class="sb">
|
||
<div class="sl">Mobile — Person <span class="sz">375px</span> <span class="state st-new">responsive</span></div>
|
||
<div class="WF-M">
|
||
<div class="WF-M-STATUS"><span class="WF-M-TIME">9:41</span><div class="WF-M-ICONS"><div class="WF-M-ICON"></div><div class="WF-M-ICON"></div><div class="WF-M-ICON"></div></div></div>
|
||
<div class="N-M"><span class="logo" style="font-size:7px">FAMILIENARCHIV</span><div class="nico" style="width:16px;height:16px"></div></div>
|
||
<div style="padding:10px 12px;background:#ECEAE4">
|
||
<div class="BACK" style="font-size:6px;margin-bottom:3px"><span class="BACK-ARROW">←</span> Zurück</div>
|
||
<div style="font-size:11px;font-weight:700;color:#002850;font-family:Georgia,serif;margin-bottom:8px">Neue Person</div>
|
||
|
||
<div style="background:#fff;border:1px solid #E0DDD6;border-radius:4px;padding:10px 12px">
|
||
<div style="font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#AAA;margin-bottom:6px">Details</div>
|
||
|
||
<!-- Type selector — stacked 2x2 on mobile -->
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;border:1px solid #D1D5DB;border-radius:3px;overflow:hidden;margin-bottom:8px">
|
||
<div style="text-align:center;font-size:6.5px;font-weight:700;padding:5px 2px;background:#002850;color:#fff;border-right:1px solid #D1D5DB;border-bottom:1px solid #D1D5DB">Person</div>
|
||
<div style="text-align:center;font-size:6.5px;font-weight:700;padding:5px 2px;color:#888;background:#F7F5F2;border-bottom:1px solid #D1D5DB">Institution</div>
|
||
<div style="text-align:center;font-size:6.5px;font-weight:700;padding:5px 2px;color:#888;background:#F7F5F2;border-right:1px solid #D1D5DB">Gruppe</div>
|
||
<div style="text-align:center;font-size:6.5px;font-weight:700;padding:5px 2px;color:#888;background:#F7F5F2">Unbekannt</div>
|
||
</div>
|
||
|
||
<!-- Title + firstName share a row -->
|
||
<div style="display:flex;gap:4px;margin-bottom:4px">
|
||
<div style="flex:0 0 50px">
|
||
<div style="font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.3px;color:#888;margin-bottom:2px">Titel</div>
|
||
<div class="FI" style="height:20px;font-size:7px;padding:0 4px;color:#C8C4BE;font-style:italic">Dr.</div>
|
||
</div>
|
||
<div style="flex:1">
|
||
<div style="font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.3px;color:#888;margin-bottom:2px">Vorname *</div>
|
||
<div class="FI" style="height:20px;font-size:7px;padding:0 4px"></div>
|
||
</div>
|
||
</div>
|
||
<div style="margin-bottom:4px">
|
||
<div style="font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.3px;color:#888;margin-bottom:2px">Nachname *</div>
|
||
<div class="FI" style="height:20px;font-size:7px;padding:0 4px"></div>
|
||
</div>
|
||
<div style="margin-bottom:4px">
|
||
<div style="font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.3px;color:#888;margin-bottom:2px">Alias</div>
|
||
<div class="FI" style="height:20px;font-size:7px;padding:0 4px"></div>
|
||
</div>
|
||
<div style="display:flex;gap:4px;margin-bottom:4px">
|
||
<div style="flex:1">
|
||
<div style="font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.3px;color:#888;margin-bottom:2px">Geburtsjahr</div>
|
||
<div class="FI" style="height:20px;font-size:7px;padding:0 4px"></div>
|
||
</div>
|
||
<div style="flex:1">
|
||
<div style="font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.3px;color:#888;margin-bottom:2px">Sterbejahr</div>
|
||
<div class="FI" style="height:20px;font-size:7px;padding:0 4px"></div>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div style="font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.3px;color:#888;margin-bottom:2px">Notizen</div>
|
||
<div style="height:32px;border:1px solid #D1D5DB;border-radius:3px;background:#fff"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-top:6px;border:1px solid #E0DDD6;border-radius:4px;background:#fff;padding:6px 12px;display:flex;align-items:center;justify-content:space-between">
|
||
<span style="font-size:7px;color:#888">Abbrechen</span>
|
||
<span style="background:#002850;color:#fff;font-size:6px;font-weight:800;padding:4px 12px;border-radius:3px;text-transform:uppercase;letter-spacing:.6px">Erstellen</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="sc">Mobile: type selector wraps to 2×2 grid. Title + firstName share a row (title ~80px, firstName fills rest). lastName goes full-width below.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- impl-ref -->
|
||
<div class="impl-ref">
|
||
<div class="impl-ref-hdr">Implementation Reference — New Person Form
|
||
<span>Real values · mockup above is ~55% scale</span>
|
||
</div>
|
||
<table>
|
||
<thead><tr><th>Element</th><th>Tailwind classes</th><th>Real size</th><th>Notes</th></tr></thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Type selector (mobile)</td>
|
||
<td><code>grid grid-cols-2 rounded-lg border border-line overflow-hidden mb-5</code></td>
|
||
<td><span class="ir-px">min-h-[44px] per button</span></td>
|
||
<td>Below <code>md</code> breakpoint: 2×2 grid. Above <code>md</code>: single row (flex). Same <code>role="radiogroup"</code> pattern.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Title + firstName row (mobile)</td>
|
||
<td><code>flex gap-4</code></td>
|
||
<td><span class="ir-px">title w-[80px], firstName flex-1</span></td>
|
||
<td>Below <code>md</code>: title shrinks to 80px. Above <code>md</code>: title gets 120px in the 3-col grid.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Default personType</td>
|
||
<td colspan="2"><code>let selectedType = $state(person?.personType ?? 'PERSON')</code></td>
|
||
<td>New person defaults to PERSON. Edit form uses existing value.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Hidden input</td>
|
||
<td><code><input type="hidden" name="personType" value={selectedType}></code></td>
|
||
<td>—</td>
|
||
<td>Submits type with the form. Read in +page.server.ts via <code>formData.get('personType')</code>.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════
|
||
SECTION 3 — PERSON CARD (DETAIL VIEW)
|
||
══════════════════════════════════ -->
|
||
<div class="sec">
|
||
<div class="sec-h"><span class="sec-num">3</span> Person Detail Card — Title Display</div>
|
||
|
||
<div class="sg sg-4">
|
||
<!-- ── Person with title ── -->
|
||
<div class="sb">
|
||
<div class="sl">Person with title <span class="state st-changed">changed</span></div>
|
||
<div class="PC">
|
||
<div class="PC-BAR"></div>
|
||
<div class="PC-BODY">
|
||
<div class="AV AV-lg AV-navy">HR</div>
|
||
<div class="PC-TITLE">Prof. Dr.</div>
|
||
<div class="PC-NAME">Heinrich Raddatz</div>
|
||
<div class="PC-ALIAS">„Der Professor“</div>
|
||
<div class="PC-DATES">1878 – 1944</div>
|
||
<div class="PC-NOTES">
|
||
<div class="PC-NOTES-LABEL">Notizen</div>
|
||
Professor der Germanistik in Leipzig.
|
||
</div>
|
||
<div class="PC-BTN">✎ Bearbeiten</div>
|
||
</div>
|
||
</div>
|
||
<div class="sc">Title shown as small-caps label above the serif display name. Color: text-ink-3.</div>
|
||
</div>
|
||
|
||
<!-- ── Person without title ── -->
|
||
<div class="sb">
|
||
<div class="sl">Person, no title <span class="state st-changed">unchanged look</span></div>
|
||
<div class="PC">
|
||
<div class="PC-BAR"></div>
|
||
<div class="PC-BODY">
|
||
<div class="AV AV-lg AV-navy">MR</div>
|
||
<div class="PC-NAME">Martha Raddatz</div>
|
||
<div class="PC-DATES">1882 – 1961</div>
|
||
<div class="PC-BTN">✎ Bearbeiten</div>
|
||
</div>
|
||
</div>
|
||
<div class="sc">No title field → no label rendered. Layout collapses naturally.</div>
|
||
</div>
|
||
|
||
<!-- ── Institution ── -->
|
||
<div class="sb">
|
||
<div class="sl">Institution <span class="state st-changed">unchanged look</span></div>
|
||
<div class="PC">
|
||
<div class="PC-BAR"></div>
|
||
<div class="PC-BODY">
|
||
<div class="AV AV-lg AV-navy" style="font-family:sans-serif;font-size:14px;font-weight:400">🏛</div>
|
||
<div class="PC-NAME">Reichsfechtschule Leipzig</div>
|
||
<div class="PC-BADGE inst">
|
||
<svg style="width:8px;height:8px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0H5m14 0h2m-2 0v-2M5 21H3m2 0v-2m4-12h2m-2 4h2m4-4h2m-2 4h2"/></svg>
|
||
Institution
|
||
</div>
|
||
<div class="PC-BTN">✎ Bearbeiten</div>
|
||
</div>
|
||
</div>
|
||
<div class="sc">Institutions never show a title line. PersonTypeBadge already renders.</div>
|
||
</div>
|
||
|
||
<!-- ── Group ── -->
|
||
<div class="sb">
|
||
<div class="sl">Group <span class="state st-changed">unchanged look</span></div>
|
||
<div class="PC">
|
||
<div class="PC-BAR"></div>
|
||
<div class="PC-BODY">
|
||
<div class="AV AV-lg AV-navy" style="font-family:sans-serif;font-size:14px;font-weight:400">👥</div>
|
||
<div class="PC-NAME">Familie Müller</div>
|
||
<div class="PC-BADGE" style="background:#f0e8f5;color:#5a2d6f;border:1px solid #d8c5e3">
|
||
<svg style="width:8px;height:8px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
||
Gruppe
|
||
</div>
|
||
<div class="PC-BTN">✎ Bearbeiten</div>
|
||
</div>
|
||
</div>
|
||
<div class="sc">Groups never show a title line either. Badge + icon avatar as before.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- impl-ref -->
|
||
<div class="impl-ref">
|
||
<div class="impl-ref-hdr">Implementation Reference — PersonCard Title Display
|
||
<span>Real values · mockup above is ~55% scale</span>
|
||
</div>
|
||
<table>
|
||
<thead><tr><th>Element</th><th>Tailwind classes</th><th>Real size</th><th>Notes</th></tr></thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Title label</td>
|
||
<td><code>text-xs font-bold uppercase tracking-widest text-ink-3 text-center</code></td>
|
||
<td><span class="ir-px">12px / 700</span></td>
|
||
<td>Only render when <code>person.title</code> is truthy AND <code>personType === 'PERSON'</code>. Placed between avatar and displayName.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Title spacing</td>
|
||
<td><code>mb-0.5</code></td>
|
||
<td><span class="ir-px">2px</span></td>
|
||
<td>Tight spacing to displayName below. When absent, avatar's mb-4 flows directly to name.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>PersonCard prop</td>
|
||
<td colspan="2"><code>title?: string | null</code> added to the person type</td>
|
||
<td>Already in PersonSummaryDTO. Pass from +page.server.ts load.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════
|
||
SECTION 4 — CONDITIONAL FIELD MATRIX
|
||
══════════════════════════════════ -->
|
||
<div class="sec">
|
||
<div class="sec-h"><span class="sec-num">4</span> Conditional Field Visibility Matrix</div>
|
||
|
||
<div style="background:#fff;border:1.5px solid #E0DDD6;border-radius:8px;overflow:hidden;margin-bottom:20px">
|
||
<table style="width:100%;border-collapse:collapse;font-size:11px">
|
||
<thead>
|
||
<tr style="background:#F0EDE6">
|
||
<th style="text-align:left;padding:10px 16px;font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;border-bottom:2px solid #E0DDD6">Field</th>
|
||
<th style="text-align:center;padding:10px 16px;font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#002850;border-bottom:2px solid #E0DDD6">Person</th>
|
||
<th style="text-align:center;padding:10px 16px;font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#1a4971;border-bottom:2px solid #E0DDD6">Institution</th>
|
||
<th style="text-align:center;padding:10px 16px;font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#5a2d6f;border-bottom:2px solid #E0DDD6">Group</th>
|
||
<th style="text-align:center;padding:10px 16px;font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#7a5a0a;border-bottom:2px solid #E0DDD6">Unknown</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr style="border-bottom:1px solid #F0EDE6">
|
||
<td style="padding:8px 16px;font-weight:700;color:#333">Title</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#166534;font-weight:700">✓ visible</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#DC2626">hidden</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#DC2626">hidden</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#DC2626">hidden</td>
|
||
</tr>
|
||
<tr style="border-bottom:1px solid #F0EDE6;background:#FAFAF8">
|
||
<td style="padding:8px 16px;font-weight:700;color:#333">firstName</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#166534;font-weight:700">✓ "Vorname"</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#DC2626">hidden</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#DC2626">hidden</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#DC2626">hidden</td>
|
||
</tr>
|
||
<tr style="border-bottom:1px solid #F0EDE6">
|
||
<td style="padding:8px 16px;font-weight:700;color:#333">lastName</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#166534;font-weight:700">✓ "Nachname"</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#166534;font-weight:700">✓ "Name"</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#166534;font-weight:700">✓ "Gruppenname"</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#166534;font-weight:700">✓ "Name"</td>
|
||
</tr>
|
||
<tr style="border-bottom:1px solid #F0EDE6;background:#FAFAF8">
|
||
<td style="padding:8px 16px;font-weight:700;color:#333">alias</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#166534;font-weight:700">✓ visible</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#166534;font-weight:700">✓ visible</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#166534;font-weight:700">✓ visible</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#DC2626">hidden</td>
|
||
</tr>
|
||
<tr style="border-bottom:1px solid #F0EDE6">
|
||
<td style="padding:8px 16px;font-weight:700;color:#333">birthYear</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#166534;font-weight:700">✓ "Geburtsjahr"</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#DC2626">hidden</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#DC2626">hidden</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#DC2626">hidden</td>
|
||
</tr>
|
||
<tr style="border-bottom:1px solid #F0EDE6;background:#FAFAF8">
|
||
<td style="padding:8px 16px;font-weight:700;color:#333">deathYear</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#166534;font-weight:700">✓ "Sterbejahr"</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#DC2626">hidden</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#DC2626">hidden</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#DC2626">hidden</td>
|
||
</tr>
|
||
<tr>
|
||
<td style="padding:8px 16px;font-weight:700;color:#333">notes</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#166534;font-weight:700">✓ visible</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#166534;font-weight:700">✓ visible</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#166534;font-weight:700">✓ visible</td>
|
||
<td style="text-align:center;padding:8px 16px;color:#166534;font-weight:700">✓ visible</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="ann-block">
|
||
<strong>Key behavioral note:</strong> When switching type in the segmented control, hidden fields are not cleared — their values are preserved in case the user switches back. The backend already handles null/empty values gracefully. Only the <code>personType</code> value changes on submission.
|
||
</div>
|
||
|
||
<!-- impl-ref -->
|
||
<div class="impl-ref">
|
||
<div class="impl-ref-hdr">Implementation Reference — Conditional Field Logic
|
||
<span>Real values · Svelte reactivity</span>
|
||
</div>
|
||
<table>
|
||
<thead><tr><th>Element</th><th>Tailwind classes</th><th>Real size</th><th>Notes</th></tr></thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Type state</td>
|
||
<td><code>let selectedType = $state<PersonType>('PERSON')</code></td>
|
||
<td>—</td>
|
||
<td>Reactive state drives all conditionals. Type imported from generated API types.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>showPersonFields</td>
|
||
<td><code>const showPersonFields = $derived(selectedType === 'PERSON')</code></td>
|
||
<td>—</td>
|
||
<td>Controls title, firstName, birthYear, deathYear visibility.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>showAlias</td>
|
||
<td><code>const showAlias = $derived(selectedType !== 'UNKNOWN')</code></td>
|
||
<td>—</td>
|
||
<td>UNKNOWN hides alias (too little info to have one).</td>
|
||
</tr>
|
||
<tr>
|
||
<td>lastNameLabel</td>
|
||
<td><code>const lastNameLabel = $derived.by(() => { switch... })</code></td>
|
||
<td>—</td>
|
||
<td>PERSON: <code>form_label_last_name</code>, INSTITUTION: <code>form_label_name</code>, GROUP: <code>form_label_group_name</code>, UNKNOWN: <code>form_label_name</code>. Needs 2 new i18n keys.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>firstName required</td>
|
||
<td colspan="2"><code>required</code> attribute only when <code>selectedType === 'PERSON'</code></td>
|
||
<td>Remove <code>required</code> on firstName when hidden. lastName stays required always.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════
|
||
SECTION 5 — ACCESSIBILITY
|
||
══════════════════════════════════ -->
|
||
<div class="sec">
|
||
<div class="sec-h"><span class="sec-num">5</span> Accessibility Requirements</div>
|
||
|
||
<div style="background:#fff;border:1.5px solid #E0DDD6;border-radius:8px;overflow:hidden">
|
||
<table style="width:100%;border-collapse:collapse;font-size:11px">
|
||
<thead>
|
||
<tr style="background:#F0EDE6">
|
||
<th style="text-align:left;padding:10px 16px;font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;border-bottom:2px solid #E0DDD6;width:200px">Requirement</th>
|
||
<th style="text-align:left;padding:10px 16px;font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;border-bottom:2px solid #E0DDD6">WCAG Criterion</th>
|
||
<th style="text-align:left;padding:10px 16px;font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;border-bottom:2px solid #E0DDD6">Implementation</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr style="border-bottom:1px solid #F0EDE6">
|
||
<td style="padding:8px 16px;font-weight:700;color:#333">Segmented control semantics</td>
|
||
<td style="padding:8px 16px;color:#555">4.1.2 Name, Role, Value</td>
|
||
<td style="padding:8px 16px;color:#555"><code style="font-size:10px">role="radiogroup"</code> on container, <code style="font-size:10px">role="radio"</code> + <code style="font-size:10px">aria-checked</code> on each button. <code style="font-size:10px">aria-label="Typ der Person"</code> on container.</td>
|
||
</tr>
|
||
<tr style="border-bottom:1px solid #F0EDE6;background:#FAFAF8">
|
||
<td style="padding:8px 16px;font-weight:700;color:#333">Keyboard navigation</td>
|
||
<td style="padding:8px 16px;color:#555">2.1.1 Keyboard</td>
|
||
<td style="padding:8px 16px;color:#555">Arrow keys cycle through options. Tab moves out of the group. Space/Enter selects.</td>
|
||
</tr>
|
||
<tr style="border-bottom:1px solid #F0EDE6">
|
||
<td style="padding:8px 16px;font-weight:700;color:#333">Touch target size</td>
|
||
<td style="padding:8px 16px;color:#555">2.5.8 Target Size (Minimum)</td>
|
||
<td style="padding:8px 16px;color:#555">Each segment button: <code style="font-size:10px">min-h-[44px]</code>. Mobile 2×2 grid: same minimum per cell.</td>
|
||
</tr>
|
||
<tr style="border-bottom:1px solid #F0EDE6;background:#FAFAF8">
|
||
<td style="padding:8px 16px;font-weight:700;color:#333">Title input label</td>
|
||
<td style="padding:8px 16px;color:#555">1.3.1 Info and Relationships</td>
|
||
<td style="padding:8px 16px;color:#555">Visible <code style="font-size:10px"><label for="title"></code>. Also <code style="font-size:10px">aria-describedby</code> linking to help text if placeholder alone is ambiguous.</td>
|
||
</tr>
|
||
<tr style="border-bottom:1px solid #F0EDE6">
|
||
<td style="padding:8px 16px;font-weight:700;color:#333">Hidden field announcement</td>
|
||
<td style="padding:8px 16px;color:#555">4.1.2 Name, Role, Value</td>
|
||
<td style="padding:8px 16px;color:#555">When type changes, use <code style="font-size:10px">aria-live="polite"</code> region to announce which fields are now visible. E.g., "Felder für Person angezeigt".</td>
|
||
</tr>
|
||
<tr>
|
||
<td style="padding:8px 16px;font-weight:700;color:#333">Focus management</td>
|
||
<td style="padding:8px 16px;color:#555">2.4.3 Focus Order</td>
|
||
<td style="padding:8px 16px;color:#555">Tab order: type selector → title (if visible) → firstName (if visible) → lastName → alias (if visible) → birthYear (if visible) → deathYear (if visible) → notes.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════
|
||
IMPLEMENTATION NOTES
|
||
══════════════════════════════════ -->
|
||
<div class="IMPL">
|
||
<h2>Implementation Notes</h2>
|
||
<div class="IMPL-GRID">
|
||
<div class="IMPL-COL">
|
||
<h3>Backend</h3>
|
||
<ul>
|
||
<li>Add <code>personType</code> field to <code>PersonUpdateDTO</code> with <code>@NotNull</code> validation</li>
|
||
<li><code>PersonService.update()</code>: set <code>person.setPersonType(dto.getPersonType())</code></li>
|
||
<li><code>PersonService.create()</code>: same — accept type from DTO</li>
|
||
<li>Regenerate OpenAPI spec + frontend types after DTO change</li>
|
||
<li><code>SKIP</code> value: exclude from API validation (backend-only, used by import)</li>
|
||
</ul>
|
||
</div>
|
||
<div class="IMPL-COL">
|
||
<h3>Frontend</h3>
|
||
<ul>
|
||
<li>Refactor <code>PersonEditForm.svelte</code>: add <code>selectedType</code> state + segmented control</li>
|
||
<li>Add <code>title</code> input to name row (narrow, max-w-[120px])</li>
|
||
<li>Wrap conditional fields in <code>{#if showPersonFields}</code></li>
|
||
<li>Update <code>PersonCard.svelte</code> prop type: add <code>title</code></li>
|
||
<li>Update both <code>+page.server.ts</code> files: read/submit <code>title</code> + <code>personType</code></li>
|
||
<li>New person form: inline the same type selector + title + conditionals</li>
|
||
</ul>
|
||
</div>
|
||
<div class="IMPL-COL">
|
||
<h3>i18n Keys (new)</h3>
|
||
<ul>
|
||
<li><code>form_label_title</code> — "Titel" / "Title" / "Título"</li>
|
||
<li><code>form_label_name</code> — "Name" / "Name" / "Nombre"</li>
|
||
<li><code>form_label_group_name</code> — "Gruppenname" / "Group name" / "Nombre del grupo"</li>
|
||
<li><code>form_label_person_type</code> — "Typ" / "Type" / "Tipo"</li>
|
||
<li><code>person_type_PERSON</code> — "Person" / "Person" / "Persona"</li>
|
||
<li><code>a11y_type_fields_visible</code> — "Felder für {type} angezeigt"</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div><!-- .doc -->
|
||
</body>
|
||
</html>
|