diff --git a/Design/build_tokens.py b/Design/build_tokens.py new file mode 100644 index 0000000..8ca01e9 --- /dev/null +++ b/Design/build_tokens.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +"""Reads Design/tokens.json and writes docs/design/tokens.md and docs/design/tokens-preview.html. +Also checks WCAG contrast for every text pairing; exits 1 if a text pair is below 4.5:1.""" +import json +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +tokens = json.loads((ROOT / "Design" / "tokens.json").read_text()) +HEX = re.compile(r"^#[0-9A-Fa-f]{6}$") + + +def lum(hexcolor): + h = hexcolor.lstrip("#") + out = [] + for i in (0, 2, 4): + c = int(h[i:i + 2], 16) / 255 + out.append(c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4) + return 0.2126 * out[0] + 0.7152 * out[1] + 0.0722 * out[2] + + +def contrast(a, b): + la, lb = sorted((lum(a), lum(b)), reverse=True) + return (la + 0.05) / (lb + 0.05) + + +# --- validation ------------------------------------------------------------- +errors = [] +for name, t in tokens["color"].items(): + for theme in ("light", "dark"): + if not HEX.match(t.get(theme, "")): + errors.append(f"color.{name}.{theme} is not a #RRGGBB value") +if errors: + print("\n".join(errors)) + sys.exit(1) + +# --- contrast --------------------------------------------------------------- +TEXT_PAIRS = [ + ("ink.primary", "surface.chassis"), ("ink.primary", "surface.panel"), ("ink.primary", "surface.paper"), + ("ink.primary", "surface.well"), ("ink.primary", "surface.selection"), ("ink.onMeter", "surface.meterFace"), + ("ink.primary", "surface.keyHigh"), ("ink.primary", "surface.keyLow"), + ("ink.secondary", "surface.chassis"), ("ink.secondary", "surface.panel"), ("ink.secondary", "surface.paper"), + ("ink.tertiary", "surface.chassis"), ("ink.tertiary", "surface.panel"), ("ink.tertiary", "surface.paper"), + ("ink.tertiary", "surface.metalHigh"), ("ink.tertiary", "surface.metalLow"), + ("ink.inverse", "accent.record"), + ("level.amber", "surface.display"), +] +rows, failed = [], False +for fg, bg in TEXT_PAIRS: + for theme in ("light", "dark"): + r = contrast(tokens["color"][fg][theme], tokens["color"][bg][theme]) + ok = r >= 4.5 + failed |= not ok + rows.append((fg, bg, theme, r, ok)) + +print(f"{'text':16} {'on':20} {'finish':6} ratio") +for fg, bg, theme, r, ok in rows: + print(f"{fg:16} {bg:20} {theme:6} {r:5.2f} {'ok' if ok else 'FAIL'}") + +# --- markdown --------------------------------------------------------------- +md = [] +add = md.append +m = tokens["meta"] +add(f"# {m['name']} — v{m['version']}\n") +add(f"**Status:** {m['status']}\n") +add(f"{m['concept']}\n") +add("Bron: `Design/tokens.json`. Dit bestand en `tokens-preview.html` worden gegenereerd met `python3 Design/build_tokens.py`; pas nooit alleen deze bestanden aan.\n") + + +def table(headers, body): + add("| " + " | ".join(headers) + " |") + add("|" + "|".join("---" for _ in headers) + "|") + for r in body: + add("| " + " | ".join(str(c) for c in r) + " |") + add("") + + +add("## Kleur\n") +add("Verwijs ernaar als `color.`. Kolommen: ALUMINIUM (licht) en GRAPHITE (donker).\n") +table(["Token", "Aluminium", "Graphite", "Gebruik"], + [(f"`color.{n}`", f"`{t['light']}`", f"`{t['dark']}`", t["use"]) for n, t in tokens["color"].items()]) +add("**Regel:** `accent.record` is het enige accent en wordt nooit voor lopende tekst gebruikt. Amber en groen zijn alleen voor niveau-indicatoren en markeringen.\n") +add("### Contrast (berekend)\n") +table(["Tekst", "Op", "Afwerking", "Verhouding", ""], + [(f"`{a}`", f"`{b}`", th, f"{r:.2f}:1", "ok" if ok else "**te laag**") for a, b, th, r, ok in rows]) + +add("## Opaciteit\n") +table(["Token", "Waarde", "Gebruik"], [(f"`opacity.{n}`", t["value"], t["use"]) for n, t in tokens["opacity"].items()]) + +add("## Lettertypen\n") +table(["Token", "Familie", "Terugval", "Gebruik"], + [(f"`font.{n}`", t["value"], t["fallback"], t["use"]) for n, t in tokens["font"].items()]) +add("## Tekststijlen\n") +table(["Token", "Familie", "Grootte", "Gewicht", "Spatiëring", "Regelhoogte", "Hoofdletters", "Gebruik"], + [(f"`type.{n}`", t["family"], f"{t['size']} pt", t["weight"], f"{t['tracking']} em", f"{t['line']} pt", + "ja" if t["caps"] else "nee", t["use"]) for n, t in tokens["type"].items()]) +add("## Segmentcijfers (getekend, geen lettertype)\n") +table(["Token", "Waarde", "Gebruik"], [(f"`segment.{n}`", t["value"], t["use"]) for n, t in tokens["segment"].items()]) + +add("## Ruimte\n") +table(["Token", "Waarde"], [(f"`space.{n}`", f"{v} pt") for n, v in tokens["space"].items()]) +add("## Indeling\n") +table(["Token", "Waarde", "Gebruik"], [(f"`layout.{n}`", t["value"], t["use"]) for n, t in tokens["layout"].items()]) +add("## Afmetingen\n") +table(["Token", "Waarde", "Gebruik"], [(f"`size.{n}`", t["value"], t["use"]) for n, t in tokens["size"].items()]) +add("## Hoekradius\n") +table(["Token", "Waarde", "Gebruik"], [(f"`radius.{n}`", t["value"], t["use"]) for n, t in tokens["radius"].items()]) +add("## Randen\n") +table(["Token", "Waarde", "Gebruik"], [(f"`border.{n}`", f"{t['value']} pt", t["use"]) for n, t in tokens["border"].items()]) + +add("## Schaduw en diepte\n") +for n, t in tokens["shadow"].items(): + add(f"### `shadow.{n}`\n\n{t['use']}" + (f" Dop verschuift {t['translateY']} pt omlaag." if "translateY" in t else "") + "\n") + table(["Laag", "Inset", "x", "y", "blur", "spread", "kleur", "alpha aluminium", "alpha graphite"], + [(i + 1, "ja" if l["inset"] else "nee", l["x"], l["y"], l["blur"], l["spread"], f"`{l['color']}`", + l["alpha"]["light"], l["alpha"]["dark"]) for i, l in enumerate(t["layers"])]) +add("## Textuur\n") +for n, t in tokens["texture"].items(): + add(f"**`texture.{n}`** — {t['use']}. Richting {t['direction']}; korrel {t['grain']['light']} (aluminium) / {t['grain']['dark']} (graphite); {t['gradient']}.\n") +add("## Meters\n") +table(["Token", "Waarde", "Gebruik"], [(f"`meter.{n}`", t["value"], t["use"]) for n, t in tokens["meter"].items()]) +add("## Beweging\n") +table(["Token", "Waarde", "Eenheid", "Gebruik"], + [(f"`motion.{n}`", t["value"], t.get("unit", ""), t["use"]) for n, t in tokens["motion"].items()]) + +add("## Wat bewust ontbreekt\n") +add("Geen neon, vaporwave, synthwave, paars-roze verlopen, gloeiende tekst, chromen letters of rasterhorizonten. Lampjes mogen een kleine gloed hebben (`shadow.lamp.on`); tekst nooit.\n") + +out_dir = ROOT / "docs" / "design" +out_dir.mkdir(parents=True, exist_ok=True) +(out_dir / "tokens.md").write_text("\n".join(md)) + +# --- preview ---------------------------------------------------------------- +template = (ROOT / "Design" / "preview.template.html").read_text() +blob = json.dumps(tokens, ensure_ascii=False).replace(" + + + + +Sst Ontwerp-tokens + + + + + +
+ +
+
+
Ontwerpsysteem · Sst
+

