Foundations

Why this isn't just "themed Material"

Short answer: Odyssey starts from Material 3 — same 37 colour roles, same shape/elevation/typography scaffolding — because M3 Expressive is a genuinely good foundation, not a bar to escape. What makes a screen read as a specific bank, not a generic app, lives entirely in the layer above that scaffolding. This page shows what that layer is, live.

What's identical to stock Material 3

No fork, no divergent grid, no invented spacing system. Same 37 M3 colour roles (plus one Odyssey addition, success, since M3 ships none). Same shape/elevation/type scale conventions. A component library built straight on M3 gets you a correctly-structured app — Foundations covers this half in full. It just doesn't get you a bank's identity, because M3 was never designed to carry one on its own.

The five things layered on top

Brandprint-driven palette

One seed (a logo's dominant colour, converted to OKLCH) generates all 37 roles deterministically — a real client's exact hex, not a picker's closest preset.

Glass + signature motif

Per-brand translucency recipe and a named background pattern (sonar rings, coastal arcs, grid-spark, shield guilloché) — the thing that makes a card art panel or hero recognisably this bank at a glance.

Glow-based elevation

Dark mode lifts surfaces with a tint of the brand colour, not a flat black shadow that goes invisible against an already-dark background.

Per-brand motion signature

A CTA's sheen-sweep timing is derived from the brand's own easing/duration tokens — a calmer bank's buttons move slower than a snappier one's, not one fixed animation for everyone.

Content + feedback tone

Typography pairing, Arabic-first RTL (not retrofitted), Arabic-Indic numerals as an independent lever, and haptic/sound feedback weighted to the brand's declared personality.

The same three pieces, side by side

Left: a stock Material 3 baseline scheme (Google's own default seed, #6750A4) — correct, accessible, and completely generic. Right: the exact same three UI pieces, live-themed as Triton via tokens/themes.css — nothing on the right is a mockup; toggle Foundations to any of the four reference brands and every one of these five layers moves with it.

Material 3 baseline

Default seed colour, Roboto, no glass/motif/glow

Available balance

12,480.50

LYD · •••• 4821

Coffee Bar
−4.50
Salary
+3,200.00

Neptune Odyssey — Triton

Same 37 M3 roles, generated from Triton's brandprint seed

Available balance

12,480.50

LYD · •••• 4821

Coffee Bar
−4.50
Salary
+3,200.00

Feature by feature

DimensionMaterial 3 baselineNeptune Odyssey
ColourOne seed hue, Google's tonal palette algorithmAny brand's real hex → OKLCH seed → all 37 roles, contrast-checked per brand
Elevation (dark)Flat black shadow — often invisible on a dark surfaceGlow tinted toward the brand primary — reads as ambient light, not a cast shadow
MotionOne default easing/duration for every appPer-brand signature timing (a CTA's sheen/nudge cycle is derived from the brand's own motion tokens)
Background textureFlat surface colourA named signature motif per brand (sonar rings / coastal arcs / grid-spark / guilloché) at brand-tuned strength
TypographyRoboto, one weight rampPer-brand display + text + numeral face pairing, with a dedicated Arabic face swap under RTL — not a fallback font
NumeralsTied to localeIndependent lever — Arabic-Indic digits under an English UI or vice versa, a real requirement for Gulf/Libyan banking apps
FeedbackNone built inHaptic weight + sound-cue hooks, tuned per brand's declared content tone
Platform reachAndroid-first originSame brandprint resolves identically on Flutter, web (React/Vue/Svelte/Laravel), byte-pinned in tests

Proof it's not a reskin bolted on top

A reskin would mean the identity layer is optional decoration a component can ship without. It isn't: neptune_flutter_ui's CI gate literally greps every widget file for a literal colour, radius, or font — the rulebook calls a screen that reads M3 roles correctly but ships no gradient, glass, motif, glow or brand type "a grey Material mockup, not done." A recent full-suite audit checked all 89 web components against Flutter's 149 classes for capability gaps and found exactly one (a missing app-bar layout variant, fixed) — the two implementations aren't approximating each other, they're the same design system twice.

GlassNeptuneGlass, NptIdentity.glassTint()
MotifNeptuneMotifLayer
Glow elevationNptIdentity.elevation1..5
Motion signatureNptMotion, derived per brand in NeptuneCta
Feedback toneNptFeedback, hapticWeightFor()