Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / OCR Service Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / fail2ban Regex (push) Has been cancelled
CI / Semgrep Security Scan (push) Has been cancelled
CI / Compose Bucket Idempotency (push) Has been cancelled
Covers the SmartModeToggle pill (inside the search input, Google AI Mode style), InterpretationChipRow anatomy, DisambiguationPicker, and all status/error/empty states as full-result-area panels. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1213 lines
74 KiB
HTML
1213 lines
74 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>NL Search — Toggle, Chips & States · /documents · Familienarchiv</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=Tinos:ital,wght@0,400;0,700&family=Montserrat:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||
<style>
|
||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||
body{font-family:'Montserrat',system-ui,sans-serif;background:#ECEAE4;color:#1A1A1A;line-height:1.5;font-size:13px}
|
||
.doc{max-width:1300px;margin:0 auto;padding:48px 32px 120px}
|
||
|
||
/* ── Masthead ── */
|
||
.mh{padding-bottom:24px;border-bottom:3px solid #012851;margin-bottom:60px}
|
||
.mh h1{font-size:23px;font-weight:900;color:#012851;letter-spacing:-.4px}
|
||
.mh p{font-size:13px;color:#555;max-width:740px;line-height:1.75;margin-top:8px}
|
||
.mh .byline{font-size:9px;color:#999;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;margin-top:10px}
|
||
.tag-row{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}
|
||
.tag{background:#012851;color:#A1DCD8;padding:2px 8px;border-radius:2px;font-size:8px;font-weight:700;letter-spacing:.8px;text-transform:uppercase}
|
||
.tag.amber{background:#7c4a00;color:#fde68a}
|
||
|
||
/* ── Section headers ── */
|
||
.sh{margin:0 0 28px}
|
||
.sh h2{font-size:16px;font-weight:900;color:#012851;letter-spacing:-.2px}
|
||
.sh p{font-size:12.5px;color:#666;max-width:720px;line-height:1.7;margin-top:5px}
|
||
.section{margin-bottom:80px;padding-bottom:80px;border-bottom:2px dashed #C8C4BE}
|
||
.section:last-of-type{border-bottom:none;margin-bottom:0;padding-bottom:0}
|
||
|
||
/* ── Token tables ── */
|
||
.token-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
|
||
.token-table{border-radius:6px;overflow:hidden}
|
||
.token-table.light{background:#fff;border:1px solid #E0DDD6}
|
||
.token-table.dark{background:#0F1923;border:1px solid #1E2D3D}
|
||
.token-head{padding:8px 14px;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;border-bottom:1px solid #E0DDD6}
|
||
.token-table.light .token-head{background:#F4F2EC;color:#888;border-bottom-color:#E0DDD6}
|
||
.token-table.dark .token-head{background:#0A1218;color:#4E6070;border-bottom-color:#1E2D3D}
|
||
.token-table table{width:100%;border-collapse:collapse;font-size:11px}
|
||
.token-table.light td{padding:6px 14px;border-bottom:1px solid #F0EEE8;vertical-align:middle}
|
||
.token-table.dark td{padding:6px 14px;border-bottom:1px solid #1A2830;vertical-align:middle;color:#8AAABB}
|
||
.token-table tr:last-child td{border-bottom:none}
|
||
.token-table.light td:first-child{font-size:9px;font-weight:700;color:#888;width:210px}
|
||
.token-table.dark td:first-child{font-size:9px;font-weight:700;color:#4E6070;width:210px}
|
||
.swatch{display:inline-block;width:12px;height:12px;border-radius:2px;vertical-align:middle;margin-right:6px}
|
||
.warn{display:inline-block;background:#FEF3C7;color:#92400E;font-size:8px;font-weight:700;padding:1px 5px;border-radius:2px;margin-left:4px;vertical-align:middle}
|
||
.pass{display:inline-block;background:#D1FAE5;color:#065F46;font-size:8px;font-weight:700;padding:1px 5px;border-radius:2px;margin-left:4px;vertical-align:middle}
|
||
|
||
/* ── Browser chrome ── */
|
||
.chrome-bar{height:20px;background:#E8E6E0;border-bottom:1px solid #C4C0BA;display:flex;align-items:center;padding:0 8px;gap:4px}
|
||
.chrome-bar.dk{background:#010a18;border-bottom-color:#0d3358}
|
||
.chrome-dot{width:6px;height:6px;border-radius:50%;background:#BDB8B1}
|
||
.chrome-dot.dk{background:#1a2a3a}
|
||
.chrome-url{flex:1;height:9px;background:#CCC8C2;border-radius:5px;margin-left:6px}
|
||
.chrome-url.dk{background:#1a2a3a}
|
||
|
||
/* ── App nav ── */
|
||
.app-nav{height:30px;background:#012851;display:flex;align-items:center;padding:0 12px;gap:10px;flex-shrink:0}
|
||
.app-logo{font-family:'Tinos',Georgia,serif;font-size:7px;font-weight:700;color:#fff;border-bottom:2px solid #A1DCD8;padding-bottom:1px}
|
||
.app-link{font-size:5.5px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:rgba(255,255,255,.4);white-space:nowrap}
|
||
.app-link.on{color:rgba(255,255,255,.9)}
|
||
.app-nav-r{margin-left:auto;display:flex;gap:6px;align-items:center}
|
||
.app-av{width:16px;height:16px;border-radius:50%;background:rgba(255,255,255,.12);display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:rgba(255,255,255,.5)}
|
||
|
||
/* ── Scale wrappers ── */
|
||
.scale-outer{border:1.5px solid #C4C0BA;border-radius:8px;overflow:hidden;box-shadow:0 4px 20px rgba(0,0,0,.1)}
|
||
.scale-outer.dk{border-color:#0d3358}
|
||
.scale-inner{width:1280px;transform:scale(0.65);transform-origin:top left}
|
||
.scale-outer-sm{border:1.5px solid #C4C0BA;border-radius:8px;overflow:hidden;box-shadow:0 4px 20px rgba(0,0,0,.1)}
|
||
.scale-inner-sm{width:520px;transform:scale(0.82);transform-origin:top left}
|
||
|
||
/* ── Split layouts ── */
|
||
.split2{display:grid;grid-template-columns:1fr 1fr;gap:24px;margin-bottom:16px}
|
||
.split3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:18px;margin-bottom:16px}
|
||
.screen-lbl{font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:1.2px;color:#888;margin-bottom:8px;display:flex;align-items:center;gap:5px}
|
||
.lbl-dot{width:8px;height:8px;border-radius:50%;display:inline-block}
|
||
.cap{font-size:10px;color:#999;font-style:italic;line-height:1.6;margin-top:10px;max-width:520px}
|
||
|
||
/* ── Rules table ── */
|
||
.rules{background:#fff;border:1px solid #E0DDD6;border-radius:6px;overflow:hidden}
|
||
.rules table{width:100%;border-collapse:collapse}
|
||
.rules th{background:#F4F2EC;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;padding:8px 12px;text-align:left;border-bottom:1px solid #E0DDD6}
|
||
.rules td{font-size:11px;color:#444;padding:8px 12px;border-bottom:1px solid #F0EEE8;vertical-align:top;line-height:1.6}
|
||
.rules tr:last-child td{border-bottom:none}
|
||
.rules td:first-child{font-size:9px;font-weight:700;color:#012851;white-space:nowrap;width:190px}
|
||
.rules td code{font-size:9px;background:#F0EFE9;padding:1px 4px;border-radius:2px;color:#555;white-space:nowrap}
|
||
|
||
/* ── Annotation ── */
|
||
.ann{display:inline-block;background:#FEF3C7;color:#92400E;font-size:8px;font-weight:700;padding:2px 7px;border-radius:2px;border:1px dashed #F59E0B;margin:3px 2px}
|
||
|
||
/* ════════════════════════════════════════════════════════
|
||
SEARCH PAGE — component styles used in all mockups
|
||
════════════════════════════════════════════════════════ */
|
||
|
||
/* Page canvas */
|
||
.pg{background:#f0efe9;padding:18px 24px 24px}
|
||
.pg.dk{background:#010e1e}
|
||
.pg-h1{font-family:'Tinos',Georgia,serif;font-size:17px;font-weight:700;color:#012851;margin-bottom:2px}
|
||
.pg.dk .pg-h1{color:#f0efe9}
|
||
.pg-sub{font-size:8px;color:#9ca3af;margin-bottom:12px}
|
||
|
||
/* Filter card — mirrors real: rounded-sm border border-line bg-surface p-6 shadow-sm mb-8 */
|
||
.sfb{background:#fff;border:1.5px solid #e4e2d7;border-radius:4px;padding:14px;margin-bottom:12px;box-shadow:0 1px 4px rgba(0,0,0,.06)}
|
||
.sfb.dk{background:#011526;border-color:#0d3358}
|
||
|
||
/* ── ROW 1: input + inline toggle + sort + filter + reset ── */
|
||
.sfb-r1{display:flex;align-items:center;gap:8px;margin-bottom:10px}
|
||
|
||
/* Input wrapper — position:relative holds the inline toggle pill */
|
||
.sfb-input-wrap{position:relative;flex:1}
|
||
.sfb-input{width:100%;height:38px;border:1.5px solid #e4e2d7;border-radius:3px;background:#fff;display:flex;align-items:center;padding-left:10px;padding-right:70px;font-size:10px;color:#9ca3af;font-family:'Montserrat',sans-serif}
|
||
.sfb-input.typed{color:#012851;font-family:'Tinos',Georgia,serif;font-size:10px;font-style:italic}
|
||
.sfb-input.dk{background:#010e1e;border-color:#1e3a55;color:#4e6070}
|
||
.sfb-input.typed.dk{color:#f0efe9}
|
||
|
||
/* Toggle PILL — sits inside the input, right edge, like Google's AI Mode */
|
||
.sfb-pill-wrap{position:absolute;right:6px;top:50%;transform:translateY(-50%);display:flex;align-items:center}
|
||
|
||
/* Keyword pill (resting) — subtle, muted */
|
||
.sfb-pill-kw{display:flex;align-items:center;gap:3px;border:1px solid #c8c4be;border-radius:99px;background:#f4f2ec;color:#4b5563;font-size:7.5px;font-weight:700;padding:3px 8px;cursor:default;white-space:nowrap;letter-spacing:.3px}
|
||
.sfb-pill-kw.dk{border-color:#1e3a55;background:#011526;color:#6b7280}
|
||
|
||
/* Smart pill (active) — navy fill, mint text, matches bg-primary/text-primary-fg */
|
||
.sfb-pill-smart{display:flex;align-items:center;gap:3px;border:1px solid #012851;border-radius:99px;background:#012851;color:#a1dcd8;font-size:7.5px;font-weight:700;padding:3px 8px;cursor:default;white-space:nowrap;letter-spacing:.3px}
|
||
.sfb-pill-smart.dk{border-color:#a1dcd8;background:#a1dcd8;color:#012851}
|
||
|
||
/* Sort + Filter + Reset buttons */
|
||
.sfb-sort-btn{height:38px;border:1.5px solid #e4e2d7;border-radius:3px;font-size:8px;font-weight:700;color:#012851;padding:0 8px;display:flex;align-items:center;gap:4px;cursor:default;background:#fff;text-transform:uppercase;letter-spacing:.4px;white-space:nowrap}
|
||
.sfb-sort-btn.dk{border-color:#1e3a55;background:#011526;color:#8b97a5}
|
||
.sfb-filter-btn{height:38px;border:1.5px solid #e4e2d7;border-radius:3px;font-size:8px;font-weight:700;color:#012851;padding:0 8px;display:flex;align-items:center;gap:4px;cursor:default;background:#f4f2ec;text-transform:uppercase;letter-spacing:.4px}
|
||
.sfb-filter-btn.dk{border-color:#1e3a55;background:#011526;color:#8b97a5}
|
||
.sfb-reset-btn{height:38px;width:38px;display:flex;align-items:center;justify-content:center;border:1.5px solid transparent;cursor:default;color:#c8c4be}
|
||
|
||
/* ── ROW 2: advanced filters (collapsed placeholder) ── */
|
||
.sfb-advanced-hint{font-size:8px;color:#c8c4be;padding:4px 2px 0;letter-spacing:.3px}
|
||
|
||
/* ── Chip row (below filter card) ── */
|
||
.chip-row{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px}
|
||
|
||
/* Standard filter chip */
|
||
.chip{display:inline-flex;align-items:stretch;border:1.5px solid #012851;border-radius:99px;background:#fff;overflow:hidden;cursor:default}
|
||
.chip.dk{border-color:#8b97a5;background:#011526}
|
||
.chip-body{display:flex;align-items:center;gap:3px;padding:4px 7px 4px 10px}
|
||
.chip-pfx{font-size:8px;font-weight:700;color:#012851;opacity:.65}
|
||
.chip.dk .chip-pfx{color:#9ca3af;opacity:1}
|
||
.chip-nm{font-family:'Tinos',Georgia,serif;font-size:9.5px;color:#012851}
|
||
.chip.dk .chip-nm{color:#f0efe9}
|
||
.chip-arr{font-size:8px;color:#6b7280;padding:0 1px}
|
||
.chip-x{width:26px;display:flex;align-items:center;justify-content:center;border-left:1px solid rgba(1,40,81,.15);color:#012851;font-size:10px}
|
||
.chip.dk .chip-x{border-left-color:rgba(139,151,165,.2);color:#9ca3af}
|
||
|
||
/* Disambiguation chip — amber */
|
||
.chip.ambig{border-color:#d97706;background:#fffbeb}
|
||
.chip.ambig .chip-pfx{color:#92400e;opacity:1}
|
||
.chip.ambig .chip-nm{color:#78350f}
|
||
.chip.ambig .chip-x{border-left-color:rgba(217,119,6,.2);color:#d97706}
|
||
.chip-hint{font-size:7.5px;color:#92400e;opacity:.75;padding:4px 8px 4px 3px;font-style:italic;display:flex;align-items:center;white-space:nowrap}
|
||
|
||
/* ── STATUS STATES — loading / error / empty
|
||
These sit in the results area; must fill the space. ── */
|
||
.status-area{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:32px 24px;gap:10px;text-align:center;min-height:160px}
|
||
|
||
/* Loading */
|
||
.status-spinner-ring{width:36px;height:36px;border:3px solid rgba(1,40,81,.12);border-top-color:#012851;border-radius:50%}
|
||
.dk .status-spinner-ring{border-color:rgba(161,220,216,.12);border-top-color:#a1dcd8}
|
||
.status-loading-title{font-size:12px;font-weight:700;color:#012851;letter-spacing:-.1px}
|
||
.dk .status-loading-title{color:#a1dcd8}
|
||
.status-loading-sub{font-size:9px;color:#9ca3af;max-width:280px;line-height:1.6}
|
||
|
||
/* Error */
|
||
.status-icon-circle{width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:18px}
|
||
.status-icon-circle.err{background:#fef2f2;border:2px solid #f87171}
|
||
.status-icon-circle.warn{background:#fffbeb;border:2px solid #fbbf24}
|
||
.status-error-title{font-size:12px;font-weight:700;color:#1f2937;letter-spacing:-.1px}
|
||
.dk .status-error-title{color:#f0efe9}
|
||
.status-error-body{font-size:9px;color:#6b7280;max-width:300px;line-height:1.7}
|
||
.dk .status-error-body{color:#8b97a5}
|
||
.status-err-btn{margin-top:4px;display:inline-flex;align-items:center;height:32px;padding:0 14px;border:1.5px solid #012851;border-radius:3px;font-size:9px;font-weight:700;color:#012851;background:#fff;cursor:default}
|
||
.dk .status-err-btn{border-color:#a1dcd8;color:#a1dcd8;background:transparent}
|
||
|
||
/* Empty */
|
||
.status-empty-icon{width:44px;height:44px;opacity:.25}
|
||
.status-empty-title{font-size:12px;font-weight:700;color:#1f2937;letter-spacing:-.1px}
|
||
.dk .status-empty-title{color:#f0efe9}
|
||
.status-empty-body{font-size:9px;color:#6b7280;max-width:300px;line-height:1.7}
|
||
.dk .status-empty-body{color:#8b97a5}
|
||
.status-empty-link{margin-top:4px;display:inline;color:#012851;font-weight:700;font-size:9px;text-decoration:underline;cursor:default}
|
||
.dk .status-empty-link{color:#a1dcd8}
|
||
|
||
/* ── Doc result rows ── */
|
||
.doc-list{display:flex;flex-direction:column;gap:5px}
|
||
.doc-row{background:#fff;border:1.5px solid #e4e2d7;border-radius:3px;padding:8px 10px;display:flex;gap:8px;align-items:flex-start}
|
||
.doc-row.dk{background:#011526;border-color:#0d3358}
|
||
.doc-thumb{width:22px;height:28px;background:#f0efe9;border:1px solid #e4e2d7;border-radius:1px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:6px;color:#c8c4be}
|
||
.doc-row.dk .doc-thumb{background:#010e1e;border-color:#1e3a55;color:#1e3a55}
|
||
.doc-info{flex:1}
|
||
.doc-title{font-family:'Tinos',Georgia,serif;font-size:9px;font-weight:700;color:#012851;margin-bottom:2px}
|
||
.doc-row.dk .doc-title{color:#f0efe9}
|
||
.doc-meta{font-size:7px;color:#9ca3af}
|
||
|
||
/* ── Picker disclosure ── */
|
||
.picker-panel{background:#fff;border:1.5px solid #012851;border-radius:4px;padding:7px;margin-top:3px;min-width:260px}
|
||
.picker-panel.dk{background:#011526;border-color:#a1dcd8}
|
||
.picker-lbl{font-size:7.5px;font-weight:800;text-transform:uppercase;letter-spacing:.08em;color:#9ca3af;display:block;padding:2px 4px 5px}
|
||
.picker-item{display:flex;align-items:center;gap:6px;padding:5px 6px;border-radius:3px;cursor:default}
|
||
.picker-item.hi{background:rgba(1,40,81,.07)}
|
||
.picker-panel.dk .picker-item.hi{background:rgba(161,220,216,.1)}
|
||
.picker-cb{width:11px;height:11px;border:1.5px solid #012851;border-radius:2px;flex-shrink:0}
|
||
.picker-panel.dk .picker-cb{border-color:#a1dcd8}
|
||
.picker-cb.on{background:#012851;border-color:#012851;display:flex;align-items:center;justify-content:center;color:#fff;font-size:7px}
|
||
.picker-panel.dk .picker-cb.on{background:#a1dcd8;border-color:#a1dcd8;color:#012851}
|
||
.picker-nm{font-family:'Tinos',Georgia,serif;font-size:9px;color:#012851}
|
||
.picker-panel.dk .picker-nm{color:#f0efe9}
|
||
.picker-yr{font-size:7.5px;color:#9ca3af;margin-left:3px}
|
||
.picker-footer{display:flex;gap:5px;justify-content:flex-end;border-top:1px solid #f0efe9;margin-top:5px;padding-top:6px}
|
||
.picker-panel.dk .picker-footer{border-top-color:#1e3a55}
|
||
.picker-cancel-btn{height:22px;padding:0 8px;border:1.5px solid #e4e2d7;border-radius:3px;font-size:7.5px;font-weight:700;color:#6b7280;background:#fff;cursor:default}
|
||
.picker-panel.dk .picker-cancel-btn{border-color:#1e3a55;background:#011526;color:#8b97a5}
|
||
.picker-ok-btn{height:22px;padding:0 8px;border-radius:3px;font-size:7.5px;font-weight:700;color:#a1dcd8;background:#012851;cursor:default}
|
||
.picker-panel.dk .picker-ok-btn{background:#a1dcd8;color:#012851}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="doc">
|
||
|
||
|
||
<!-- ══ MASTHEAD ══════════════════════════════════════════════════════════════ -->
|
||
<div class="mh">
|
||
<h1>NL Search — Toggle, Interpretation Chips & States · /documents</h1>
|
||
<p>
|
||
Visual specification for the natural-language search mode. The mode toggle lives
|
||
<strong>inside the search input as an inline pill</strong> — inspired by Google's AI Mode button —
|
||
keeping the filter row unchanged. Loading, error, and empty states are full-height panels
|
||
that fill the result area rather than inline one-liners.
|
||
Issue #739 — Archive Intelligence milestone.
|
||
</p>
|
||
<div class="byline">Familienarchiv · 2026-06-06 · Leonie Voss, UX Lead</div>
|
||
<div class="tag-row">
|
||
<span class="tag">NL Search · Issue #739</span>
|
||
<span class="tag">Desktop 1280 px / Mobile 320 px</span>
|
||
<span class="tag">Light + Dark</span>
|
||
<span class="tag">Archive Intelligence Milestone</span>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══ SECTION 1 — DESIGN TOKENS ════════════════════════════════════════════ -->
|
||
<div class="section">
|
||
<div class="sh">
|
||
<h2>1 · Design tokens</h2>
|
||
<p>All colour values for the toggle pill, chips, and status states. No new tokens — everything maps to existing <code>layout.css</code> semantics.</p>
|
||
</div>
|
||
|
||
<div class="token-grid">
|
||
<div class="token-table light">
|
||
<div class="token-head">Light theme</div>
|
||
<table>
|
||
<tr>
|
||
<td>Toggle pill — keyword (resting)</td>
|
||
<td><span class="swatch" style="background:#f4f2ec;border:1px solid #c8c4be"></span>#f4f2ec bg · #c8c4be border · #4b5563 text — muted, doesn't compete with query text</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Toggle pill — smart (active)</td>
|
||
<td><span class="swatch" style="background:#012851"></span>#012851 bg (bg-primary) · <span class="swatch" style="background:#a1dcd8;border:1px solid #ccc"></span>#a1dcd8 text (text-primary-fg)<span class="pass">9.2:1 AAA ✓</span></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Chip border / text</td>
|
||
<td><span class="swatch" style="background:#012851"></span>#012851 — navy 1.5 px; Tinos for person names<span class="pass">14.5:1 AAA ✓</span></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Chip prefix (type label)</td>
|
||
<td>#012851 at 65% opacity — "Absender:", "Zeitraum:", "Stichwort:"</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Chip × divider</td>
|
||
<td>rgba(1,40,81,.15) — 1 px left border inside chip</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Disambiguation chip border</td>
|
||
<td><span class="swatch" style="background:#d97706"></span>#d97706 — amber; signals disambiguation needed</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Status area — loading spinner</td>
|
||
<td>rgba(1,40,81,.12) track + #012851 active slice; 36 px dia, 3 px stroke</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Status area — error icon circle</td>
|
||
<td>#fef2f2 bg · #f87171 border — red-100/red-400</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Status area — warning icon circle</td>
|
||
<td>#fffbeb bg · #fbbf24 border — amber-50/amber-400</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Focus ring (chips + link)</td>
|
||
<td>focus-visible:ring-2 focus-visible:ring-brand-navy on chip wrappers and fallback link</td>
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
<div class="token-table dark">
|
||
<div class="token-head">Dark theme</div>
|
||
<table>
|
||
<tr>
|
||
<td>Toggle pill — keyword (resting)</td>
|
||
<td><span class="swatch" style="background:#011526;border:1px solid #1e3a55"></span>#011526 bg · #1e3a55 border · #6b7280 text</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Toggle pill — smart (active)</td>
|
||
<td><span class="swatch" style="background:#a1dcd8;border:1px solid #ccc"></span>#a1dcd8 bg · #012851 text — exact inverse<span class="pass" style="background:rgba(209,250,229,.15);color:#6EE7B7">9.2:1 AAA ✓</span></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Chip border</td>
|
||
<td><span class="swatch" style="background:#8b97a5;border:1px solid #444"></span>#8b97a5 — blue-grey</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Chip name text</td>
|
||
<td><span class="swatch" style="background:#f0efe9;border:1px solid #555"></span>#f0efe9 — sand-white<span class="pass" style="background:rgba(209,250,229,.15);color:#6EE7B7">14.5:1 AAA ✓</span></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Status area — loading spinner</td>
|
||
<td>rgba(161,220,216,.12) track + #a1dcd8 mint active slice</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Status loading title</td>
|
||
<td><span class="swatch" style="background:#a1dcd8;border:1px solid #555"></span>#a1dcd8 — mint</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Status error title</td>
|
||
<td><span class="swatch" style="background:#f0efe9;border:1px solid #555"></span>#f0efe9 — sand-white</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Status body text</td>
|
||
<td><span class="swatch" style="background:#8b97a5;border:1px solid #444"></span>#8b97a5 — muted blue-grey</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Error action button</td>
|
||
<td>border + text #a1dcd8 mint on transparent bg</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Empty state fallback link</td>
|
||
<td>#a1dcd8 mint, underlined</td>
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══ SECTION 2 — TOGGLE PILL INSIDE INPUT ══════════════════════════════════ -->
|
||
<div class="section">
|
||
<div class="sh">
|
||
<h2>2 · Toggle pill inside the search input — keyword vs smart mode</h2>
|
||
<p>The toggle is an absolutely-positioned pill at the <strong>right edge of the search input</strong>, replacing the magnifier icon. The input gets extra right padding (<code>pr-28</code>) to keep query text from overlapping the pill. The rest of the filter row — Sort, Filter, Reset — is unchanged. On desktop the pill reads "Text" or "KI"; on mobile (below <code>sm</code>) it reads "KI-Suche" / "Textsuche" for clarity.</p>
|
||
</div>
|
||
|
||
<!-- ── Toggle zoom: anatomy ── -->
|
||
<div style="margin-bottom:32px">
|
||
<div class="screen-lbl"><span class="lbl-dot" style="background:#6b7280"></span>Toggle pill — anatomy (both states at 2× zoom)</div>
|
||
<div style="display:flex;gap:20px;align-items:flex-end;flex-wrap:wrap;margin-bottom:10px">
|
||
|
||
<!-- Keyword pill -->
|
||
<div style="background:#f0efe9;padding:18px 20px;border-radius:6px;border:1.5px solid #e4e2d7">
|
||
<div style="font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.08em;color:#9ca3af;margin-bottom:12px">Keyword mode (resting)</div>
|
||
<!-- 2x enlarged pill -->
|
||
<div style="display:flex;align-items:center;gap:6px;border:2px solid #c8c4be;border-radius:99px;background:#f4f2ec;color:#4b5563;font-size:14px;font-weight:700;padding:6px 16px;width:fit-content">
|
||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="3" width="14" height="10" rx="2"/><line x1="4" y1="7" x2="12" y2="7"/><line x1="4" y1="10" x2="9" y2="10"/></svg>
|
||
Text
|
||
</div>
|
||
<div style="margin-top:10px;font-size:9px;color:#9ca3af;line-height:1.6">
|
||
bg: #f4f2ec (bg-muted)<br>border: #c8c4be (border-line)<br>text: #4b5563 (text-ink-2)<br>aria-pressed="false"
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Smart pill -->
|
||
<div style="background:#f0efe9;padding:18px 20px;border-radius:6px;border:1.5px solid #e4e2d7">
|
||
<div style="font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.08em;color:#9ca3af;margin-bottom:12px">Smart mode (active)</div>
|
||
<div style="display:flex;align-items:center;gap:6px;border:2px solid #012851;border-radius:99px;background:#012851;color:#a1dcd8;font-size:14px;font-weight:700;padding:6px 16px;width:fit-content">
|
||
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1l1.5 4.5H14l-3.7 2.7 1.4 4.3L8 10.2l-3.7 2.3 1.4-4.3L2 5.5h4.5z"/></svg>
|
||
KI
|
||
</div>
|
||
<div style="margin-top:10px;font-size:9px;color:#9ca3af;line-height:1.6">
|
||
bg: #012851 (bg-primary)<br>text: #a1dcd8 (text-primary-fg)<br>Matches AND/OR operator active<br>aria-pressed="true"
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Smart pill dark -->
|
||
<div style="background:#010e1e;padding:18px 20px;border-radius:6px;border:1.5px solid #0d3358">
|
||
<div style="font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.08em;color:#4e6070;margin-bottom:12px">Smart mode — dark</div>
|
||
<div style="display:flex;align-items:center;gap:6px;border:2px solid #a1dcd8;border-radius:99px;background:#a1dcd8;color:#012851;font-size:14px;font-weight:700;padding:6px 16px;width:fit-content">
|
||
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1l1.5 4.5H14l-3.7 2.7 1.4 4.3L8 10.2l-3.7 2.3 1.4-4.3L2 5.5h4.5z"/></svg>
|
||
KI
|
||
</div>
|
||
<div style="margin-top:10px;font-size:9px;color:#4e6070;line-height:1.6">
|
||
bg: #a1dcd8 (mint) — inverted<br>text: #012851 (navy)<br>Same pattern as selected node dark
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Full search bar — side by side ── -->
|
||
<div class="split2">
|
||
|
||
<!-- Keyword mode light -->
|
||
<div>
|
||
<div class="screen-lbl"><span class="lbl-dot" style="background:#A1DCD8;border:1px solid #ccc"></span>Full filter bar — keyword mode (light)</div>
|
||
<div class="scale-outer" style="width:832px;height:84px">
|
||
<div class="scale-inner" style="height:129px">
|
||
<div class="pg" style="padding:14px 24px">
|
||
<div class="sfb" style="padding:12px;margin-bottom:0">
|
||
<div class="sfb-r1" style="margin-bottom:0">
|
||
<div class="sfb-input-wrap">
|
||
<div class="sfb-input">Suche nach Absender, Empfänger, Inhalt…</div>
|
||
<div class="sfb-pill-wrap">
|
||
<div class="sfb-pill-kw">
|
||
<svg width="9" height="9" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><rect x="1" y="3" width="14" height="10" rx="2"/><line x1="4" y1="7" x2="12" y2="7"/><line x1="4" y1="10" x2="9" y2="10"/></svg>
|
||
Text
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="sfb-sort-btn">Sortierung ▾</div>
|
||
<div class="sfb-filter-btn">
|
||
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 8 8 12 12 8"/></svg>
|
||
Filter
|
||
</div>
|
||
<div class="sfb-reset-btn">✕</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="cap">Keyword mode. The "Text" pill sits at the right edge of the input, muted (#f4f2ec) so it doesn't compete with the query text. Sort, Filter, and Reset remain in the same position in the filter row.</p>
|
||
</div>
|
||
|
||
<!-- Smart mode light -->
|
||
<div>
|
||
<div class="screen-lbl"><span class="lbl-dot" style="background:#012851"></span>Full filter bar — smart mode active (light)</div>
|
||
<div class="scale-outer" style="width:832px;height:84px">
|
||
<div class="scale-inner" style="height:129px">
|
||
<div class="pg" style="padding:14px 24px">
|
||
<div class="sfb" style="padding:12px;margin-bottom:0">
|
||
<div class="sfb-r1" style="margin-bottom:0">
|
||
<div class="sfb-input-wrap">
|
||
<div class="sfb-input typed">Was hat walter im krieg geschrieben?</div>
|
||
<div class="sfb-pill-wrap">
|
||
<div class="sfb-pill-smart">
|
||
<svg width="9" height="9" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1l1.5 4.5H14l-3.7 2.7 1.4 4.3L8 10.2l-3.7 2.3 1.4-4.3L2 5.5h4.5z"/></svg>
|
||
KI
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="sfb-sort-btn">Sortierung ▾</div>
|
||
<div class="sfb-filter-btn">
|
||
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 8 8 12 12 8"/></svg>
|
||
Filter
|
||
</div>
|
||
<div class="sfb-reset-btn">✕</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="cap">Smart mode active. The "KI" pill becomes navy (#012851) with mint text (#a1dcd8) — bg-primary / text-primary-fg, matching the AND/OR operator active state. Query text is in Tinos italic. The rest of the bar is unchanged.</p>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- dark variants -->
|
||
<div class="split2" style="margin-top:0">
|
||
<div>
|
||
<div class="screen-lbl"><span class="lbl-dot" style="background:#1a2a3a"></span>Keyword mode — dark</div>
|
||
<div class="scale-outer dk" style="width:832px;height:84px">
|
||
<div class="scale-inner" style="height:129px;background:#010e1e">
|
||
<div class="pg dk" style="padding:14px 24px">
|
||
<div class="sfb dk" style="padding:12px;margin-bottom:0">
|
||
<div class="sfb-r1" style="margin-bottom:0">
|
||
<div class="sfb-input-wrap">
|
||
<div class="sfb-input dk">Suche nach Absender, Empfänger, Inhalt…</div>
|
||
<div class="sfb-pill-wrap">
|
||
<div class="sfb-pill-kw dk">
|
||
<svg width="9" height="9" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><rect x="1" y="3" width="14" height="10" rx="2"/><line x1="4" y1="7" x2="12" y2="7"/><line x1="4" y1="10" x2="9" y2="10"/></svg>
|
||
Text
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="sfb-sort-btn dk">Sortierung ▾</div>
|
||
<div class="sfb-filter-btn dk">
|
||
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 8 8 12 12 8"/></svg>
|
||
Filter
|
||
</div>
|
||
<div class="sfb-reset-btn" style="color:#2a3a4a">✕</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="cap">Dark, keyword mode. Pill uses #1e3a55 border on #011526 bg with #6b7280 text.</p>
|
||
</div>
|
||
<div>
|
||
<div class="screen-lbl"><span class="lbl-dot" style="background:#a1dcd8;border:1px solid #555"></span>Smart mode — dark</div>
|
||
<div class="scale-outer dk" style="width:832px;height:84px">
|
||
<div class="scale-inner" style="height:129px;background:#010e1e">
|
||
<div class="pg dk" style="padding:14px 24px">
|
||
<div class="sfb dk" style="padding:12px;margin-bottom:0">
|
||
<div class="sfb-r1" style="margin-bottom:0">
|
||
<div class="sfb-input-wrap">
|
||
<div class="sfb-input typed dk">Was hat walter im krieg geschrieben?</div>
|
||
<div class="sfb-pill-wrap">
|
||
<div class="sfb-pill-smart dk">
|
||
<svg width="9" height="9" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1l1.5 4.5H14l-3.7 2.7 1.4 4.3L8 10.2l-3.7 2.3 1.4-4.3L2 5.5h4.5z"/></svg>
|
||
KI
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="sfb-sort-btn dk">Sortierung ▾</div>
|
||
<div class="sfb-filter-btn dk">
|
||
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 8 8 12 12 8"/></svg>
|
||
Filter
|
||
</div>
|
||
<div class="sfb-reset-btn" style="color:#2a3a4a">✕</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="cap">Dark, smart mode. Pill inverts to mint bg (#a1dcd8) + navy text (#012851).</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══ SECTION 3 — LOADING STATE ═════════════════════════════════════════════ -->
|
||
<div class="section">
|
||
<div class="sh">
|
||
<h2>3 · Loading state — full-area panel</h2>
|
||
<p>The loading state fills the result area instead of showing a tiny inline spinner. A centred, vertically-padded panel with a large spinner ring and two lines of text matches the space that results would normally occupy. <code>role="status" aria-live="polite"</code> announces arrival to screen readers without stealing focus. Spinner uses <code>motion-safe:animate-spin</code>, subtitle uses <code>motion-safe:animate-pulse</code>.</p>
|
||
</div>
|
||
|
||
<div class="split2">
|
||
<!-- Light loading -->
|
||
<div>
|
||
<div class="screen-lbl"><span class="lbl-dot" style="background:#A1DCD8;border:1px solid #ccc"></span>Light — loading</div>
|
||
<div class="scale-outer" style="width:832px;height:325px">
|
||
<div class="scale-inner" style="height:500px">
|
||
<div class="chrome-bar"><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-url"></div></div>
|
||
<div class="app-nav">
|
||
<div class="app-logo">Familienarchiv</div>
|
||
<div class="app-link on">Dokumente</div>
|
||
<div class="app-link">Personen</div>
|
||
<div class="app-link">Stammbaum</div>
|
||
<div class="app-nav-r"><div class="app-av">M</div></div>
|
||
</div>
|
||
<div class="pg">
|
||
<div class="pg-h1">Dokumente</div>
|
||
<div class="pg-sub">Intelligente Suche aktiv</div>
|
||
<div class="sfb">
|
||
<div class="sfb-r1" style="margin-bottom:0">
|
||
<div class="sfb-input-wrap">
|
||
<div class="sfb-input typed">Was hat walter im krieg geschrieben?</div>
|
||
<div class="sfb-pill-wrap">
|
||
<div class="sfb-pill-smart">
|
||
<svg width="9" height="9" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1l1.5 4.5H14l-3.7 2.7 1.4 4.3L8 10.2l-3.7 2.3 1.4-4.3L2 5.5h4.5z"/></svg>
|
||
KI
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="sfb-sort-btn">Sortierung ▾</div>
|
||
<div class="sfb-filter-btn"><svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 8 8 12 12 8"/></svg> Filter</div>
|
||
<div class="sfb-reset-btn">✕</div>
|
||
</div>
|
||
</div>
|
||
<!-- Loading panel -->
|
||
<div class="status-area" role="status" aria-live="polite">
|
||
<div class="status-spinner-ring"></div>
|
||
<div class="status-loading-title">Archiv wird befragt…</div>
|
||
<div class="status-loading-sub">Die KI analysiert Ihre Anfrage.<br>Das kann bis zu 15 Sekunden dauern.</div>
|
||
</div>
|
||
<div style="text-align:center">
|
||
<span class="ann">role="status" aria-live="polite"</span>
|
||
<span class="ann">motion-safe:animate-spin (ring)</span>
|
||
<span class="ann">motion-safe:animate-pulse (subtitle)</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="cap">Light. The spinner ring is 36 px with 3 px stroke. Subtitle explains the expected wait — "bis zu 15 Sekunden" manages expectations. No timeout countdown shown during the request.</p>
|
||
</div>
|
||
|
||
<!-- Dark loading -->
|
||
<div>
|
||
<div class="screen-lbl"><span class="lbl-dot" style="background:#1a2a3a"></span>Dark — loading</div>
|
||
<div class="scale-outer dk" style="width:832px;height:325px">
|
||
<div class="scale-inner" style="height:500px;background:#010e1e">
|
||
<div class="chrome-bar dk"><div class="chrome-dot dk"></div><div class="chrome-dot dk"></div><div class="chrome-dot dk"></div><div class="chrome-url dk"></div></div>
|
||
<div class="app-nav">
|
||
<div class="app-logo">Familienarchiv</div>
|
||
<div class="app-link on">Dokumente</div>
|
||
<div class="app-link">Personen</div>
|
||
<div class="app-link">Stammbaum</div>
|
||
<div class="app-nav-r"><div class="app-av">M</div></div>
|
||
</div>
|
||
<div class="pg dk">
|
||
<div class="pg-h1">Dokumente</div>
|
||
<div class="pg-sub">Intelligente Suche aktiv</div>
|
||
<div class="sfb dk">
|
||
<div class="sfb-r1" style="margin-bottom:0">
|
||
<div class="sfb-input-wrap">
|
||
<div class="sfb-input typed dk">Was hat walter im krieg geschrieben?</div>
|
||
<div class="sfb-pill-wrap">
|
||
<div class="sfb-pill-smart dk">
|
||
<svg width="9" height="9" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1l1.5 4.5H14l-3.7 2.7 1.4 4.3L8 10.2l-3.7 2.3 1.4-4.3L2 5.5h4.5z"/></svg>
|
||
KI
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="sfb-sort-btn dk">Sortierung ▾</div>
|
||
<div class="sfb-filter-btn dk"><svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 8 8 12 12 8"/></svg> Filter</div>
|
||
<div class="sfb-reset-btn" style="color:#2a3a4a">✕</div>
|
||
</div>
|
||
</div>
|
||
<div class="status-area dk" role="status">
|
||
<div class="status-spinner-ring"></div>
|
||
<div class="status-loading-title">Archiv wird befragt…</div>
|
||
<div class="status-loading-sub" style="color:#4e6070">Die KI analysiert Ihre Anfrage.<br>Das kann bis zu 15 Sekunden dauern.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="cap">Dark. Spinner track rgba(161,220,216,.12), active slice mint #a1dcd8. Title in mint. Subtitle in muted #4e6070.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══ SECTION 4 — CHIPS: SINGLE-NAME ═══════════════════════════════════════ -->
|
||
<div class="section">
|
||
<div class="sh">
|
||
<h2>4 · Interpretation chips — single-name query (light)</h2>
|
||
<p>After a successful NL response, <code>InterpretationChipRow</code> renders above the result list. Every chip has a type prefix so the senior audience knows what the filter is — "1914–1918" without "Zeitraum:" is ambiguous. Keyword chips only appear when <code>keywordsApplied === true</code>.</p>
|
||
</div>
|
||
|
||
<div class="scale-outer" style="width:832px;height:370px">
|
||
<div class="scale-inner" style="height:569px">
|
||
<div class="chrome-bar"><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-url"></div></div>
|
||
<div class="app-nav">
|
||
<div class="app-logo">Familienarchiv</div>
|
||
<div class="app-link on">Dokumente</div>
|
||
<div class="app-link">Personen</div>
|
||
<div class="app-link">Stammbaum</div>
|
||
<div class="app-nav-r"><div class="app-av">M</div></div>
|
||
</div>
|
||
<div class="pg">
|
||
<div class="pg-h1">Dokumente</div>
|
||
<div class="pg-sub">12 Ergebnisse</div>
|
||
<div class="sfb">
|
||
<div class="sfb-r1" style="margin-bottom:0">
|
||
<div class="sfb-input-wrap">
|
||
<div class="sfb-input typed">Was hat walter im krieg geschrieben?</div>
|
||
<div class="sfb-pill-wrap">
|
||
<div class="sfb-pill-smart"><svg width="9" height="9" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1l1.5 4.5H14l-3.7 2.7 1.4 4.3L8 10.2l-3.7 2.3 1.4-4.3L2 5.5h4.5z"/></svg> KI</div>
|
||
</div>
|
||
</div>
|
||
<div class="sfb-sort-btn">Sortierung ▾</div>
|
||
<div class="sfb-filter-btn"><svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 8 8 12 12 8"/></svg> Filter</div>
|
||
<div class="sfb-reset-btn">✕</div>
|
||
</div>
|
||
</div>
|
||
<!-- Chips -->
|
||
<div class="chip-row">
|
||
<div class="chip">
|
||
<div class="chip-body"><span class="chip-pfx">Absender:</span><span class="chip-nm">Walter Raddatz</span></div>
|
||
<div class="chip-x">×</div>
|
||
</div>
|
||
<div class="chip">
|
||
<div class="chip-body"><span class="chip-pfx">Zeitraum:</span><span class="chip-nm">1914–1918</span></div>
|
||
<div class="chip-x">×</div>
|
||
</div>
|
||
<div class="chip">
|
||
<div class="chip-body"><span class="chip-pfx">Stichwort:</span><span class="chip-nm">krieg</span></div>
|
||
<div class="chip-x">×</div>
|
||
</div>
|
||
</div>
|
||
<!-- Results -->
|
||
<div class="doc-list">
|
||
<div class="doc-row"><div class="doc-thumb">▤</div><div class="doc-info"><div class="doc-title">Brief an Emma Raddatz, 14. August 1916</div><div class="doc-meta">Von Walter Raddatz · 1916 · 3 Seiten</div></div></div>
|
||
<div class="doc-row"><div class="doc-thumb">▤</div><div class="doc-info"><div class="doc-title">Feldpostkarte aus Verdun, Juni 1917</div><div class="doc-meta">Von Walter Raddatz · 1917 · 1 Seite</div></div></div>
|
||
<div class="doc-row"><div class="doc-thumb">▤</div><div class="doc-info"><div class="doc-title">Brief an die Familie, Weihnachten 1914</div><div class="doc-meta">Von Walter Raddatz · 1914 · 2 Seiten</div></div></div>
|
||
<div class="doc-row"><div class="doc-thumb">▤</div><div class="doc-info"><div class="doc-title">Postkarte an Frieda, März 1918</div><div class="doc-meta">Von Walter Raddatz · 1918 · 1 Seite</div></div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="cap">Three chips: Absender (person), Zeitraum (date range), Stichwort (keyword — only because <code>keywordsApplied === true</code>). Removing any chip calls <code>GET /api/documents/search</code> with that param absent. The × button has <code>aria-label="Filter entfernen: Absender Walter Raddatz"</code>.</p>
|
||
</div>
|
||
|
||
|
||
<!-- ══ SECTION 5 — CHIPS: 2-NAME DIRECTIONAL ════════════════════════════════ -->
|
||
<div class="section">
|
||
<div class="sh">
|
||
<h2>5 · Interpretation chips — 2-name directional + long-name truncation</h2>
|
||
<p>When <code>resolvedPersons</code> has 2 entries, a single directional chip replaces two person chips. Index 0 = sender, index 1 = receiver. Each name is in a separately-truncatable <code><span></code>; the → arrow is <code>aria-hidden</code>; the chip wrapper carries a plain-language <code>aria-label</code>. The × button sits outside both spans — never clipped.</p>
|
||
</div>
|
||
|
||
<div class="split2">
|
||
<!-- Directional chip desktop -->
|
||
<div>
|
||
<div class="screen-lbl"><span class="lbl-dot" style="background:#A1DCD8;border:1px solid #ccc"></span>Directional chip — desktop</div>
|
||
<div class="scale-outer" style="width:832px;height:240px">
|
||
<div class="scale-inner" style="height:369px">
|
||
<div class="chrome-bar"><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-url"></div></div>
|
||
<div class="app-nav"><div class="app-logo">Familienarchiv</div><div class="app-link on">Dokumente</div><div class="app-link">Personen</div><div class="app-nav-r"><div class="app-av">M</div></div></div>
|
||
<div class="pg">
|
||
<div class="pg-h1">Dokumente</div>
|
||
<div class="pg-sub">4 Ergebnisse</div>
|
||
<div class="sfb">
|
||
<div class="sfb-r1" style="margin-bottom:0">
|
||
<div class="sfb-input-wrap">
|
||
<div class="sfb-input typed">Briefe von walter an emma um 1916</div>
|
||
<div class="sfb-pill-wrap"><div class="sfb-pill-smart"><svg width="9" height="9" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1l1.5 4.5H14l-3.7 2.7 1.4 4.3L8 10.2l-3.7 2.3 1.4-4.3L2 5.5h4.5z"/></svg> KI</div></div>
|
||
</div>
|
||
<div class="sfb-sort-btn">Sortierung ▾</div>
|
||
<div class="sfb-filter-btn"><svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 8 8 12 12 8"/></svg> Filter</div>
|
||
<div class="sfb-reset-btn">✕</div>
|
||
</div>
|
||
</div>
|
||
<div class="chip-row">
|
||
<!-- Single directional chip -->
|
||
<div class="chip">
|
||
<div class="chip-body" style="gap:2px">
|
||
<span style="max-width:128px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:'Tinos',Georgia,serif;font-size:9.5px;color:#012851">Walter Raddatz</span>
|
||
<span class="chip-arr" aria-hidden="true">→</span>
|
||
<span style="max-width:128px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:'Tinos',Georgia,serif;font-size:9.5px;color:#012851">Emma Raddatz</span>
|
||
</div>
|
||
<div class="chip-x">×</div>
|
||
</div>
|
||
<div class="chip">
|
||
<div class="chip-body"><span class="chip-pfx">Zeitraum:</span><span class="chip-nm">1914–1918</span></div>
|
||
<div class="chip-x">×</div>
|
||
</div>
|
||
</div>
|
||
<div class="ann">aria-label="Von Walter Raddatz zu Emma Raddatz, Filter entfernen"</div>
|
||
<div class="ann">Removing clears BOTH senderId AND receiverId</div>
|
||
<div class="doc-list" style="margin-top:8px">
|
||
<div class="doc-row"><div class="doc-thumb">▤</div><div class="doc-info"><div class="doc-title">Brief an Emma Raddatz, 14. August 1916</div><div class="doc-meta">Von Walter Raddatz · 1916</div></div></div>
|
||
<div class="doc-row"><div class="doc-thumb">▤</div><div class="doc-info"><div class="doc-title">Brief an Emma, Ostern 1915</div><div class="doc-meta">Von Walter Raddatz · 1915</div></div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="cap">Single directional chip for 2-name resolution. The → arrow is <code>aria-hidden</code>. Chip's <code>aria-label</code> reads "Von Walter Raddatz zu Emma Raddatz, Filter entfernen" — plain language for screen readers and the 60+ audience.</p>
|
||
</div>
|
||
|
||
<!-- Long-name truncation -->
|
||
<div>
|
||
<div class="screen-lbl"><span class="lbl-dot" style="background:#6b7280"></span>Long-name truncation at 320 px</div>
|
||
<div class="scale-outer-sm" style="width:263px;height:93px">
|
||
<div class="scale-inner-sm" style="height:113px">
|
||
<div class="pg" style="padding:12px;min-height:unset">
|
||
<div class="chip-row" style="max-width:295px">
|
||
<div class="chip" style="max-width:100%">
|
||
<div class="chip-body" style="gap:2px;overflow:hidden;min-width:0">
|
||
<span style="max-width:96px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:'Tinos',Georgia,serif;font-size:9.5px;color:#012851">Wilhelmine-Frieder…</span>
|
||
<span class="chip-arr" aria-hidden="true">→</span>
|
||
<span style="max-width:96px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:'Tinos',Georgia,serif;font-size:9.5px;color:#012851">Emma-Karl…</span>
|
||
</div>
|
||
<div class="chip-x" style="flex-shrink:0">×</div>
|
||
</div>
|
||
</div>
|
||
<div class="ann">max-w-[8rem] per name span; × outside both</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="cap">At 320 px, each name span caps at <code>max-w-[8rem]</code> (128 px) with truncation ellipsis. The × button is a fixed-width sibling outside the chip-body — never clipped regardless of how long the names are.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══ SECTION 6 — DISAMBIGUATION ═══════════════════════════════════════════ -->
|
||
<div class="section">
|
||
<div class="sh">
|
||
<h2>6 · Disambiguation chip & picker</h2>
|
||
<p>When <code>ambiguousPersons</code> is non-empty the chip renders in amber. Clicking it opens an accessible disclosure. Focus moves into the list on open; Escape returns focus to the trigger. <strong>Build this after the multi-OR approach is confirmed in #738.</strong></p>
|
||
</div>
|
||
|
||
<div class="split2">
|
||
<!-- Ambiguous chip collapsed -->
|
||
<div>
|
||
<div class="screen-lbl"><span class="lbl-dot" style="background:#d97706"></span>Disambiguation chip — collapsed</div>
|
||
<div class="scale-outer-sm" style="width:427px;height:82px">
|
||
<div class="scale-inner-sm" style="height:100px">
|
||
<div class="pg" style="padding:12px;min-height:unset">
|
||
<div class="chip-row" style="margin-bottom:4px">
|
||
<div class="chip ambig">
|
||
<div class="chip-body"><span class="chip-pfx">Absender:</span><span class="chip-nm">Walter Raddatz, Walter Müller</span></div>
|
||
<div class="chip-hint">(auswählen…)</div>
|
||
<div class="chip-x">×</div>
|
||
</div>
|
||
</div>
|
||
<div class="ann">aria-expanded="false" · aria-label="Mehrere Personen gefunden — zum Auswählen klicken"</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="cap">Amber chip signals "action needed". Names listed comma-separated; "(auswählen…)" in italics makes the affordance explicit for the 60+ audience. "▾" alone is not sufficient. Search results are empty while ambiguous.</p>
|
||
</div>
|
||
|
||
<!-- Picker open -->
|
||
<div>
|
||
<div class="screen-lbl"><span class="lbl-dot" style="background:#012851"></span>Picker open — first item focused</div>
|
||
<div class="scale-outer-sm" style="width:427px;height:221px">
|
||
<div class="scale-inner-sm" style="height:270px">
|
||
<div class="pg" style="padding:12px;min-height:unset">
|
||
<div class="chip-row" style="margin-bottom:4px">
|
||
<div class="chip ambig">
|
||
<div class="chip-body"><span class="chip-pfx">Absender:</span><span class="chip-nm">Walter Raddatz, Walter Müller</span></div>
|
||
<div class="chip-hint">(auswählen…)</div>
|
||
<div class="chip-x">×</div>
|
||
</div>
|
||
</div>
|
||
<div class="ann" style="margin-bottom:6px">aria-expanded="true" · Focus moves to first item on open</div>
|
||
<div class="picker-panel" style="max-width:320px">
|
||
<span class="picker-lbl">Welcher Walter ist gemeint?</span>
|
||
<div class="picker-item hi">
|
||
<div class="picker-cb on">✓</div>
|
||
<div><span class="picker-nm">Walter Raddatz</span><span class="picker-yr">1888–1952 · Sohn von Karl Raddatz</span></div>
|
||
</div>
|
||
<div class="picker-item">
|
||
<div class="picker-cb"></div>
|
||
<div><span class="picker-nm">Walter Müller</span><span class="picker-yr">1882–1941 · Ehemann von Frieda Raddatz</span></div>
|
||
</div>
|
||
<div class="picker-footer">
|
||
<div class="picker-cancel-btn">Abbrechen</div>
|
||
<div class="picker-ok-btn">Suchen</div>
|
||
</div>
|
||
</div>
|
||
<div class="ann">Escape → focus returns to trigger · "Suchen" → GET /api/documents/search</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="cap">Picker open. Focus lands on the first item. Abbrechen (or Escape) closes and returns focus to the trigger — keyboard position never lost. Suchen fires <code>GET /api/documents/search</code> immediately with selected person IDs.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══ SECTION 7 — EMPTY & ERROR STATES ═════════════════════════════════════ -->
|
||
<div class="section">
|
||
<div class="sh">
|
||
<h2>7 · Empty state & error states — full result-area panels</h2>
|
||
<p>All three outcome states fill the result area with a centred panel that uses the available vertical space. They share the same layout: icon → title → body → action. The height matches what a short result list would occupy so the page doesn't collapse to a one-liner with a wall of white below.</p>
|
||
</div>
|
||
|
||
<div class="split3">
|
||
|
||
<!-- Empty state -->
|
||
<div>
|
||
<div class="screen-lbl"><span class="lbl-dot" style="background:#9ca3af"></span>Empty — zero results</div>
|
||
<div class="scale-outer" style="width:832px;height:390px">
|
||
<div class="scale-inner" style="height:600px">
|
||
<div class="chrome-bar"><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-url"></div></div>
|
||
<div class="app-nav"><div class="app-logo">Familienarchiv</div><div class="app-link on">Dokumente</div><div class="app-link">Personen</div><div class="app-nav-r"><div class="app-av">M</div></div></div>
|
||
<div class="pg">
|
||
<div class="pg-h1">Dokumente</div>
|
||
<div class="pg-sub">Keine Ergebnisse</div>
|
||
<div class="sfb">
|
||
<div class="sfb-r1" style="margin-bottom:0">
|
||
<div class="sfb-input-wrap">
|
||
<div class="sfb-input typed">Briefe über gärten im frühling</div>
|
||
<div class="sfb-pill-wrap"><div class="sfb-pill-smart"><svg width="9" height="9" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1l1.5 4.5H14l-3.7 2.7 1.4 4.3L8 10.2l-3.7 2.3 1.4-4.3L2 5.5h4.5z"/></svg> KI</div></div>
|
||
</div>
|
||
<div class="sfb-sort-btn">Sortierung ▾</div>
|
||
<div class="sfb-filter-btn"><svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 8 8 12 12 8"/></svg> Filter</div>
|
||
<div class="sfb-reset-btn">✕</div>
|
||
</div>
|
||
</div>
|
||
<!-- Chips still visible -->
|
||
<div class="chip-row">
|
||
<div class="chip"><div class="chip-body"><span class="chip-pfx">Absender:</span><span class="chip-nm">Walter Raddatz</span></div><div class="chip-x">×</div></div>
|
||
<div class="chip"><div class="chip-body"><span class="chip-pfx">Stichwort:</span><span class="chip-nm">gärten</span></div><div class="chip-x">×</div></div>
|
||
</div>
|
||
<!-- Empty panel -->
|
||
<div class="status-area">
|
||
<svg class="status-empty-icon" width="44" height="44" viewBox="0 0 24 24" fill="none" stroke="#012851" stroke-width="1.5"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="8" y1="11" x2="14" y2="11" stroke-dasharray="2 1"/></svg>
|
||
<div class="status-empty-title">Keine Ergebnisse</div>
|
||
<div class="status-empty-body">Für „Briefe über gärten" wurden keine Dokumente gefunden. Versuchen Sie es mit einer einfachen Stichwortsuche.</div>
|
||
<a class="status-empty-link" role="button" tabindex="0">Als Volltextsuche wiederholen</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="cap">Chips remain visible — the user sees what was searched. The fallback link switches to keyword mode in-place (Option A from issue): <code>smartMode = false</code>, keeps <code>q</code>, triggers keyword search. No navigation, no scroll reset. Min-h 44 px touch target on link.</p>
|
||
</div>
|
||
|
||
<!-- UNAVAILABLE 503 -->
|
||
<div>
|
||
<div class="screen-lbl"><span class="lbl-dot" style="background:#b91c1c"></span>503 — SMART_SEARCH_UNAVAILABLE</div>
|
||
<div class="scale-outer" style="width:832px;height:390px">
|
||
<div class="scale-inner" style="height:600px">
|
||
<div class="chrome-bar"><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-url"></div></div>
|
||
<div class="app-nav"><div class="app-logo">Familienarchiv</div><div class="app-link on">Dokumente</div><div class="app-link">Personen</div><div class="app-nav-r"><div class="app-av">M</div></div></div>
|
||
<div class="pg">
|
||
<div class="pg-h1">Dokumente</div>
|
||
<div class="pg-sub">Intelligente Suche nicht verfügbar</div>
|
||
<div class="sfb">
|
||
<div class="sfb-r1" style="margin-bottom:0">
|
||
<div class="sfb-input-wrap">
|
||
<div class="sfb-input typed">Was hat walter geschrieben?</div>
|
||
<div class="sfb-pill-wrap"><div class="sfb-pill-smart"><svg width="9" height="9" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1l1.5 4.5H14l-3.7 2.7 1.4 4.3L8 10.2l-3.7 2.3 1.4-4.3L2 5.5h4.5z"/></svg> KI</div></div>
|
||
</div>
|
||
<div class="sfb-sort-btn">Sortierung ▾</div>
|
||
<div class="sfb-filter-btn"><svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 8 8 12 12 8"/></svg> Filter</div>
|
||
<div class="sfb-reset-btn">✕</div>
|
||
</div>
|
||
</div>
|
||
<!-- Error panel -->
|
||
<div class="status-area">
|
||
<div class="status-icon-circle err">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#b91c1c" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><circle cx="12" cy="16" r="1" fill="#b91c1c"/></svg>
|
||
</div>
|
||
<div class="status-error-title">Intelligente Suche nicht verfügbar</div>
|
||
<div class="status-error-body">Die KI-Suche ist momentan nicht erreichbar. Sie können Ihre Anfrage als einfache Volltextsuche wiederholen.</div>
|
||
<button class="status-err-btn">Zur Volltextsuche wechseln</button>
|
||
</div>
|
||
<div style="text-align:center"><span class="ann">ErrorCode: SMART_SEARCH_UNAVAILABLE</span><span class="ann">i18n: search_error_unavailable + search_switch_to_keyword</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="cap">503. Full-area error panel with icon, explanatory body text, and the keyword fallback button. The button calls <code>switchToKeywordMode()</code>. Separate <code>case</code> in <code>getErrorMessage()</code>.</p>
|
||
</div>
|
||
|
||
<!-- RATE LIMITED 429 -->
|
||
<div>
|
||
<div class="screen-lbl"><span class="lbl-dot" style="background:#d97706"></span>429 — SMART_SEARCH_RATE_LIMITED</div>
|
||
<div class="scale-outer" style="width:832px;height:390px">
|
||
<div class="scale-inner" style="height:600px">
|
||
<div class="chrome-bar"><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-url"></div></div>
|
||
<div class="app-nav"><div class="app-logo">Familienarchiv</div><div class="app-link on">Dokumente</div><div class="app-link">Personen</div><div class="app-nav-r"><div class="app-av">M</div></div></div>
|
||
<div class="pg">
|
||
<div class="pg-h1">Dokumente</div>
|
||
<div class="pg-sub">Zu viele Anfragen</div>
|
||
<div class="sfb">
|
||
<div class="sfb-r1" style="margin-bottom:0">
|
||
<div class="sfb-input-wrap">
|
||
<div class="sfb-input typed">Was hat walter geschrieben?</div>
|
||
<div class="sfb-pill-wrap"><div class="sfb-pill-smart"><svg width="9" height="9" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1l1.5 4.5H14l-3.7 2.7 1.4 4.3L8 10.2l-3.7 2.3 1.4-4.3L2 5.5h4.5z"/></svg> KI</div></div>
|
||
</div>
|
||
<div class="sfb-sort-btn">Sortierung ▾</div>
|
||
<div class="sfb-filter-btn"><svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 8 8 12 12 8"/></svg> Filter</div>
|
||
<div class="sfb-reset-btn">✕</div>
|
||
</div>
|
||
</div>
|
||
<!-- Warning panel — no button -->
|
||
<div class="status-area">
|
||
<div class="status-icon-circle warn">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#d97706" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><circle cx="12" cy="17" r="1" fill="#d97706"/></svg>
|
||
</div>
|
||
<div class="status-error-title">Zu viele Anfragen</div>
|
||
<div class="status-error-body">Du hast die intelligente Suche zu häufig genutzt.<br>Bitte warte eine Minute und versuche es erneut.</div>
|
||
<!-- Intentionally NO button — rate limit is temporary -->
|
||
</div>
|
||
<div style="text-align:center"><span class="ann">ErrorCode: SMART_SEARCH_RATE_LIMITED</span><span class="ann">No fallback button — limit is temporary</span><span class="ann">Separate case in getErrorMessage()</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="cap">429. Warning-style panel with no keyword fallback button — the rate limit is temporary; the user should wait and retry. Do not group this <code>case</code> with UNAVAILABLE even if messages look similar.</p>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══ SECTION 8 — MOBILE 320 px ═════════════════════════════════════════════ -->
|
||
<div class="section">
|
||
<div class="sh">
|
||
<h2>8 · Mobile (320 px) — toggle in input, chips wrapping</h2>
|
||
<p>The toggle pill stays inside the input on mobile too — the input is full-width anyway so there is room. Below the <code>sm</code> breakpoint (640 px) the pill label expands slightly to "KI-Suche" / "Textsuche" for senior legibility at small sizes. Chips use <code>flex flex-wrap</code> — no horizontal scroll at 320 px.</p>
|
||
</div>
|
||
|
||
<div class="split2">
|
||
|
||
<!-- Mobile keyword mode -->
|
||
<div>
|
||
<div class="screen-lbl"><span class="lbl-dot" style="background:#A1DCD8;border:1px solid #ccc"></span>Mobile 320 px — keyword mode</div>
|
||
<div class="scale-outer-sm" style="width:263px;height:262px">
|
||
<div class="scale-inner-sm" style="height:320px">
|
||
<div style="height:18px;background:#E8E6E0;border-bottom:1px solid #C4C0BA;display:flex;align-items:center;padding:0 8px;gap:4px"><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-url"></div></div>
|
||
<div style="height:28px;background:#012851;display:flex;align-items:center;padding:0 10px"><div class="app-logo">Familienarchiv</div><div style="margin-left:auto"><div class="app-av">M</div></div></div>
|
||
<div class="pg" style="padding:10px">
|
||
<div class="pg-h1" style="font-size:14px;margin-bottom:2px">Dokumente</div>
|
||
<div class="pg-sub">Suche in Briefen und Urkunden</div>
|
||
<div class="sfb" style="padding:10px">
|
||
<!-- Input + pill (full width) -->
|
||
<div class="sfb-input-wrap" style="margin-bottom:8px">
|
||
<div class="sfb-input" style="padding-right:80px;height:44px;font-size:9px">Suche…</div>
|
||
<div class="sfb-pill-wrap">
|
||
<div class="sfb-pill-kw" style="font-size:7px;padding:3px 7px">
|
||
<svg width="8" height="8" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><rect x="1" y="3" width="14" height="10" rx="2"/><line x1="4" y1="7" x2="12" y2="7"/><line x1="4" y1="10" x2="9" y2="10"/></svg>
|
||
Textsuche
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Sort + Filter row -->
|
||
<div style="display:flex;gap:5px">
|
||
<div class="sfb-sort-btn" style="height:36px;font-size:7.5px;padding:0 6px">Sortierung ▾</div>
|
||
<div class="sfb-filter-btn" style="height:36px;font-size:7.5px;padding:0 6px;flex:1;justify-content:center">Filter</div>
|
||
<div class="sfb-reset-btn" style="height:36px;width:32px">✕</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="cap">Keyword mode on mobile. Pill reads "Textsuche" at narrow widths for senior legibility — abbreviated "Text" could be read as a noun. The input is 44 px tall (meets touch target). Sort and Filter buttons remain in the same row below.</p>
|
||
</div>
|
||
|
||
<!-- Mobile smart mode + chips -->
|
||
<div>
|
||
<div class="screen-lbl"><span class="lbl-dot" style="background:#012851"></span>Mobile 320 px — smart mode + chips wrapping</div>
|
||
<div class="scale-outer-sm" style="width:263px;height:360px">
|
||
<div class="scale-inner-sm" style="height:439px">
|
||
<div style="height:18px;background:#E8E6E0;border-bottom:1px solid #C4C0BA;display:flex;align-items:center;padding:0 8px;gap:4px"><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-url"></div></div>
|
||
<div style="height:28px;background:#012851;display:flex;align-items:center;padding:0 10px"><div class="app-logo">Familienarchiv</div><div style="margin-left:auto"><div class="app-av">M</div></div></div>
|
||
<div class="pg" style="padding:10px">
|
||
<div class="pg-h1" style="font-size:14px;margin-bottom:2px">Dokumente</div>
|
||
<div class="pg-sub">12 Ergebnisse</div>
|
||
<div class="sfb" style="padding:10px">
|
||
<div class="sfb-input-wrap" style="margin-bottom:8px">
|
||
<div class="sfb-input typed" style="padding-right:80px;height:44px;font-size:9px">Was hat walter im krieg…</div>
|
||
<div class="sfb-pill-wrap">
|
||
<div class="sfb-pill-smart" style="font-size:7px;padding:3px 7px">
|
||
<svg width="8" height="8" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1l1.5 4.5H14l-3.7 2.7 1.4 4.3L8 10.2l-3.7 2.3 1.4-4.3L2 5.5h4.5z"/></svg>
|
||
KI-Suche
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;gap:5px">
|
||
<div class="sfb-sort-btn" style="height:36px;font-size:7.5px;padding:0 6px">Sortierung ▾</div>
|
||
<div class="sfb-filter-btn" style="height:36px;font-size:7.5px;padding:0 6px;flex:1;justify-content:center">Filter</div>
|
||
<div class="sfb-reset-btn" style="height:36px;width:32px">✕</div>
|
||
</div>
|
||
</div>
|
||
<!-- Chips wrapping -->
|
||
<div class="chip-row" style="max-width:303px;gap:5px">
|
||
<div class="chip" style="font-size:8px">
|
||
<div class="chip-body" style="padding:4px 5px 4px 8px">
|
||
<span class="chip-pfx">Absender:</span>
|
||
<span style="max-width:80px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:'Tinos',Georgia,serif;font-size:9px;color:#012851">Walter Raddatz</span>
|
||
</div>
|
||
<div class="chip-x" style="width:22px;font-size:9px;flex-shrink:0">×</div>
|
||
</div>
|
||
<div class="chip" style="font-size:8px">
|
||
<div class="chip-body" style="padding:4px 5px 4px 8px">
|
||
<span class="chip-pfx">Zeitraum:</span><span class="chip-nm" style="font-size:9px">1914–1918</span>
|
||
</div>
|
||
<div class="chip-x" style="width:22px;font-size:9px;flex-shrink:0">×</div>
|
||
</div>
|
||
<!-- Stichwort wraps to next line -->
|
||
<div class="chip" style="font-size:8px">
|
||
<div class="chip-body" style="padding:4px 5px 4px 8px">
|
||
<span class="chip-pfx">Stichwort:</span><span class="chip-nm" style="font-size:9px">krieg</span>
|
||
</div>
|
||
<div class="chip-x" style="width:22px;font-size:9px;flex-shrink:0">×</div>
|
||
</div>
|
||
</div>
|
||
<!-- Doc list -->
|
||
<div class="doc-list" style="gap:4px">
|
||
<div class="doc-row" style="padding:7px 8px"><div class="doc-thumb" style="width:18px;height:24px;font-size:5px">▤</div><div class="doc-info"><div class="doc-title" style="font-size:8.5px">Brief an Emma, August 1916</div><div class="doc-meta">Walter Raddatz · 1916</div></div></div>
|
||
<div class="doc-row" style="padding:7px 8px"><div class="doc-thumb" style="width:18px;height:24px;font-size:5px">▤</div><div class="doc-info"><div class="doc-title" style="font-size:8.5px">Feldpostkarte, Juni 1917</div><div class="doc-meta">Walter Raddatz · 1917</div></div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="cap">Smart mode on mobile. Pill reads "KI-Suche". The input is 44 px tall. Chips wrap at 303 px — "Stichwort: krieg" lands on the second line. No horizontal scroll. × buttons at 22 px wide, outside truncatable spans.</p>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══ SECTION 9 — IMPLEMENTATION REFERENCE ══════════════════════════════════ -->
|
||
<div class="section">
|
||
<div class="sh">
|
||
<h2>9 · Implementation reference</h2>
|
||
</div>
|
||
<div class="rules">
|
||
<table>
|
||
<thead><tr><th>Element</th><th>Tailwind / CSS</th><th>Notes</th></tr></thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Input wrapper</td>
|
||
<td><code>relative flex-1</code></td>
|
||
<td>Position context for the absolutely-placed toggle pill</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Search input — smart mode</td>
|
||
<td><code>pr-28</code> (≥ sm), <code>pr-24</code> (mobile) — extra right padding for pill</td>
|
||
<td>Also: <code>maxlength="500"</code> when <code>smartMode</code>; <code>oninput={smartMode ? undefined : onSearch}</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Toggle pill wrapper</td>
|
||
<td><code>absolute right-2 top-1/2 -translate-y-1/2</code></td>
|
||
<td>Sits over the input's right padding; <code>pointer-events-auto</code> (parent input is pointer-events-none on the right side for the icon slot)</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Pill — keyword (resting)</td>
|
||
<td><code>flex items-center gap-1.5 rounded-full border border-line bg-muted text-ink-2 text-[7.5px] font-bold px-2.5 py-1 min-h-[28px] cursor-pointer focus-visible:ring-2 focus-visible:ring-brand-navy outline-none</code></td>
|
||
<td><code>aria-pressed="false"</code>; icon + "Text" (desktop) / "Textsuche" (mobile, <code>sm:hidden</code>)</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Pill — smart (active)</td>
|
||
<td><code>flex items-center gap-1.5 rounded-full border border-primary bg-primary text-primary-fg text-[7.5px] font-bold px-2.5 py-1 min-h-[28px] cursor-pointer focus-visible:ring-2 focus-visible:ring-brand-navy outline-none</code></td>
|
||
<td><code>aria-pressed="true"</code>; icon + "KI" (desktop) / "KI-Suche" (mobile). Matches AND/OR button active pattern</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Chip wrapper</td>
|
||
<td><code>inline-flex items-stretch border-[1.5px] border-primary rounded-full bg-surface overflow-hidden focus-visible:ring-2 focus-visible:ring-brand-navy outline-none</code></td>
|
||
<td>Entire wrapper focusable — ring on wrapper, not only the × button</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Chip body</td>
|
||
<td><code>flex items-center gap-1 pl-3 pr-2 py-1</code></td>
|
||
<td>Prefix: <code>text-[8px] font-bold opacity-65</code>; name: <code>font-serif text-[9.5px]</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Chip name span (directional)</td>
|
||
<td><code>sm:max-w-[12rem] max-w-[8rem] truncate</code></td>
|
||
<td>Two separate spans; <code><span aria-hidden="true">→</span></code> between. Chip: <code>aria-label="Von {p0} zu {p1}, Filter entfernen"</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Chip × button</td>
|
||
<td><code>w-7 shrink-0 flex items-center justify-center border-l border-primary/15 text-[11px] text-ink min-h-[36px]</code></td>
|
||
<td><code>aria-label="Filter entfernen: {label}"</code>; min-h-[36px] extends touch target beyond visual size</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Chip row</td>
|
||
<td><code>flex flex-wrap gap-2</code></td>
|
||
<td>Wraps at 320 px without horizontal scroll; no max-width on the row itself</td>
|
||
</tr>
|
||
<tr>
|
||
<td>SmartSearchStatus — loading</td>
|
||
<td><code>flex flex-col items-center justify-center gap-3 py-16 text-center</code></td>
|
||
<td>Spinner: <code>w-9 h-9 rounded-full border-[3px] border-primary/12 border-t-primary motion-safe:animate-spin</code>; title: <code>text-sm font-bold</code>; sub: <code>text-[9px] text-ink-3 max-w-[20rem]</code>. Wrapper: <code>role="status" aria-live="polite"</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>SmartSearchStatus — error (503)</td>
|
||
<td><code>flex flex-col items-center gap-3 py-16 text-center</code></td>
|
||
<td>Icon circle: <code>w-10 h-10 rounded-full bg-red-50 border-2 border-red-400 flex items-center justify-center</code>; action: <code>border border-primary text-primary px-4 py-2 text-[9px] font-bold rounded-sm</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>SmartSearchStatus — warning (429)</td>
|
||
<td>Same layout as 503 but amber icon; <strong>no action button</strong></td>
|
||
<td>Separate <code>case SMART_SEARCH_RATE_LIMITED</code> in <code>getErrorMessage()</code>. Do not group with 503</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Empty state fallback link</td>
|
||
<td><code>text-primary font-bold text-[9px] underline underline-offset-2 focus-visible:ring-2 focus-visible:ring-brand-navy outline-none py-3 inline-block</code></td>
|
||
<td>Option A: <code>smartMode = false</code>, keep <code>q</code>, call <code>onSearch()</code>. No navigation. i18n: <code>search_empty_retry_keyword</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Disambiguation chip</td>
|
||
<td>Same chip structure but <code>border-amber-600 bg-amber-50</code>; append "(auswählen…)" hint span in italics</td>
|
||
<td>Trigger: <code>aria-expanded aria-controls</code>; <code>min-h-[44px]</code>; <code>aria-label={m.search_disambiguation_trigger_label()}</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Disambiguation picker panel</td>
|
||
<td><code>bg-surface border border-primary rounded-sm p-2</code></td>
|
||
<td>Focus moves to first item on open. Escape → return focus to trigger. Suchen → <code>GET /api/documents/search</code></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="sh" style="margin-top:28px">
|
||
<h2>9.1 · i18n keys (messages/{de,en,es}.json)</h2>
|
||
</div>
|
||
<div class="rules">
|
||
<table>
|
||
<thead><tr><th>Key</th><th>German</th><th>Used by</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><code>search_toggle_smart_label</code></td><td>"KI" / "KI-Suche"</td><td>SmartModeToggle — smart mode</td></tr>
|
||
<tr><td><code>search_toggle_keyword_label</code></td><td>"Text" / "Textsuche"</td><td>SmartModeToggle — keyword mode</td></tr>
|
||
<tr><td><code>search_loading_nl</code></td><td>"Archiv wird befragt…"</td><td>SmartSearchStatus loading title</td></tr>
|
||
<tr><td><code>search_loading_nl_sub</code></td><td>"Die KI analysiert Ihre Anfrage. Das kann bis zu 15 Sekunden dauern."</td><td>SmartSearchStatus loading subtitle</td></tr>
|
||
<tr><td><code>search_error_unavailable</code></td><td>"Intelligente Suche nicht verfügbar"</td><td>SmartSearchStatus 503 title</td></tr>
|
||
<tr><td><code>search_error_unavailable_body</code></td><td>"Die KI-Suche ist momentan nicht erreichbar. Sie können Ihre Anfrage als einfache Volltextsuche wiederholen."</td><td>SmartSearchStatus 503 body</td></tr>
|
||
<tr><td><code>search_switch_to_keyword</code></td><td>"Zur Volltextsuche wechseln"</td><td>SmartSearchStatus 503 button</td></tr>
|
||
<tr><td><code>search_error_rate_limited</code></td><td>"Zu viele Anfragen"</td><td>SmartSearchStatus 429 title</td></tr>
|
||
<tr><td><code>search_error_rate_limited_body</code></td><td>"Du hast die intelligente Suche zu häufig genutzt. Bitte warte eine Minute und versuche es erneut."</td><td>SmartSearchStatus 429 body</td></tr>
|
||
<tr><td><code>search_empty_retry_keyword</code></td><td>"Als Volltextsuche wiederholen"</td><td>Empty state link</td></tr>
|
||
<tr><td><code>search_filter_remove_label</code></td><td>"Filter entfernen: {label}"</td><td>Chip × button aria-label</td></tr>
|
||
<tr><td><code>search_disambiguation_trigger_label</code></td><td>"Mehrere Personen gefunden — zum Auswählen klicken"</td><td>Disambiguation chip trigger</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="sh" style="margin-top:28px">
|
||
<h2>9.2 · Atomic ErrorCode rollout (one commit)</h2>
|
||
</div>
|
||
<div class="rules">
|
||
<table>
|
||
<thead><tr><th>Step</th><th>File</th><th>Change</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>1</td><td><code>ErrorCode.java</code></td><td>Add <code>SMART_SEARCH_UNAVAILABLE</code>, <code>SMART_SEARCH_RATE_LIMITED</code></td></tr>
|
||
<tr><td>2</td><td><code>frontend/src/lib/shared/errors.ts</code></td><td>Add both to <code>ErrorCode</code> union type</td></tr>
|
||
<tr><td>3</td><td><code>errors.ts → getErrorMessage()</code></td><td>One separate <code>case</code> per code — do not group</td></tr>
|
||
<tr><td>4</td><td><code>messages/{de,en,es}.json</code></td><td>All 12 keys above in all three locale files</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
</div><!-- /doc -->
|
||
</body>
|
||
</html>
|