Ontwerp-tokens

+

+
+
+
Ter goedkeuring
+
+
Bron · Design/tokens.json
+
+
+ +
+
01Instrumenten in gebruikmeter.* · motion.needle.* · segment.*
+
+
Afwerking · Aluminiumlicht
+
Afwerking · Graphitedonker
+
+
+ +
+
02Kleurcolor.*
+
+
+ +
+
03Diepte en materiaalshadow.* · border.* · texture.*
+
+
+
+
+
+ +
+
04Typografiefont.* · type.*
+
+
+ +
+
05Ruimte, hoeken, randenspace.* · radius.* · border.*
+
+
Ruimte (pt)
+
+
Hoekradius
+
+
+ +
+
06Bewegingmotion.*
+
+
+
Toets en lampje
+
+

Toets: 50 ms in, 90 ms uit, zonder veer. Lampje: 30 ms aan, 200 ms uit — dooft als een gloeidraad.

+
+
+
Wijzer-ballistiek
+
+

Veer: 300 ms tot 99 %, demping 0,80 → ongeveer 1,5 % overschot. De meters bij 01 gebruiken dezelfde waarden.

+
+
+
+ +
+
07Wat het niet is
+
    +
  • Geen neon, vaporwave, synthwave, paars-roze verlopen, gloeiende tekst, chromen letters of rasterhorizonten.
  • +
  • Eén accent: het rode opnamelampje. Amber en groen alleen als niveau-indicator of markering, nooit als versiering.
  • +
  • Lampjes mogen een kleine gloed hebben. Tekst nooit.
  • +
  • Geen eenmalige waarden in componenten: elke afmeting, kleur en tijd komt uit deze tokens.
  • +
