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
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.
276 lines
15 KiB
Markdown
276 lines
15 KiB
Markdown
# 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
|
||
~1894–1945; 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. 6–9 grid cards, 4–8 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 `×`.)
|