engine/y2k_aero library

The Y2K / Frutiger Aero family (spec §5.3 #10, enum y2kChrome, chip label "Y2K Aero"; motion brief §2.3 "Y2K / chrome"): 2001–2008 glossy optimism. Pure Dart: part of the generator, so every constant here is part of the family's engine-v3 definition and must not change without a version bump.

Two variants, drawn first on the background stream (so a seed keeps its variant across palette retries and the Accessible lock):

  • AERO (3 in 5, BackgroundKind.aeroSky): opaque, saturated gel keys — pearl or blue-lavender moonstone digits, frost or sea-glass function keys, blue and green gel operators — each with a hard white specular cap over its top 42 %, a deeper tone right under the cap and a light caustic at the bottom, under an azure sky with a lake horizon (Y2kScene.sky), over sunlit grass hills (Y2kScene.meadow) or deep in blue lagoon water with god rays (Y2kScene.water, the dark themes), with rising bubbles.
  • CHROME (2 in 5, BackgroundKind.chromeHolo): satin-silver metal digits and titanium function keys in a light–dark–light chrome bezel, candy-gel operators, on pastel or black holographic foil with sparkles; heavy type with a slashed zero.

Keys are opaque where the label sits. The renderer paints each face as a vertical profile (y2kGelProfile / y2kMetalProfile) derived from the opaque face colour, plus white overlays (cap, sheen, hover, brushed streaks) and a lighter caustic. Inside the label zone (the middle 40 % of the height, y2kLabelZoneTop–y2kLabelZoneBottom) nothing darker than the profile's deep tone (floored at L 0.70, 0.735 under the Accessible lock) is drawn, and every label is darker than its face, so the label's worst backdrop is one of y2kLabelZoneSamples: the rest and pressed profiles blended in every proportion the renderer can show (validated as GuardrailRule.y2kLabelZone).

The press (motion brief "Y2K / chrome"): frame 1 swaps the body to the pressed face (y2kPressedFace: hue +20°, or an accent tint on the near-neutral pearl / silver / frost / titanium faces), flares the cap and slides it toward the finger; on release a specular band sweeps the face once, left to right.

Shared spec fields, as this family uses them:

Field Meaning here
KeyStyleSpec.kind always KeyStyleKind.glass ("glossy"; faces are opaque)
outlineWidthCdp AERO rim 1.00–1.50 dp; CHROME bezel ring 2.5–3.5 dp
highlightPermille specular cap peak alpha (pressed + 0.10; ≥ 0.8 drawn on dark scenes)
refractionPermille caustic glow alpha at the key's bottom (pressed × 1.8)
edgeShadePermille depth of the deep tone under the cap (and the metal dark bands)
shadowOffsetCdp / shadowBlurCdp the soft drop shadow (σ = blur / 2; offset ≤ 3 dp, blur ≥ 5 dp, so it stays within y2kEdgeShadowAlpha at a key's edge)
stateLayerPermille 0: the pressed cue is artwork, not a state layer
Palette.background the Material chrome's surface (settings, history): the horizon haze (sky, meadow), the mid water (deep water) or the foil's base
Palette.highlight the specular white, always opaque white
Palette.shadow the drop-shadow colour and alpha
Palette.accent = the focus ring (toolbar glyphs)
KeyColors.edge AERO rim / CHROME hairline (≥ 3:1 on the scene under the lock)
BackgroundSpec.colors six scene colours (y2k_aero_scene.dart lists the roles)
angleTenths AERO sun azimuth; CHROME holo band angle
pattern / patternColor / patternScaleCdp / patternOpacityPermille bubbles (AERO) or sparkles (CHROME): tint, size and layout seed, opacity
driftPeriodMs on / off only (periods y2kAeroDriftMs, y2kChromeDriftMs)
MotionSpec.fx / verb sheen / gloss (painted by the key face)

Every draw comes from the generator's named sub-streams (palette and its retry streams, background, shape, style, type, motion, name), so a theme is a pure function of (seed, engineVersion, accessible). The variant is a background draw, so a retry keeps it; brightness is the first palette draw, so a retry may turn a light sky into deep water (the shape, style, type and motion never change).

Classes

Y2kGel
One gel or candy colour: hue (tenths, may run past 3600 and wrap), lightness (thousandths; waterLMilli in the dark AERO scene), chroma as a share of the gamut (thousandths) capped at capMilli, and its weight.

Enums

Y2kScene
The five scenes. AERO light themes are sky or meadow, AERO dark themes water; CHROME light themes pastelHolo, dark blackHolo.

Constants

y2kAccentMinContrast → const double
Operator / = vs digit: contrast ≥ y2kAccentMinContrast or min ΔEOK (normal + CVD) ≥ y2kAccentMinDeltaE (GuardrailRule.y2kAccentVsDigit).
y2kAccentMinDeltaE → const double
y2kAccessibleDeepFloorMilli → const int
y2kAccessibleFaceFloorMilli → const int
y2kAccessibleFaceMinLMilli → const int
y2kAccessibleLabelStartLMilli → const int
y2kAeroDefinition → const FamilyDefinition
Y2K / Frutiger Aero (spec §5.3 #10; motion brief §2.3 "Y2K / chrome"): opaque gel orbs or chrome-bezelled metal keys with a hard specular cap. Press: effectsFast in, 439/0.75 out (seeded × {0.9, 1, 1.1}, ζ ± 0.05), scale 0.96, a frame-1 hue +20° and cap flare, a specular sweep on release; pop entry 0.88 → 1 on expressiveFast; sound glass. The palette is built by buildY2kAeroCandidate.
y2kAeroDriftMs → const int
Drift period of each scene kind (constant: driftPeriodMs only says whether the scene drifts; a transition drops it to 0).
y2kAeroGels → const List<Y2kGel>
AERO gels: greens (0–2) and blues (3–5), every hue range ≥ 3° inside its 30° bucket (buckets 4–9).
y2kAeroLightSceneWeights → const List<int>
AERO light scenes: [sky, meadow] weights (background stream).
y2kAeroRimCdp → const IntRange
y2kBubbleMaxAlpha → const double
The bubble sprite's highest alpha (its rim over its body; the glint is lower and never overlaps the rim): a bubble paints its tint at most at this × the pattern opacity (y2kBubbleBodyAlpha in the scene painter lists the sprite).
y2kCausticZoneShare → const double
Peak caustic alpha inside the label zone, as a share of the caustic's alpha (the oval's radial ramp where it meets the zone's bottom edge).
y2kChromeBezelCdp → const IntRange
y2kChromeCandies → const List<Y2kGel>
CHROME candies (buckets 0–4, 7, 9–11), lightness capped at 0.82 so a candy never matches a silver digit by lightness — except dandelion, which sits at L 0.84–0.88 (a darker yellow turns ochre) and stands apart from silver by its chroma and warmth (C ≈ 0.16 at 84–96°).
y2kChromeDriftMs → const int
y2kChromeFloorAlpha → const int
CHROME: the pearl floor (the base colour over the bottom 15 % of the foil) at most at this alpha × 255.
y2kDarkPanelGloss → const int
y2kDarkShadowAlpha → const int
The key drop shadow's alpha × 255 on a dark scene.
y2kDeepFloorMilli → const int
Floor of the deep tone under the cap × 1000 (standard / Accessible); the pressed deep tone may sit 25 lower.
y2kEdgeDropMilli → const int
AERO rim: the face 0.24 darker (y2kAeroEdge).
y2kEdgeShadeRange → const IntRange
y2kEqualsMinDeltaE → const double
= must stand this far (min ΔEOK, normal + CVD) from the operators before the shared rule's 0.05 (pre-separation, as in Neo-brutalism).
y2kFaceFloorMilli → const int
The validator's face floor × 1000 (standard / Accessible): a little under the generator's, so the shared = / operator separation step has room (GuardrailRule.y2kLabelPolarity).
y2kFaceMaxLMilli → const int
Highest key face lightness the generator moves a face to × 1000.
y2kFaceMinLMilli → const int
Lowest generated key face lightness × 1000 (standard / Accessible).
y2kGelMinChroma → const double
A = gel paler than this (OKLCH chroma) is a last resort.
y2kGelStops → const List<double>
Gel body profile stops (fractions of the key's height): M, M, T, F, M, B (y2kGelProfile).
y2kGroundMinDeltaE → const double
AERO light skies: the digit and function faces (and in a meadow the gel faces too: green gels melt into grass) at least this far (ΔEOK) from the ground band behind the keypad (GuardrailRule.y2kGround); the generator aims at y2kGroundTargetDeltaE.
y2kGroundTargetDeltaE → const double
y2kHighlightRange → const IntRange
Renderer contract ranges (GuardrailRule.y2kGloss). The shadow's offset ≤ 3 dp and blur ≥ 5 dp keep it within y2kEdgeShadowAlpha just outside a key's edge.
y2kLabelStartLMilli → const int
Label search start lightness × 1000 (standard / Accessible).
y2kLabelZoneBottom → const double
y2kLabelZoneInset → const double
y2kLabelZoneTop → const double
The label zone: the middle 40 % of the key's height, inset 12 % at the sides. Only the opaque face profile and lighter overlays are drawn here.
y2kLightPanelGloss → const int
Display panel gloss alpha × 255 on a light / dark panel.
y2kMetalStops → const List<double>
Metal body profile stops: sky, sky, D1 (the horizon line), F, M, D2, ground (y2kMetalProfile).
y2kPanelDarkMaxMilli → const int
y2kPanelLightMinMilli → const int
Display panel lightness bands (GuardrailRule.y2kPanel): light panels ≥ 0.85, dark ≤ 0.24.
y2kRefractionRange → const IntRange
y2kShadowBlurCdp → const IntRange
y2kShadowOffsetCdp → const IntRange

Properties

y2kWaterGels → List<Y2kGel>
The AERO gels in deep water: azure and cornflower (indices 4 and 5, at their weights) become one cyan-leaning azure at L 0.80–0.82, so no operator or = shares the blue-lavender hue of the moonstone digits (L ≤ 0.755) and a blue gel stands clear of them by lightness while keeping its chroma (≈ 0.10–0.12; sRGB blues fade fast above L 0.8).
final

Functions

buildY2kAeroCandidate(int seed, FamilyDefinition def, int engineVersion, {int attempt = 0, bool accessible = false}) → ThemeSpec
Builds one unvalidated Y2K / Frutiger Aero candidate (the generator repairs and validates it, retries with palette/retry/n and finally falls back to a Y2K hero).
repairY2kAeroExtras(Palette p, ThemeSpec spec, GuardrailLimits lim, List<int> bgs) → Palette?
Family-specific repairs after labels and display texts (called from repairTheme for ThemeFamily.y2kChrome specs only); null = unrepairable. Each key face is lifted to the gel floor and its label re-solved on its label zone (the face lifting 0.03 at a time when the label alone cannot reach the floor); AERO rims follow the final faces; under the Accessible lock every rim, and always the focus ring, is solved to 3:1 on the scene and the keys' shadow.
validateY2kAero(ThemeSpec spec, GuardrailLimits lim, List<int> bgs) → List<GuardrailViolation>
The family-specific guardrails (called from validateTheme for ThemeFamily.y2kChrome specs only); bgs are the scene samples.
y2kAccentHueTenths(Palette p) → int
The accent hue (tenths): the operator face's hue.
y2kAeroDisplayBackdrops(ThemeSpec spec, List<int> surfaces, List<int> bgs) → List<int>?
Everything display text of spec may be drawn on, for displayBackdrops: the opaque panel, the panel under its gloss band and (CHROME) under the gloss and the glare (y2kPanelSamples); display text never sits on the bare scene.
y2kAeroEdge(int face) → int
The AERO rim of face: 0.24 darker, chroma × 0.9 (≥ 0.035), turned 8° toward green on yellow-greens (an olive rim reads dirty).
y2kAeroSceneSamples(BackgroundSpec bg) → List<int>?
Guardrail background samples of a Y2K scene: every opaque colour a key, label, rim, focus ring or the toolbar can sit over. H = the colour hull of colors[0..4] (meshHullSamples: stops, pairwise ¼ / ½ / ¾ points and centroids — every gradient blend, hill and floor); the light overlay (colors[5] at its alpha: sun glow, flare ghosts, god rays, foil crease, all budgeted to stack ≤ that alpha) over H; CHROME's pearl floor over H; and the bubble / sparkle tint at its highest alpha (a bubble's rim over its body, y2kBubbleMaxAlpha; a sparkle is one path) over all of those. Null for malformed specs (the generic sampling then applies, and GuardrailRule.y2kScene fails).
y2kCausticGlow(int face) → int
The caustic glow colour of face: lighter, softer.
y2kChromeEdge(int digitHueTenths) → int
The CHROME bezel hairline: a dark neutral in the digit hue.
y2kDeep(int face, int depthMilli, int floorMilli) → int
The deep tone of face: lightness lowered by depthMilli / 1000 but never below floorMilli / 1000, chroma × 1.12.
y2kDeepFloorFor({required bool accessible}) → int
The deep-tone floor × 1000 under the standard or Accessible lock.
y2kEdgeSamples(List<int> bgs, int shadow) → List<int>
What a key rim or the focus ring is seen against: the scene samples bgs, plain and under the keys' own shadow (y2kEdgeShadowAlpha).
y2kEdgeShadowAlpha(int shadow) → int
The key shadow's alpha × 255 just outside a key's edge, where its rim and the focus ring are seen: 0.9 of the drop shadow's alpha. The shadow is the face shifted down by the offset and blurred with σ = blur / 2, so directly under the bottom edge it covers Φ(offset / σ) ≤ Φ(3 / 2.5) ≈ 0.885 of its alpha for every style the generator draws (offset 2–3 dp, blur 5–8 dp).
y2kGelProfile(int face, int depthMilli, int floorMilli) → List<int>
The gel body colours at y2kGelStops for face: M, M, T, F, M, B with M = F + 0.025, T the deep tone (y2kDeep, depthMilli deep, floored at floorMilli) and B = F + 0.06.
y2kGroundSamples(BackgroundSpec bg) → List<int>
The ground band behind an AERO light keypad: colors[3], colors[4] and their midpoint. Behind the keys the scene painter draws only these and their blends (the haze ramp ends at the keypad's top edge, a meadow's far field is the midpoint and its near hill's crest is colors[3]; the lake glint, the sun's glow, flare ghosts and large bubbles stay beside the calculator), plus the micro-bubbles.
y2kIsMeadow(BackgroundSpec bg) → bool
Whether an AERO light scene's ground (colors[3]) is grass (a yellow- green to green hue) rather than a lake reflection.
y2kLabelZoneSamples(int face, {required int accentHueTenths, required int depthPermille, required int refractionPermille, bool metal = false, bool accessible = false}) → List<int>
Every colour a label of a key with face can sit on inside the label zone, at rest and pressed: the body profile (y2kGelProfile or, for metal faces, y2kMetalProfile) of the face and of its pressed face (y2kPressedFace at accentHueTenths), each adjacent pair of stops that reaches into the zone blended at 0, ½ and 1, every rest / pressed pair blended at 0, ¼, ½, ¾ and 1 (the renderer fades the pressed body over the rest body with paint alpha), plus the caustic glow at its in-zone peak (refractionPermille at rest, × 1.8 pressed). White overlays (cap, sheen, hover, streaks) only raise luminance, so they are not samples: labels are always darker than their faces.
y2kLightCap(BackgroundKind kind, {required bool dark}) → int
Overlay caps per scene kind and brightness: colors[5] alpha and the pattern opacity × 1000 (GuardrailRule.y2kScene).
y2kMetalProfile(int face, int depthMilli, int floorMilli) → List<int>
The satin-metal body colours at y2kMetalStops for face: a cool sky reflection, a dark horizon line D1 (floored like the gel's deep tone), the face, M, a dark band D2 and a warm ground reflection.
y2kMix(int a, int b, double t) → int
a → b at t per channel in encoded sRGB (what gradients and paint alpha blend in), opaque.
y2kPanelGlareAlpha(int surface) → int
The CHROME LCD glare streak's peak alpha × 255 for surface: half the gloss (rounded up).
y2kPanelGlossAlpha(int surface) → int
The display panel's gloss alpha × 255 for surface.
y2kPanelSamples(int surface, {bool chrome = false}) → List<int>
What display text sits on: the opaque panel, the panel under its gloss band and, on a chrome LCD, under the gloss with the glare streak crossing it (the lightest the panel gets).
y2kPatternCap(BackgroundKind kind, {required bool dark}) → int
Pattern opacity ceiling × 1000 (see y2kLightCap).
y2kPressedFace(int face, int accentHueTenths) → int
The pressed face (frame-1 cue): lightness − 0.025; a coloured gel (C ≥ 0.06) keeps its chroma and turns +20°, a near-neutral face takes chroma 0.07 at the accent hue + 20°.
y2kSceneOf(BackgroundSpec bg, {required bool dark}) → Y2kScene
The scene of bg (dark: the theme's brightness; AERO light scenes with a green ground are meadows).
y2kTone(int argb, int dLMilli, int cPermille) → int
argb with its OKLCH lightness moved by dLMilli / 1000 and its chroma scaled by cPermille / 1000 (hue kept), gamut-mapped.
y2kVaporPair(int a, int b) → bool
CHROME candies that pair warm pink with cool aqua or blueberry (bubblegum, grape, orchid × aqua, blueberry; strawberry × aqua): Vaporwave's signature, so the = pairing step skips them.