+
+
+ + + + diff --git a/Design/tokens.json b/Design/tokens.json new file mode 100644 index 0000000..0563c1d --- /dev/null +++ b/Design/tokens.json @@ -0,0 +1,204 @@ +{ + "meta": { + "name": "Sst Design Tokens", + "version": "0.1.0-draft", + "status": "Ter goedkeuring — nog niet gebruikt door de app", + "concept": "Draagbare veldrecorder / cassettedeck uit de jaren 80 (Sony TC-D5, Marantz PMD, Nakamichi, Braun). Twee afwerkingen: ALUMINIUM (licht) en GRAPHITE (donker). Eén accent: het rode opnamelampje. Amber en groen alleen voor niveau-indicatoren." + }, + + "color": { + "surface.chassis": { "light": "#CBC8C0", "dark": "#1C1B19", "use": "Venster-achtergrond; het 'huis' van het apparaat" }, + "surface.metalHigh": { "light": "#E3E1DB", "dark": "#3B3A36", "use": "Geborsteld metaal, bovenkant van de verloop" }, + "surface.metalLow": { "light": "#BDBAB2", "dark": "#262522", "use": "Geborsteld metaal, onderkant van de verloop" }, + "surface.panel": { "light": "#E2DFD5", "dark": "#2B2A27", "use": "Frontplaat van mat plastic; zijbalk en bedieningspanelen" }, + "surface.well": { "light": "#B4B1A8", "dark": "#131211", "use": "Verzonken vlak: invoervelden, display-vensters" }, + "surface.paper": { "light": "#EEE9D9", "dark": "#232220", "use": "Cassette-label crème: lijsten met transcripties en woordenboek" }, + "surface.keyHigh": { "light": "#E6E3DA", "dark": "#44433F", "use": "Toetsdop, bovenkant" }, + "surface.keyLow": { "light": "#CBC8BF", "dark": "#33322F", "use": "Toetsdop, onderkant" }, + "surface.keyPressed": { "light": "#BEBBB2", "dark": "#2A2927", "use": "Ingedrukte toets" }, + "surface.meterFace": { "light": "#EFE6C8", "dark": "#D8CEA6", "use": "Wijzerplaat van de VU-meter (blijft crème in beide afwerkingen)" }, + "surface.selection": { "light": "#D4CDB2", "dark": "#3A3828", "use": "Geselecteerde rij in een lijst" }, + "surface.display": { "light": "#1B1A18", "dark": "#0E0D0C", "use": "Displayglas van de teller; donker in beide afwerkingen" }, + "display.segmentUnlit": { "light": "#3A3320", "dark": "#2A2415", "use": "Gedoofd segment op het displayglas" }, + + "ink.primary": { "light": "#1E1D1B", "dark": "#ECE8DC", "use": "Hoofdtekst en wijzer" }, + "ink.secondary": { "light": "#403D38", "dark": "#C4BFB2", "use": "Secundaire tekst" }, + "ink.tertiary": { "light": "#4D4A44", "dark": "#ABA79B", "use": "Zeefdruk-labels en hulptekst" }, + "ink.disabled": { "light": "#8F8B82", "dark": "#6B685F", "use": "Uitgeschakeld; nooit voor essentiële tekst" }, + "ink.inverse": { "light": "#F4EFE3", "dark": "#F4EFE3", "use": "Tekst op rode of donkere vulling; crème in beide afwerkingen" }, + "ink.onMeter": { "light": "#1E1D1B", "dark": "#1E1D1B", "use": "Schaal, cijfers en wijzer op de wijzerplaat; vast donker in beide afwerkingen" }, + + "accent.record": { "light": "#C8281E", "dark": "#CC2A20", "use": "ENIGE accent: opnamelampje, REC-toets, piek-LED. Nooit voor lopende tekst" }, + "accent.recordUnlit": { "light": "#7A2E28", "dark": "#4A1F1B", "use": "Lampje uit" }, + + "level.green": { "light": "#5E9E48", "dark": "#6DB556", "use": "Niveau-segment, brandend" }, + "level.amber": { "light": "#D9A023", "dark": "#E5B032", "use": "Niveau-segment, waarschuwing, gecorrigeerd woord" }, + "level.greenUnlit": { "light": "#A8B79C", "dark": "#26331F", "use": "Groen segment, uit" }, + "level.amberUnlit": { "light": "#CDBA8A", "dark": "#3D3418", "use": "Amber segment, uit" }, + + "line.seamDark": { "light": "#8C8981", "dark": "#0A0A09", "use": "Naad tussen panelen, donkere helft" }, + "line.seamLight": { "light": "#F3F1EB", "dark": "#45433F", "use": "Naad tussen panelen, lichte helft" }, + "line.hairline": { "light": "#A9A69D", "dark": "#3A3834", "use": "Fijne scheidingslijn, zeefdruk-kader" }, + "line.focus": { "light": "#1E1D1B", "dark": "#ECE8DC", "use": "Toetsenbord-focus" } + }, + + "opacity": { + "mark": { "value": 0.30, "use": "Amber markering achter een gecorrigeerd woord" }, + "disabled": { "value": 0.45, "use": "Uitgeschakelde bediening" }, + "unlit": { "value": 1.00, "use": "Gedoofde lampjes gebruiken eigen 'unlit'-kleuren, geen transparantie" } + }, + + "font": { + "family.chrome": { "value": "Helvetica Neue", "fallback": "Helvetica, Arial", "use": "Zeefdruk, toetsen, koppen: neutrale grotesk" }, + "family.text": { "value": "SF Pro Text", "fallback": "system-ui", "use": "Leesbare tekst: transcripties, woordenboek" }, + "family.mono": { "value": "SF Mono", "fallback": "Menlo, monospace", "use": "Teller, tijden, woordenboekbestand" } + }, + + "type": { + "label": { "family": "chrome", "size": 10, "weight": 500, "tracking": 0.09, "line": 12, "caps": true, "use": "Zeefdruk-label" }, + "labelSmall": { "family": "chrome", "size": 9, "weight": 500, "tracking": 0.10, "line": 11, "caps": true, "use": "Schaalmarkeringen en eenheden" }, + "control": { "family": "chrome", "size": 11, "weight": 500, "tracking": 0.06, "line": 14, "caps": true, "use": "Opschrift op toetsen" }, + "heading": { "family": "chrome", "size": 12, "weight": 500, "tracking": 0.12, "line": 16, "caps": true, "use": "Paneelkop" }, + "body": { "family": "text", "size": 13, "weight": 400, "tracking": 0, "line": 18, "caps": false, "use": "Standaardtekst" }, + "reading": { "family": "text", "size": 15, "weight": 400, "tracking": 0, "line": 22, "caps": false, "use": "Transcript" }, + "meta": { "family": "text", "size": 11, "weight": 400, "tracking": 0, "line": 14, "caps": false, "use": "Bijschriften" }, + "counter": { "family": "mono", "size": 22, "weight": 500, "tracking": 0, "line": 26, "caps": false, "tabular": true, "use": "Teller" }, + "counterSmall": { "family": "mono", "size": 12, "weight": 500, "tracking": 0, "line": 16, "caps": false, "tabular": true, "use": "Tijdstip in een rij" }, + "code": { "family": "mono", "size": 12, "weight": 400, "tracking": 0, "line": 18, "caps": false, "use": "Woordenboekregels" } + }, + + "segment": { + "digitHeight": { "value": 36, "use": "Hoogte van een getekend segmentcijfer" }, + "digitWidth": { "value": 20, "use": "Breedte van een segmentcijfer" }, + "thickness": { "value": 4, "use": "Dikte van één segment" }, + "gap": { "value": 1, "use": "Ruimte tussen segmenten" }, + "slant": { "value": 0, "use": "Schuinte in graden; 0 = rechtop" } + }, + + "space": { + "0": 0, "1": 2, "2": 4, "3": 8, "4": 12, "5": 16, "6": 24, "7": 32, "8": 48 + }, + + "layout": { + "window.minWidth": { "value": 800, "use": "Minimale vensterbreedte" }, + "window.minHeight": { "value": 560, "use": "Minimale vensterhoogte" }, + "window.defaultWidth": { "value": 1080, "use": "Standaard vensterbreedte" }, + "window.defaultHeight":{ "value": 700, "use": "Standaard vensterhoogte" }, + "sidebar.width": { "value": 232, "use": "Breedte zijbalk" }, + "settings.width": { "value": 560, "use": "Breedte instellingenvenster" }, + "panel.padding": { "value": 16, "use": "Binnenmarge van een paneel (= space.5)" }, + "panel.gap": { "value": 12, "use": "Afstand tussen panelen (= space.4)" } + }, + + "size": { + "control.height": { "value": 28, "use": "Standaardhoogte van toets of veld" }, + "key.minWidth": { "value": 56, "use": "Minimale breedte van een toets" }, + "key.recWidth": { "value": 64, "use": "REC-toets, breedte" }, + "key.recHeight": { "value": 44, "use": "REC-toets, hoogte" }, + "lamp.diameter": { "value": 10, "use": "Standaard lampje" }, + "lamp.large": { "value": 14, "use": "Opnamelampje" }, + "switch.width": { "value": 40, "use": "Tuimelschakelaar, breedte" }, + "switch.height": { "value": 22, "use": "Tuimelschakelaar, hoogte" }, + "row.height": { "value": 40, "use": "Rij in transcriptielijst" }, + "icon.small": { "value": 16, "use": "Klein pictogram" }, + "icon.medium": { "value": 20, "use": "Normaal pictogram" }, + "hit.min": { "value": 24, "use": "Minimaal klikdoel" } + }, + + "radius": { + "none": { "value": 0, "use": "Panelen en velden: scherpe randen" }, + "sharp": { "value": 1, "use": "Toetsranden: bijna scherp" }, + "sm": { "value": 2, "use": "Toetsen, velden" }, + "md": { "value": 4, "use": "Grote panelen" }, + "bezel": { "value": 6, "use": "Rand van de VU-meter" }, + "lamp": { "value": 999, "use": "Lampjes: rond" } + }, + + "border": { + "hairline": { "value": 1, "use": "Fijne lijn" }, + "seam": { "value": 2, "use": "Naad: 1 donkere lijn boven, 1 lichte lijn eronder, leest als gegraveerde groef" }, + "bezel": { "value": 3, "use": "Rand om de VU-meter" }, + "focus": { "value": 2, "use": "Focusring, 2 pt offset" } + }, + + "shadow": { + "key.raised": { + "use": "Toets in rust: lichtvang boven, donkere onderrand, valschaduw", + "layers": [ + { "inset": true, "x": 0, "y": 1, "blur": 0, "spread": 0, "color": "#FFFFFF", "alpha": { "light": 0.65, "dark": 0.10 } }, + { "inset": true, "x": 0, "y": -1, "blur": 0, "spread": 0, "color": "#000000", "alpha": { "light": 0.25, "dark": 0.55 } }, + { "inset": false, "x": 0, "y": 2, "blur": 2, "spread": 0, "color": "#000000", "alpha": { "light": 0.35, "dark": 0.60 } } + ] + }, + "key.pressed": { + "use": "Ingedrukte toets: dop 1 pt omlaag, geen valschaduw", + "layers": [ + { "inset": true, "x": 0, "y": 2, "blur": 4, "spread": 0, "color": "#000000", "alpha": { "light": 0.50, "dark": 0.70 } }, + { "inset": true, "x": 0, "y": 0, "blur": 0, "spread": 1, "color": "#000000", "alpha": { "light": 0.35, "dark": 0.60 } } + ], + "translateY": 1 + }, + "well": { + "use": "Verzonken vlak met lichtvang op de onderlip", + "layers": [ + { "inset": true, "x": 0, "y": 1, "blur": 3, "spread": 0, "color": "#000000", "alpha": { "light": 0.40, "dark": 0.70 } }, + { "inset": false, "x": 0, "y": 1, "blur": 0, "spread": 0, "color": "#FFFFFF", "alpha": { "light": 0.55, "dark": 0.07 } } + ] + }, + "panel": { + "use": "Frontplaat boven het chassis", + "layers": [ + { "inset": true, "x": 0, "y": 1, "blur": 0, "spread": 0, "color": "#FFFFFF", "alpha": { "light": 0.60, "dark": 0.08 } }, + { "inset": false, "x": 0, "y": 3, "blur": 8, "spread": 0, "color": "#000000", "alpha": { "light": 0.22, "dark": 0.55 } } + ] + }, + "lamp.on": { + "use": "Brandend lampje: kleine gloed, alleen voor lampjes en nooit voor tekst", + "layers": [ + { "inset": false, "x": 0, "y": 0, "blur": 6, "spread": 1, "color": "#CC2A20", "alpha": { "light": 0.45, "dark": 0.55 } }, + { "inset": true, "x": 0, "y": -1, "blur": 1, "spread": 0, "color": "#000000", "alpha": { "light": 0.35, "dark": 0.45 } } + ] + } + }, + + "texture": { + "brushed": { + "use": "Geborsteld aluminium voor chassis en zijbalk", + "direction": "horizontaal", + "grain": { "light": 0.035, "dark": 0.05 }, + "gradient": "surface.metalHigh naar surface.metalLow, van boven naar beneden" + } + }, + + "meter": { + "vu.referenceDbfs": { "value": -20, "use": "0 VU = -20 dBFS (RMS)" }, + "vu.min": { "value": -20, "use": "Onderkant van de schaal in VU" }, + "vu.max": { "value": 3, "use": "Bovenkant van de schaal in VU; vanaf 0 VU rood" }, + "vu.arcDegrees": { "value": 90, "use": "Hoek tussen minimale en maximale stand" }, + "vu.width": { "value": 260, "use": "Breedte van de meter" }, + "vu.height": { "value": 150, "use": "Hoogte van de meter" }, + "vu.needleWidth": { "value": 1.5, "use": "Dikte van de wijzer" }, + "vu.ticks": { "value": "-20 -10 -7 -5 -3 0 +3", "use": "Genummerde schaalmarkeringen" }, + "led.segments": { "value": 12, "use": "Aantal segmenten: 8 groen, 3 amber, 1 rood" }, + "led.segmentSize": { "value": "6x14", "use": "Segment in pt (breedte x hoogte)" }, + "led.gap": { "value": 2, "use": "Ruimte tussen segmenten" }, + "peak.holdMs": { "value": 1000, "use": "Piek-LED blijft zo lang branden" } + }, + + "motion": { + "duration.instant": { "value": 0, "unit": "ms", "use": "Geen overgang" }, + "duration.fast": { "value": 60, "unit": "ms", "use": "Toets in/uit" }, + "duration.base": { "value": 120, "unit": "ms", "use": "Standaardovergang" }, + "duration.slow": { "value": 220, "unit": "ms", "use": "Paneel of venster" }, + "easing.mechanical":{ "value": "cubic-bezier(0.2, 0, 0, 1)", "use": "Toetsen: direct, zonder veer" }, + "easing.standard": { "value": "cubic-bezier(0.4, 0, 0.2, 1)", "use": "Overige overgangen" }, + "key.press": { "value": 50, "unit": "ms", "use": "Toets indrukken" }, + "key.release": { "value": 90, "unit": "ms", "use": "Toets loslaten" }, + "lamp.on": { "value": 30, "unit": "ms", "use": "Lampje aan" }, + "lamp.off": { "value": 200, "unit": "ms", "use": "Lampje uit: dooft langzaam, als een gloeidraad" }, + "needle.response": { "value": 0.30, "unit": "s", "use": "VU-ballistiek: 300 ms om de stand te bereiken" }, + "needle.damping": { "value": 0.80, "unit": "ratio", "use": "Veerdemping; geeft ongeveer 1,5 % overschot" }, + "needle.updateHz": { "value": 60, "unit": "Hz", "use": "Verversing van de wijzer" }, + "reducedMotion": { "value": "Overgangen 0 ms; wijzer blijft bewegen maar met demping 1.0 (geen overschot)", "use": "Systeeminstelling 'Verminder beweging'" } + } +} diff --git a/docs/design/tokens-preview.html b/docs/design/tokens-preview.html new file mode 100644 index 0000000..de04656 --- /dev/null +++ b/docs/design/tokens-preview.html @@ -0,0 +1,380 @@ + + + + + +Sst Ontwerp-tokens + + + + + +
+ +
+
+
Ontwerpsysteem · Sst
+

