Files
familienarchiv/design_handoff_familienarchiv_redesign/_AUTHORING_KIT.md
marcel fa510f3991
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m1s
CI / OCR Service Tests (push) Successful in 28s
CI / Backend Unit Tests (push) Successful in 6m21s
CI / fail2ban Regex (push) Successful in 44s
CI / Semgrep Security Scan (push) Successful in 27s
CI / Compose Bucket Idempotency (push) Successful in 1m8s
nightly / deploy-staging (push) Successful in 4m43s
nightly / npm-audit (push) Failing after 19s
Renovate / renovate (push) Failing after 38s
feat(redesign): token foundation close-out + design handoff (#854)
Adds the Mappe design handoff as in-repo ground truth and closes out the design-token foundation: --radius-sm/md/full + --shadow-sm/md tokens (distinct dark values in both dark blocks) and the canonical 10-color $lib/shared/avatarPalette.ts (AA-darkened sage/amber/sand swatches), guarded by avatarPalette.spec.ts. Closes #854.
2026-06-16 17:17:27 +02:00

276 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Prototype Authoring Kit — "Mappe" redesign
You are authoring a **`.dc.html` design prototype** for the Familienarchiv redesign. These
prototypes are the **pixel ground truth** for one screen. They are NOT production code — they
run in a tiny prototyping runtime (`support.js`) and are opened directly in a browser.
**Read alongside this kit:** `DESIGN_RULES.md` (the binding visual law) and the existing
prototype you are told to use as a template. When this kit and `DESIGN_RULES.md` disagree,
`DESIGN_RULES.md` wins; when a rule and an existing rendered prototype disagree, the
prototype wins.
Everything below is **copy-verbatim**. Do not invent new tokens, colors, fonts, radii, or
spacings. Use only `var(--c-*)` / `var(--font-*)` / `var(--shadow-*)` and the literal values
shown here.
---
## 1. File skeleton (copy exactly; fill the `<main>` and the `renderVals()`)
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="colors_and_type.css">
<style>
html,body{margin:0;padding:0;background:#f0efe9}
*{box-sizing:border-box}
input,button,textarea,select{font-family:inherit}
::selection{background:#a1dcd8;color:#012851}
[data-theme='dark'] .dgicon{filter:invert(1) brightness(1.6)}
.fa-link{transition:color .15s, background .15s, border-color .15s}
</style>
<script>(function(){try{if(localStorage.getItem('theme')==='dark'){document.documentElement.dataset.theme='dark';}}catch(e){}})();</script>
</helmet>
<div style="min-height:100vh; background:var(--c-canvas); color:var(--c-ink); font-family:var(--font-serif)">
<dc-import name="ArchiveHeader" active="ACTIVE_KEY" hint-size="100%,68px"></dc-import>
<main style="max-width:1180px; margin:0 auto; padding:40px 32px 80px">
<!-- PAGE CONTENT HERE -->
</main>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{}">
class Component extends DCLogic {
av(name){
const pal = ['#5a8a6a','#a0522d','#c17a00','#607080','#7a4f9a','#c0446e','#3060b0','#4a7a3a','#9a8040','#c05540'];
let h = 0; for (let i=0;i<name.length;i++){ h = (h*31 + name.charCodeAt(i)) >>> 0; }
const parts = name.trim().split(/\s+/);
const initials = (parts[0][0] + (parts.length>1 ? parts[parts.length-1][0] : '')).toUpperCase();
const bg = pal[h % pal.length];
const base = { borderRadius:'999px', color:'#fff', display:'flex', alignItems:'center', justifyContent:'center', fontFamily:'var(--font-sans)', fontWeight:700, flexShrink:0, lineHeight:1, background:bg };
return { bg, initials, name,
a26:{ ...base, width:26, height:26, fontSize:10 },
a28:{ ...base, width:28, height:28, fontSize:11 },
a40:{ ...base, width:40, height:40, fontSize:14 },
a48:{ ...base, width:48, height:48, fontSize:16 } };
}
renderVals(){
return { /* data the template renders */ };
}
}
</script>
</body>
</html>
```
- `ACTIVE_KEY``dokumente · personen · geschichten · zeitstrahl · aktivitaeten · stammbaum · themen · regeln`.
Pick the section your page belongs to (e.g. a document edit page → `dokumente`; a person
edit page → `personen`; a timeline event editor → `zeitstrahl`).
- **Auth pages (`Anmeldung`) have NO `ArchiveHeader`** — they get their own branded shell
(see §11).
## 2. Runtime constructs (this is all the runtime understands)
- `{{ expr }}` — interpolate a value from `renderVals()` (path access: `a.b`, `a[0]`). Works
in text and in any attribute, including `style="{{ obj }}"` where `obj` is a JS style
object.
- `<sc-for list="{{ items }}" as="x" hint-placeholder-count="6"> … {{ x.foo }} … </sc-for>`
- `<sc-if value="{{ flag }}" hint-placeholder-val="{{ false }}"> … </sc-if>`
- `onClick="{{ handler }}"` where `handler` is a function returned from `renderVals()`.
- The logic class is `class Component extends DCLogic`. `renderVals()` returns the flat data
object. Use `this.props.X` to read a prop. `this.state` + `this.setState({...})` for
interactivity (rarely needed — prototypes are mostly static).
- To inject a raw element from logic (e.g. an icon inside a loop), use
`React.createElement('img', { className:'dgicon', src:'assets/icons/Mail-MD.svg', style:{width:18,height:18,opacity:.5} })`.
- **camelCase event/style props** in the template: `onClick`, not `onclick`.
## 3. PageHeader (DESIGN_RULES §3) — every top-level page opens with this
```html
<div style="margin-bottom:30px; display:flex; align-items:flex-end; justify-content:space-between; gap:24px; flex-wrap:wrap">
<div style="border-left:4px solid var(--c-accent); padding-left:18px">
<div style="font-family:var(--font-sans); font-size:12px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--c-ink-3); margin-bottom:8px">EYEBROW</div>
<h1 style="font-family:var(--font-serif); font-weight:700; font-size:46px; line-height:1.06; color:var(--c-ink); margin:0">Title</h1>
<p style="font-family:var(--font-serif); font-style:italic; font-size:16px; color:var(--c-ink-2); margin:10px 0 0; max-width:520px">Lede sentence, Sie-form.</p>
</div>
<!-- right slot: a count span OR a primary button (see §6) -->
<span style="font-family:var(--font-sans); font-size:12px; color:var(--c-ink-3)">147 Dokumente</span>
</div>
```
Edit/detail/form pages still open with a PageHeader (eyebrow like `PERSON BEARBEITEN`,
`EREIGNIS`, `NEUE PERSON`). Immersive split-pane workbenches (document detail viewer,
document/enrich edit) may use a compact top bar instead — follow your page brief.
## 4. Card (DESIGN_RULES §7)
Base card, **with the 3px mint top-border archival signature** (most content cards):
```html
<div style="background:var(--c-surface); border:1px solid var(--c-line); border-top:3px solid var(--c-accent); box-shadow:var(--shadow-sm); border-radius:2px; padding:24px"></div>
```
Variants: inline/resume strip uses `border-left:3px solid var(--c-accent)` instead of the top
border. Section caption inside a card:
```html
<h2 style="font-family:var(--font-sans); font-size:12px; font-weight:700; letter-spacing:.12em; text-transform:uppercase; color:var(--c-ink-3); margin:0 0 16px">Section title</h2>
```
## 5. Segmented control (DESIGN_RULES §6) — filters / view switches / binary type picks
```html
<div style="display:inline-flex; border:1px solid var(--c-line)">
<span class="fa-link" style="font-family:var(--font-sans); font-size:12px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; color:var(--c-primary-fg); background:var(--c-primary); padding:10px 16px; cursor:pointer">Alle</span>
<span class="fa-link" style="font-family:var(--font-sans); font-size:12px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; color:var(--c-ink-2); background:var(--c-surface); padding:10px 16px; border-left:1px solid var(--c-line); cursor:pointer">Zweitens</span>
<!-- repeat inactive segments; each adds border-left:1px solid var(--c-line) -->
</div>
```
Active = `background:var(--c-primary); color:var(--c-primary-fg)`. Inactive =
`background:var(--c-surface); color:var(--c-ink-2)`.
## 6. Buttons (DESIGN_RULES §2 casing law — all UPPERCASE Montserrat 700)
```html
<!-- PRIMARY (navy) -->
<button class="fa-link" style="background:var(--c-primary); color:var(--c-primary-fg); border:none; padding:11px 20px; min-height:44px; font-family:var(--font-sans); font-size:12px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; cursor:pointer; border-radius:2px">Speichern</button>
<!-- SECONDARY (bordered) -->
<button class="fa-link" style="background:var(--c-surface); color:var(--c-ink-2); border:1px solid var(--c-line); padding:11px 20px; min-height:44px; font-family:var(--font-sans); font-size:12px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; cursor:pointer; border-radius:2px">Abbrechen</button>
<!-- DANGER (destructive — Löschen) -->
<a href="#" class="fa-link" style="display:inline-flex; align-items:center; min-height:44px; font-family:var(--font-sans); font-size:12px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--c-danger); text-decoration:none">Löschen</a>
```
## 7. Form field + label (DESIGN_RULES §1, §2)
```html
<label style="display:block">
<span style="display:block; font-family:var(--font-sans); font-size:12px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--c-ink-3); margin-bottom:6px">Titel</span>
<input placeholder="z.B. Brief an Clara" style="width:100%; border:1px solid var(--c-line); background:var(--c-surface); padding:11px 14px; font-family:var(--font-serif); font-size:16px; color:var(--c-ink); outline:none; border-radius:2px">
</label>
```
Inputs are Tinos 16px (auth inputs 17px). Textareas same. Focus is shown by the runtime's
default outline; if you add a visible focus style use a 2px `var(--c-focus-ring)` outline with
2px offset — never remove focus without a replacement.
## 8. MetaLine (DESIGN_RULES §7) — ` · `-separated, optional leading icon
```html
<div style="display:flex; align-items:center; gap:8px; font-family:var(--font-sans); font-size:12px; color:var(--c-ink-2)">
<img class="dgicon" src="assets/icons/Calendar-Add-MD.svg" style="width:14px; height:14px; opacity:.5">
<span>14. März 1923</span><span>·</span><span>14 Dokumente</span><span>·</span><span>4 Personen</span>
</div>
```
## 9. Status dot (DESIGN_RULES §7) — 7px circle + UPPERCASE label
```html
<span style="display:inline-flex; align-items:center; gap:7px">
<span style="width:7px; height:7px; border-radius:999px; background:#5a8a6a"></span>
<span style="font-family:var(--font-sans); font-size:10px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--c-ink-2)">Transkribiert</span>
</span>
```
Status colors: Transkribiert / Veröffentlicht / Bestätigt `#5a8a6a` · In Arbeit `#c17a00` ·
Neu / Entwurf / Unbestätigt `#607080`. **Never color alone** — always the label too.
## 10. Empty state (DESIGN_RULES §7) — dashed, serif heading, German ellipsis
```html
<div style="border:1px dashed var(--c-line); border-radius:2px; padding:48px 24px; text-align:center">
<div style="font-family:var(--font-serif); font-size:20px; color:var(--c-ink); margin-bottom:8px">Noch keine Geschichten angelegt.</div>
<div style="font-family:var(--font-sans); font-size:13px; color:var(--c-ink-3)">Beginnen Sie mit einem Brief…</div>
</div>
```
## 11. Auth shell (only for `Anmeldung.dc.html` — no ArchiveHeader)
```html
<div style="min-height:100vh; display:flex; flex-direction:column; background:var(--c-canvas)">
<header style="background:var(--c-header)">
<div style="height:4px; background:#a1dcd8"></div>
<div style="max-width:1180px; margin:0 auto; padding:0 32px; height:64px; display:flex; align-items:center; color:#fff">
<span style="font-family:var(--font-sans); font-size:18px; font-weight:700; letter-spacing:.16em; text-transform:uppercase">Familienarchiv</span>
</div>
</header>
<div style="flex:1; display:flex; align-items:center; justify-content:center; padding:40px 16px">
<div style="width:100%; max-width:400px; background:var(--c-surface); border:1px solid var(--c-line); border-top:3px solid var(--c-accent); box-shadow:var(--shadow-sm); border-radius:2px; padding:36px 32px">
<h1 style="font-family:var(--font-serif); font-size:30px; font-weight:700; color:var(--c-ink); margin:0 0 8px">Anmelden</h1>
<!-- labelled 17px inputs (§7), full-width primary button (§6), secondary link row -->
</div>
</div>
</div>
```
Title is **Tinos sentence-case** (NOT an uppercase chrome label). Inputs 17px. Register
variant uses `max-width:640px` and sectioned fields.
## 12. Icons (render as `<img class="dgicon">`, never inline SVG, never emoji)
```html
<img class="dgicon" src="assets/icons/Mail-MD.svg" style="width:16px; height:16px; opacity:.4">
```
Available in `assets/icons/`: `Account-MD` · `Arrow-Right-MD` · `Bookmarks-MD` ·
`Calendar-Add-MD` · `Chat-MD` · `Check-MD` · `Copy-Item-MD` · `Edit-Content-MD` ·
`Filter-MD` · `Folder-MD` · `Globe-MD` · `Library-MD` · `Location-MD` · `Mag-Glass-MD` ·
`Mail-MD` · `Refresh-MD` · `Upload-MD` · `View-More-MD`. Use only these; pick the closest
match. Icons rest at `opacity:.4` (`.5` on meta lines). They invert automatically in dark
mode via the `.dgicon` rule in the skeleton.
## 13. Hard rules checklist (verify before you finish)
- [ ] Every color is a `var(--c-*)` token — **zero** raw hex except the `av()` palette and the
`#a1dcd8` mint stripe inside the header/auth shell. No `red-*`/`gray-*`/Tailwind.
- [ ] Casing law: UI chrome (labels, buttons, nav, captions, tags, status, eyebrow) is
**UPPERCASE Montserrat 700 + wide tracking**; headlines & body & names are **Tinos
sentence case**. Quotes use `„…"`; snippets are *italic*.
- [ ] Cards carry the 3px mint **top** border (or 3px mint **left** for inline strips).
- [ ] Touch targets ≥ 44px (`min-height:44px`) on buttons / interactive rows / icon buttons.
- [ ] Icon-only buttons would carry an `aria-label` in production — add `title="…"` in the
prototype so intent is clear.
- [ ] **No** transforms, scale, translate, blur, glassmorphism, gradients, or emoji. Motion is
color only.
- [ ] German, formal **Sie**. Use realistic family-archive content (Kurrent/Sütterlin letters
~18941945; people like Herbert Cram, Clara Cram, Marie Cram, Eugenie de Gruyter).
- [ ] Renders correctly in light AND dark — because you used tokens, it will. Do not hardcode
anything that breaks dark mode.
- [ ] Avatars use `this.av(name)` and the size objects (`a26/a28/a40/a48`). Same name → same
color (10-color palette).
## 14. Realistic data
Pull field names / sections from the **current Svelte page** you are given so the prototype
reflects what the app actually shows. Invent plausible German archival content for the values.
Aim for enough rows/items to show the layout breathing (e.g. 69 grid cards, 48 list rows).
## 15. Person chip (multiselect / token input)
A selected person inside a form (person multiselect, sender/receiver token input, etc.) is a
**square 2px rectangle chip**`--radius-sm`, matching inputs/cards across the app. The
**avatar inside stays round**, but the chip container is **NOT** a round pill. (`radius-full`
in §1 is for avatars, dots, and status/count pills — not person chips.) Use this exact
shape everywhere a removable person appears:
```html
<span style="display:inline-flex; align-items:center; gap:8px; background:var(--c-muted); border:1px solid var(--c-line); border-radius:2px; padding:4px 6px 4px 4px">
<span style="{{ p.a26 }}">{{ p.initials }}</span>
<span style="font-family:var(--font-serif); font-size:14px; color:var(--c-ink)">{{ p.name }}</span>
<a href="#" title="Person entfernen" class="fa-link" style="display:inline-flex; align-items:center; justify-content:center; width:28px; height:28px; font-family:var(--font-sans); font-size:15px; font-weight:700; color:var(--c-ink-3); text-decoration:none">×</a>
</span>
```
The person **name** is content → Tinos serif. The chip itself is `border-radius:2px`. Wrap
chip lists in `display:flex; flex-wrap:wrap; gap:8px`. (A read-only correspondent link uses
the same square 2px container with no `×`.)