validateVaporwave function
The family-specific guardrails (called from validateTheme for
ThemeFamily.vaporwave specs only):
- GuardrailRule.vaporOperatorCue: the recorded weight cue is real (operators ≥ 200 heavier).
- GuardrailRule.vaporOperatorFace: operator and
=faces ΔEOK ≥ 0.08 from the digit face (normal vision). - GuardrailRule.vaporAccentVsDigit: both accents stand apart from the digit and function faces under every simulated CVD (ΔEOK ≥ 0.06) and in lightness (contrast ≥ 1.2).
- GuardrailRule.vaporScene: a well-formed scene (ten opaque colours, horizon 380–460 ‰, drift 0 or ≥ 40 s) inside its luminance band.
- GuardrailRule.vaporPanel: the panel, title-bar and ink colours are opaque.
- GuardrailRule.vaporOutline: every key edge is the drawn ink, and in light themes the ink is ≥ 3:1 on every scene sample (WCAG 1.4.11: pastel keys on a pastel floor are delimited by it).
- GuardrailRule.vaporBoundary: in dark themes every key is delimited (≥ 3:1) from every plain floor colour it can sit over by its face or its ink, and from every in-between grid-over-floor colour by its face, its ink or its outer bevel highlight. The guarantee therefore covers the highlighted top and left edges of a key at rest; on the bottom and right edges only the ink and the face are counted.
Implementation
List<GuardrailViolation> validateVaporwave(
ThemeSpec spec,
GuardrailLimits lim,
List<int> bgs,
) {
final out = <GuardrailViolation>[];
final p = spec.palette;
if (spec.nonColourOperatorCue != OperatorCue.weight ||
spec.type.operatorWeight - spec.type.keyWeight < 200) {
out.add(
GuardrailViolation(
GuardrailRule.vaporOperatorCue,
(spec.type.operatorWeight - spec.type.keyWeight).toDouble(),
200,
'operator weight bonus',
),
);
}
for (final (name, k) in [('operator', p.operator), ('equals', p.equals)]) {
final de = deltaEOKArgb(k.face, p.digit.face);
if (de < vaporwaveOperatorFaceDeltaE) {
out.add(
GuardrailViolation(
GuardrailRule.vaporOperatorFace,
de,
vaporwaveOperatorFaceDeltaE,
name,
),
);
}
for (final (bodyName, body) in [
('digit', p.digit),
('function', p.function),
]) {
final cvd = minCvdDeltaE(k.face, body.face);
if (cvd < vaporwaveAccentBodyDeltaE) {
out.add(
GuardrailViolation(
GuardrailRule.vaporAccentVsDigit,
cvd,
vaporwaveAccentBodyDeltaE,
'$name vs $bodyName (cvd)',
),
);
}
final cr = contrastRatio(k.face, body.face);
if (cr < vaporwaveAccentBodyContrast) {
out.add(
GuardrailViolation(
GuardrailRule.vaporAccentVsDigit,
cr,
vaporwaveAccentBodyContrast,
'$name vs $bodyName (contrast)',
),
);
}
}
}
final bg = spec.background;
final wellFormed = vaporwaveSceneWellFormed(bg);
if (!wellFormed) {
out.add(
GuardrailViolation(
GuardrailRule.vaporScene,
bg.colors.length.toDouble(),
10,
'scene kind and colours',
),
);
}
if (bg.angleTenths < vaporwaveHorizonMinPermille ||
bg.angleTenths > vaporwaveHorizonMaxPermille) {
out.add(
GuardrailViolation(
GuardrailRule.vaporScene,
bg.angleTenths.toDouble(),
vaporwaveHorizonMinPermille.toDouble(),
'horizon (permille)',
),
);
}
if (bg.driftPeriodMs != 0 && bg.driftPeriodMs < 40000) {
out.add(
GuardrailViolation(
GuardrailRule.vaporScene,
bg.driftPeriodMs.toDouble(),
40000,
'drift period (ms)',
),
);
}
if (wellFormed) {
for (final s in bgs) {
final y = luminanceArgb(s);
if (spec.isDark
? y > vaporwaveDarkSceneCheckY
: y < vaporwaveLightSceneCheckY) {
out.add(
GuardrailViolation(
GuardrailRule.vaporScene,
y,
spec.isDark ? vaporwaveDarkSceneCheckY : vaporwaveLightSceneCheckY,
'scene luminance band',
),
);
break;
}
}
}
for (final (name, c) in [
('surface', p.surface),
('highlight', p.highlight),
('accent', p.accent),
('shadow', p.shadow),
]) {
if (!_opaque(c)) {
out.add(
GuardrailViolation(
GuardrailRule.vaporPanel,
((c >>> 24) & 0xFF) / 255,
1,
'$name translucent',
),
);
}
}
final groups = [
('digit', p.digit),
('operator', p.operator),
('equals', p.equals),
('function', p.function),
];
for (final (name, k) in groups) {
if (k.edge != p.shadow) {
out.add(
GuardrailViolation(
GuardrailRule.vaporOutline,
0,
1,
'$name edge is not the ink',
),
);
}
}
if (!spec.isDark) {
var worst = double.infinity;
for (final s in bgs) {
final r = contrastRatio(p.shadow | 0xFF000000, s);
if (r < worst) worst = r;
}
if (worst < ratioLarge) {
out.add(
GuardrailViolation(
GuardrailRule.vaporOutline,
worst,
ratioLarge,
'ink',
),
);
}
} else if (wellFormed) {
final keySamples = vaporwaveKeySamplesOf(bg.colors);
final mixes = _luminances(vaporwaveKeyMixSamplesOf(bg.colors));
final hp = _highlightPermilleOf(spec.keyStyle);
for (final (name, k) in groups) {
final worst = _worstBoundary(k.face, p.shadow, keySamples);
if (worst < ratioLarge) {
out.add(
GuardrailViolation(
GuardrailRule.vaporBoundary,
worst,
ratioLarge,
name,
),
);
}
final lit = _worstBoundaryLit(
k.face,
p.shadow,
vaporwaveBevelHighlight(k.face, hp),
mixes,
);
if (lit < ratioLarge) {
out.add(
GuardrailViolation(
GuardrailRule.vaporBoundary,
lit,
ratioLarge,
'$name (grid over floor)',
),
);
}
}
}
return out;
}