feat(engine): Terminal design-token system in ui_theme
Replaces the prior Premium-Solitaire palette and ad-hoc constants with the full Terminal (base16-eighties) token set: near-black surface ramp, cyan primary CTA, lime/lavender/gold/teal/pink semantic accents, 5-rung type scale, 7-rung 4-multiple spacing scale, 3-step radius, 14-rung z-index hierarchy, and a complete motion budget. Card drop-shadow alphas pinned to 0 — Terminal depth is 1px borders + tonal layering, not box-shadow. Tokens stay as `pub const` so static contexts (default Sprite colours etc.) keep compiling; a future UiTheme resource can layer runtime switching on top without breaking the constant API. Four unit tests pin the spacing/type/z-index invariants so a careless edit can't silently break the scale. Plugin-by-plugin migration to consume these tokens follows in subsequent commits. Spec: docs/ui-mockups/design-system.md Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -8,12 +8,12 @@
|
|||||||
//! engine; collapsing them into one source of truth keeps the visual
|
//! engine; collapsing them into one source of truth keeps the visual
|
||||||
//! system coherent and makes future palette swaps a single-file change.
|
//! system coherent and makes future palette swaps a single-file change.
|
||||||
//!
|
//!
|
||||||
//! Palette is "Midnight Purple + Balatro accent" — see the 2026-04-30
|
//! Palette is "Terminal" (base16-eighties) — see
|
||||||
//! UX overhaul Phase 2 proposal for the rationale behind specific
|
//! `docs/ui-mockups/design-system.md` for the full token spec, mockup
|
||||||
//! values. The tokens are exposed as `pub const` so static contexts
|
//! library, and rationale. The tokens are exposed as `pub const` so
|
||||||
//! (default colours on Sprite components, etc.) can use them; a future
|
//! static contexts (default colours on Sprite components, etc.) can use
|
||||||
//! `UiTheme` resource can layer runtime switching on top without
|
//! them; a future `UiTheme` resource can layer runtime switching on top
|
||||||
//! changing the constant API.
|
//! without changing the constant API.
|
||||||
|
|
||||||
use bevy::color::Color;
|
use bevy::color::Color;
|
||||||
use bevy::math::Vec2;
|
use bevy::math::Vec2;
|
||||||
@@ -21,93 +21,96 @@ use bevy::prelude::Val;
|
|||||||
use solitaire_data::AnimSpeed;
|
use solitaire_data::AnimSpeed;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Colours — Midnight Purple base with a Balatro-yellow primary accent.
|
// Colours — Terminal (base16-eighties): near-black surface ramp with a cyan
|
||||||
|
// primary accent and lime/lavender/gold/teal/pink semantic accents.
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Window backstop and the default text colour on top of `ACCENT_PRIMARY`.
|
/// Window backstop and the default text colour on top of `ACCENT_PRIMARY`.
|
||||||
/// Deep midnight purple, near-black. `#1A0F2E`.
|
/// Near-black terminal background. `#151515`.
|
||||||
pub const BG_BASE: Color = Color::srgb(0.102, 0.059, 0.180);
|
pub const BG_BASE: Color = Color::srgb(0.082, 0.082, 0.082);
|
||||||
|
|
||||||
/// Elevated surface — modal cards, popover panels, button backgrounds.
|
/// Elevated surface — modal cards, popover panels, button backgrounds.
|
||||||
/// One step lighter than `BG_BASE` so cards visually float above the
|
/// One step lighter than `BG_BASE` so cards read as a separate plane
|
||||||
/// felt without needing real drop shadows. `#2D1B69`.
|
/// without needing drop shadows. `#202020`.
|
||||||
pub const BG_ELEVATED: Color = Color::srgb(0.176, 0.106, 0.412);
|
pub const BG_ELEVATED: Color = Color::srgb(0.125, 0.125, 0.125);
|
||||||
|
|
||||||
/// Hovered/highlighted surface — used on button hover and on the
|
/// Hovered/highlighted surface — used on button hover and on the
|
||||||
/// currently-active row of a popover. `#3A2580`.
|
/// currently-active row of a popover. `#2a2a2a`.
|
||||||
pub const BG_ELEVATED_HI: Color = Color::srgb(0.227, 0.145, 0.502);
|
pub const BG_ELEVATED_HI: Color = Color::srgb(0.165, 0.165, 0.165);
|
||||||
|
|
||||||
/// Top elevation step — Secondary button hover, popover currently-
|
/// Top elevation step — Secondary button hover, popover currently-
|
||||||
/// hovered row. One rung above `BG_ELEVATED_HI`. `#482F97`.
|
/// hovered row. One rung above `BG_ELEVATED_HI`. `#353535`.
|
||||||
pub const BG_ELEVATED_TOP: Color = Color::srgb(0.282, 0.184, 0.592);
|
pub const BG_ELEVATED_TOP: Color = Color::srgb(0.208, 0.208, 0.208);
|
||||||
|
|
||||||
/// Pressed-button surface — `BG_ELEVATED` darkened ~15%. `#26155B`.
|
/// Pressed-button surface — sits below `BG_ELEVATED` so a press reads
|
||||||
pub const BG_ELEVATED_PRESSED: Color = Color::srgb(0.149, 0.082, 0.357);
|
/// as the surface receding rather than rising. `#1a1a1a`.
|
||||||
|
pub const BG_ELEVATED_PRESSED: Color = Color::srgb(0.102, 0.102, 0.102);
|
||||||
|
|
||||||
/// Uniform scrim under every modal. The audit found 0.60–0.92 alpha
|
/// Uniform scrim under every modal. Per the Terminal design system,
|
||||||
/// drift across 11 overlay plugins; this single value replaces all of
|
/// modals dim the table aggressively (95% opacity) without blurring,
|
||||||
/// them. `rgba(13, 7, 28, 0.85)`.
|
/// to maintain the crisp synthwave-flat aesthetic. `rgba(21, 21, 21, 0.95)`.
|
||||||
pub const SCRIM: Color = Color::srgba(0.051, 0.027, 0.110, 0.85);
|
pub const SCRIM: Color = Color::srgba(0.082, 0.082, 0.082, 0.95);
|
||||||
|
|
||||||
/// Translucent fill for the top-of-window HUD band painted by
|
/// Solid fill for the top-of-window HUD band painted by
|
||||||
/// `hud_plugin::spawn_hud_band`. Same midnight-purple hue as `BG_BASE`,
|
/// `hud_plugin::spawn_hud_band`. Terminal HUD chips are opaque
|
||||||
/// but at 0.70 alpha so the green felt reads through subtly — enough
|
/// `surface-container` panels — no transparency — so the chrome reads
|
||||||
/// to mark the band as "UI" without feeling like a hard chrome strip.
|
/// as a status-line strip rather than a glassy overlay. `#202020`.
|
||||||
/// `rgba(26, 15, 46, 0.70)`.
|
pub const BG_HUD_BAND: Color = Color::srgba(0.125, 0.125, 0.125, 1.0);
|
||||||
pub const BG_HUD_BAND: Color = Color::srgba(0.102, 0.059, 0.180, 0.70);
|
|
||||||
|
|
||||||
/// Primary text — warm off-white with a hint of purple to fit the
|
/// Primary text — warm off-white. The base16-eighties foreground.
|
||||||
/// midnight palette without feeling clinical. `#F5F0FF`.
|
/// `#d0d0d0`.
|
||||||
pub const TEXT_PRIMARY: Color = Color::srgb(0.961, 0.941, 1.000);
|
pub const TEXT_PRIMARY: Color = Color::srgb(0.816, 0.816, 0.816);
|
||||||
|
|
||||||
/// Secondary text — captions, hints, muted labels. Lavender-grey.
|
/// Secondary text — captions, hints, muted labels. `#a0a0a0`.
|
||||||
/// `#B5A8D5`.
|
pub const TEXT_SECONDARY: Color = Color::srgb(0.627, 0.627, 0.627);
|
||||||
pub const TEXT_SECONDARY: Color = Color::srgb(0.710, 0.659, 0.835);
|
|
||||||
|
|
||||||
/// Disabled text — greyed-out buttons, locked items. `#6B5F85`.
|
/// Disabled text — greyed-out buttons, locked items. `#505050`.
|
||||||
pub const TEXT_DISABLED: Color = Color::srgb(0.420, 0.373, 0.522);
|
pub const TEXT_DISABLED: Color = Color::srgb(0.314, 0.314, 0.314);
|
||||||
|
|
||||||
/// Balatro-yellow primary accent — the loudest colour in the palette.
|
/// Cyan primary accent — the CTA colour of the system. Reserved for
|
||||||
/// Reserved for primary actions (Confirm, Play Again), win states, and
|
/// primary actions (Play, Resume, Save), focus rings, and selection.
|
||||||
/// "look here" callouts. `BG_BASE` text on top of this colour passes
|
/// `BG_BASE` text on top of this colour passes AAA contrast. `#6fc2ef`.
|
||||||
/// AAA contrast. `#FFD23F`.
|
pub const ACCENT_PRIMARY: Color = Color::srgb(0.435, 0.761, 0.937);
|
||||||
pub const ACCENT_PRIMARY: Color = Color::srgb(1.000, 0.824, 0.247);
|
|
||||||
|
|
||||||
/// Brightened `ACCENT_PRIMARY` for hover states on primary buttons.
|
/// Brightened `ACCENT_PRIMARY` for hover states on primary buttons.
|
||||||
/// Picks up saturation while keeping the same hue. `#FFE36B`.
|
/// Picks up luminance while keeping the same hue. `#a8dcf5`.
|
||||||
pub const ACCENT_PRIMARY_HOVER: Color = Color::srgb(1.000, 0.890, 0.420);
|
pub const ACCENT_PRIMARY_HOVER: Color = Color::srgb(0.659, 0.863, 0.961);
|
||||||
|
|
||||||
/// Warm magenta secondary accent — celebratory states (achievement
|
/// Lavender secondary accent — celebratory states (level-up,
|
||||||
/// unlocked, streak milestones). Used sparingly so it stays special.
|
/// achievement unlocked, streak milestones). Used sparingly so it stays
|
||||||
/// `#FF6B9D`.
|
/// special. `#e1a3ee`.
|
||||||
pub const ACCENT_SECONDARY: Color = Color::srgb(1.000, 0.420, 0.616);
|
pub const ACCENT_SECONDARY: Color = Color::srgb(0.882, 0.639, 0.933);
|
||||||
|
|
||||||
/// Success — foundation completion, valid drop tint, sync OK. `#4ADE80`.
|
/// Success — foundation completion, valid drop tint, sync OK. Lime
|
||||||
pub const STATE_SUCCESS: Color = Color::srgb(0.290, 0.871, 0.502);
|
/// from base16-eighties. `#acc267`.
|
||||||
|
pub const STATE_SUCCESS: Color = Color::srgb(0.675, 0.761, 0.404);
|
||||||
|
|
||||||
/// Warning — penalty signal. **Both** Undo and Recycle counters use
|
/// Warning — penalty signal, daily-seed expiry countdown, sync-pending
|
||||||
/// this when non-zero (the audit found these were inconsistent — Undos
|
/// status. Gold from base16-eighties. **Both** Undo and Recycle
|
||||||
/// amber, Recycles white). `#FFA94D`.
|
/// counters use this when non-zero. `#ddb26f`.
|
||||||
pub const STATE_WARNING: Color = Color::srgb(1.000, 0.663, 0.302);
|
pub const STATE_WARNING: Color = Color::srgb(0.867, 0.698, 0.435);
|
||||||
|
|
||||||
/// Danger — rejection shake, illegal placement, sync error. `#F77272`.
|
/// Danger — rejection shake, illegal placement, sync error. Pink from
|
||||||
pub const STATE_DANGER: Color = Color::srgb(0.969, 0.447, 0.447);
|
/// base16-eighties (also doubles as `suit-red` per the design.md
|
||||||
|
/// rationale). `#fb9fb1`.
|
||||||
|
pub const STATE_DANGER: Color = Color::srgb(0.984, 0.624, 0.694);
|
||||||
|
|
||||||
/// Info — daily-challenge constraint, draw-cycle indicator. `#6BBBFF`.
|
/// Info — neutral system toasts, sync-connected indicator. Teal from
|
||||||
pub const STATE_INFO: Color = Color::srgb(0.420, 0.733, 1.000);
|
/// base16-eighties. `#12cfc0`.
|
||||||
|
pub const STATE_INFO: Color = Color::srgb(0.071, 0.812, 0.753);
|
||||||
|
|
||||||
/// Soft fill colour for the drop-target overlay shown over every legal
|
/// Soft fill colour for the drop-target overlay shown over every legal
|
||||||
/// destination pile while the player is dragging a card. Same green hue
|
/// destination pile while the player is dragging a card. Same lime hue
|
||||||
/// as `STATE_SUCCESS` (`#4ADE80`) so the visual language stays
|
/// as `STATE_SUCCESS` (`#acc267`) so the visual language stays
|
||||||
/// consistent, but at 10 % alpha so the underlying card faces remain
|
/// consistent, but at 10 % alpha so the underlying card faces remain
|
||||||
/// fully readable through the wash.
|
/// fully readable through the wash.
|
||||||
pub const DROP_TARGET_FILL: Color = Color::srgba(0.290, 0.871, 0.502, 0.10);
|
pub const DROP_TARGET_FILL: Color = Color::srgba(0.675, 0.761, 0.404, 0.10);
|
||||||
|
|
||||||
/// Outline colour for the drop-target overlay. Matches the
|
/// Outline colour for the drop-target overlay. Matches the
|
||||||
/// `STATE_SUCCESS` hue at 75 % alpha so the rectangle border reads
|
/// `STATE_SUCCESS` hue at 75 % alpha so the rectangle border reads
|
||||||
/// unmistakably against both the felt and stacked card faces without
|
/// unmistakably against both the felt and stacked card faces without
|
||||||
/// drowning the cards themselves.
|
/// drowning the cards themselves.
|
||||||
pub const DROP_TARGET_OUTLINE: Color = Color::srgba(0.290, 0.871, 0.502, 0.75);
|
pub const DROP_TARGET_OUTLINE: Color = Color::srgba(0.675, 0.761, 0.404, 0.75);
|
||||||
|
|
||||||
/// Thickness of the drop-target outline edges, in world-space pixels.
|
/// Thickness of the drop-target outline edges, in world-space pixels.
|
||||||
pub const DROP_TARGET_OUTLINE_PX: f32 = 3.0;
|
pub const DROP_TARGET_OUTLINE_PX: f32 = 3.0;
|
||||||
@@ -131,7 +134,7 @@ pub const STOCK_BADGE_BG: Color = BG_ELEVATED_HI;
|
|||||||
/// Foreground (text) colour of the stock-pile remaining-count chip.
|
/// Foreground (text) colour of the stock-pile remaining-count chip.
|
||||||
///
|
///
|
||||||
/// `ACCENT_PRIMARY` keeps the chip readable against the elevated
|
/// `ACCENT_PRIMARY` keeps the chip readable against the elevated
|
||||||
/// purple background and matches the Balatro accent already used for
|
/// surface background and matches the cyan accent already used for
|
||||||
/// other "look here" callouts.
|
/// other "look here" callouts.
|
||||||
pub const STOCK_BADGE_FG: Color = ACCENT_PRIMARY;
|
pub const STOCK_BADGE_FG: Color = ACCENT_PRIMARY;
|
||||||
|
|
||||||
@@ -159,15 +162,19 @@ pub const Z_STOCK_BADGE: f32 = 30.0;
|
|||||||
/// separately via [`CARD_SHADOW_ALPHA_IDLE`] / [`CARD_SHADOW_ALPHA_DRAG`].
|
/// separately via [`CARD_SHADOW_ALPHA_IDLE`] / [`CARD_SHADOW_ALPHA_DRAG`].
|
||||||
pub const CARD_SHADOW_COLOR: Color = Color::srgb(0.0, 0.0, 0.0);
|
pub const CARD_SHADOW_COLOR: Color = Color::srgb(0.0, 0.0, 0.0);
|
||||||
|
|
||||||
/// Alpha for the resting-state card shadow. Low enough that 52 stacked
|
/// Alpha for the resting-state card shadow. Set to 0 under the Terminal
|
||||||
/// shadows do not darken the felt into a uniform smear, high enough that
|
/// design system: depth is achieved through 1px suit-color borders and
|
||||||
/// each card reads as separated from the surface.
|
/// tonal layering, not blur shadows ("no `box-shadow` anywhere" is a
|
||||||
pub const CARD_SHADOW_ALPHA_IDLE: f32 = 0.25;
|
/// hard constraint of `docs/ui-mockups/design-system.md`). The shadow
|
||||||
|
/// rendering code path is left in place so a future palette swap can
|
||||||
|
/// re-enable it without touching consumers.
|
||||||
|
pub const CARD_SHADOW_ALPHA_IDLE: f32 = 0.0;
|
||||||
|
|
||||||
/// Alpha for the lifted/dragged card shadow. Stronger than the idle value
|
/// Alpha for the lifted/dragged card shadow. Set to 0 for the same
|
||||||
/// so the dragged stack visibly "casts more shadow" while the player holds
|
/// reason as [`CARD_SHADOW_ALPHA_IDLE`]. Drag affordance under the
|
||||||
/// it above the table.
|
/// Terminal system is the cyan focus glow + z-index lift, not a deeper
|
||||||
pub const CARD_SHADOW_ALPHA_DRAG: f32 = 0.40;
|
/// shadow.
|
||||||
|
pub const CARD_SHADOW_ALPHA_DRAG: f32 = 0.0;
|
||||||
|
|
||||||
/// World-space pixel offset of the resting-state card shadow relative to
|
/// World-space pixel offset of the resting-state card shadow relative to
|
||||||
/// its parent card centre. Down-and-right matches a soft top-left light
|
/// its parent card centre. Down-and-right matches a soft top-left light
|
||||||
@@ -197,15 +204,20 @@ pub const CARD_SHADOW_PADDING_DRAG: Vec2 = Vec2::new(8.0, 8.0);
|
|||||||
pub const CARD_SHADOW_LOCAL_Z: f32 = -0.05;
|
pub const CARD_SHADOW_LOCAL_Z: f32 = -0.05;
|
||||||
|
|
||||||
/// Subtle border — default popover, card, and idle button outline.
|
/// Subtle border — default popover, card, and idle button outline.
|
||||||
pub const BORDER_SUBTLE: Color = Color::srgba(0.647, 0.549, 1.000, 0.12);
|
/// `outline-variant` from the design system at full alpha; the Terminal
|
||||||
|
/// aesthetic uses solid 1px borders rather than translucent washes.
|
||||||
|
/// `#353535`.
|
||||||
|
pub const BORDER_SUBTLE: Color = Color::srgba(0.208, 0.208, 0.208, 1.0);
|
||||||
|
|
||||||
/// Strong border — hover outline, focused button, active popover.
|
/// Strong border — hover outline, focused button, active popover.
|
||||||
pub const BORDER_STRONG: Color = Color::srgba(0.647, 0.549, 1.000, 0.30);
|
/// `outline` from the design system. `#505050`.
|
||||||
|
pub const BORDER_STRONG: Color = Color::srgba(0.314, 0.314, 0.314, 1.0);
|
||||||
|
|
||||||
/// 2 px ring drawn around the focused interactive element. Balatro yellow
|
/// 2 px ring drawn around the focused interactive element. Cyan
|
||||||
/// (matches `ACCENT_PRIMARY`) at 85% alpha so the ring stays legible
|
/// (matches `ACCENT_PRIMARY`) at 85% alpha so the ring stays legible
|
||||||
/// against both elevated surfaces and the modal scrim backdrop.
|
/// against both elevated surfaces and the modal scrim backdrop.
|
||||||
pub const FOCUS_RING: Color = Color::srgba(1.0, 0.823, 0.247, 0.85);
|
/// `rgba(111, 194, 239, 0.85)`.
|
||||||
|
pub const FOCUS_RING: Color = Color::srgba(0.435, 0.761, 0.937, 0.85);
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Typography scale (px) — 5 rungs replace the prior
|
// Typography scale (px) — 5 rungs replace the prior
|
||||||
|
|||||||
Reference in New Issue
Block a user