feat: generate Swift design tokens from tokens.json and add SstUI base

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 15:19:06 +02:00
co-authored by Claude Sonnet 5.5
parent bf731dab37
commit bd6bc3b890
10 changed files with 659 additions and 21 deletions
+9 -9
View File
@@ -264,29 +264,29 @@ $('#rad').innerHTML = Object.entries(T.radius).map(([n, r]) => `<div><i style="b
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 pivot = { x: W / 2, y: H + VU['vu.pivotOffset'].value };
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;
const R = VU['vu.scaleRadius'].value;
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"/>`;
s += `<path d="${arcPath(R - 2, redFrom, 1)}" stroke="var(--c-accent-record)" stroke-width="${VU['vu.redBandWidth'].value}" 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);
const [x0, y0] = pt(R, a), [x1, y1] = pt(R - (major ? VU['vu.tickMajor'].value : VU['vu.tickMinor'].value), 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>`; }
if (major) { const [tx, ty] = pt(R - VU['vu.labelInset'].value, 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 += `<line class="needle" x1="${pivot.x}" y1="${pivot.y}" x2="${pivot.x}" y2="${pivot.y - VU['vu.needleLength'].value}" 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;
@@ -304,9 +304,9 @@ function digit(ch, ox, lit) {
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 = '';
const adv = SEG.advance.value; 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; }
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 += SEG.colonWidth.value; }
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>`;
@@ -350,7 +350,7 @@ function frame(now) {
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_);
const [nx, ny] = pt(VU['vu.needleLength'].value, 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;