docs: add design tokens (draft) with generated reference and preview
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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.<naam>`. 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("</", "<\\/")
|
||||||
|
(out_dir / "tokens-preview.html").write_text(template.replace("/*TOKENS_JSON*/", blob))
|
||||||
|
print("wrote docs/design/tokens.md and docs/design/tokens-preview.html")
|
||||||
|
sys.exit(1 if failed else 0)
|
||||||
@@ -0,0 +1,380 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="nl">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Sst Ontwerp-tokens</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; }
|
||||||
|
html { -webkit-font-smoothing: antialiased; }
|
||||||
|
body { background: var(--c-surface-chassis); color: var(--c-ink-primary); font: 13px/18px var(--f-text); min-height: 100vh; }
|
||||||
|
|
||||||
|
.wrap { max-width: 1180px; margin: 0 auto; padding: var(--s-7) var(--s-5) 96px; }
|
||||||
|
.brushed { background-image: var(--tex-brushed), linear-gradient(180deg, var(--c-surface-metalHigh), var(--c-surface-metalLow)); }
|
||||||
|
|
||||||
|
/* silkscreen */
|
||||||
|
.silk { font: 500 10px/12px var(--f-chrome); letter-spacing: .09em; text-transform: uppercase; color: var(--c-ink-tertiary); }
|
||||||
|
.silk-s { font: 500 9px/11px var(--f-chrome); letter-spacing: .10em; text-transform: uppercase; color: var(--c-ink-tertiary); }
|
||||||
|
.head { font: 500 12px/16px var(--f-chrome); letter-spacing: .12em; text-transform: uppercase; color: var(--c-ink-primary); }
|
||||||
|
.mono { font: 500 12px/16px var(--f-mono); font-variant-numeric: tabular-nums; }
|
||||||
|
|
||||||
|
/* masthead */
|
||||||
|
.mast { display: flex; align-items: stretch; margin-bottom: var(--s-6); background: var(--c-surface-panel); box-shadow: var(--sh-panel); border-top: 1px solid var(--c-line-seamDark); }
|
||||||
|
.mast .id { padding: var(--s-5); flex: 1; }
|
||||||
|
.mast h1 { font: 500 22px/26px var(--f-chrome); letter-spacing: .12em; text-transform: uppercase; margin-top: var(--s-2); }
|
||||||
|
.mast p { color: var(--c-ink-secondary); max-width: 62ch; margin-top: var(--s-3); }
|
||||||
|
.mast .stamp { padding: var(--s-5); border-left: 2px solid var(--c-line-seamDark); box-shadow: -1px 0 0 var(--c-line-seamLight); display: flex; flex-direction: column; justify-content: center; gap: var(--s-3); min-width: 220px; }
|
||||||
|
.stamp .row { display: flex; align-items: center; gap: var(--s-3); }
|
||||||
|
|
||||||
|
section { margin-top: var(--s-7); }
|
||||||
|
section > .sec-head { display: flex; align-items: baseline; gap: var(--s-4); margin-bottom: var(--s-4); padding-bottom: var(--s-3); border-bottom: 1px solid var(--c-line-seamDark); box-shadow: 0 1px 0 var(--c-line-seamLight); }
|
||||||
|
.sec-head .no { font: 500 12px var(--f-mono); color: var(--c-ink-tertiary); }
|
||||||
|
.sec-head .tk { margin-left: auto; font: 11px var(--f-mono); color: var(--c-ink-tertiary); }
|
||||||
|
|
||||||
|
.two { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-5); }
|
||||||
|
@media (max-width: 860px) { .two { grid-template-columns: 1fr; } .mast { flex-direction: column; } .mast .stamp { border-left: 0; border-top: 2px solid var(--c-line-seamDark); } }
|
||||||
|
|
||||||
|
/* a finish is a complete, scoped set of variables */
|
||||||
|
.finish { background: var(--c-surface-chassis); color: var(--c-ink-primary); padding: var(--s-5); border: 1px solid var(--c-line-seamDark); }
|
||||||
|
.finish.brushed { background-image: var(--tex-brushed), linear-gradient(180deg, var(--c-surface-metalHigh), var(--c-surface-metalLow)); }
|
||||||
|
.finish > .silk.top { display: flex; justify-content: space-between; margin-bottom: var(--s-4); }
|
||||||
|
|
||||||
|
/* swatches */
|
||||||
|
.sw-group { margin-bottom: var(--s-4); }
|
||||||
|
.sw-group .silk { margin-bottom: var(--s-2); display: block; }
|
||||||
|
.sw { display: grid; grid-template-columns: 28px 1fr auto; align-items: center; gap: var(--s-3); padding: 3px 0; border-bottom: 1px solid var(--c-line-hairline); }
|
||||||
|
.sw i { width: 28px; height: 18px; border: 1px solid var(--c-line-seamDark); display: block; }
|
||||||
|
.sw b { font: 500 11px var(--f-mono); }
|
||||||
|
.sw span { font: 11px var(--f-mono); color: var(--c-ink-tertiary); }
|
||||||
|
|
||||||
|
/* type */
|
||||||
|
.ty { display: grid; grid-template-columns: 150px 1fr; gap: var(--s-4); padding: var(--s-3) 0; border-bottom: 1px solid var(--c-line-hairline); align-items: baseline; }
|
||||||
|
.ty .spec { font: 11px/15px var(--f-mono); color: var(--c-ink-tertiary); }
|
||||||
|
.ty .spec b { color: var(--c-ink-primary); display: block; font-weight: 500; }
|
||||||
|
|
||||||
|
/* spacing / radius / borders */
|
||||||
|
.spc { display: flex; align-items: flex-end; gap: var(--s-5); flex-wrap: wrap; }
|
||||||
|
.spc div { text-align: center; } .spc i { display: block; background: var(--c-ink-primary); height: 14px; margin-bottom: var(--s-2); }
|
||||||
|
.rad { display: flex; gap: var(--s-5); flex-wrap: wrap; }
|
||||||
|
.rad div { text-align: center; } .rad i { display: block; width: 56px; height: 56px; background: var(--c-surface-panel); border: 1px solid var(--c-line-seamDark); margin-bottom: var(--s-2); }
|
||||||
|
|
||||||
|
/* parts */
|
||||||
|
.panel { background: var(--c-surface-panel); box-shadow: var(--sh-panel); border-top: 1px solid var(--c-line-seamDark); padding: var(--s-5); }
|
||||||
|
.well { background: var(--c-surface-well); box-shadow: var(--sh-well); padding: var(--s-4); }
|
||||||
|
.seam { border-top: 1px solid var(--c-line-seamDark); border-bottom: 1px solid var(--c-line-seamLight); height: 2px; }
|
||||||
|
.key { display: inline-flex; align-items: center; justify-content: center; gap: var(--s-3); height: var(--z-control-height); min-width: var(--z-key-minWidth); padding: 0 var(--s-4);
|
||||||
|
font: 500 11px/14px var(--f-chrome); letter-spacing: .06em; text-transform: uppercase; color: var(--c-ink-primary); cursor: pointer; user-select: none;
|
||||||
|
background: linear-gradient(180deg, var(--c-surface-keyHigh), var(--c-surface-keyLow)); border: 1px solid var(--c-line-seamDark); border-radius: var(--r-sm);
|
||||||
|
box-shadow: var(--sh-key-raised); transition: transform var(--m-key-release) var(--e-mechanical), box-shadow var(--m-key-release) var(--e-mechanical), background var(--m-key-release) var(--e-mechanical); }
|
||||||
|
.key:active, .key.down { background: var(--c-surface-keyPressed); box-shadow: var(--sh-key-pressed); transform: translateY(1px); transition-duration: var(--m-key-press); }
|
||||||
|
.key.rec { min-width: var(--z-key-recWidth); height: var(--z-key-recHeight); background: linear-gradient(180deg, var(--c-accent-record), var(--c-accent-record)); color: var(--c-ink-inverse); }
|
||||||
|
.key.rec.down { filter: brightness(.88); }
|
||||||
|
.key:focus-visible { outline: var(--b-focus) solid var(--c-line-focus); outline-offset: 2px; }
|
||||||
|
.lamp { width: var(--z-lamp-diameter); height: var(--z-lamp-diameter); border-radius: var(--r-lamp); background: radial-gradient(circle at 35% 30%, rgba(255,255,255,.55), rgba(255,255,255,0) 38%), var(--c-accent-recordUnlit);
|
||||||
|
border: 1px solid var(--c-line-seamDark); transition: background var(--m-lamp-off), box-shadow var(--m-lamp-off); flex: none; }
|
||||||
|
.lamp.on { background: radial-gradient(circle at 35% 30%, rgba(255,255,255,.6), rgba(255,255,255,0) 38%), var(--c-accent-record); box-shadow: var(--sh-lamp-on); transition-duration: var(--m-lamp-on); }
|
||||||
|
.lamp.big { width: var(--z-lamp-large); height: var(--z-lamp-large); }
|
||||||
|
.lamp.amber.on { background: radial-gradient(circle at 35% 30%, rgba(255,255,255,.6), rgba(255,255,255,0) 38%), var(--c-level-amber); box-shadow: none; }
|
||||||
|
.lamp.amber { background: radial-gradient(circle at 35% 30%, rgba(255,255,255,.3), rgba(255,255,255,0) 38%), var(--c-level-amberUnlit); }
|
||||||
|
.stack { display: flex; gap: var(--s-4); flex-wrap: wrap; align-items: center; }
|
||||||
|
.stack.col { flex-direction: column; align-items: stretch; }
|
||||||
|
.cap { margin-top: var(--s-2); }
|
||||||
|
|
||||||
|
/* paper list */
|
||||||
|
.paper { background: var(--c-surface-paper); box-shadow: var(--sh-well); }
|
||||||
|
.paper .r { display: grid; grid-template-columns: 76px 1fr; gap: var(--s-4); padding: var(--s-3) var(--s-4); min-height: var(--z-row-height); border-bottom: 1px solid var(--c-line-hairline); align-items: baseline; }
|
||||||
|
.paper .r.sel { background: var(--c-surface-selection); }
|
||||||
|
.paper .t { font: 500 12px/16px var(--f-mono); color: var(--c-ink-secondary); font-variant-numeric: tabular-nums; }
|
||||||
|
.paper .x { font: 15px/22px var(--f-text); }
|
||||||
|
mark { background: color-mix(in srgb, var(--c-level-amber) calc(var(--o-mark) * 100%), transparent); color: inherit; padding: 0 1px; }
|
||||||
|
mark s { color: var(--c-ink-tertiary); text-decoration-thickness: 1px; margin-right: 4px; }
|
||||||
|
|
||||||
|
/* instruments */
|
||||||
|
.rig { display: grid; grid-template-columns: auto 1fr; gap: var(--s-5); align-items: start; }
|
||||||
|
@media (max-width: 640px) { .rig { grid-template-columns: 1fr; } }
|
||||||
|
.vu { width: var(--m-vu-width); max-width: 100%; display: block; border-radius: var(--r-bezel); border: var(--b-bezel) solid var(--c-line-seamDark); box-shadow: var(--sh-well), 0 1px 0 var(--c-line-seamLight); background: var(--c-surface-meterFace); }
|
||||||
|
.display { background: var(--c-surface-display); box-shadow: var(--sh-well); padding: var(--s-3) var(--s-4); display: inline-block; }
|
||||||
|
.leds { display: flex; gap: var(--m-led-gap); }
|
||||||
|
.leds i { width: 6px; height: 14px; background: var(--c-level-greenUnlit); border-radius: var(--r-sharp); display: block; }
|
||||||
|
.leds i.g.on { background: var(--c-level-green); } .leds i.a { background: var(--c-level-amberUnlit); } .leds i.a.on { background: var(--c-level-amber); }
|
||||||
|
.leds i.r { background: var(--c-accent-recordUnlit); } .leds i.r.on { background: var(--c-accent-record); }
|
||||||
|
|
||||||
|
ul.rules { list-style: none; padding: 0; display: grid; gap: var(--s-3); max-width: 70ch; }
|
||||||
|
ul.rules li { padding-left: var(--s-5); position: relative; color: var(--c-ink-secondary); }
|
||||||
|
ul.rules li::before { content: ""; position: absolute; left: 0; top: 8px; width: 8px; height: 1px; background: var(--c-ink-primary); }
|
||||||
|
@media (prefers-reduced-motion: reduce) { * { transition-duration: 0ms !important; } }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script id="tokens" type="application/json">/*TOKENS_JSON*/</script>
|
||||||
|
|
||||||
|
<div class="wrap" id="root">
|
||||||
|
|
||||||
|
<header class="mast">
|
||||||
|
<div class="id">
|
||||||
|
<div class="silk">Ontwerpsysteem · Sst</div>
|
||||||
|
<h1>Ontwerp-tokens</h1>
|
||||||
|
<p id="concept"></p>
|
||||||
|
</div>
|
||||||
|
<div class="stamp">
|
||||||
|
<div class="row"><span class="lamp amber on"></span><span class="silk">Ter goedkeuring</span></div>
|
||||||
|
<div class="mono" id="ver"></div>
|
||||||
|
<div class="silk-s">Bron · Design/tokens.json</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="sec-head"><span class="no">01</span><span class="head">Instrumenten in gebruik</span><span class="tk">meter.* · motion.needle.* · segment.*</span></div>
|
||||||
|
<div class="two">
|
||||||
|
<div class="finish" data-finish="light"><div class="silk top"><span>Afwerking · Aluminium</span><span>licht</span></div><div class="rig-host"></div></div>
|
||||||
|
<div class="finish" data-finish="dark"><div class="silk top"><span>Afwerking · Graphite</span><span>donker</span></div><div class="rig-host"></div></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="sec-head"><span class="no">02</span><span class="head">Kleur</span><span class="tk">color.*</span></div>
|
||||||
|
<div class="two" id="colors"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="sec-head"><span class="no">03</span><span class="head">Diepte en materiaal</span><span class="tk">shadow.* · border.* · texture.*</span></div>
|
||||||
|
<div class="two">
|
||||||
|
<div class="finish brushed" data-finish="light" data-depth></div>
|
||||||
|
<div class="finish brushed" data-finish="dark" data-depth></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="sec-head"><span class="no">04</span><span class="head">Typografie</span><span class="tk">font.* · type.*</span></div>
|
||||||
|
<div id="type" class="panel"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="sec-head"><span class="no">05</span><span class="head">Ruimte, hoeken, randen</span><span class="tk">space.* · radius.* · border.*</span></div>
|
||||||
|
<div class="panel stack col" style="gap:24px">
|
||||||
|
<div><div class="silk cap">Ruimte (pt)</div><div class="spc" id="spc"></div></div>
|
||||||
|
<div class="seam"></div>
|
||||||
|
<div><div class="silk cap">Hoekradius</div><div class="rad" id="rad"></div></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="sec-head"><span class="no">06</span><span class="head">Beweging</span><span class="tk">motion.*</span></div>
|
||||||
|
<div class="two">
|
||||||
|
<div class="panel">
|
||||||
|
<div class="silk cap">Toets en lampje</div>
|
||||||
|
<div class="stack" style="margin-top:12px"><button class="key" type="button">Stop</button><button class="key" type="button" id="lampbtn">Lampje</button><span class="lamp big" id="lampdemo"></span></div>
|
||||||
|
<p style="margin-top:12px;color:var(--c-ink-secondary)">Toets: 50 ms in, 90 ms uit, zonder veer. Lampje: 30 ms aan, 200 ms uit — dooft als een gloeidraad.</p>
|
||||||
|
</div>
|
||||||
|
<div class="panel">
|
||||||
|
<div class="silk cap">Wijzer-ballistiek</div>
|
||||||
|
<div class="stack" style="margin-top:12px"><button class="key" type="button" id="step">Stap naar 0 VU</button><button class="key" type="button" id="stepHigh">Stap naar +3 VU</button></div>
|
||||||
|
<p style="margin-top:12px;color:var(--c-ink-secondary)">Veer: 300 ms tot 99 %, demping 0,80 → ongeveer 1,5 % overschot. De meters bij 01 gebruiken dezelfde waarden.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="sec-head"><span class="no">07</span><span class="head">Wat het niet is</span></div>
|
||||||
|
<ul class="rules">
|
||||||
|
<li>Geen neon, vaporwave, synthwave, paars-roze verlopen, gloeiende tekst, chromen letters of rasterhorizonten.</li>
|
||||||
|
<li>Eén accent: het rode opnamelampje. Amber en groen alleen als niveau-indicator of markering, nooit als versiering.</li>
|
||||||
|
<li>Lampjes mogen een kleine gloed hebben. Tekst nooit.</li>
|
||||||
|
<li>Geen eenmalige waarden in componenten: elke afmeting, kleur en tijd komt uit deze tokens.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const T = JSON.parse(document.getElementById('tokens').textContent);
|
||||||
|
const $ = (s, r = document) => r.querySelector(s);
|
||||||
|
const $$ = (s, r = document) => [...r.querySelectorAll(s)];
|
||||||
|
const kebab = n => n.replace(/\./g, '-');
|
||||||
|
const rgba = (hex, a) => { const h = hex.slice(1); return `rgba(${parseInt(h.slice(0,2),16)},${parseInt(h.slice(2,4),16)},${parseInt(h.slice(4,6),16)},${a})`; };
|
||||||
|
const shadow = (tok, th) => tok.layers.map(l => `${l.inset ? 'inset ' : ''}${l.x}px ${l.y}px ${l.blur}px ${l.spread}px ${rgba(l.color, l.alpha[th])}`).join(', ');
|
||||||
|
const grain = th => {
|
||||||
|
const g = T.texture.brushed.grain[th];
|
||||||
|
const svg = `<svg xmlns='http://www.w3.org/2000/svg' width='320' height='320'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.004 0.85' numOctaves='2' seed='7'/><feColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1.4 -0.2'/></filter><rect width='100%' height='100%' filter='url(%23n)' opacity='${g * 9}'/></svg>`;
|
||||||
|
return `url("data:image/svg+xml;utf8,${svg}")`;
|
||||||
|
};
|
||||||
|
|
||||||
|
function vars(th) {
|
||||||
|
const v = {};
|
||||||
|
for (const [n, c] of Object.entries(T.color)) v['--c-' + kebab(n)] = c[th];
|
||||||
|
for (const [n, s] of Object.entries(T.shadow)) v['--sh-' + kebab(n)] = shadow(s, th);
|
||||||
|
for (const [n, o] of Object.entries(T.opacity)) v['--o-' + n] = o.value;
|
||||||
|
for (const [n, s] of Object.entries(T.space)) v['--s-' + n] = s + 'px';
|
||||||
|
for (const [n, r] of Object.entries(T.radius)) v['--r-' + n] = r.value + 'px';
|
||||||
|
for (const [n, b] of Object.entries(T.border)) v['--b-' + n] = b.value + 'px';
|
||||||
|
for (const [n, s] of Object.entries(T.size)) v['--z-' + kebab(n)] = s.value + 'px';
|
||||||
|
for (const [n, f] of Object.entries(T.font)) v['--f-' + n.replace('family.', '')] = `"${f.value}", ${f.fallback}`;
|
||||||
|
for (const [n, m] of Object.entries(T.motion)) {
|
||||||
|
if (m.unit === 'ms') v['--m-' + kebab(n)] = m.value + 'ms'; else if (typeof m.value === 'string' && m.value.startsWith('cubic')) v['--e-' + n.replace('easing.', '')] = m.value;
|
||||||
|
}
|
||||||
|
v['--m-vu-width'] = T.meter['vu.width'].value + 'px';
|
||||||
|
v['--m-led-gap'] = T.meter['led.gap'].value + 'px';
|
||||||
|
v['--tex-brushed'] = grain(th);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
function apply(el, th) { for (const [k, val] of Object.entries(vars(th))) el.style.setProperty(k, val); el.dataset.finish = th; }
|
||||||
|
apply(document.body, 'light');
|
||||||
|
$$('.finish').forEach(el => apply(el, el.dataset.finish));
|
||||||
|
|
||||||
|
$('#concept').textContent = T.meta.concept;
|
||||||
|
$('#ver').textContent = 'v' + T.meta.version;
|
||||||
|
|
||||||
|
/* ---- 02 colour swatches ---- */
|
||||||
|
const groups = {};
|
||||||
|
for (const [n, c] of Object.entries(T.color)) (groups[n.split('.')[0]] ??= []).push([n, c]);
|
||||||
|
const groupLabel = { surface: 'Oppervlak', ink: 'Inkt', accent: 'Accent', level: 'Niveau', line: 'Lijnen', display: 'Display' };
|
||||||
|
for (const th of ['light', 'dark']) {
|
||||||
|
const box = document.createElement('div');
|
||||||
|
box.className = 'finish'; apply(box, th);
|
||||||
|
box.innerHTML = `<div class="silk top"><span>Afwerking · ${th === 'light' ? 'Aluminium' : 'Graphite'}</span><span>${th === 'light' ? 'licht' : 'donker'}</span></div>` +
|
||||||
|
Object.entries(groups).map(([g, items]) => `<div class="sw-group"><span class="silk">${groupLabel[g] || g}</span>` +
|
||||||
|
items.map(([n, c]) => `<div class="sw" title="${c.use}"><i style="background:${c[th]}"></i><b>${n}</b><span>${c[th]}</span></div>`).join('') + '</div>').join('');
|
||||||
|
$('#colors').appendChild(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 03 depth ---- */
|
||||||
|
$$('[data-depth]').forEach(el => {
|
||||||
|
el.innerHTML = `
|
||||||
|
<div class="silk top"><span>Afwerking · ${el.dataset.finish === 'light' ? 'Aluminium' : 'Graphite'}</span><span>geborsteld metaal</span></div>
|
||||||
|
<div class="panel stack col" style="gap:20px">
|
||||||
|
<div class="stack"><button class="key" type="button">Toets</button><button class="key down" type="button">Ingedrukt</button><button class="key rec" type="button"><span class="lamp on"></span>Rec</button><button class="key rec down" type="button">Rec</button></div>
|
||||||
|
<div class="seam"></div>
|
||||||
|
<div class="stack" style="align-items:stretch"><div class="well" style="flex:1"><div class="silk">Verzonken vlak</div><div class="mono" style="margin-top:6px">well</div></div><div class="stack col" style="gap:8px;justify-content:center"><div class="stack"><span class="lamp big on"></span><span class="silk-s">Lamp aan</span></div><div class="stack"><span class="lamp big"></span><span class="silk-s">Lamp uit</span></div></div></div>
|
||||||
|
<div class="paper"><div class="r"><span class="t">14:02:11</span><span class="x">Ik wil de meeting verplaatsen naar tomorrow morning.</span></div>
|
||||||
|
<div class="r sel"><span class="t">14:03:40</span><span class="x">Hij stuurt het door naar <mark><s>cloud code</s>Claude Code</mark> vandaag.</span></div></div>
|
||||||
|
</div>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ---- 04 type ---- */
|
||||||
|
const fam = f => `'${T.font['family.' + f].value}', ${T.font['family.' + f].fallback}`;
|
||||||
|
const samples = { label: 'Opnameniveau', labelSmall: 'VU · dB', control: 'Start opname', heading: 'Transcripties', body: 'Doorzoek je eerdere transcripties en woordenboek.', reading: 'Ik wil de meeting verplaatsen naar tomorrow morning, want de deadline komt eraan.', meta: 'Gisteren, 14:02 · 8,4 s', counter: '00:12.4', counterSmall: '14:02:11', code: 'cloud code => Claude Code' };
|
||||||
|
$('#type').innerHTML = Object.entries(T.type).map(([n, t]) => `
|
||||||
|
<div class="ty"><div class="spec"><b>type.${n}</b>${t.family} ${t.size}/${t.line} · ${t.weight}${t.caps ? ' · CAPS' : ''}${t.tracking ? ' · +' + t.tracking + 'em' : ''}</div>
|
||||||
|
<div style="font:${t.weight} ${t.size}px/${t.line}px ${fam(t.family)};letter-spacing:${t.tracking}em;${t.caps ? 'text-transform:uppercase;' : ''}${t.tabular ? 'font-variant-numeric:tabular-nums;' : ''}">${samples[n] || n}</div></div>`).join('');
|
||||||
|
|
||||||
|
/* ---- 05 spacing / radius ---- */
|
||||||
|
$('#spc').innerHTML = Object.entries(T.space).map(([n, v]) => `<div><i style="width:${v || 1}px"></i><span class="mono">${n} · ${v}</span></div>`).join('');
|
||||||
|
$('#rad').innerHTML = Object.entries(T.radius).map(([n, r]) => `<div><i style="border-radius:${Math.min(r.value, 28)}px"></i><span class="mono">${n} · ${r.value >= 999 ? 'rond' : r.value}</span></div>`).join('');
|
||||||
|
|
||||||
|
/* ---- instruments ---- */
|
||||||
|
const VU = T.meter;
|
||||||
|
const vuMin = VU['vu.min'].value, vuMax = VU['vu.max'].value, arc = VU['vu.arcDegrees'].value;
|
||||||
|
const W = VU['vu.width'].value, H = VU['vu.height'].value;
|
||||||
|
const pivot = { x: W / 2, y: H + 22 };
|
||||||
|
const frac = v => Math.max(0, Math.min(1.03, (v - vuMin) / (vuMax - vuMin)));
|
||||||
|
const ang = f => (-arc / 2 + arc * f) * Math.PI / 180;
|
||||||
|
const pt = (r, a) => [pivot.x + r * Math.sin(a), pivot.y - r * Math.cos(a)];
|
||||||
|
const f2 = n => n.toFixed(2);
|
||||||
|
|
||||||
|
function meterSvg() {
|
||||||
|
const R = 142;
|
||||||
|
let s = `<svg class="vu" viewBox="0 0 ${W} ${H}" role="img" aria-label="VU-meter" style="aspect-ratio:${W}/${H}">`;
|
||||||
|
s += `<rect width="${W}" height="${H}" fill="var(--c-surface-meterFace)"/>`;
|
||||||
|
const redFrom = frac(0);
|
||||||
|
const arcPath = (r0, f0, f1) => { const [x0, y0] = pt(r0, ang(f0)), [x1, y1] = pt(r0, ang(f1)); return `M${f2(x0)} ${f2(y0)} A${r0} ${r0} 0 0 1 ${f2(x1)} ${f2(y1)}`; };
|
||||||
|
s += `<path d="${arcPath(R, 0, redFrom)}" stroke="var(--c-ink-onMeter)" stroke-width="1" fill="none"/>`;
|
||||||
|
s += `<path d="${arcPath(R - 2, redFrom, 1)}" stroke="var(--c-accent-record)" stroke-width="5" fill="none"/>`;
|
||||||
|
for (let v = vuMin; v <= vuMax; v++) {
|
||||||
|
const major = [-20, -10, -7, -5, -3, 0, 3].includes(v), a = ang(frac(v));
|
||||||
|
const [x0, y0] = pt(R, a), [x1, y1] = pt(R - (major ? 11 : 6), a);
|
||||||
|
s += `<line x1="${f2(x0)}" y1="${f2(y0)}" x2="${f2(x1)}" y2="${f2(y1)}" stroke="${v > 0 ? 'var(--c-accent-record)' : 'var(--c-ink-onMeter)'}" stroke-width="${major ? 1.4 : 1}"/>`;
|
||||||
|
if (major) { const [tx, ty] = pt(R - 24, a); s += `<text x="${f2(tx)}" y="${f2(ty + 3)}" text-anchor="middle" font-family="${fam('chrome')}" font-weight="500" font-size="9" letter-spacing=".5" fill="${v > 0 ? 'var(--c-accent-record)' : 'var(--c-ink-onMeter)'}">${v > 0 ? '+' + v : v}</text>`; }
|
||||||
|
}
|
||||||
|
s += `<text x="${W / 2}" y="${H - 30}" text-anchor="middle" font-family="${fam('chrome')}" font-weight="500" font-size="15" letter-spacing="3" fill="var(--c-ink-onMeter)">VU</text>`;
|
||||||
|
s += `<text x="14" y="${H - 10}" font-family="${fam('chrome')}" font-weight="500" font-size="8" letter-spacing="1.2" fill="var(--c-ink-onMeter)" opacity=".75">SST · FIELD RECORDER</text>`;
|
||||||
|
s += `<line class="needle" x1="${pivot.x}" y1="${pivot.y}" x2="${pivot.x}" y2="${pivot.y - 146}" stroke="var(--c-ink-onMeter)" stroke-width="${VU['vu.needleWidth'].value}" stroke-linecap="butt"/>`;
|
||||||
|
s += `<circle cx="${W - 18}" cy="18" r="5" class="peak" fill="var(--c-accent-recordUnlit)" stroke="var(--c-ink-onMeter)" stroke-width=".8"/><text x="${W - 18}" y="34" text-anchor="middle" font-family="${fam('chrome')}" font-size="7" font-weight="500" letter-spacing=".8" fill="var(--c-ink-onMeter)">PEAK</text>`;
|
||||||
|
s += `</svg>`;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SEG = T.segment, DW = SEG.digitWidth.value, DH = SEG.digitHeight.value, TH = SEG.thickness.value, GP = SEG.gap.value;
|
||||||
|
const MAP = { 0: 'abcdef', 1: 'bc', 2: 'abdeg', 3: 'abcdg', 4: 'bcfg', 5: 'acdfg', 6: 'acdefg', 7: 'abc', 8: 'abcdefg', 9: 'abcdfg' };
|
||||||
|
function hbar(x0, x1, cy) { const h = TH / 2; return `${x0},${cy} ${x0 + h},${cy - h} ${x1 - h},${cy - h} ${x1},${cy} ${x1 - h},${cy + h} ${x0 + h},${cy + h}`; }
|
||||||
|
function vbar(cx, y0, y1) { const h = TH / 2; return `${cx},${y0} ${cx + h},${y0 + h} ${cx + h},${y1 - h} ${cx},${y1} ${cx - h},${y1 - h} ${cx - h},${y0 + h}`; }
|
||||||
|
function digit(ch, ox, lit) {
|
||||||
|
const h = TH / 2, L = h, Rr = DW - h, mid = DH / 2, g = GP;
|
||||||
|
const P = { a: hbar(L + h + g, Rr - h - g, h), g: hbar(L + h + g, Rr - h - g, mid), d: hbar(L + h + g, Rr - h - g, DH - h),
|
||||||
|
f: vbar(L, h + h + g, mid - h - g), b: vbar(Rr, h + h + g, mid - h - g), e: vbar(L, mid + h + g, DH - h - h - g), c: vbar(Rr, mid + h + g, DH - h - h - g) };
|
||||||
|
const on = MAP[ch] || '';
|
||||||
|
return `<g transform="translate(${ox},0)">` + Object.entries(P).map(([k, pts]) => `<polygon points="${pts}" fill="${lit && on.includes(k) ? 'var(--c-level-amber)' : 'var(--c-display-segmentUnlit)'}"/>`).join('') + `</g>`;
|
||||||
|
}
|
||||||
|
function counterSvg(text) {
|
||||||
|
const adv = DW + 6; let x = 0, out = '';
|
||||||
|
for (const ch of text) {
|
||||||
|
if (ch === ':' || ch === '.') { out += `<rect x="${x + 2}" y="${ch === ':' ? DH * .28 : DH - 5}" width="4" height="4" fill="var(--c-level-amber)"/>` + (ch === ':' ? `<rect x="${x + 2}" y="${DH * .64}" width="4" height="4" fill="var(--c-level-amber)"/>` : ''); x += 10; }
|
||||||
|
else { out += digit(ch, x, true); x += adv; }
|
||||||
|
}
|
||||||
|
return `<svg width="${x}" height="${DH}" viewBox="0 0 ${x} ${DH}" role="img" aria-label="Teller ${text}">${out}</svg>`;
|
||||||
|
}
|
||||||
|
const ghost8 = () => counterSvg('88:88.8').replace(/var\(--c-level-amber\)/g, 'var(--c-display-segmentUnlit)');
|
||||||
|
|
||||||
|
const rigs = [];
|
||||||
|
$$('.rig-host').forEach(host => {
|
||||||
|
host.innerHTML = `
|
||||||
|
<div class="rig">
|
||||||
|
<div>${meterSvg()}<div class="silk-s" style="margin-top:6px">Niveau · 0 VU = ${VU['vu.referenceDbfs'].value} dBFS</div></div>
|
||||||
|
<div class="stack col" style="gap:14px">
|
||||||
|
<div><div class="silk-s" style="margin-bottom:6px">Tijd</div>
|
||||||
|
<div class="display"><div style="position:relative"><div class="ghost" style="position:absolute;inset:0">${ghost8()}</div><div class="live" style="position:relative"></div></div></div></div>
|
||||||
|
<div><div class="silk-s" style="margin-bottom:6px">Niveau</div><div class="leds">${Array.from({length: 12}, (_, i) => `<i class="${i < 8 ? 'g' : i < 11 ? 'a' : 'r'}"></i>`).join('')}</div></div>
|
||||||
|
<div class="stack"><button class="key rec down" type="button" data-rec><span class="lamp on"></span>Rec</button><button class="key" type="button" data-stop>Stop</button></div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
rigs.push({ host, needle: $('.needle', host), peak: $('.peak', host), live: $('.live', host), leds: $$('.leds i', host), rec: $('[data-rec]', host), stop: $('[data-stop]', host), lamp: $('[data-rec] .lamp', host), x: 0, v: 0, target: -40, peakUntil: 0, running: true, t0: performance.now(), mode: 'speech' });
|
||||||
|
});
|
||||||
|
|
||||||
|
/* spring: critically-parametrised second-order system, 99% settling = needle.response */
|
||||||
|
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
const zeta = reduce ? 1 : T.motion['needle.damping'].value, resp = T.motion['needle.response'].value;
|
||||||
|
const wn = 4.6 / (zeta * resp);
|
||||||
|
function envelope(t) {
|
||||||
|
const cyc = t % 9;
|
||||||
|
if (cyc > 6.2) return -40;
|
||||||
|
const syl = Math.floor(t * 4.2);
|
||||||
|
const r = Math.sin(syl * 12.9898) * 43758.5453; const rnd = r - Math.floor(r);
|
||||||
|
const gap = Math.sin(t * 4.2 * Math.PI) > -0.55 ? 0 : -25;
|
||||||
|
return rnd > .12 ? (-15 + rnd * 16.5 + gap) : -40;
|
||||||
|
}
|
||||||
|
let last = performance.now();
|
||||||
|
function frame(now) {
|
||||||
|
const dt = Math.min(.05, (now - last) / 1000); last = now;
|
||||||
|
for (const r of rigs) {
|
||||||
|
const t = (now - r.t0) / 1000;
|
||||||
|
if (r.mode === 'speech') r.target = r.running ? envelope(t) : -40;
|
||||||
|
const tgt = frac(r.target);
|
||||||
|
const a = wn * wn * (tgt - r.x) - 2 * zeta * wn * r.v;
|
||||||
|
r.v += a * dt; r.x += r.v * dt;
|
||||||
|
const ang_ = ang(Math.max(-0.02, Math.min(1.05, r.x)));
|
||||||
|
const [nx, ny] = pt(146, ang_);
|
||||||
|
r.needle.setAttribute('x2', f2(nx)); r.needle.setAttribute('y2', f2(ny));
|
||||||
|
const vuNow = vuMin + (vuMax - vuMin) * r.x;
|
||||||
|
if (vuNow > 0 && r.running) r.peakUntil = now + VU['peak.holdMs'].value;
|
||||||
|
r.peak.setAttribute('fill', now < r.peakUntil ? 'var(--c-accent-record)' : 'var(--c-accent-recordUnlit)');
|
||||||
|
const lit = Math.round(Math.max(0, Math.min(1, (vuNow - vuMin) / (vuMax - vuMin))) * 12);
|
||||||
|
r.leds.forEach((l, i) => l.classList.toggle('on', i < lit));
|
||||||
|
const secs = r.running ? t : (r.frozen ?? 0); if (r.running) r.frozen = t;
|
||||||
|
const mm = String(Math.floor(secs / 60) % 100).padStart(2, '0'), ss = String(Math.floor(secs % 60)).padStart(2, '0'), d = Math.floor((secs * 10) % 10);
|
||||||
|
const txt = `${mm}:${ss}.${d}`;
|
||||||
|
if (r.txt !== txt) { r.live.innerHTML = counterSvg(txt); r.txt = txt; }
|
||||||
|
}
|
||||||
|
requestAnimationFrame(frame);
|
||||||
|
}
|
||||||
|
requestAnimationFrame(frame);
|
||||||
|
rigs.forEach(r => {
|
||||||
|
r.stop.onclick = () => { r.running = false; r.rec.classList.remove('down'); r.lamp.classList.remove('on'); };
|
||||||
|
r.rec.onclick = () => { if (!r.running) { r.running = true; r.t0 = performance.now(); r.rec.classList.add('down'); r.lamp.classList.add('on'); } };
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ---- 06 motion demos ---- */
|
||||||
|
$('#lampbtn').onclick = () => $('#lampdemo').classList.toggle('on');
|
||||||
|
const stepTo = v => rigs.forEach(r => { r.mode = 'manual'; r.running = false; r.target = v; setTimeout(() => { r.target = -40; setTimeout(() => { r.mode = 'speech'; r.running = true; r.t0 = performance.now(); r.rec.classList.add('down'); r.lamp.classList.add('on'); }, 1800); }, 1800); r.rec.classList.remove('down'); r.lamp.classList.remove('on'); });
|
||||||
|
$('#step').onclick = () => stepTo(0);
|
||||||
|
$('#stepHigh').onclick = () => stepTo(3);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -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'" }
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -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.<naam>`. 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.
|
||||||
Reference in New Issue
Block a user