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.
10 KiB
Familienarchiv — Design Rules (binding)
This is the visual law for the redesign. It promotes the informal Regeln page into
enforceable specs. When this doc and a prototype disagree, the prototype wins — these
values are transcribed from the prototypes, but the rendered file is ground truth.
Built on the De Gruyter Brill corporate identity. Tone: restrained, archival, formal-respectful — institutional, not cute. German-first, formal Sie, never emoji.
1. Design tokens
Port prototypes/colors_and_type.css verbatim into the app (Tailwind 4 @theme or CSS
custom properties). Components reference semantic tokens, never raw hex.
Color — light mode
| Token | Value | Use |
|---|---|---|
--c-canvas |
#f0efe9 |
Page background (warm sand). Flat, no gradients. |
--c-surface |
#ffffff |
Cards, inputs, menus. |
--c-muted |
#f5f4ef |
Subtle fills (mission-control tiles, hover wash). |
--c-line |
#e4e2d7 |
1px borders. |
--c-line-2 |
#eeede8 |
Inner dividers / row separators. |
--c-ink |
#012851 |
Primary text (navy). |
--c-ink-2 |
#4b5563 |
Secondary / body text. |
--c-ink-3 |
#6b7280 |
Meta, placeholder, captions. |
--c-primary |
#012851 |
Primary buttons, active segment, header. |
--c-primary-fg |
#ffffff |
Text on primary. |
--c-accent |
#a1dcd8 |
Mint — decorative only. Top stripes, left rules, underlines, timeline spine. Never carries text. |
--c-accent-bg |
rgba(161,220,216,.15) |
Tinted note/skill backgrounds. |
--c-header |
#012851 |
Header bar (always navy, both themes). |
--c-turquoise |
#00c7b1 |
Transcription mode only. |
--c-danger |
#c0392b |
Destructive actions (Löschen). |
--c-focus-ring |
#012851 |
2px focus outline, 2px offset (mint in dark). |
Color — dark mode (:root[data-theme='dark'])
Navy-tinted. Key flips: --c-canvas:#010e1e, --c-surface:#011526, --c-muted:#011a30,
--c-line:#0d3358, --c-ink:#f0efe9, --c-ink-2:#9ca3af, --c-ink-3:#8b97a5.
--c-accent flips to turquoise #00c7b1, and --c-primary flips to mint #a1dcd8
with --c-primary-fg:#012851. Full set in colors_and_type.css.
Person / avatar palette (deterministic — see §5)
#5a8a6a #a0522d #c17a00 #607080 #7a4f9a #c0446e #3060b0 #4a7a3a #9a8040 #c05540
The live avatar constant is
$lib/shared/avatarPalette.ts(single source of truth). Three of these hues fail the ≥4.5:1 white-initials contrast floor and ship as AA-darkened variants there (sage#527e61, amber#a46800, sand#897239); the bright hues above remain the decorative tag-dot colors.
Tag dot colors
--c-tag-sage #5a8a6a, --c-tag-sienna #a0522d, --c-tag-amber #c17a00,
--c-tag-slate #607080, --c-tag-violet #7a4f9a, --c-tag-rose #c0446e,
--c-tag-cobalt #3060b0, --c-tag-moss #4a7a3a, --c-tag-sand #9a8040,
--c-tag-coral #c05540.
Badge types (Personen)
| Type | bg / text / border |
|---|---|
| Institution | #e8eff7 / #1a4971 / #c4d5e8 |
| Gruppe | #f0e8f5 / #5a2d6f / #d8c5e3 |
| Unbekannt | #fdf4e3 / #7a5a0a / #f0ddb3 |
Radius / shadow
--radius-sm: 2px— cards, inputs, buttons, segmented control. The default.--radius-md: 4px— tag chips/badges only.--radius-full: 9999px— avatars, dots, pills.--shadow-sm: 0 1px 2px 0 rgb(0 0 0/.05)— resting cards.--shadow-md: 0 4px 6px -1px rgb(0 0 0/.1), 0 2px 4px -2px rgb(0 0 0/.1)— dropdowns.
2. Typography
Two families. Montserrat (--font-sans) for all UI chrome; Tinos (--font-serif)
for headlines, body, letter content, transcriptions, story prose.
| Role | Family | Size / weight | Treatment |
|---|---|---|---|
Page title (h1) |
Tinos | 46px / 700, line-height 1.06 | sentence case |
| Story detail title | Tinos | 38px / 700, lh 1.15 | sentence case |
Card title (h3) |
Tinos | 19–24px / 700 | sentence case |
| Body / letter / snippet | Tinos | 15–18px, lh 1.55–1.75 | snippets italic, quotes use „…“ |
| Rubric / eyebrow label | Montserrat | 12px / 700, letter-spacing:.14em, UPPERCASE |
above every page title |
| Section caption | Montserrat | 11–12px / 700, .12–.14em, UPPERCASE |
card headers |
| Button / nav label | Montserrat | 11–12px / 700, .08–.1em, UPPERCASE |
|
| Tag chip label | Montserrat | 10px / 700, .13–.15em, UPPERCASE |
|
| Meta line | Montserrat | 12px / 400 | counts, dates; separated by · |
Casing law: UI chrome (labels, buttons, nav, captions, tags) is ALL CAPS + wide tracking, Montserrat bold. Headlines and document titles are sentence case in Tinos serif.
3. Shared component: page header (eyebrow + title)
Every top-level page opens with this block. Build it once as a PageHeader
component (props: eyebrow, title, lede, optional right-side count or action).
<div border-left:4px solid var(--c-accent); padding-left:18px>
<eyebrow> Montserrat 12px/700, .14em, UPPERCASE, color --c-ink-3, margin-bottom 8px
<h1> Tinos 46px/700, lh 1.06, color --c-ink
<lede> Tinos italic 16px, color --c-ink-2, margin-top 10px, max-width 520px
The 4px mint left rule is the signature. A right-aligned count
(38 Personen, 147 Dokumente · 38 Personen) or a primary action button sits opposite via
justify-content:space-between; align-items:flex-end.
Page shell for every screen: min-height:100vh; background:var(--c-canvas), header on top,
then <main style="max-width:1180px; margin:0 auto; padding:40px 32px 80px">.
4. Shared component: app header + nav (ArchiveHeader)
Single sticky header reused on every page. This is the most important thing to extract into one component — it is currently duplicated and must not be.
position:sticky; top:0; z-index:50; background:var(--c-header).- 4px mint stripe (
#a1dcd8) across the very top, above the bar. - Bar:
max-width:1180px; margin:0 auto; padding:0 32px; height:64px; display:flex; align-items:center, white text. - Wordmark
FAMILIENARCHIV: Montserrat 18px/700,letter-spacing:.16em, UPPERCASE,margin-right:28px. - Nav items: Montserrat 11px/700,
.07em, UPPERCASE, colorrgba(255,255,255,.6),line-height:44px. Active item → color#fff+border-bottom:2px solid #a1dcd8. Nav order: Dokumente · Personen · Briefwechsel · Geschichten · Zeitstrahl · Aktivitäten (· Regeln, internal). Each links to its route; pass the active key as a prop. - Right cluster: Hell / Dunkel theme toggle (segmented; active segment = mint bg
#a1dcd8+ navy text) and a round user avatar chip (MR, white bg, navy text, 32px). - Theme toggle writes
localStorage['theme']('light'|'dark') and setsdocument.documentElement.dataset.theme. A tiny inline boot script in<head>reads it before paint to avoid a flash. Map this to the app's existing theme mechanism if one exists; otherwise replicate. - Motion:
transition-colorsonly. No transforms, no scale on press.
5. Shared primitive: avatar + deterministic color
Every person is a round avatar with initials, colored by a hash of the name so the same person is always the same color across every screen. Extract this into one util + component — it is currently copy-pasted into 6 files.
// palette = the 10 person colors in §1
function avatarFor(name) {
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();
return { bg: palette[h % palette.length], initials };
}
- Always a perfect circle (
border-radius:full), white initials in Montserrat 700,line-height:1. - Sizes in use: 48px/16px (selectors, person cards), 40px/14px (story byline, feed,
Briefwechsel rows), 26px/10px (overlapping stacks —
margin-left:-6px+2px solid var(--c-surface)ring), 28px (timeline person nodes). - The color distinguishes, it does not decorate — never restyle it for emphasis.
6. Shared component: segmented control (filters / views)
Inline filter switch used on Personen, Geschichten, Zeitstrahl, Aktivitäten, and the header theme toggle.
display:inline-flex; border:1px solid var(--c-line)(radius 0 / sm; segments share borders).- Each segment: Montserrat 12px/700,
.08em, UPPERCASE,padding:9–10px 16px,cursor:pointer. - Active segment:
background:var(--c-primary); color:var(--c-primary-fg). - Inactive:
background:var(--c-surface); color:var(--c-ink-2),border-left:1px solid var(--c-line)between segments.
7. Cards, metadata, empty states
Card: background:var(--c-surface); border:1px solid var(--c-line); box-shadow:var(--shadow-sm); border-radius:2px; padding:20–24px.
Most content cards add a 3px mint top border (border-top:3px solid var(--c-accent)) as
the archival signature; some use a 3px mint left border for inline/resume strips.
Metadata line: one Montserrat 12px line, items separated by ·, often led by a 14px
De Gruyter icon at opacity:.5. Example: 📅 14. März 1923 · 14 Dokumente · 4 Personen
(icon is an <img>, not emoji).
Status dots: 7px circle + UPPERCASE label. Transkribiert/Veröffentlicht #5a8a6a,
In Arbeit #c17a00, Neu/Entwurf #607080.
Empty state: dashed 1px var(--c-line) border, centered. Serif heading
(Noch keine Geschichten angelegt.) + Montserrat sub line ending in the German ellipsis
(Beginnen Sie mit einem Brief…). Quiet, Sie-form, helpful — never cute.
8. Motion, hover, imagery
- All motion is color,
transition: color/background/border .15–.2s. No transform, no scale, no press-down. - Rows:
hover:bg-muted/50. Links: 2px mint underline attext-underline-offset:3px,text-decoration-thickness:2px. Header nav:white/60 → white. - Backdrops
bg-black/20. No backdrop-blur, no glassmorphism, no gradients, no noise. - Imagery is warm aged letter scans; UI chrome stays cool navy to contrast.
- De Gruyter icons: black strokes as
<img>,opacity-40resting tint (.65on the dark header), globally inverted in dark mode.