Files
familienarchiv/design_handoff_familienarchiv_redesign/DESIGN_RULES.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
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

10 KiB
Raw Blame History

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 1924px / 700 sentence case
Body / letter / snippet Tinos 1518px, lh 1.551.75 snippets italic, quotes use „…“
Rubric / eyebrow label Montserrat 12px / 700, letter-spacing:.14em, UPPERCASE above every page title
Section caption Montserrat 1112px / 700, .12.14em, UPPERCASE card headers
Button / nav label Montserrat 1112px / 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, color rgba(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 sets document.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-colors only. 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:910px 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:2024px. 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 at text-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-40 resting tint (.65 on the dark header), globally inverted in dark mode.