Files
familienarchiv/docs/specs/geschichten-person-integration-spec.html
2026-05-05 12:39:20 +02:00

503 lines
34 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Geschichten — Personenverknüpfung · Familienarchiv Spec</title>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300;9..144,400;9..144,500&family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet"/>
<style>
:root{--color-page:#FAFAF7;--color-surface:#F5F4EE;--color-subtle:#EDECEA;--color-border:#D8D7D0;--color-text-muted:#6B6A63;--color-text:#1C1C18;--navy:#012851;--mint:#A1DCD8;--sand:#F0EFE9;--turquoise:#00C7B1;--blue-tint:#E6F1FB;--blue:#2D7DD2;--blue-dark:#185FA5;--green-tint:#E8F5EA;--green:#3D8C4A;--green-dark:#2E6E39;--purple-tint:#EEEDFE;--purple:#534AB7;--purple-dark:#3C3489;--font-display:'Fraunces',Georgia,serif;--font-sans:'DM Sans',system-ui,sans-serif;--font-mono:'DM Mono',monospace;--radius-sm:4px;--radius-md:6px;--radius-lg:10px;--radius-xl:16px;--shadow-card:0 1px 3px rgba(28,28,24,.06),0 1px 2px rgba(28,28,24,.04);--shadow-raised:0 4px 12px rgba(28,28,24,.08),0 2px 4px rgba(28,28,24,.04);--shadow-overlay:0 8px 32px rgba(28,28,24,.12),0 2px 8px rgba(28,28,24,.06);}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
body{font-family:var(--font-sans);background:#E8E7E2;color:var(--color-text);font-size:14px;line-height:1.6;}
.doc{max-width:1200px;margin:0 auto;padding:48px 40px 120px;}
.doc-header{display:flex;justify-content:space-between;align-items:flex-end;padding-bottom:28px;border-bottom:1px solid var(--color-border);margin-bottom:48px;background:var(--color-page);margin:-48px -40px 48px;padding:48px 40px 28px;border-radius:var(--radius-xl) var(--radius-xl) 0 0;}
.doc-header h1{font-family:var(--font-display);font-size:28px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
.doc-header p{font-size:13px;color:var(--color-text-muted);max-width:680px;}
.doc-meta{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);text-align:right;line-height:1.9;}
.pill{display:inline-block;padding:2px 8px;border-radius:var(--radius-sm);font-size:10px;font-weight:500;letter-spacing:.05em;}
.pill-p{background:var(--purple-tint);color:var(--purple-dark);}
.section{margin-bottom:64px;}
.section-title{font-size:10px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--color-text-muted);padding-bottom:10px;border-bottom:1px solid var(--color-border);margin-bottom:24px;}
.prose{font-size:13px;color:var(--color-text-muted);line-height:1.65;max-width:720px;margin-bottom:20px;}
.jh{padding:20px 24px;border-radius:var(--radius-xl);margin-bottom:40px;display:flex;align-items:center;gap:16px;}
.jh .jn{font-family:var(--font-display);font-size:48px;font-weight:300;line-height:1;opacity:.5;}
.jh h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
.jh p{font-size:13px;line-height:1.5;}.jh .fl{font-family:var(--font-mono);font-size:11px;margin-top:6px;opacity:.7;}
.jh-p{background:var(--purple-tint);border:1px solid #C5C2F5;}.jh-p .jn{color:var(--purple);}.jh-p p,.jh-p .fl{color:var(--purple-dark);}
.scr{margin-bottom:56px;}
.scr-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;}
.scr-head h3{font-family:var(--font-display);font-size:20px;font-weight:500;letter-spacing:-.02em;}
.scr-id{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);padding:2px 8px;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-page);}
.scr-desc{font-size:12px;color:var(--color-text-muted);line-height:1.6;max-width:720px;margin-bottom:6px;}
.scr-var{font-size:11px;color:var(--color-text-muted);margin-bottom:20px;}.scr-var strong{color:var(--color-text);}
.previews{display:flex;gap:32px;flex-wrap:wrap;justify-content:center;align-items:flex-start;margin-bottom:20px;}
.prev-col{display:flex;flex-direction:column;align-items:center;gap:10px;}
.bp-lbl{font-family:var(--font-mono);font-size:10px;color:var(--color-text-muted);}
/* ── Frame shells ── */
.desk{width:100%;max-width:1040px;background:#E8E7E2;border-radius:var(--radius-xl);overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.06);display:flex;flex-direction:column;min-height:560px;}
.phone{width:320px;flex-shrink:0;background:#E8E7E2;border-radius:36px;overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.07);display:flex;flex-direction:column;border:6px solid #1C1C18;}
.pst{padding:10px 20px 0;display:flex;justify-content:space-between;align-items:center;font-size:12px;background:var(--color-page);}.pst b{font-weight:600;}.pst span{font-size:10px;}
.pb{flex:1;overflow-y:auto;display:flex;flex-direction:column;}
/* ── FA chrome ── */
.fa-nav{height:32px;background:var(--navy);display:flex;align-items:center;padding:0 12px;gap:8px;flex-shrink:0;}
.fa-logo{font-size:7px;font-weight:900;color:#fff;letter-spacing:.8px;border-bottom:2px solid var(--mint);padding-bottom:1px;}
.fa-link{font-size:5.5px;color:rgba(255,255,255,.4);font-weight:700;text-transform:uppercase;letter-spacing:.05em;}
.fa-link.active{color:var(--mint);border-bottom:1px solid rgba(161,220,216,.5);padding-bottom:1px;}
.fa-nav-r{margin-left:auto;display:flex;gap:5px;align-items:center;}
.fa-av{width:16px;height:16px;background:rgba(255,255,255,.1);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:rgba(255,255,255,.5);}
/* ── Page content area ── */
.page-body{padding:14px 16px;background:#E8E7E2;flex:1;overflow-y:auto;}
.back-btn{display:inline-flex;align-items:center;gap:3px;font-size:6.5px;font-weight:600;color:var(--color-text-muted);margin-bottom:10px;}
.back-arrow{font-size:8px;}
/* ── 2-col person page layout ── */
.person-layout{display:grid;grid-template-columns:35% 65%;gap:10px;}
/* ── Card (shared project pattern) ── */
.card{background:#fff;border:1px solid #E4E2D7;border-radius:3px;padding:10px;box-shadow:0 1px 2px rgba(0,0,0,.04);}
.card+.card{margin-top:6px;}
.card-h{font-size:6px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:#9CA3AF;margin-bottom:7px;}
/* ── PersonCard ── */
.person-avatar{width:36px;height:36px;background:var(--navy);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;color:var(--mint);margin-bottom:6px;}
.person-name{font-family:Georgia,serif;font-size:11px;font-weight:500;color:var(--navy);margin-bottom:2px;}
.person-dates{font-size:6px;color:var(--color-text-muted);}
/* ── Generic list rows in right-col cards ── */
.row-item{display:flex;align-items:center;gap:5px;padding:3px 0;border-bottom:1px solid #F0EFE9;font-size:6.5px;color:var(--color-text);}
.row-item:last-child{border-bottom:none;}
.row-av{width:14px;height:14px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:#fff;flex-shrink:0;}
.row-name{flex:1;font-weight:600;}
.row-muted{color:var(--color-text-muted);font-size:6px;}
.count-chip{background:#F0EFE9;border-radius:8px;padding:1px 4px;font-size:5.5px;font-weight:700;color:var(--color-text-muted);}
/* ── Geschichten card editorial list ── */
.gesch-list{display:flex;flex-direction:column;}
.gesch-row{display:flex;gap:7px;padding:5px 0;border-bottom:1px solid #F0EFE9;}
.gesch-row:first-child{padding-top:0;}
.gesch-row:last-child{border-bottom:none;padding-bottom:0;}
.gesch-meta{width:62px;flex-shrink:0;display:flex;flex-direction:column;gap:1.5px;}
.gesch-av{width:16px;height:16px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:5.5px;font-weight:800;color:#fff;flex-shrink:0;}
.gesch-author{font-size:5.5px;font-weight:700;color:var(--color-text);line-height:1.3;}
.gesch-date{font-size:5px;color:var(--color-text-muted);}
.gesch-content{flex:1;min-width:0;}
.gesch-title{font-family:Georgia,serif;font-size:7.5px;color:var(--navy);line-height:1.3;margin-bottom:2px;display:block;}
.gesch-title:hover{text-decoration:underline;}
.gesch-excerpt{font-size:6px;color:var(--color-text-muted);line-height:1.4;overflow:hidden;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;}
.gesch-footer{display:flex;justify-content:space-between;align-items:center;margin-top:6px;padding-top:4px;border-top:1px solid #F0EFE9;}
.gesch-all-link{font-size:6px;color:var(--color-text-muted);}
.gesch-write-link{font-size:6px;color:var(--turquoise);font-weight:600;}
/* ── Alias tags ── */
.alias-tag{display:inline-block;background:#F0EFE9;border:1px solid #E4E2D7;border-radius:3px;padding:1px 5px;font-size:5.5px;font-weight:600;color:var(--color-text-muted);margin:1px 1px 1px 0;}
/* ── impl-ref / agent box ── */
.agent{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:24px;margin-top:20px;}
.agent h4{font-family:var(--font-mono);font-size:12px;font-weight:500;margin-bottom:8px;color:var(--navy);}
.agent pre{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);margin-bottom:12px;white-space:pre-wrap;background:rgba(0,0,0,.03);padding:10px;border-radius:4px;}
.at{width:100%;border-collapse:collapse;font-size:11px;}
.at th{font-family:var(--font-mono);font-size:10px;font-weight:500;text-align:left;color:var(--color-text-muted);padding:6px 8px;border-bottom:2px solid var(--color-border);}
.at td{padding:5px 8px;border-bottom:1px solid var(--color-subtle);vertical-align:top;line-height:1.5;}
.at tr.grp td{font-family:var(--font-mono);font-size:9px;color:var(--color-text-muted);background:var(--color-subtle);font-weight:500;letter-spacing:.06em;text-transform:uppercase;padding:4px 8px;}
.at code{font-family:var(--font-mono);font-size:10px;color:var(--navy);background:rgba(1,40,81,.06);padding:1px 4px;border-radius:2px;}
/* ── LLM guide ── */
.llm{background:var(--color-page);border:1px solid var(--color-border);border-radius:var(--radius-xl);padding:40px;margin-top:48px;}
.llm h2{font-family:var(--font-display);font-size:22px;font-weight:500;margin-bottom:24px;}
.llm h3{font-size:13px;font-weight:600;margin:20px 0 8px;}
.llm p,.llm li{font-size:13px;color:var(--color-text-muted);line-height:1.65;}
.llm ul,.llm ol{padding-left:20px;margin-bottom:16px;}
.llm table{width:100%;border-collapse:collapse;font-size:12px;margin-bottom:16px;}
.llm th{font-family:var(--font-mono);font-size:10px;text-align:left;padding:5px 8px;border-bottom:2px solid var(--color-border);color:var(--color-text-muted);}
.llm td{padding:5px 8px;border-bottom:1px solid var(--color-subtle);vertical-align:top;}
.llm code{font-family:var(--font-mono);font-size:11px;color:var(--navy);background:rgba(1,40,81,.06);padding:1px 4px;border-radius:2px;}
/* ── Silence note ── */
.silence-note{background:#fff;border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:20px 24px;display:flex;align-items:flex-start;gap:12px;}
.silence-icon{font-size:18px;opacity:.4;flex-shrink:0;margin-top:2px;}
.silence-note p{font-size:13px;color:var(--color-text-muted);line-height:1.6;}
</style>
</head>
<body>
<div class="doc">
<!-- ── Header ── -->
<div class="doc-header">
<div>
<h1>Geschichten — Personenverknüpfung</h1>
<p>Entdeckung verlinkter Geschichten über die Personendetailseite. Lesende, die nach Franz Raddatz suchen, finden automatisch alle veröffentlichten Geschichten, die ihn erwähnen — als neue Karte am Ende der rechten Spalte.</p>
</div>
<div class="doc-meta">
Familienarchiv<br/>
<span class="pill pill-p">Final Spec</span><br/>
2026-05-02 · @leonievoss
</div>
</div>
<!-- ── Journey header ── -->
<div class="jh jh-p">
<div class="jn">P</div>
<div>
<h2>Personen-Entdeckung</h2>
<p>Neugierige Leserinnen und Leser suchen Personen und finden dort alle Geschichten, die über sie erzählt wurden — ohne separaten Suchlauf.</p>
<div class="fl">/persons/[id] · +page.svelte rechte Spalte · GeschichtenCard.svelte</div>
</div>
</div>
<!-- ── Section: Konzept ── -->
<div class="section">
<div class="section-title">Konzept</div>
<p class="prose">Die Personendetailseite hat bereits eine informationsdichte rechte Spalte: Briefpartner, Beziehungen, gesendete und empfangene Briefe. Die Geschichten-Karte wird ganz am Ende dieser Spalte angehängt — sie erscheint nur, wenn mindestens eine veröffentlichte Geschichte diese Person erwähnt. Ist keine vorhanden, fehlt die Karte vollständig; kein Leerszustand, keine Platzhalter.</p>
<p class="prose">Innerhalb der Karte gilt das redaktionelle Listen-Layout: Metadaten links (Avatar, Autorenname, Datum), Titel und Ausschnitt rechts — konsistent mit der /geschichten-Übersichtsseite. BLOG_WRITERs sehen im Karten-Header einen Schnellzugriff, um eine neue Geschichte mit dieser Person vorauszufüllen.</p>
<p class="prose">Die Karte ist bewusst passiv: Sie zeigt, was andere über diese Person geschrieben haben. Sie ist kein Aufruf zum Schreiben, sondern ein stiller Hinweis auf vorhandene Erinnerungen.</p>
</div>
<!-- ── Screen P-1: Desktop ── -->
<div class="section">
<div class="section-title">Screens</div>
<div class="scr">
<div class="scr-head">
<h3>P-1 — /persons/[id] · Geschichten-Karte in der rechten Spalte</h3>
<span class="scr-id">P-1</span>
</div>
<p class="scr-desc">Vollständige Personendetailseite mit der neuen Geschichten-Karte am Ende der rechten Spalte. Franz Raddatz hat drei veröffentlichte Geschichten, die ihn erwähnen.</p>
<p class="scr-var"><strong>Desktop · 1040px</strong> — Zweispaltiges Layout (35 % / 65 %), Karte erscheint als letztes Element rechts.</p>
<div class="previews">
<div class="prev-col">
<span class="bp-lbl">Desktop · 1040px</span>
<div class="desk">
<div class="fa-nav">
<span class="fa-logo">ARCHIV</span>
<span class="fa-link" style="margin-left:8px">Dokumente</span>
<span class="fa-link active" style="margin-left:6px">Personen</span>
<span class="fa-link" style="margin-left:6px">Geschichten</span>
<span class="fa-link" style="margin-left:6px">Chronik</span>
<div class="fa-nav-r"><div class="fa-av">MR</div></div>
</div>
<div class="page-body">
<div class="back-btn"><span class="back-arrow"></span> Zurück</div>
<div class="person-layout">
<!-- Left col -->
<div>
<!-- PersonCard -->
<div class="card">
<div style="display:flex;align-items:flex-start;justify-content:space-between;">
<div>
<div class="person-avatar">FR</div>
<div class="person-name">Franz Raddatz</div>
<div class="person-dates">* 14. März 1898 in Breslau &nbsp;† 7. Nov. 1952 in Hamburg</div>
</div>
<span style="font-size:6px;color:var(--color-text-muted);cursor:pointer"></span>
</div>
<div style="margin-top:6px;font-size:6px;color:var(--color-text-muted);">Briefschreiber, Gutsverwalter</div>
</div>
<!-- NameHistoryCard -->
<div class="card" style="margin-top:6px">
<div class="card-h">Namensvarianten</div>
<div>
<span class="alias-tag">Franz</span>
<span class="alias-tag">François</span>
<span class="alias-tag">F. Raddatz</span>
<span class="alias-tag">Franzl</span>
</div>
</div>
</div>
<!-- Right col -->
<div>
<!-- CoCorrespondents -->
<div class="card">
<div class="card-h">Briefpartner</div>
<div class="row-item">
<div class="row-av" style="background:#5A3080">EM</div>
<span class="row-name">Emma Müller</span>
<span class="count-chip">34 Briefe</span>
</div>
<div class="row-item">
<div class="row-av" style="background:#2D7DD2">HK</div>
<span class="row-name">Heinrich Kohl</span>
<span class="count-chip">18 Briefe</span>
</div>
<div class="row-item">
<div class="row-av" style="background:#3D8C4A">GR</div>
<span class="row-name">Gertrud Raddatz</span>
<span class="count-chip">11 Briefe</span>
</div>
</div>
<!-- Relationships -->
<div class="card" style="margin-top:6px">
<div class="card-h">Beziehungen</div>
<div class="row-item">
<div class="row-av" style="background:#5A3080">EM</div>
<span class="row-name">Emma Müller</span>
<span class="row-muted">Ehefrau</span>
</div>
<div class="row-item">
<div class="row-av" style="background:#E8862A">KR</div>
<span class="row-name">Klaus Raddatz</span>
<span class="row-muted">Sohn</span>
</div>
</div>
<!-- Sent docs -->
<div class="card" style="margin-top:6px">
<div class="card-h">Verfasste Briefe <span style="font-weight:400;text-transform:none;letter-spacing:0">(47)</span></div>
<div class="row-item"><span class="row-muted" style="width:55px;flex-shrink:0">12. Juli 1938</span><span class="row-name" style="font-size:6.5px">Brief an Emma Müller</span></div>
<div class="row-item"><span class="row-muted" style="width:55px;flex-shrink:0">3. März 1937</span><span class="row-name" style="font-size:6.5px">Brief an Heinrich Kohl</span></div>
<div class="row-item"><span class="row-muted" style="width:55px;flex-shrink:0">18. Okt. 1935</span><span class="row-name" style="font-size:6.5px">Brief an Gertrud Raddatz</span></div>
<div style="font-size:6px;color:var(--color-text-muted);margin-top:4px;">+ 44 weitere anzeigen</div>
</div>
<!-- Received docs -->
<div class="card" style="margin-top:6px">
<div class="card-h">Erhaltene Briefe <span style="font-weight:400;text-transform:none;letter-spacing:0">(23)</span></div>
<div class="row-item"><span class="row-muted" style="width:55px;flex-shrink:0">14. Aug. 1938</span><span class="row-name" style="font-size:6.5px">Brief von Emma Müller</span></div>
<div class="row-item"><span class="row-muted" style="width:55px;flex-shrink:0">7. Apr. 1936</span><span class="row-name" style="font-size:6.5px">Brief von Heinrich Kohl</span></div>
</div>
<!-- ★ GESCHICHTEN KARTE — NEU ★ -->
<div class="card" style="margin-top:6px;border-color:#C5C2F5;border-width:1px;">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:7px;">
<div class="card-h" style="margin-bottom:0">Geschichten</div>
<!-- BLOG_WRITER only: -->
<span class="gesch-write-link">+ Geschichte schreiben</span>
</div>
<div class="gesch-list">
<!-- Row 1 -->
<div class="gesch-row">
<div class="gesch-meta">
<div class="gesch-av" style="background:var(--navy)">MR</div>
<div class="gesch-author">Maria Raddatz</div>
<div class="gesch-date">14. März 2025</div>
</div>
<div class="gesch-content">
<a class="gesch-title">Der Sommer in Breslau</a>
<div class="gesch-excerpt">Oma erzählte oft vom letzten Sommer vor dem Krieg, als die Familie noch vollständig zusammen war…</div>
</div>
</div>
<!-- Row 2 -->
<div class="gesch-row">
<div class="gesch-meta">
<div class="gesch-av" style="background:#5A3080">KR</div>
<div class="gesch-author">Klaus Raddatz</div>
<div class="gesch-date">2. Jan. 2025</div>
</div>
<div class="gesch-content">
<a class="gesch-title">Wie Opa Franz den Hof rettete</a>
<div class="gesch-excerpt">Es war ein kalter November, als der Notar anklopfte. Opa hatte nur wenige Stunden Zeit…</div>
</div>
</div>
<!-- Row 3 -->
<div class="gesch-row">
<div class="gesch-meta">
<div class="gesch-av" style="background:#2D7DD2">GK</div>
<div class="gesch-author">Gertrud Koch</div>
<div class="gesch-date">18. Okt. 2024</div>
</div>
<div class="gesch-content">
<a class="gesch-title">Die Hochzeit im Krieg</a>
<div class="gesch-excerpt">1943, mitten im Chaos — Emma bestand darauf, dass das Fest stattfand…</div>
</div>
</div>
</div>
<div class="gesch-footer">
<span class="gesch-all-link">Alle Geschichten zu Franz Raddatz →</span>
</div>
</div>
<!-- ★ ENDE GESCHICHTEN KARTE ★ -->
</div>
</div>
</div>
</div>
</div>
</div>
<div class="agent">
<h4>P-1 · Implementation Reference</h4>
<table class="at">
<thead><tr><th>Element</th><th>Wert / Klasse</th><th>Hinweis</th></tr></thead>
<tbody>
<tr class="grp"><td colspan="3">Karten-Wrapper</td></tr>
<tr><td>Geschichten-Karte</td><td><code>mt-6 bg-white shadow-sm border border-brand-sand rounded-sm p-6</code></td><td>Identisches Pattern wie alle anderen rechten Spalten-Karten</td></tr>
<tr><td>Karten-Header</td><td><code>flex items-center justify-between mb-5</code></td><td></td></tr>
<tr><td>Section-Heading</td><td><code>text-xs font-bold uppercase tracking-widest text-gray-400</code></td><td>Projekt-Standard, identisch mit anderen Karten</td></tr>
<tr><td>"+ Geschichte schreiben"</td><td><code>text-[10px] text-turquoise font-semibold hover:underline</code></td><td>Link zu <code>/geschichten/new?personId={id}</code>, nur mit BLOG_WRITE</td></tr>
<tr class="grp"><td colspan="3">Redaktionelle Liste (Stories)</td></tr>
<tr><td>Listen-Wrapper</td><td><code>flex flex-col divide-y divide-brand-sand</code></td><td></td></tr>
<tr><td>Story-Zeile</td><td><code>flex gap-3 py-3 first:pt-0 last:pb-0</code></td><td>min-h-[44px] auf Mobile (Touch-Target WCAG 2.2)</td></tr>
<tr><td>Meta-Spalte</td><td><code>w-[72px] shrink-0 flex flex-col gap-0.5</code></td><td>Feste Breite, kein Overflow</td></tr>
<tr><td>Autoren-Avatar</td><td><code>w-6 h-6 rounded-full text-[9px] font-bold text-white flex items-center justify-center</code></td><td><code>personAvatarColor(author.id)</code> aus existierendem Util</td></tr>
<tr><td>Autorenname</td><td><code>font-sans text-[10px] font-semibold text-ink leading-tight</code></td><td></td></tr>
<tr><td>Datum</td><td><code>font-sans text-[10px] text-ink-3</code></td><td><code>formatDate(publishedAt)</code></td></tr>
<tr><td>Story-Titel (Link)</td><td><code>block font-serif text-[12px] text-ink hover:text-primary hover:underline leading-snug</code></td><td>Link zu <code>/geschichten/{id}</code></td></tr>
<tr><td>Ausschnitt</td><td><code>font-sans text-[10px] text-ink-3 line-clamp-1 mt-0.5</code></td><td>Erste ~80 Zeichen des body-Texts (HTML-Tags gestripped)</td></tr>
<tr class="grp"><td colspan="3">Karten-Footer</td></tr>
<tr><td>"Alle Geschichten"-Link</td><td><code>mt-3 block font-sans text-[10px] text-ink-3 hover:text-primary</code></td><td>Nur wenn <code>geschichten.length >= 3</code>. Link: <code>/geschichten?personId={id}</code></td></tr>
<tr class="grp"><td colspan="3">Konditionelles Rendering</td></tr>
<tr><td>Karte sichtbar</td><td><code>{#if geschichten.length > 0}</code></td><td>Kein Leerszustand — Stille ist korrekt (US-BLOG-005)</td></tr>
<tr><td>Max. angezeigte Geschichten</td><td>3 in der Karte</td><td>Server-seitig mit <code>&amp;limit=3</code> limitieren</td></tr>
</tbody>
</table>
</div>
</div>
<!-- ── Screen P-2: Mobile ── -->
<div class="scr">
<div class="scr-head">
<h3>P-2 — Mobile · Geschichten-Karte gestapelt</h3>
<span class="scr-id">P-2</span>
</div>
<p class="scr-desc">Auf Mobile kollabiert das 2-Spalten-Layout zu einer einzigen Spalte. Die Geschichten-Karte erscheint unter allen anderen Karten — genau in der Reihenfolge der Desktop-Ansicht von oben nach unten.</p>
<p class="scr-var"><strong>Mobile · 320px</strong> — SingleColumn, vollbreite Karten. Jede Geschichten-Zeile hat min-h-[44px] als Touch-Target.</p>
<div class="previews">
<div class="prev-col">
<span class="bp-lbl">Mobile · 320px (nach unten gescrollt)</span>
<div class="phone">
<div class="pst"><b>9:41</b><span>●●●</span></div>
<div class="pb">
<div class="fa-nav">
<span class="fa-logo">ARCHIV</span>
<div class="fa-nav-r"><div class="fa-av">MR</div></div>
</div>
<div class="page-body" style="padding:10px;">
<div class="back-btn"><span class="back-arrow"></span> Personen</div>
<!-- Hint that there are cards above -->
<div style="text-align:center;font-size:6px;color:var(--color-text-muted);padding:4px 0;margin-bottom:4px;border:1px dashed var(--color-border);border-radius:3px;">… PersonCard · Namensvarianten · Briefpartner · Beziehungen · Briefe …</div>
<!-- Geschichten Karte full width -->
<div class="card" style="border-color:#C5C2F5;">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:7px;">
<div class="card-h" style="margin-bottom:0">Geschichten</div>
<span class="gesch-write-link" style="font-size:5.5px">+ Geschichte schreiben</span>
</div>
<div class="gesch-list">
<div class="gesch-row" style="min-height:44px;align-items:center;">
<div class="gesch-meta">
<div class="gesch-av" style="background:var(--navy)">MR</div>
<div class="gesch-author">Maria Raddatz</div>
<div class="gesch-date">14. März 2025</div>
</div>
<div class="gesch-content">
<a class="gesch-title" style="font-size:8px">Der Sommer in Breslau</a>
<div class="gesch-excerpt">Oma erzählte oft vom letzten Sommer vor dem Krieg…</div>
</div>
</div>
<div class="gesch-row" style="min-height:44px;align-items:center;">
<div class="gesch-meta">
<div class="gesch-av" style="background:#5A3080">KR</div>
<div class="gesch-author">Klaus Raddatz</div>
<div class="gesch-date">2. Jan. 2025</div>
</div>
<div class="gesch-content">
<a class="gesch-title" style="font-size:8px">Wie Opa Franz den Hof rettete</a>
<div class="gesch-excerpt">Es war ein kalter November, als der Notar anklopfte…</div>
</div>
</div>
<div class="gesch-row" style="min-height:44px;align-items:center;">
<div class="gesch-meta">
<div class="gesch-av" style="background:#2D7DD2">GK</div>
<div class="gesch-author">Gertrud Koch</div>
<div class="gesch-date">18. Okt. 2024</div>
</div>
<div class="gesch-content">
<a class="gesch-title" style="font-size:8px">Die Hochzeit im Krieg</a>
<div class="gesch-excerpt">1943, mitten im Chaos — Emma bestand darauf…</div>
</div>
</div>
</div>
<div class="gesch-footer">
<span class="gesch-all-link">Alle Geschichten zu Franz Raddatz →</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ── Screen P-3: Stille ── -->
<div class="scr">
<div class="scr-head">
<h3>P-3 — Keine Geschichten — Karte fehlt (Stille)</h3>
<span class="scr-id">P-3</span>
</div>
<p class="scr-desc">Wenn keine veröffentlichten Geschichten diese Person erwähnen, erscheint die Karte nicht. Kein Leerszustand, keine Platzhalter — die Seite endet einfach bei den erhaltenen Briefen.</p>
<div class="silence-note">
<div class="silence-icon">🔇</div>
<p><strong>Stille ist korrekt</strong> — US-BLOG-005 schreibt explizit vor: <em>„Given a document has no published stories, when I view the document detail page, then no 'Geschichten' section is shown (no empty state needed — silence is correct)."</em> Das gilt analog für die Personenseite. Die Karte wird nur gerendert, wenn <code>geschichten.length &gt; 0</code>. Keine leere Karte, kein „Noch keine Geschichten"-Text, kein Aufruf zum Schreiben an Lesende.</p>
</div>
</div>
</div>
<!-- ── LLM Implementation Guide ── -->
<div class="llm">
<h2>Implementation Guide — Personen-Entdeckung</h2>
<h3>1. Backend-Änderungen</h3>
<table>
<thead><tr><th>Endpunkt</th><th>Änderung</th><th>Hinweis</th></tr></thead>
<tbody>
<tr><td><code>GET /api/geschichten?personId={id}&amp;status=PUBLISHED&amp;limit=3</code></td><td>Neuer Filter-Parameter</td><td>Nur PUBLISHED zurückgeben, auch ohne BLOG_WRITE. Limit serverseitig.</td></tr>
<tr><td><code>GET /api/persons/{id}</code> Loader</td><td>Mitladen der Geschichten</td><td>Im <code>+page.server.ts</code> parallel zu sentDocuments laden.</td></tr>
</tbody>
</table>
<h3>2. Frontend-Änderungen</h3>
<ul>
<li><code>persons/[id]/+page.server.ts</code> — geschichten parallel laden, in pageData exponieren</li>
<li><code>persons/[id]/+page.svelte</code><code>GeschichtenCard</code> am Ende der rechten Spalte rendern (<code>mt-6</code>), nur wenn <code>data.geschichten.length &gt; 0</code></li>
<li>Neues Shared-Component: <code>$lib/components/GeschichtenCard.svelte</code></li>
</ul>
<h3>3. GeschichtenCard.svelte — Props</h3>
<table>
<thead><tr><th>Prop</th><th>Typ</th><th>Verwendung</th></tr></thead>
<tbody>
<tr><td><code>geschichten</code></td><td><code>Geschichte[]</code></td><td>Die Geschichten-Liste (max. 3 vom Server)</td></tr>
<tr><td><code>personId</code></td><td><code>string | undefined</code></td><td>Für "alle anzeigen"- und "schreiben"-Link</td></tr>
<tr><td><code>canWrite</code></td><td><code>boolean</code></td><td>Steuert "+ Geschichte schreiben" Sichtbarkeit</td></tr>
</tbody>
</table>
<h3>4. Wiederverwendbare Utilities</h3>
<ul>
<li><code>personAvatarColor(id)</code> — bereits vorhanden in <code>$lib/utils/personFormat.ts</code></li>
<li><code>formatDate(date)</code> — bereits vorhanden in <code>$lib/utils/date.ts</code></li>
<li>Story-Ausschnitt: HTML-Tags aus <code>body</code> strippen, erste 80 Zeichen nehmen</li>
</ul>
<h3>5. Barrierefreiheit</h3>
<ul>
<li>Alle Story-Links haben beschreibenden Text (Titel der Geschichte)</li>
<li>Touch-Targets auf Mobile: <code>min-h-[44px]</code> je Zeile (WCAG 2.2)</li>
<li>"+ Geschichte schreiben"-Link hat ausreichend Kontrast: Turquoise (#00C7B1) auf Weiß = 2.8:1 — für diesen kleinen UI-Hinweis akzeptabel, aber <strong>kein Body-Text</strong></li>
<li>Strukturell: Die Karte ist eine <code>&lt;section&gt;</code> mit <code>aria-labelledby</code> auf die Überschrift</li>
</ul>
<h3>6. Kein eigener Leerszustand</h3>
<p>Die Komponente wird nur gerendert, wenn <code>geschichten.length &gt; 0</code>. Keine leere Karte, kein Platzhaltertext, kein Aufruf zum Schreiben an Lesende ohne BLOG_WRITE-Berechtigung. Stille ist das korrekte Design für fehlende Geschichten.</p>
</div>
</div>
</body>
</html>