1253 lines
83 KiB
HTML
1253 lines
83 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Conversations — Narrow Column Redesign 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:1560px;margin:0 auto;padding:48px 32px}
|
||
|
||
/* Masthead */
|
||
.masthead{background:#0D2240;border-radius:10px;padding:36px 40px;margin-bottom:56px;display:flex;align-items:flex-start;justify-content:space-between;gap:24px}
|
||
.masthead-left h1{font-size:26px;font-weight:900;color:#fff;letter-spacing:-.5px;margin-bottom:6px}
|
||
.masthead-left p{font-size:13px;color:rgba(255,255,255,.55);max-width:600px;line-height:1.6}
|
||
.masthead-meta{margin-top:14px;display:flex;gap:10px;flex-wrap:wrap}
|
||
.masthead-tag{font-size:10px;font-weight:800;padding:3px 10px;border-radius:20px;text-transform:uppercase;letter-spacing:1px}
|
||
.v-badge{background:#A6DAD8;color:#002850}
|
||
.file-badge{background:rgba(255,255,255,.12);color:rgba(255,255,255,.7)}
|
||
.issue-badge{background:rgba(166,218,216,.15);color:#A6DAD8;border:1px solid rgba(166,218,216,.3)}
|
||
|
||
/* Section layout */
|
||
.section{margin-bottom:72px}
|
||
.section+.section{border-top:2px dashed #C8C4BE;padding-top:64px}
|
||
.sec-intro{display:flex;align-items:flex-start;gap:20px;margin-bottom:28px}
|
||
.sec-num{font-size:64px;font-weight:900;color:#DDD;line-height:1;flex-shrink:0;width:80px}
|
||
.sec-badge{display:inline-block;font-size:10px;font-weight:800;padding:3px 10px;border-radius:20px;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px}
|
||
.badge-arch{background:#F0F0F0;color:#555}
|
||
.badge-filter{background:#E3EEFF;color:#1D4ED8}
|
||
.badge-chat{background:#D6F0EE;color:#0F5B58}
|
||
.badge-bubble{background:#FEF3C7;color:#B45309}
|
||
.badge-divider{background:#F3E8FF;color:#7C3AED}
|
||
.badge-summary{background:#DCFCE7;color:#166534}
|
||
.badge-empty{background:#FEE2E2;color:#B91C1C}
|
||
.badge-impl{background:#F0F0F0;color:#444}
|
||
.sec-title{font-size:20px;font-weight:800;color:#002850;margin-bottom:4px}
|
||
.sec-tagline{font-size:13px;color:#666;max-width:680px;line-height:1.6}
|
||
|
||
/* Wireframe frames */
|
||
.screens{display:flex;gap:20px;align-items:flex-start;margin-bottom:16px;flex-wrap:wrap}
|
||
.screens.cols2{display:grid;grid-template-columns:1fr 1fr;gap:20px}
|
||
.screens.cols3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px}
|
||
.screen-block{display:flex;flex-direction:column;min-width:0}
|
||
.screen-label{font-size:10px;font-weight:800;color:#888;text-transform:uppercase;letter-spacing:1.5px;margin-bottom:8px;display:flex;align-items:center;gap:8px}
|
||
.sz{background:#E8E4DF;color:#666;padding:1px 6px;border-radius:3px;font-size:9px}
|
||
.state-tag{padding:1px 7px;border-radius:3px;font-size:9px;font-weight:700;background:#DBEAFE;color:#1E40AF}
|
||
.state-tag.active{background:#DCFCE7;color:#166534}
|
||
.state-tag.collapsed{background:#FEF3C7;color:#92400E}
|
||
.state-tag.mobile{background:#F3E8FF;color:#7C3AED}
|
||
.screen-cap{font-size:9px;color:#888;margin-top:6px;text-align:center;font-style:italic;line-height:1.5}
|
||
|
||
/* Wireframe shell */
|
||
.wf{background:#fff;border:2px solid #B8B4AE;border-radius:10px;overflow:hidden;box-shadow:0 4px 20px rgba(0,0,0,.08);position:relative}
|
||
.wf-bar{height:26px;background:#E8E4DF;border-bottom:1px solid #C8C4BE;display:flex;align-items:center;padding:0 10px;gap:5px}
|
||
.dot{width:8px;height:8px;border-radius:50%;background:#C8C4BE}
|
||
.dot.r{background:#F87171}.dot.y{background:#FCD34D}.dot.g{background:#4ADE80}
|
||
.urlbar{flex:1;height:13px;background:#D8D4CE;border-radius:3px;margin-left:8px;display:flex;align-items:center;padding:0 6px}
|
||
.urlbar span{font-size:8px;color:#888;font-family:monospace}
|
||
|
||
/* Navbar */
|
||
.N{height:44px;background:#0D2240;display:flex;align-items:center;padding:0 18px;gap:14px;flex-shrink:0}
|
||
.N-m{height:46px;background:#0D2240;display:flex;align-items:center;padding:0 14px;justify-content:space-between;flex-shrink:0}
|
||
.logo{font-size:10px;font-weight:900;color:#fff;letter-spacing:1px}
|
||
.nl{font-size:8px;color:rgba(255,255,255,.5);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:8px;align-items:center}
|
||
.nav-ico{width:22px;height:22px;background:rgba(255,255,255,.12);border-radius:5px}
|
||
.nav-user{font-size:8px;color:rgba(255,255,255,.6);font-weight:700}
|
||
|
||
/* Main content areas */
|
||
.MAIN{flex:1;padding:16px 20px;display:flex;flex-direction:column;gap:10px;min-width:0;background:#ECEAE4}
|
||
.MAIN-sm{flex:1;padding:10px 14px;display:flex;flex-direction:column;gap:8px;min-width:0;background:#ECEAE4}
|
||
|
||
/* Page chrome */
|
||
.PAGE-HEAD{border-bottom:1px solid rgba(26,26,26,.1);padding-bottom:8px;margin-bottom:10px}
|
||
.PAGE-H1{font-size:14px;font-weight:800;color:#0D2240;font-family:Georgia,serif}
|
||
.PAGE-SUB{font-size:8px;color:#888;margin-top:2px}
|
||
|
||
/* Filter bar variants */
|
||
.FB-FULL{border:1.5px solid #C8C4BE;background:#F7F5F2;padding:14px;box-shadow:0 2px 8px rgba(0,0,0,.04);margin-bottom:10px}
|
||
.FB-STRIP{border-bottom:1.5px solid #E0DDD6;background:#F7F5F2;padding:7px 12px;display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
|
||
.FB-STRIP-left{display:flex;align-items:center;gap:8px}
|
||
.FB-STRIP-names{font-size:10px;font-weight:700;color:#0D2240;font-family:Georgia,serif}
|
||
.FB-STRIP-meta{font-size:8px;color:#999}
|
||
.FB-STRIP-btn{font-size:8px;font-weight:800;color:#002850;border:1.5px solid #002850;padding:3px 8px;border-radius:3px;text-transform:uppercase;letter-spacing:.5px;cursor:pointer;white-space:nowrap}
|
||
.FB-ROW1{display:grid;grid-template-columns:1fr 28px 1fr;gap:8px;align-items:end;margin-bottom:10px}
|
||
.FB-ROW2{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px}
|
||
.FB-LABEL{font-size:7.5px;font-weight:800;color:#888;text-transform:uppercase;letter-spacing:.5px;margin-bottom:3px}
|
||
.FB-INPUT{height:28px;border:1.5px solid #D1D5DB;border-radius:3px;background:#fff;display:flex;align-items:center;padding:0 8px;font-size:9px;color:#333}
|
||
.FB-INPUT.placeholder{color:#AAA;font-style:italic}
|
||
.FB-SWAP{width:28px;height:28px;border:1.5px solid #C8C4BE;border-radius:3px;background:#F0EDE8;display:flex;align-items:center;justify-content:center;font-size:10px;color:#666;align-self:flex-end}
|
||
.FB-SORT{height:28px;border:1.5px solid #D1D5DB;border-radius:3px;background:#fff;display:flex;align-items:center;justify-content:center;font-size:8px;font-weight:700;color:#444;gap:3px;text-transform:uppercase;letter-spacing:.3px}
|
||
.FB-APPLY{height:28px;background:#002850;color:#fff;border-radius:3px;display:flex;align-items:center;justify-content:center;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;cursor:pointer;margin-top:10px}
|
||
.FB-CANCEL{font-size:8px;color:#888;text-decoration:underline;text-align:center;margin-top:5px;cursor:pointer}
|
||
|
||
/* Overlay panel */
|
||
.FB-OVERLAY{border:1.5px solid #002850;background:#fff;padding:14px;box-shadow:0 8px 24px rgba(0,0,0,.12);margin-bottom:10px;position:relative}
|
||
.FB-OVERLAY::before{content:'▲';position:absolute;top:-9px;left:20px;color:#002850;font-size:10px}
|
||
|
||
/* Chat column */
|
||
.CHAT-OUTER{border:1.5px solid #E0DDD6;background:#F7F5F2;border-radius:4px;box-shadow:0 2px 8px rgba(0,0,0,.05);padding:12px;position:relative;overflow:hidden}
|
||
.CHAT-COL{max-width:300px;margin:0 auto;display:flex;flex-direction:column;gap:6px}
|
||
.CHAT-COL-FULL{width:100%;display:flex;flex-direction:column;gap:6px}
|
||
|
||
/* Bubble rows */
|
||
.BUBBLE-ROW{display:flex;width:100%}
|
||
.BUBBLE-ROW.right{justify-content:flex-end}
|
||
.BUBBLE-ROW.left{justify-content:flex-start}
|
||
.BUBBLE-GROUP{display:flex;gap:5px;max-width:75%}
|
||
.BUBBLE-GROUP.right{flex-direction:row-reverse}
|
||
.BUBBLE-GROUP.left{flex-direction:row}
|
||
.AVATAR{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:6px;font-weight:800;flex-shrink:0;align-self:flex-end;margin-bottom:2px}
|
||
.AVATAR.sender{background:#002850;color:#fff;border:1px solid #002850}
|
||
.AVATAR.receiver{background:#fff;color:#444;border:1.5px solid #C8C4BE}
|
||
.BUBBLE{border-radius:6px;padding:7px 9px;max-width:100%}
|
||
.BUBBLE.sender{background:#002850;color:#fff;border-bottom-right-radius:2px;border:1px solid #002850}
|
||
.BUBBLE.receiver{background:#E8E4DF;color:#1A1A1A;border-bottom-left-radius:2px;border:1.5px solid #D1CCC8}
|
||
.BUBBLE-TITLE{font-size:8px;font-weight:700;font-family:Georgia,serif;line-height:1.3;margin-bottom:3px}
|
||
.BUBBLE.sender .BUBBLE-TITLE{color:#fff}
|
||
.BUBBLE.receiver .BUBBLE-TITLE{color:#1A1A1A}
|
||
.BUBBLE-META{font-size:7px;text-transform:uppercase;letter-spacing:.4px;opacity:.7;margin-bottom:3px}
|
||
.BUBBLE.sender .BUBBLE-META{color:rgba(255,255,255,.7)}
|
||
.BUBBLE.receiver .BUBBLE-META{color:#666}
|
||
.BUBBLE-STATUS{display:flex;align-items:center;gap:3px}
|
||
.STATUS-DOT{width:5px;height:5px;border-radius:50%;flex-shrink:0}
|
||
.STATUS-DOT.uploaded{background:#22C55E}
|
||
.STATUS-DOT.transcribed{background:#3B82F6}
|
||
.STATUS-DOT.reviewed{background:#A855F7}
|
||
.STATUS-DOT.archived{background:#6B7280}
|
||
.STATUS-DOT.placeholder{background:#F59E0B}
|
||
.STATUS-LABEL{font-size:6.5px;text-transform:uppercase;letter-spacing:.4px;font-weight:700}
|
||
.BUBBLE.sender .STATUS-LABEL{color:rgba(255,255,255,.65)}
|
||
.BUBBLE.receiver .STATUS-LABEL{color:#666}
|
||
|
||
/* Year divider */
|
||
.YEAR-DIV{display:flex;align-items:center;padding:4px 0;margin:2px 0}
|
||
.YEAR-LINE{flex:1;border-top:1px solid #D1CCC8}
|
||
.YEAR-LABEL{font-size:7px;font-weight:800;color:#AAA;text-transform:uppercase;letter-spacing:1px;padding:0 8px}
|
||
|
||
/* Summary bar */
|
||
.SUMBAR{display:flex;align-items:center;justify-content:space-between;padding:0 0 8px;margin-bottom:0}
|
||
.SUMBAR-count{font-size:9px;font-weight:600;color:#888}
|
||
.SUMBAR-link{font-size:8px;font-weight:700;color:#002850;display:flex;align-items:center;gap:3px}
|
||
|
||
/* Empty states */
|
||
.EMPTY{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:24px 12px;text-align:center;border:1.5px dashed #D1CCC8;border-radius:4px;background:#F7F5F2}
|
||
.EMPTY-ICON{width:28px;height:28px;border-radius:50%;background:#E8E4DF;display:flex;align-items:center;justify-content:center;font-size:14px;margin-bottom:8px}
|
||
.EMPTY-H{font-size:10px;font-weight:700;color:#444;font-family:Georgia,serif;margin-bottom:3px}
|
||
.EMPTY-S{font-size:8px;color:#999;line-height:1.4}
|
||
.EMPTY-sm{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:16px 12px;text-align:center;border:1.5px solid #E0DDD6;border-radius:4px;background:#F7F5F2}
|
||
|
||
/* Annotation callouts */
|
||
.ANN{position:absolute;background:#FFFBEB;border:1px solid #FDE68A;border-radius:4px;padding:3px 6px;font-size:7.5px;color:#92400E;font-weight:600;line-height:1.3;white-space:nowrap;z-index:10}
|
||
.ANN-line{position:absolute;border-left:1px dashed #FDE68A;z-index:9}
|
||
|
||
/* Spec tables */
|
||
.BP-table{width:100%;border-collapse:collapse;font-size:11px;margin-top:8px}
|
||
.BP-table th{text-align:left;padding:8px 12px;font-size:10px;font-weight:800;color:#888;text-transform:uppercase;letter-spacing:.5px;border-bottom:2px solid #E8E4DF;background:#F7F5F2}
|
||
.BP-table td{padding:8px 12px;border-bottom:1px solid #F0EDE6;color:#444;vertical-align:top}
|
||
.BP-table tr:last-child td{border-bottom:none}
|
||
.BP-table code{font-size:10px;font-family:monospace;background:#F3F4F6;padding:1px 4px;border-radius:2px}
|
||
.BP-table td:first-child{font-weight:700;white-space:nowrap}
|
||
|
||
/* Decision boxes */
|
||
.DECISION{background:#F0F7FF;border-left:4px solid #002850;border-radius:0 6px 6px 0;border-right:1px solid #BFDBFE;border-top:1px solid #BFDBFE;border-bottom:1px solid #BFDBFE;padding:14px 16px;margin:16px 0}
|
||
.DECISION-title{font-size:10px;font-weight:800;color:#002850;text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px;display:flex;align-items:center;gap:6px}
|
||
.DECISION ul{display:flex;flex-direction:column;gap:5px;padding-left:0}
|
||
.DECISION li{font-size:11px;color:#1E3A5F;line-height:1.5;list-style:none;padding-left:16px;position:relative}
|
||
.DECISION li::before{content:'→';position:absolute;left:0;color:#60A5FA;font-weight:700}
|
||
|
||
/* Impl notes */
|
||
.IMPL-RULES{display:flex;flex-direction:column;gap:0}
|
||
.IMPL-RULE{padding:12px 16px;border-bottom:1px solid #E8E4DF;display:grid;grid-template-columns:28px 1fr;gap:12px;align-items:start}
|
||
.IMPL-RULE:last-child{border-bottom:none}
|
||
.IMPL-RULE:nth-child(odd){background:#FAFAF7}
|
||
.IMPL-NUM{font-size:11px;font-weight:900;color:#002850;padding-top:1px}
|
||
.IMPL-BODY{}
|
||
.IMPL-TITLE{font-size:11px;font-weight:800;color:#1A1A1A;margin-bottom:3px}
|
||
.IMPL-TEXT{font-size:11px;color:#555;line-height:1.6}
|
||
.IMPL-TEXT code{font-size:10px;font-family:monospace;background:#F3F4F6;padding:1px 4px;border-radius:2px;color:#1A1A1A}
|
||
|
||
/* Before/after comparison */
|
||
.CMP{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1.5px solid #E0DDD6;border-radius:6px;overflow:hidden;margin-top:12px}
|
||
.CMP-col{padding:0}
|
||
.CMP-head{padding:10px 14px;font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;border-bottom:1.5px solid #E0DDD6}
|
||
.CMP-head.before{background:#FFF5F5;color:#B91C1C;border-right:1px solid #E0DDD6}
|
||
.CMP-head.after{background:#F0FDF4;color:#166534}
|
||
.CMP-body{padding:12px 14px}
|
||
.CMP-col.before .CMP-body{border-right:1px solid #E0DDD6;background:#FFFAFA}
|
||
.CMP-col.after .CMP-body{background:#FAFFFE}
|
||
.CMP-item{font-size:11px;color:#444;padding:3px 0;line-height:1.5;display:flex;align-items:flex-start;gap:6px}
|
||
.CMP-item::before{content:'✕';color:#EF4444;font-size:10px;flex-shrink:0;margin-top:1px}
|
||
.CMP-col.after .CMP-item::before{content:'✓';color:#22C55E}
|
||
.CMP-item code{font-size:10px;font-family:monospace;background:#F3F4F6;padding:1px 4px;border-radius:2px}
|
||
|
||
/* File table */
|
||
.FILE-TABLE{width:100%;border-collapse:collapse;font-size:11px;margin-top:12px}
|
||
.FILE-TABLE th{text-align:left;padding:9px 14px;font-size:10px;font-weight:800;color:#888;text-transform:uppercase;letter-spacing:.5px;border-bottom:2px solid #E8E4DF;background:#F7F5F2}
|
||
.FILE-TABLE td{padding:9px 14px;border-bottom:1px solid #F0EDE6;color:#444;vertical-align:top}
|
||
.FILE-TABLE tr:last-child td{border-bottom:none}
|
||
.FILE-TABLE code{font-size:10px;font-family:monospace;background:#F3F4F6;padding:1px 4px;border-radius:2px}
|
||
.FILE-TABLE .lines{font-size:10px;font-weight:700;color:#002850}
|
||
.FILE-TABLE .delta-add{color:#166534;font-weight:700}
|
||
.FILE-TABLE .delta-rm{color:#B91C1C;font-weight:700}
|
||
|
||
/* General utility */
|
||
code{font-size:10px;font-family:monospace;background:#F3F4F6;padding:1px 4px;border-radius:2px;color:#1A1A1A}
|
||
.note{font-size:11px;color:#666;line-height:1.6;margin-bottom:10px}
|
||
.note strong{color:#1A1A1A}
|
||
.SPACER{height:1px;background:#E0DDD6;margin:14px 0}
|
||
.row-gap{display:flex;gap:20px;align-items:flex-start;margin-bottom:16px;flex-wrap:wrap}
|
||
|
||
/* Mobile wireframe shell */
|
||
.wf-m{background:#fff;border:2px solid #B8B4AE;border-radius:14px;overflow:hidden;box-shadow:0 4px 16px rgba(0,0,0,.08);width:160px;flex-shrink:0}
|
||
.wf-m .N-m{height:36px}
|
||
.mobile-status{height:8px;background:#0D2240;display:flex;align-items:center;justify-content:flex-end;padding:0 8px}
|
||
.mobile-status-dot{width:3px;height:3px;border-radius:50%;background:rgba(255,255,255,.4);margin-left:2px}
|
||
|
||
/* Tablet wireframe */
|
||
.wf-t{background:#fff;border:2px solid #B8B4AE;border-radius:10px;overflow:hidden;box-shadow:0 4px 16px rgba(0,0,0,.08);width:260px;flex-shrink:0}
|
||
|
||
/* i18n table */
|
||
.I18N-TABLE{width:100%;border-collapse:collapse;font-size:11px;margin-top:8px}
|
||
.I18N-TABLE th{text-align:left;padding:7px 12px;font-size:10px;font-weight:800;color:#888;text-transform:uppercase;letter-spacing:.5px;border-bottom:2px solid #E8E4DF;background:#F7F5F2}
|
||
.I18N-TABLE td{padding:7px 12px;border-bottom:1px solid #F0EDE6;vertical-align:top}
|
||
.I18N-TABLE code{font-family:monospace;font-size:10px;background:#F3F4F6;padding:1px 4px;border-radius:2px}
|
||
.I18N-TABLE .de{color:#1A1A1A;font-weight:600}
|
||
.I18N-TABLE .en{color:#444}
|
||
.I18N-TABLE .es{color:#666}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="doc">
|
||
|
||
<!-- ═══════════════════════════════════════════ MASTHEAD ═══════════════════════ -->
|
||
<div class="masthead">
|
||
<div class="masthead-left">
|
||
<h1>Conversations — Narrow Column Redesign</h1>
|
||
<p>Developer specification for the WhatsApp/SMS-inspired chat layout. Constrains all bubble content to a centred 640 px column so opposing bubbles are close together — familiar to older users, easier to follow on wide screens.</p>
|
||
<div class="masthead-meta">
|
||
<span class="masthead-tag v-badge">v1.0 · 2026-03-29</span>
|
||
<span class="masthead-tag file-badge">3 files changed</span>
|
||
<span class="masthead-tag issue-badge">feature/153-notification-history branch</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════════════ SECTION 0 — ARCHITECTURE ══════════ -->
|
||
<div class="section">
|
||
<div class="sec-intro">
|
||
<div class="sec-num">0</div>
|
||
<div class="sec-meta">
|
||
<span class="sec-badge badge-arch">Architecture</span>
|
||
<div class="sec-title">Files, routes, and constraints</div>
|
||
<div class="sec-tagline">Three files. One route. No new dependencies. All changes are presentational — the data layer, server load functions, and URL query-param contract are untouched.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<table class="FILE-TABLE">
|
||
<thead>
|
||
<tr>
|
||
<th>File</th>
|
||
<th>What changes</th>
|
||
<th>Approx. lines touched</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><code>frontend/src/routes/conversations/ConversationFilterBar.svelte</code></td>
|
||
<td>Add <code>expanded</code> bindable prop. Render either a collapsed strip or the full form depending on state. Add "Adjust" button. Add "Apply"/"Cancel" controls for overlay mode.</td>
|
||
<td class="lines"><span class="delta-add">+38</span> / <span class="delta-rm">−2</span></td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>frontend/src/routes/conversations/ConversationTimeline.svelte</code></td>
|
||
<td>Remove central vertical line. Wrap bubble list in <code>max-w-[640px] mx-auto</code> column. Change bubble max-width. Add status text labels alongside status dots.</td>
|
||
<td class="lines"><span class="delta-add">+14</span> / <span class="delta-rm">−6</span></td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>frontend/src/routes/conversations/+page.svelte</code></td>
|
||
<td>Add <code>filterExpanded</code> reactive state. Auto-collapse when both persons are selected and documents load. Pass <code>bind:expanded</code> to FilterBar.</td>
|
||
<td class="lines"><span class="delta-add">+8</span> / <span class="delta-rm">−1</span></td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>frontend/src/lib/paraglide/messages/*.json</code> (de / en / es)</td>
|
||
<td>Add 6 new i18n keys: <code>conv_filter_adjust</code> + 5 status label keys.</td>
|
||
<td class="lines"><span class="delta-add">+6</span> per file</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<div class="DECISION">
|
||
<div class="DECISION-title">🏗 Architecture decisions</div>
|
||
<ul>
|
||
<li>Route stays at <code>/conversations</code> — query params (<code>senderId</code>, <code>receiverId</code>, <code>from</code>, <code>to</code>, <code>dir</code>) are the only data contract. No changes to <code>+page.server.ts</code>.</li>
|
||
<li>The <code>filterExpanded</code> flag lives in <code>+page.svelte</code> (not FilterBar) so the parent controls auto-collapse behaviour after navigation.</li>
|
||
<li>No JS animation libraries. Collapse uses a CSS <code>transition: max-height</code> or a Svelte <code>#if</code> block — developer's choice, but the spec uses <code>#if</code> for simplicity.</li>
|
||
<li>Breakpoints: mobile 375 px, tablet 768 px, desktop 1440 px. The 640 px column is achieved with Tailwind <code>max-w-[640px]</code> — no new CSS variables needed.</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<p class="note" style="margin-top:12px"><strong>Unchanged:</strong> <code>+page.server.ts</code>, the API client, all repository/service code, URL query-param names, Paraglide message IDs already in use, canWrite guard, and all existing test IDs (<code>data-testid</code>).</p>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════════════ SECTION 1 — FILTER BAR ════════════ -->
|
||
<div class="section">
|
||
<div class="sec-intro">
|
||
<div class="sec-num">1</div>
|
||
<div class="sec-meta">
|
||
<span class="sec-badge badge-filter">Filter Bar</span>
|
||
<div class="sec-title">Collapsible filter bar — 4 states</div>
|
||
<div class="sec-tagline">When no conversation is active the full form stays visible (nothing to collapse yet). Once both persons are selected and results load, the bar auto-collapses to a single-line strip. Tapping "Adjust" re-expands it as an inline overlay.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 1a + 1b side by side -->
|
||
<div class="screens cols2" style="margin-bottom:24px">
|
||
<!-- 1a: Desktop expanded (no conversation) -->
|
||
<div class="screen-block">
|
||
<div class="screen-label">1a <span class="sz">Desktop 1440</span> <span class="state-tag">No conversation selected</span></div>
|
||
<div class="wf" style="width:100%">
|
||
<div class="wf-bar"><div class="dot r"></div><div class="dot y"></div><div class="dot g"></div><div class="urlbar"><span>familienarchiv.local/conversations</span></div></div>
|
||
<div class="N">
|
||
<div class="logo">FAMILIENARCHIV</div>
|
||
<div class="nl">Dokumente</div><div class="nl on">Gespräche</div><div class="nl">Personen</div>
|
||
<div class="nr"><div class="nav-ico"></div><div class="nav-user">MA</div></div>
|
||
</div>
|
||
<div class="MAIN">
|
||
<div class="PAGE-HEAD">
|
||
<div class="PAGE-H1">Gespräche</div>
|
||
<div class="PAGE-SUB">Briefwechsel zwischen Familienmitgliedern</div>
|
||
</div>
|
||
<!-- Full filter bar -->
|
||
<div class="FB-FULL">
|
||
<div class="FB-ROW1">
|
||
<div>
|
||
<div class="FB-LABEL">Person A</div>
|
||
<div class="FB-INPUT placeholder">Name eingeben…</div>
|
||
</div>
|
||
<div class="FB-SWAP">⇅</div>
|
||
<div>
|
||
<div class="FB-LABEL">Person B</div>
|
||
<div class="FB-INPUT placeholder">Name eingeben…</div>
|
||
</div>
|
||
</div>
|
||
<div class="FB-ROW2">
|
||
<div>
|
||
<div class="FB-LABEL">Von Datum</div>
|
||
<div class="FB-INPUT placeholder">TT.MM.JJJJ</div>
|
||
</div>
|
||
<div>
|
||
<div class="FB-LABEL">Bis Datum</div>
|
||
<div class="FB-INPUT placeholder">TT.MM.JJJJ</div>
|
||
</div>
|
||
<div>
|
||
<div class="FB-LABEL"> </div>
|
||
<div class="FB-SORT">Sortierung: Neueste zuerst ▾</div>
|
||
</div>
|
||
</div>
|
||
<div style="margin-top:6px;font-size:8px;color:#AAA;font-style:italic">↑ No collapse button — conversation not yet active</div>
|
||
</div>
|
||
<!-- Empty state -->
|
||
<div class="EMPTY" style="padding:32px">
|
||
<div class="EMPTY-ICON">👥</div>
|
||
<div class="EMPTY-H">Wähle zwei Personen aus</div>
|
||
<div class="EMPTY-S">Wähle Person A und Person B aus, um ihre Korrespondenz anzuzeigen</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="screen-cap">Full filter form always expanded when senderId or receiverId is absent. No collapse/strip needed — there is no conversation to preserve context from.</div>
|
||
</div>
|
||
|
||
<!-- 1b: Desktop collapsed strip (conversation active) -->
|
||
<div class="screen-block">
|
||
<div class="screen-label">1b <span class="sz">Desktop 1440</span> <span class="state-tag collapsed">Conversation active · collapsed</span></div>
|
||
<div class="wf" style="width:100%;position:relative">
|
||
<div class="wf-bar"><div class="dot r"></div><div class="dot y"></div><div class="dot g"></div><div class="urlbar"><span>familienarchiv.local/conversations?senderId=…&receiverId=…</span></div></div>
|
||
<div class="N">
|
||
<div class="logo">FAMILIENARCHIV</div>
|
||
<div class="nl">Dokumente</div><div class="nl on">Gespräche</div><div class="nl">Personen</div>
|
||
<div class="nr"><div class="nav-ico"></div><div class="nav-user">MA</div></div>
|
||
</div>
|
||
<div class="MAIN">
|
||
<div class="PAGE-HEAD">
|
||
<div class="PAGE-H1">Gespräche</div>
|
||
<div class="PAGE-SUB">Briefwechsel zwischen Familienmitgliedern</div>
|
||
</div>
|
||
<!-- Collapsed strip -->
|
||
<div class="FB-STRIP">
|
||
<div class="FB-STRIP-left">
|
||
<div>
|
||
<div class="FB-STRIP-names">Anna Müller ⇄ Heinrich Raddatz</div>
|
||
<div class="FB-STRIP-meta">47 Dokumente · 1928–1965</div>
|
||
</div>
|
||
</div>
|
||
<div class="FB-STRIP-btn">Anpassen</div>
|
||
</div>
|
||
<!-- Summary bar -->
|
||
<div class="SUMBAR">
|
||
<div class="SUMBAR-count">47 Dokumente · 1928–1965</div>
|
||
<div class="SUMBAR-link">+ Neues Dokument</div>
|
||
</div>
|
||
<!-- Narrow column chat -->
|
||
<div class="CHAT-OUTER">
|
||
<div class="CHAT-COL">
|
||
<div class="YEAR-DIV"><div class="YEAR-LINE"></div><div class="YEAR-LABEL">1928</div><div class="YEAR-LINE"></div></div>
|
||
<div class="BUBBLE-ROW right">
|
||
<div class="BUBBLE-GROUP right">
|
||
<div class="AVATAR sender">AM</div>
|
||
<div class="BUBBLE sender">
|
||
<div class="BUBBLE-TITLE">Brief an Heinrich, April 1928</div>
|
||
<div class="BUBBLE-META">12.04.1928 · München</div>
|
||
<div class="BUBBLE-STATUS"><div class="STATUS-DOT uploaded"></div><div class="STATUS-LABEL" style="color:rgba(255,255,255,.65)">Hochgeladen</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="BUBBLE-ROW left">
|
||
<div class="BUBBLE-GROUP left">
|
||
<div class="AVATAR receiver">HR</div>
|
||
<div class="BUBBLE receiver">
|
||
<div class="BUBBLE-TITLE">Antwort vom 3. Mai</div>
|
||
<div class="BUBBLE-META">03.05.1928 · Berlin</div>
|
||
<div class="BUBBLE-STATUS"><div class="STATUS-DOT transcribed"></div><div class="STATUS-LABEL">Transkribiert</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="BUBBLE-ROW right">
|
||
<div class="BUBBLE-GROUP right">
|
||
<div class="AVATAR sender">AM</div>
|
||
<div class="BUBBLE sender">
|
||
<div class="BUBBLE-TITLE">Postkarte, Sommer 1928</div>
|
||
<div class="BUBBLE-META">15.07.1928</div>
|
||
<div class="BUBBLE-STATUS"><div class="STATUS-DOT reviewed"></div><div class="STATUS-LABEL" style="color:rgba(255,255,255,.65)">Geprüft</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="screen-cap">Auto-collapsed to single strip after results load. "Anpassen" button re-expands. Year + count visible in strip for quick orientation without opening filters.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 1c + 1d -->
|
||
<div class="screens cols2">
|
||
<!-- 1c: Expanded overlay (Adjust clicked) -->
|
||
<div class="screen-block">
|
||
<div class="screen-label">1c <span class="sz">Desktop 1440</span> <span class="state-tag active">Overlay expanded after "Adjust"</span></div>
|
||
<div class="wf" style="width:100%">
|
||
<div class="wf-bar"><div class="dot r"></div><div class="dot y"></div><div class="dot g"></div><div class="urlbar"><span>familienarchiv.local/conversations?senderId=…</span></div></div>
|
||
<div class="N">
|
||
<div class="logo">FAMILIENARCHIV</div>
|
||
<div class="nl">Dokumente</div><div class="nl on">Gespräche</div>
|
||
<div class="nr"><div class="nav-ico"></div></div>
|
||
</div>
|
||
<div class="MAIN">
|
||
<div class="PAGE-HEAD"><div class="PAGE-H1">Gespräche</div></div>
|
||
<!-- Still show strip (context) -->
|
||
<div class="FB-STRIP" style="opacity:.5">
|
||
<div class="FB-STRIP-left"><div class="FB-STRIP-names">Anna Müller ⇄ Heinrich Raddatz</div></div>
|
||
<div class="FB-STRIP-btn" style="opacity:.4">Anpassen</div>
|
||
</div>
|
||
<!-- Overlay form drops below strip -->
|
||
<div class="FB-OVERLAY">
|
||
<div style="font-size:8px;font-weight:800;color:#002850;text-transform:uppercase;letter-spacing:.5px;margin-bottom:10px">Filter anpassen</div>
|
||
<div class="FB-ROW1">
|
||
<div>
|
||
<div class="FB-LABEL">Person A</div>
|
||
<div class="FB-INPUT">Anna Müller</div>
|
||
</div>
|
||
<div class="FB-SWAP">⇅</div>
|
||
<div>
|
||
<div class="FB-LABEL">Person B</div>
|
||
<div class="FB-INPUT">Heinrich Raddatz</div>
|
||
</div>
|
||
</div>
|
||
<div class="FB-ROW2">
|
||
<div>
|
||
<div class="FB-LABEL">Von Datum</div>
|
||
<div class="FB-INPUT placeholder">—</div>
|
||
</div>
|
||
<div>
|
||
<div class="FB-LABEL">Bis Datum</div>
|
||
<div class="FB-INPUT placeholder">—</div>
|
||
</div>
|
||
<div>
|
||
<div class="FB-LABEL"> </div>
|
||
<div class="FB-SORT">Neueste zuerst ▾</div>
|
||
</div>
|
||
</div>
|
||
<div class="FB-APPLY">Anwenden</div>
|
||
<div class="FB-CANCEL">Abbrechen</div>
|
||
</div>
|
||
<!-- Blurred chat behind overlay -->
|
||
<div class="CHAT-OUTER" style="opacity:.3;pointer-events:none">
|
||
<div class="CHAT-COL">
|
||
<div class="BUBBLE-ROW right"><div class="BUBBLE-GROUP right"><div class="BUBBLE sender"><div class="BUBBLE-TITLE">Brief an Heinrich…</div></div></div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="screen-cap">Overlay drops inline below the strip (not a floating modal). Strip remains visible at reduced opacity. "Abbrechen" dismisses back to collapsed. "Anwenden" fires applyFilters() and collapses.</div>
|
||
</div>
|
||
|
||
<!-- 1d: Mobile filter bar -->
|
||
<div class="screen-block">
|
||
<div class="screen-label">1d <span class="sz">Mobile 375</span> <span class="state-tag mobile">Mobile — both states</span></div>
|
||
<div style="display:flex;gap:20px;align-items:flex-start">
|
||
<!-- Mobile collapsed -->
|
||
<div>
|
||
<div style="font-size:9px;font-weight:700;color:#888;text-align:center;margin-bottom:6px;text-transform:uppercase;letter-spacing:.5px">Collapsed</div>
|
||
<div class="wf-m">
|
||
<div class="mobile-status"><div class="mobile-status-dot"></div><div class="mobile-status-dot"></div><div class="mobile-status-dot"></div></div>
|
||
<div class="N-m"><div class="logo" style="font-size:8px">FAMILIENARCHIV</div><div class="nav-ico"></div></div>
|
||
<div class="MAIN-sm">
|
||
<div style="font-size:9px;font-weight:800;color:#0D2240;font-family:Georgia,serif;border-bottom:1px solid rgba(0,0,0,.08);padding-bottom:5px;margin-bottom:4px">Gespräche</div>
|
||
<div class="FB-STRIP" style="margin:0 -14px;padding:6px 10px;background:#F0EDE8;border-top:1px solid #DDD8D0;border-bottom:1px solid #DDD8D0;display:flex;align-items:center;justify-content:space-between;margin-bottom:6px">
|
||
<div>
|
||
<div style="font-size:8px;font-weight:700;color:#0D2240;font-family:Georgia,serif">A. Müller → H. Raddatz</div>
|
||
<div style="font-size:7px;color:#999">47 Dok. · 1928–1965</div>
|
||
</div>
|
||
<div style="font-size:7px;font-weight:800;color:#002850;border:1px solid #002850;padding:2px 5px;border-radius:2px;text-transform:uppercase">Anpassen</div>
|
||
</div>
|
||
<div class="CHAT-OUTER" style="padding:8px">
|
||
<div class="CHAT-COL-FULL" style="gap:4px">
|
||
<div class="YEAR-DIV"><div class="YEAR-LINE"></div><div class="YEAR-LABEL">1928</div><div class="YEAR-LINE"></div></div>
|
||
<div class="BUBBLE-ROW right">
|
||
<div class="BUBBLE-GROUP right" style="max-width:82%">
|
||
<div class="BUBBLE sender" style="padding:5px 7px">
|
||
<div class="BUBBLE-TITLE">Brief, April 1928</div>
|
||
<div class="BUBBLE-META">12.04.1928</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="BUBBLE-ROW left">
|
||
<div class="BUBBLE-GROUP left" style="max-width:82%">
|
||
<div class="BUBBLE receiver" style="padding:5px 7px">
|
||
<div class="BUBBLE-TITLE">Antwort, Mai 1928</div>
|
||
<div class="BUBBLE-META">03.05.1928</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Mobile expanded -->
|
||
<div>
|
||
<div style="font-size:9px;font-weight:700;color:#888;text-align:center;margin-bottom:6px;text-transform:uppercase;letter-spacing:.5px">Expanded</div>
|
||
<div class="wf-m">
|
||
<div class="mobile-status"><div class="mobile-status-dot"></div><div class="mobile-status-dot"></div><div class="mobile-status-dot"></div></div>
|
||
<div class="N-m"><div class="logo" style="font-size:8px">FAMILIENARCHIV</div><div class="nav-ico"></div></div>
|
||
<div class="MAIN-sm" style="gap:6px">
|
||
<div style="font-size:9px;font-weight:800;color:#0D2240;font-family:Georgia,serif;border-bottom:1px solid rgba(0,0,0,.08);padding-bottom:5px">Gespräche</div>
|
||
<div style="border:1px solid #C8C4BE;background:#F7F5F2;padding:10px">
|
||
<div class="FB-LABEL">Person A</div>
|
||
<div class="FB-INPUT" style="height:24px;font-size:8px;margin-bottom:8px">Anna Müller</div>
|
||
<div style="text-align:center;margin-bottom:8px;font-size:9px;color:#888">⇅ Tauschen</div>
|
||
<div class="FB-LABEL">Person B</div>
|
||
<div class="FB-INPUT" style="height:24px;font-size:8px;margin-bottom:8px">Heinrich Raddatz</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:5px;margin-bottom:8px">
|
||
<div>
|
||
<div class="FB-LABEL">Von</div>
|
||
<div class="FB-INPUT placeholder" style="height:24px;font-size:8px">—</div>
|
||
</div>
|
||
<div>
|
||
<div class="FB-LABEL">Bis</div>
|
||
<div class="FB-INPUT placeholder" style="height:24px;font-size:8px">—</div>
|
||
</div>
|
||
</div>
|
||
<div class="FB-SORT" style="height:24px;font-size:7px;margin-bottom:6px">Neueste zuerst ▾</div>
|
||
<div class="FB-APPLY" style="height:26px;font-size:8px">Anwenden</div>
|
||
<div class="FB-CANCEL">Abbrechen</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="screen-cap">Mobile: full-width tap target on collapsed strip. Expanded mode stacks all fields vertically. Swap button sits between Person A and B. "Abbrechen" collapses without navigating.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="DECISION" style="margin-top:20px">
|
||
<div class="DECISION-title">📐 Filter bar prop contract (updated)</div>
|
||
<ul>
|
||
<li>Add <code>expanded = $bindable(true)</code> to FilterBar's prop definition. Default <code>true</code> so existing tests that don't pass the prop still see the full form.</li>
|
||
<li>In <code>+page.svelte</code>: <code>let filterExpanded = $state(!data.filters.senderId || !data.filters.receiverId)</code>. After successful load with results, set <code>filterExpanded = false</code> inside the <code>$effect</code> that syncs filter values.</li>
|
||
<li>"Anwenden" inside the expanded overlay calls <code>onapplyFilters()</code> then sets <code>expanded = false</code> (local) — the parent's bound state updates via Svelte 5 bindable.</li>
|
||
<li>"Abbrechen" sets <code>expanded = false</code> without calling <code>onapplyFilters()</code>.</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════════════ SECTION 2 — CHAT COLUMN ══════════ -->
|
||
<div class="section">
|
||
<div class="sec-intro">
|
||
<div class="sec-num">2</div>
|
||
<div class="sec-meta">
|
||
<span class="sec-badge badge-chat">Chat Column</span>
|
||
<div class="sec-title">Narrow column layout — 3 breakpoints</div>
|
||
<div class="sec-tagline">The structural change. All bubble content is centred in a <code>max-w-[640px]</code> column inside the existing chat container. The outer container (border, shadow, surface bg) stays at full page width. No central dividing line.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Concept diagram -->
|
||
<div style="background:#fff;border:1.5px solid #E0DDD6;border-radius:6px;padding:20px;margin-bottom:24px;display:flex;gap:40px;align-items:center">
|
||
<!-- Before diagram -->
|
||
<div style="flex:1">
|
||
<div style="font-size:10px;font-weight:800;color:#B91C1C;text-transform:uppercase;letter-spacing:.5px;margin-bottom:10px">Before — full-width split</div>
|
||
<div style="background:#F7F5F2;border:1px solid #D1CCC8;border-radius:4px;padding:12px;position:relative;height:140px;overflow:hidden">
|
||
<!-- Central line -->
|
||
<div style="position:absolute;top:0;bottom:0;left:50%;width:1px;background:#D1CCC8;opacity:.6"></div>
|
||
<!-- Right bubble (wide) -->
|
||
<div style="position:absolute;right:8px;top:14px;width:44%;background:#002850;border-radius:6px 6px 2px 6px;padding:6px 8px">
|
||
<div style="font-size:7px;color:#fff;font-weight:600">Brief, April 1928</div>
|
||
<div style="font-size:6px;color:rgba(255,255,255,.6);margin-top:2px">12.04.1928</div>
|
||
</div>
|
||
<!-- Left bubble (wide) -->
|
||
<div style="position:absolute;left:8px;top:60px;width:44%;background:#E8E4DF;border-radius:6px 6px 6px 2px;padding:6px 8px">
|
||
<div style="font-size:7px;color:#333;font-weight:600">Antwort, Mai 1928</div>
|
||
<div style="font-size:6px;color:#888;margin-top:2px">03.05.1928</div>
|
||
</div>
|
||
<!-- Right bubble 2 -->
|
||
<div style="position:absolute;right:8px;top:106px;width:38%;background:#002850;border-radius:6px 6px 2px 6px;padding:6px 8px">
|
||
<div style="font-size:7px;color:#fff;font-weight:600">Postkarte, Sommer</div>
|
||
<div style="font-size:6px;color:rgba(255,255,255,.6);margin-top:2px">15.07.1928</div>
|
||
</div>
|
||
<!-- Gap annotation -->
|
||
<div style="position:absolute;top:36px;left:50%;transform:translateX(-50%);font-size:8px;color:#B91C1C;font-weight:700;white-space:nowrap">← wide gap →</div>
|
||
</div>
|
||
</div>
|
||
<!-- Arrow -->
|
||
<div style="font-size:28px;color:#D1CCC8">→</div>
|
||
<!-- After diagram -->
|
||
<div style="flex:1">
|
||
<div style="font-size:10px;font-weight:800;color:#166534;text-transform:uppercase;letter-spacing:.5px;margin-bottom:10px">After — narrow 640 px column</div>
|
||
<div style="background:#F7F5F2;border:1px solid #D1CCC8;border-radius:4px;padding:12px;position:relative;height:140px;overflow:hidden;display:flex;flex-direction:column;justify-content:center">
|
||
<!-- Narrow column indicator -->
|
||
<div style="max-width:60%;margin:0 auto;width:100%;display:flex;flex-direction:column;gap:5px">
|
||
<div style="display:flex;justify-content:flex-end">
|
||
<div style="width:55%;background:#002850;border-radius:6px 6px 2px 6px;padding:5px 7px">
|
||
<div style="font-size:7px;color:#fff;font-weight:600">Brief, April 1928</div>
|
||
<div style="font-size:6px;color:rgba(255,255,255,.6);margin-top:1px">12.04.1928</div>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;justify-content:flex-start">
|
||
<div style="width:55%;background:#E8E4DF;border-radius:6px 6px 6px 2px;padding:5px 7px">
|
||
<div style="font-size:7px;color:#333;font-weight:600">Antwort, Mai 1928</div>
|
||
<div style="font-size:6px;color:#888;margin-top:1px">03.05.1928</div>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;justify-content:flex-end">
|
||
<div style="width:48%;background:#002850;border-radius:6px 6px 2px 6px;padding:5px 7px">
|
||
<div style="font-size:7px;color:#fff;font-weight:600">Postkarte, Sommer</div>
|
||
<div style="font-size:6px;color:rgba(255,255,255,.6);margin-top:1px">15.07.1928</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Width annotation -->
|
||
<div style="position:absolute;bottom:4px;left:50%;transform:translateX(-50%);font-size:7px;color:#166534;font-weight:700;white-space:nowrap">max-w-[640px] · mx-auto</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="screens" style="gap:20px;flex-wrap:nowrap;align-items:flex-start">
|
||
<!-- 2a: Desktop -->
|
||
<div class="screen-block" style="flex:2;min-width:0">
|
||
<div class="screen-label">2a <span class="sz">Desktop 1440</span></div>
|
||
<div class="wf" style="width:100%">
|
||
<div class="wf-bar"><div class="dot r"></div><div class="dot y"></div><div class="dot g"></div><div class="urlbar"><span>familienarchiv.local/conversations?senderId=a1&receiverId=b2</span></div></div>
|
||
<div class="N"><div class="logo">FAMILIENARCHIV</div><div class="nl">Dokumente</div><div class="nl on">Gespräche</div><div class="nl">Personen</div><div class="nr"><div class="nav-ico"></div><div class="nav-user">MA</div></div></div>
|
||
<div class="MAIN" style="gap:8px">
|
||
<div class="PAGE-HEAD"><div class="PAGE-H1">Gespräche</div></div>
|
||
<div class="FB-STRIP"><div class="FB-STRIP-left"><div><div class="FB-STRIP-names">Anna Müller ⇄ Heinrich Raddatz</div><div class="FB-STRIP-meta">47 Dokumente · 1928–1965</div></div></div><div class="FB-STRIP-btn">Anpassen</div></div>
|
||
<div class="SUMBAR"><div class="SUMBAR-count">47 Dokumente · 1928–1965</div><div class="SUMBAR-link">+ Neues Dokument</div></div>
|
||
<!-- Chat outer at full width, column centred inside -->
|
||
<div class="CHAT-OUTER" style="padding:14px 20px">
|
||
<!-- Narrow column centred -->
|
||
<div style="max-width:340px;margin:0 auto;display:flex;flex-direction:column;gap:6px">
|
||
<div class="YEAR-DIV"><div class="YEAR-LINE"></div><div class="YEAR-LABEL">1928</div><div class="YEAR-LINE"></div></div>
|
||
<div class="BUBBLE-ROW right">
|
||
<div class="BUBBLE-GROUP right">
|
||
<div class="AVATAR sender">AM</div>
|
||
<div class="BUBBLE sender">
|
||
<div class="BUBBLE-TITLE">Brief an Heinrich, April 1928</div>
|
||
<div class="BUBBLE-META">12.04.1928 · München</div>
|
||
<div class="BUBBLE-STATUS"><div class="STATUS-DOT uploaded"></div><div class="STATUS-LABEL" style="color:rgba(255,255,255,.65)">Hochgeladen</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="BUBBLE-ROW left">
|
||
<div class="BUBBLE-GROUP left">
|
||
<div class="AVATAR receiver">HR</div>
|
||
<div class="BUBBLE receiver">
|
||
<div class="BUBBLE-TITLE">Antwort vom 3. Mai 1928</div>
|
||
<div class="BUBBLE-META">03.05.1928 · Berlin</div>
|
||
<div class="BUBBLE-STATUS"><div class="STATUS-DOT transcribed"></div><div class="STATUS-LABEL">Transkribiert</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="BUBBLE-ROW right">
|
||
<div class="BUBBLE-GROUP right">
|
||
<div class="AVATAR sender">AM</div>
|
||
<div class="BUBBLE sender">
|
||
<div class="BUBBLE-TITLE">Postkarte, Sommer 1928</div>
|
||
<div class="BUBBLE-META">15.07.1928</div>
|
||
<div class="BUBBLE-STATUS"><div class="STATUS-DOT reviewed"></div><div class="STATUS-LABEL" style="color:rgba(255,255,255,.65)">Geprüft</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="YEAR-DIV"><div class="YEAR-LINE"></div><div class="YEAR-LABEL">1929</div><div class="YEAR-LINE"></div></div>
|
||
<div class="BUBBLE-ROW left">
|
||
<div class="BUBBLE-GROUP left">
|
||
<div class="AVATAR receiver">HR</div>
|
||
<div class="BUBBLE receiver">
|
||
<div class="BUBBLE-TITLE">Neujahrskarte 1929</div>
|
||
<div class="BUBBLE-META">01.01.1929</div>
|
||
<div class="BUBBLE-STATUS"><div class="STATUS-DOT archived"></div><div class="STATUS-LABEL">Archiviert</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Annotation: column width -->
|
||
<div style="text-align:center;margin-top:8px;font-size:8px;color:#AAA;font-style:italic">⟵ outer container: full page width (max-w-5xl) · chat column: max-w-[640px] centred ⟶</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="screen-cap">Desktop 1440 px. Chat outer container spans to page max-w-5xl (unchanged). Bubble column is max-w-[640px] mx-auto inside. The grey padding on each side is empty space — visually frames the conversation. No central line.</div>
|
||
</div>
|
||
|
||
<!-- 2b: Tablet -->
|
||
<div class="screen-block" style="flex:1;min-width:220px">
|
||
<div class="screen-label">2b <span class="sz">Tablet 768</span></div>
|
||
<div class="wf-t">
|
||
<div class="wf-bar" style="height:20px"><div class="dot r"></div><div class="dot y"></div><div class="dot g"></div></div>
|
||
<div class="N" style="height:36px;padding:0 10px"><div class="logo" style="font-size:8px">FAMILIENARCHIV</div><div class="nl" style="font-size:7px">Gespräche</div><div class="nr"><div class="nav-ico" style="width:18px;height:18px"></div></div></div>
|
||
<div class="MAIN-sm">
|
||
<div class="FB-STRIP" style="margin:0 -14px;padding:5px 10px;background:#F0EDE8;border-bottom:1px solid #DDD8D0;margin-bottom:6px">
|
||
<div class="FB-STRIP-left"><div><div style="font-size:9px;font-weight:700;color:#0D2240;font-family:Georgia,serif">A. Müller ⇄ H. Raddatz</div><div style="font-size:7px;color:#999">47 Dok. · 1928–1965</div></div></div>
|
||
<div style="font-size:7px;font-weight:800;color:#002850;border:1px solid #002850;padding:2px 5px;border-radius:2px;text-transform:uppercase">Anpassen</div>
|
||
</div>
|
||
<div style="font-size:8px;color:#888;margin-bottom:4px">47 Dokumente · 1928–1965</div>
|
||
<div class="CHAT-OUTER" style="padding:8px">
|
||
<div style="max-width:220px;margin:0 auto;display:flex;flex-direction:column;gap:5px">
|
||
<div class="YEAR-DIV"><div class="YEAR-LINE"></div><div class="YEAR-LABEL">1928</div><div class="YEAR-LINE"></div></div>
|
||
<div class="BUBBLE-ROW right">
|
||
<div class="BUBBLE-GROUP right">
|
||
<div class="AVATAR sender" style="width:16px;height:16px;font-size:5px">AM</div>
|
||
<div class="BUBBLE sender" style="padding:5px 7px">
|
||
<div class="BUBBLE-TITLE">Brief an Heinrich, April</div>
|
||
<div class="BUBBLE-META">12.04.1928</div>
|
||
<div class="BUBBLE-STATUS"><div class="STATUS-DOT uploaded"></div><div class="STATUS-LABEL" style="color:rgba(255,255,255,.6)">Hochgeladen</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="BUBBLE-ROW left">
|
||
<div class="BUBBLE-GROUP left">
|
||
<div class="AVATAR receiver" style="width:16px;height:16px;font-size:5px">HR</div>
|
||
<div class="BUBBLE receiver" style="padding:5px 7px">
|
||
<div class="BUBBLE-TITLE">Antwort, Mai 1928</div>
|
||
<div class="BUBBLE-META">03.05.1928</div>
|
||
<div class="BUBBLE-STATUS"><div class="STATUS-DOT transcribed"></div><div class="STATUS-LABEL">Transkribiert</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="screen-cap">Tablet 768 px. Column narrows to max-w-[560px]. Avatars visible (sm:flex). Filter strip collapsed.</div>
|
||
</div>
|
||
|
||
<!-- 2c: Mobile -->
|
||
<div class="screen-block" style="flex:0 0 auto">
|
||
<div class="screen-label">2c <span class="sz">Mobile 375</span></div>
|
||
<div class="wf-m">
|
||
<div class="mobile-status"></div>
|
||
<div class="N-m" style="height:36px"><div class="logo" style="font-size:8px">FAMILIENARCHIV</div><div class="nav-ico"></div></div>
|
||
<div class="MAIN-sm" style="padding:8px 10px;gap:6px">
|
||
<div style="font-size:9px;font-weight:800;color:#0D2240;font-family:Georgia,serif;padding-bottom:5px;border-bottom:1px solid rgba(0,0,0,.08)">Gespräche</div>
|
||
<div style="background:#F0EDE8;border-bottom:1px solid #DDD8D0;padding:5px 8px;margin:0 -10px;display:flex;align-items:center;justify-content:space-between">
|
||
<div style="font-size:8px;font-weight:700;color:#0D2240;font-family:Georgia,serif">A. Müller → H. Raddatz</div>
|
||
<div style="font-size:7px;font-weight:800;color:#002850;border:1px solid #002850;padding:2px 4px;border-radius:2px;text-transform:uppercase">Anpassen</div>
|
||
</div>
|
||
<div class="CHAT-OUTER" style="padding:6px">
|
||
<!-- Mobile: column is naturally full width -->
|
||
<div style="display:flex;flex-direction:column;gap:5px">
|
||
<div class="YEAR-DIV"><div class="YEAR-LINE"></div><div class="YEAR-LABEL" style="font-size:6px">1928</div><div class="YEAR-LINE"></div></div>
|
||
<div class="BUBBLE-ROW right">
|
||
<div class="BUBBLE-GROUP right" style="max-width:82%">
|
||
<!-- No avatar on mobile -->
|
||
<div class="BUBBLE sender" style="padding:5px 7px">
|
||
<div class="BUBBLE-TITLE" style="font-size:7.5px">Brief an Heinrich, April 1928</div>
|
||
<div class="BUBBLE-META" style="font-size:6px">12.04.1928</div>
|
||
<div class="BUBBLE-STATUS"><div class="STATUS-DOT uploaded"></div><div class="STATUS-LABEL" style="font-size:6px;color:rgba(255,255,255,.6)">Hochgeladen</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="BUBBLE-ROW left">
|
||
<div class="BUBBLE-GROUP left" style="max-width:82%">
|
||
<div class="BUBBLE receiver" style="padding:5px 7px">
|
||
<div class="BUBBLE-TITLE" style="font-size:7.5px">Antwort, Mai 1928</div>
|
||
<div class="BUBBLE-META" style="font-size:6px">03.05.1928</div>
|
||
<div class="BUBBLE-STATUS"><div class="STATUS-DOT transcribed"></div><div class="STATUS-LABEL" style="font-size:6px">Transkribiert</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="BUBBLE-ROW right">
|
||
<div class="BUBBLE-GROUP right" style="max-width:82%">
|
||
<div class="BUBBLE sender" style="padding:5px 7px">
|
||
<div class="BUBBLE-TITLE" style="font-size:7.5px">Postkarte, Sommer</div>
|
||
<div class="BUBBLE-META" style="font-size:6px">15.07.1928</div>
|
||
<div class="BUBBLE-STATUS"><div class="STATUS-DOT reviewed"></div><div class="STATUS-LABEL" style="font-size:6px;color:rgba(255,255,255,.6)">Geprüft</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="screen-cap">Mobile 375 px. No avatars (hidden sm:flex). Bubbles max-w-[85%]. Column fills full width naturally — no mx-auto padding needed.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════════════ SECTION 3 — BUBBLE CARD ══════════ -->
|
||
<div class="section">
|
||
<div class="sec-intro">
|
||
<div class="sec-num">3</div>
|
||
<div class="sec-meta">
|
||
<span class="sec-badge badge-bubble">Bubble Card</span>
|
||
<div class="sec-title">Bubble card anatomy — sender & receiver</div>
|
||
<div class="sec-tagline">The cards are links (<code><a href="/documents/{id}"></code>). The key WCAG fix: status indicator gains a text label next to the coloured dot so colour is no longer the sole information channel.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="screens cols2">
|
||
<!-- Sender bubble -->
|
||
<div class="screen-block">
|
||
<div class="screen-label">3a — Sender bubble (right-aligned, navy)</div>
|
||
<div style="background:#F7F5F2;border:1.5px solid #E0DDD6;border-radius:6px;padding:24px;position:relative">
|
||
<!-- Rendered bubble -->
|
||
<div style="display:flex;justify-content:flex-end;margin-bottom:20px">
|
||
<div style="display:flex;gap:8px;flex-direction:row-reverse;max-width:75%">
|
||
<div class="AVATAR sender" style="width:28px;height:28px;font-size:9px;align-self:flex-end;margin-bottom:2px">AM</div>
|
||
<div class="BUBBLE sender" style="padding:10px 12px;font-size:1px">
|
||
<div class="BUBBLE-TITLE" style="font-size:10px;margin-bottom:4px">Brief an Heinrich Raddatz, April 1928</div>
|
||
<div class="BUBBLE-META" style="font-size:9px;margin-bottom:5px">12.04.1928 · München</div>
|
||
<div class="BUBBLE-STATUS" style="gap:4px">
|
||
<div class="STATUS-DOT uploaded" style="width:6px;height:6px"></div>
|
||
<div class="STATUS-LABEL" style="font-size:8px;color:rgba(255,255,255,.65)">Hochgeladen</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Annotated breakdown -->
|
||
<div style="border-top:1px dashed #C8C4BE;padding-top:14px">
|
||
<table style="width:100%;font-size:10px;border-collapse:collapse">
|
||
<tr><td style="padding:3px 0;color:#888;width:38%;vertical-align:top">Container</td><td style="padding:3px 0;color:#333"><code>max-w-[80%]</code> (was <code>md:max-w-[70%]</code>). No breakpoint prefix needed — column is already narrow.</td></tr>
|
||
<tr><td style="padding:3px 0;color:#888;vertical-align:top">Corner cut</td><td style="padding:3px 0;color:#333"><code>rounded rounded-br-none</code> — WhatsApp-style tail at bottom-right</td></tr>
|
||
<tr><td style="padding:3px 0;color:#888;vertical-align:top">Background</td><td style="padding:3px 0;color:#333"><code>bg-primary</code> (#002850 navy)</td></tr>
|
||
<tr><td style="padding:3px 0;color:#888;vertical-align:top">Title</td><td style="padding:3px 0;color:#333"><code>font-serif text-sm font-medium text-primary-fg</code></td></tr>
|
||
<tr><td style="padding:3px 0;color:#888;vertical-align:top">Meta row</td><td style="padding:3px 0;color:#333"><code>font-sans text-[10px] tracking-wider uppercase text-primary-fg/70</code></td></tr>
|
||
<tr><td style="padding:3px 0;color:#888;vertical-align:top">Status — NEW</td><td style="padding:3px 0;color:#333"><code>flex items-center gap-1</code> → dot + text label (see note below)</td></tr>
|
||
<tr><td style="padding:3px 0;color:#888;vertical-align:top">Hover</td><td style="padding:3px 0;color:#333"><code>hover:-translate-y-0.5 hover:shadow-md</code> — retained unchanged</td></tr>
|
||
<tr><td style="padding:3px 0;color:#888;vertical-align:top">Avatar</td><td style="padding:3px 0;color:#333"><code>hidden sm:flex</code> — 32×32, navy fill, white initials</td></tr>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Receiver bubble -->
|
||
<div class="screen-block">
|
||
<div class="screen-label">3b — Receiver bubble (left-aligned, grey)</div>
|
||
<div style="background:#F7F5F2;border:1.5px solid #E0DDD6;border-radius:6px;padding:24px;position:relative">
|
||
<div style="display:flex;justify-content:flex-start;margin-bottom:20px">
|
||
<div style="display:flex;gap:8px;max-width:75%">
|
||
<div class="AVATAR receiver" style="width:28px;height:28px;font-size:9px;align-self:flex-end;margin-bottom:2px">HR</div>
|
||
<div class="BUBBLE receiver" style="padding:10px 12px">
|
||
<div class="BUBBLE-TITLE" style="font-size:10px;margin-bottom:4px">Antwort vom 3. Mai 1928</div>
|
||
<div class="BUBBLE-META" style="font-size:9px;margin-bottom:5px">03.05.1928 · Berlin</div>
|
||
<div class="BUBBLE-STATUS" style="gap:4px">
|
||
<div class="STATUS-DOT transcribed" style="width:6px;height:6px"></div>
|
||
<div class="STATUS-LABEL" style="font-size:8px;color:#444">Transkribiert</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div style="border-top:1px dashed #C8C4BE;padding-top:14px">
|
||
<table style="width:100%;font-size:10px;border-collapse:collapse">
|
||
<tr><td style="padding:3px 0;color:#888;width:38%;vertical-align:top">Corner cut</td><td style="padding:3px 0;color:#333"><code>rounded rounded-bl-none</code> — tail at bottom-left</td></tr>
|
||
<tr><td style="padding:3px 0;color:#888;vertical-align:top">Background</td><td style="padding:3px 0;color:#333"><code>bg-muted/50</code> (light grey, ~#E8E4DF)</td></tr>
|
||
<tr><td style="padding:3px 0;color:#888;vertical-align:top">Title</td><td style="padding:3px 0;color:#333"><code>font-serif text-sm font-medium text-ink</code></td></tr>
|
||
<tr><td style="padding:3px 0;color:#888;vertical-align:top">Meta row</td><td style="padding:3px 0;color:#333"><code>font-sans text-[10px] tracking-wider uppercase text-ink-2</code></td></tr>
|
||
<tr><td style="padding:3px 0;color:#888;vertical-align:top">Status — NEW</td><td style="padding:3px 0;color:#333">Same structure, label colour <code>text-ink-2</code> (dark enough for contrast)</td></tr>
|
||
<tr><td style="padding:3px 0;color:#888;vertical-align:top">Avatar</td><td style="padding:3px 0;color:#333"><code>hidden sm:flex</code> — 32×32, white bg, ink initials, border</td></tr>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Status label table -->
|
||
<div style="background:#fff;border:1.5px solid #E0DDD6;border-radius:6px;padding:16px;margin-top:16px">
|
||
<div style="font-size:10px;font-weight:800;color:#555;text-transform:uppercase;letter-spacing:1px;margin-bottom:10px">Status dot → label mapping (ConversationTimeline.svelte)</div>
|
||
<table class="BP-table">
|
||
<thead><tr><th>DocumentStatus</th><th>Dot colour class</th><th>Label key (i18n)</th><th>de</th><th>Sender text colour</th><th>Receiver text colour</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><code>PLACEHOLDER</code></td><td><code>bg-yellow-400</code></td><td><code>conv_status_placeholder</code></td><td>Platzhalter</td><td><code>text-primary-fg/60</code></td><td><code>text-ink-2</code></td></tr>
|
||
<tr><td><code>UPLOADED</code></td><td><code>bg-accent</code> (green)</td><td><code>conv_status_uploaded</code></td><td>Hochgeladen</td><td><code>text-primary-fg/65</code></td><td><code>text-ink-2</code></td></tr>
|
||
<tr><td><code>TRANSCRIBED</code></td><td><code>bg-blue-400</code></td><td><code>conv_status_transcribed</code></td><td>Transkribiert</td><td><code>text-primary-fg/65</code></td><td><code>text-ink-2</code></td></tr>
|
||
<tr><td><code>REVIEWED</code></td><td><code>bg-purple-400</code></td><td><code>conv_status_reviewed</code></td><td>Geprüft</td><td><code>text-primary-fg/65</code></td><td><code>text-ink-2</code></td></tr>
|
||
<tr><td><code>ARCHIVED</code></td><td><code>bg-gray-400</code></td><td><code>conv_status_archived</code></td><td>Archiviert</td><td><code>text-primary-fg/50</code></td><td><code>text-ink-3</code></td></tr>
|
||
</tbody>
|
||
</table>
|
||
<p style="font-size:10px;color:#888;margin-top:8px">Implement as a <code>const statusLabels: Record<string, string></code> in <code>ConversationTimeline.svelte</code> using <code>m.conv_status_*</code> functions. Fall back to <code>doc.status</code> for unknown values.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════════════ SECTION 4 — YEAR DIVIDERS ════════ -->
|
||
<div class="section">
|
||
<div class="sec-intro">
|
||
<div class="sec-num">4</div>
|
||
<div class="sec-meta">
|
||
<span class="sec-badge badge-divider">Year Dividers</span>
|
||
<div class="sec-title">Year dividers inside the narrow column</div>
|
||
<div class="sec-tagline">No HTML change needed. The existing <code>flex items-center</code> with <code>flex-grow border-t</code> lines auto-sizes to whatever container it sits in. Moving the bubble list inside the 640 px column automatically constrains dividers too.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="screens" style="align-items:flex-start">
|
||
<div class="screen-block" style="flex:1">
|
||
<div class="screen-label">4a — Year divider in narrow column</div>
|
||
<div style="background:#F7F5F2;border:1.5px solid #E0DDD6;border-radius:6px;padding:24px">
|
||
<div style="max-width:400px;margin:0 auto;display:flex;flex-direction:column;gap:10px">
|
||
<!-- Before year divider context -->
|
||
<div class="BUBBLE-ROW right">
|
||
<div class="BUBBLE-GROUP right" style="max-width:70%">
|
||
<div class="BUBBLE sender" style="padding:8px 10px"><div class="BUBBLE-TITLE" style="font-size:10px">Brief, Dezember 1928</div><div class="BUBBLE-META" style="font-size:8px">18.12.1928</div></div>
|
||
</div>
|
||
</div>
|
||
<!-- Year divider -->
|
||
<div class="YEAR-DIV" style="padding:6px 0">
|
||
<div class="YEAR-LINE"></div>
|
||
<div class="YEAR-LABEL" style="font-size:9px;padding:0 12px">1929</div>
|
||
<div class="YEAR-LINE"></div>
|
||
</div>
|
||
<!-- After year divider context -->
|
||
<div class="BUBBLE-ROW left">
|
||
<div class="BUBBLE-GROUP left" style="max-width:70%">
|
||
<div class="BUBBLE receiver" style="padding:8px 10px"><div class="BUBBLE-TITLE" style="font-size:10px">Neujahrskarte 1929</div><div class="BUBBLE-META" style="font-size:8px">01.01.1929</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div style="border-top:1px dashed #C8C4BE;padding-top:12px;margin-top:16px;font-size:10px;color:#666;line-height:1.7">
|
||
<strong>Unchanged Svelte code:</strong><br>
|
||
<code><div class="relative flex items-center py-2 text-center"></code><br>
|
||
<code> <div class="flex-grow border-t border-line"></div></code><br>
|
||
<code> <span class="mx-4 font-sans text-xs font-bold tracking-widest text-ink/40 uppercase">{year}</span></code><br>
|
||
<code> <div class="flex-grow border-t border-line"></div></code><br>
|
||
<code></div></code><br><br>
|
||
The only structural change is that this element now lives inside the <code>max-w-[640px] mx-auto</code> wrapper div instead of the previous full-width flex column.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════════════ SECTION 5 — SUMMARY BAR ══════════ -->
|
||
<div class="section">
|
||
<div class="sec-intro">
|
||
<div class="sec-num">5</div>
|
||
<div class="sec-meta">
|
||
<span class="sec-badge badge-summary">Summary Bar</span>
|
||
<div class="sec-title">Summary bar — above the narrow column</div>
|
||
<div class="sec-tagline">The summary bar (count + year range + new doc link) sits outside the narrow column at full page width. It remains at <code>max-w-5xl</code> because it is UI chrome, not conversation content.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="screen-block" style="max-width:700px">
|
||
<div class="screen-label">5a — Summary bar placement</div>
|
||
<div style="background:#ECEAE4;border:1.5px solid #E0DDD6;border-radius:6px;padding:16px">
|
||
<!-- Page max-w indicator -->
|
||
<div style="border:1px dashed #A6DAD8;border-radius:4px;padding:10px;margin-bottom:4px">
|
||
<div style="font-size:7.5px;color:#0F5B58;font-weight:700;text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px">max-w-5xl (page column)</div>
|
||
<!-- Summary bar -->
|
||
<div style="display:flex;align-items:center;justify-content:space-between;padding:0 0 8px;border-bottom:1px solid #E0DDD6;margin-bottom:8px">
|
||
<span style="font-size:10px;font-weight:600;color:#888">47 Dokumente · 1928–1965</span>
|
||
<span style="font-size:9px;font-weight:700;color:#002850;display:flex;align-items:center;gap:3px">+ Neues Dokument</span>
|
||
</div>
|
||
<!-- Chat outer -->
|
||
<div style="background:#F7F5F2;border:1px solid #D1CCC8;border-radius:3px;padding:10px;position:relative">
|
||
<div style="font-size:7.5px;color:#0F5B58;font-weight:700;text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px;text-align:center">chat container — full width, bg-surface border shadow</div>
|
||
<!-- Narrow column indicator -->
|
||
<div style="border:1px dashed #002850;border-radius:3px;max-width:55%;margin:0 auto;padding:8px;text-align:center">
|
||
<div style="font-size:7.5px;color:#002850;font-weight:700;text-transform:uppercase;letter-spacing:.5px">max-w-[640px] · mx-auto</div>
|
||
<div style="font-size:7px;color:#888;margin-top:3px">bubble list lives here</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p style="font-size:10px;color:#666;margin-top:8px;line-height:1.6">The <code>mb-4 flex items-center justify-between</code> summary div in <code>ConversationTimeline.svelte</code> is unchanged — it is already outside the chat container div. No code change needed for the summary bar itself.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════════════ SECTION 6 — EMPTY STATES ═════════ -->
|
||
<div class="section">
|
||
<div class="sec-intro">
|
||
<div class="sec-num">6</div>
|
||
<div class="sec-meta">
|
||
<span class="sec-badge badge-empty">Empty States</span>
|
||
<div class="sec-title">Two empty state variants</div>
|
||
<div class="sec-tagline">The "no conversation selected" state keeps the full expanded filter bar (no strip). The "conversation selected but no results" state uses the collapsed strip and a compact empty message.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="screens cols2">
|
||
<!-- 6a: No conversation selected -->
|
||
<div class="screen-block">
|
||
<div class="screen-label">6a — No conversation selected (senderId or receiverId missing)</div>
|
||
<div class="wf" style="width:100%">
|
||
<div class="wf-bar"><div class="dot r"></div><div class="dot y"></div><div class="dot g"></div><div class="urlbar"><span>familienarchiv.local/conversations</span></div></div>
|
||
<div class="N"><div class="logo">FAMILIENARCHIV</div><div class="nl on">Gespräche</div><div class="nr"><div class="nav-ico"></div></div></div>
|
||
<div class="MAIN" style="gap:8px">
|
||
<div class="PAGE-HEAD"><div class="PAGE-H1">Gespräche</div><div class="PAGE-SUB">Briefwechsel zwischen Familienmitgliedern</div></div>
|
||
<div class="FB-FULL">
|
||
<div class="FB-ROW1">
|
||
<div><div class="FB-LABEL">Person A</div><div class="FB-INPUT placeholder">Name eingeben…</div></div>
|
||
<div class="FB-SWAP">⇅</div>
|
||
<div><div class="FB-LABEL">Person B</div><div class="FB-INPUT">Heinrich Raddatz</div></div>
|
||
</div>
|
||
<div class="FB-ROW2">
|
||
<div><div class="FB-LABEL">Von Datum</div><div class="FB-INPUT placeholder">TT.MM.JJJJ</div></div>
|
||
<div><div class="FB-LABEL">Bis Datum</div><div class="FB-INPUT placeholder">TT.MM.JJJJ</div></div>
|
||
<div><div class="FB-LABEL"> </div><div class="FB-SORT">Neueste zuerst ▾</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="EMPTY">
|
||
<div class="EMPTY-ICON">👥</div>
|
||
<div class="EMPTY-H">Wähle zwei Personen aus</div>
|
||
<div class="EMPTY-S" style="max-width:220px">Wähle Person A und Person B aus, um ihren Briefwechsel anzuzeigen</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="screen-cap">Full filter bar expanded (filterExpanded = true). "conv_empty_heading" / "conv_empty_text" message keys unchanged. Empty state uses dashed border to signal "waiting for input".</div>
|
||
</div>
|
||
|
||
<!-- 6b: Conversation selected, no results -->
|
||
<div class="screen-block">
|
||
<div class="screen-label">6b — Conversation selected, no results found</div>
|
||
<div class="wf" style="width:100%">
|
||
<div class="wf-bar"><div class="dot r"></div><div class="dot y"></div><div class="dot g"></div><div class="urlbar"><span>familienarchiv.local/conversations?senderId=a1&receiverId=b2&from=1960…</span></div></div>
|
||
<div class="N"><div class="logo">FAMILIENARCHIV</div><div class="nl on">Gespräche</div><div class="nr"><div class="nav-ico"></div></div></div>
|
||
<div class="MAIN" style="gap:8px">
|
||
<div class="PAGE-HEAD"><div class="PAGE-H1">Gespräche</div></div>
|
||
<div class="FB-STRIP">
|
||
<div class="FB-STRIP-left"><div><div class="FB-STRIP-names">Anna Müller ⇄ Heinrich Raddatz</div><div class="FB-STRIP-meta">Von 1960 · Neueste zuerst</div></div></div>
|
||
<div class="FB-STRIP-btn">Anpassen</div>
|
||
</div>
|
||
<div class="EMPTY-sm" style="padding:32px 12px">
|
||
<div style="font-size:12px;color:#888;margin-bottom:6px">🔍</div>
|
||
<div class="EMPTY-H">Keine Dokumente gefunden</div>
|
||
<div class="EMPTY-S" style="max-width:240px;margin-bottom:10px">Für diesen Zeitraum wurden keine Dokumente gefunden. Passe die Filter an.</div>
|
||
<div style="font-size:9px;font-weight:800;color:#002850;border:1.5px solid #002850;padding:5px 12px;border-radius:3px;text-transform:uppercase;letter-spacing:.5px;cursor:pointer">Anpassen</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="screen-cap">Collapsed strip still shows who the conversation is between. Compact empty state with an inline "Anpassen" CTA that triggers filterExpanded = true. Uses "conv_no_results_heading" / "conv_no_results_text" keys unchanged.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════════════ SECTION 7 — IMPLEMENTATION NOTES ═ -->
|
||
<div class="section">
|
||
<div class="sec-intro">
|
||
<div class="sec-num">7</div>
|
||
<div class="sec-meta">
|
||
<span class="sec-badge badge-impl">Implementation</span>
|
||
<div class="sec-title">Implementation notes — developer checklist</div>
|
||
<div class="sec-tagline">Numbered rules. Each rule maps to a specific line-level change in one of the three files.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="background:#fff;border:1.5px solid #E0DDD6;border-radius:6px;overflow:hidden">
|
||
<div class="IMPL-RULES">
|
||
<div class="IMPL-RULE">
|
||
<div class="IMPL-NUM">1</div>
|
||
<div class="IMPL-BODY">
|
||
<div class="IMPL-TITLE">Narrow column container (ConversationTimeline.svelte)</div>
|
||
<div class="IMPL-TEXT">Wrap the <code>flex flex-col gap-4</code> div (currently inside <code>p-6 md:p-8</code>) with <code><div class="max-w-[640px] mx-auto"></code>. The outer <code>relative overflow-hidden rounded-sm border border-line bg-surface shadow-sm</code> container stays unchanged at full page width.</div>
|
||
</div>
|
||
</div>
|
||
<div class="IMPL-RULE">
|
||
<div class="IMPL-NUM">2</div>
|
||
<div class="IMPL-BODY">
|
||
<div class="IMPL-TITLE">Remove central vertical line (ConversationTimeline.svelte)</div>
|
||
<div class="IMPL-TEXT">Delete the entire <code><div class="absolute top-0 bottom-0 left-1/2 hidden w-px -translate-x-1/2 transform bg-muted md:block"></div></code> element. It served as a decorative lane divider on wide screens; inside a narrow column it would bisect the bubbles incorrectly.</div>
|
||
</div>
|
||
</div>
|
||
<div class="IMPL-RULE">
|
||
<div class="IMPL-NUM">3</div>
|
||
<div class="IMPL-BODY">
|
||
<div class="IMPL-TITLE">Bubble max-width (ConversationTimeline.svelte)</div>
|
||
<div class="IMPL-TEXT">Change <code>max-w-[90%] md:max-w-[70%]</code> to <code>max-w-[80%]</code>. No breakpoint prefix is needed — the column itself is narrow so 80 % of 640 px (~512 px) is the effective max on all screens.</div>
|
||
</div>
|
||
</div>
|
||
<div class="IMPL-RULE">
|
||
<div class="IMPL-NUM">4</div>
|
||
<div class="IMPL-BODY">
|
||
<div class="IMPL-TITLE">Status text label (ConversationTimeline.svelte — WCAG fix)</div>
|
||
<div class="IMPL-TEXT">Replace the lone <code><span class="mt-1.5 h-1.5 w-1.5 flex-shrink-0 rounded-full … " title={doc.status}></span></code> with:<br>
|
||
<code><span class="flex items-center gap-1"><span class="h-1.5 w-1.5 flex-shrink-0 rounded-full {colorClass}"></span><span class="text-[9px] font-sans uppercase tracking-wider {labelColorClass}">{statusLabel}</span></span></code><br>
|
||
Define <code>const statusLabels: Record<string, string></code> mapping status codes to <code>m.conv_status_*()</code> calls. Derive <code>statusLabel</code> as <code>statusLabels[doc.status] ?? doc.status</code>.</div>
|
||
</div>
|
||
</div>
|
||
<div class="IMPL-RULE">
|
||
<div class="IMPL-NUM">5</div>
|
||
<div class="IMPL-BODY">
|
||
<div class="IMPL-TITLE">filterExpanded state (+page.svelte)</div>
|
||
<div class="IMPL-TEXT">Add <code>let filterExpanded = $state(!data.filters.senderId || !data.filters.receiverId)</code> after the existing state declarations. Inside the existing <code>$effect</code> that syncs filter values, append: <code>if (senderId && receiverId) filterExpanded = false;</code>. This auto-collapses after a successful navigation that loads results.</div>
|
||
</div>
|
||
</div>
|
||
<div class="IMPL-RULE">
|
||
<div class="IMPL-NUM">6</div>
|
||
<div class="IMPL-BODY">
|
||
<div class="IMPL-TITLE">Pass expanded to FilterBar (+page.svelte)</div>
|
||
<div class="IMPL-TEXT">Add <code>bind:expanded={filterExpanded}</code> to the <code><ConversationFilterBar …></code> element. This is the only change to the JSX-like template in <code>+page.svelte</code> beyond step 5.</div>
|
||
</div>
|
||
</div>
|
||
<div class="IMPL-RULE">
|
||
<div class="IMPL-NUM">7</div>
|
||
<div class="IMPL-BODY">
|
||
<div class="IMPL-TITLE">Collapsed strip rendering (ConversationFilterBar.svelte)</div>
|
||
<div class="IMPL-TEXT">Add <code>expanded = $bindable(true)</code> to the prop definition. Wrap the existing form content in <code>{#if expanded}…{/if}</code>. Add a new <code>{:else}</code> branch that renders the single-line strip: <code><div class="flex items-center justify-between px-4 py-2 border-b border-line bg-surface mb-10"></code> containing person names + "Anpassen" button. The button's <code>onclick</code> sets <code>expanded = true</code>.</div>
|
||
</div>
|
||
</div>
|
||
<div class="IMPL-RULE">
|
||
<div class="IMPL-NUM">8</div>
|
||
<div class="IMPL-BODY">
|
||
<div class="IMPL-TITLE">Apply / Cancel in expanded overlay mode (ConversationFilterBar.svelte)</div>
|
||
<div class="IMPL-TEXT">When <code>expanded</code> is <code>true</code> and a conversation was previously active (i.e. <code>senderId && receiverId</code> are non-empty at mount time), render "Anwenden" and "Abbrechen" buttons at the bottom of the form. "Anwenden" calls <code>onapplyFilters()</code> then sets <code>expanded = false</code>. "Abbrechen" only sets <code>expanded = false</code>. When no conversation is active, these extra buttons are not shown — the person typeahead's <code>onchange</code> already fires <code>onapplyFilters()</code> automatically.</div>
|
||
</div>
|
||
</div>
|
||
<div class="IMPL-RULE">
|
||
<div class="IMPL-NUM">9</div>
|
||
<div class="IMPL-BODY">
|
||
<div class="IMPL-TITLE">Avatar visibility unchanged</div>
|
||
<div class="IMPL-TEXT">Keep <code>hidden sm:block</code> on the avatar wrapper. On mobile (375 px) avatars are hidden; on tablet+ they show at 32×32. No change needed — this class already exists in <code>ConversationTimeline.svelte</code>.</div>
|
||
</div>
|
||
</div>
|
||
<div class="IMPL-RULE">
|
||
<div class="IMPL-NUM">10</div>
|
||
<div class="IMPL-BODY">
|
||
<div class="IMPL-TITLE">Year divider — no change</div>
|
||
<div class="IMPL-TEXT">The existing <code>data-testid="year-divider"</code> element uses <code>flex-grow border-t</code> which auto-fills its container. Moving it inside the <code>max-w-[640px]</code> wrapper is the only structural change, and that is covered by rule 1. No attribute or class change needed on the divider element itself.</div>
|
||
</div>
|
||
</div>
|
||
<div class="IMPL-RULE">
|
||
<div class="IMPL-NUM">11</div>
|
||
<div class="IMPL-BODY">
|
||
<div class="IMPL-TITLE">i18n keys to add</div>
|
||
<div class="IMPL-TEXT">Add 6 keys to <code>messages/de.json</code>, <code>en.json</code>, and <code>es.json</code>. See i18n table below.</div>
|
||
</div>
|
||
</div>
|
||
<div class="IMPL-RULE">
|
||
<div class="IMPL-NUM">12</div>
|
||
<div class="IMPL-BODY">
|
||
<div class="IMPL-TITLE">Keyboard / accessibility</div>
|
||
<div class="IMPL-TEXT">"Anpassen" is a <code><button></code> — natively focusable, responds to Enter/Space. No ARIA additions needed beyond what a standard button provides. The status text label (rule 4) resolves the only existing WCAG colour-only information issue on this page.</div>
|
||
</div>
|
||
</div>
|
||
<div class="IMPL-RULE">
|
||
<div class="IMPL-NUM">13</div>
|
||
<div class="IMPL-BODY">
|
||
<div class="IMPL-TITLE">canWrite guard unchanged</div>
|
||
<div class="IMPL-TEXT">The <code>{#if canWrite}</code> block around the "+ Neues Dokument" link in <code>ConversationTimeline.svelte</code> is outside the narrow column wrapper (it sits in the summary bar). No change needed.</div>
|
||
</div>
|
||
</div>
|
||
<div class="IMPL-RULE">
|
||
<div class="IMPL-NUM">14</div>
|
||
<div class="IMPL-BODY">
|
||
<div class="IMPL-TITLE">data-testid attributes preserved</div>
|
||
<div class="IMPL-TEXT">Existing test IDs must not be removed: <code>conv-swap-btn</code>, <code>conv-summary</code>, <code>conv-new-doc-link</code>, <code>year-divider</code>. The new "Anpassen" button should receive <code>data-testid="conv-filter-adjust-btn"</code>.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- i18n table -->
|
||
<div style="margin-top:24px;background:#fff;border:1.5px solid #E0DDD6;border-radius:6px;overflow:hidden">
|
||
<div style="padding:12px 16px;font-size:10px;font-weight:800;color:#555;text-transform:uppercase;letter-spacing:1px;border-bottom:1px solid #E0DDD6;background:#F7F5F2">i18n keys to add (messages/de.json · en.json · es.json)</div>
|
||
<table class="I18N-TABLE">
|
||
<thead><tr><th>Key</th><th>de</th><th>en</th><th>es</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><code>conv_filter_adjust</code></td><td class="de">Anpassen</td><td class="en">Adjust</td><td class="es">Ajustar</td></tr>
|
||
<tr><td><code>conv_filter_apply</code></td><td class="de">Anwenden</td><td class="en">Apply</td><td class="es">Aplicar</td></tr>
|
||
<tr><td><code>conv_filter_cancel</code></td><td class="de">Abbrechen</td><td class="en">Cancel</td><td class="es">Cancelar</td></tr>
|
||
<tr><td><code>conv_status_placeholder</code></td><td class="de">Platzhalter</td><td class="en">Placeholder</td><td class="es">Marcador</td></tr>
|
||
<tr><td><code>conv_status_uploaded</code></td><td class="de">Hochgeladen</td><td class="en">Uploaded</td><td class="es">Subido</td></tr>
|
||
<tr><td><code>conv_status_transcribed</code></td><td class="de">Transkribiert</td><td class="en">Transcribed</td><td class="es">Transcrito</td></tr>
|
||
<tr><td><code>conv_status_reviewed</code></td><td class="de">Geprüft</td><td class="en">Reviewed</td><td class="es">Revisado</td></tr>
|
||
<tr><td><code>conv_status_archived</code></td><td class="de">Archiviert</td><td class="en">Archived</td><td class="es">Archivado</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════════════ BEFORE / AFTER COMPARISON ════════ -->
|
||
<div class="section">
|
||
<div class="sec-intro">
|
||
<div class="sec-num" style="color:#C8C4BE">Δ</div>
|
||
<div class="sec-meta">
|
||
<span class="sec-badge badge-arch">Comparison</span>
|
||
<div class="sec-title">Before / after — full diff summary</div>
|
||
<div class="sec-tagline">Side-by-side summary of every meaningful behavioural and visual change.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="CMP">
|
||
<div class="CMP-col before">
|
||
<div class="CMP-head before">Before</div>
|
||
<div class="CMP-body">
|
||
<div class="CMP-item">Full filter form always visible, never collapses</div>
|
||
<div class="CMP-item">Bubbles race to opposite edges of 100 % screen width</div>
|
||
<div class="CMP-item">Central vertical grey line bisects wide chat container</div>
|
||
<div class="CMP-item">Bubble max-width: <code>md:max-w-[70%]</code> — up to 70 % of full page at desktop</div>
|
||
<div class="CMP-item">Status: colour-only dot, <code>title</code> tooltip (WCAG failure)</div>
|
||
<div class="CMP-item">Status dot classes: only <code>bg-accent</code> (uploaded) vs <code>bg-yellow-400</code> (all else)</div>
|
||
<div class="CMP-item">No "Anpassen" / "Anwenden" / "Abbrechen" controls</div>
|
||
<div class="CMP-item">Mobile: avatars hidden by <code>hidden sm:block</code> — correct but max-width too wide</div>
|
||
</div>
|
||
</div>
|
||
<div class="CMP-col after">
|
||
<div class="CMP-head after">After</div>
|
||
<div class="CMP-body">
|
||
<div class="CMP-item">Filter auto-collapses to single strip when conversation loads; re-expands on demand</div>
|
||
<div class="CMP-item">All bubbles centred in <code>max-w-[640px]</code> column — tight WhatsApp-style gap</div>
|
||
<div class="CMP-item">Central line removed — irrelevant inside narrow column</div>
|
||
<div class="CMP-item">Bubble max-width: <code>max-w-[80%]</code> — 80 % of 640 px = ~512 px, same visual weight across breakpoints</div>
|
||
<div class="CMP-item">Status: dot + text label side-by-side (WCAG 1.4.1 satisfied)</div>
|
||
<div class="CMP-item">Status dot has 5 distinct classes mapping to full <code>DocumentStatus</code> lifecycle</div>
|
||
<div class="CMP-item">FilterBar gains "Anpassen" (strip), "Anwenden" + "Abbrechen" (overlay)</div>
|
||
<div class="CMP-item">Mobile unchanged in avatar logic; bubble group max-width explicitly set to <code>max-w-[85%]</code></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div><!-- /.doc -->
|
||
</body>
|
||
</html>
|