validateVaporwave function

List<GuardrailViolation> validateVaporwave(
  1. ThemeSpec spec,
  2. GuardrailLimits lim,
  3. List<int> bgs
)

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;
}