Ontwerp-tokens

+

+
+
+
Ter goedkeuring
+
+
Bron · Design/tokens.json
+
+
+ +
+
01Instrumenten in gebruikmeter.* · motion.needle.* · segment.*
+
+
Afwerking · Aluminiumlicht
+
Afwerking · Graphitedonker
+
+
+ +
+
02Kleurcolor.*
+
+
+ +
+
03Diepte en materiaalshadow.* · border.* · texture.*
+
+
+
+
+
+ +
+
04Typografiefont.* · type.*
+
+
+ +
+
05Ruimte, hoeken, randenspace.* · radius.* · border.*
+
+
Ruimte (pt)
+
+
Hoekradius
+
+
+ +
+
06Bewegingmotion.*
+
+
+
Toets en lampje
+
+

Toets: 50 ms in, 90 ms uit, zonder veer. Lampje: 30 ms aan, 200 ms uit — dooft als een gloeidraad.

+
+
+
Wijzer-ballistiek
+
+

Veer: 300 ms tot 99 %, demping 0,80 → ongeveer 1,5 % overschot. De meters bij 01 gebruiken dezelfde waarden.

+
+
+
+ +
+
07Wat het niet is
+
    +
  • Geen neon, vaporwave, synthwave, paars-roze verlopen, gloeiende tekst, chromen letters of rasterhorizonten.
  • +
  • Eén accent: het rode opnamelampje. Amber en groen alleen als niveau-indicator of markering, nooit als versiering.
  • +
  • Lampjes mogen een kleine gloed hebben. Tekst nooit.
  • +
  • Geen eenmalige waarden in componenten: elke afmeting, kleur en tijd komt uit deze tokens.
  • +
