Files
Ferrous-Solitaire/docs/ui-mockups/design-system.md
T
funman300 dd970215cc fix(engine): drop card-face border to remove gray-corner artifact
Player feedback after the 2-colour revert: "I do not like the
grey corners on the cards." The visible artifact was anti-
aliasing physics — the 1 px suit-coloured stroke (red for
hearts/diamonds, near-white for clubs/spades) faded through
gray pixels into the dark play surface at each rounded corner,
producing a visible "gray sliver" at the four arcs of every
card.

Fix: drop the stroke entirely. The card body fill defines the
shape against the play surface; the 5-unit brightness gap
between `#1a1a1a` body and `#151515` surface is enough to read
as a card edge without an explicit stroke. Anti-aliasing on a
fill-only rounded rect blends `#1a1a1a → #151515` over a few
pixels — barely perceptible compared to the
`stroke → transparent` gradient that produced the artifact.

### Changes

- `card_face_svg.rs`: removed `stroke="{colour}" stroke-width="2"`
  from the card body rect. Reverted the 1 px stroke inset back
  to `(x=0, y=0, width=256, height=384)` since there's no
  longer a stroke to keep inside the pixmap. Module-level
  comment updated to document the reasoning.
- `design-system.md` § Game Cards line 225 updated: "Border:
  1px solid in suit color" → "Border: none." with the
  artifact rationale recorded as audit trail.
