validateCyberpunk function
The family-specific guardrails (called from validateTheme for
ThemeFamily.cyberpunk specs only):
cyberOperatorCue: the recorded fill-versus-outline cue is real. Digit and function faces are opaque plates (L ≤ cyberPlateMaxL) with labels lighter than the face; operator and=faces are opaque slabs (L ≥ cyberSlabMinL) with darker labels and ≥ 3:1 against the digit plate; operators are ≥ 200 heavier.cyberEdge: the plate edge lines that carry the cue are opaque and ≥ 3:1 against their faces, 1–1.5 dp wide.cyberKeyBoundary: every key group's face or opaque edge reaches 3:1 against every scene sample (WCAG 1.4.11).cyberScene: the scene is a staticcyberGridwith five colours, opaque stops, inks under their alpha ceilings and a tilt within ±20°; the panel scanline alpha is ≤ cyberPanelScanMaxAlpha (0 under the Accessible lock); keys arecutwith chamfer 12–24 % and no generic state layer.
Implementation
List<GuardrailViolation> validateCyberpunk(
ThemeSpec spec,
GuardrailLimits lim,
List<int> bgs,
) {
final out = <GuardrailViolation>[];
final p = spec.palette;
final plates = {'digit': p.digit, 'function': p.function};
final slabs = {'operator': p.operator, 'equals': p.equals};
// cyberOperatorCue.
for (final MapEntry(key: name, value: k) in plates.entries) {
final l = argbToOklch(k.face | 0xFF000000).l;
if (!_opaque(k.face) ||
l > cyberPlateMaxL ||
luminanceArgb(k.label | 0xFF000000) <= luminanceArgb(k.face)) {
out.add(
GuardrailViolation(
GuardrailRule.cyberOperatorCue,
l,
cyberPlateMaxL,
'$name plate (opaque, L ≤ $cyberPlateMaxL, lighter label)',
),
);
}
}
for (final MapEntry(key: name, value: k) in slabs.entries) {
final l = argbToOklch(k.face | 0xFF000000).l;
if (!_opaque(k.face) ||
l < cyberSlabMinL ||
luminanceArgb(k.label | 0xFF000000) >= luminanceArgb(k.face)) {
out.add(
GuardrailViolation(
GuardrailRule.cyberOperatorCue,
l,
cyberSlabMinL,
'$name slab (opaque, L ≥ $cyberSlabMinL, darker label)',
),
);
}
final cr = contrastRatio(k.face | 0xFF000000, p.digit.face | 0xFF000000);
if (cr < cyberSlabVsPlateRatio) {
out.add(
GuardrailViolation(
GuardrailRule.cyberOperatorCue,
cr,
cyberSlabVsPlateRatio,
'$name slab vs digit plate',
),
);
}
}
final bonus = spec.type.operatorWeight - spec.type.keyWeight;
if (bonus < 200) {
out.add(
GuardrailViolation(
GuardrailRule.cyberOperatorCue,
bonus.toDouble(),
200,
'operator weight bonus',
),
);
}
// cyberEdge.
for (final MapEntry(key: name, value: k) in plates.entries) {
final ratio = _opaque(k.edge) ? contrastRatio(k.edge, k.face) : 1.0;
if (ratio < ratioLarge) {
out.add(
GuardrailViolation(GuardrailRule.cyberEdge, ratio, ratioLarge, name),
);
}
}
final width = spec.keyStyle.outlineWidthCdp;
if (!cyberEdgeWidthCdp.contains(width)) {
out.add(
GuardrailViolation(
GuardrailRule.cyberEdge,
width / 100,
cyberEdgeWidthCdp.lo / 100,
'edge width (dp)',
),
);
}
// cyberKeyBoundary.
for (final MapEntry(key: name, value: k) in {...plates, ...slabs}.entries) {
final face = _opaque(k.face) ? _minContrastOver(k.face, bgs) : 1.0;
final edge = _opaque(k.edge) ? _minContrastOver(k.edge, bgs) : 1.0;
if (face < ratioLarge && edge < ratioLarge) {
out.add(
GuardrailViolation(
GuardrailRule.cyberKeyBoundary,
face > edge ? face : edge,
ratioLarge,
name,
),
);
}
}
// cyberScene.
void scene(String detail, [double measured = 0, double required = 0]) =>
out.add(
GuardrailViolation(
GuardrailRule.cyberScene,
measured,
required,
detail,
),
);
final bg = spec.background;
if (bg.kind != BackgroundKind.cyberGrid) scene('kind ${bg.kind.name}');
if (bg.colors.length != 5) {
scene('colours', bg.colors.length.toDouble(), 5);
} else {
if (!_opaque(bg.colors[0]) || !_opaque(bg.colors[1])) scene('stops');
for (final (i, cap) in [
(2, cyberGridMaxAlpha),
(3, cyberTraceMaxAlpha),
(4, cyberScanMaxAlpha),
]) {
final a = _alpha(bg.colors[i]);
if (a > cap) scene('ink $i alpha', a.toDouble(), cap.toDouble());
}
}
final angle = bg.angleTenths % 3600;
if (angle > cyberMaxAngleTenths && angle < 3600 - cyberMaxAngleTenths) {
scene('angle', angle.toDouble(), cyberMaxAngleTenths.toDouble());
}
if (bg.driftPeriodMs != 0) scene('drift', bg.driftPeriodMs.toDouble());
final scanAlpha = _alpha(p.shadow);
if (scanAlpha > cyberPanelScanMaxAlpha ||
(spec.accessible && scanAlpha != 0)) {
scene(
'panel scanline alpha',
scanAlpha.toDouble(),
spec.accessible ? 0 : cyberPanelScanMaxAlpha.toDouble(),
);
}
if (spec.keyShape.kind != KeyShapeKind.cut ||
!cyberCornerPermille.contains(spec.keyShape.cornerPermille)) {
scene('shape', spec.keyShape.cornerPermille.toDouble());
}
if (spec.keyStyle.stateLayerPermille != 0) scene('state layer');
return out;
}