+
+
+ + + + diff --git a/docs/design/tokens.md b/docs/design/tokens.md new file mode 100644 index 0000000..0a93212 --- /dev/null +++ b/docs/design/tokens.md @@ -0,0 +1,283 @@ +# Sst Design Tokens — v0.1.0-draft + +**Status:** Ter goedkeuring — nog niet gebruikt door de app + +Draagbare veldrecorder / cassettedeck uit de jaren 80 (Sony TC-D5, Marantz PMD, Nakamichi, Braun). Twee afwerkingen: ALUMINIUM (licht) en GRAPHITE (donker). Eén accent: het rode opnamelampje. Amber en groen alleen voor niveau-indicatoren. + +Bron: `Design/tokens.json`. Dit bestand en `tokens-preview.html` worden gegenereerd met `python3 Design/build_tokens.py`; pas nooit alleen deze bestanden aan. + +## Kleur + +Verwijs ernaar als `color.`. Kolommen: ALUMINIUM (licht) en GRAPHITE (donker). + +| Token | Aluminium | Graphite | Gebruik | +|---|---|---|---| +| `color.surface.chassis` | `#CBC8C0` | `#1C1B19` | Venster-achtergrond; het 'huis' van het apparaat | +| `color.surface.metalHigh` | `#E3E1DB` | `#3B3A36` | Geborsteld metaal, bovenkant van de verloop | +| `color.surface.metalLow` | `#BDBAB2` | `#262522` | Geborsteld metaal, onderkant van de verloop | +| `color.surface.panel` | `#E2DFD5` | `#2B2A27` | Frontplaat van mat plastic; zijbalk en bedieningspanelen | +| `color.surface.well` | `#B4B1A8` | `#131211` | Verzonken vlak: invoervelden, display-vensters | +| `color.surface.paper` | `#EEE9D9` | `#232220` | Cassette-label crème: lijsten met transcripties en woordenboek | +| `color.surface.keyHigh` | `#E6E3DA` | `#44433F` | Toetsdop, bovenkant | +| `color.surface.keyLow` | `#CBC8BF` | `#33322F` | Toetsdop, onderkant | +| `color.surface.keyPressed` | `#BEBBB2` | `#2A2927` | Ingedrukte toets | +| `color.surface.meterFace` | `#EFE6C8` | `#D8CEA6` | Wijzerplaat van de VU-meter (blijft crème in beide afwerkingen) | +| `color.surface.selection` | `#D4CDB2` | `#3A3828` | Geselecteerde rij in een lijst | +| `color.surface.display` | `#1B1A18` | `#0E0D0C` | Displayglas van de teller; donker in beide afwerkingen | +| `color.display.segmentUnlit` | `#3A3320` | `#2A2415` | Gedoofd segment op het displayglas | +| `color.ink.primary` | `#1E1D1B` | `#ECE8DC` | Hoofdtekst en wijzer | +| `color.ink.secondary` | `#403D38` | `#C4BFB2` | Secundaire tekst | +| `color.ink.tertiary` | `#4D4A44` | `#ABA79B` | Zeefdruk-labels en hulptekst | +| `color.ink.disabled` | `#8F8B82` | `#6B685F` | Uitgeschakeld; nooit voor essentiële tekst | +| `color.ink.inverse` | `#F4EFE3` | `#F4EFE3` | Tekst op rode of donkere vulling; crème in beide afwerkingen | +| `color.ink.onMeter` | `#1E1D1B` | `#1E1D1B` | Schaal, cijfers en wijzer op de wijzerplaat; vast donker in beide afwerkingen | +| `color.accent.record` | `#C8281E` | `#CC2A20` | ENIGE accent: opnamelampje, REC-toets, piek-LED. Nooit voor lopende tekst | +| `color.accent.recordUnlit` | `#7A2E28` | `#4A1F1B` | Lampje uit | +| `color.level.green` | `#5E9E48` | `#6DB556` | Niveau-segment, brandend | +| `color.level.amber` | `#D9A023` | `#E5B032` | Niveau-segment, waarschuwing, gecorrigeerd woord | +| `color.level.greenUnlit` | `#A8B79C` | `#26331F` | Groen segment, uit | +| `color.level.amberUnlit` | `#CDBA8A` | `#3D3418` | Amber segment, uit | +| `color.line.seamDark` | `#8C8981` | `#0A0A09` | Naad tussen panelen, donkere helft | +| `color.line.seamLight` | `#F3F1EB` | `#45433F` | Naad tussen panelen, lichte helft | +| `color.line.hairline` | `#A9A69D` | `#3A3834` | Fijne scheidingslijn, zeefdruk-kader | +| `color.line.focus` | `#1E1D1B` | `#ECE8DC` | Toetsenbord-focus | + +**Regel:** `accent.record` is het enige accent en wordt nooit voor lopende tekst gebruikt. Amber en groen zijn alleen voor niveau-indicatoren en markeringen. + +### Contrast (berekend) + +| Tekst | Op | Afwerking | Verhouding | | +|---|---|---|---|---| +| `ink.primary` | `surface.chassis` | light | 10.08:1 | ok | +| `ink.primary` | `surface.chassis` | dark | 14.05:1 | ok | +| `ink.primary` | `surface.panel` | light | 12.63:1 | ok | +| `ink.primary` | `surface.panel` | dark | 11.72:1 | ok | +| `ink.primary` | `surface.paper` | light | 13.87:1 | ok | +| `ink.primary` | `surface.paper` | dark | 12.98:1 | ok | +| `ink.primary` | `surface.well` | light | 7.86:1 | ok | +| `ink.primary` | `surface.well` | dark | 15.27:1 | ok | +| `ink.primary` | `surface.selection` | light | 10.57:1 | ok | +| `ink.primary` | `surface.selection` | dark | 9.65:1 | ok | +| `ink.onMeter` | `surface.meterFace` | light | 13.49:1 | ok | +| `ink.onMeter` | `surface.meterFace` | dark | 10.67:1 | ok | +| `ink.primary` | `surface.keyHigh` | light | 13.13:1 | ok | +| `ink.primary` | `surface.keyHigh` | dark | 8.08:1 | ok | +| `ink.primary` | `surface.keyLow` | light | 10.07:1 | ok | +| `ink.primary` | `surface.keyLow` | dark | 10.47:1 | ok | +| `ink.secondary` | `surface.chassis` | light | 6.47:1 | ok | +| `ink.secondary` | `surface.chassis` | dark | 9.38:1 | ok | +| `ink.secondary` | `surface.panel` | light | 8.11:1 | ok | +| `ink.secondary` | `surface.panel` | dark | 7.82:1 | ok | +| `ink.secondary` | `surface.paper` | light | 8.90:1 | ok | +| `ink.secondary` | `surface.paper` | dark | 8.66:1 | ok | +| `ink.tertiary` | `surface.chassis` | light | 5.28:1 | ok | +| `ink.tertiary` | `surface.chassis` | dark | 7.16:1 | ok | +| `ink.tertiary` | `surface.panel` | light | 6.62:1 | ok | +| `ink.tertiary` | `surface.panel` | dark | 5.97:1 | ok | +| `ink.tertiary` | `surface.paper` | light | 7.27:1 | ok | +| `ink.tertiary` | `surface.paper` | dark | 6.61:1 | ok | +| `ink.tertiary` | `surface.metalHigh` | light | 6.75:1 | ok | +| `ink.tertiary` | `surface.metalHigh` | dark | 4.73:1 | ok | +| `ink.tertiary` | `surface.metalLow` | light | 4.55:1 | ok | +| `ink.tertiary` | `surface.metalLow` | dark | 6.37:1 | ok | +| `ink.inverse` | `accent.record` | light | 4.84:1 | ok | +| `ink.inverse` | `accent.record` | dark | 4.67:1 | ok | +| `level.amber` | `surface.display` | light | 7.46:1 | ok | +| `level.amber` | `surface.display` | dark | 9.79:1 | ok | + +## Opaciteit + +| Token | Waarde | Gebruik | +|---|---|---| +| `opacity.mark` | 0.3 | Amber markering achter een gecorrigeerd woord | +| `opacity.disabled` | 0.45 | Uitgeschakelde bediening | +| `opacity.unlit` | 1.0 | Gedoofde lampjes gebruiken eigen 'unlit'-kleuren, geen transparantie | + +## Lettertypen + +| Token | Familie | Terugval | Gebruik | +|---|---|---|---| +| `font.family.chrome` | Helvetica Neue | Helvetica, Arial | Zeefdruk, toetsen, koppen: neutrale grotesk | +| `font.family.text` | SF Pro Text | system-ui | Leesbare tekst: transcripties, woordenboek | +| `font.family.mono` | SF Mono | Menlo, monospace | Teller, tijden, woordenboekbestand | + +## Tekststijlen + +| Token | Familie | Grootte | Gewicht | Spatiëring | Regelhoogte | Hoofdletters | Gebruik | +|---|---|---|---|---|---|---|---| +| `type.label` | chrome | 10 pt | 500 | 0.09 em | 12 pt | ja | Zeefdruk-label | +| `type.labelSmall` | chrome | 9 pt | 500 | 0.1 em | 11 pt | ja | Schaalmarkeringen en eenheden | +| `type.control` | chrome | 11 pt | 500 | 0.06 em | 14 pt | ja | Opschrift op toetsen | +| `type.heading` | chrome | 12 pt | 500 | 0.12 em | 16 pt | ja | Paneelkop | +| `type.body` | text | 13 pt | 400 | 0 em | 18 pt | nee | Standaardtekst | +| `type.reading` | text | 15 pt | 400 | 0 em | 22 pt | nee | Transcript | +| `type.meta` | text | 11 pt | 400 | 0 em | 14 pt | nee | Bijschriften | +| `type.counter` | mono | 22 pt | 500 | 0 em | 26 pt | nee | Teller | +| `type.counterSmall` | mono | 12 pt | 500 | 0 em | 16 pt | nee | Tijdstip in een rij | +| `type.code` | mono | 12 pt | 400 | 0 em | 18 pt | nee | Woordenboekregels | + +## Segmentcijfers (getekend, geen lettertype) + +| Token | Waarde | Gebruik | +|---|---|---| +| `segment.digitHeight` | 36 | Hoogte van een getekend segmentcijfer | +| `segment.digitWidth` | 20 | Breedte van een segmentcijfer | +| `segment.thickness` | 4 | Dikte van één segment | +| `segment.gap` | 1 | Ruimte tussen segmenten | +| `segment.slant` | 0 | Schuinte in graden; 0 = rechtop | + +## Ruimte + +| Token | Waarde | +|---|---| +| `space.0` | 0 pt | +| `space.1` | 2 pt | +| `space.2` | 4 pt | +| `space.3` | 8 pt | +| `space.4` | 12 pt | +| `space.5` | 16 pt | +| `space.6` | 24 pt | +| `space.7` | 32 pt | +| `space.8` | 48 pt | + +## Indeling + +| Token | Waarde | Gebruik | +|---|---|---| +| `layout.window.minWidth` | 800 | Minimale vensterbreedte | +| `layout.window.minHeight` | 560 | Minimale vensterhoogte | +| `layout.window.defaultWidth` | 1080 | Standaard vensterbreedte | +| `layout.window.defaultHeight` | 700 | Standaard vensterhoogte | +| `layout.sidebar.width` | 232 | Breedte zijbalk | +| `layout.settings.width` | 560 | Breedte instellingenvenster | +| `layout.panel.padding` | 16 | Binnenmarge van een paneel (= space.5) | +| `layout.panel.gap` | 12 | Afstand tussen panelen (= space.4) | + +## Afmetingen + +| Token | Waarde | Gebruik | +|---|---|---| +| `size.control.height` | 28 | Standaardhoogte van toets of veld | +| `size.key.minWidth` | 56 | Minimale breedte van een toets | +| `size.key.recWidth` | 64 | REC-toets, breedte | +| `size.key.recHeight` | 44 | REC-toets, hoogte | +| `size.lamp.diameter` | 10 | Standaard lampje | +| `size.lamp.large` | 14 | Opnamelampje | +| `size.switch.width` | 40 | Tuimelschakelaar, breedte | +| `size.switch.height` | 22 | Tuimelschakelaar, hoogte | +| `size.row.height` | 40 | Rij in transcriptielijst | +| `size.icon.small` | 16 | Klein pictogram | +| `size.icon.medium` | 20 | Normaal pictogram | +| `size.hit.min` | 24 | Minimaal klikdoel | + +## Hoekradius + +| Token | Waarde | Gebruik | +|---|---|---| +| `radius.none` | 0 | Panelen en velden: scherpe randen | +| `radius.sharp` | 1 | Toetsranden: bijna scherp | +| `radius.sm` | 2 | Toetsen, velden | +| `radius.md` | 4 | Grote panelen | +| `radius.bezel` | 6 | Rand van de VU-meter | +| `radius.lamp` | 999 | Lampjes: rond | + +## Randen + +| Token | Waarde | Gebruik | +|---|---|---| +| `border.hairline` | 1 pt | Fijne lijn | +| `border.seam` | 2 pt | Naad: 1 donkere lijn boven, 1 lichte lijn eronder, leest als gegraveerde groef | +| `border.bezel` | 3 pt | Rand om de VU-meter | +| `border.focus` | 2 pt | Focusring, 2 pt offset | + +## Schaduw en diepte + +### `shadow.key.raised` + +Toets in rust: lichtvang boven, donkere onderrand, valschaduw + +| Laag | Inset | x | y | blur | spread | kleur | alpha aluminium | alpha graphite | +|---|---|---|---|---|---|---|---|---| +| 1 | ja | 0 | 1 | 0 | 0 | `#FFFFFF` | 0.65 | 0.1 | +| 2 | ja | 0 | -1 | 0 | 0 | `#000000` | 0.25 | 0.55 | +| 3 | nee | 0 | 2 | 2 | 0 | `#000000` | 0.35 | 0.6 | + +### `shadow.key.pressed` + +Ingedrukte toets: dop 1 pt omlaag, geen valschaduw Dop verschuift 1 pt omlaag. + +| Laag | Inset | x | y | blur | spread | kleur | alpha aluminium | alpha graphite | +|---|---|---|---|---|---|---|---|---| +| 1 | ja | 0 | 2 | 4 | 0 | `#000000` | 0.5 | 0.7 | +| 2 | ja | 0 | 0 | 0 | 1 | `#000000` | 0.35 | 0.6 | + +### `shadow.well` + +Verzonken vlak met lichtvang op de onderlip + +| Laag | Inset | x | y | blur | spread | kleur | alpha aluminium | alpha graphite | +|---|---|---|---|---|---|---|---|---| +| 1 | ja | 0 | 1 | 3 | 0 | `#000000` | 0.4 | 0.7 | +| 2 | nee | 0 | 1 | 0 | 0 | `#FFFFFF` | 0.55 | 0.07 | + +### `shadow.panel` + +Frontplaat boven het chassis + +| Laag | Inset | x | y | blur | spread | kleur | alpha aluminium | alpha graphite | +|---|---|---|---|---|---|---|---|---| +| 1 | ja | 0 | 1 | 0 | 0 | `#FFFFFF` | 0.6 | 0.08 | +| 2 | nee | 0 | 3 | 8 | 0 | `#000000` | 0.22 | 0.55 | + +### `shadow.lamp.on` + +Brandend lampje: kleine gloed, alleen voor lampjes en nooit voor tekst + +| Laag | Inset | x | y | blur | spread | kleur | alpha aluminium | alpha graphite | +|---|---|---|---|---|---|---|---|---| +| 1 | nee | 0 | 0 | 6 | 1 | `#CC2A20` | 0.45 | 0.55 | +| 2 | ja | 0 | -1 | 1 | 0 | `#000000` | 0.35 | 0.45 | + +## Textuur + +**`texture.brushed`** — Geborsteld aluminium voor chassis en zijbalk. Richting horizontaal; korrel 0.035 (aluminium) / 0.05 (graphite); surface.metalHigh naar surface.metalLow, van boven naar beneden. + +## Meters + +| Token | Waarde | Gebruik | +|---|---|---| +| `meter.vu.referenceDbfs` | -20 | 0 VU = -20 dBFS (RMS) | +| `meter.vu.min` | -20 | Onderkant van de schaal in VU | +| `meter.vu.max` | 3 | Bovenkant van de schaal in VU; vanaf 0 VU rood | +| `meter.vu.arcDegrees` | 90 | Hoek tussen minimale en maximale stand | +| `meter.vu.width` | 260 | Breedte van de meter | +| `meter.vu.height` | 150 | Hoogte van de meter | +| `meter.vu.needleWidth` | 1.5 | Dikte van de wijzer | +| `meter.vu.ticks` | -20 -10 -7 -5 -3 0 +3 | Genummerde schaalmarkeringen | +| `meter.led.segments` | 12 | Aantal segmenten: 8 groen, 3 amber, 1 rood | +| `meter.led.segmentSize` | 6x14 | Segment in pt (breedte x hoogte) | +| `meter.led.gap` | 2 | Ruimte tussen segmenten | +| `meter.peak.holdMs` | 1000 | Piek-LED blijft zo lang branden | + +## Beweging + +| Token | Waarde | Eenheid | Gebruik | +|---|---|---|---| +| `motion.duration.instant` | 0 | ms | Geen overgang | +| `motion.duration.fast` | 60 | ms | Toets in/uit | +| `motion.duration.base` | 120 | ms | Standaardovergang | +| `motion.duration.slow` | 220 | ms | Paneel of venster | +| `motion.easing.mechanical` | cubic-bezier(0.2, 0, 0, 1) | | Toetsen: direct, zonder veer | +| `motion.easing.standard` | cubic-bezier(0.4, 0, 0.2, 1) | | Overige overgangen | +| `motion.key.press` | 50 | ms | Toets indrukken | +| `motion.key.release` | 90 | ms | Toets loslaten | +| `motion.lamp.on` | 30 | ms | Lampje aan | +| `motion.lamp.off` | 200 | ms | Lampje uit: dooft langzaam, als een gloeidraad | +| `motion.needle.response` | 0.3 | s | VU-ballistiek: 300 ms om de stand te bereiken | +| `motion.needle.damping` | 0.8 | ratio | Veerdemping; geeft ongeveer 1,5 % overschot | +| `motion.needle.updateHz` | 60 | Hz | Verversing van de wijzer | +| `motion.reducedMotion` | Overgangen 0 ms; wijzer blijft bewegen maar met demping 1.0 (geen overschot) | | Systeeminstelling 'Verminder beweging' | + +## Wat bewust ontbreekt + +Geen neon, vaporwave, synthwave, paars-roze verlopen, gloeiende tekst, chromen letters of rasterhorizonten. Lampjes mogen een kleine gloed hebben (`shadow.lamp.on`); tekst nooit.