- `card_face_svg_pin.rs` rebaselined: all 52 face hashes drift
  (every card's perimeter pixels changed); 5 back hashes
  unchanged.

Workspace clippy + cargo test --workspace clean. 1191 passing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 12:41:54 -07:00

15 KiB
Raw Blame History

name, colors, typography, rounded, spacing
name colors typography rounded spacing
Terminal
surface surface-dim surface-bright surface-container-lowest surface-container-low surface-container surface-container-high surface-container-highest on-surface on-surface-variant inverse-surface inverse-on-surface outline outline-variant surface-tint primary on-primary primary-container on-primary-container inverse-primary secondary on-secondary secondary-container on-secondary-container tertiary on-tertiary tertiary-container on-tertiary-container error on-error error-container on-error-container background on-background surface-variant suit-red suit-black suit-red-cb highlight-valid highlight-celebration highlight-warning highlight-info
#151515 #0d0d0d #2a2a2a #0a0a0a #1a1a1a #202020 #2a2a2a #353535 #d0d0d0 #a0a0a0 #d0d0d0 #151515 #505050 #353535 #a54242 #a54242 #151515 #3a1f1f #d5a8a8 #993e3e #acc267 #151515 #2a3320 #c5d585 #e1a3ee #151515 #3a2a40 #eec3f5 #fb9fb1 #151515 #4a2530 #fdc3ce #151515 #d0d0d0 #353535 #fb9fb1 #d0d0d0 #acc267 #acc267 #e1a3ee #ddb26f #12cfc0
hud-score hud-timer card-rank body-md label-caps headline
fontFamily fontSize fontWeight lineHeight letterSpacing
JetBrains Mono 24px 700 32px -0.02em
fontFamily fontSize fontWeight lineHeight
JetBrains Mono 16px 400 24px
fontFamily fontSize fontWeight lineHeight
JetBrains Mono 18px 700 18px
fontFamily fontSize fontWeight lineHeight
Inter 16px 400 24px
fontFamily fontSize fontWeight lineHeight letterSpacing
JetBrains Mono 12px 500 16px 0.08em
fontFamily fontSize fontWeight lineHeight letterSpacing
JetBrains Mono 28px 700 32px -0.01em
sm DEFAULT md lg xl full
0.125rem 0.25rem 0.5rem 0.75rem 1rem 9999px
margin-edge gutter-card stack-overlap touch-target-min
1rem 0.375rem 2rem 48dp

Brand & Style

The "Terminal" design system replaces the previous "Premium Solitaire" calm-indie aesthetic with a retro-terminal / synthwave identity. The intent is the visual confidence of a well-tuned terminal emulator (think Berkeley Mono dotfiles, base16-eighties, CRT phosphor): monospaced, dense, legible, snappy. It is not casino-glitz, not skeuomorphic felt, and not whimsical.

The personality is technical, deliberate, slightly playful. Cards are flat with thin colored strokes; the HUD reads like a status bar; modals look like terminal panes. Motion is short and snap-easing — no bouncy springs. Long-session calm is preserved by keeping the chroma low and reserving saturated accents for meaning (CTAs, feedback, celebrations) rather than decoration.

Influences: base16-eighties (Chris Kempson), Berkeley Mono, Vim/Neovim status lines, the iA Writer aesthetic, classic CRT phosphor with no chromatic aberration.

Palette

The palette is base16-eighties — a 16-slot terminal palette where indices 0007 form a monochrome ramp and 080F provide saturated accents. We map base16 slots to Material Design 3 token roles below.

Source palette (base16-eighties)

Slot Hex Role
base00 #151515 background
base01 #202020 surface-container
base02 #303030 line-highlight (subtle)
base03 #505050 outline / muted text
base04 #b0b0b0 secondary text
base05 #d0d0d0 foreground / on-surface
base06 #e0e0e0 bright text
base07 #f5f5f5 brightest highlight
base08 #fb9fb1 red — used for error, suit-red
base09 #ddb26f orange — used for warning chips
base0A #acc267 yellow/lime — used for highlight-valid (drag targets, valid moves)
base0B #12cfc0 green/teal — used for highlight-info (toasts, neutral status)
base0C #6fc2ef cyan/sky — historically the primary CTA; now reserved for ad-hoc accents only
base0D #6fc2ef (alias)
base08 (project) #a54242 brick red — primary CTA, focus ring, selection (project-specific extension; the base16-eighties base08 slot is #fb9fb1 pink which we keep as error/suit-red)
suit-red-cb slot #acc267 lime — color-blind-mode swap for red suits (was #6fc2ef cyan before the 2026-05-08 primary-accent swap; lime is the next-best non-red base16-eighties accent)
base0E #e1a3ee violet — used for celebration (level-up, achievement unlock)
base0F #fb9fb1 (alias)

Semantic assignments

  • CTA / Primary action: brick red #a54242. Reserved for "Play," "New Game," "Save," "Resume," and the focus ring on selected cards. Never used decoratively. (Was cyan #6fc2ef before the 2026-05-08 swap.)
  • Valid-move / drag-target highlight: lime #acc267. Reserved for in-game feedback only. Never appears in chrome.
  • Celebration: lavender #e1a3ee. Used for level-up flashes, achievement unlock cards, and the daily-streak chip when the streak is active. Quiet otherwise.
  • Warning / soft alert: gold #ddb26f. Used for "challenge expires in N minutes" chips, sync-pending status, and the daily-seed countdown.
  • Info: teal #12cfc0. Used for neutral system toasts and the sync-connected indicator.
  • Error: pink #fb9fb1. Used for sync conflict, server unreachable, invalid move shake.

Suit Colors

Two-color traditional pairing, with mandatory color-blind support. Saturated red for hearts + diamonds, near-white for clubs

  • spades — the "Microsoft Solitaire on dark mode" feel of a real playing-card deck. (A brief 4-color-deck experiment shipped between v0.21.0 and the next post-cut commit; reverted to traditional 2-color at the player's request.)
Suit Default Color-blind mode Glyph differentiation
Hearts #e35353 (saturated red) #acc267 (lime) Solid filled glyph
Diamonds #e35353 (saturated red) #acc267 (lime) Outlined glyph (1.5px stroke)
Spades #e8e8e8 (near-white) #e8e8e8 (unchanged) Solid filled glyph
Clubs #e8e8e8 (near-white) #e8e8e8 (unchanged) Outlined glyph (1.5px stroke)

The outlined-glyph treatment is the primary differentiation mechanism. Color is supplementary. This means a player viewing the game on a monochrome display, or with severe red-green deficiency, can still distinguish all four suits without context. This is a hard requirement, not an optional setting.

The "color-blind mode" toggle in Settings swaps both red suits (hearts + diamonds) from #e35353 to #acc267 (lime); clubs + spades stay at the near-white. The toggle does not turn the outlined glyphs on or off, because outlined glyphs are always on. (Was red→cyan before the 2026-05-08 primary-accent swap; CBM moved to lime to stay hue-distinct from the new red-family primary.)

Typography

Monospace-forward, dual-font system.

  • JetBrains Mono is used for: HUD (score, timer, moves), card rank/value text, all labels, all headlines, all numerals anywhere in the app, and any chip-style component. This is the dominant face.
  • Inter is used only for: long-form body copy (Help screen, Settings descriptions, achievement tooltips, onboarding copy). It is the exception, not the default.

Weights: 400 regular, 500 medium for labels, 700 bold for HUD numbers and headlines. No 600 / no italics anywhere — the terminal aesthetic doesn't have them.

Letter spacing: tight (-0.02em) on HUD score for visual mass; wide (+0.08em) on uppercase labels for readability at 12px. Body uses default (0).

HUD numbers must use tabular figures (font-feature-settings: 'tnum') so the timer and score don't reflow as digits change.

Layout & Spacing

Optimized for Android portrait, 390×844 (Pixel 6 baseline), API 34.

  • Margins: 16px (1rem) edge safety margin. Tighter than the previous system's 24px. Eighties palettes are dense by nature; over-padding fights the aesthetic.
  • Tableau: 7-column layout, 32px (2rem) vertical card overlap. Tighter than before to fit a longer cascade on phone screens.
  • HUD position: top of screen, in the system safe area. Bottom 64px holds the action bar (Undo / Hint / New Game / Auto-complete). Action bar is always visible in-game — no hover-fade — because there is no hover on touch.
  • Touch target minimum: 48dp on all interactive elements. Cards in the tableau may be smaller visually but use a 48dp invisible hit area centered on the visible glyph.

Elevation & Depth

Depth is created through tonal layering and 1px outlines, not blur shadows. (Synthwave-flat, not Material-soft.)

  • Level 0 (Background): the #151515 base canvas.
  • Level 1 (Tableau slots, empty piles): 1px dashed outline in #353535. Empty foundations show a faint suit glyph at 12% opacity inside the outline.
  • Level 2 (Cards at rest): solid #1a1a1a fill, 1px solid border in the suit color (so the suit is detectable at a glance even if the card is partially obscured).
  • Level 3 (Active / dragged card): same border, but glow effect: 0 0 12px of #a54242 at 40% opacity. No scale transform — flatness preserved. Z-index lifts above siblings.
  • Modals: full-screen with backdrop #151515 at 95% opacity (just enough to dim the table without blurring it). Modal panel is #202020 with a 1px #505050 border — like a terminal pane.
  • Toasts: bottom of screen, #202020 fill, 1px border in the toast's accent color (info=teal, warning=gold, error=pink, celebration=lavender). 16px monospaced caption.

No box-shadow is used anywhere. All depth is achieved with borders and tonal value. This is a hard constraint.

Shapes

The shape language is soft-rounded but tight:

  • Cards: rounded-md (8px) — slightly less rounded than the previous system's 16px to read more "technical."
  • Buttons / chips / inputs: rounded (4px) default, rounded-sm (2px) for the smallest chips.
  • Modals / sheets: rounded-lg (12px).
  • Avatars / circular indicators: rounded-full.
  • Card-back pattern corners: matches the card's rounded-md.

Selection highlights use a 2px inset stroke in #a54242 following the host shape's corner radius. Never an outer stroke — the outer stroke is reserved for the suit-color hairline.

Motion

Snappy, no spring. All transitions use ease-out with a 120ms duration unless specified.

  • Card lift (start drag): 80ms.
  • Card place (drop): 120ms with a 16ms holdframe (no bounce).
  • Modal enter: 200ms ease-out, fade + 8px translate-up.
  • Modal exit: 120ms ease-in, fade only.
  • Selection ring appear: 80ms.
  • Win-summary stat reveal: 60ms each, staggered 40ms.
  • HUD number tick: instant (no transition) — terminal counters don't ease.

Optional CRT effect: a 1-frame scanline sweep across the screen on game-state transitions (start, win, restart). User-toggleable in Settings. Off by default.

Components

Game Cards

Flat face design.

  • Background: #1a1a1a
  • Border: none. The card shape is defined by the body fill alone against the play surface. The earlier 1px suit-coloured border was removed because it produced visible anti-aliasing artifacts at the rounded corners (a "gray sliver" where the colored stroke faded through gray pixels into the dark play surface). The 5-unit brightness gap between #1a1a1a body and #151515 surface is enough to read as a card edge without an explicit stroke.
  • Top-left: rank in JetBrains Mono Bold 18px + small suit glyph (10px)
  • Bottom-right: large suit glyph (32px), upright (same orientation as the top-left small glyph — single-orientation digital play does not benefit from the traditional 180° inverted-corner indicator)
  • Corner radius: 8px
  • Suit differentiation: hearts and spades have filled glyphs; diamonds and clubs have outlined glyphs (1.5px stroke)

Card Back ("Terminal" theme)

  • Theme name: "Terminal"
  • Author: "Rusty Solitaire"
  • Background: #151515
  • Pattern: horizontal scanlines at 2px pitch in #1a1a1a (1px line, 1px gap), full bleed
  • Border: 1px solid #353535
  • Top-left badge: a 12×16px solid #a54242 block (the "terminal cursor"), 6px from the corner
  • Bottom-right monogram: the characters ▌RS in JetBrains Mono 12px, color #505050, 6px from the corner
  • Corner radius: 8px (matches face)

Primary Buttons

Solid #a54242 fill, #151515 text, JetBrains Mono Medium 14px uppercase with +0.08em tracking. 4px corner radius. Pressed state: darken to #7a3030. Disabled: #353535 fill, #505050 text.

Secondary Buttons

Transparent fill, 1px #505050 border, #d0d0d0 text. Hover/press: border becomes #a54242, text becomes #a54242.

HUD Chips

#202020 fill, no border, 4px radius. Monospaced 16px text. Score chip pulses to #acc267 for 200ms when score increases.

Drag Targets

When a card is being dragged over a valid pile, the pile's empty-slot dashed outline becomes:

  • Solid 1px in #acc267
  • Plus a 0 0 8px outer glow in #acc267 at 30% opacity

This is the only place glow effects appear in the system.

Modals

Full-screen backdrop at 95% opacity. Centered panel: #202020 fill, 1px #505050 border, 12px corner radius. Title bar shows the screen name in monospaced 14px, color #a0a0a0, with a single cursor character prefix to reinforce the terminal pane motif.

Navigation Bar

Fixed at the bottom of in-game screens. Height: 64px. #202020 fill, 1px top border in #353535. Four icon buttons: Undo / Hint / New / Auto-complete. Icons: 24px, 1.5px stroke weight, color #d0d0d0. Active/pressed: icon color #a54242.

Status / Sync Indicator

Top-right corner of the HUD: a 6px circular dot.

  • Connected & synced: #12cfc0
  • Pending: #ddb26f (pulsing 1.5s)
  • Error: #fb9fb1 (steady)
  • Offline: #505050

Accessibility

  1. Color-blind mode (Settings → Gameplay): swaps the red suits' default #e35353 for #acc267 (lime). Outlined-glyph differentiation remains active in all modes.
  2. High-contrast mode (Settings → Gameplay): boosts on-surface from #d0d0d0 to #f5f5f5, outline from #505050 to #a0a0a0, suit-red from #fb9fb1 to #ff8aa0.
  3. Reduce-motion mode (Settings → Gameplay): disables card-lift transition (instant z-lift), disables CRT scanline effect, disables the warning-chip pulse animation.
  4. Tabular figures are mandatory for any number that updates live (timer, score, moves) so they don't reflow.
  5. Touch targets are 48dp minimum even when the visual element is smaller.
  6. Text contrast: all body text on background passes WCAG AA at minimum (#d0d0d0 on #151515 = 9.5:1; #a0a0a0 on #151515 = 5.7:1).