Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 04f9bf9be3 | |||
| a292a7ead0 | |||
| d109c32b75 | |||
| dd101b3d54 | |||
| af414b6aed | |||
| ae84dc1504 | |||
| 8719f77ec2 | |||
| a14200ac2f | |||
| e8bf9d79da | |||
| 48b28d29f8 | |||
| babe5cc9c8 | |||
| 3a4bb63a6f | |||
| 56233687b0 | |||
| 73ac67d76b | |||
| a27cf5a020 | |||
| 29136d815d | |||
| ef54cdeb65 | |||
| e080b49914 | |||
| 54005d5494 | |||
| 44f5972edd | |||
| 13ae16051d | |||
| a65e5b8c7b | |||
| 6204db8bb1 | |||
| c84d9f445c | |||
| cacb19c03f | |||
| 39b84965b6 | |||
| 41a009a693 | |||
| fa7f98ac52 | |||
| 9891ae4ba3 | |||
| cdcaddaabe | |||
| d752870007 | |||
| 1d1543e4bc | |||
| 651f4060e6 | |||
| a1376075bd | |||
| ceec4fc486 | |||
| 0d477ac9fd | |||
| 4b51e50203 | |||
| f2d2119db5 | |||
| 59424a370c | |||
| fb8b2ac684 | |||
| 690e1d2ad6 | |||
| 35516d31f6 | |||
| 9b065e5ac6 | |||
| e1b8766e15 | |||
| 67c150bd7b | |||
| aa2a021712 | |||
| 6037596cc0 | |||
| d7ffb16df5 | |||
| b57db017d3 | |||
| 0b3140ad6d | |||
| e41def8c89 | |||
| aad8bb9c83 | |||
| 55c235b55f | |||
| 21ec03b157 | |||
| 17e3112502 | |||
| de4751115f | |||
| 9ff48ace5b | |||
| 91b7605b9f | |||
| 42d90b199c | |||
| 3e11e9e79a |
@@ -6,7 +6,628 @@ project follows [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
_Nothing yet._
|
||||
No threads in flight. v0.21.0 cut on 2026-05-08; CHANGELOG accumulates
|
||||
the next cycle here.
|
||||
|
||||
## [0.21.0] — 2026-05-08
|
||||
|
||||
Closes the visual-identity arc opened in v0.20.0. Three through-lines
|
||||
landed: the **card-face / suit / card-back artwork migration** that
|
||||
v0.20.0 deliberately deferred, the **splash boot-screen + replay-
|
||||
overlay polish** that closes Resume-prompt Options B and C, and a
|
||||
late-cycle **`ACCENT_PRIMARY` palette swap** from cyan `#6fc2ef` to
|
||||
brick red `#a54242` after a quick stakeholder review on the
|
||||
shipped art.
|
||||
|
||||
The card-face arc is the largest piece by commit count (10 of the
|
||||
25 post-tag commits) and shape: it ports both rendering paths
|
||||
production traverses — the PNG fallback at `assets/cards/*.png`
|
||||
and the bundled-default theme SVGs at
|
||||
`solitaire_engine/assets/themes/default/*.svg` that
|
||||
`include_bytes!()`-embed into the binary and override the PNGs at
|
||||
runtime — to identical Terminal-aesthetic art generated by the
|
||||
same `face_svg` / `back_svg` builders. A new
|
||||
`card_face_svg_pin` integration test pins rasteriser output via
|
||||
FNV-1a on raw RGBA bytes, so future `usvg`/`resvg` upgrades or
|
||||
intentional builder edits surface as test failures rather than
|
||||
silent visual drift. The pin test fired three times during the
|
||||
arc (text→path glyph fix, glyph orientation tweak, palette swap)
|
||||
and rebaselined cleanly each time via the empty-then-paste
|
||||
bootstrap pattern baked into the test.
|
||||
|
||||
Three sign-off follow-ups surfaced once a human booted the
|
||||
running game and they all matched the same shape — "fallback
|
||||
path the chrome migration walked past": the embedded default
|
||||
theme overrode the new PNGs at runtime, the table backgrounds
|
||||
were a separate PNG path that the v0.20.0 chrome migration
|
||||
didn't touch, and the action-button row's `font_size: 16.0`
|
||||
literal slipped through the typography migration audit. All
|
||||
three are recorded under "Fixed" below.
|
||||
|
||||
Phase 8 (sync) and the Phase Android runtime gaps (JNI bridges,
|
||||
APK launch verification on device) remain open and roll forward.
|
||||
|
||||
### Added
|
||||
|
||||
- **Card-face SVG generator pipeline** (`5623368` plan doc,
|
||||
`3a4bb63` PoC, `babe5cc` full generator, `48b28d2` pin test).
|
||||
`solitaire_engine/examples/card_face_generator.rs` writes 52
|
||||
face PNGs + 5 back PNGs into `assets/cards/` and 53 theme SVGs
|
||||
into `solitaire_engine/assets/themes/default/`, all from the
|
||||
shared `face_svg` / `back_svg` builders in the new
|
||||
`solitaire_engine::assets::card_face_svg` module. Run with
|
||||
`cargo run --example card_face_generator --release`. The PoC
|
||||
(`card_face_poc.rs`) stays alongside as historical record of
|
||||
the per-card grain proof. Pin test `card_face_svg_pin`
|
||||
guards rasterised output via inline FNV-1a so the arc has
|
||||
test-time coverage of both intentional builder edits (rebase
|
||||
via empty-then-paste) and unintentional dependency-upgrade
|
||||
drift.
|
||||
- **Background generator example** (in `8719f77`).
|
||||
`solitaire_engine/examples/background_generator.rs` emits 5
|
||||
flat Terminal-palette play-surface PNGs at 120 × 168, the
|
||||
same tile size the legacy felt textures used (the runtime
|
||||
stretches to `window_size * 2.0` so source resolution is
|
||||
immaterial). All 5 slots stay in the near-black family —
|
||||
`#151515` canonical, `#0a0a0a` deeper, `#1a1a1a` elevated,
|
||||
`#121820` cool tint, `#201812` warm tint.
|
||||
- **Splash boot-screen port** (`cacb19c`). Full mockup-spec
|
||||
splash: header, boot log, progress bar, palette swatches,
|
||||
version footer, plus the `SplashFadable` scaffold that lets
|
||||
any future overlay fade `N >> 3` elements via one marker +
|
||||
one global lerp query (replaces the `Without<X>, Without<Y>`
|
||||
exclusion pattern that the legacy splash hit at three
|
||||
siblings).
|
||||
- **Splash trailing cursor pulse** (`29136d8`). Trailing
|
||||
6×12 px Node, sine-pulsed, multiplied with the global splash
|
||||
fade — the "multiply, don't override" pattern that resolves
|
||||
the original `cacb19c` skip-rationale. Closes Option B half 1
|
||||
from the SESSION_HANDOFF Resume prompt.
|
||||
- **Splash tiled scanline overlay** (`a27cf5a`). Runtime-
|
||||
generated 2×2 RGBA8 texture tiled via `NodeImageMode::Tiled`;
|
||||
per-pixel alpha × tint alpha gives multiplicative fade
|
||||
integration without new abstractions. Closes Option B
|
||||
half 2.
|
||||
- **Replay overlay scrub bar** (`c84d9f4`). 1px accent fill at
|
||||
the bottom of the banner, mirroring `cursor / total`. Per-
|
||||
frame updater + scrub-pct unit tests.
|
||||
- **Replay overlay banner label port** (`6204db8`). The
|
||||
"▌ replay" headline picks up the cursor-block treatment that
|
||||
aligns it with the splash boot-screen idiom.
|
||||
- **Replay overlay GAME caption** (`54005d5`). `GAME #YYYY-DDD`
|
||||
game-identifier caption beneath the headline. Mirrors the
|
||||
mockup's right-anchored ID but stays grouped with the headline
|
||||
so the two pieces of "this is a replay of game X" read as one
|
||||
unit.
|
||||
- **Replay overlay MOVE chip** (`e080b49`). `MOVE N/M` progress
|
||||
readout wrapped in a 1px accent-bordered chip — discrete
|
||||
callout rather than free-floating text. Closes Option C from
|
||||
the SESSION_HANDOFF Resume prompt (paired with `54005d5`).
|
||||
- **Terminal desktop-adaptation spec** (`39b8496`).
|
||||
`docs/ui-mockups/desktop-adaptation.md` — the rules-based
|
||||
companion to the 24-mockup library. Closes the spec gap
|
||||
exposed when 23 of 24 mockups turned out to be mobile-only;
|
||||
any future plugin port should read this first and apply the
|
||||
universal rules before consulting the per-screen table.
|
||||
- **`solitaire_engine::assets::card_face_svg` module**
|
||||
(`48b28d2`). Public SVG builders (`face_svg`, `back_svg`,
|
||||
`suit_path_d`) extracted from the example so the pin test
|
||||
could call them — examples can't be referenced from
|
||||
`tests/`. The generator and the test now share the same
|
||||
source-of-truth, so the pin guards both rendering paths
|
||||
the engine consults.
|
||||
|
||||
### Changed
|
||||
|
||||
- **`ACCENT_PRIMARY` swapped from cyan `#6fc2ef` to brick red
|
||||
`#a54242`** (`a292a7e`). Project-wide palette decision after
|
||||
initial rollout. Affects every cyan-accented surface — splash
|
||||
boot screen, home menu glyphs, action chevrons, replay
|
||||
overlay banner + scrub fill + chip border, achievement
|
||||
checkmarks, leaderboard #1 indicator, radial menu fill, focus
|
||||
ring, card-back canonical badge. `RED_SUIT_COLOUR_CBM`
|
||||
swapped in lockstep from cyan to lime `#acc267` so the
|
||||
colour-blind alternative stays hue-distinct from the new
|
||||
red-family primary. Comment doc strings throughout the
|
||||
engine retuned from "cyan" to "accent" / "primary-accent" so
|
||||
future palette changes don't require comment churn. Spec doc
|
||||
`design-system.md` updated in lockstep with historical
|
||||
references preserved as audit trail.
|
||||
- **Card-face / suit / card-back constants migrated to Terminal
|
||||
palette in lockstep with new artwork** (`e8bf9d7`). Five
|
||||
constants flipped: `CARD_FACE_COLOUR` → `#1a1a1a` (was
|
||||
off-white `#fafaf2`), `RED_SUIT_COLOUR` → `#fb9fb1` (was deep
|
||||
red `#c71f26`), `BLACK_SUIT_COLOUR` → `#d0d0d0` (was near-
|
||||
black `#141414`), `CARD_FACE_COLOUR_RED_CBM` renamed to
|
||||
`RED_SUIT_COLOUR_CBM` and repurposed from a face-background
|
||||
tint to a suit-glyph swap (the Terminal face is uniformly
|
||||
`CARD_FACE_COLOUR` regardless of CBM; CBM only swaps red
|
||||
suits to a hue-distinct alternative in the glyph itself).
|
||||
`card_back_colour()` retuned to the 5 base16-eighties accent
|
||||
colours matching `BACK_ACCENTS`. `face_colour()` deleted —
|
||||
the function collapsed to a constant once the Terminal face
|
||||
became uniform. `text_colour()` gained a `color_blind: bool`
|
||||
parameter to surface the CBM swap on the constant-fallback
|
||||
path (the production path bakes glyphs into the PNG, but
|
||||
tests under `MinimalPlugins` still need the CBM-aware
|
||||
fallback). Four `face_colour` CBM tests collapsed into two
|
||||
`text_colour` CBM tests in the same commit.
|
||||
- **Default-theme SVG art regenerated to Terminal aesthetic**
|
||||
(`a14200a`). `solitaire_engine/assets/themes/default/*.svg`
|
||||
— the bundled-default theme that
|
||||
`include_bytes!()`-embeds into the binary — was still the
|
||||
legacy vector-playing-cards art post-`e8bf9d7`. The PNG
|
||||
migration alone didn't change what production rendered
|
||||
because `apply_theme_to_card_image_set` overrides
|
||||
`CardImageSet.faces[..]` at startup with the theme's
|
||||
rasterised SVG handles. Both rendering paths now agree:
|
||||
same `face_svg` / `back_svg` builders feed both paths, and
|
||||
the pin test guards both.
|
||||
- **Card glyphs render upright in both corners** (`dd101b3`).
|
||||
The traditional 180° inverted-corner-indicator rotation on
|
||||
the bottom-right glyph was dropped at user preference —
|
||||
single-orientation digital play doesn't benefit from the
|
||||
flipped-readback convention. Both glyphs now render in the
|
||||
same upright orientation. `design-system.md` § Game Cards
|
||||
line 220 updated in lockstep — the deviation from
|
||||
traditional playing-card layout is documented in the spec,
|
||||
not just the code.
|
||||
- **Action-button row typography aligned to `TYPE_BODY`**
|
||||
(`ae84dc1`). Was a hardcoded `font_size: 16.0` literal that
|
||||
the v0.20.0 typography-migration audit walked past. Brings
|
||||
it in line with the `TYPE_*` token system every other text
|
||||
element in `hud_plugin` already routes through, and trims
|
||||
~12% off label widths so the action-button row no longer
|
||||
collides with the left-anchored HUD column at portrait /
|
||||
narrow window widths. Pairs with a horizontal-padding step-
|
||||
down from `VAL_SPACE_3` to `VAL_SPACE_2`: ~96 px reclaimed
|
||||
across the 6-button row.
|
||||
- **Table backgrounds flattened to solid Terminal colours**
|
||||
(`8719f77`). Replaces the legacy felt-texture PNGs at
|
||||
`assets/backgrounds/bg_*.png` with 5 flat near-black
|
||||
variants per design-system.md (Terminal play surface is
|
||||
flat; no felt, no gradient). On-disk tile weight drops
|
||||
from ~16 KB average to ~100 bytes per tile; runtime
|
||||
appearance flips from green felt to flat `#151515`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Card suit glyphs rendered as near-invisible "tofu" marks**
|
||||
(`af414b6`). The bundled `FiraMono` in
|
||||
`svg_loader::shared_fontdb` doesn't carry usable U+2660-2666
|
||||
glyphs at the requested size — usvg silently substituted a
|
||||
default-size fallback regardless of `font-size="20"` /
|
||||
`font-size="64"`. Switched suit-glyph rendering from `<text>`
|
||||
elements to inline SVG `<path>` elements via a new
|
||||
`suit_path_d` helper authoring each suit as a single closed
|
||||
perimeter in a 32×32 logical box. Path-based rendering
|
||||
bypasses the font system entirely — same bytes on every
|
||||
machine, no fontdb dependency, no substitution risk. Same
|
||||
path data renders correctly whether filled (♥ ♠) or outlined
|
||||
(♦ ♣ — the always-on color-blind glyph differentiation).
|
||||
- **Default-theme SVGs were overriding new PNG artwork at
|
||||
runtime** (`a14200a`). The PNG migration in `e8bf9d7` looked
|
||||
correct under `cargo test` (the constant-fallback path
|
||||
matched) but a real `cargo run` showed legacy white cards
|
||||
because `theme::plugin::apply_theme_to_card_image_set`
|
||||
overlays the bundled-default theme's rasterised SVGs onto
|
||||
`CardImageSet.faces[..]` at startup, and those SVGs were
|
||||
still legacy. Fixed by regenerating both rendering paths
|
||||
from the same `face_svg` / `back_svg` builders. The
|
||||
migration plan flagged "Theme system — out of scope here";
|
||||
that was a planning miss documented in the SESSION_HANDOFF.
|
||||
- **Top-bar HUD column collided with action-button row at
|
||||
portrait window widths** (`ae84dc1`). Both nodes were
|
||||
absolute-positioned siblings at `top: VAL_SPACE_2` without a
|
||||
shared flex parent, so they could overlap horizontally when
|
||||
the window narrowed past their combined natural widths.
|
||||
Fixed via the typography + padding tightening described
|
||||
under "Changed" — minimal-blast-radius fix; the structural
|
||||
fix (shared `JustifyContent::SpaceBetween` parent) stays
|
||||
open as a follow-up if narrower windows surface.
|
||||
- **Table-surface fill was still legacy green felt despite
|
||||
v0.20.0's chrome-migration claim** (`8719f77`). Commit
|
||||
`651f406` retuned in-engine constants but the runtime path
|
||||
loads from `assets/backgrounds/bg_0.png`, an on-disk PNG that
|
||||
the migration didn't touch. Same shape as the default-theme
|
||||
override above — token migration walked past a fallback
|
||||
rendering path. Fixed by regenerating the 5 background PNGs.
|
||||
|
||||
### Stats
|
||||
|
||||
- **1184 passing tests / 0 failing** across the workspace
|
||||
(net +8 from v0.20.0's 1176 baseline). New tests this cycle:
|
||||
the scrub-bar pair (`scrub_pct_covers_state_corners`,
|
||||
`overlay_scrub_fill_tracks_cursor`); the splash boot-screen
|
||||
pair (`splash_renders_terminal_boot_screen_content`,
|
||||
`fadables_start_transparent_and_reach_full_alpha`); the
|
||||
splash-polish pair (`build_scanline_image_has_expected_2x2_rgba_bytes`,
|
||||
`scanline_overlay_spawns_and_fades_with_splash`); the
|
||||
card-face pin (one integration test in
|
||||
`card_face_svg_pin.rs` that exercises 57 rasteriser outputs
|
||||
through 57 hash comparisons in a single
|
||||
`#[test]`-marked function); and the CBM consolidation that
|
||||
rewrote four `face_colour` tests as two `text_colour` CBM
|
||||
tests in the same commit (net 0 to count, clean rewrite).
|
||||
- Zero clippy warnings under `cargo clippy --workspace
|
||||
--all-targets -- -D warnings`.
|
||||
- `cargo test --workspace` clean.
|
||||
|
||||
### Documentation
|
||||
|
||||
- `docs/ui-mockups/card-face-migration.md` (`5623368`) — the
|
||||
multi-session lockstep migration plan that the card-face arc
|
||||
followed step-by-step. Now reads as historical record of
|
||||
closed work; lessons documented under "Process notes" in
|
||||
SESSION_HANDOFF.md.
|
||||
- `docs/ui-mockups/desktop-adaptation.md` (`39b8496`) — rules-
|
||||
based companion to the 24-mockup library. Required reading
|
||||
before any future plugin port.
|
||||
- `docs/ui-mockups/design-system.md` updates: § Game Cards
|
||||
line 220 (glyph orientation), CTA / suit-red-cb / Card-back
|
||||
badge / Primary button / Bottom-bar active-icon palette
|
||||
retunes for the cyan→red swap. Historical references
|
||||
preserved as audit trail.
|
||||
- Multiple `SESSION_HANDOFF.md` refreshes (`a65e5b8`,
|
||||
`13ae160`, `44f5972`, `73ac67d`, `ef54cde`, `d109c32`)
|
||||
recording Options B / C / D closures and process notes.
|
||||
|
||||
## [0.20.0] — 2026-05-07
|
||||
|
||||
Two through-lines closed: a full **Android port** (build target,
|
||||
first 54 MB APK, JNI-free per-app persistence shim) and the
|
||||
**Terminal visual-identity port** that replaces the prior
|
||||
Premium-Solitaire palette across every UI surface. The Android
|
||||
arc opened in `fb8b2ac` (compile + APK), continued in `4b51e50`
|
||||
(`solitaire_data::data_dir` shim closing the CLAUDE.md §10
|
||||
`dirs::data_dir() = None` pitfall), and is functional end-to-end
|
||||
on a real device — though the runtime artwork is still the legacy
|
||||
white-card palette, and JNI ClipboardManager / keyring bridges
|
||||
remain stubbed (matching v0.19.0's documented fallback behaviour).
|
||||
The Terminal port lands as a top-down stack: the `ui_theme` token
|
||||
API in `0d477ac` is load-bearing, and the rest of the cycle is
|
||||
downstream applications (modal scaffold, gameplay-feedback,
|
||||
toasts, table / card chrome, splash cursor, hint-highlight
|
||||
pairing). The card faces and suit-pip palette are deliberately
|
||||
NOT migrated — those track PNG artwork that hasn't been
|
||||
regenerated yet, and swapping the fallback constants ahead of the
|
||||
artwork would mix two visual systems on any code path where
|
||||
image loading fails.
|
||||
|
||||
The 24 Stitch-rendered mockups in `docs/ui-mockups/` are now
|
||||
in-tree (`fa7f98a`); future plugin work should diff against the
|
||||
matching mockup before touching pixels.
|
||||
|
||||
Two threads from v0.19.0's punch list also closed in this cycle:
|
||||
the pull-failure test flake (`67c150b`), the Settings opt-out for
|
||||
the smart-default window sizer (`e1b8766`), and the share-link
|
||||
discoverability surfacing (`9b065e5`). The remaining v0.19.0
|
||||
candidate — the app-icon round — stays open.
|
||||
|
||||
### Added
|
||||
|
||||
- **`ui_theme` Terminal design-token system** (`0d477ac`). Single
|
||||
source of truth for the engine's visual identity:
|
||||
base16-eighties palette (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, full
|
||||
motion budget, and four invariant-pinning unit tests. Every
|
||||
downstream port commit in this cycle reads from this module —
|
||||
swapping the palette is now a one-file edit, not a hunt across
|
||||
~50 plugin files. Card-shadow alphas pinned to 0 (Terminal
|
||||
achieves depth via 1px borders + tonal layering, no
|
||||
`box-shadow`); the rendering path is left intact so a future
|
||||
palette can re-enable shadows without touching consumers.
|
||||
- **`ToastVariant` enum + Terminal toast styling** (`a137607`).
|
||||
Toasts now follow `docs/ui-mockups/design-system.md`: opaque
|
||||
`BG_ELEVATED` fill, 1px accent border keyed off
|
||||
`Info` / `Warning` / `Error` / `Celebration` variants, 18px
|
||||
monospaced caption (`TYPE_BODY_LG`), bottom-anchored. All ten
|
||||
call sites pass their semantic variant: achievement / level-up
|
||||
/ XP / daily / weekly / challenge → Celebration (lavender);
|
||||
goal-announcement / time-attack / settings volume / auto-complete
|
||||
→ Info (teal). Two regression tests pin variant→border mapping
|
||||
to the design tokens and require all four borders to be visually
|
||||
distinct. Queued and immediate toasts use slightly different
|
||||
bottom anchors (6 % vs. 14 %) so a celebration toast spawned
|
||||
alongside a queued info banner layers above it.
|
||||
- **Terminal cursor block on the splash overlay** (`cdcadda`).
|
||||
The launch splash now renders the design system's signature
|
||||
`▌` cyan (`ACCENT_PRIMARY`) glyph (96 px, hand-tuned literal)
|
||||
above the wordmark, matching `docs/ui-mockups/splash-mobile.html`.
|
||||
Cursor fades on the same per-frame alpha schedule as the title
|
||||
and subtitle so the brand beat still dissolves as a single
|
||||
layer. Did *not* pull in the mockup's full boot-loader treatment
|
||||
(scanline overlay, ✓ check log, progress bar, ROOT@SOLITAIRE
|
||||
prompt) — those are aesthetic features warranting their own
|
||||
commit.
|
||||
- **Terminal design-system spec + 24-mockup library** (`fa7f98a`).
|
||||
`docs/ui-mockups/design-system.md` (palette, type scale, spacing
|
||||
scale, motion budget, component library, accessibility notes —
|
||||
color-blind toggle, high-contrast mode, glyph differentiation,
|
||||
canonical `"Terminal"` card-back theme) and 24 Stitch-rendered
|
||||
mockups (HTML + PNG): 12 redesigned existing screens, 1 desktop
|
||||
home variant, 2 onboarding steps, and 9 missing-plugin screens
|
||||
(splash, challenge, time-attack, weekly-goals, leaderboard,
|
||||
sync, level-up, replay, radial-menu). The spec the rest of this
|
||||
cycle ports against; future plugin work diffs here before
|
||||
touching pixels.
|
||||
- **Android build target — first working APK** (`fb8b2ac`).
|
||||
`cargo apk build -p solitaire_app --target x86_64-linux-android`
|
||||
now produces a 54 MB debug-signed APK at
|
||||
`target/debug/apk/solitaire-quest.apk`. Five gating points
|
||||
resolved end-to-end:
|
||||
- **`solitaire_app` split into bin + lib.** cargo-apk needs a
|
||||
`cdylib` to bundle as `libmain.so`; pure-bin crates panic
|
||||
with "Bin is not compatible with Cdylib". `src/lib.rs`
|
||||
carries the ECS bootstrap as `pub fn run`; `src/main.rs` is
|
||||
a 3-line shim that delegates for the desktop path.
|
||||
- **`[package.metadata.android]`** pins target SDK 34 / min
|
||||
SDK 26 and points `assets = "../assets"` at the workspace
|
||||
asset directory so desktop and APK share one set.
|
||||
- **Workspace `bevy` features** add `android-native-activity`
|
||||
(target-gated inside bevy_internal — desktop builds compile
|
||||
it out). Pairs with cargo-apk's NativeActivity wrapper.
|
||||
- **`arboard` target-gated** to `cfg(not(target_os =
|
||||
"android"))`. The crate has no Android backend; cargo apk
|
||||
fails with E0433 on `platform::Clipboard` if left
|
||||
unconditional. Stats's "Copy share link" surfaces an
|
||||
informational toast on Android until JNI ClipboardManager
|
||||
lands in the Phase-Android round.
|
||||
- **`keyring` + `keyring-core` target-gated.** Bionic doesn't
|
||||
expose `libc::__errno_location` so the transitive
|
||||
`rpassword` won't compile. `auth_tokens` ships an Android
|
||||
stub returning `KeychainUnavailable` for every call —
|
||||
matches the existing fallback for a Linux box without
|
||||
Secret Service.
|
||||
- Cosmetic: cargo-apk panics post-sign when it tries to also
|
||||
wrap the bin target. The APK on disk is unaffected;
|
||||
`cargo apk build --lib` is the small workaround.
|
||||
- **Android developer setup + build runbook** (`59424a3`).
|
||||
Captures Debian 13 toolchain install (JDK 21, unzip, SDK
|
||||
licence prompts), the `cargo apk build` invocation, the
|
||||
cosmetic post-sign panic workaround, and a what-is-wired-vs-
|
||||
stubbed table for the android target. Runnable on a fresh
|
||||
clone — no machine-local context required.
|
||||
- **F3-toggleable FPS / frame-time overlay** (`690e1d2`).
|
||||
`DiagnosticsHudPlugin` wraps Bevy's `FrameTimeDiagnosticsPlugin`
|
||||
and renders a corner readout the developer toggles with F3.
|
||||
Hidden by default; F3 is not gated by pause / modal state.
|
||||
Reads `smoothed()` so the cell isn't a per-frame jittery
|
||||
scoreboard. Format: `FPS NN \u{2022} M.MM ms`. Anchored
|
||||
top-right at `z = Z_SPLASH + 100` above every modal / toast /
|
||||
splash. Update system bails when hidden so the
|
||||
diagnostic-store lookup is free when nobody's looking.
|
||||
- **"Smart window size" Settings toggle** (`e1b8766`). Gameplay
|
||||
section gains an opt-out toggle for v0.19.0's
|
||||
`apply_smart_default_window_size` system. New
|
||||
`Settings::disable_smart_default_size: bool` with
|
||||
`#[serde(default)]` so legacy `settings.json` files load to
|
||||
the shipped behaviour (smart sizer enabled). `solitaire_app::main`
|
||||
reads the flag once at startup and skips the system's
|
||||
registration when set. Saved window geometry still wins over
|
||||
both branches; tooltip on the row makes that explicit.
|
||||
- **"Shareable" badge on the Latest-win caption** (`9b065e5`).
|
||||
The Stats overlay's Latest-win caption now appends
|
||||
`\u{2022} Shareable` when the displayed replay carries a
|
||||
populated `share_url`. Players can see at a glance whether the
|
||||
Copy share link button will produce a URL or surface the
|
||||
upload-prerequisite toast.
|
||||
- **Help overlay covers M / P / Win-Summary-Enter** (`35516d3`).
|
||||
Three new rows in the Overlays section: M (Home / Mode
|
||||
launcher), P (Profile), and the Enter accelerator that
|
||||
dismisses the Win Summary modal. Three post-v0.18 entries
|
||||
that had drifted out of the cheat sheet are now listed.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Gameplay-feedback colours route through Terminal state
|
||||
tokens** (`ceec4fc`). Selection-highlight tints in
|
||||
`selection_plugin` and the valid-drop marker tint in
|
||||
`cursor_plugin` were hand-tuned RGB literals. Migrated to
|
||||
semantic state tokens: keyboard-drag picking source →
|
||||
`ACCENT_PRIMARY` (cyan focus); keyboard-drag lifted source →
|
||||
`STATE_WARNING` (gold attention); destination → `STATE_SUCCESS`
|
||||
(lime valid-move); `cursor_plugin::MARKER_VALID` →
|
||||
`STATE_SUCCESS` at 0.55 α with a tracking test pinning its RGB
|
||||
to the token. Three stale doc comments in `ui_modal` corrected
|
||||
("loud yellow CTA" / "magenta secondary accent" → cyan /
|
||||
lavender to match the actual token values).
|
||||
- **`table_plugin` chrome migration to Terminal tokens** (`651f406`).
|
||||
`marker_colour` promoted to module-level `pub const
|
||||
PILE_MARKER_DEFAULT_COLOUR` so `cursor_plugin::MARKER_DEFAULT`
|
||||
imports the const directly — replaces the prior
|
||||
duplicated literal kept in sync only by doc comment with a
|
||||
compile-enforced invariant. The empty-tableau "K" placeholder
|
||||
text now uses `TEXT_PRIMARY` at 0.35 α; `HINT_PILE_HIGHLIGHT_COLOUR`
|
||||
retuned from bright `srgb(1.0, 0.85, 0.1)` to the `STATE_WARNING`
|
||||
token (`#ddb26f`) with a tracking test, and the existing "is
|
||||
gold" character test loosened to fit the muted Terminal gold
|
||||
while still rejecting non-warm colours.
|
||||
- **`card_plugin` chrome migration to Terminal tokens** (`d752870`).
|
||||
Drag-elevation shadow now sources its colour from
|
||||
`CARD_SHADOW_COLOR` + `CARD_SHADOW_ALPHA_DRAG` so the Terminal
|
||||
"no box-shadow" policy disables the stack shadow in lockstep
|
||||
with the per-card shadows. `RIGHT_CLICK_HIGHLIGHT_COLOUR`
|
||||
retuned from raw green to `STATE_SUCCESS` at 0.6 α with a
|
||||
tracking test. The duplicated `PILE_MARKER_DEFAULT_COLOUR`
|
||||
const dropped — this plugin now imports the promoted const
|
||||
from `table_plugin`. Stock recycle "↺" text moved from raw
|
||||
white-at-0.7-α to `TEXT_PRIMARY.with_alpha(0.7)`. Card-face /
|
||||
suit / card-back palette constants were intentionally NOT
|
||||
migrated (the runtime path renders PNG artwork that's still on
|
||||
the previous "white card" palette).
|
||||
- **Hint-source card tint matches the destination pile**
|
||||
(`9891ae4`). `input_plugin`'s hint-source card tint moved from
|
||||
raw bright-yellow `srgba(1.0, 1.0, 0.4, 1.0)` to `STATE_WARNING`,
|
||||
so the source card and the destination pile (which already uses
|
||||
`STATE_WARNING` via `HINT_PILE_HIGHLIGHT_COLOUR`) wear the same
|
||||
attention colour as a coherent pair.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`solitaire_data::data_dir` shim closes the Android persistence
|
||||
gap** (`4b51e50`). `dirs::data_dir()` returns `None` on Android,
|
||||
which silently disabled every persistence path (settings, stats,
|
||||
achievements, replays, game-state, time-attack sessions, user
|
||||
themes). New `solitaire_data::platform::data_dir()` shim falls
|
||||
through to `dirs::data_dir()` on desktop and returns the per-app
|
||||
sandbox at `/data/data/com.solitairequest.app/files` on Android
|
||||
— no JNI needed, since the package id is pinned in
|
||||
`[package.metadata.android]`. Six call sites across
|
||||
`solitaire_data` plus `solitaire_engine/assets/user_dir.rs`
|
||||
migrated. CLAUDE.md §10 already flagged this as a known
|
||||
pitfall; the shim pays it down at the one chokepoint instead
|
||||
of per feature.
|
||||
- **`card_shadow_params` test aligned with Terminal "no shadow"
|
||||
intent** (`1d1543e`). The Terminal token system pinned both
|
||||
`CARD_SHADOW_ALPHA_IDLE` and `CARD_SHADOW_ALPHA_DRAG` to 0.0,
|
||||
which made the prior `drag_alpha > idle_alpha` assertion fail
|
||||
(`0 > 0` is false). Loosened to `drag_alpha >= idle_alpha`
|
||||
with a comment naming the new invariant: under Terminal both
|
||||
are 0; under any future palette that re-enables shadows, drag
|
||||
still must not be weaker than idle. The useful regression-guard
|
||||
(catching an accidental swap of the two constants) is preserved.
|
||||
- **`pull_failure_sets_error_status` test flake** (`67c150b`).
|
||||
The fixed 5-update budget was the last test still subject to
|
||||
the AsyncComputeTaskPool starvation mode that v0.19.0's
|
||||
auto-save fix already cleared. Replaced with a wall-clock-
|
||||
bounded loop (5-second deadline, `std::thread::yield_now`
|
||||
between iterations) that exits as soon as the status flips.
|
||||
Mirrors the auto-save flake fix shape.
|
||||
|
||||
### Stats
|
||||
|
||||
- **1176 passing tests / 0 failing** across the workspace
|
||||
(six new tests this cycle: four `ui_theme` invariant guards
|
||||
for the type / spacing / z-index scales + `scaled_duration`,
|
||||
one toast-variant-border-mapping pair, and four palette-
|
||||
tracking guards on `MARKER_VALID` / `HINT_PILE_HIGHLIGHT_COLOUR`
|
||||
/ `RIGHT_CLICK_HIGHLIGHT_COLOUR` / toast-border distinctness).
|
||||
- Zero clippy warnings under `--workspace --all-targets -- -D warnings`.
|
||||
|
||||
## [0.19.0] — 2026-05-06
|
||||
|
||||
Closes the v0.18.0 punch list (items B and D — async hint and
|
||||
persistent replay share URLs), expands desktop platform fit
|
||||
(Wayland session support + monitor-aware default window size for
|
||||
HiDPI / 4K displays), polishes the win-celebration and
|
||||
double-click animation paths, and clears two test-flake
|
||||
contributors. A short-lived "Rusty Pixel" pixel-art card theme
|
||||
was prototyped and reverted in the same window — the engine
|
||||
plumbing it touched (`pixel_art` field on `ThemeMeta`, PNG
|
||||
manifest face support, second `embedded://` theme channel) was
|
||||
fully reverted and is not part of this release.
|
||||
|
||||
### Changed
|
||||
|
||||
- **H-key hint runs on `AsyncComputeTaskPool`** (`3e11e9e`). The
|
||||
synchronous `try_solve_from_state` call on every H press is gone;
|
||||
`handle_keyboard_hint` now spawns a task whose result the new
|
||||
`pending_hint::poll_pending_hint_task` system surfaces one frame
|
||||
later. New `PendingHintTask` resource carries the in-flight handle
|
||||
plus `move_count_at_spawn` for staleness detection;
|
||||
`drop_pending_hint_on_state_change` cancels the task whenever the
|
||||
game state shifts; `PendingHintTask::spawn` implements
|
||||
cancel-on-replace so two quick H presses keep at most one task in
|
||||
flight. Mirrors the v0.18.0 `PendingNewGameSeed` template.
|
||||
`emit_hint_visuals` and `find_heuristic_hint` are extracted as
|
||||
`pub` helpers so the polling system can call them.
|
||||
- **Persistent replay share URLs** (`42d90b1`). v0.18.0's
|
||||
`LastSharedReplayUrl` was an in-memory resource wiped on quit —
|
||||
the player had to share within the session of the win.
|
||||
`solitaire_data::Replay` now carries a `share_url: Option<String>`
|
||||
field with `#[serde(default)]` (no `REPLAY_SCHEMA_VERSION` bump
|
||||
needed; older `replays.json` files load unchanged with `share_url
|
||||
== None` on every entry). `poll_replay_upload_result` writes the
|
||||
resolved URL into `replays[0].share_url` and persists the updated
|
||||
history via `save_replay_history_to`. The Stats overlay's
|
||||
"Copy share link" button reads from
|
||||
`history.0.replays[selected.0].share_url`, so the Prev/Next
|
||||
selector's currently-displayed replay drives the clipboard
|
||||
contents — each historical win keeps its own URL.
|
||||
`LastSharedReplayUrl` removed (its role is now subsumed by the
|
||||
`share_url` field on the replay record).
|
||||
|
||||
### Added
|
||||
|
||||
- **Wayland session support** (`b57db01`). The workspace
|
||||
`Cargo.toml` Bevy feature list now enables `wayland` alongside
|
||||
`x11`. winit prefers Wayland when `WAYLAND_DISPLAY` is set on the
|
||||
session, falling back to X11 when it isn't. Pre-fix, a Wayland
|
||||
desktop environment fell through to XWayland, rendering the
|
||||
game inside an X11 frame stitched into the Wayland compositor.
|
||||
Post-fix, the game opens as a native Wayland surface. Costs a
|
||||
few hundred KB of binary for the libwayland-client bindings;
|
||||
cross-distro friendly because winit dlopen-probes the libraries
|
||||
rather than hard-linking them.
|
||||
- **Monitor-relative default window size** (`b57db01`). On launches
|
||||
with no saved geometry, the new
|
||||
`apply_smart_default_window_size` Update system queries
|
||||
`Monitor` (with the `PrimaryMonitor` marker) and resizes the
|
||||
primary window to ~70 % of the monitor's *logical* size on the
|
||||
first frame. Before, every fresh launch opened at 1280×800
|
||||
regardless of monitor; on a 4K monitor that's a comparatively
|
||||
tiny window in one corner. Logical size already accounts for
|
||||
the OS's HiDPI scale factor, so a Retina display reporting
|
||||
scale_factor 2.0 yields the same physical inches as a 1080p
|
||||
display reporting 1.0. Skipped entirely when saved geometry was
|
||||
applied — the player's chosen size always wins.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Duplicate "You Win" toast on game-won** (`55c235b`). The
|
||||
post-win UI was firing two celebration surfaces: a 4-second
|
||||
toast banner ("You Win! Score: X Time: Y") on top of the
|
||||
`win_summary_plugin`'s "You Won!" modal. In screenshots the
|
||||
toast banner was partially clipped behind the modal card,
|
||||
peeking out on either side. The toast predated the modal and is
|
||||
strictly subsumed by it; removed. The cards-fly-off cascade
|
||||
animation (`MotionCurve::Expressive` per-card rotation drift)
|
||||
is unchanged — that's the visual celebration, distinct from
|
||||
the textual celebration the modal owns. `WIN_TOAST_SECS` const
|
||||
removed.
|
||||
- **Double-click on a single card with no destination now plays
|
||||
the reject animation** (`d7ffb16`). `handle_double_click` only
|
||||
fired `MoveRejectedEvent` for multi-card stacks with no
|
||||
destination; a double-click on a single card whose top didn't
|
||||
fit any foundation or tableau slot produced zero feedback —
|
||||
no `card_invalid.wav`, no source-pile shake. Both priorities'
|
||||
failure paths now converge on a single rejection at the end of
|
||||
the double-click branch, so single-card and stack misses get
|
||||
the same feedback shape as drag-and-drop rejections.
|
||||
- **Double-click move animation no longer plays twice**
|
||||
(`6037596`). On a successful double-click, the slide-to-
|
||||
destination animation rendered twice — once from the move's
|
||||
`StateChangedEvent` landing, then again from the release's
|
||||
`end_drag` firing a redundant `StateChangedEvent` mid-slide.
|
||||
`sync_cards_on_change` saw the card mid-CardAnim (`cur ≠
|
||||
target`) and replaced the in-flight tween with a fresh one
|
||||
starting at the mid-position, visibly restarting the slide. The
|
||||
defensive `StateChangedEvent` write in `end_drag`'s
|
||||
uncommitted-drag branch is removed; `start_drag` only mutates
|
||||
`DragState` (never card transforms), so an uncommitted drag
|
||||
has no visual side effect to undo. The committed-drag branch
|
||||
keeps its `StateChangedEvent` since real drag snap-backs do
|
||||
need a resync.
|
||||
- **`auto_save_writes_after_30_seconds` test flake** (`91b7605`).
|
||||
The test's single-frame `app.update()` was sensitive to
|
||||
first-frame `Time::delta_secs()` variance under heavy parallel
|
||||
cargo-test load, and to production-disk
|
||||
`~/.local/share/solitaire_quest/game_state.json` state leaking
|
||||
into the test world via `GamePlugin::build`'s load path.
|
||||
`test_app` now resets `PendingRestoredGame(None)` after plugin
|
||||
build (preventing the dev machine's saved-game state from
|
||||
tripping the auto-save guard) and the test re-arms the timer in
|
||||
a small bounded loop until the file appears (robust against
|
||||
first-frame Time variance). No production-code change.
|
||||
|
||||
### Stats
|
||||
|
||||
- 1170 passing tests (was 1166 at v0.18.0 close — net +4 from
|
||||
the persistent share URL backwards-compat test, the three
|
||||
async-hint tests, minus the dropped synchronous hint tests).
|
||||
- Zero clippy warnings under `--workspace --all-targets -- -D warnings`.
|
||||
|
||||
## [0.18.0] — 2026-05-06
|
||||
|
||||
|
||||
@@ -126,6 +126,19 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
@@ -719,7 +732,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"console_error_panic_hook",
|
||||
"ctrlc",
|
||||
"downcast-rs",
|
||||
"downcast-rs 2.0.2",
|
||||
"log",
|
||||
"thiserror 2.0.18",
|
||||
"variadics_please",
|
||||
@@ -753,7 +766,7 @@ dependencies = [
|
||||
"crossbeam-channel",
|
||||
"derive_more",
|
||||
"disqualified",
|
||||
"downcast-rs",
|
||||
"downcast-rs 2.0.2",
|
||||
"either",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
@@ -801,7 +814,7 @@ dependencies = [
|
||||
"bevy_utils",
|
||||
"bevy_window",
|
||||
"derive_more",
|
||||
"downcast-rs",
|
||||
"downcast-rs 2.0.2",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"thiserror 2.0.18",
|
||||
@@ -1200,7 +1213,7 @@ dependencies = [
|
||||
"bevy_utils",
|
||||
"derive_more",
|
||||
"disqualified",
|
||||
"downcast-rs",
|
||||
"downcast-rs 2.0.2",
|
||||
"erased-serde",
|
||||
"foldhash 0.2.0",
|
||||
"glam 0.30.10",
|
||||
@@ -1259,7 +1272,7 @@ dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"bytemuck",
|
||||
"derive_more",
|
||||
"downcast-rs",
|
||||
"downcast-rs 2.0.2",
|
||||
"encase",
|
||||
"fixedbitset",
|
||||
"glam 0.30.10",
|
||||
@@ -1854,6 +1867,18 @@ dependencies = [
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop-wayland-source"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
|
||||
dependencies = [
|
||||
"calloop",
|
||||
"rustix 0.38.44",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cbc"
|
||||
version = "0.1.2"
|
||||
@@ -2709,6 +2734,12 @@ version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "2.0.2"
|
||||
@@ -5792,6 +5823,15 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.39.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.9"
|
||||
@@ -6165,7 +6205,7 @@ dependencies = [
|
||||
"pico-args",
|
||||
"rgb",
|
||||
"svgtypes",
|
||||
"tiny-skia",
|
||||
"tiny-skia 0.12.0",
|
||||
"usvg",
|
||||
"zune-jpeg",
|
||||
]
|
||||
@@ -6502,6 +6542,19 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "sctk-adwaita"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"log",
|
||||
"memmap2",
|
||||
"smithay-client-toolkit",
|
||||
"tiny-skia 0.11.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sec1"
|
||||
version = "0.7.3"
|
||||
@@ -6846,6 +6899,31 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smithay-client-toolkit"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"calloop",
|
||||
"calloop-wayland-source",
|
||||
"cursor-icon",
|
||||
"libc",
|
||||
"log",
|
||||
"memmap2",
|
||||
"rustix 0.38.44",
|
||||
"thiserror 1.0.69",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-csd-frame",
|
||||
"wayland-cursor",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-wlr",
|
||||
"wayland-scanner",
|
||||
"xkeysym",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smol_str"
|
||||
version = "0.2.2"
|
||||
@@ -6938,7 +7016,7 @@ dependencies = [
|
||||
"solitaire_sync",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"tiny-skia",
|
||||
"tiny-skia 0.12.0",
|
||||
"tokio",
|
||||
"usvg",
|
||||
"uuid",
|
||||
@@ -7525,7 +7603,7 @@ dependencies = [
|
||||
"crc32fast",
|
||||
"crossbeam-channel",
|
||||
"datasketches",
|
||||
"downcast-rs",
|
||||
"downcast-rs 2.0.2",
|
||||
"fastdivide",
|
||||
"fnv",
|
||||
"fs4",
|
||||
@@ -7577,7 +7655,7 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c57166f5bcfd478f370ab8445afb4678dce44801fa5ce5c451aaf8595583c5dc"
|
||||
dependencies = [
|
||||
"downcast-rs",
|
||||
"downcast-rs 2.0.2",
|
||||
"fastdivide",
|
||||
"itertools 0.14.0",
|
||||
"serde",
|
||||
@@ -7765,6 +7843,20 @@ dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"bytemuck",
|
||||
"cfg-if",
|
||||
"log",
|
||||
"tiny-skia-path 0.11.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia"
|
||||
version = "0.12.0"
|
||||
@@ -7777,7 +7869,18 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"log",
|
||||
"png 0.18.1",
|
||||
"tiny-skia-path",
|
||||
"tiny-skia-path 0.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia-path"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"bytemuck",
|
||||
"strict-num",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8548,7 +8651,7 @@ dependencies = [
|
||||
"siphasher",
|
||||
"strict-num",
|
||||
"svgtypes",
|
||||
"tiny-skia-path",
|
||||
"tiny-skia-path 0.12.0",
|
||||
"ttf-parser",
|
||||
"unicode-bidi",
|
||||
"unicode-script",
|
||||
@@ -8754,6 +8857,114 @@ dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"downcast-rs 1.2.1",
|
||||
"rustix 1.1.4",
|
||||
"scoped-tls",
|
||||
"smallvec",
|
||||
"wayland-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.31.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"rustix 1.1.4",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-csd-frame"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"cursor-icon",
|
||||
"wayland-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-cursor"
|
||||
version = "0.31.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d"
|
||||
dependencies = [
|
||||
"rustix 1.1.4",
|
||||
"wayland-client",
|
||||
"xcursor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.32.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols-plasma"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols-wlr"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.31.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-sys"
|
||||
version = "0.31.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
|
||||
dependencies = [
|
||||
"dlib",
|
||||
"log",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.97"
|
||||
@@ -9569,6 +9780,7 @@ version = "0.30.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"android-activity",
|
||||
"atomic-waker",
|
||||
"bitflags 2.11.1",
|
||||
@@ -9583,6 +9795,7 @@ dependencies = [
|
||||
"dpi",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"memmap2",
|
||||
"ndk",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
@@ -9594,11 +9807,17 @@ dependencies = [
|
||||
"raw-window-handle",
|
||||
"redox_syscall 0.4.1",
|
||||
"rustix 0.38.44",
|
||||
"sctk-adwaita",
|
||||
"smithay-client-toolkit",
|
||||
"smol_str",
|
||||
"tracing",
|
||||
"unicode-segmentation",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-plasma",
|
||||
"web-sys",
|
||||
"web-time",
|
||||
"windows-sys 0.52.0",
|
||||
@@ -9766,6 +9985,12 @@ version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||
|
||||
[[package]]
|
||||
name = "xcursor"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
|
||||
|
||||
[[package]]
|
||||
name = "xkbcommon-dl"
|
||||
version = "0.4.2"
|
||||
|
||||
@@ -54,12 +54,24 @@ bevy = { version = "0.18", default-features = false, features = [
|
||||
"bevy_window",
|
||||
"custom_cursor",
|
||||
"reflect_auto_register",
|
||||
# default_platform (desktop subset; no android/wayland/webgl/gilrs/sysinfo)
|
||||
# default_platform (desktop subset)
|
||||
"std",
|
||||
"bevy_winit",
|
||||
"default_font",
|
||||
"multi_threaded",
|
||||
# winit prefers Wayland when WAYLAND_DISPLAY is set on the
|
||||
# session and falls through to X11 otherwise. Without `wayland`,
|
||||
# winit-on-Wayland-session falls back to XWayland which renders
|
||||
# the game in an X11 frame inside the Wayland compositor.
|
||||
"wayland",
|
||||
"x11",
|
||||
# Android: NativeActivity glue. The feature is target-gated inside
|
||||
# bevy_internal — desktop builds compile it out, so leaving it on
|
||||
# the always-on list is harmless on Linux/macOS/Windows. Pairs with
|
||||
# cargo-apk's NativeActivity wrapper (cargo-apk 0.10+ uses this by
|
||||
# default). Switch to `android-game-activity` later if we want
|
||||
# AndroidX GameActivity for Google Play Games integration.
|
||||
"android-native-activity",
|
||||
# common_api
|
||||
"bevy_color",
|
||||
"bevy_image",
|
||||
|
||||
@@ -1,179 +1,668 @@
|
||||
# Solitaire Quest — Session Handoff
|
||||
|
||||
**Last updated:** 2026-05-06 (post-v0.18.0 draft) — 24 commits since
|
||||
the v0.17.0 tag bundle the launch-experience round (Restore prompt +
|
||||
auto-show Home / mode picker), the MSSC-style Home picker rework
|
||||
(header chips, draw-mode chips, picture-tile mode cards, Today's
|
||||
Event callout, glyph fixes), the last solver hot path moving onto
|
||||
`AsyncComputeTaskPool`, "Won before" HUD chip, "Copy share link"
|
||||
Stats button, the `N` keybinding finally routing through the real
|
||||
Confirm/Cancel modal, Esc-on-modal layering fixes, and the
|
||||
unified-3.0 Claude rule set (CLAUDE.md / CLAUDE_SPEC.md /
|
||||
CLAUDE_WORKFLOW.md / CLAUDE_PROMPT_PACK.md). Test-discipline prune
|
||||
removed 43 low-value tests in the same window.
|
||||
**Last updated:** 2026-05-08 — v0.20.0 cut and tagged at `41a009a`,
|
||||
all post-cut commits pushed to origin (HEAD = `dd101b3`), working
|
||||
tree clean.
|
||||
The cut itself shipped two through-lines: a full **Terminal visual-
|
||||
identity port** (token system, modal scaffold, gameplay-feedback,
|
||||
toasts, table / card chrome, splash cursor) and the **Android
|
||||
persistence shim** that closes the `dirs::data_dir() = None` pitfall
|
||||
flagged in CLAUDE.md §10. Since the cut, the post-tag work split
|
||||
into two arcs: (1) splash boot-screen port + replay-overlay
|
||||
banner enrichments + desktop-adaptation spec — closing Resume-prompt
|
||||
Options B and C (see "Since the v0.20.0 cut" entries below); and
|
||||
(2) **the card-face artwork regeneration arc — Option D, closed
|
||||
2026-05-08** — full Terminal cards rendering on every face, plus
|
||||
three follow-up fixes that surfaced during sign-off (default-theme
|
||||
SVG override, table backgrounds, top-bar overlap), plus a
|
||||
glyph-orientation tweak (no 180° inverted-corner rotation).
|
||||
|
||||
## Status at pause
|
||||
|
||||
- **HEAD on origin:** `v0.17.0-24-gc497c31` (24 ahead of v0.17.0,
|
||||
not yet tagged).
|
||||
- **Working tree:** clean.
|
||||
- **HEAD locally:** see `git rev-parse HEAD`. Most recent narrative
|
||||
entry below names the latest substantive commit; this status line
|
||||
intentionally avoids hard-coding the SHA so a docs-only edit
|
||||
doesn't immediately stale the handoff.
|
||||
- **HEAD on origin:** matches local. All post-cut commits pushed
|
||||
through `dd101b3`. Decide whether to roll the post-tag work
|
||||
into v0.20.1 / v0.21.0-candidates the next time a release is cut.
|
||||
- **Working tree:** clean. No WIP outstanding.
|
||||
- **`artwork/` directory:** still untracked. Intentional.
|
||||
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings`
|
||||
clean (verified this session).
|
||||
- **Tests:** **1166 passing / 0 failing** across the workspace
|
||||
(verified this session). The first run flaked once on
|
||||
`solitaire_engine::game_plugin::tests::auto_save_writes_after_30_seconds`
|
||||
— a one-frame `app.update()` test that depends on `time.delta_secs()`
|
||||
on an otherwise-fresh `App`. Reproduced clean on the second run;
|
||||
passes in isolation. Worth tightening if it flakes again, but
|
||||
not blocking the v0.18.0 cut.
|
||||
- **Tags on origin:** `v0.9.0` through `v0.17.0`.
|
||||
- **CHANGELOG:** v0.18.0 entry drafted in `[Unreleased]`'s slot —
|
||||
ready for tag once build + tests are reverified.
|
||||
clean.
|
||||
- **Tests:** **1184 passing / 0 failing** across the workspace.
|
||||
Net delta from the 1180 baseline: splash polish added two
|
||||
(`build_scanline_image_has_expected_2x2_rgba_bytes`,
|
||||
`scanline_overlay_spawns_and_fades_with_splash`); the
|
||||
card-face migration added one (`card_face_svg_pin` integration
|
||||
test) and consolidated two (`face_colour` CBM tests folded
|
||||
into `text_colour` CBM tests, net −2 then +1 from pin);
|
||||
call it +4 net.
|
||||
- **Tags on origin:** `v0.9.0` through `v0.20.0`. v0.20.0 is on
|
||||
`41a009a`.
|
||||
|
||||
## Where we are
|
||||
## Since the v0.20.0 cut (un-pushed)
|
||||
|
||||
v0.17.0's punch list had four candidates (A–D); two of the three
|
||||
non-packaging items shipped in this round:
|
||||
### `39b8496` `docs(ui): add Terminal desktop-adaptation spec`
|
||||
|
||||
- **B — "Won previously" HUD indicator:** shipped in `bdac754`.
|
||||
- **C — Replay sharing:** shipped in `540869c` ("Copy share link"
|
||||
Stats button + clipboard via `arboard`, in-memory `LastSharedReplayUrl`).
|
||||
`docs/ui-mockups/desktop-adaptation.md` — 283 lines covering
|
||||
viewport assumptions, seven universal adaptation rules, and per-
|
||||
screen geometry rules for the priority surfaces (Game Table, Win
|
||||
Summary, Settings, Help, Pause, Home, Splash, Stats, and the
|
||||
modal-pattern screens Profile / Achievements / Theme Picker /
|
||||
Daily Challenge). Closes the spec gap — 23 of 24 mockups were
|
||||
mobile-only, but the v0.20.0 token-port pass was already layout-
|
||||
agnostic so nothing shipped broken. The spec matters for *next*
|
||||
ports.
|
||||
|
||||
Item **A** (solver-on-`AsyncComputeTaskPool`) shipped *partially* in
|
||||
`d489e7a` — the winnable-only seed-selection path is now async with
|
||||
cancel-on-replace. The hint path (`H` key,
|
||||
`try_solve_with_first_move` / `try_solve_from_state`) is still
|
||||
synchronous. The proven `PendingNewGameSeed` template is the
|
||||
template for the hint port.
|
||||
**Why rules > visual mockups for this gap:** Stitch's
|
||||
`generate_variants` API timed out on the layout-only adaptation
|
||||
prompt (server-side flake, not a prompt-shape issue — confirmed
|
||||
by polling `list_screens` with no new variant landing). A markdown
|
||||
rules file applies to every screen including the 9 missing-plugin
|
||||
surfaces (splash, challenge, time-attack, weekly-goals,
|
||||
leaderboard, sync, level-up, replay-overlay, radial-menu) that
|
||||
aren't in the Stitch project at all. It's also referenceable from
|
||||
code comments and commit messages without loading an image.
|
||||
|
||||
Item **D** (desktop packaging) is unchanged — still gated on
|
||||
artwork + signing certs from the player.
|
||||
### `cacb19c` `feat(engine): port the splash to the Terminal boot-screen treatment`
|
||||
|
||||
The launch experience is also substantially different from v0.17.0:
|
||||
on first launch with a saved game the player now sees the Restore
|
||||
prompt; on every launch (after splash + restore resolution) they see
|
||||
the auto-show Home / mode picker.
|
||||
Implements the full mockup-spec splash from
|
||||
`docs/ui-mockups/splash-mobile.html` plus the desktop adaptation
|
||||
rules:
|
||||
|
||||
### Design direction (unchanged)
|
||||
- **Header**: cursor block (96 px `▌`), wordmark ("Solitaire
|
||||
Quest"), 192 px divider, "TERMINAL EDITION" subtitle.
|
||||
- **Boot log**: three ✓ check rows (`assets loaded`,
|
||||
`theme: terminal`, `progress restored`) + a `▌ ready_` line.
|
||||
Capped at 480 px width on desktop (else 70 % viewport).
|
||||
- **Progress bar**: 1 px track (`BORDER_SUBTLE`) with a 100 %-
|
||||
width cyan (`ACCENT_PRIMARY`) fill + `DONE · 247 ASSETS`
|
||||
caption. Capped at 720 px on desktop (else 80 %).
|
||||
- **Footer**: `BASE16-EIGHTIES` label, eight palette swatches
|
||||
(12 × 12 px each — one per named token in the design system),
|
||||
version line.
|
||||
|
||||
- **Tone:** Balatro — chunky readable type, theatrical hierarchy,
|
||||
satisfying micro-interactions.
|
||||
- **Palette:** Midnight Purple base + Balatro yellow primary + warm
|
||||
magenta secondary.
|
||||
- See `~/.claude/projects/-home-manage-Rusty-Solitare/memory/project_ux_overhaul_2026-04.md`
|
||||
(machine-local).
|
||||
**Refactored the alpha-fade scaffold** from per-marker queries
|
||||
(`SplashTitle` / `SplashSubtitle` / `SplashCursor`) to a single
|
||||
`SplashFadable { base_color: Color }` + `SplashFadableBg`
|
||||
variant. ~15 fadable elements share one global query each;
|
||||
adding more is one component-attach, not three new query types.
|
||||
|
||||
**Skipped, with rationale captured in the commit:**
|
||||
- Scanline overlay (needs a tiled-pattern asset or custom shader).
|
||||
*Open in "Visual-identity follow-ups" below.*
|
||||
- Pulsing cursor on the "ready_" line (would fight the global
|
||||
fade timeline). *Open in "Visual-identity follow-ups" below.*
|
||||
- "RUSTY SOLITAIRE" wordmark from the mockup (the actual product
|
||||
is "Solitaire Quest"; the mockup leaked the repo name). *Closed
|
||||
— the in-engine wordmark stays "Solitaire Quest".*
|
||||
|
||||
### `c84d9f4` `feat(engine): scrub fill bar + per-frame updater for replay overlay`
|
||||
|
||||
Closes the WIP described in the prior handoff. Adds the 1 px cyan
|
||||
scrub bar called for in `docs/ui-mockups/replay-overlay-mobile.html`:
|
||||
a track in `BORDER_SUBTLE` spans the bottom edge of the banner and
|
||||
the cyan `ACCENT_PRIMARY` fill mirrors `cursor / total` via a new
|
||||
`ReplayOverlayScrubFill` component + `update_scrub_fill` system.
|
||||
The pure `scrub_pct` helper is shared between the spawn path
|
||||
(initial fill width) and the per-frame updater so the first paint
|
||||
already reflects state instead of popping `0 → cursor` on the
|
||||
first tick — same shape as the existing `format_progress` /
|
||||
`update_progress_text` split. Two new tests cover the four corners
|
||||
of `scrub_pct` and an end-to-end drive of `ReplayPlaybackState`
|
||||
asserting `Node.width` on the unique scrub-fill entity. Same
|
||||
change-detection guard as the text updaters, so an idle replay
|
||||
leaves the node untouched.
|
||||
|
||||
Header text treatment (closed by `6204db8` immediately below),
|
||||
move-log scroll, MOVE chip, and WIN MOVE callout from the same
|
||||
mockup are still open — separate commits.
|
||||
|
||||
### `6204db8` `feat(engine): port replay banner label to ▌ cursor-block treatment`
|
||||
|
||||
Aligns the replay overlay's headline with the splash boot-screen
|
||||
idiom landed in `cacb19c`: `Replay` → `▌ replay` and
|
||||
`Replay complete` → `▌ replay complete`. The cursor block (`▌`,
|
||||
U+258C) prefixed to a lowercased label reads as a Terminal output
|
||||
line rather than a generic UI title, tightening the family
|
||||
resemblance between the two top-level overlay surfaces. Pure
|
||||
text-content change; no behavioural shift, no new components, no
|
||||
new systems.
|
||||
|
||||
**Mockup deviation (intentional):** the source mockup string in
|
||||
`docs/ui-mockups/replay-overlay-mobile.html` is `▌replay.tsx`. The
|
||||
`.tsx` is a prototyping leak — Stitch renders in React, so the
|
||||
mockup author reached for a familiar filename — and was dropped
|
||||
for the in-engine version since the codebase is Rust. The `▌` +
|
||||
lowercase pattern is what reads as a Terminal-output-line; the
|
||||
extension is incidental. (Same shape as the "RUSTY SOLITAIRE"
|
||||
wordmark deviation noted under `cacb19c` — the mockup leaked the
|
||||
repo name; the actual product is "Solitaire Quest".)
|
||||
|
||||
### `54005d5` `feat(engine): add GAME #YYYY-DDD caption beneath the replay headline`
|
||||
|
||||
Adds the right-anchored game-identifier piece of the replay-overlay
|
||||
mockup, adapted to live *under* the existing "▌ replay" headline as
|
||||
a `TYPE_CAPTION` (11 px) / `TEXT_SECONDARY` subtitle. Format is
|
||||
`GAME #{year}-{ordinal:03}` (e.g. `GAME #2026-122` for a replay
|
||||
recorded 2026-05-02) — year + chrono ordinal gives a compact,
|
||||
monotonically-increasing identifier matching the mockup's
|
||||
`GAME #2024-127` motif. New `ReplayOverlayGameCaption` marker, new
|
||||
pure helper `format_game_caption(state) -> Option<String>` (None
|
||||
for Inactive / Completed since the replay is consumed in those
|
||||
branches; spawn-time fall-through to empty string).
|
||||
|
||||
**Layout impact:** `BANNER_HEIGHT` bumped 48 → 60 px so the new
|
||||
left column (headline + 2 px gap + caption ≈ 39 px content) fits
|
||||
under the scrub bar with room to spare. +12 px banner mass is the
|
||||
deliberate cost of the new content; no other plugin observes
|
||||
`BANNER_HEIGHT` so the change is local.
|
||||
|
||||
Two new tests (1180 → 1182): `format_game_caption_covers_state_corners`
|
||||
pins the three branches plus the zero-pad-to-3-digits invariant
|
||||
for early-January ordinals; `overlay_game_caption_shows_replay_date`
|
||||
drives `ReplayPlaybackState` end-to-end.
|
||||
|
||||
### `e080b49` `feat(engine): restyle replay progress text as Terminal MOVE chip`
|
||||
|
||||
Closes the centre-text half of the replay-overlay enrichments. The
|
||||
plain "Move N of M" text becomes a 1px `ACCENT_PRIMARY`-bordered
|
||||
chip containing "MOVE N/M" — uppercase + slash separator reads as
|
||||
a Terminal output line and matches the floating-chip motif in
|
||||
`docs/ui-mockups/replay-overlay-mobile.html`. The chip lives
|
||||
in-banner rather than floating above the focused card (the
|
||||
screen-takeover treatment that requires plumbing cursor → card
|
||||
identity remains deferred).
|
||||
|
||||
**Implementation note:** `BorderColor` in Bevy 0.18 is a per-side
|
||||
struct, not a tuple — `BorderColor::all(ACCENT_PRIMARY)` is the
|
||||
correct constructor. Worth pinning for next time we touch a
|
||||
border-painted UI surface. The `ReplayOverlayProgressText` marker
|
||||
stays on the inner Text rather than the new chip Node so
|
||||
`update_progress_text` keeps repainting unchanged — a deliberate
|
||||
"markers belong on the entity that updates change" choice.
|
||||
|
||||
Test count unchanged (1182); `overlay_progress_text_reflects_cursor`
|
||||
swapped its assertion from "Move 5 of 10" to "MOVE 5/10".
|
||||
|
||||
This pair (`54005d5` + `e080b49`) closes Option C from the
|
||||
SESSION_HANDOFF Resume prompt's banner-local enrichments. Floating-
|
||||
chip-above-focused-card and the full screen-takeover redesign
|
||||
remain — both data-layer or cross-plugin and intentionally still
|
||||
open.
|
||||
|
||||
### `29136d8` `feat(engine): add pulsing trailing cursor to splash "▌ ready_" line`
|
||||
|
||||
Closes the cursor-pulse half of the splash polish arc deferred in
|
||||
`cacb19c`. The "▌ ready_" line now ends with a 6×12 px cyan Node
|
||||
that pulses on a 1 s sine cadence, multiplied with the global
|
||||
splash fade timeline so the cursor never reaches full alpha while
|
||||
the rest of the splash is still fading in.
|
||||
|
||||
**The "multiply, don't override" pattern.** Two systems write the
|
||||
same `BackgroundColor` per frame: `advance_splash` writes the
|
||||
global-fade alpha, `pulse_splash_cursor` overwrites with
|
||||
`global_alpha × pulse_factor`. Both derive from `SplashAge` on the
|
||||
root, so the writes are commensurate — the second one isn't
|
||||
"fighting" the first, just refining it. This is the cleanest fix
|
||||
for the "fight the global fade timeline" warning the original
|
||||
`cacb19c` skip note flagged.
|
||||
|
||||
**Defensive division guard.** `cursor_pulse_factor(age, period, min)`
|
||||
short-circuits to `1.0` when `period <= 0.0` so a future
|
||||
misconfiguration produces a steady cursor rather than NaN
|
||||
propagation (NaN in alpha = invisible UI, hard to debug). Worth
|
||||
mirroring on every trig/division helper, not just this one.
|
||||
|
||||
One new test (1182 → 1183): `cursor_pulse_factor_corners` pins the
|
||||
peak (factor = 1 at age = period / 4), trough (factor = min at age =
|
||||
period × 3 / 4), and the zero/negative-period guard.
|
||||
|
||||
### `a27cf5a` `feat(engine): add tiled scanline overlay to splash`
|
||||
|
||||
Closes the scanline half of the splash polish arc. A fullscreen
|
||||
`ImageNode` tiles a runtime-generated 2×2 RGBA8 texture over the
|
||||
splash content — top row transparent, bottom row `#1a1a1a` at
|
||||
~30 % alpha — producing the 1 px-pitch horizontal scanline pattern
|
||||
called for in `docs/ui-mockups/splash-mobile.html`.
|
||||
|
||||
**Texture-α × tint-α composite for fade integration.** The 30 %
|
||||
alpha is baked into the texture pixels, not the `ImageNode.color`
|
||||
tint. `advance_splash`'s new third query writes
|
||||
`(1, 1, 1, global_alpha)` into the tint each tick; the GPU
|
||||
multiplies texture-α by tint-α, so the visible composite is
|
||||
`0.3 × global_alpha`. Cleaner than building a "multiplicative
|
||||
fadable" abstraction in the ECS — the GPU already does this
|
||||
multiplication for free.
|
||||
|
||||
**Bevy 0.18 API surprises (worth pinning):**
|
||||
- `RenderAssetUsages` re-exports under `bevy::asset::`, not
|
||||
`bevy::render::render_asset::`. Type name unchanged; module
|
||||
path moved.
|
||||
- `TextureFormat::pixel_size()` returns `Result<usize, _>` rather
|
||||
than the bare `usize` you'd expect for a static format query.
|
||||
Annoying enough that the `debug_assert_eq!` against the buffer
|
||||
length just hard-codes the `2 × 2 × 4 = 16` literal.
|
||||
|
||||
Headless test fixture now also `init_resource::<Assets<Image>>()`
|
||||
since `MinimalPlugins` doesn't pull `AssetPlugin` — same pattern
|
||||
`settings_plugin::tests` already used. Without it, the
|
||||
`Option<ResMut<Assets<Image>>>` parameter on `spawn_splash` would
|
||||
fall through and the scanline overlay would silently skip,
|
||||
defeating the new tests.
|
||||
|
||||
Two new tests (1183 → 1185):
|
||||
`build_scanline_image_has_expected_2x2_rgba_bytes` locks the
|
||||
texture pixels literally so a future tweak can't drift the
|
||||
appearance silently; `scanline_overlay_spawns_and_fades_with_splash`
|
||||
asserts spawn placement under `SplashRoot` and the new
|
||||
fade-images branch's correctness end-to-end.
|
||||
|
||||
This pair (`29136d8` + `a27cf5a`) closes Option B from the
|
||||
SESSION_HANDOFF Resume prompt — both splash polish pieces now
|
||||
shipped.
|
||||
|
||||
### `5623368`…`dd101b3` — Option D card-face migration arc
|
||||
|
||||
Closed 2026-05-08 across nine commits. The full Terminal card
|
||||
artwork now renders end-to-end. Detail breakdown lives in the
|
||||
"Visual-identity follow-ups" punch-list entry below; the short
|
||||
version:
|
||||
|
||||
- Migration plan + pipeline tooling: `5623368` (plan doc),
|
||||
`3a4bb63` (single-card PoC proving the `usvg`/`resvg` pipeline
|
||||
at per-card grain), `babe5cc` (full
|
||||
`solitaire_engine/examples/card_face_generator.rs` example
|
||||
emitting 52 faces + 5 backs into `assets/cards/`), `48b28d2`
|
||||
(the `card_face_svg_pin` integration test pinning rasteriser
|
||||
output via inline FNV-1a hashing of raw RGBA8 bytes — the
|
||||
pin's bootstrap pattern, "empty `EXPECTED` → run → paste",
|
||||
is the maintenance interface for future intentional changes).
|
||||
- Lockstep step 4+5: `e8bf9d7`. New PNG bytes + the 5
|
||||
`card_plugin` constants (`CARD_FACE_COLOUR`,
|
||||
`RED_SUIT_COLOUR`, `BLACK_SUIT_COLOUR`,
|
||||
`CARD_FACE_COLOUR_RED_CBM` → `RED_SUIT_COLOUR_CBM`,
|
||||
`card_back_colour`) + signature shifts in one commit.
|
||||
`face_colour` deleted — Terminal face is uniformly
|
||||
`CARD_FACE_COLOUR` regardless of CBM, so the function
|
||||
collapsed to a constant. `text_colour` gained a
|
||||
`color_blind: bool` parameter (red→cyan suit-glyph swap when
|
||||
CBM is on). Four `face_colour` CBM tests folded into two
|
||||
`text_colour` CBM tests in lockstep.
|
||||
- Three follow-ups that surfaced during sign-off, all from the
|
||||
same "fallback path the migration walked past" pattern:
|
||||
`a14200a` regenerated the embedded **default-theme SVGs** at
|
||||
`solitaire_engine/assets/themes/default/*.svg`; those bytes
|
||||
`include_bytes!()`-embed into the binary and override
|
||||
`assets/cards/*.png` at startup, so the PNG migration alone
|
||||
didn't change what production rendered. `8719f77`
|
||||
regenerated `assets/backgrounds/bg_*.png` to flat Terminal
|
||||
near-black (5 solid-colour PNGs via a new
|
||||
`solitaire_engine/examples/background_generator.rs` example).
|
||||
`ae84dc1` cleared the **top-bar overlap** at portrait/narrow
|
||||
window widths by swapping the action-button row's hardcoded
|
||||
`font_size: 16.0` to `TYPE_BODY` (a typography-migration
|
||||
miss) and stepping horizontal padding from `VAL_SPACE_3`
|
||||
to `VAL_SPACE_2`.
|
||||
- Glyph-rendering fix: `af414b6`. The bundled `FiraMono`
|
||||
doesn't carry usable U+2660-2666 glyphs at the requested
|
||||
size — `usvg` was silently substituting tiny "tofu" marks.
|
||||
Switched suit glyphs from `<text>` elements to inline SVG
|
||||
`<path>` elements via a new `suit_path_d` helper. Path-based
|
||||
rendering bypasses the font system entirely; same bytes on
|
||||
every machine, no fontdb dependency, no substitution risk.
|
||||
Same path data renders correctly whether filled (♥ ♠) or
|
||||
outlined (♦ ♣ — the always-on color-blind glyph
|
||||
differentiation).
|
||||
- Glyph-orientation tweak: `dd101b3`. Removed the 180° rotation
|
||||
from the bottom-right large suit glyph at user request. Both
|
||||
glyphs now render upright. `design-system.md` § Game Cards
|
||||
line 220 updated in lockstep — the deliberate deviation from
|
||||
the traditional inverted-corner-indicator convention is
|
||||
documented in the spec, not just the code.
|
||||
|
||||
The pin test fired exactly twice during this arc (once for the
|
||||
text→path switch, once for the unrotation) and rebaselined
|
||||
cleanly each time via the empty-then-paste pattern. The 5
|
||||
`back_*` hashes stayed identical across both rebaselines —
|
||||
secondary signal that the FNV-1a fingerprinting is purely
|
||||
deterministic on rasteriser output.
|
||||
|
||||
This arc closes Option D from the SESSION_HANDOFF Resume prompt
|
||||
and effectively completes the Terminal visual-identity port —
|
||||
only the toast warning/error variant slots remain wired-but-
|
||||
unused.
|
||||
|
||||
## What shipped in v0.20.0 (frozen at `41a009a`)
|
||||
|
||||
### Terminal visual-identity port
|
||||
|
||||
Top-down stack — every commit downstream of the token system
|
||||
reads from it, so swapping the palette is now a one-file edit:
|
||||
|
||||
- **`ui_theme` token system** (`0d477ac`). base16-eighties
|
||||
palette, 5-rung type scale, 7-rung 4-multiple spacing scale,
|
||||
3-step radius, 14-rung z-index hierarchy, full motion budget,
|
||||
4 invariant-pinning unit tests. Card-shadow alphas pinned to 0
|
||||
(Terminal achieves depth via 1px borders + tonal layering).
|
||||
- **Modal scaffold already on tokens** — `ui_modal` was ported
|
||||
in the same commit's wake; three stale "loud yellow" /
|
||||
"magenta secondary" doc comments fixed.
|
||||
- **Gameplay feedback → semantic state tokens** (`ceec4fc`).
|
||||
Selection / valid-drop tints route through `ACCENT_PRIMARY` /
|
||||
`STATE_WARNING` / `STATE_SUCCESS`.
|
||||
- **Toasts** (`a137607`). New `ToastVariant` enum
|
||||
(Info / Warning / Error / Celebration); opaque `BG_ELEVATED`
|
||||
+ 1px accent border + bottom-anchor. All ten call sites pass
|
||||
their semantic variant.
|
||||
- **`table_plugin` chrome** (`651f406`).
|
||||
`PILE_MARKER_DEFAULT_COLOUR` promoted; `cursor_plugin` imports
|
||||
it, replacing a "kept in sync" doc comment with a compile-
|
||||
enforced invariant. `HINT_PILE_HIGHLIGHT_COLOUR` →
|
||||
`STATE_WARNING`.
|
||||
- **`card_plugin` chrome** (`d752870`). Drag-elevation shadow
|
||||
routes through `CARD_SHADOW_*` tokens. `RIGHT_CLICK_HIGHLIGHT_COLOUR`
|
||||
→ `STATE_SUCCESS`. Stock recycle "↺" text → `TEXT_PRIMARY @ 0.7α`.
|
||||
Card-face / suit / card-back palette intentionally NOT migrated
|
||||
(artwork dependency — see open-list item below).
|
||||
- **Splash cursor** (`cdcadda`). The signature `▌` cyan glyph
|
||||
(96 px) added above the wordmark, matching the spec.
|
||||
*Subsequently expanded post-cut by `cacb19c` into the full
|
||||
boot-screen treatment.*
|
||||
- **Hint-source / dest pairing** (`9891ae4`). `input_plugin`'s
|
||||
source-card tint now matches the destination pile's
|
||||
`STATE_WARNING`.
|
||||
- **Design system + 24-mockup library** (`fa7f98a`).
|
||||
`docs/ui-mockups/design-system.md` + 24 Stitch mockups (HTML +
|
||||
PNG) covering every screen plus 9 missing-plugin surfaces.
|
||||
- **`card_shadow_params` test aligned** (`1d1543e`). Drag-vs-
|
||||
idle shadow assertion loosened to `>=` to accept the Terminal
|
||||
"no shadow" intent without losing the regression-guard.
|
||||
|
||||
### Android persistence
|
||||
|
||||
- **`solitaire_data::data_dir` shim** (`4b51e50`). New
|
||||
`solitaire_data::platform::data_dir()` falls through to
|
||||
`dirs::data_dir()` on desktop and returns the per-app sandbox
|
||||
at `/data/data/com.solitairequest.app/files` on Android — no
|
||||
JNI needed (package id pinned in `[package.metadata.android]`).
|
||||
Six `solitaire_data` callsites + `solitaire_engine/assets/user_dir.rs`
|
||||
migrated. Settings, stats, achievements, replays, game-state,
|
||||
time-attack sessions, and user themes now persist on Android.
|
||||
|
||||
### Inherited from earlier in the cycle (pre-session)
|
||||
|
||||
- Android build target + APK (`fb8b2ac`), runbook (`59424a3`),
|
||||
F3 FPS overlay (`690e1d2`), Smart Window Size opt-out
|
||||
(`e1b8766`), Shareable badge (`9b065e5`), Help cheat-sheet
|
||||
M/P/Enter rows (`35516d3`), `pull_failure_sets_error_status`
|
||||
flake fix (`67c150b`).
|
||||
|
||||
## Open punch list
|
||||
|
||||
### Phase Android (build + persistence shipped; runtime gaps remain)
|
||||
|
||||
- **APK launch verification on AVD / device.** `adb install` then
|
||||
`adb logcat` against the `bevy_test` AVD or an x86_64 device.
|
||||
The build works and persistence is wired, but no end-to-end
|
||||
device run has been logged. Shakes out runtime bugs the build +
|
||||
unit tests can't catch.
|
||||
- **JNI ClipboardManager bridge.** Replaces the Android stub for
|
||||
the Stats "Copy share link" toast. `arboard` doesn't ship an
|
||||
Android backend; small custom JNI call.
|
||||
- **Android Keystore for credentials.** `keyring` is target-gated
|
||||
to a stub returning `KeychainUnavailable`; replace with Android
|
||||
Keystore via JNI when sync auth ships on mobile.
|
||||
- **Google Play Games (gpgs) integration.** Listed as a
|
||||
Phase-Android target since Phase 1; now unblocked by the build
|
||||
target.
|
||||
- **Cosmetic `cargo apk build --lib` workaround.** Post-sign
|
||||
panic doesn't affect the APK on disk but produces noisy stderr.
|
||||
Either upstream a cargo-apk fix or document `--lib` as
|
||||
canonical in the runbook.
|
||||
|
||||
### Visual-identity follow-ups (opened by v0.20.0's port)
|
||||
|
||||
- *Card-face / suit / card-back artwork regeneration — closed
|
||||
2026-05-08 by the commit chain `5623368` → `dd101b3`.* The
|
||||
Terminal spec called for dark `#1a1a1a` cards with light suit
|
||||
pips (pink for hearts/diamonds, foreground gray for spades/
|
||||
clubs). Closed across nine commits over two arcs:
|
||||
- **Plan + tooling (`5623368`–`48b28d2`):** migration plan
|
||||
doc, single-card PoC, full `card_face_generator` example
|
||||
(52 faces + 5 backs into `assets/cards/`), and the
|
||||
`card_face_svg_pin` integration test pinning rasteriser
|
||||
output via FNV-1a so future `usvg`/`resvg` upgrades surface
|
||||
as test failures rather than silent visual drift.
|
||||
- **Lockstep step 4+5 (`e8bf9d7`):** PNGs + the 5 `card_plugin`
|
||||
constants + signature shifts in one commit.
|
||||
`CARD_FACE_COLOUR_RED_CBM` renamed to `RED_SUIT_COLOUR_CBM`
|
||||
and repurposed from a face-tint to a suit-glyph swap (the
|
||||
Terminal face is uniform `CARD_FACE_COLOUR` regardless of
|
||||
CBM; CBM only swaps red suits to cyan in the glyph itself).
|
||||
`face_colour` deleted, `text_colour` gained a `color_blind`
|
||||
parameter.
|
||||
- **Three follow-ups that surfaced during sign-off:**
|
||||
`a14200a` regenerated the **default-theme SVGs** at
|
||||
`solitaire_engine/assets/themes/default/*.svg` — those
|
||||
`include_bytes!()`-embed into the binary and override
|
||||
`assets/cards/*.png` at runtime, so the PNG migration alone
|
||||
didn't change what production rendered. `8719f77`
|
||||
regenerated `assets/backgrounds/bg_*.png` to flat Terminal
|
||||
near-black (5 solid-colour PNGs via a new
|
||||
`background_generator` example). `ae84dc1` cleared the
|
||||
**top-bar overlap** at portrait/narrow window widths by
|
||||
swapping the action-button row's hardcoded `font_size: 16.0`
|
||||
to `TYPE_BODY` and stepping horizontal padding from
|
||||
`VAL_SPACE_3` to `VAL_SPACE_2`.
|
||||
- **Glyph-rendering fix (`af414b6`):** suit glyphs render as
|
||||
inline SVG paths (not `<text>`) because the bundled
|
||||
`FiraMono` doesn't carry usable U+2660-2666 at the
|
||||
requested size — `usvg` was silently substituting tiny
|
||||
"tofu" marks. Path-based rendering bypasses the font system
|
||||
entirely; same bytes on every machine. The pin test
|
||||
rebaselined cleanly via the empty-then-paste pattern.
|
||||
- **Glyph-orientation tweak (`dd101b3`):** removed the 180°
|
||||
rotation from the bottom-right large suit glyph at user
|
||||
request — both glyphs now render in the same upright
|
||||
orientation. `design-system.md` § Game Cards line 220
|
||||
updated in lockstep to document the deliberate deviation
|
||||
from the traditional inverted-corner-indicator convention.
|
||||
- *Splash boot-loader scanline overlay — closed by `a27cf5a`.*
|
||||
Runtime-generated 2 × 2 RGBA8 texture tiled via
|
||||
`NodeImageMode::Tiled`; per-pixel alpha × tint alpha gives
|
||||
multiplicative fade integration without new abstractions.
|
||||
- *Splash cursor pulse — closed by `29136d8`.* Trailing 6 × 12 px
|
||||
cyan Node, sine-pulsed, multiplied with the global splash fade
|
||||
(the "multiply, don't override" pattern that resolves the
|
||||
original `cacb19c` skip-rationale).
|
||||
- **Replay-overlay enrichments beyond the scrub bar.** Banner-local
|
||||
pieces of the mockup (`docs/ui-mockups/replay-overlay-mobile.html`)
|
||||
all shipped: scrub bar (`c84d9f4`), `▌ replay` cursor-block label
|
||||
(`6204db8`), `GAME #YYYY-DDD` caption (`54005d5`), `MOVE N/M`
|
||||
chip restyle (`e080b49`). What's still open are the cross-plugin
|
||||
/ data-layer pieces: a `MOVE N/M` chip *floating above the
|
||||
focused card* during playback (would need to thread the cursor
|
||||
through to the card layer — `update_progress_text` writes the
|
||||
banner chip but the card-position lookup belongs in `card_plugin`).
|
||||
The full mockup's screen-takeover treatment — mini-tableau
|
||||
preview, playback controls, move-log scroll, WIN MOVE marker on
|
||||
the scrub bar — is a multi-session redesign with
|
||||
data-layer impact (move-log scroller; the WIN MOVE marker
|
||||
needs a `win_move_index` field on `Replay` that doesn't yet
|
||||
exist). Banner-overlay behaviour is intentionally preserved
|
||||
for now.
|
||||
- **Toast Warning / Error variants.** The `ToastVariant` enum
|
||||
has slots for `Warning` (gold) and `Error` (pink) but no
|
||||
in-engine event uses them yet. Wire when a warning- or error-
|
||||
flavoured toast event materialises.
|
||||
|
||||
### Carried forward from v0.19.0
|
||||
|
||||
- **App icon round.** `Window::icon` not yet wired; no
|
||||
`.icns` / `.ico` / Linux hicolor PNG hierarchy. The 11-size
|
||||
icon export the v0.19 handoff referenced is *not* currently
|
||||
in `artwork/` (current `artwork/` holds the reverted Rusty
|
||||
Pixel card PNGs and is intentionally untracked); icon-export
|
||||
needs to be re-run before this item can be picked up.
|
||||
Half-day task once the PNGs are back in place. No cert
|
||||
dependency.
|
||||
|
||||
### Other small candidates
|
||||
|
||||
- **Prev/Next selector chips spawn site.** v0.19.0's `9b065e5`
|
||||
noted Prev/Next markers exist in `stats_plugin` but no spawn
|
||||
site renders them today — the Shareable badge therefore lands
|
||||
on the single-replay caption. If/when Prev/Next is plumbed,
|
||||
the badge will need to follow.
|
||||
- **Toast queue / immediate unification.** The two toast paths
|
||||
(`spawn_queued_toast` for `InfoToastEvent` queue; `spawn_toast`
|
||||
for fire-and-forget) now share visual treatment but remain
|
||||
separate functions because they serve different temporal
|
||||
needs (sequential vs. parallel). If overlap becomes a UX
|
||||
issue, merge into one queue with priority lanes.
|
||||
|
||||
### Process notes
|
||||
|
||||
- **The desktop-adaptation spec is the canonical reference for
|
||||
geometry decisions** when porting any future plugin. Read
|
||||
`docs/ui-mockups/desktop-adaptation.md` first; apply the
|
||||
universal rules to every surface; consult the per-screen
|
||||
table for the priority surfaces. The 9 missing-plugin screens
|
||||
(splash now ported; eight remaining) inherit the universal
|
||||
rules without dedicated guidance.
|
||||
- **Stitch `generate_variants` is unreliable for layout-only
|
||||
adaptation prompts** as of 2026-05-07. The first call timed
|
||||
out and no variant ever landed in `list_screens`. If a future
|
||||
session wants visual desktop mockups, prefer
|
||||
`generate_screen_from_text` with a fresh narrow prompt per
|
||||
screen rather than `generate_variants` against existing
|
||||
mobile screens.
|
||||
- **Token-port pattern.** v0.20.0's chrome-migration commits
|
||||
set a reusable shape for "centralised design system applied
|
||||
across N plugins":
|
||||
1. Constants module (`ui_theme.rs`) is the source of truth.
|
||||
2. Const sites that can't call `Alpha::with_alpha` (not yet
|
||||
`const` on stable) use a literal RGB matching the token,
|
||||
with a unit test pinning the RGB to the token (e.g.
|
||||
`MARKER_VALID`, `HINT_PILE_HIGHLIGHT_COLOUR`,
|
||||
`RIGHT_CLICK_HIGHLIGHT_COLOUR`).
|
||||
3. Cross-plugin duplication (e.g. `MARKER_DEFAULT` ↔
|
||||
`PILE_MARKER_DEFAULT_COLOUR`) collapses to a single
|
||||
promoted const re-exported from one plugin and imported
|
||||
by the other — replaces "kept in sync" doc comments with a
|
||||
compile-time invariant.
|
||||
4. Domain colours (suit pips, card faces, lerp helpers) stay
|
||||
as literals with a comment naming the rationale; only UI
|
||||
chrome routes through tokens.
|
||||
- **`SplashFadable` scaffolding pattern** (introduced in
|
||||
`cacb19c`). Any future overlay that needs to fade `N >> 3`
|
||||
elements together should follow the same shape: one tiny
|
||||
marker carrying the full-alpha base colour, one global query
|
||||
that lerps every marker's alpha each frame, no per-element
|
||||
query plumbing. Cleanly outscales the `Without<X>, Without<Y>`
|
||||
query exclusion pattern that the old splash was hitting at
|
||||
three siblings.
|
||||
|
||||
### Canonical remote
|
||||
|
||||
`github.com/funman300/Rusty_Solitaire` is the canonical repo.
|
||||
Always push there.
|
||||
Always push there. **Local master has unpushed post-cut commits**
|
||||
— run `git log --oneline origin/master..HEAD` for the live list;
|
||||
`git push` is the next durability step (or roll the post-cut
|
||||
commits into v0.20.1).
|
||||
|
||||
## v0.18.0 (drafted 2026-05-06, not yet tagged)
|
||||
### Design direction (Terminal — base16-eighties)
|
||||
|
||||
| Area | Commit | What landed |
|
||||
|---|---|---|
|
||||
| Restore prompt | `3c7a0eb` + `f863d85` | Welcome-back modal on launch when an in-progress save exists; save preserved across exits while the prompt is unanswered. |
|
||||
| Async winnable-only seeds | `d489e7a` | `PendingNewGameSeed` resource + `poll_pending_new_game_seed` running `.before(GameMutation)`. Fixes the worst-case 6 s UI stall on a New Game click. Cancel-on-replace contract covered by tests. |
|
||||
| Won-before HUD chip | `bdac754` | Reads `ReplayHistoryResource`; lights `✓ Won before` on tier-2 row when current `(seed, draw_mode, mode)` is in history. |
|
||||
| Copy share link | `540869c` | `arboard` clipboard + new Stats button + `SyncProvider::push_replay` returning the share URL. In-memory only; per-session sharing. |
|
||||
| MSSC Home picker | `ae40a1d`, `b73d246`, `9fe650f`, `40d6e0a`, `c30b04e`, `d065d49` | Header stats strip (clickable → Profile), draw-mode chips, per-mode score/streak chips, Today's Event callout on Daily, picture-tile 2-up grid with FiraMono-covered glyphs (♣ ◆ ○ ▲ →). |
|
||||
| Auto-show Home | `dd63261`, `b7c3a49`, `c497c31` | Auto-shows after splash; gated on Restore prompt; freezes timers (elapsed + Time Attack) while up. |
|
||||
| `N` opens real modal | `93660c2` | Removes the "Press N again" double-tap; routes through `ConfirmNewGameScreen`. `Shift+N` retains the bypass. |
|
||||
| Win Summary keyboard | `17e0737` | Enter dismisses + starts a fresh deal. |
|
||||
| Esc-on-modal fixes | `08b006f`, `d48b948`, `9aa0dd2` | Esc no longer opens Pause underneath the modal it just closed; Home maps Esc to Cancel; Restore maps Esc to Continue; topmost-modal-wins when Profile stacks on Home. |
|
||||
| Layout fixes | `a4bc063`, `cc63532` | Settings rows full-width with label-spacer-cluster; popover rows excluded from action-bar auto-fade. |
|
||||
| Empty-state copy | `56e2e6f` | Leaderboard / Achievements onboarding hints; volume hotkeys emit toast feedback. |
|
||||
| Test prune | `a49a340` | −43 low-value tests; future briefs request behaviour contracts only. |
|
||||
| Docs unified-3.0 | `f2f30c8` | Adopts CLAUDE.md / CLAUDE_SPEC.md / CLAUDE_WORKFLOW.md / CLAUDE_PROMPT_PACK.md; trims duplicated rule passages. |
|
||||
|
||||
## Open punch list
|
||||
|
||||
### Carried forward from v0.17.0
|
||||
|
||||
- **Solver-on-`AsyncComputeTaskPool` for the H-key hint** —
|
||||
remaining synchronous solver hot path. The seed-selection port
|
||||
in `d489e7a` is the template: `PendingHintTask` resource, polling
|
||||
system running `.before(GameMutation)`, cancel-on-replace, fall
|
||||
back to the heuristic on inconclusive. Diff should stay scoped
|
||||
to `input_plugin.rs` plus a small `pending_hint.rs`.
|
||||
- **Desktop packaging** per `ARCHITECTURE.md §17`. Arch PKGBUILD
|
||||
exists in `/home/manage/solitaire-quest-pkgbuild/` (separate
|
||||
repo). Pending: app icon, macOS `.icns` + notarisation cert,
|
||||
Windows `.ico` + Authenticode cert, AppImage recipe.
|
||||
|
||||
### New this round
|
||||
|
||||
- **Persistent share link.** `LastSharedReplayUrl` is in-memory only
|
||||
— the player must share within the session of the win. If
|
||||
cross-session sharing turns into a real ask, persist alongside
|
||||
the rolling replay history.
|
||||
- **Per-mode artwork.** Picture tiles use Unicode glyphs as
|
||||
placeholders chosen from FiraMono's actual coverage. When real
|
||||
artwork lands, swap each tile's `Text` node for an `Image` node
|
||||
— tile layout, focus order, click handling, and chip rendering
|
||||
are unchanged.
|
||||
|
||||
### Process notes (from this round)
|
||||
|
||||
- **Test inflation pattern (resolved this round):** older agent
|
||||
briefs reflexively asked for ≥3 tests per feature, producing 43
|
||||
low-value coverage entries on stdlib/serde-derive mechanics. Going
|
||||
forward, ask for tests that pin behaviour contracts or
|
||||
regressions on real bugs only. See
|
||||
`feedback_test_discipline.md` in auto-memory.
|
||||
- **Solver async refactor sequencing (worked this round):** rather
|
||||
than porting the whole solver-on-main-thread surface in one PR
|
||||
(the rollback case from before v0.17.0), the
|
||||
`PendingNewGameSeed` work shipped one well-bounded path with two
|
||||
tests covering the happy path and cancel-on-replace. The hint
|
||||
port should follow the same shape.
|
||||
- **Tone:** retro-terminal / synthwave — flat depth (no box-shadows),
|
||||
monospaced-forward typography (JetBrains Mono / FiraMono), tight
|
||||
16 px edge margins, 8 px card radius.
|
||||
- **Palette:** near-black surface ramp (`#151515` / `#202020` /
|
||||
`#2a2a2a` / `#353535`), cyan primary CTA (`#6fc2ef`), lime
|
||||
success (`#acc267`), gold warning (`#ddb26f`), pink error /
|
||||
suit-red (`#fb9fb1`), lavender celebration (`#e1a3ee`), teal
|
||||
info (`#12cfc0`).
|
||||
- **Two-color suits.** Red = `#fb9fb1`, black = `#d0d0d0`.
|
||||
Outlined glyphs for diamonds & clubs are *always on*; the
|
||||
Settings "color-blind mode" toggle only swaps red → cyan.
|
||||
|
||||
## Resume prompt
|
||||
|
||||
```
|
||||
You are a senior Rust + Bevy developer working on Solitaire Quest.
|
||||
Working directory: <Rusty_Solitaire clone path on this machine>.
|
||||
Branch: master. Direction is OPEN — v0.18.0 has been drafted but
|
||||
not tagged: 24 commits past v0.17.0 cover the launch-experience
|
||||
round, MSSC Home picker, async winnable-only seeds, Won-before
|
||||
HUD, Copy share link, N-key flow rework, Esc-layering fixes, and
|
||||
the unified-3.0 Claude rule set.
|
||||
Branch: master. v0.20.0 is tagged at 41a009a; the post-cut work
|
||||
through dd101b3 is pushed to origin (Options B, C, D all closed).
|
||||
Run `git log --oneline 41a009a..HEAD` to see what landed since the
|
||||
tag — substantives: desktop-adaptation spec, splash boot-screen
|
||||
port, replay-overlay banner enrichments, and the full card-face
|
||||
artwork arc (52 faces + 5 backs as Terminal SVG-rasterised PNGs,
|
||||
default-theme SVGs in lockstep, table backgrounds flattened,
|
||||
top-bar layout fix, glyph orientation upright).
|
||||
|
||||
State: HEAD at v0.17.0-24-gc497c31. Working tree clean.
|
||||
CHANGELOG.md has the v0.18.0 entry slotted under [Unreleased].
|
||||
State: HEAD locally — see `git rev-parse HEAD`. Working tree is
|
||||
clean. All workspace tests pass (~1180+; check with
|
||||
`cargo test --workspace`), clippy clean.
|
||||
|
||||
READ FIRST (in order, before doing anything):
|
||||
1. SESSION_HANDOFF.md — this file
|
||||
2. CHANGELOG.md — v0.18.0 draft entry
|
||||
2. CHANGELOG.md — [0.20.0] section is the most recent cut
|
||||
3. CLAUDE.md — unified-3.0 rule set
|
||||
4. CLAUDE_SPEC.md — formal architecture spec
|
||||
5. ARCHITECTURE.md — crate responsibilities + data flow
|
||||
6. ~/.claude/projects/<this-project>/memory/MEMORY.md
|
||||
6. docs/ui-mockups/ — design system + 24-mockup library +
|
||||
desktop-adaptation.md (the rules-based
|
||||
companion to the mockups; read this
|
||||
before any plugin port)
|
||||
7. docs/android/* — Android setup + build runbook
|
||||
8. ~/.claude/projects/<this-project>/memory/MEMORY.md
|
||||
— saved feedback / project context
|
||||
(machine-local; may be missing on a
|
||||
fresh machine)
|
||||
|
||||
DECISION TO ASK THE PLAYER FIRST:
|
||||
A. Tag v0.18.0 — promote `[Unreleased]` to `[0.18.0]` (already
|
||||
done in this session's draft), reverify build + clippy +
|
||||
tests, tag, push. Mechanical close-out.
|
||||
B. Solver-on-AsyncComputeTaskPool for the H-key hint, using the
|
||||
`d489e7a` seed-selection port as template. Last synchronous
|
||||
solver hot path. Smallest delta on the open punch list.
|
||||
C. Desktop packaging — needs artwork + signing certs from the
|
||||
player; can't be driven by the agent alone.
|
||||
D. Persistent share link — store the URL alongside replay
|
||||
history so cross-session sharing works.
|
||||
A. Push the post-cut commits to origin. Either as-is on master
|
||||
or rolled into a v0.20.1 cut (CHANGELOG entry + tag).
|
||||
Mechanical, but local master diverges from origin until done.
|
||||
B. *Closed by `29136d8` + `a27cf5a`.* Both splash polish
|
||||
pieces shipped (cursor pulse + scanline overlay). No further
|
||||
splash work pending unless a new mockup detail surfaces.
|
||||
C. *Closed by `54005d5` + `e080b49`.* Banner-local replay-overlay
|
||||
pieces all shipped (scrub bar, ▌ label, GAME caption, MOVE
|
||||
chip). Remaining are cross-plugin (floating MOVE chip above
|
||||
the focused card — needs cursor → card-position plumbing) or
|
||||
multi-session (full screen-takeover redesign — move-log
|
||||
scroll, mini tableau, WIN MOVE marker, data-layer impact).
|
||||
Either belongs in its own decision tree the next time replay
|
||||
work surfaces.
|
||||
D. *Closed 2026-05-08 by `5623368`…`dd101b3`.* The full
|
||||
card-face / suit / card-back / default-theme / table-
|
||||
background / top-bar / glyph-orientation arc landed across
|
||||
nine commits. Terminal cards rendering on every face (dark
|
||||
`#1a1a1a` background, pink/gray suit glyphs as inline SVG
|
||||
paths, scanline-pattern cyan-accent backs); both rendering
|
||||
paths (`assets/cards/*.png` and the bundled-default theme
|
||||
SVGs at `solitaire_engine/assets/themes/default/*.svg`) in
|
||||
lockstep; pin test (`card_face_svg_pin`) guards against
|
||||
future rasteriser drift. Visual-identity arc effectively
|
||||
complete — only the toast warning/error variant slots
|
||||
remain wired-but-unused.
|
||||
E. App icon round — re-run artwork/Icon Export.html (the
|
||||
export PNGs are not currently in `artwork/`), then wire
|
||||
Window::icon + generate .icns / .ico. Half-day task. No
|
||||
cert dependency.
|
||||
F. APK launch verification on AVD / device + the JNI bridges
|
||||
it would shake out (ClipboardManager, Keystore).
|
||||
|
||||
WORKFLOW NOTES:
|
||||
- Commits use:
|
||||
git -c user.name=funman300 -c user.email=root@vscode.infinity \
|
||||
commit -m "..."
|
||||
- Use the system git config (already correct).
|
||||
- When attributing playtester feedback in commits/docs, use
|
||||
"Quat" not "Rhys" (saved feedback memory).
|
||||
- Sub-agents stage + verify only; orchestrator commits.
|
||||
- Every commit must pass build / clippy / test before pushing.
|
||||
- Push to GitHub (origin) — that is the canonical remote.
|
||||
- Push to GitHub (origin) — gh auth setup-git wired on
|
||||
primary dev box; verify on laptop before first push.
|
||||
|
||||
OPEN AT THE START: ask which of A–D. Don't pick unilaterally.
|
||||
OPEN AT THE START: ask which of A–F. Don't pick unilaterally.
|
||||
```
|
||||
|
||||
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 469 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 469 B |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 469 B |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 472 B |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 472 B |
|
Before Width: | Height: | Size: 2.3 MiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 283 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 300 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 357 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 300 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 318 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 365 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 179 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 256 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,228 @@
|
||||
# Android build — developer setup
|
||||
|
||||
This doc captures the toolchain install + build invocation for the
|
||||
Android target. Steps are runnable on a fresh Debian 13 (trixie) box;
|
||||
later sections document what's known to compile, what's stubbed, and
|
||||
the next milestones.
|
||||
|
||||
> **Status (2026-05-07):** First working APK at `fb8b2ac`. 54 MB
|
||||
> debug-signed `solitaire-quest.apk` for `x86_64-linux-android`. Has
|
||||
> NOT yet been verified to launch on a device or emulator — that's
|
||||
> the next milestone.
|
||||
|
||||
---
|
||||
|
||||
## 1. Toolchain install (Debian 13 / trixie)
|
||||
|
||||
Run as one block. Will pull ~15-20 GB of disk between APT, the SDK,
|
||||
the NDK, the system image, and Rust target sysroots. Requires sudo.
|
||||
|
||||
```bash
|
||||
# 1. JDK 21 (Android tooling needs JDK 17+; Debian 13 default is 21).
|
||||
sudo apt update && sudo apt install -y openjdk-21-jdk-headless unzip wget
|
||||
|
||||
# 2. SDK directory + Google's cmdline-tools bootstrap.
|
||||
export ANDROID_HOME="$HOME/Android/Sdk"
|
||||
mkdir -p "$ANDROID_HOME/cmdline-tools"
|
||||
wget -O /tmp/cmdline-tools.zip \
|
||||
https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip
|
||||
unzip -q /tmp/cmdline-tools.zip -d "$ANDROID_HOME/cmdline-tools"
|
||||
mv "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/latest"
|
||||
rm /tmp/cmdline-tools.zip
|
||||
|
||||
# 3. Persist env vars.
|
||||
{
|
||||
echo ''
|
||||
echo '# Android dev'
|
||||
echo 'export ANDROID_HOME="$HOME/Android/Sdk"'
|
||||
echo 'export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/26.3.11579264"'
|
||||
echo 'export JAVA_HOME="$(dirname $(dirname $(readlink -f $(which java))))"'
|
||||
echo 'export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator"'
|
||||
} >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
|
||||
# 4. Accept SDK licences (interactive prompts answered by `yes |`).
|
||||
yes | sdkmanager --licenses
|
||||
|
||||
# 5. Platform packages — ~5 GB.
|
||||
sdkmanager \
|
||||
"platform-tools" \
|
||||
"platforms;android-34" \
|
||||
"build-tools;34.0.0" \
|
||||
"ndk;26.3.11579264" \
|
||||
"emulator" \
|
||||
"system-images;android-34;google_apis;x86_64"
|
||||
|
||||
# 6. AVD for testing (one-time).
|
||||
echo no | avdmanager create avd \
|
||||
-n bevy_test \
|
||||
-k "system-images;android-34;google_apis;x86_64" \
|
||||
-d pixel_7
|
||||
|
||||
# 7. Rust cross-compile targets.
|
||||
rustup target add \
|
||||
aarch64-linux-android \
|
||||
armv7-linux-androideabi \
|
||||
x86_64-linux-android \
|
||||
i686-linux-android
|
||||
|
||||
# 8. cargo-apk.
|
||||
cargo install cargo-apk
|
||||
```
|
||||
|
||||
Sanity:
|
||||
|
||||
```bash
|
||||
java --version | head -1 # openjdk 21.0.x
|
||||
adb --version | head -1 # 35.x or higher
|
||||
sdkmanager --list_installed | head # build-tools, emulator, ndk, platforms, system-images
|
||||
avdmanager list avd | head # bevy_test
|
||||
rustup target list --installed | grep android # 4 targets
|
||||
cargo apk --help | head -5
|
||||
```
|
||||
|
||||
If `sdkmanager --version` errors with `JAVA_HOME is not set`, the env
|
||||
section in step 3 didn't apply to your shell — `source ~/.bashrc`
|
||||
again or open a new terminal.
|
||||
|
||||
### Optional: emulator runtime libs
|
||||
|
||||
The Android emulator is dynamically linked against X11/GL/audio. If
|
||||
`emulator -list-avds` works but `emulator -avd bevy_test` complains
|
||||
about `libX11.so.6`, install:
|
||||
|
||||
```bash
|
||||
sudo apt install -y \
|
||||
libx11-6 libxcursor1 libxrandr2 libxi6 libxinerama1 libxxf86vm1 \
|
||||
libgl1 libnss3 libpulse0 libxcomposite1
|
||||
```
|
||||
|
||||
Headless emulator launch:
|
||||
|
||||
```bash
|
||||
emulator -avd bevy_test -no-window -gpu swiftshader_indirect &
|
||||
adb wait-for-device && adb devices
|
||||
# Stop later:
|
||||
# adb -s emulator-5554 emu kill
|
||||
```
|
||||
|
||||
Headless + software rendering is fine for "does it boot" smoke tests
|
||||
but useless for perf measurement — use a physical Pixel-class device
|
||||
over USB for real numbers.
|
||||
|
||||
---
|
||||
|
||||
## 2. Build the APK
|
||||
|
||||
```bash
|
||||
cargo apk build -p solitaire_app --target x86_64-linux-android
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
target/debug/apk/solitaire-quest.apk
|
||||
```
|
||||
|
||||
Targets shipped via `[package.metadata.android].build_targets` in
|
||||
`solitaire_app/Cargo.toml`:
|
||||
|
||||
| Target | Use |
|
||||
|--------|-----|
|
||||
| `aarch64-linux-android` | Real phones (modern 64-bit ARM) |
|
||||
| `armv7-linux-androideabi` | Older 32-bit ARM phones |
|
||||
| `x86_64-linux-android` | The `bevy_test` AVD on this dev box |
|
||||
|
||||
Build any of them with `--target <triple>`.
|
||||
|
||||
### Known cosmetic warning
|
||||
|
||||
After the APK is signed cargo-apk panics with:
|
||||
|
||||
```
|
||||
thread 'main' panicked: Bin is not compatible with Cdylib
|
||||
```
|
||||
|
||||
This happens AFTER the APK is on disk and signed. cargo-apk is
|
||||
trying to also wrap the desktop `[[bin]]` target. The APK is still
|
||||
valid. Work around with `--lib`:
|
||||
|
||||
```bash
|
||||
cargo apk build -p solitaire_app --target x86_64-linux-android --lib
|
||||
```
|
||||
|
||||
(Permanent fix to come — likely a `[[bin]] required-features = ["desktop"]`
|
||||
gate so cargo-apk skips the bin target on Android.)
|
||||
|
||||
---
|
||||
|
||||
## 3. Install + run
|
||||
|
||||
Physical device:
|
||||
|
||||
```bash
|
||||
adb devices # confirm connection
|
||||
adb install target/debug/apk/solitaire-quest.apk
|
||||
adb shell am start -n com.solitairequest.app/android.app.NativeActivity
|
||||
adb logcat | grep -iE "RustStdoutStderr|solitaire|panic"
|
||||
```
|
||||
|
||||
Emulator:
|
||||
|
||||
```bash
|
||||
emulator -avd bevy_test -no-window -gpu swiftshader_indirect &
|
||||
adb wait-for-device
|
||||
adb install target/debug/apk/solitaire-quest.apk
|
||||
# ... same start + logcat steps as above.
|
||||
```
|
||||
|
||||
If `adb install` errors with `INSTALL_FAILED_NO_MATCHING_ABIS`, the
|
||||
emulator is x86_64 but the APK was built for arm — rebuild with the
|
||||
`x86_64-linux-android` target, or add an x86_64 system image to the
|
||||
AVD.
|
||||
|
||||
---
|
||||
|
||||
## 4. What's wired vs. what's stubbed
|
||||
|
||||
The first build pass (commit `fb8b2ac`) gates four desktop-only
|
||||
crates / call sites so the workspace cross-compiles. Each gate is
|
||||
documented at its call site.
|
||||
|
||||
| Surface | Desktop | Android |
|
||||
|---------|---------|---------|
|
||||
| Bevy windowing | x11 + wayland | `android-native-activity` (NativeActivity glue) |
|
||||
| Clipboard ("Copy share link") | `arboard` writes URL | Toast surfaces the URL inline |
|
||||
| OS keychain (JWT tokens) | `keyring` v4 → Secret Service / Keychain / Credential Store | Stub returning `KeychainUnavailable`; sync requires fresh login each launch |
|
||||
| App entry point | `bin` target → `solitaire_app::run()` | `cdylib` target loaded by NativeActivity |
|
||||
|
||||
What's NOT yet ported / not yet measured:
|
||||
|
||||
- `dirs::data_dir()` returns `None` on Android. Callers in
|
||||
`solitaire_data/src/storage.rs`, `progress.rs`, `replay.rs`,
|
||||
`achievements.rs`, `settings.rs` all need an Android-aware
|
||||
helper (likely `/data/data/com.solitairequest.app/files`).
|
||||
- Touch UX pass — hit-target sizes, modal scaling on small screens,
|
||||
app lifecycle (suspend / resume), font scaling.
|
||||
- Android Keystore via JNI for `auth_tokens`.
|
||||
- JNI ClipboardManager for share links.
|
||||
- Google Play Games sign-in (the `solitaire_gpgs` crate referenced
|
||||
in older docs doesn't yet exist).
|
||||
|
||||
---
|
||||
|
||||
## 5. Iteration loop
|
||||
|
||||
```bash
|
||||
# Edit code…
|
||||
cargo build -p solitaire_app # desktop sanity
|
||||
cargo clippy --workspace --all-targets -- -D warnings # gate
|
||||
cargo test --workspace # gate
|
||||
cargo apk build -p solitaire_app --target x86_64-linux-android --lib
|
||||
adb install -r target/debug/apk/solitaire-quest.apk # `-r` reinstalls
|
||||
adb logcat -c && adb shell am start -n com.solitairequest.app/android.app.NativeActivity
|
||||
adb logcat | grep -iE "RustStdoutStderr|solitaire"
|
||||
```
|
||||
|
||||
`adb logcat` is the canonical way to see Bevy / Rust panic output —
|
||||
they end up in the `RustStdoutStderr` tag.
|
||||
@@ -0,0 +1,293 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="dark" lang="en"><head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>Rusty Solitaire - Achievements</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700;800&family=Inter:wght@400;500;700&family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
"colors": {
|
||||
"tertiary-fixed": "#fbd7ff",
|
||||
"on-tertiary-container": "#683476",
|
||||
"surface-container-lowest": "#0b0f11",
|
||||
"error": "#fb9fb1",
|
||||
"secondary-fixed-dim": "#bad073",
|
||||
"on-primary-fixed-variant": "#004c69",
|
||||
"background": "#101417",
|
||||
"error-container": "#93000a",
|
||||
"tertiary-container": "#e1a3ee",
|
||||
"inverse-primary": "#00668a",
|
||||
"highlight-valid": "#acc267",
|
||||
"suit-red": "#fb9fb1",
|
||||
"on-surface-variant": "#bfc8cf",
|
||||
"on-secondary": "#293500",
|
||||
"on-primary-container": "#004f6c",
|
||||
"surface-tint": "#7ed0fe",
|
||||
"on-surface": "#e0e3e6",
|
||||
"outline-variant": "#3f484e",
|
||||
"on-background": "#e0e3e6",
|
||||
"primary-fixed": "#c4e7ff",
|
||||
"inverse-surface": "#e0e3e6",
|
||||
"info": "#12cfc0",
|
||||
"inverse-on-surface": "#2d3134",
|
||||
"warning": "#ddb26f",
|
||||
"on-tertiary-fixed-variant": "#653173",
|
||||
"on-secondary-container": "#b2c86d",
|
||||
"on-secondary-fixed-variant": "#3c4d00",
|
||||
"highlight-celebration": "#e1a3ee",
|
||||
"surface": "#151515",
|
||||
"surface-container-highest": "#313538",
|
||||
"outline": "#505050",
|
||||
"on-primary": "#003549",
|
||||
"on-error-container": "#ffdad6",
|
||||
"surface-variant": "#313538",
|
||||
"on-error": "#690005",
|
||||
"suit-black": "#d0d0d0",
|
||||
"primary": "#a1dcff",
|
||||
"suit-red-cb": "#6fc2ef",
|
||||
"surface-bright": "#363a3d",
|
||||
"on-tertiary": "#4c195b",
|
||||
"surface-dim": "#101417",
|
||||
"primary-container": "#6fc2ef",
|
||||
"tertiary": "#f7c3ff",
|
||||
"primary-fixed-dim": "#7ed0fe",
|
||||
"surface-container-high": "#272a2d",
|
||||
"on-secondary-fixed": "#161e00",
|
||||
"surface-container": "#1c2023",
|
||||
"tertiary-fixed-dim": "#f0b0fc",
|
||||
"secondary-fixed": "#d5ec8c",
|
||||
"secondary-container": "#435401",
|
||||
"on-tertiary-fixed": "#340043",
|
||||
"on-primary-fixed": "#001e2c",
|
||||
"secondary": "#bad073",
|
||||
"surface-container-low": "#181c1f"
|
||||
},
|
||||
"borderRadius": {
|
||||
"DEFAULT": "0.125rem",
|
||||
"lg": "0.25rem",
|
||||
"xl": "0.5rem",
|
||||
"full": "0.75rem"
|
||||
},
|
||||
"spacing": {
|
||||
"stack-overlap": "2rem",
|
||||
"gutter-card": "0.375rem",
|
||||
"touch-target-min": "48dp",
|
||||
"margin-edge": "1rem",
|
||||
"action-bar-height": "64px"
|
||||
},
|
||||
"fontFamily": {
|
||||
"card-rank": ["JetBrains Mono"],
|
||||
"body-md": ["Inter"],
|
||||
"label-caps": ["JetBrains Mono"],
|
||||
"hud-score": ["JetBrains Mono"],
|
||||
"headline": ["JetBrains Mono"],
|
||||
"hud-timer": ["JetBrains Mono"]
|
||||
},
|
||||
"fontSize": {
|
||||
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
|
||||
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
|
||||
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}],
|
||||
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
|
||||
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}]
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
}
|
||||
body {
|
||||
background-color: #151515;
|
||||
color: #e0e3e6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
.scanline {
|
||||
background: linear-gradient(to bottom, transparent 50%, rgba(0,0,0,0.1) 50%);
|
||||
background-size: 100% 2px;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
body {
|
||||
min-height: max(884px, 100dvh);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="font-body-md text-body-md overflow-x-hidden pb-[action-bar-height]">
|
||||
<!-- Status Bar -->
|
||||
<header class="fixed top-0 w-full h-[32px] bg-[#202020] flex items-center justify-between px-margin-edge z-[60] border-b border-outline-variant">
|
||||
<div class="flex items-center gap-2 font-label-caps text-on-surface">
|
||||
<span class="text-primary">▌</span>achievements.json
|
||||
</div>
|
||||
<div class="font-label-caps text-[#a0a0a0]">
|
||||
8/19 UNLOCKED
|
||||
</div>
|
||||
</header>
|
||||
<!-- Top App Bar (Shared Component Reference) -->
|
||||
<nav class="fixed top-[32px] w-full h-[64px] bg-surface flex items-center justify-between px-margin-edge z-50 border-b border-outline-variant">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="material-symbols-outlined text-primary" data-icon="terminal">terminal</span>
|
||||
<h1 class="font-headline text-[20px] text-primary uppercase tracking-widest">Rusty Solitaire</h1>
|
||||
</div>
|
||||
<button class="w-10 h-10 flex items-center justify-center hover:bg-surface-container-highest transition-colors">
|
||||
<span class="material-symbols-outlined text-on-surface-variant" data-icon="settings">settings</span>
|
||||
</button>
|
||||
</nav>
|
||||
<main class="mt-[112px] px-margin-edge">
|
||||
<!-- Hero Progress Card -->
|
||||
<section class="w-full h-[100px] bg-[#202020] border border-[#353535] rounded-lg p-4 mb-6">
|
||||
<div class="flex flex-col justify-between h-full">
|
||||
<span class="font-label-caps text-[10px] text-[#a0a0a0]">PROGRESS</span>
|
||||
<div class="flex items-baseline gap-2">
|
||||
<span class="font-headline text-[28px] font-bold text-[#d0d0d0]">8/19</span>
|
||||
<span class="font-label-caps text-[14px] text-highlight-celebration">(42%)</span>
|
||||
</div>
|
||||
<div class="w-full h-[4px] bg-[#353535] rounded-full overflow-hidden mt-1">
|
||||
<div class="h-full bg-highlight-celebration" style="width: 42%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Filter Chip Row -->
|
||||
<section class="flex gap-2 mb-6 overflow-x-auto no-scrollbar">
|
||||
<button class="h-[32px] px-3 flex items-center justify-center border border-[#6fc2ef] text-[#6fc2ef] rounded-[4px] font-label-caps text-[11px]">
|
||||
[ ALL ]
|
||||
</button>
|
||||
<button class="h-[32px] px-3 flex items-center justify-center border border-outline text-[#a0a0a0] rounded-[4px] font-label-caps text-[11px] hover:border-primary hover:text-primary transition-colors">
|
||||
UNLOCKED
|
||||
</button>
|
||||
<button class="h-[32px] px-3 flex items-center justify-center border border-outline text-[#a0a0a0] rounded-[4px] font-label-caps text-[11px] hover:border-primary hover:text-primary transition-colors">
|
||||
LOCKED
|
||||
</button>
|
||||
<button class="h-[32px] px-3 flex items-center justify-center border border-outline text-[#a0a0a0] rounded-[4px] font-label-caps text-[11px] hover:border-primary hover:text-primary transition-colors">
|
||||
SECRET
|
||||
</button>
|
||||
</section>
|
||||
<!-- Achievements Grid -->
|
||||
<section class="grid grid-cols-2 gap-3 mb-10">
|
||||
<!-- FIRST WIN -->
|
||||
<div class="h-[100px] bg-[#202020] border border-[#353535] p-3 flex flex-col justify-between rounded-sm">
|
||||
<div class="w-8 h-8 rounded-full bg-[#6fc2ef] flex items-center justify-center">
|
||||
<span class="material-symbols-outlined text-[#151515] text-[20px]" data-icon="emoji_events" style="font-variation-settings: 'FILL' 1;">emoji_events</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-label-caps text-[13px] font-bold text-[#d0d0d0] leading-none mb-1">FIRST WIN</h3>
|
||||
<p class="font-label-caps text-[10px] text-[#a0a0a0] leading-tight">Win your first game</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- SPEED DEMON -->
|
||||
<div class="h-[100px] bg-[#202020] border border-highlight-celebration p-3 flex flex-col justify-between rounded-sm relative">
|
||||
<div class="absolute inset-0 border border-highlight-celebration opacity-20 pointer-events-none"></div>
|
||||
<div class="w-8 h-8 rounded-full bg-[#6fc2ef] flex items-center justify-center">
|
||||
<span class="material-symbols-outlined text-[#151515] text-[20px]" data-icon="speed" style="font-variation-settings: 'FILL' 1;">speed</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-label-caps text-[13px] font-bold text-[#d0d0d0] leading-none mb-1">SPEED DEMON</h3>
|
||||
<p class="font-label-caps text-[10px] text-[#a0a0a0] leading-tight">Win in under 3:00</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- STREAK 10 -->
|
||||
<div class="h-[100px] bg-[#202020] border border-[#353535] p-3 flex flex-col justify-between rounded-sm">
|
||||
<div class="w-8 h-8 rounded-full bg-[#6fc2ef] flex items-center justify-center">
|
||||
<span class="material-symbols-outlined text-[#151515] text-[20px]" data-icon="bolt" style="font-variation-settings: 'FILL' 1;">bolt</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-label-caps text-[13px] font-bold text-[#d0d0d0] leading-none mb-1">STREAK 10</h3>
|
||||
<p class="font-label-caps text-[10px] text-[#a0a0a0] leading-tight">10 wins in a row</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- DAILY DEFENDER -->
|
||||
<div class="h-[100px] bg-[#202020] border border-[#353535] p-3 flex flex-col justify-between rounded-sm">
|
||||
<div class="w-8 h-8 rounded-full bg-[#6fc2ef] flex items-center justify-center">
|
||||
<span class="material-symbols-outlined text-[#151515] text-[20px]" data-icon="calendar_today" style="font-variation-settings: 'FILL' 1;">calendar_today</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-label-caps text-[13px] font-bold text-[#d0d0d0] leading-none mb-1">DAILY DEFENDER</h3>
|
||||
<p class="font-label-caps text-[10px] text-[#a0a0a0] leading-tight">Complete 7 daily seeds</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- PERFECTIONIST (LOCKED) -->
|
||||
<div class="h-[100px] bg-[#0d0d0d] border border-[#353535] p-3 flex flex-col justify-between rounded-sm opacity-80">
|
||||
<div class="w-8 h-8 rounded-full border border-[#505050] flex items-center justify-center">
|
||||
<span class="material-symbols-outlined text-[#505050] text-[20px]" data-icon="undo">undo</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-label-caps text-[13px] font-bold text-[#505050] leading-none mb-1">PERFECTIONIST</h3>
|
||||
<p class="font-label-caps text-[10px] text-[#353535] leading-tight">Win without using undo</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- CHALLENGE BEATEN (LOCKED) -->
|
||||
<div class="h-[100px] bg-[#0d0d0d] border border-[#353535] p-3 flex flex-col justify-between rounded-sm opacity-80">
|
||||
<div class="w-8 h-8 rounded-full border border-[#505050] flex items-center justify-center">
|
||||
<span class="material-symbols-outlined text-[#505050] text-[20px]" data-icon="military_tech">military_tech</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-label-caps text-[13px] font-bold text-[#505050] leading-none mb-1">CHALLENGE BEATEN</h3>
|
||||
<p class="font-label-caps text-[10px] text-[#353535] leading-tight">Complete CHALLENGE mode</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- SECRET (LOCKED) -->
|
||||
<div class="h-[100px] bg-[#0d0d0d] border border-[#353535] p-3 flex flex-col justify-between rounded-sm opacity-80">
|
||||
<div class="w-8 h-8 rounded-full border border-[#505050] flex items-center justify-center">
|
||||
<span class="material-symbols-outlined text-[#505050] text-[20px]" data-icon="help_outline">help_outline</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-label-caps text-[13px] font-bold text-[#505050] leading-none mb-1">????</h3>
|
||||
<p class="font-label-caps text-[10px] text-[#353535] leading-tight">SECRET · Hidden until unlocked</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- PAR HUNTER (LOCKED) -->
|
||||
<div class="h-[100px] bg-[#0d0d0d] border border-[#353535] p-3 flex flex-col justify-between rounded-sm opacity-80">
|
||||
<div class="w-8 h-8 rounded-full border border-[#505050] flex items-center justify-center">
|
||||
<span class="material-symbols-outlined text-[#505050] text-[20px]" data-icon="golf_course">golf_course</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-label-caps text-[13px] font-bold text-[#505050] leading-none mb-1">PAR HUNTER</h3>
|
||||
<p class="font-label-caps text-[10px] text-[#353535] leading-tight">Beat par on 50 games</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<!-- Footer Status -->
|
||||
<footer class="fixed bottom-[action-bar-height] w-full h-[24px] bg-background border-t border-outline-variant flex items-center justify-between px-margin-edge z-40 text-[10px] font-label-caps">
|
||||
<div class="flex items-center">
|
||||
<span class="text-primary mr-1">▌</span>
|
||||
<span class="text-on-surface-variant">NORMAL</span>
|
||||
<span class="mx-2 text-outline">│</span>
|
||||
<span class="text-on-surface-variant">achievements</span>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<div><span class="text-[#a0a0a0]">[F]</span> <span class="text-[#505050]">filter</span></div>
|
||||
<div><span class="text-[#a0a0a0]">[/]</span> <span class="text-[#505050]">search</span></div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Bottom Navigation Bar (Shared Component Reference) -->
|
||||
<nav class="fixed bottom-0 w-full h-action-bar-height bg-surface-container flex justify-around items-center px-margin-edge z-50 border-t border-outline-variant">
|
||||
<button class="flex flex-col items-center justify-center text-on-surface-variant p-2 hover:text-primary hover:bg-surface-container-highest transition-colors active:scale-95">
|
||||
<span class="material-symbols-outlined" data-icon="power_settings_new">power_settings_new</span>
|
||||
<span class="font-label-caps text-[10px] mt-1">[Q] QUIT</span>
|
||||
</button>
|
||||
<button class="flex flex-col items-center justify-center text-on-surface-variant p-2 hover:text-primary hover:bg-surface-container-highest transition-colors active:scale-95">
|
||||
<span class="material-symbols-outlined" data-icon="add_box">add_box</span>
|
||||
<span class="font-label-caps text-[10px] mt-1">[N] NEW</span>
|
||||
</button>
|
||||
<button class="flex flex-col items-center justify-center text-on-surface-variant p-2 hover:text-primary hover:bg-surface-container-highest transition-colors active:scale-95">
|
||||
<span class="material-symbols-outlined" data-icon="undo">undo</span>
|
||||
<span class="font-label-caps text-[10px] mt-1">[U] UNDO</span>
|
||||
</button>
|
||||
<button class="flex flex-col items-center justify-center text-on-surface-variant p-2 hover:text-primary hover:bg-surface-container-highest transition-colors active:scale-95">
|
||||
<span class="material-symbols-outlined" data-icon="lightbulb">lightbulb</span>
|
||||
<span class="font-label-caps text-[10px] mt-1">[H] HINT</span>
|
||||
</button>
|
||||
</nav>
|
||||
<!-- CRT Overlay Effect (Visual Decoration) -->
|
||||
<div class="fixed inset-0 pointer-events-none z-[100] opacity-[0.03] scanline"></div>
|
||||
</body></html>
|
||||
|
After Width: | Height: | Size: 41 KiB |
@@ -0,0 +1,251 @@
|
||||
# Card-face artwork migration plan
|
||||
|
||||
**Status:** planning artifact (no code changed by this document).
|
||||
**Tracks:** the "Card-face / suit / card-back artwork regeneration"
|
||||
item in `SESSION_HANDOFF.md` → "Visual-identity follow-ups"
|
||||
(SESSION_HANDOFF Resume prompt option D).
|
||||
**Companion to:** `docs/ui-mockups/design-system.md` (Game Cards
|
||||
spec, lines 214–233) and `docs/ui-mockups/desktop-adaptation.md`
|
||||
(rules-based companion to the mockups).
|
||||
|
||||
## Why this is a multi-session arc
|
||||
|
||||
Every post-v0.20.0 visual-identity port to date (modal scaffold,
|
||||
toasts, table chrome, splash boot screen, replay overlay) was a
|
||||
**single rendering path** — change tokens, change comments, ship.
|
||||
Cards have **two** rendering paths that are visually identical
|
||||
today and would visually disagree the moment one moves:
|
||||
|
||||
1. **PNG path (production).** `assets/cards/faces/<rank><suit>.png`
|
||||
loaded into `CardImageSet.faces[suit][rank]` at startup; card
|
||||
sprites blit the texture. 52 face PNGs + 5 back PNGs already
|
||||
in `assets/`, all the legacy white-card aesthetic from the
|
||||
pre-Terminal design system.
|
||||
2. **Constant fallback (tests + asset-missing edge).** When
|
||||
`CardImageSet` isn't a registered resource (the case under
|
||||
`MinimalPlugins` test fixtures, and the bare-bones path the
|
||||
first-frame of production hits before assets resolve), the
|
||||
renderer falls back to solid-colour sprites driven by the
|
||||
`card_plugin` constants:
|
||||
- `CARD_FACE_COLOUR` — `(0.98, 0.98, 0.95)` cream-ish white.
|
||||
- `RED_SUIT_COLOUR` — `(0.78, 0.12, 0.15)` warm red.
|
||||
- `BLACK_SUIT_COLOUR` — `(0.08, 0.08, 0.08)` near-black.
|
||||
- `CARD_FACE_COLOUR_RED_CBM` — `(0.85, 0.92, 1.0, 1.0)` light
|
||||
blue (the legacy color-blind tint).
|
||||
- `card_back_colour(idx)` — five legacy back themes.
|
||||
|
||||
A single-path migration leaves a known-broken state where tests
|
||||
pass against Terminal constants while a human sees legacy artwork
|
||||
on screen — the exact bisection-hostile drift the handoff's
|
||||
"in lockstep" warning preempts.
|
||||
|
||||
## Target state — Terminal aesthetic
|
||||
|
||||
Per `design-system.md` § Game Cards (lines 214–233):
|
||||
|
||||
### Card face
|
||||
|
||||
| Element | Spec |
|
||||
|---|---|
|
||||
| Background | `#1a1a1a` |
|
||||
| Border | 1 px solid in **suit colour** (pink for ♥/♦, foreground gray for ♠/♣) |
|
||||
| Corner radius | 8 px |
|
||||
| Top-left | rank in JetBrains Mono **Bold 18 px** + small suit glyph (10 px) |
|
||||
| Bottom-right | large suit glyph (32 px), rotated 180° |
|
||||
| Glyph fill rule | ♥ ♠ filled; ♦ ♣ outlined (1.5 px stroke). Always on, not a toggle. |
|
||||
|
||||
### Suit colours (always-on glyph differentiation is the *primary*
|
||||
distinguishing mechanism; colour is supplementary):
|
||||
|
||||
| Suit | Default | Color-blind mode |
|
||||
|---|---|---|
|
||||
| Hearts | `#fb9fb1` (pink) | `#6fc2ef` (cyan) |
|
||||
| Diamonds | `#fb9fb1` (pink) | `#6fc2ef` (cyan) |
|
||||
| Spades | `#d0d0d0` (gray) | `#d0d0d0` (unchanged) |
|
||||
| Clubs | `#d0d0d0` (gray) | `#d0d0d0` (unchanged) |
|
||||
|
||||
### Card back ("Terminal" theme)
|
||||
|
||||
| Element | Spec |
|
||||
|---|---|
|
||||
| Background | `#151515` |
|
||||
| Pattern | horizontal scanlines at 2 px pitch in `#1a1a1a` (1 px line, 1 px gap), full bleed |
|
||||
| Border | 1 px solid `#353535` |
|
||||
| Top-left badge | 12×16 px solid `#6fc2ef` block, 6 px from corner |
|
||||
| Bottom-right monogram | `▌RS` in JetBrains Mono 12 px `#505050`, 6 px from corner |
|
||||
| Corner radius | 8 px |
|
||||
| Theme name / author | `"Terminal"` / `"Rusty Solitaire"` |
|
||||
|
||||
## Generation pipeline — programmatic SVG via the existing
|
||||
`resvg` stack
|
||||
|
||||
### Why this path (vs. external tooling or direct `tiny_skia`)
|
||||
|
||||
The codebase already ships an SVG-to-PNG rasteriser at
|
||||
`solitaire_engine/src/assets/svg_loader.rs`:
|
||||
|
||||
- Public `rasterize_svg(svg_bytes: &[u8], target: UVec2) -> Result<Image, _>`
|
||||
- Backed by `usvg` (parser) + `resvg` (renderer) + `tiny_skia`
|
||||
(CPU pixmap)
|
||||
- Bundled font db includes JetBrains-style mono (FiraMono — same
|
||||
face the splash uses; close enough to JetBrains Mono for
|
||||
rasterisation purposes, and identical to what the Bevy UI
|
||||
consumes in the rest of the app)
|
||||
- `RenderAssetUsages::default()` is the call-site convention here
|
||||
|
||||
This means: **generating new card PNGs is one new file
|
||||
(`solitaire_engine/examples/card_face_generator.rs`) calling an
|
||||
existing public function.** No new dependencies, no asset-pipeline
|
||||
changes, no build-script machinery. Anyone who runs the example
|
||||
gets bit-identical artwork.
|
||||
|
||||
The two alternatives are weaker:
|
||||
|
||||
- **External tool (Inkscape / Figma / hand-design)** — produces
|
||||
one-off PNGs that can't be re-generated reproducibly without
|
||||
re-opening the source files in a specific tool. Iteration cost
|
||||
is high; design tweaks (e.g. "make the suit glyph 2 px larger")
|
||||
require a designer-in-the-loop.
|
||||
- **Direct `tiny_skia` painting calls** — bypasses SVG entirely,
|
||||
but loses the readability of "open the SVG to see exactly what
|
||||
the card looks like." Also reinvents primitives (rounded
|
||||
rectangles, text layout) that `usvg` already handles.
|
||||
|
||||
### Output format
|
||||
|
||||
PNG, RGBA8 sRGB, **dimensions 256 × 384** (2:3 aspect, half the
|
||||
default `SvgLoaderSettings` of 512 × 768).
|
||||
|
||||
Rationale: cards never exceed ~250 px wide on desktop windows
|
||||
today, and 256 × 384 PNGs are ~6 KB each at this content density
|
||||
(13.4 KB total for a full deck of 52 + 5 backs). The default 512 ×
|
||||
768 is 2× what's needed and quadruples the on-disk asset weight.
|
||||
The existing legacy PNGs are 512 × 768 — reducing the new ones
|
||||
halves the runtime asset size.
|
||||
|
||||
## Lockstep migration — recommended order
|
||||
|
||||
Each step is a separate commit; the constraint is that **steps 4
|
||||
and 5 must land in the same commit** (or at most adjacent commits
|
||||
on the same branch) so the rendered output never diverges between
|
||||
the two paths.
|
||||
|
||||
1. **(Done — this commit)** Land the migration plan doc.
|
||||
2. **Land the SVG generator example.** New
|
||||
`solitaire_engine/examples/card_face_generator.rs`. Output
|
||||
goes to `assets/cards/faces/` and `assets/cards/backs/`. Run
|
||||
once locally to seed the new artwork. The example file stays
|
||||
in-tree as a regenerator for future tweaks.
|
||||
3. **(Optional — can land separately)** Add a one-shot regression
|
||||
test that re-runs the generator into a `tempdir` and compares
|
||||
the resulting bytes against the on-disk artwork; pinning the
|
||||
generator output prevents silent drift if `usvg`/`resvg` ever
|
||||
tweak rendering. Skip if the test runtime cost is unacceptable.
|
||||
4. **Land the new artwork** (PNG bytes from step 2 committed to
|
||||
`assets/cards/`) **and** the constant migration in the *same
|
||||
commit*:
|
||||
- `CARD_FACE_COLOUR` → `Color::srgb(0.102, 0.102, 0.102)` (`#1a1a1a`)
|
||||
- `RED_SUIT_COLOUR` → `Color::srgb(0.984, 0.624, 0.694)` (`#fb9fb1`)
|
||||
- `BLACK_SUIT_COLOUR` → `Color::srgb(0.816, 0.816, 0.816)` (`#d0d0d0`)
|
||||
- `CARD_FACE_COLOUR_RED_CBM` → `Color::srgb(0.435, 0.761, 0.937)` (`#6fc2ef`) — note this is now the colour-blind *suit* colour, not a face tint; semantics shift slightly.
|
||||
- `card_back_colour(idx)` — re-author for the Terminal palette;
|
||||
index 0 stays the canonical "Terminal" back from `design-system.md`.
|
||||
5. **Test updates land in step 4's commit.** The pinning tests at
|
||||
`card_plugin.rs` lines 1749, 1750, 1767, 1768, 2057, 2063,
|
||||
2071, 2081 all assert against the old constants. New
|
||||
assertions update in lockstep with the constant changes.
|
||||
|
||||
## CBM (color-blind mode) semantics shift — flag
|
||||
|
||||
The **legacy** `CARD_FACE_COLOUR_RED_CBM` was a *face tint* — red
|
||||
suits got a light-blue background wash. The **Terminal** spec
|
||||
moves CBM into the *suit colour* itself (red glyphs swap to cyan).
|
||||
Step 4 will rename / repurpose this constant; it's not a 1:1
|
||||
replacement.
|
||||
|
||||
Two options:
|
||||
|
||||
- **Rename + repurpose:** `CARD_FACE_COLOUR_RED_CBM` →
|
||||
`RED_SUIT_COLOUR_CBM`. Communicates the semantic shift in the
|
||||
symbol name. Requires touching every callsite.
|
||||
- **Keep the name, change the meaning:** less code churn but
|
||||
worse for greppability — a future reader hitting the legacy
|
||||
name will assume face-tint behaviour.
|
||||
|
||||
Recommendation: **rename**. The CBM swap is a one-frame operation
|
||||
even if it touches every existing callsite (currently lines 642,
|
||||
2071, 2081 per `grep -n CARD_FACE_COLOUR_RED_CBM`).
|
||||
|
||||
## Theme system — out of scope here
|
||||
|
||||
The card-theme system (`docs/CARD_PLAN.md`, `theme/plugin.rs`)
|
||||
already supports user-supplied themes via `assets/themes/<theme>/`
|
||||
SVG files rasterised by `svg_loader.rs`. The new Terminal artwork
|
||||
is the **default theme**, not a new entry in the theme picker —
|
||||
the theme system continues to overlay user themes on top of the
|
||||
default at runtime.
|
||||
|
||||
If the next session wants to also ship Terminal as a *named theme
|
||||
slot* (so a user can switch back to the legacy artwork via the
|
||||
theme picker), that's an additive change after step 4 and lives
|
||||
in `theme::plugin::apply_theme_to_card_image_set`.
|
||||
|
||||
## Test impact summary
|
||||
|
||||
`grep -n CARD_FACE_COLOUR\\b\|RED_SUIT_COLOUR\\b\|BLACK_SUIT_COLOUR\\b` in
|
||||
`card_plugin.rs`:
|
||||
|
||||
- Line 1749–1750: red-suit text colour assertions (♥ + ♦).
|
||||
- Line 1767–1768: black-suit text colour assertions (♠ + ♣).
|
||||
- Line 2057, 2063: face-colour assertion in default mode.
|
||||
- Line 2071, 2081: face-colour assertion in CBM.
|
||||
|
||||
The four suit-colour and two face-colour tests are **invariant
|
||||
guards** — they exist precisely so a constant tweak surfaces here
|
||||
rather than in a visual review. Step 4 updates each in lockstep
|
||||
with the constant value change. No new test infrastructure
|
||||
needed.
|
||||
|
||||
## Open questions to resolve before step 4
|
||||
|
||||
1. **Border colour conflict.** The spec (line 218) says "Border:
|
||||
1 px solid in suit colour." The fallback path doesn't draw a
|
||||
border today — it draws solid-colour sprites. Step 4 either:
|
||||
(a) leaves the fallback as solid-colour squares (the test
|
||||
environment doesn't visually validate borders anyway), or
|
||||
(b) extends the fallback renderer to paint a 1 px outline.
|
||||
Recommend (a) — fallback fidelity isn't load-bearing.
|
||||
2. **Glyph rendering in the constant fallback.** The fallback
|
||||
today doesn't render suit glyphs at all — it's a coloured
|
||||
square. The spec's filled-vs-outlined glyph differentiation
|
||||
only matters in the PNG path. No change to the constant
|
||||
fallback for glyphs.
|
||||
3. **High-contrast mode.** `design-system.md` line 274 mentions
|
||||
a high-contrast accessibility mode (boosts foreground from
|
||||
`#d0d0d0` to `#f5f5f5`, suit-red from `#fb9fb1` to `#ff8aa0`).
|
||||
Not currently implemented anywhere; out of scope for this
|
||||
migration but worth flagging for a future accessibility pass.
|
||||
|
||||
## Post-migration — what's still open
|
||||
|
||||
- **High-contrast mode** (above).
|
||||
- **Reduced-motion mode** for card lift / drop transitions
|
||||
(also a `design-system.md` accessibility item, separate from
|
||||
artwork).
|
||||
- **The 9 missing-plugin screens** (splash, challenge,
|
||||
time-attack, weekly-goals, leaderboard, sync, level-up,
|
||||
replay, radial-menu) per `project_ui_overhaul` memory still
|
||||
need their plugin ports — separate from the cards arc.
|
||||
|
||||
## Sign-off criteria for "D closed"
|
||||
|
||||
D from the SESSION_HANDOFF Resume prompt is closed when **all of
|
||||
the following hold simultaneously**:
|
||||
|
||||
- The 52 face PNGs + 5 back PNGs in `assets/cards/` are the
|
||||
Terminal-aesthetic artwork (regeneratable via the example).
|
||||
- The five `card_plugin` constants reflect the Terminal palette.
|
||||
- All pinning tests pass against the new values.
|
||||
- A human boots the game and sees Terminal cards (not white
|
||||
cards). This sign-off needs a real `cargo run`, not just
|
||||
`cargo test`.
|
||||
@@ -0,0 +1,219 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="dark" lang="en"><head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=390, height=844, initial-scale=1.0" name="viewport"/>
|
||||
<title>Challenge Mode Menu</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@400;500;700&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
"colors": {
|
||||
"inverse-surface": "#e0e3e6",
|
||||
"error-container": "#93000a",
|
||||
"tertiary": "#f7c3ff",
|
||||
"on-primary-container": "#004f6c",
|
||||
"on-surface": "#e0e3e6",
|
||||
"surface-dim": "#101417",
|
||||
"surface-container-high": "#272a2d",
|
||||
"surface-container-lowest": "#0b0f11",
|
||||
"secondary-container": "#435401",
|
||||
"suit-red": "#fb9fb1",
|
||||
"on-error": "#690005",
|
||||
"surface-container-low": "#181c1f",
|
||||
"surface-variant": "#313538",
|
||||
"surface-tint": "#7ed0fe",
|
||||
"primary-container": "#6fc2ef",
|
||||
"background": "#101417",
|
||||
"primary": "#a1dcff",
|
||||
"outline": "#505050",
|
||||
"suit-black": "#d0d0d0",
|
||||
"secondary-fixed": "#d5ec8c",
|
||||
"surface-container": "#202020",
|
||||
"on-tertiary-fixed": "#340043",
|
||||
"on-tertiary-fixed-variant": "#653173",
|
||||
"outline-variant": "#3f484e",
|
||||
"on-surface-variant": "#bfc8cf",
|
||||
"error": "#fb9fb1",
|
||||
"on-primary-fixed": "#001e2c",
|
||||
"highlight-celebration": "#e1a3ee",
|
||||
"highlight-valid": "#acc267",
|
||||
"suit-red-cb": "#6fc2ef",
|
||||
"primary-fixed-dim": "#7ed0fe",
|
||||
"tertiary-fixed-dim": "#f0b0fc",
|
||||
"primary-fixed": "#c4e7ff",
|
||||
"on-error-container": "#ffdad6",
|
||||
"tertiary-container": "#e1a3ee",
|
||||
"on-secondary": "#293500",
|
||||
"on-tertiary": "#4c195b",
|
||||
"on-background": "#e0e3e6",
|
||||
"secondary-fixed-dim": "#bad073",
|
||||
"secondary": "#bad073",
|
||||
"inverse-primary": "#00668a",
|
||||
"surface-bright": "#363a3d",
|
||||
"surface": "#151515",
|
||||
"on-tertiary-container": "#683476",
|
||||
"on-secondary-fixed": "#161e00",
|
||||
"inverse-on-surface": "#2d3134",
|
||||
"warning": "#ddb26f",
|
||||
"info": "#12cfc0",
|
||||
"surface-container-highest": "#313538",
|
||||
"on-primary-fixed-variant": "#004c69",
|
||||
"tertiary-fixed": "#fbd7ff",
|
||||
"on-secondary-fixed-variant": "#3c4d00",
|
||||
"on-secondary-container": "#b2c86d",
|
||||
"on-primary": "#003549"
|
||||
},
|
||||
"fontFamily": {
|
||||
"mono": ["JetBrains Mono", "monospace"],
|
||||
"label-caps": ["JetBrains Mono"],
|
||||
"hud-score": ["JetBrains Mono"],
|
||||
"body-md": ["Inter"],
|
||||
"card-rank": ["JetBrains Mono"],
|
||||
"hud-timer": ["JetBrains Mono"],
|
||||
"headline": ["JetBrains Mono"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
}
|
||||
body {
|
||||
background-color: #101417;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
.retro-scanline {
|
||||
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.1) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.03), rgba(0, 255, 0, 0.01), rgba(0, 0, 255, 0.03));
|
||||
background-size: 100% 2px, 3px 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
body {
|
||||
min-height: max(884px, 100dvh);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex items-center justify-center min-h-screen text-on-background overflow-hidden">
|
||||
<!-- Mobile Container (390x844) -->
|
||||
<div class="relative w-[390px] h-[844px] bg-background flex flex-col overflow-hidden border border-outline-variant">
|
||||
<!-- Status Bar -->
|
||||
<div class="h-[32px] bg-surface-container flex items-center justify-between px-4 text-[11px] font-mono border-b border-outline-variant shrink-0">
|
||||
<span class="text-suit-black">▌challenge.tsx</span>
|
||||
<span class="text-[#a0a0a0]">LV 12 · UNLOCKED</span>
|
||||
</div>
|
||||
<!-- Header -->
|
||||
<header class="h-[80px] px-margin-edge flex flex-col justify-center border-b border-outline-variant shrink-0">
|
||||
<h1 class="text-[24px] font-bold leading-tight text-suit-black">CHALLENGE MODE</h1>
|
||||
<p class="text-[12px] text-[#a0a0a0] mt-1">Curated puzzles · Beat par for bonus XP</p>
|
||||
</header>
|
||||
<!-- Stats Row -->
|
||||
<div class="mx-margin-edge mt-4 bg-surface-container rounded-[4px] p-3 flex items-center justify-between border border-outline-variant shrink-0">
|
||||
<div class="flex items-baseline gap-1">
|
||||
<span class="text-[14px] font-bold text-suit-black">DONE 8/24</span>
|
||||
<span class="text-[14px] font-bold text-highlight-celebration">(33%)</span>
|
||||
</div>
|
||||
<span class="text-outline-variant text-[14px]">│</span>
|
||||
<div class="text-[14px] font-bold text-suit-black">BEST AVG 03:42</div>
|
||||
<span class="text-outline-variant text-[14px]">│</span>
|
||||
<div class="text-[14px] font-bold text-highlight-valid">+1240 XP</div>
|
||||
</div>
|
||||
<!-- Scrollable List Area -->
|
||||
<div class="flex-1 overflow-y-auto px-margin-edge pt-4 space-y-3 pb-6">
|
||||
<!-- Card 1 -->
|
||||
<div class="h-[80px] bg-surface-container border border-outline rounded-[4px] flex relative overflow-hidden">
|
||||
<div class="w-[6px] h-full bg-warning"></div>
|
||||
<div class="flex-1 flex items-center justify-between px-4">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-[14px] font-bold text-suit-black">DEEP STACK</span>
|
||||
<span class="text-[12px] text-on-surface-variant">Win with 0 stock · ★★★☆☆</span>
|
||||
</div>
|
||||
<div class="bg-highlight-valid px-2 py-1 rounded-[2px] text-background text-[11px] font-bold">
|
||||
✓ DONE
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Card 2 -->
|
||||
<div class="h-[80px] bg-surface-container border border-primary rounded-[4px] flex relative overflow-hidden">
|
||||
<div class="w-[6px] h-full bg-highlight-valid"></div>
|
||||
<div class="flex-1 flex items-center justify-between px-4">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-[14px] font-bold text-suit-black">SPEED RUN</span>
|
||||
<span class="text-[12px] text-on-surface-variant">Win under 2:30 · ★★☆☆☆</span>
|
||||
</div>
|
||||
<div class="bg-primary px-2 py-1 rounded-[2px] text-background text-[11px] font-bold">
|
||||
▶ ACTIVE
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Card 3 -->
|
||||
<div class="h-[80px] bg-surface-container border border-outline rounded-[4px] flex relative overflow-hidden">
|
||||
<div class="w-[6px] h-full bg-suit-red"></div>
|
||||
<div class="flex-1 flex items-center justify-between px-4">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-[14px] font-bold text-suit-black">NO UNDO</span>
|
||||
<span class="text-[12px] text-on-surface-variant">Win without undo · ★★★★☆</span>
|
||||
</div>
|
||||
<div class="bg-primary px-2 py-1 rounded-[2px] text-background text-[11px] font-bold">
|
||||
▶ ACTIVE
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Card 4 -->
|
||||
<div class="h-[80px] bg-surface-container border border-outline rounded-[4px] flex relative overflow-hidden">
|
||||
<div class="w-[6px] h-full bg-info"></div>
|
||||
<div class="flex-1 flex items-center justify-between px-4">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-[14px] font-bold text-suit-black">FOUR SUITS</span>
|
||||
<span class="text-[12px] text-on-surface-variant">1 card per suit · ★☆☆☆☆</span>
|
||||
</div>
|
||||
<div class="bg-highlight-valid px-2 py-1 rounded-[2px] text-background text-[11px] font-bold">
|
||||
✓ DONE
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Card 5 (Locked) -->
|
||||
<div class="h-[80px] bg-surface-container border border-outline-variant rounded-[4px] flex relative overflow-hidden opacity-60">
|
||||
<div class="w-[6px] h-full bg-highlight-celebration"></div>
|
||||
<div class="flex-1 flex items-center justify-between px-4">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-[14px] font-bold text-suit-black">PERFECT RUN</span>
|
||||
<span class="text-[12px] text-on-surface-variant">Below par moves · ★★★★★</span>
|
||||
</div>
|
||||
<div class="bg-outline px-2 py-1 rounded-[2px] text-on-surface text-[11px] font-bold">
|
||||
🔒 LOCKED
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Filler Graphic for retro feel -->
|
||||
<div class="flex items-center justify-center py-4">
|
||||
<div class="h-[1px] flex-1 bg-outline-variant"></div>
|
||||
<span class="px-4 text-[10px] text-outline text-label-caps">END OF LIST</span>
|
||||
<div class="h-[1px] flex-1 bg-outline-variant"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Shared Component: Terminal Context (Used as Footer) -->
|
||||
<div class="h-[24px] bg-surface px-4 flex items-center justify-between text-[10px] font-mono border-t border-outline-variant shrink-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-primary">▌ NORMAL</span>
|
||||
<span class="text-outline">│</span>
|
||||
<span class="text-on-surface-variant uppercase tracking-widest">challenge</span>
|
||||
</div>
|
||||
<div class="text-[#a0a0a0] flex items-center gap-3">
|
||||
<span>[ENTER] select</span>
|
||||
<span>[F] filter</span>
|
||||
<span class="text-suit-red">[ESC] back</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Retro Scanline Overlay -->
|
||||
<div class="absolute inset-0 retro-scanline z-50"></div>
|
||||
</div>
|
||||
</body></html>
|
||||
|
After Width: | Height: | Size: 67 KiB |
@@ -0,0 +1,258 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="dark" lang="en"><head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=390, height=844, initial-scale=1.0" name="viewport"/>
|
||||
<title>Rusty Solitaire - Daily Challenge</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700;800&family=Inter:wght@400;500;600&family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
}
|
||||
body {
|
||||
background-color: #101417;
|
||||
color: #e0e3e6;
|
||||
font-family: 'Inter', sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
.scanline-bg {
|
||||
background: linear-gradient(to bottom, transparent 50%, rgba(26, 26, 26, 0.5) 50%);
|
||||
background-size: 100% 4px;
|
||||
}
|
||||
</style>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
"colors": {
|
||||
"on-secondary-fixed": "#161e00",
|
||||
"on-error": "#690005",
|
||||
"on-primary-fixed": "#001e2c",
|
||||
"tertiary": "#f7c3ff",
|
||||
"secondary-fixed-dim": "#bad073",
|
||||
"primary-container": "#6fc2ef",
|
||||
"surface-dim": "#101417",
|
||||
"surface-variant": "#313538",
|
||||
"on-error-container": "#ffdad6",
|
||||
"warning": "#ddb26f",
|
||||
"on-surface": "#e0e3e6",
|
||||
"inverse-on-surface": "#2d3134",
|
||||
"surface-tint": "#7ed0fe",
|
||||
"error-container": "#93000a",
|
||||
"on-tertiary": "#4c195b",
|
||||
"info": "#12cfc0",
|
||||
"tertiary-fixed": "#fbd7ff",
|
||||
"tertiary-fixed-dim": "#f0b0fc",
|
||||
"primary": "#a1dcff",
|
||||
"on-primary": "#003549",
|
||||
"inverse-surface": "#e0e3e6",
|
||||
"highlight-valid": "#acc267",
|
||||
"surface-container-low": "#181c1f",
|
||||
"surface-container": "#1c2023",
|
||||
"on-surface-variant": "#bfc8cf",
|
||||
"secondary-container": "#435401",
|
||||
"error": "#fb9fb1",
|
||||
"surface": "#151515",
|
||||
"primary-fixed": "#c4e7ff",
|
||||
"outline": "#505050",
|
||||
"surface-container-highest": "#313538",
|
||||
"on-secondary": "#293500",
|
||||
"on-primary-container": "#004f6c",
|
||||
"secondary-fixed": "#d5ec8c",
|
||||
"background": "#101417",
|
||||
"surface-container-high": "#272a2d",
|
||||
"suit-red-cb": "#6fc2ef",
|
||||
"surface-container-lowest": "#0b0f11",
|
||||
"suit-red": "#fb9fb1",
|
||||
"on-secondary-container": "#b2c86d",
|
||||
"outline-variant": "#3f484e",
|
||||
"on-secondary-fixed-variant": "#3c4d00",
|
||||
"inverse-primary": "#00668a",
|
||||
"surface-bright": "#363a3d",
|
||||
"primary-fixed-dim": "#7ed0fe",
|
||||
"tertiary-container": "#e1a3ee",
|
||||
"on-background": "#e0e3e6",
|
||||
"on-tertiary-container": "#683476",
|
||||
"suit-black": "#d0d0d0",
|
||||
"on-primary-fixed-variant": "#004c69",
|
||||
"secondary": "#bad073",
|
||||
"on-tertiary-fixed-variant": "#653173",
|
||||
"on-tertiary-fixed": "#340043",
|
||||
"highlight-celebration": "#e1a3ee"
|
||||
},
|
||||
"borderRadius": {
|
||||
"DEFAULT": "0.125rem",
|
||||
"lg": "0.25rem",
|
||||
"xl": "0.5rem",
|
||||
"full": "0.75rem"
|
||||
},
|
||||
"spacing": {
|
||||
"gutter-card": "0.375rem",
|
||||
"stack-overlap": "2rem",
|
||||
"margin-edge": "1rem",
|
||||
"action-bar-height": "64px",
|
||||
"touch-target-min": "48dp"
|
||||
},
|
||||
"fontFamily": {
|
||||
"label-caps": ["JetBrains Mono"],
|
||||
"hud-timer": ["JetBrains Mono"],
|
||||
"card-rank": ["JetBrains Mono"],
|
||||
"hud-score": ["JetBrains Mono"],
|
||||
"body-md": ["Inter"],
|
||||
"headline": ["JetBrains Mono"]
|
||||
},
|
||||
"fontSize": {
|
||||
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
|
||||
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
|
||||
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}],
|
||||
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}]
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
min-height: max(884px, 100dvh);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex flex-col min-h-screen max-w-[390px] mx-auto overflow-hidden shadow-2xl border-x border-outline">
|
||||
<!-- 1. Status Bar -->
|
||||
<div class="h-[32px] bg-[#202020] flex items-center justify-between px-margin-edge border-b border-outline">
|
||||
<span class="font-hud-timer text-[12px] text-on-surface-variant">▌daily/2024-127.json</span>
|
||||
<div class="bg-warning/10 border border-warning px-2 py-0.5 rounded-sm">
|
||||
<span class="font-hud-timer text-[11px] text-warning font-bold tracking-tighter">EXPIRES 11:42:30</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Main Content Canvas -->
|
||||
<main class="flex-1 p-margin-edge space-y-4 overflow-y-auto pb-8">
|
||||
<!-- 2. Header Card -->
|
||||
<section class="h-[130px] bg-[#1a1a1a] border border-[#353535] rounded-lg p-4 flex flex-col justify-between">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-headline font-bold text-[24px] text-suit-black leading-none">MAY 07 · 2026</span>
|
||||
<span class="font-headline font-extrabold text-[32px] text-highlight-valid -tracking-[0.01em] leading-tight">#2024-127</span>
|
||||
</div>
|
||||
<span class="font-label-caps text-[11px] text-on-surface-variant/70">DRAW-3 · DIFFICULTY ★★★☆☆ · PAR 04:30</span>
|
||||
</section>
|
||||
<!-- 3. Primary CTA -->
|
||||
<button class="w-full h-[64px] bg-primary-container text-surface font-headline font-bold text-[14px] uppercase tracking-wider rounded-lg active:scale-95 transition-transform duration-80 flex items-center justify-center gap-2">
|
||||
<span class="material-symbols-outlined text-[18px]">play_arrow</span>
|
||||
ATTEMPT TODAY'S SEED
|
||||
</button>
|
||||
<!-- 4. Your Attempts Card -->
|
||||
<section class="h-[96px] bg-[#202020] rounded-lg p-4 flex flex-col justify-between">
|
||||
<span class="font-label-caps text-[11px] text-on-surface-variant/60 uppercase">YOUR ATTEMPTS</span>
|
||||
<div class="flex justify-between items-end">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-hud-score text-[16px] text-suit-black">BEST 04:12</span>
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<span class="bg-warning text-surface text-[10px] font-bold px-1.5 py-0.5 rounded-sm">WIN</span>
|
||||
<span class="font-label-caps text-[11px] text-warning">RANK 17/2843</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="font-hud-timer text-[13px] text-error mb-1">LAST: FAILED at move 47</span>
|
||||
</div>
|
||||
</section>
|
||||
<!-- 5. Leaderboard Card -->
|
||||
<section class="bg-[#202020] rounded-lg p-4 flex flex-col flex-grow">
|
||||
<span class="font-label-caps text-[11px] text-on-surface-variant/60 uppercase mb-4">TOP TODAY · 2,843 PLAYERS</span>
|
||||
<div class="space-y-0 divide-y divide-[#353535]">
|
||||
<!-- Row 1 -->
|
||||
<div class="h-[32px] flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="w-5 h-5 flex items-center justify-center bg-warning text-surface text-[10px] font-bold rounded-full">01</span>
|
||||
<span class="font-hud-timer text-[14px]">swift_jaguar</span>
|
||||
</div>
|
||||
<span class="font-hud-timer text-[14px] text-on-surface-variant">02:47</span>
|
||||
</div>
|
||||
<!-- Row 2 -->
|
||||
<div class="h-[32px] flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="w-5 h-5 flex items-center justify-center bg-[#a0a0a0] text-surface text-[10px] font-bold rounded-full">02</span>
|
||||
<span class="font-hud-timer text-[14px]">base16_fan</span>
|
||||
</div>
|
||||
<span class="font-hud-timer text-[14px] text-on-surface-variant">03:12</span>
|
||||
</div>
|
||||
<!-- Row 3 -->
|
||||
<div class="h-[32px] flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="w-5 h-5 flex items-center justify-center bg-[#7a5d3b] text-surface text-[10px] font-bold rounded-full">03</span>
|
||||
<span class="font-hud-timer text-[14px]">cli_player</span>
|
||||
</div>
|
||||
<span class="font-hud-timer text-[14px] text-on-surface-variant">03:54</span>
|
||||
</div>
|
||||
<!-- Row 4 -->
|
||||
<div class="h-[32px] flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="w-5 h-5 flex items-center justify-center bg-[#353535] text-on-surface-variant text-[10px] font-bold rounded-full">04</span>
|
||||
<span class="font-hud-timer text-[14px]">tablejockey</span>
|
||||
</div>
|
||||
<span class="font-hud-timer text-[14px] text-on-surface-variant">04:01</span>
|
||||
</div>
|
||||
<!-- Row 5 -->
|
||||
<div class="h-[32px] flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="w-5 h-5 flex items-center justify-center bg-[#353535] text-on-surface-variant text-[10px] font-bold rounded-full">05</span>
|
||||
<span class="font-hud-timer text-[14px]">vim_motions</span>
|
||||
</div>
|
||||
<span class="font-hud-timer text-[14px] text-on-surface-variant">04:05</span>
|
||||
</div>
|
||||
<!-- Row 17 (YOU) -->
|
||||
<div class="h-[36px] flex items-center justify-between bg-primary-container/10 -mx-4 px-4 border-y border-primary-container/20">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="w-5 h-5 flex items-center justify-center bg-primary-container text-surface text-[10px] font-bold rounded-full">17</span>
|
||||
<span class="font-hud-timer text-[14px] text-primary-container font-bold">(YOU) anonymous</span>
|
||||
</div>
|
||||
<span class="font-hud-timer text-[14px] text-primary-container font-bold">04:12</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex-1 border-t border-[#353535] pt-4 flex flex-col items-center justify-center opacity-30 select-none">
|
||||
<span class="material-symbols-outlined text-[48px]">terminal</span>
|
||||
<span class="font-label-caps text-[10px] mt-2">END OF VISIBLE LOG</span>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<!-- 6. Footer Navigation -->
|
||||
<footer class="h-[24px] bg-background border-t border-outline flex items-center justify-between px-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-label-caps text-[10px] text-on-surface-variant">▌ NORMAL │ daily</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="font-label-caps text-[9px] text-on-surface-variant/70"><span class="text-primary-container">[ENTER]</span> attempt</span>
|
||||
<span class="font-label-caps text-[9px] text-on-surface-variant/70"><span class="text-primary-container">[L]</span> full leaderboard</span>
|
||||
<span class="font-label-caps text-[9px] text-on-surface-variant/70"><span class="text-primary-container">[ESC]</span> back</span>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Shared Component Shell Rendering Logic -->
|
||||
<header class="w-full top-0 sticky bg-background border-b border-outline flex justify-between items-center px-margin-edge h-action-bar-height hidden">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="material-symbols-outlined text-primary">terminal</span>
|
||||
<h1 class="font-headline text-headline text-primary uppercase tracking-widest">RUSTY SOLITAIRE</h1>
|
||||
</div>
|
||||
<span class="material-symbols-outlined text-on-surface-variant hover:text-primary transition-colors duration-120 cursor-pointer">settings</span>
|
||||
</header>
|
||||
<nav class="fixed bottom-0 w-full h-action-bar-height z-50 bg-surface-container border-t border-outline flex justify-around items-center px-2 hidden">
|
||||
<div class="flex flex-col items-center justify-center text-on-surface-variant hover:bg-surface-container-highest transition-colors duration-120 cursor-pointer w-full h-full">
|
||||
<span class="material-symbols-outlined">refresh</span>
|
||||
<span class="font-label-caps text-label-caps">DEAL</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center text-on-surface-variant hover:bg-surface-container-highest transition-colors duration-120 cursor-pointer w-full h-full">
|
||||
<span class="material-symbols-outlined">undo</span>
|
||||
<span class="font-label-caps text-label-caps">UNDO</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center text-on-surface-variant hover:bg-surface-container-highest transition-colors duration-120 cursor-pointer w-full h-full">
|
||||
<span class="material-symbols-outlined">lightbulb</span>
|
||||
<span class="font-label-caps text-label-caps">HINT</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center text-primary dark:text-primary-fixed-dim hover:bg-surface-container-highest transition-colors duration-120 cursor-pointer w-full h-full">
|
||||
<span class="material-symbols-outlined">menu</span>
|
||||
<span class="font-label-caps text-label-caps">MENU</span>
|
||||
</div>
|
||||
</nav>
|
||||
</body></html>
|
||||
|
After Width: | Height: | Size: 42 KiB |
@@ -0,0 +1,280 @@
|
||||
---
|
||||
name: Terminal
|
||||
colors:
|
||||
surface: '#151515'
|
||||
surface-dim: '#0d0d0d'
|
||||
surface-bright: '#2a2a2a'
|
||||
surface-container-lowest: '#0a0a0a'
|
||||
surface-container-low: '#1a1a1a'
|
||||
surface-container: '#202020'
|
||||
surface-container-high: '#2a2a2a'
|
||||
surface-container-highest: '#353535'
|
||||
on-surface: '#d0d0d0'
|
||||
on-surface-variant: '#a0a0a0'
|
||||
inverse-surface: '#d0d0d0'
|
||||
inverse-on-surface: '#151515'
|
||||
outline: '#505050'
|
||||
outline-variant: '#353535'
|
||||
surface-tint: '#a54242'
|
||||
primary: '#a54242'
|
||||
on-primary: '#151515'
|
||||
primary-container: '#3a1f1f'
|
||||
on-primary-container: '#d5a8a8'
|
||||
inverse-primary: '#993e3e'
|
||||
secondary: '#acc267'
|
||||
on-secondary: '#151515'
|
||||
secondary-container: '#2a3320'
|
||||
on-secondary-container: '#c5d585'
|
||||
tertiary: '#e1a3ee'
|
||||
on-tertiary: '#151515'
|
||||
tertiary-container: '#3a2a40'
|
||||
on-tertiary-container: '#eec3f5'
|
||||
error: '#fb9fb1'
|
||||
on-error: '#151515'
|
||||
error-container: '#4a2530'
|
||||
on-error-container: '#fdc3ce'
|
||||
background: '#151515'
|
||||
on-background: '#d0d0d0'
|
||||
surface-variant: '#353535'
|
||||
suit-red: '#fb9fb1'
|
||||
suit-black: '#d0d0d0'
|
||||
suit-red-cb: '#acc267'
|
||||
highlight-valid: '#acc267'
|
||||
highlight-celebration: '#e1a3ee'
|
||||
highlight-warning: '#ddb26f'
|
||||
highlight-info: '#12cfc0'
|
||||
typography:
|
||||
hud-score:
|
||||
fontFamily: JetBrains Mono
|
||||
fontSize: 24px
|
||||
fontWeight: '700'
|
||||
lineHeight: 32px
|
||||
letterSpacing: '-0.02em'
|
||||
hud-timer:
|
||||
fontFamily: JetBrains Mono
|
||||
fontSize: 16px
|
||||
fontWeight: '400'
|
||||
lineHeight: 24px
|
||||
card-rank:
|
||||
fontFamily: JetBrains Mono
|
||||
fontSize: 18px
|
||||
fontWeight: '700'
|
||||
lineHeight: 18px
|
||||
body-md:
|
||||
fontFamily: Inter
|
||||
fontSize: 16px
|
||||
fontWeight: '400'
|
||||
lineHeight: 24px
|
||||
label-caps:
|
||||
fontFamily: JetBrains Mono
|
||||
fontSize: 12px
|
||||
fontWeight: '500'
|
||||
lineHeight: 16px
|
||||
letterSpacing: '0.08em'
|
||||
headline:
|
||||
fontFamily: JetBrains Mono
|
||||
fontSize: 28px
|
||||
fontWeight: '700'
|
||||
lineHeight: 32px
|
||||
letterSpacing: '-0.01em'
|
||||
rounded:
|
||||
sm: 0.125rem
|
||||
DEFAULT: 0.25rem
|
||||
md: 0.5rem
|
||||
lg: 0.75rem
|
||||
xl: 1rem
|
||||
full: 9999px
|
||||
spacing:
|
||||
margin-edge: 1rem
|
||||
gutter-card: 0.375rem
|
||||
stack-overlap: 2rem
|
||||
touch-target-min: 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 00–07 form a monochrome ramp and 08–0F 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 mapping**, with mandatory color-blind support:
|
||||
|
||||
| Suit | Default | Color-blind mode | Glyph differentiation |
|
||||
|---|---|---|---|
|
||||
| Hearts | `#fb9fb1` (pink) | `#acc267` (lime) | Solid filled glyph |
|
||||
| Diamonds | `#fb9fb1` (pink) | `#acc267` (lime) | **Outlined glyph (1.5px stroke)** |
|
||||
| Spades | `#d0d0d0` (foreground) | `#d0d0d0` | Solid filled glyph |
|
||||
| Clubs | `#d0d0d0` (foreground) | `#d0d0d0` | **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 only swaps red→lime; it 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: 1px solid in suit color (pink for hearts/diamonds, foreground gray for spades/clubs)
|
||||
- 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 red suits' default `#fb9fb1` 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).
|
||||
@@ -0,0 +1,283 @@
|
||||
# Terminal — Desktop Adaptation Spec
|
||||
|
||||
> **Why this exists.** The 24 mockups in this directory are mobile
|
||||
> (390 × 844 logical, iPhone 14 Pro frame) with one exception
|
||||
> (`home-menu-desktop.html`). The Stitch project that produced them
|
||||
> is named "Solitaire Quest *Mobile* Redesign" — the mobile-first
|
||||
> framing was deliberate when the new Android target opened, but
|
||||
> desktop is still the primary delivery surface. Porting the mobile
|
||||
> mockups 1:1 would land a 390-px-wide column floating in the middle
|
||||
> of an 1800 × 1100 window. This file is the rules-based desktop
|
||||
> companion — apply these adaptations whenever you port a Bevy
|
||||
> plugin against a mobile mockup in this directory.
|
||||
|
||||
## Status
|
||||
|
||||
* **Token system.** All tokens (palette, type scale, spacing,
|
||||
radii, motion) in `design-system.md` are layout-agnostic and
|
||||
apply unchanged on both targets. Do **not** introduce desktop-
|
||||
specific token variants — adapt geometry, not tokens.
|
||||
* **Already adapted in code.** v0.20.0's port is layout-agnostic
|
||||
(modal scaffold, toasts, table chrome, card chrome, gameplay-
|
||||
feedback, splash cursor). Those surfaces already adapt
|
||||
correctly because their Bevy UI nodes use flex / percent /
|
||||
stretch sizing rather than fixed pixel widths from the
|
||||
mockups.
|
||||
* **Not yet adapted in code.** Any future plugin port that
|
||||
copies layout from a mobile mockup must apply the rules below.
|
||||
|
||||
## Viewport assumptions
|
||||
|
||||
| Range | Width × height | Source |
|
||||
|---|---|---|
|
||||
| Mobile target | 390 × 844 | iPhone 14 Pro logical, Stitch mockup canvas |
|
||||
| Desktop minimum | 1024 × 600 | Smaller windows degrade to mobile rules |
|
||||
| Desktop default | ~70 % of monitor | `apply_smart_default_window_size` (since v0.19.0) |
|
||||
| Desktop typical | 1600 × 900 to 2560 × 1440 | The range we tune for |
|
||||
| Desktop max | 3840 × 2160 | 4K, with HiDPI scaling already applied |
|
||||
|
||||
The "smart default" sizer means a 1080p monitor opens a ~1344 × 756
|
||||
window, a 1440p monitor opens ~1792 × 1008, a 4K monitor opens
|
||||
~2688 × 1512. Tune for the 1600–2400 width band as the centre of
|
||||
the distribution; below 1024 width, fall back to the mobile rules
|
||||
verbatim.
|
||||
|
||||
## Universal adaptation rules
|
||||
|
||||
Apply these to every screen unless the per-screen section
|
||||
overrides them.
|
||||
|
||||
### 1. Edge margins
|
||||
|
||||
| Mobile | Desktop |
|
||||
|---|---|
|
||||
| `margin-edge: 16px` (`SPACE_4`) | `SPACE_5` (24 px) for windows < 1440 wide; `SPACE_6` (32 px) for 1440–2400; `SPACE_7` (48 px) for ≥ 2400 |
|
||||
|
||||
Engine: drive from `LayoutResource` based on `Window` size, not a
|
||||
constant.
|
||||
|
||||
### 2. Modal max-width
|
||||
|
||||
| Mobile | Desktop |
|
||||
|---|---|
|
||||
| `100% - 2 × edge-margin` | `min(720 px, 50 % of viewport)` |
|
||||
|
||||
The 720 px cap is already in `ui_modal::spawn_modal`. No code
|
||||
change needed; this rule documents *why* it's there.
|
||||
|
||||
### 3. Vertical content stacks
|
||||
|
||||
A mobile screen often stacks `Header → Body → Footer` vertically
|
||||
to fit a tall narrow column. On desktop, prefer horizontal
|
||||
distribution where the content allows:
|
||||
|
||||
* **Header rows that stack vertically on mobile** (title above
|
||||
count above timer) → keep them in one horizontal row on
|
||||
desktop.
|
||||
* **Two-column flex layouts** (e.g. Settings rows: label left,
|
||||
control right) — already work on both targets; no change.
|
||||
* **Cards stacking with `mt-48`-style fixed gaps** — replace with
|
||||
flex / percent gaps so the layout breathes.
|
||||
|
||||
### 4. Touch-target minimums
|
||||
|
||||
Mobile spec mandates 48 dp minimum touch targets. Desktop has no
|
||||
such floor (mouse precision is finer), but **don't shrink below
|
||||
mobile's 48 px** for primary actions — keyboard / gamepad focus
|
||||
rings still need a visible target.
|
||||
|
||||
Secondary controls (chip-style toggles, hotkey hints, etc.) can
|
||||
shrink to `TYPE_BODY` (14 px) text + `SPACE_3` (12 px) padding on
|
||||
desktop where they were larger on mobile.
|
||||
|
||||
### 5. Bottom-anchored elements
|
||||
|
||||
Mobile mockups often anchor key controls (action bar, primary CTA,
|
||||
toast position) to the bottom of the viewport for thumb reach.
|
||||
Desktop has no thumb-reach concern:
|
||||
|
||||
* **Toasts** — keep bottom-anchored (already done in `a137607`),
|
||||
the design language is consistent across targets and the
|
||||
bottom is still the least-disruptive overlay zone.
|
||||
* **Action bars** — top of viewport on desktop unless the
|
||||
per-screen section says otherwise. The HUD already sits on
|
||||
top.
|
||||
* **Single primary CTA** — modals already right-align in the
|
||||
actions row; no change.
|
||||
|
||||
### 6. Typography rungs unchanged
|
||||
|
||||
Do **not** shift `TYPE_*` tokens up a rung for desktop. The
|
||||
spec's 14 / 18 / 26 / 40 progression is already calibrated for
|
||||
the desktop reading distance (60–90 cm). Mobile uses the same
|
||||
rungs at a closer reading distance (30–40 cm); same physical
|
||||
angular size on the eye.
|
||||
|
||||
### 7. Hotkey hints become full strings
|
||||
|
||||
Mobile cells like `▌Esc` — the cursor block plus key letter — can
|
||||
expand to `[Esc] cancel` style on desktop where horizontal
|
||||
real-estate is cheap. Drives discoverability of keyboard-only
|
||||
flows. Optional; only apply where horizontal space exists.
|
||||
|
||||
## Per-screen adaptation rules
|
||||
|
||||
### Game Table
|
||||
|
||||
Mockup: `game-table-mobile.html` (390 × 844).
|
||||
|
||||
| Element | Mobile | Desktop |
|
||||
|---|---|---|
|
||||
| HUD band | full width, 56 px tall | full width, 48 px tall |
|
||||
| Foundation row | 4 piles centred, fan-tight | 4 piles centred, **gutter doubled** so the row fills ~50 % of viewport width |
|
||||
| Stock + waste | left of foundations, stacked | left of foundations, **horizontal pair**: stock on the left, waste to its immediate right (the mobile vertical pair feels cramped on a wide canvas) |
|
||||
| Tableau row | 7 columns, 4 % gutter | 7 columns, **6 % gutter**, total tableau block ≤ 70 % viewport width |
|
||||
| Card aspect | 2 : 3 (already in `Layout::card_size`) | unchanged — card aspect is domain |
|
||||
| Tableau fan | `TABLEAU_FAN_FRAC = 0.25` | unchanged — fan is in card-height units, not viewport units |
|
||||
| Drag-shadow offset | small | unchanged — pinned to 0 alpha under Terminal anyway |
|
||||
|
||||
**Engine impact:** `solitaire_engine/src/layout.rs::compute_layout`
|
||||
already drives most of this from `Window::size()`. The mobile vs.
|
||||
desktop difference is the gutter percentages — bake desktop
|
||||
gutters when window width ≥ 1024.
|
||||
|
||||
### Win Summary
|
||||
|
||||
Mockup: `win-summary-mobile.html` (390 × 858).
|
||||
|
||||
| Element | Mobile | Desktop |
|
||||
|---|---|---|
|
||||
| Modal width | 100 % − 2 × edge | **`min(720 px, 50 % viewport)`** (already done by `ui_modal`) |
|
||||
| Score row | stacked vertically (line per metric) | **3-column grid**: Score / Time / Moves in one row, breakdown rows below in single-line per row |
|
||||
| Action buttons | full-width stacked (Play Again, Continue, Stats) | **right-aligned action row** — the existing `spawn_modal_actions` already does this on both targets |
|
||||
|
||||
**Engine impact:** `solitaire_engine/src/win_summary_plugin.rs`. The
|
||||
score-breakdown-stagger animation (`MOTION_SCORE_BREAKDOWN_*`) is
|
||||
unchanged across targets.
|
||||
|
||||
### Settings
|
||||
|
||||
Mockup: `settings-mobile.html` (390 × 4330 — long scroll).
|
||||
|
||||
| Element | Mobile | Desktop |
|
||||
|---|---|---|
|
||||
| Modal width | 100 % − 2 × edge | `min(720 px, 50 % viewport)` |
|
||||
| Sections | full-width labels above stacked controls | **section labels left, control widget right** — already the engine's pattern; no change |
|
||||
| Long page | scroll the whole modal | **two-column layout**: nav (sections list) on left ~30 %, current section on right ~70 %. Reduces scroll distance on desktop |
|
||||
| Sliders | full-width on mobile | cap at 320 px on desktop |
|
||||
|
||||
**Engine impact:** if a desktop port wants the two-column nav, it's
|
||||
a `settings_plugin` rewrite. Keep the existing single-column
|
||||
stacked-modal layout for now — it works on both targets and the
|
||||
two-column variant is a polish item, not a blocker.
|
||||
|
||||
### Help & Controls
|
||||
|
||||
Mockup: `help-mobile.html` (390 × 2544).
|
||||
|
||||
| Element | Mobile | Desktop |
|
||||
|---|---|---|
|
||||
| Modal width | 100 % − 2 × edge | `min(720 px, 50 % viewport)` |
|
||||
| Section list | one column of `Heading → 2-col rows` | **two columns of section blocks** for windows ≥ 1280 wide; halves vertical scroll distance |
|
||||
| Hotkey rows | `key | description` 2-col flex | unchanged; 2-col already adapts |
|
||||
|
||||
**Engine impact:** `help_plugin`. Single-column on mobile, 2-col
|
||||
on desktop windows ≥ 1280 wide is a flex-wrap option.
|
||||
|
||||
### Pause Menu
|
||||
|
||||
Mockup: `pause-menu-mobile.html` (390 × 1768).
|
||||
|
||||
Already a small modal; no significant geometry change. Modal
|
||||
already uses `ui_modal::spawn_modal` which caps width and centres.
|
||||
No desktop-specific rule.
|
||||
|
||||
### Home Menu
|
||||
|
||||
Mockup: `home-menu-mobile.html` and `home-menu-desktop.html`
|
||||
(both already in this directory — desktop variant is the
|
||||
authoritative reference).
|
||||
|
||||
The desktop mockup already specifies the layout. Cross-check it
|
||||
against the mobile version when porting; differences are
|
||||
deliberate (more horizontal real-estate, larger primary CTA, the
|
||||
secondary actions row).
|
||||
|
||||
### Splash
|
||||
|
||||
Mockup: `splash-mobile.html` (390 × 844).
|
||||
|
||||
| Element | Mobile | Desktop |
|
||||
|---|---|---|
|
||||
| Full-screen overlay | `inset-0` | unchanged — splash always covers the viewport |
|
||||
| Cursor block (`▌`) | 96 px JetBrains Mono | unchanged — already done in `cdcadda`. The 96 px size scales fine on desktop because the splash is a brand beat, not a layout-driven element |
|
||||
| Title `RUSTY SOLITAIRE` | 32 px | scale to 40 px (`TYPE_DISPLAY`) on desktop |
|
||||
| Subtitle `TERMINAL EDITION` | 12 px | unchanged |
|
||||
| Boot log lines | 70 % width column | cap at 480 px so the column doesn't stretch on a wide window |
|
||||
| Progress bar | 100 % − 2 × edge | cap at 720 px |
|
||||
| Palette swatch row + version footer | bottom-anchored | unchanged; bottom-anchor still reads correctly on desktop |
|
||||
|
||||
**Engine impact:** `splash_plugin` already has the cursor block
|
||||
(`cdcadda`). The boot log / progress bar / palette swatch rows
|
||||
are the next polish increment when option D is picked up.
|
||||
|
||||
### Stats
|
||||
|
||||
Mockup: `stats-mobile.html` (390 × 2624).
|
||||
|
||||
| Element | Mobile | Desktop |
|
||||
|---|---|---|
|
||||
| Modal width | 100 % − 2 × edge | `min(720 px, 50 % viewport)` |
|
||||
| Big-number cards | 2 × 2 grid | **4 × 1 row** for windows ≥ 1024 wide (the four headline metrics fit in a single horizontal row at desktop scale) |
|
||||
| Latest-win caption | full-width line | unchanged |
|
||||
| Replay clip / share row | full-width row | unchanged |
|
||||
|
||||
### Profile / Achievements / Theme Picker / Daily Challenge
|
||||
|
||||
These follow the **standard modal pattern** (`spawn_modal` with
|
||||
header / body / actions). They already work on desktop because
|
||||
`ui_modal` handles modal-width capping. Per-screen tweaks are
|
||||
small and listed below; no structural changes:
|
||||
|
||||
* **Profile** — avatar + level / streak chips can flow into a
|
||||
single horizontal row on desktop instead of stacking.
|
||||
* **Achievements** — 3 × N grid on mobile becomes 4 × N or 5 × N
|
||||
on desktop where windows ≥ 1280 wide.
|
||||
* **Theme Picker** — 2-col grid of theme cards on mobile becomes
|
||||
3- or 4-col on desktop.
|
||||
* **Daily Challenge** — single-column scroll on both; no change.
|
||||
|
||||
## Mockup parity gap
|
||||
|
||||
The 9 missing-plugin screens (`splash`, `challenge`, `time-attack`,
|
||||
`weekly-goals`, `leaderboard`, `sync`, `level-up`, `replay-overlay`,
|
||||
`radial-menu`) have only mobile mockups. When porting any of these
|
||||
plugins:
|
||||
|
||||
1. Read the mobile mockup for content + visual hierarchy.
|
||||
2. Apply the universal adaptation rules above.
|
||||
3. Apply the closest matching per-screen rule (e.g. an info modal
|
||||
uses the same shape as Win Summary or Stats).
|
||||
4. **No new layout pattern without explicit user approval.**
|
||||
Adapting an existing pattern is in scope; inventing a desktop-
|
||||
specific component is design work and should be flagged as such.
|
||||
|
||||
## Process notes
|
||||
|
||||
* **Smart-default sizer is the layout's source of truth.** Before
|
||||
reading the mockup, always re-read `Window::size()` —
|
||||
`apply_smart_default_window_size` runs at startup and the
|
||||
player can resize freely. Hardcoded breakpoints in plugin code
|
||||
should reference the *current* `Window` width via a
|
||||
`LayoutResource` lookup, not the launch size.
|
||||
* **`WindowResized` already drives layout recomputes** (CLAUDE.md
|
||||
§3.4). Any per-window-width adaptation in this file should hook
|
||||
into the existing recompute path, not a new system.
|
||||
* **Mobile rules win at narrow desktop windows.** A user dragging
|
||||
their desktop window down to 600 px width is closer to the
|
||||
mobile use-case than the desktop one. Below 1024 px width,
|
||||
apply the mobile rules verbatim.
|
||||
* **Run on a 4K monitor before declaring a port done.** HiDPI
|
||||
scaling routes through Bevy's logical sizing, but visual
|
||||
polish (border thickness, motion budgets at high refresh rate)
|
||||
is worth eyeballing.
|
||||
@@ -0,0 +1,253 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="dark" lang="en"><head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@400;500&family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
}
|
||||
.outlined-glyph {
|
||||
-webkit-text-stroke: 1.5px currentColor;
|
||||
color: transparent;
|
||||
}
|
||||
.scanline-pattern {
|
||||
background: repeating-linear-gradient(
|
||||
0deg,
|
||||
#1a1a1a,
|
||||
#1a1a1a 2px,
|
||||
#151515 2px,
|
||||
#151515 4px
|
||||
);
|
||||
}
|
||||
.tabular-nums {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
</style>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
"colors": {
|
||||
"surface": "#151515",
|
||||
"secondary-fixed": "#d5ec8c",
|
||||
"warning": "#ddb26f",
|
||||
"tertiary-fixed": "#fbd7ff",
|
||||
"on-tertiary-fixed-variant": "#653173",
|
||||
"on-background": "#e0e3e6",
|
||||
"on-primary-container": "#004f6c",
|
||||
"surface-container-lowest": "#0b0f11",
|
||||
"on-surface": "#e0e3e6",
|
||||
"error": "#fb9fb1",
|
||||
"primary-fixed-dim": "#7ed0fe",
|
||||
"inverse-primary": "#00668a",
|
||||
"surface-container-high": "#272a2d",
|
||||
"suit-red-cb": "#6fc2ef",
|
||||
"surface-bright": "#363a3d",
|
||||
"on-primary": "#003549",
|
||||
"on-tertiary": "#4c195b",
|
||||
"error-container": "#93000a",
|
||||
"on-tertiary-fixed": "#340043",
|
||||
"surface-container": "#202020",
|
||||
"tertiary-container": "#e1a3ee",
|
||||
"on-primary-fixed-variant": "#004c69",
|
||||
"surface-container-highest": "#313538",
|
||||
"highlight-celebration": "#e1a3ee",
|
||||
"highlight-valid": "#acc267",
|
||||
"primary": "#a1dcff",
|
||||
"secondary-fixed-dim": "#bad073",
|
||||
"on-primary-fixed": "#001e2c",
|
||||
"on-error-container": "#ffdad6",
|
||||
"secondary": "#bad073",
|
||||
"on-tertiary": "#293500",
|
||||
"on-secondary-container": "#b2c86d",
|
||||
"inverse-on-surface": "#2d3134",
|
||||
"on-error": "#690005",
|
||||
"info": "#12cfc0",
|
||||
"suit-red": "#fb9fb1",
|
||||
"surface-dim": "#101417",
|
||||
"surface-tint": "#7ed0fe",
|
||||
"background": "#101417",
|
||||
"secondary-container": "#435401",
|
||||
"surface-variant": "#313538",
|
||||
"outline-variant": "#3f484e",
|
||||
"on-surface-variant": "#bfc8cf",
|
||||
"primary-fixed": "#c4e7ff",
|
||||
"on-secondary-fixed-variant": "#3c4d00",
|
||||
"on-secondary-fixed": "#161e00",
|
||||
"suit-black": "#d0d0d0"
|
||||
},
|
||||
"borderRadius": {
|
||||
"DEFAULT": "0.125rem",
|
||||
"lg": "0.25rem",
|
||||
"xl": "0.5rem",
|
||||
"full": "0.75rem"
|
||||
},
|
||||
"spacing": {
|
||||
"touch-target-min": "48px",
|
||||
"margin-edge": "1rem",
|
||||
"action-bar-height": "64px",
|
||||
"stack-overlap": "2rem",
|
||||
"gutter-card": "0.375rem"
|
||||
},
|
||||
"fontFamily": {
|
||||
"label-caps": ["JetBrains Mono"],
|
||||
"headline": ["JetBrains Mono"],
|
||||
"card-rank": ["JetBrains Mono"],
|
||||
"body-md": ["Inter"],
|
||||
"hud-score": ["JetBrains Mono"],
|
||||
"hud-timer": ["JetBrains Mono"]
|
||||
},
|
||||
"fontSize": {
|
||||
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
|
||||
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
|
||||
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
|
||||
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}],
|
||||
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}]
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
min-height: max(884px, 100dvh);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-surface text-on-surface font-body-md overflow-hidden selection:bg-primary selection:text-surface">
|
||||
<!-- TopAppBar -->
|
||||
<header class="fixed top-0 w-full flex justify-between items-center px-margin-edge h-[56px] bg-surface-container border-b border-outline dark:border-outline z-50">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-primary">terminal</span>
|
||||
<h1 class="font-hud-score text-[18px] text-primary">solitaire.sh</h1>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-[6px] h-[6px] rounded-full bg-info"></div>
|
||||
<span class="material-symbols-outlined text-on-surface-variant">settings</span>
|
||||
</div>
|
||||
</header>
|
||||
<!-- HUD Band -->
|
||||
<div class="fixed top-[56px] left-0 w-full h-[56px] bg-surface-container border-b border-outline-variant flex items-center justify-around px-margin-edge z-40">
|
||||
<div class="bg-surface p-1 px-3 rounded flex flex-col items-center">
|
||||
<span class="font-label-caps text-[10px] text-on-surface-variant">SCORE</span>
|
||||
<span class="font-hud-score text-primary tabular-nums">247</span>
|
||||
</div>
|
||||
<div class="bg-surface p-1 px-3 rounded flex flex-col items-center border border-outline">
|
||||
<span class="font-label-caps text-[10px] text-on-surface-variant">TIME</span>
|
||||
<span class="font-hud-timer text-on-surface tabular-nums">12:34</span>
|
||||
</div>
|
||||
<div class="bg-surface p-1 px-3 rounded flex flex-col items-center">
|
||||
<span class="font-label-caps text-[10px] text-on-surface-variant">MOVES</span>
|
||||
<span class="font-hud-score text-secondary tabular-nums">87</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Main Game Table -->
|
||||
<main class="pt-[124px] px-margin-edge h-screen w-full relative">
|
||||
<!-- Top Row: Stock, Waste, Foundations -->
|
||||
<div class="grid grid-cols-7 gap-gutter-card h-[110px]">
|
||||
<!-- Stock -->
|
||||
<div class="relative w-full h-full rounded-xl border border-outline-variant bg-surface overflow-hidden scanline-pattern">
|
||||
<div class="absolute top-1 left-1 w-3 h-4 bg-suit-red-cb"></div>
|
||||
<div class="absolute bottom-1 right-1 font-label-caps text-[8px] text-suit-black">▌RS</div>
|
||||
<div class="absolute bottom-[-16px] left-0 w-full text-center font-label-caps text-[10px] text-on-surface-variant">STOCK · 18</div>
|
||||
</div>
|
||||
<!-- Waste -->
|
||||
<div class="relative w-full h-full rounded-xl border border-suit-red bg-[#1a1a1a] flex flex-col justify-between p-1.5">
|
||||
<div class="font-card-rank text-suit-red leading-none">10<br/><span class="font-normal">♥</span></div>
|
||||
<div class="self-end text-[32px] font-card-rank text-suit-red rotate-180">♥</div>
|
||||
</div>
|
||||
<!-- Empty Gap -->
|
||||
<div></div>
|
||||
<!-- Foundation S -->
|
||||
<div class="relative w-full h-full rounded-xl border border-dashed border-outline-variant flex items-center justify-center">
|
||||
<span class="text-on-surface-variant opacity-20 text-[32px] font-card-rank">♠</span>
|
||||
</div>
|
||||
<!-- Foundation H -->
|
||||
<div class="relative w-full h-full rounded-xl border border-suit-red bg-[#1a1a1a] flex flex-col justify-between p-1.5">
|
||||
<div class="font-card-rank text-suit-red leading-none">2<br/><span class="font-normal">♥</span></div>
|
||||
<div class="self-end text-[32px] font-card-rank text-suit-red rotate-180">♥</div>
|
||||
</div>
|
||||
<!-- Foundation C -->
|
||||
<div class="relative w-full h-full rounded-xl border border-dashed border-outline-variant flex items-center justify-center">
|
||||
<span class="text-on-surface-variant opacity-20 text-[32px] font-card-rank outlined-glyph">♣</span>
|
||||
</div>
|
||||
<!-- Foundation D -->
|
||||
<div class="relative w-full h-full rounded-xl border border-dashed border-outline-variant flex items-center justify-center">
|
||||
<span class="text-on-surface-variant opacity-20 text-[32px] font-card-rank outlined-glyph">♦</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tableau -->
|
||||
<div class="mt-8 grid grid-cols-7 gap-gutter-card items-start relative h-[400px]">
|
||||
<!-- Col 1 -->
|
||||
<div class="relative w-full h-full">
|
||||
<div class="w-full h-[96px] rounded-xl border border-suit-black bg-[#1a1a1a] p-1.5">
|
||||
<div class="font-card-rank text-suit-black leading-none">K<br/><span class="font-normal">♠</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Col 2 -->
|
||||
<div class="relative w-full h-full">
|
||||
<div class="w-full h-[96px] rounded-xl border border-outline bg-[#1a1a1a] scanline-pattern"></div>
|
||||
<div class="absolute top-[32px] w-full h-[96px] rounded-xl border border-suit-red bg-[#1a1a1a] p-1.5">
|
||||
<div class="font-card-rank text-suit-red leading-none">Q<br/><span class="font-normal">♥</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Col 3 -->
|
||||
<div class="relative w-full h-full">
|
||||
<div class="w-full h-[96px] rounded-xl border border-outline bg-[#1a1a1a] scanline-pattern"></div>
|
||||
<div class="absolute top-[32px] w-full h-[96px] rounded-xl border border-outline bg-[#1a1a1a] scanline-pattern"></div>
|
||||
<div class="absolute top-[64px] w-full h-[96px] rounded-xl border border-suit-red bg-[#1a1a1a] p-1.5">
|
||||
<div class="font-card-rank text-suit-red leading-none">10<br/><span class="font-normal outlined-glyph">♦</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Col 4 -->
|
||||
<div class="relative w-full h-full">
|
||||
<div class="w-full h-[96px] rounded-xl border border-outline bg-[#1a1a1a] scanline-pattern"></div>
|
||||
<div class="w-full h-[96px] rounded-xl border border-outline bg-[#1a1a1a] scanline-pattern absolute top-[32px]"></div>
|
||||
<!-- Valid Drop Target Glow -->
|
||||
<div class="absolute top-[64px] w-full h-[96px] rounded-xl border border-suit-black bg-[#1a1a1a] p-1.5 ring-4 ring-highlight-valid/30">
|
||||
<div class="font-card-rank text-suit-black leading-none">9<br/><span class="font-normal">♠</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Col 5, 6 (Empty/Filler) -->
|
||||
<div class="relative w-full"></div>
|
||||
<div class="relative w-full"></div>
|
||||
<!-- Col 7 -->
|
||||
<div class="relative w-full">
|
||||
<!-- Original Position Placeholder -->
|
||||
<div class="w-full h-[96px] rounded-xl border border-dashed border-outline"></div>
|
||||
<!-- Being Dragged Card -->
|
||||
<div class="absolute top-[-20px] left-[30px] w-full h-[96px] rounded-xl border border-suit-red bg-[#1a1a1a] p-1.5 shadow-[0_0_20px_rgba(111,194,239,0.4)] z-50 ring-1 ring-primary/40">
|
||||
<div class="font-card-rank text-suit-red leading-none">4<br/><span class="font-normal outlined-glyph">♦</span></div>
|
||||
<div class="absolute bottom-1 right-1 text-[24px] font-card-rank text-suit-red rotate-180 outlined-glyph">♦</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<!-- BottomNavBar / Action Bar -->
|
||||
<nav class="fixed bottom-0 left-0 w-full h-action-bar-height bg-surface-container border-t border-outline-variant flex justify-around items-center px-margin-edge z-50">
|
||||
<button class="flex flex-col items-center justify-center text-on-surface-variant hover:text-info transition-colors duration-120 active:opacity-80">
|
||||
<span class="material-symbols-outlined" data-icon="menu">menu</span>
|
||||
<span class="font-label-caps text-[10px] mt-1">[ESC] MENU</span>
|
||||
</button>
|
||||
<button class="flex flex-col items-center justify-center text-info font-bold active:opacity-80">
|
||||
<span class="material-symbols-outlined" data-icon="undo">undo</span>
|
||||
<span class="font-label-caps text-[10px] mt-1">[U] UNDO</span>
|
||||
</button>
|
||||
<button class="flex flex-col items-center justify-center text-on-surface-variant hover:text-info transition-colors duration-120 active:opacity-80">
|
||||
<span class="material-symbols-outlined" data-icon="lightbulb">lightbulb</span>
|
||||
<span class="font-label-caps text-[10px] mt-1">[H] HINT</span>
|
||||
</button>
|
||||
<button class="flex flex-col items-center justify-center text-on-surface-variant hover:text-info transition-colors duration-120 active:opacity-80">
|
||||
<span class="material-symbols-outlined" data-icon="add_box">add_box</span>
|
||||
<span class="font-label-caps text-[10px] mt-1">[N] NEW</span>
|
||||
</button>
|
||||
</nav>
|
||||
<!-- Drag & CRT Overlay (Visual Decoration) -->
|
||||
<div class="pointer-events-none fixed inset-0 z-[100] opacity-[0.03] scanline-pattern mix-blend-overlay"></div>
|
||||
</body></html>
|
||||
|
After Width: | Height: | Size: 26 KiB |
@@ -0,0 +1,200 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="dark" lang="en"><head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@400&family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
"colors": {
|
||||
"on-secondary-container": "#b2c86d",
|
||||
"secondary-fixed-dim": "#bad073",
|
||||
"surface-tint": "#7ed0fe",
|
||||
"on-surface-variant": "#bfc8cf",
|
||||
"surface-container-low": "#181c1f",
|
||||
"secondary-fixed": "#d5ec8c",
|
||||
"primary-fixed-dim": "#7ed0fe",
|
||||
"secondary": "#bad073",
|
||||
"tertiary-container": "#e1a3ee",
|
||||
"inverse-on-surface": "#2d3134",
|
||||
"surface-container-lowest": "#0b0f11",
|
||||
"on-error-container": "#ffdad6",
|
||||
"on-primary-fixed-variant": "#004c69",
|
||||
"secondary-container": "#435401",
|
||||
"background": "#101417",
|
||||
"surface-variant": "#313538",
|
||||
"on-primary-container": "#004f6c",
|
||||
"highlight-valid": "#acc267",
|
||||
"outline-variant": "#3f484e",
|
||||
"on-background": "#e0e3e6",
|
||||
"surface-bright": "#363a3d",
|
||||
"on-tertiary-fixed-variant": "#653173",
|
||||
"on-secondary-fixed": "#161e00",
|
||||
"surface-dim": "#101417",
|
||||
"on-surface": "#e0e3e6",
|
||||
"info": "#12cfc0",
|
||||
"on-secondary": "#293500",
|
||||
"suit-red": "#fb9fb1",
|
||||
"error": "#fb9fb1",
|
||||
"error-container": "#93000a",
|
||||
"surface-container": "#202020",
|
||||
"primary-fixed": "#c4e7ff",
|
||||
"warning": "#ddb26f",
|
||||
"tertiary": "#f7c3ff",
|
||||
"highlight-celebration": "#e1a3ee",
|
||||
"tertiary-fixed": "#fbd7ff",
|
||||
"inverse-surface": "#e0e3e6",
|
||||
"tertiary-fixed-dim": "#f0b0fc",
|
||||
"primary-container": "#6fc2ef",
|
||||
"on-secondary-fixed-variant": "#3c4d00",
|
||||
"on-tertiary": "#4c195b",
|
||||
"suit-red-cb": "#6fc2ef",
|
||||
"surface-container-highest": "#313538",
|
||||
"on-primary-fixed": "#001e2c",
|
||||
"surface-container-high": "#272a2d",
|
||||
"primary": "#a1dcff",
|
||||
"suit-black": "#d0d0d0",
|
||||
"on-tertiary-container": "#683476",
|
||||
"on-error": "#690005",
|
||||
"inverse-primary": "#00668a",
|
||||
"on-tertiary-fixed": "#340043",
|
||||
"outline": "#505050",
|
||||
"on-primary": "#003549",
|
||||
"surface": "#151515"
|
||||
},
|
||||
"fontFamily": {
|
||||
"jetbrains": ["JetBrains Mono", "monospace"],
|
||||
"inter": ["Inter", "sans-serif"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
vertical-align: middle;
|
||||
font-size: 16px;
|
||||
}
|
||||
.tabular-nums { font-variant-numeric: tabular-nums; }
|
||||
body { background-color: #151515; }
|
||||
</style>
|
||||
<style>
|
||||
body {
|
||||
min-height: max(884px, 100dvh);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex items-center justify-center min-h-screen p-4">
|
||||
<!-- Mobile Container (390x844) -->
|
||||
<div class="w-[390px] h-[844px] bg-surface flex flex-col overflow-hidden relative border border-outline/20">
|
||||
<!-- 1. Status Bar -->
|
||||
<header class="h-[32px] bg-surface-container flex items-center justify-between px-4 shrink-0">
|
||||
<span class="font-jetbrains text-[12px] font-bold text-suit-black tracking-tight">▌rusty-solitaire(1) · MAN PAGE</span>
|
||||
<button class="font-jetbrains text-[12px] font-bold text-suit-black/60 hover:text-primary transition-colors">× CLOSE</button>
|
||||
</header>
|
||||
<!-- 2. Heading Band -->
|
||||
<div class="h-[120px] px-4 pt-10 pb-4 shrink-0">
|
||||
<h1 class="font-jetbrains font-bold text-[24px] text-suit-black leading-none mb-1">GESTURES & SHORTCUTS</h1>
|
||||
<p class="font-inter text-[13px] text-on-surface-variant/80">Touch gestures and keyboard equivalents.</p>
|
||||
</div>
|
||||
<!-- Scrollable Content Section -->
|
||||
<main class="flex-1 overflow-y-auto px-4 pb-8 space-y-6">
|
||||
<!-- 3a. TOUCH GESTURES -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="font-jetbrains text-[11px] font-medium tracking-widest text-on-surface-variant/60 uppercase">TOUCH GESTURES</h2>
|
||||
<div class="space-y-1">
|
||||
<!-- Row 1 -->
|
||||
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||
<div class="w-[40%] flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-suit-black" data-icon="square">square</span>
|
||||
<span class="font-jetbrains text-[13px] font-medium text-suit-black uppercase">TAP card</span>
|
||||
</div>
|
||||
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">Select / unselect for move</div>
|
||||
</div>
|
||||
<!-- Row 2 -->
|
||||
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||
<div class="w-[40%] flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-suit-black" data-icon="east">east</span>
|
||||
<span class="font-jetbrains text-[13px] font-medium text-suit-black uppercase">DRAG stack</span>
|
||||
</div>
|
||||
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">Move with translucent ghost preview</div>
|
||||
</div>
|
||||
<!-- Row 3 -->
|
||||
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||
<div class="w-[40%] flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-suit-black" data-icon="double_arrow">double_arrow</span>
|
||||
<span class="font-jetbrains text-[13px] font-medium text-suit-black uppercase">DOUBLE-TAP</span>
|
||||
</div>
|
||||
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">Auto-send to best foundation</div>
|
||||
</div>
|
||||
<!-- Row 4 -->
|
||||
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||
<div class="w-[40%] flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-suit-black" data-icon="touch_app">touch_app</span>
|
||||
<span class="font-jetbrains text-[13px] font-medium text-suit-black uppercase">LONG-PRESS</span>
|
||||
</div>
|
||||
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">Highlight all legal moves for card</div>
|
||||
</div>
|
||||
<!-- Row 5 -->
|
||||
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||
<div class="w-[40%] flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-suit-black" data-icon="south">south</span>
|
||||
<span class="font-jetbrains text-[13px] font-medium text-suit-black uppercase">SWIPE DOWN</span>
|
||||
</div>
|
||||
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">Reveal hidden action bar</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- 3b. KEYBOARD SHORTCUTS -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="font-jetbrains text-[11px] font-medium tracking-widest text-on-surface-variant/60 uppercase">KEYBOARD SHORTCUTS</h2>
|
||||
<div class="space-y-1">
|
||||
<!-- Row 1 -->
|
||||
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||
<div class="w-[40%] font-jetbrains text-[13px] font-medium text-suit-black uppercase">[U]</div>
|
||||
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">Undo last move</div>
|
||||
</div>
|
||||
<!-- Row 2 -->
|
||||
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||
<div class="w-[40%] font-jetbrains text-[13px] font-medium text-suit-black uppercase">[H]</div>
|
||||
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">Show hint</div>
|
||||
</div>
|
||||
<!-- Row 3 -->
|
||||
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||
<div class="w-[40%] font-jetbrains text-[13px] font-medium text-suit-black uppercase">[N]</div>
|
||||
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">New game</div>
|
||||
</div>
|
||||
<!-- Row 4 -->
|
||||
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||
<div class="w-[40%] font-jetbrains text-[13px] font-medium text-suit-black uppercase">[A]</div>
|
||||
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">Auto-complete (when possible)</div>
|
||||
</div>
|
||||
<!-- Row 5 -->
|
||||
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||
<div class="w-[40%] font-jetbrains text-[13px] font-medium text-suit-black uppercase">[ESC]</div>
|
||||
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">Pause / back</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<!-- 4. Footer -->
|
||||
<footer class="h-[24px] bg-surface-container border-t border-outline/20 flex items-center justify-between px-2 shrink-0">
|
||||
<div class="font-jetbrains text-[10px] text-suit-black">
|
||||
<span class="opacity-80">▌ NORMAL │ help</span>
|
||||
</div>
|
||||
<div class="font-jetbrains text-[10px] uppercase tracking-wider flex items-center gap-1">
|
||||
<span class="text-outline">PRESS</span>
|
||||
<span class="text-on-surface-variant">[ESC]</span>
|
||||
<span class="text-outline">OR TAP</span>
|
||||
<span class="text-on-surface-variant">×</span>
|
||||
<span class="text-outline">TO RETURN</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</body></html>
|
||||
|
After Width: | Height: | Size: 42 KiB |
@@ -0,0 +1,343 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="dark" lang="en"><head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>RS_TERMINAL_OS - Rusty Solitaire</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700;800&family=Inter:wght@400;500&family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
font-size: 18px;
|
||||
}
|
||||
body {
|
||||
background-color: #151515;
|
||||
color: #d0d0d0;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
overflow: hidden;
|
||||
}
|
||||
.scanline {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: rgba(26, 26, 26, 0.5);
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: #151515;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: #353535;
|
||||
}
|
||||
</style>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
"colors": {
|
||||
"on-tertiary-container": "#683476",
|
||||
"surface-dim": "#101417",
|
||||
"primary-fixed": "#c4e7ff",
|
||||
"on-error": "#690005",
|
||||
"on-secondary-fixed": "#161e00",
|
||||
"on-tertiary": "#4c195b",
|
||||
"primary-fixed-dim": "#7ed0fe",
|
||||
"outline-variant": "#3f484e",
|
||||
"tertiary": "#f7c3ff",
|
||||
"surface": "#151515",
|
||||
"tertiary-container": "#e1a3ee",
|
||||
"highlight-celebration": "#e1a3ee",
|
||||
"background": "#101417",
|
||||
"surface-container": "#202020",
|
||||
"primary-container": "#6fc2ef",
|
||||
"on-secondary-fixed-variant": "#3c4d00",
|
||||
"on-surface": "#d0d0d0",
|
||||
"inverse-on-surface": "#2d3134",
|
||||
"on-error-container": "#ffdad6",
|
||||
"surface-container-low": "#181c1f",
|
||||
"on-tertiary-fixed": "#340043",
|
||||
"on-secondary-container": "#b2c86d",
|
||||
"on-background": "#e0e3e6",
|
||||
"secondary-container": "#435401",
|
||||
"error": "#fb9fb1",
|
||||
"info": "#12cfc0",
|
||||
"on-surface-variant": "#bfc8cf",
|
||||
"warning": "#ddb26f",
|
||||
"inverse-primary": "#00668a",
|
||||
"tertiary-fixed-dim": "#f0b0fc",
|
||||
"surface-tint": "#7ed0fe",
|
||||
"suit-black": "#d0d0d0",
|
||||
"tertiary-fixed": "#fbd7ff",
|
||||
"on-secondary": "#293500",
|
||||
"on-primary-fixed": "#001e2c",
|
||||
"surface-container-highest": "#313538",
|
||||
"error-container": "#93000a",
|
||||
"surface-container-high": "#272a2d",
|
||||
"on-primary-container": "#004f6c",
|
||||
"inverse-surface": "#e0e3e6",
|
||||
"on-primary": "#003549",
|
||||
"suit-red-cb": "#6fc2ef",
|
||||
"on-primary-fixed-variant": "#004c69",
|
||||
"on-tertiary-fixed-variant": "#653173",
|
||||
"secondary-fixed": "#d5ec8c",
|
||||
"highlight-valid": "#acc267",
|
||||
"surface-variant": "#313538",
|
||||
"secondary": "#bad073",
|
||||
"secondary-fixed-dim": "#bad073",
|
||||
"outline": "#505050",
|
||||
"surface-container-lowest": "#0b0f11",
|
||||
"primary": "#a1dcff",
|
||||
"surface-bright": "#363a3d",
|
||||
"suit-red": "#fb9fb1"
|
||||
},
|
||||
"borderRadius": {
|
||||
"DEFAULT": "0px",
|
||||
"lg": "0px",
|
||||
"xl": "0px",
|
||||
"full": "0px"
|
||||
},
|
||||
"spacing": {
|
||||
"stack-overlap": "2rem",
|
||||
"touch-target-min": "48px",
|
||||
"margin-edge": "1rem",
|
||||
"gutter-card": "0.375rem",
|
||||
"action-bar-height": "64px"
|
||||
},
|
||||
"fontFamily": {
|
||||
"card-rank": ["JetBrains Mono"],
|
||||
"headline": ["JetBrains Mono"],
|
||||
"hud-timer": ["JetBrains Mono"],
|
||||
"hud-score": ["JetBrains Mono"],
|
||||
"body-md": ["Inter"],
|
||||
"label-caps": ["JetBrains Mono"]
|
||||
},
|
||||
"fontSize": {
|
||||
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
|
||||
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
|
||||
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}],
|
||||
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}]
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-surface text-on-surface h-screen flex flex-col antialiased">
|
||||
<!-- TOP BAR (32px) -->
|
||||
<header class="h-8 bg-surface-container border-b border-outline flex items-center justify-between px-4 z-50">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-primary-container font-bold">▌</span>
|
||||
<h1 class="font-headline text-[14px] font-bold tracking-tight text-on-surface">RS_TERMINAL_OS</h1>
|
||||
</div>
|
||||
<nav class="flex gap-4 font-label-caps text-[12px] uppercase tracking-widest">
|
||||
<span class="text-primary-container">[ HOME ]</span>
|
||||
<span class="text-on-surface-variant hover:text-primary transition-colors cursor-pointer">· PLAY</span>
|
||||
<span class="text-on-surface-variant hover:text-primary transition-colors cursor-pointer">· STATS</span>
|
||||
<span class="text-on-surface-variant hover:text-primary transition-colors cursor-pointer">· SETTINGS</span>
|
||||
</nav>
|
||||
<div class="flex items-center gap-3 font-label-caps text-[11px] text-on-surface-variant">
|
||||
<div class="flex items-center gap-1">
|
||||
<span>LV 12</span>
|
||||
<span class="text-outline">|</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>XP 320/500</span>
|
||||
<div class="w-[60px] h-1 bg-surface-container-highest">
|
||||
<div class="h-full bg-primary-container w-[64%]"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-outline">|</span>
|
||||
<div class="flex items-center gap-1 text-info">
|
||||
<span class="w-2 h-2 rounded-full bg-info"></span>
|
||||
<span class="uppercase">Synced</span>
|
||||
</div>
|
||||
<span class="text-outline">|</span>
|
||||
<span class="text-outline">v0.20.0</span>
|
||||
</div>
|
||||
</header>
|
||||
<!-- MAIN CONTENT AREA -->
|
||||
<main class="flex-1 flex overflow-hidden">
|
||||
<!-- LEFT PANE (40%) -->
|
||||
<section class="w-[40%] border-r border-outline flex flex-col p-8 gap-8 overflow-y-auto custom-scrollbar">
|
||||
<div class="space-y-1">
|
||||
<p class="text-outline font-label-caps text-xs">▌play.tsx</p>
|
||||
<h2 class="font-headline text-[32px] font-bold text-on-surface leading-none uppercase">Ready to play?</h2>
|
||||
<p class="text-on-surface-variant font-label-caps text-sm tracking-wide">RESUME · 12:34 ELAPSED · DRAW-3</p>
|
||||
</div>
|
||||
<button class="w-full h-24 bg-primary-container text-surface font-headline text-[24px] font-bold flex items-center justify-center gap-4 hover:brightness-110 active:scale-[0.98] transition-all">
|
||||
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 1;">play_arrow</span>
|
||||
CONTINUE GAME
|
||||
</button>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<button class="h-12 border border-outline bg-transparent text-on-surface font-label-caps text-sm hover:border-primary-container hover:text-primary-container transition-all flex items-center justify-center gap-2">
|
||||
<span class="material-symbols-outlined">add</span>
|
||||
NEW GAME
|
||||
</button>
|
||||
<button class="h-12 border border-outline bg-transparent text-on-surface font-label-caps text-sm hover:border-primary-container hover:text-primary-container transition-all flex items-center justify-center gap-2">
|
||||
<span class="material-symbols-outlined">refresh</span>
|
||||
RESTART RUN
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<p class="text-outline font-label-caps text-xs uppercase tracking-widest">Game Modes</p>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<!-- Zen -->
|
||||
<div class="aspect-square border border-outline flex flex-col items-center justify-center gap-2 hover:bg-surface-container transition-colors cursor-pointer group">
|
||||
<span class="material-symbols-outlined text-outline group-hover:text-primary-container">spa</span>
|
||||
<span class="font-label-caps text-[10px] uppercase">Zen</span>
|
||||
</div>
|
||||
<!-- Time Attack -->
|
||||
<div class="aspect-square border border-outline flex flex-col items-center justify-center gap-2 hover:bg-surface-container transition-colors cursor-pointer group">
|
||||
<span class="material-symbols-outlined text-outline group-hover:text-primary-container">timer</span>
|
||||
<span class="font-label-caps text-[10px] uppercase text-center">Time<br/>Attack</span>
|
||||
</div>
|
||||
<!-- Locked Challenge -->
|
||||
<div class="aspect-square bg-[#0d0d0d] border border-outline/30 flex flex-col items-center justify-center gap-2 relative opacity-60">
|
||||
<span class="material-symbols-outlined text-outline">lock</span>
|
||||
<span class="font-label-caps text-[10px] uppercase">Challenge</span>
|
||||
<div class="absolute -top-2 -right-2 bg-warning text-surface px-1 py-0.5 text-[8px] font-bold">LV 5</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- VISUAL DECORATION (IMAGE PLACEHOLDER) -->
|
||||
<div class="mt-auto pt-8">
|
||||
<div class="w-full h-40 border border-outline overflow-hidden">
|
||||
<img class="w-full h-full object-cover opacity-40 grayscale hover:grayscale-0 transition-all duration-700" data-alt="A dark, high-contrast digital art piece showing an abstract terminal interface with glowing cyan scanlines and retro-futuristic grid patterns. The composition is geometric and minimalist, following a synthwave aesthetic with deep black backgrounds and crisp cyan light elements. The lighting is moody and artificial, suggesting a high-performance computer screen in a dimly lit server room. Professional, sharp-edged UI design style." src="https://lh3.googleusercontent.com/aida-public/AB6AXuAet8SrRWSacZfwd8ISRQdDC7CDGixBwRnPAVMmMcjbifq1jnHSzCGWgSSL6YPSRfCkLNWr91BxTzV4zigGjMBLlk7rCLo5I7X7F6ydinDrKJVqZkRbvHJeSo90BPANoQwZtzPvhKXVEA9C2DbBaj8KPR4ObCo24Mj25NXPvGNThOE-3BSpuU6MPC-hrUMPVCPJpZnJdI_OmSz8mT021vjTxFERN12S1PFOzXKmNUDleoTDIat-8UifyKmKg4eKilecrBW6sFqaBw"/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- CENTER PANE (30%) -->
|
||||
<section class="w-[30%] border-r border-outline flex flex-col p-8 gap-8 overflow-y-auto custom-scrollbar">
|
||||
<div class="space-y-1">
|
||||
<p class="text-outline font-label-caps text-xs">▌daily.json</p>
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="font-headline text-[18px] font-bold text-on-surface">MAY 07 · 2026</h3>
|
||||
<span class="bg-warning/20 text-warning px-2 py-1 text-[10px] font-bold border border-warning/40">EXPIRES 11:42:30</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-surface-container p-6 border border-outline space-y-4">
|
||||
<div class="space-y-1">
|
||||
<p class="text-on-surface-variant font-label-caps text-[10px] uppercase tracking-tighter">Current Seed</p>
|
||||
<p class="font-headline text-[24px] font-extrabold text-highlight-valid">#2024-127</p>
|
||||
</div>
|
||||
<button class="w-full py-3 bg-primary-container text-surface font-label-caps text-xs font-bold uppercase tracking-widest hover:brightness-110 active:scale-95 transition-all">
|
||||
▶ Attempt Today
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<p class="text-outline font-label-caps text-xs uppercase tracking-widest">Global Standings</p>
|
||||
<div class="space-y-1 text-xs font-label-caps">
|
||||
<div class="flex justify-between py-2 border-b border-outline/30 text-highlight-valid">
|
||||
<span>01 │ swift_jaguar</span>
|
||||
<span>02:47</span>
|
||||
</div>
|
||||
<div class="flex justify-between py-2 border-b border-outline/30 text-on-surface-variant">
|
||||
<span>02 │ pixel_drifter</span>
|
||||
<span>03:12</span>
|
||||
</div>
|
||||
<div class="flex justify-between py-2 border-b border-outline/30 text-on-surface-variant">
|
||||
<span>03 │ null_ptr</span>
|
||||
<span>03:15</span>
|
||||
</div>
|
||||
<div class="flex justify-between py-2 border-b border-outline/30 text-on-surface-variant">
|
||||
<span>04 │ core_dump_88</span>
|
||||
<span>03:44</span>
|
||||
</div>
|
||||
<div class="flex justify-between py-2 text-primary-container bg-primary-container/10 px-2 -mx-2">
|
||||
<span>12 │ YOU (anon)</span>
|
||||
<span>--:--</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- RIGHT PANE (30%) -->
|
||||
<section class="w-[30%] flex flex-col p-8 gap-8 overflow-y-auto custom-scrollbar">
|
||||
<div class="space-y-1">
|
||||
<p class="text-outline font-label-caps text-xs">▌stats.log</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="border border-outline p-4 space-y-1">
|
||||
<p class="text-on-surface-variant text-[10px] uppercase">Games</p>
|
||||
<p class="font-hud-score text-[28px] text-on-surface">247</p>
|
||||
</div>
|
||||
<div class="border border-outline p-4 space-y-1 text-highlight-valid">
|
||||
<p class="text-on-surface-variant text-[10px] uppercase">Win Rate</p>
|
||||
<p class="font-hud-score text-[28px]">61%</p>
|
||||
</div>
|
||||
<div class="border border-outline p-4 space-y-1">
|
||||
<p class="text-on-surface-variant text-[10px] uppercase">Best Time</p>
|
||||
<p class="font-hud-score text-[28px]">01:54</p>
|
||||
</div>
|
||||
<div class="border border-outline p-4 space-y-1 text-primary-container">
|
||||
<p class="text-on-surface-variant text-[10px] uppercase">Streak</p>
|
||||
<p class="font-hud-score text-[28px]">7</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<p class="text-outline font-label-caps text-xs uppercase tracking-widest">Achievements (8/19)</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<!-- Filled Cyan Dots -->
|
||||
<div class="w-3 h-3 bg-primary-container"></div>
|
||||
<div class="w-3 h-3 bg-primary-container"></div>
|
||||
<div class="w-3 h-3 bg-primary-container"></div>
|
||||
<div class="w-3 h-3 bg-primary-container"></div>
|
||||
<div class="w-3 h-3 bg-primary-container"></div>
|
||||
<div class="w-3 h-3 bg-primary-container"></div>
|
||||
<div class="w-3 h-3 bg-primary-container"></div>
|
||||
<div class="w-3 h-3 bg-primary-container"></div>
|
||||
<!-- Empty Dots -->
|
||||
<div class="w-3 h-3 border border-outline"></div>
|
||||
<div class="w-3 h-3 border border-outline"></div>
|
||||
<div class="w-3 h-3 border border-outline"></div>
|
||||
<div class="w-3 h-3 border border-outline"></div>
|
||||
<div class="w-3 h-3 border border-outline"></div>
|
||||
<div class="w-3 h-3 border border-outline"></div>
|
||||
<div class="w-3 h-3 border border-outline"></div>
|
||||
<div class="w-3 h-3 border border-outline"></div>
|
||||
<div class="w-3 h-3 border border-outline"></div>
|
||||
<div class="w-3 h-3 border border-outline"></div>
|
||||
<div class="w-3 h-3 border border-outline"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-auto border border-outline bg-surface-container p-4 flex items-center justify-between hover:border-primary-container transition-colors cursor-pointer group">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 bg-primary-container text-surface flex items-center justify-center font-bold text-lg">RS</div>
|
||||
<div class="space-y-0.5">
|
||||
<p class="text-on-surface font-bold text-xs">anonymous@local</p>
|
||||
<p class="text-on-surface-variant text-[10px]">Session: Active</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="material-symbols-outlined text-primary-container group-hover:translate-x-1 transition-transform">arrow_forward</span>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<!-- BOTTOM BAR (24px) -->
|
||||
<footer class="h-6 bg-surface-container border-t border-outline flex items-center justify-between px-4 text-[10px] font-label-caps">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-primary-container">▌ NORMAL</span>
|
||||
<span class="text-outline">│</span>
|
||||
<span class="text-on-surface-variant">~/rusty-solitaire/home</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 text-on-surface-variant">
|
||||
<div class="flex items-center gap-1"><span class="text-primary-container">[SPACE]</span> play</div>
|
||||
<div class="flex items-center gap-1"><span class="text-primary-container">[D]</span> daily</div>
|
||||
<div class="flex items-center gap-1"><span class="text-primary-container">[S]</span> settings</div>
|
||||
<div class="flex items-center gap-1"><span class="text-primary-container">[?]</span> help</div>
|
||||
</div>
|
||||
<div class="text-outline">
|
||||
2026-05-07 17:42 EDT
|
||||
</div>
|
||||
</footer>
|
||||
<!-- GLOBAL SCANLINE EFFECT -->
|
||||
<div class="fixed inset-0 pointer-events-none z-[100] overflow-hidden opacity-10">
|
||||
<div class="absolute inset-0" style="background: repeating-linear-gradient(0deg, #151515, #151515 2px, #202020 4px);"></div>
|
||||
</div>
|
||||
</body></html>
|
||||
|
After Width: | Height: | Size: 47 KiB |
@@ -0,0 +1,225 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="dark" lang="en"><head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>Rusty Solitaire - Main Menu</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@400&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
"colors": {
|
||||
"outline": "#505050",
|
||||
"suit-red-cb": "#6fc2ef",
|
||||
"suit-black": "#d0d0d0",
|
||||
"surface-container-high": "#272a2d",
|
||||
"primary-fixed": "#c4e7ff",
|
||||
"on-secondary-container": "#b2c86d",
|
||||
"secondary-fixed": "#d5ec8c",
|
||||
"on-tertiary-container": "#683476",
|
||||
"surface-tint": "#7ed0fe",
|
||||
"background": "#101417",
|
||||
"primary-container": "#6fc2ef",
|
||||
"inverse-surface": "#e0e3e6",
|
||||
"highlight-celebration": "#e1a3ee",
|
||||
"surface-container-low": "#181c1f",
|
||||
"on-surface": "#d0d0d0",
|
||||
"primary": "#a1dcff",
|
||||
"on-tertiary-fixed": "#340043",
|
||||
"secondary-container": "#435401",
|
||||
"inverse-primary": "#00668a",
|
||||
"tertiary-fixed": "#fbd7ff",
|
||||
"surface-bright": "#363a3d",
|
||||
"on-secondary-fixed-variant": "#3c4d00",
|
||||
"warning": "#ddb26f",
|
||||
"tertiary-container": "#e1a3ee",
|
||||
"suit-red": "#fb9fb1",
|
||||
"primary-fixed-dim": "#7ed0fe",
|
||||
"info": "#12cfc0",
|
||||
"on-primary-fixed": "#001e2c",
|
||||
"surface-container-lowest": "#0b0f11",
|
||||
"error": "#fb9fb1",
|
||||
"surface-variant": "#313538",
|
||||
"on-error": "#690005",
|
||||
"surface": "#151515",
|
||||
"surface-container": "#202020",
|
||||
"on-primary-container": "#004f6c",
|
||||
"inverse-on-surface": "#2d3134",
|
||||
"on-primary-fixed-variant": "#004c69",
|
||||
"on-secondary": "#293500",
|
||||
"error-container": "#93000a",
|
||||
"secondary": "#bad073",
|
||||
"tertiary": "#f7c3ff",
|
||||
"outline-variant": "#3f484e",
|
||||
"on-secondary-fixed": "#161e00",
|
||||
"secondary-fixed-dim": "#bad073",
|
||||
"surface-container-highest": "#313538",
|
||||
"on-surface-variant": "#bfc8cf",
|
||||
"tertiary-fixed-dim": "#f0b0fc",
|
||||
"on-tertiary-fixed-variant": "#653173",
|
||||
"on-error-container": "#ffdad6",
|
||||
"on-primary": "#003549",
|
||||
"on-background": "#e0e3e6",
|
||||
"surface-dim": "#101417",
|
||||
"on-tertiary": "#4c195b",
|
||||
"highlight-valid": "#acc267"
|
||||
},
|
||||
"borderRadius": {
|
||||
"DEFAULT": "0.125rem",
|
||||
"lg": "0.25rem",
|
||||
"xl": "0.5rem",
|
||||
"full": "0.75rem"
|
||||
},
|
||||
"spacing": {
|
||||
"margin-edge": "1rem",
|
||||
"touch-target-min": "48px",
|
||||
"stack-overlap": "2rem",
|
||||
"action-bar-height": "64px",
|
||||
"gutter-card": "0.375rem"
|
||||
},
|
||||
"fontFamily": {
|
||||
"hud-timer": ["JetBrains Mono"],
|
||||
"headline": ["JetBrains Mono"],
|
||||
"label-caps": ["JetBrains Mono"],
|
||||
"hud-score": ["JetBrains Mono"],
|
||||
"body-md": ["Inter"],
|
||||
"card-rank": ["JetBrains Mono"]
|
||||
},
|
||||
"fontSize": {
|
||||
"hud-timer": ["16px", { "lineHeight": "24px", "fontWeight": "400" }],
|
||||
"headline": ["28px", { "lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700" }],
|
||||
"label-caps": ["12px", { "lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500" }],
|
||||
"hud-score": ["24px", { "lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700" }],
|
||||
"body-md": ["16px", { "lineHeight": "24px", "fontWeight": "400" }],
|
||||
"card-rank": ["18px", { "lineHeight": "18px", "fontWeight": "700" }]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.scanline {
|
||||
background: linear-gradient(to bottom, rgba(255,255,255,0), rgba(255,255,255,0) 50%, rgba(0,0,0,0.1) 50%, rgba(0,0,0,0.1));
|
||||
background-size: 100% 4px;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
body {
|
||||
min-height: max(884px, 100dvh);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-surface text-on-surface font-hud-timer min-h-screen flex flex-col relative overflow-hidden">
|
||||
<!-- Subtle CRT scanline overlay -->
|
||||
<div class="absolute inset-0 pointer-events-none scanline opacity-20 z-0"></div>
|
||||
<!-- Status Bar Zone -->
|
||||
<div class="h-6 w-full flex justify-end items-center px-margin-edge pt-2 z-10 relative">
|
||||
<div class="w-2 h-2 rounded-full bg-info"></div>
|
||||
</div>
|
||||
<!-- Header -->
|
||||
<header class="px-margin-edge pt-4 pb-6 flex justify-between items-center z-10 relative">
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="font-headline text-headline text-on-surface">▌RUSTY SOLITAIRE</span>
|
||||
<div class="w-2 h-6 bg-primary-container inline-block ml-1 animate-pulse"></div>
|
||||
</div>
|
||||
<div class="bg-surface-container px-3 py-1 flex items-center gap-2 border border-outline">
|
||||
<span class="font-label-caps text-label-caps text-on-surface">LV 12</span>
|
||||
<div class="w-2 h-2 rounded-full bg-highlight-celebration"></div>
|
||||
</div>
|
||||
</header>
|
||||
<!-- Main Content Canvas -->
|
||||
<main class="flex-1 px-margin-edge flex flex-col gap-8 z-10 relative pb-24 overflow-y-auto">
|
||||
<!-- XP Section -->
|
||||
<section class="flex flex-col gap-2">
|
||||
<div class="w-full h-1 bg-surface-container border border-outline relative">
|
||||
<div class="absolute top-0 left-0 h-full bg-primary-container w-[64%]"></div>
|
||||
</div>
|
||||
<div class="font-label-caps text-label-caps text-on-surface-variant text-right">
|
||||
320 / 500 XP
|
||||
</div>
|
||||
</section>
|
||||
<!-- Primary Action -->
|
||||
<section class="flex flex-col gap-2">
|
||||
<button class="w-full h-[56px] bg-primary-container text-surface flex items-center justify-center gap-2 hover:bg-surface-tint transition-colors duration-120">
|
||||
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 1;">play_arrow</span>
|
||||
<span class="font-label-caps text-[14px] uppercase tracking-widest font-bold">PLAY</span>
|
||||
</button>
|
||||
<div class="font-label-caps text-label-caps text-on-surface-variant text-center">
|
||||
RESUME LAST GAME · 12:34 ELAPSED
|
||||
</div>
|
||||
</section>
|
||||
<!-- Daily Challenge Tile -->
|
||||
<section>
|
||||
<div class="bg-surface-container border border-outline p-4 flex justify-between items-center hover:bg-surface-container-high transition-colors cursor-pointer group">
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="font-label-caps text-label-caps text-primary">DAILY CHALLENGE</span>
|
||||
<span class="font-body-md text-body-md text-on-surface">DRAW-3 · SEED #2024-127</span>
|
||||
<div class="inline-flex">
|
||||
<span class="bg-surface px-2 py-0.5 border border-warning text-warning font-label-caps text-[10px]">EXPIRES 11:42:30</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="material-symbols-outlined text-primary group-hover:translate-x-1 transition-transform">chevron_right</span>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Special Modes Grid -->
|
||||
<section class="flex flex-col gap-4">
|
||||
<h2 class="font-label-caps text-label-caps text-on-surface-variant">SPECIAL MODES</h2>
|
||||
<div class="grid grid-cols-3 gap-gutter-card">
|
||||
<!-- ZEN -->
|
||||
<button class="aspect-square bg-surface border border-outline flex flex-col items-center justify-center gap-2 hover:border-primary hover:text-primary transition-colors text-on-surface">
|
||||
<span class="material-symbols-outlined text-[32px]">self_improvement</span>
|
||||
<span class="font-label-caps text-label-caps">ZEN</span>
|
||||
</button>
|
||||
<!-- TIME ATTACK -->
|
||||
<button class="aspect-square bg-surface border border-outline flex flex-col items-center justify-center gap-2 hover:border-primary hover:text-primary transition-colors text-on-surface">
|
||||
<span class="material-symbols-outlined text-[32px]">timer</span>
|
||||
<span class="font-label-caps text-label-caps">TIME ATTACK</span>
|
||||
</button>
|
||||
<!-- CHALLENGE (Locked) -->
|
||||
<button class="aspect-square bg-[#0d0d0d] border border-surface-container-high flex flex-col items-center justify-center gap-2 text-on-surface-variant opacity-75 cursor-not-allowed relative">
|
||||
<span class="material-symbols-outlined text-[32px]">lock</span>
|
||||
<span class="font-label-caps text-label-caps">CHALLENGE</span>
|
||||
<div class="absolute top-2 right-2 bg-surface px-1 py-0.5 border border-warning text-warning font-label-caps text-[10px]">
|
||||
LV 5
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Secondary Nav Grid -->
|
||||
<section class="grid grid-cols-2 gap-y-4 gap-x-6 pb-6">
|
||||
<button class="flex items-center gap-3 h-[56px] border-l-2 border-outline pl-3 hover:border-primary hover:text-primary transition-colors text-on-surface justify-start">
|
||||
<span class="material-symbols-outlined">bar_chart</span>
|
||||
<span class="font-label-caps text-label-caps">STATS</span>
|
||||
</button>
|
||||
<button class="flex items-center gap-3 h-[56px] border-l-2 border-outline pl-3 hover:border-primary hover:text-primary transition-colors text-on-surface justify-start relative">
|
||||
<span class="material-symbols-outlined">emoji_events</span>
|
||||
<span class="font-label-caps text-label-caps">ACHIEVEMENTS</span>
|
||||
<div class="absolute right-2 top-1/2 -translate-y-1/2 w-2 h-2 rounded-full bg-highlight-celebration"></div>
|
||||
</button>
|
||||
<button class="flex items-center gap-3 h-[56px] border-l-2 border-outline pl-3 hover:border-primary hover:text-primary transition-colors text-on-surface justify-start">
|
||||
<span class="material-symbols-outlined">format_list_numbered</span>
|
||||
<span class="font-label-caps text-label-caps">LEADERBOARD</span>
|
||||
</button>
|
||||
<button class="flex items-center gap-3 h-[56px] border-l-2 border-outline pl-3 hover:border-primary hover:text-primary transition-colors text-on-surface justify-start">
|
||||
<span class="material-symbols-outlined">account_circle</span>
|
||||
<span class="font-label-caps text-label-caps">PROFILE</span>
|
||||
</button>
|
||||
</section>
|
||||
<!-- Footer Links -->
|
||||
<footer class="flex flex-col items-center gap-4 mt-auto">
|
||||
<div class="flex items-center gap-4 font-label-caps text-label-caps text-primary cursor-pointer hover:text-surface-tint">
|
||||
<span>SETTINGS</span>
|
||||
<span class="text-on-surface-variant">·</span>
|
||||
<span>HELP</span>
|
||||
</div>
|
||||
<div class="font-label-caps text-[10px] text-on-surface-variant text-center opacity-60">
|
||||
v0.20.0 — TERMINAL THEME · BUILD 2026.05
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
</body></html>
|
||||
|
After Width: | Height: | Size: 28 KiB |
@@ -0,0 +1,315 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="dark" lang="en"><head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>Rusty Solitaire - Leaderboard</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700;800&family=Inter:wght@400;500&family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
body {
|
||||
background-color: #151515;
|
||||
color: #e0e3e6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.scanline-overlay {
|
||||
background: linear-gradient(to bottom, rgba(21, 21, 21, 0) 50%, rgba(26, 26, 26, 0.2) 50%);
|
||||
background-size: 100% 4px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.terminal-glow {
|
||||
box-shadow: 0 0 10px rgba(111, 194, 239, 0.1);
|
||||
}
|
||||
</style>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
"colors": {
|
||||
"outline": "#505050",
|
||||
"on-surface-variant": "#bfc8cf",
|
||||
"secondary-container": "#435401",
|
||||
"surface-container-lowest": "#0b0f11",
|
||||
"primary": "#a1dcff",
|
||||
"secondary-fixed": "#d5ec8c",
|
||||
"on-secondary-fixed": "#161e00",
|
||||
"on-error": "#690005",
|
||||
"inverse-primary": "#00668a",
|
||||
"surface-container": "#202020",
|
||||
"highlight-valid": "#acc267",
|
||||
"suit-black": "#d0d0d0",
|
||||
"on-secondary-fixed-variant": "#3c4d00",
|
||||
"on-primary-fixed": "#001e2c",
|
||||
"on-tertiary-fixed-variant": "#653173",
|
||||
"primary-fixed": "#c4e7ff",
|
||||
"inverse-on-surface": "#2d3134",
|
||||
"secondary-fixed-dim": "#bad073",
|
||||
"on-secondary": "#293500",
|
||||
"on-surface": "#e0e3e6",
|
||||
"on-tertiary-container": "#683476",
|
||||
"secondary": "#bad073",
|
||||
"surface-bright": "#363a3d",
|
||||
"tertiary-container": "#e1a3ee",
|
||||
"surface-variant": "#313538",
|
||||
"suit-red": "#fb9fb1",
|
||||
"primary-fixed-dim": "#7ed0fe",
|
||||
"surface-container-low": "#181c1f",
|
||||
"surface": "#151515",
|
||||
"suit-red-cb": "#6fc2ef",
|
||||
"on-primary": "#003549",
|
||||
"primary-container": "#6fc2ef",
|
||||
"on-background": "#e0e3e6",
|
||||
"tertiary": "#f7c3ff",
|
||||
"surface-container-highest": "#313538",
|
||||
"tertiary-fixed-dim": "#f0b0fc",
|
||||
"tertiary-fixed": "#fbd7ff",
|
||||
"info": "#12cfc0",
|
||||
"error": "#fb9fb1",
|
||||
"warning": "#ddb26f",
|
||||
"on-primary-container": "#004f6c",
|
||||
"surface-container-high": "#272a2d",
|
||||
"inverse-surface": "#e0e3e6",
|
||||
"error-container": "#93000a",
|
||||
"on-tertiary-fixed": "#340043",
|
||||
"surface-tint": "#7ed0fe",
|
||||
"on-tertiary": "#4c195b",
|
||||
"background": "#101417",
|
||||
"on-error-container": "#ffdad6",
|
||||
"on-secondary-container": "#b2c86d",
|
||||
"outline-variant": "#3f484e",
|
||||
"highlight-celebration": "#e1a3ee",
|
||||
"surface-dim": "#101417",
|
||||
"on-primary-fixed-variant": "#004c69"
|
||||
},
|
||||
"borderRadius": {
|
||||
"DEFAULT": "0.125rem",
|
||||
"lg": "0.25rem",
|
||||
"xl": "0.5rem",
|
||||
"full": "0.75rem"
|
||||
},
|
||||
"spacing": {
|
||||
"touch-target-min": "48px",
|
||||
"action-bar-height": "64px",
|
||||
"stack-overlap": "2rem",
|
||||
"gutter-card": "0.375rem",
|
||||
"margin-edge": "1rem"
|
||||
},
|
||||
"fontFamily": {
|
||||
"card-rank": ["JetBrains Mono"],
|
||||
"body-md": ["Inter"],
|
||||
"headline": ["JetBrains Mono"],
|
||||
"label-caps": ["JetBrains Mono"],
|
||||
"hud-timer": ["JetBrains Mono"],
|
||||
"hud-score": ["JetBrains Mono"]
|
||||
},
|
||||
"fontSize": {
|
||||
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
|
||||
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
|
||||
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
|
||||
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}]
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
min-height: max(884px, 100dvh);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="font-body-md overflow-hidden h-[844px] w-[390px] mx-auto relative border-x border-outline/20">
|
||||
<div class="scanline-overlay absolute inset-0 z-0"></div>
|
||||
<!-- Top AppBar (Identity Anchor) -->
|
||||
<header class="fixed top-0 w-full h-action-bar-height z-50 flex items-center px-margin-edge justify-between bg-surface dark:bg-surface text-primary dark:text-primary border-b border-outline dark:border-outline">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-primary">terminal</span>
|
||||
<h1 class="font-headline text-headline text-primary dark:text-primary uppercase tracking-tighter">Rusty Solitaire</h1>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="material-symbols-outlined text-on-surface-variant hover:bg-surface-variant transition-colors duration-120 p-2 rounded-lg cursor-pointer">sync</span>
|
||||
</div>
|
||||
</header>
|
||||
<main class="pt-[64px] h-[calc(100%-64px)] flex flex-col z-10 relative">
|
||||
<!-- Pseudo Status Bar -->
|
||||
<div class="h-[32px] bg-surface-container flex items-center justify-between px-4 font-label-caps text-[10px] tracking-tight">
|
||||
<div class="text-[#a0a0a0]">▌leaderboard.tsx</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="flex items-center gap-1">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-info"></span>
|
||||
<span class="text-on-surface-variant">SYNCED</span>
|
||||
</span>
|
||||
<span class="text-outline">v0.20.0</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tab Strip -->
|
||||
<nav class="h-[40px] bg-[#1a1a1a] border-b border-[#353535] flex items-center">
|
||||
<div class="flex-1 flex flex-col items-center justify-center relative">
|
||||
<span class="font-label-caps text-[11px] text-[#6fc2ef]">[ TODAY ]</span>
|
||||
<div class="absolute bottom-0 w-full h-[2px] bg-[#6fc2ef]"></div>
|
||||
</div>
|
||||
<div class="flex-1 flex items-center justify-center">
|
||||
<span class="font-label-caps text-[11px] text-[#a0a0a0]">WEEK</span>
|
||||
</div>
|
||||
<div class="flex-1 flex items-center justify-center">
|
||||
<span class="font-label-caps text-[11px] text-[#a0a0a0]">ALL-TIME</span>
|
||||
</div>
|
||||
<div class="flex-1 flex items-center justify-center">
|
||||
<span class="font-label-caps text-[11px] text-[#a0a0a0]">FRIENDS</span>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="flex-1 overflow-y-auto px-margin-edge pt-4 space-y-4 pb-[88px]">
|
||||
<!-- Hero Podium Card -->
|
||||
<section class="h-[120px] bg-surface-container border border-[#353535] rounded-lg p-2 flex flex-col justify-between">
|
||||
<div class="font-label-caps text-[10px] text-[#a0a0a0]">TOP 3 · TODAY</div>
|
||||
<div class="flex gap-2 items-end justify-between flex-1 mt-1">
|
||||
<!-- 2nd -->
|
||||
<div class="flex-1 border border-[#a0a0a0] h-full rounded flex flex-col items-center justify-center relative py-1">
|
||||
<span class="font-card-rank text-[16px] text-[#a0a0a0]">02</span>
|
||||
<span class="text-[9px] font-mono text-[#d0d0d0] truncate w-full text-center px-1">base16_fan</span>
|
||||
<span class="text-[10px] font-mono text-[#a0a0a0]">03:12</span>
|
||||
</div>
|
||||
<!-- 1st -->
|
||||
<div class="flex-[1.2] border border-warning h-[110%] mb-[-2px] rounded-lg bg-surface flex flex-col items-center justify-center relative py-1 terminal-glow">
|
||||
<span class="absolute top-1 right-1 text-warning material-symbols-outlined text-[14px]">star</span>
|
||||
<span class="font-card-rank text-[24px] text-warning leading-none">01</span>
|
||||
<span class="text-[11px] font-mono text-[#d0d0d0] font-bold truncate w-full text-center px-1">swift_jaguar</span>
|
||||
<span class="text-[12px] font-mono text-[#d0d0d0]">02:47</span>
|
||||
</div>
|
||||
<!-- 3rd -->
|
||||
<div class="flex-1 border border-[#7a5d3b] h-full rounded flex flex-col items-center justify-center relative py-1">
|
||||
<span class="font-card-rank text-[16px] text-[#7a5d3b]">03</span>
|
||||
<span class="text-[9px] font-mono text-[#d0d0d0] truncate w-full text-center px-1">cli_player</span>
|
||||
<span class="text-[10px] font-mono text-[#a0a0a0]">03:54</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Search/Filter Row -->
|
||||
<div class="flex items-center gap-2 h-[40px]">
|
||||
<div class="px-3 h-8 border border-outline rounded flex items-center justify-center bg-surface-container-low">
|
||||
<span class="font-label-caps text-[10px] text-[#6fc2ef]">[ ALL TIMES ]</span>
|
||||
</div>
|
||||
<div class="flex-1 h-8 border border-outline rounded flex items-center px-2 bg-surface gap-2">
|
||||
<span class="font-mono text-[12px] text-outline">/ search players</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Leaderboard List -->
|
||||
<div class="space-y-0.5 font-mono text-[12px]">
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between px-2 pb-1 border-b border-outline/20 text-outline text-[10px] uppercase font-bold tracking-widest">
|
||||
<span>Rank & User</span>
|
||||
<span>Time</span>
|
||||
</div>
|
||||
<!-- Rank 04 -->
|
||||
<div class="flex items-center justify-between px-2 py-2 border-b border-[#353535]">
|
||||
<div class="flex gap-4">
|
||||
<span class="text-[#a0a0a0] w-8">004</span>
|
||||
<span class="text-on-surface">tablejockey</span>
|
||||
</div>
|
||||
<span class="text-[#a0a0a0]">04:01</span>
|
||||
</div>
|
||||
<!-- Rank 05 -->
|
||||
<div class="flex items-center justify-between px-2 py-2 border-b border-[#353535]">
|
||||
<div class="flex gap-4">
|
||||
<span class="text-[#a0a0a0] w-8">005</span>
|
||||
<span class="text-on-surface">vim_motions</span>
|
||||
</div>
|
||||
<span class="text-[#a0a0a0]">04:05</span>
|
||||
</div>
|
||||
<!-- Rank 06 -->
|
||||
<div class="flex items-center justify-between px-2 py-2 border-b border-[#353535]">
|
||||
<div class="flex gap-4">
|
||||
<span class="text-[#a0a0a0] w-8">006</span>
|
||||
<span class="text-on-surface">tmux_lover</span>
|
||||
</div>
|
||||
<span class="text-[#a0a0a0]">04:18</span>
|
||||
</div>
|
||||
<!-- Rank 07 -->
|
||||
<div class="flex items-center justify-between px-2 py-2 border-b border-[#353535]">
|
||||
<div class="flex gap-4">
|
||||
<span class="text-[#a0a0a0] w-8">007</span>
|
||||
<span class="text-on-surface">nvim_dotfiles</span>
|
||||
</div>
|
||||
<span class="text-[#a0a0a0]">04:23</span>
|
||||
</div>
|
||||
<!-- Rank 08 -->
|
||||
<div class="flex items-center justify-between px-2 py-2 border-b border-[#353535]">
|
||||
<div class="flex gap-4">
|
||||
<span class="text-[#a0a0a0] w-8">008</span>
|
||||
<span class="text-on-surface">dark_theme</span>
|
||||
</div>
|
||||
<span class="text-[#a0a0a0]">04:31</span>
|
||||
</div>
|
||||
<!-- Spacer for truncated view -->
|
||||
<div class="flex justify-center py-2 text-outline/30 tracking-[1em]">...</div>
|
||||
<!-- YOU (Rank 17) -->
|
||||
<div class="flex items-center justify-between px-2 py-2 bg-[#1f3a4a]/30 border border-[#6fc2ef]/40 rounded-sm">
|
||||
<div class="flex gap-4">
|
||||
<span class="text-[#6fc2ef] w-8 font-bold">▶ 017</span>
|
||||
<span class="text-[#6fc2ef] font-bold">anonymous (YOU)</span>
|
||||
</div>
|
||||
<span class="text-[#6fc2ef] font-bold">04:12</span>
|
||||
</div>
|
||||
<!-- Rank 18 -->
|
||||
<div class="flex items-center justify-between px-2 py-2 border-b border-[#353535]">
|
||||
<div class="flex gap-4">
|
||||
<span class="text-[#a0a0a0] w-8">018</span>
|
||||
<span class="text-on-surface">bash_brawler</span>
|
||||
</div>
|
||||
<span class="text-[#a0a0a0]">05:01</span>
|
||||
</div>
|
||||
<!-- Rank 19 -->
|
||||
<div class="flex items-center justify-between px-2 py-2 border-b border-[#353535]">
|
||||
<div class="flex gap-4">
|
||||
<span class="text-[#a0a0a0] w-8">019</span>
|
||||
<span class="text-on-surface">curl_master</span>
|
||||
</div>
|
||||
<span class="text-[#a0a0a0]">05:14</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- CLI Style Footer -->
|
||||
<footer class="fixed bottom-0 w-full h-[24px] bg-[#202020] border-t border-[#353535] px-2 flex items-center justify-between font-mono text-[9px] z-50">
|
||||
<div class="text-[#a0a0a0]">
|
||||
<span class="text-info font-bold">▌</span> NORMAL │ leaderboard
|
||||
</div>
|
||||
<div class="text-[#a0a0a0] flex gap-3">
|
||||
<span>[1-4] tab</span>
|
||||
<span>[/] search</span>
|
||||
<span>[ESC] back</span>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Shared Component: BottomNavBar -->
|
||||
<nav class="fixed bottom-[24px] w-full h-action-bar-height z-50 flex justify-around items-center bg-surface-container dark:bg-surface-container border-t border-outline dark:border-outline">
|
||||
<button class="flex flex-col items-center justify-center text-on-surface-variant dark:text-on-surface-variant p-2 hover:text-primary dark:hover:text-primary transition-all duration-120 ease-linear active:bg-surface-container-highest">
|
||||
<span class="material-symbols-outlined">playing_cards</span>
|
||||
<span class="font-label-caps text-label-caps">DEAL [F1]</span>
|
||||
</button>
|
||||
<button class="flex flex-col items-center justify-center text-on-surface-variant dark:text-on-surface-variant p-2 hover:text-primary dark:hover:text-primary transition-all duration-120 ease-linear active:bg-surface-container-highest">
|
||||
<span class="material-symbols-outlined">undo</span>
|
||||
<span class="font-label-caps text-label-caps">UNDO [Z]</span>
|
||||
</button>
|
||||
<button class="flex flex-col items-center justify-center text-on-surface-variant dark:text-on-surface-variant p-2 hover:text-primary dark:hover:text-primary transition-all duration-120 ease-linear active:bg-surface-container-highest">
|
||||
<span class="material-symbols-outlined">lightbulb</span>
|
||||
<span class="font-label-caps text-label-caps">HINT [H]</span>
|
||||
</button>
|
||||
<button class="flex flex-col items-center justify-center bg-primary-container dark:bg-primary-container text-on-primary-container dark:text-on-primary-container rounded-none p-2 transition-all duration-120 ease-linear active:bg-surface-container-highest">
|
||||
<span class="material-symbols-outlined">analytics</span>
|
||||
<span class="font-label-caps text-label-caps">STATS [S]</span>
|
||||
</button>
|
||||
<button class="flex flex-col items-center justify-center text-on-surface-variant dark:text-on-surface-variant p-2 hover:text-primary dark:hover:text-primary transition-all duration-120 ease-linear active:bg-surface-container-highest">
|
||||
<span class="material-symbols-outlined">menu</span>
|
||||
<span class="font-label-caps text-label-caps">MENU [ESC]</span>
|
||||
</button>
|
||||
</nav>
|
||||
</main>
|
||||
</body></html>
|
||||
|
After Width: | Height: | Size: 43 KiB |
@@ -0,0 +1,259 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="dark" lang="en"><head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>ROOT@SOLITAIRE:~ | LEVEL UP</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@400;500&family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
}
|
||||
.scanline-overlay {
|
||||
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06));
|
||||
background-size: 100% 2px, 3px 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
.card-glow {
|
||||
box-shadow: 0 0 24px rgba(225, 163, 238, 0.25);
|
||||
}
|
||||
</style>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
"colors": {
|
||||
"tertiary-fixed": "#fbd7ff",
|
||||
"secondary-container": "#435401",
|
||||
"on-tertiary-fixed": "#340043",
|
||||
"inverse-surface": "#e0e3e6",
|
||||
"tertiary-container": "#e1a3ee",
|
||||
"background": "#101417",
|
||||
"on-primary": "#003549",
|
||||
"info": "#12cfc0",
|
||||
"surface-container-highest": "#313538",
|
||||
"secondary-fixed": "#d5ec8c",
|
||||
"secondary-fixed-dim": "#bad073",
|
||||
"on-surface-variant": "#bfc8cf",
|
||||
"on-secondary": "#293500",
|
||||
"on-tertiary-fixed-variant": "#653173",
|
||||
"surface-container-low": "#181c1f",
|
||||
"surface-container-high": "#272a2d",
|
||||
"secondary": "#bad073",
|
||||
"outline-variant": "#3f484e",
|
||||
"on-surface": "#e0e3e6",
|
||||
"surface-tint": "#7ed0fe",
|
||||
"on-tertiary": "#4c195b",
|
||||
"on-secondary-fixed": "#161e00",
|
||||
"primary-fixed": "#c4e7ff",
|
||||
"on-tertiary-container": "#683476",
|
||||
"on-secondary-container": "#b2c86d",
|
||||
"surface-container-lowest": "#0b0f11",
|
||||
"inverse-primary": "#00668a",
|
||||
"primary-container": "#6fc2ef",
|
||||
"surface-container": "#1c2023",
|
||||
"on-background": "#e0e3e6",
|
||||
"suit-red-cb": "#6fc2ef",
|
||||
"surface-dim": "#101417",
|
||||
"on-primary-fixed-variant": "#004c69",
|
||||
"tertiary": "#f7c3ff",
|
||||
"on-secondary-fixed-variant": "#3c4d00",
|
||||
"highlight-celebration": "#e1a3ee",
|
||||
"on-primary-fixed": "#001e2c",
|
||||
"primary-fixed-dim": "#7ed0fe",
|
||||
"tertiary-fixed-dim": "#f0b0fc",
|
||||
"inverse-on-surface": "#2d3134",
|
||||
"error": "#fb9fb1",
|
||||
"error-container": "#93000a",
|
||||
"surface-bright": "#363a3d",
|
||||
"on-primary-container": "#004f6c",
|
||||
"warning": "#ddb26f",
|
||||
"surface": "#151515",
|
||||
"suit-black": "#d0d0d0",
|
||||
"highlight-valid": "#acc267",
|
||||
"outline": "#505050",
|
||||
"surface-variant": "#313538",
|
||||
"on-error-container": "#ffdad6",
|
||||
"on-error": "#690005",
|
||||
"primary": "#a1dcff",
|
||||
"suit-red": "#fb9fb1"
|
||||
},
|
||||
"borderRadius": {
|
||||
"DEFAULT": "0.125rem",
|
||||
"lg": "0.25rem",
|
||||
"xl": "0.5rem",
|
||||
"full": "0.75rem"
|
||||
},
|
||||
"spacing": {
|
||||
"gutter-card": "0.375rem",
|
||||
"action-bar-height": "64px",
|
||||
"stack-overlap": "2rem",
|
||||
"margin-edge": "1rem"
|
||||
},
|
||||
"fontFamily": {
|
||||
"card-rank": ["JetBrains Mono"],
|
||||
"hud-score": ["JetBrains Mono"],
|
||||
"body-md": ["Inter"],
|
||||
"headline": ["JetBrains Mono"],
|
||||
"label-caps": ["JetBrains Mono"],
|
||||
"hud-timer": ["JetBrains Mono"]
|
||||
},
|
||||
"fontSize": {
|
||||
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
|
||||
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}],
|
||||
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
|
||||
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
|
||||
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}]
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
min-height: max(884px, 100dvh);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-background text-on-background font-body-md overflow-hidden h-screen select-none">
|
||||
<!-- Top App Bar -->
|
||||
<header class="fixed top-0 w-full z-50 flex justify-between items-center px-margin-edge h-action-bar-height bg-background dark:bg-background border-b border-outline-variant dark:border-outline-variant">
|
||||
<div class="font-headline text-headline text-primary dark:text-primary uppercase tracking-tighter">ROOT@SOLITAIRE:~</div>
|
||||
<div class="flex gap-4">
|
||||
<span class="material-symbols-outlined text-primary" data-icon="memory">memory</span>
|
||||
<span class="material-symbols-outlined text-primary" data-icon="settings_ethernet">settings_ethernet</span>
|
||||
<span class="material-symbols-outlined text-primary" data-icon="wifi_tethering">wifi_tethering</span>
|
||||
</div>
|
||||
</header>
|
||||
<!-- Main Tableau (Dimmed Background) -->
|
||||
<main class="pt-24 px-4 flex flex-col gap-8 opacity-20 filter grayscale">
|
||||
<!-- HUD Chips -->
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="bg-surface-container p-3 flex flex-col">
|
||||
<span class="font-label-caps text-label-caps text-on-surface-variant uppercase">SCORE</span>
|
||||
<span class="font-hud-score text-hud-score text-primary">04,820</span>
|
||||
</div>
|
||||
<div class="bg-surface-container p-3 flex flex-col items-end">
|
||||
<span class="font-label-caps text-label-caps text-on-surface-variant uppercase">TIMER</span>
|
||||
<span class="font-hud-timer text-hud-timer text-on-surface">04:12</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Foundation & Stock -->
|
||||
<div class="flex gap-gutter-card justify-between">
|
||||
<div class="flex gap-gutter-card">
|
||||
<div class="w-[64px] h-[88px] border border-dashed border-outline rounded-DEFAULT"></div>
|
||||
<div class="w-[64px] h-[88px] bg-surface border border-outline rounded-DEFAULT relative overflow-hidden">
|
||||
<div class="absolute inset-0 scanline-overlay"></div>
|
||||
<div class="absolute top-2 left-2 w-3 h-4 bg-suit-red-cb"></div>
|
||||
<div class="absolute bottom-2 right-2 font-card-rank text-[12px] text-on-surface">▌RS</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-gutter-card">
|
||||
<div class="w-[64px] h-[88px] border border-outline rounded-DEFAULT bg-surface"></div>
|
||||
<div class="w-[64px] h-[88px] border border-outline rounded-DEFAULT bg-surface"></div>
|
||||
<div class="w-[64px] h-[88px] border border-outline rounded-DEFAULT bg-surface"></div>
|
||||
<div class="w-[64px] h-[88px] border border-outline rounded-DEFAULT bg-surface"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Cascades -->
|
||||
<div class="grid grid-cols-7 gap-gutter-card">
|
||||
<div class="h-24 border border-dashed border-outline rounded-DEFAULT"></div>
|
||||
<div class="h-24 border border-dashed border-outline rounded-DEFAULT"></div>
|
||||
<div class="h-24 border border-dashed border-outline rounded-DEFAULT"></div>
|
||||
<div class="h-24 border border-dashed border-outline rounded-DEFAULT"></div>
|
||||
<div class="h-24 border border-dashed border-outline rounded-DEFAULT"></div>
|
||||
<div class="h-24 border border-dashed border-outline rounded-DEFAULT"></div>
|
||||
<div class="h-24 border border-dashed border-outline rounded-DEFAULT"></div>
|
||||
</div>
|
||||
</main>
|
||||
<!-- CELEBRATION OVERLAY SCREEM -->
|
||||
<div class="fixed inset-0 z-[100] flex items-center justify-center bg-surface/95 backdrop-blur-sm">
|
||||
<!-- Celebration Card -->
|
||||
<div class="w-[340px] h-[480px] bg-[#202020] border border-highlight-celebration rounded-xl card-glow flex flex-col overflow-hidden relative">
|
||||
<!-- Title Bar -->
|
||||
<div class="h-[28px] bg-[#1a1a1a] border-b border-outline flex items-center px-4 shrink-0">
|
||||
<span class="text-primary mr-1">▌</span>
|
||||
<span class="font-headline text-[12px] text-[#a0a0a0]">level-up.tsx</span>
|
||||
</div>
|
||||
<!-- Content Area -->
|
||||
<div class="flex-grow p-6 flex flex-col">
|
||||
<!-- Hero Band -->
|
||||
<div class="text-center mb-6">
|
||||
<div class="font-headline text-[14px] text-highlight-celebration uppercase tracking-[0.08em] mb-2">▲ LEVEL UP</div>
|
||||
<div class="flex items-baseline justify-center gap-2">
|
||||
<span class="font-headline font-bold text-[96px] text-suit-black leading-none tracking-tighter">13</span>
|
||||
<div class="flex flex-col items-start">
|
||||
<span class="font-label-caps text-[11px] text-outline uppercase">FROM 12</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 font-headline font-medium text-[13px] text-highlight-celebration tracking-[0.08em]">█ NEW PERKS UNLOCKED</div>
|
||||
</div>
|
||||
<!-- Perks List -->
|
||||
<div class="space-y-2 mb-6">
|
||||
<!-- Item 1 -->
|
||||
<div class="h-[48px] bg-[#1a1a1a] rounded-[4px] px-3 flex items-center justify-between">
|
||||
<span class="font-headline text-[13px] text-suit-black">▢ +1 daily challenge slot</span>
|
||||
<span class="bg-highlight-celebration/10 text-highlight-celebration px-2 py-0.5 rounded-full font-label-caps text-[10px] border border-highlight-celebration/30">NEW</span>
|
||||
</div>
|
||||
<!-- Item 2 -->
|
||||
<div class="h-[48px] bg-[#1a1a1a] rounded-[4px] px-3 flex items-center justify-between">
|
||||
<span class="font-headline text-[13px] text-suit-black">▢ Background: Forest</span>
|
||||
<span class="bg-highlight-valid/10 text-highlight-valid px-2 py-0.5 rounded-full font-label-caps text-[10px] border border-highlight-valid/30">UNLOCKED</span>
|
||||
</div>
|
||||
<!-- Item 3 -->
|
||||
<div class="h-[48px] bg-[#1a1a1a] rounded-[4px] px-3 flex items-center justify-between">
|
||||
<span class="font-headline text-[13px] text-suit-black">▢ Card-back: Stripes</span>
|
||||
<span class="bg-highlight-valid/10 text-highlight-valid px-2 py-0.5 rounded-full font-label-caps text-[10px] border border-highlight-valid/30">UNLOCKED</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- XP Recap -->
|
||||
<div class="h-[48px] bg-[#1a1a1a] rounded-[4px] px-4 flex items-center justify-between mt-auto">
|
||||
<span class="font-headline text-[12px] text-[#a0a0a0]">XP</span>
|
||||
<span class="font-headline font-bold text-[14px] text-highlight-valid uppercase">+200 XP THIS LEVEL</span>
|
||||
<div class="w-[60px] h-[4px] bg-outline-variant rounded-full overflow-hidden">
|
||||
<div class="w-[0%] h-full bg-suit-red-cb"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Action Button -->
|
||||
<button class="h-[56px] w-full bg-suit-red-cb flex items-center justify-center gap-2 hover:opacity-90 active:opacity-75 transition-opacity">
|
||||
<span class="font-headline font-bold text-[14px] text-background tracking-wider">▶ CONTINUE</span>
|
||||
</button>
|
||||
<!-- Scanline layer inside card -->
|
||||
<div class="absolute inset-0 scanline-overlay opacity-20 pointer-events-none"></div>
|
||||
</div>
|
||||
<!-- Caption -->
|
||||
<div class="absolute bottom-8 w-full text-center">
|
||||
<span class="font-body-md text-[11px] text-[#a0a0a0] uppercase tracking-widest opacity-60">Tap anywhere to dismiss</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bottom Nav Bar -->
|
||||
<nav class="fixed bottom-0 w-full z-50 flex justify-between items-center h-action-bar-height bg-surface-container dark:bg-surface-container border-t border-outline dark:border-outline">
|
||||
<div class="flex flex-col items-center justify-center bg-primary text-background p-2 w-1/5 h-full transition-colors font-label-caps text-label-caps uppercase tracking-widest">
|
||||
<span class="material-symbols-outlined" data-icon="videogame_asset">videogame_asset</span>
|
||||
<span>NORMAL</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center text-on-surface-variant p-2 w-1/5 h-full hover:text-primary transition-colors font-label-caps text-label-caps uppercase tracking-widest">
|
||||
<span class="material-symbols-outlined" data-icon="edit">edit</span>
|
||||
<span>INSERT</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center text-on-surface-variant p-2 w-1/5 h-full hover:text-primary transition-colors font-label-caps text-label-caps uppercase tracking-widest">
|
||||
<span class="material-symbols-outlined" data-icon="visibility">visibility</span>
|
||||
<span>VISUAL</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center text-on-surface-variant p-2 w-1/5 h-full hover:text-primary transition-colors font-label-caps text-label-caps uppercase tracking-widest">
|
||||
<span class="material-symbols-outlined" data-icon="auto_fix_high">auto_fix_high</span>
|
||||
<span>SOLVE</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center text-on-surface-variant p-2 w-1/5 h-full hover:text-primary transition-colors font-label-caps text-label-caps uppercase tracking-widest">
|
||||
<span class="material-symbols-outlined" data-icon="power_settings_new">power_settings_new</span>
|
||||
<span>QUIT</span>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- Persistent Background Overlay (CRT Effect) -->
|
||||
<div class="fixed inset-0 pointer-events-none z-[200] scanline-overlay opacity-30"></div>
|
||||
</body></html>
|
||||
|
After Width: | Height: | Size: 49 KiB |
@@ -0,0 +1,206 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="dark" lang="en"><head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700;800&family=Inter:wght@400;500&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
"colors": {
|
||||
"tertiary": "#f7c3ff",
|
||||
"surface": "#151515",
|
||||
"surface-container-lowest": "#0b0f11",
|
||||
"surface-variant": "#313538",
|
||||
"surface-tint": "#7ed0fe",
|
||||
"primary": "#a1dcff",
|
||||
"on-tertiary-fixed-variant": "#653173",
|
||||
"on-error-container": "#ffdad6",
|
||||
"on-tertiary-container": "#683476",
|
||||
"secondary": "#bad073",
|
||||
"surface-dim": "#101417",
|
||||
"inverse-on-surface": "#2d3134",
|
||||
"on-tertiary": "#4c195b",
|
||||
"surface-container-high": "#272a2d",
|
||||
"on-primary-fixed-variant": "#004c69",
|
||||
"surface-container": "#202020",
|
||||
"highlight-celebration": "#e1a3ee",
|
||||
"background": "#101417",
|
||||
"suit-red": "#fb9fb1",
|
||||
"on-secondary-container": "#b2c86d",
|
||||
"inverse-surface": "#e0e3e6",
|
||||
"error": "#fb9fb1",
|
||||
"on-surface": "#d0d0d0",
|
||||
"info": "#12cfc0",
|
||||
"secondary-container": "#435401",
|
||||
"tertiary-fixed-dim": "#f0b0fc",
|
||||
"surface-bright": "#363a3d",
|
||||
"outline-variant": "#3f484e",
|
||||
"on-secondary-fixed-variant": "#3c4d00",
|
||||
"warning": "#ddb26f",
|
||||
"surface-container-highest": "#313538",
|
||||
"secondary-fixed": "#d5ec8c",
|
||||
"highlight-valid": "#acc267",
|
||||
"secondary-fixed-dim": "#bad073",
|
||||
"tertiary-container": "#e1a3ee",
|
||||
"suit-red-cb": "#6fc2ef",
|
||||
"on-error": "#690005",
|
||||
"on-primary-container": "#004f6c",
|
||||
"suit-black": "#d0d0d0",
|
||||
"inverse-primary": "#00668a",
|
||||
"surface-container-low": "#181c1f",
|
||||
"on-primary-fixed": "#001e2c",
|
||||
"on-secondary": "#293500",
|
||||
"primary-fixed": "#c4e7ff",
|
||||
"on-tertiary-fixed": "#340043",
|
||||
"outline": "#505050",
|
||||
"error-container": "#93000a",
|
||||
"tertiary-fixed": "#fbd7ff",
|
||||
"primary-container": "#6fc2ef",
|
||||
"on-background": "#e0e3e6",
|
||||
"on-primary": "#003549",
|
||||
"on-surface-variant": "#bfc8cf",
|
||||
"on-secondary-fixed": "#161e00",
|
||||
"primary-fixed-dim": "#7ed0fe"
|
||||
},
|
||||
"borderRadius": {
|
||||
"DEFAULT": "0.125rem",
|
||||
"lg": "0.25rem",
|
||||
"xl": "0.5rem",
|
||||
"full": "0.75rem"
|
||||
},
|
||||
"spacing": {
|
||||
"margin-edge": "1rem",
|
||||
"gutter-card": "0.375rem",
|
||||
"touch-target-min": "48dp",
|
||||
"stack-overlap": "2rem",
|
||||
"action-bar-height": "64px"
|
||||
},
|
||||
"fontFamily": {
|
||||
"card-rank": ["JetBrains Mono"],
|
||||
"hud-timer": ["JetBrains Mono"],
|
||||
"body-md": ["Inter"],
|
||||
"headline": ["JetBrains Mono"],
|
||||
"label-caps": ["JetBrains Mono"],
|
||||
"hud-score": ["JetBrains Mono"]
|
||||
},
|
||||
"fontSize": {
|
||||
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
|
||||
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
|
||||
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
|
||||
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}]
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
}
|
||||
body {
|
||||
background-color: #151515;
|
||||
color: #d0d0d0;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
.card-scanline {
|
||||
background: linear-gradient(rgba(21, 21, 21, 0) 50%, rgba(26, 26, 26, 1) 50%);
|
||||
background-size: 100% 4px;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
body {
|
||||
min-height: max(884px, 100dvh);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex items-center justify-center min-h-screen">
|
||||
<!-- Mobile Canvas (390x844 simulated) -->
|
||||
<main class="w-[390px] h-[844px] bg-surface relative overflow-hidden flex flex-col">
|
||||
<!-- Status Bar -->
|
||||
<header class="h-8 bg-surface-container flex items-center justify-between px-margin-edge border-b border-outline-variant">
|
||||
<div class="font-hud-timer text-[11px] text-primary tracking-tight">▌onboard/01-draw.tsx</div>
|
||||
<div class="font-hud-timer text-[11px] text-on-surface-variant font-bold">STEP 1 OF 3</div>
|
||||
</header>
|
||||
<!-- Content Canvas -->
|
||||
<div class="flex-1 overflow-y-auto pb-action-bar-height">
|
||||
<!-- Hero Section -->
|
||||
<section class="h-[140px] flex flex-col items-center justify-center mt-8">
|
||||
<div class="w-8 h-12 bg-primary animate-pulse mb-2"></div>
|
||||
<h1 class="font-headline text-headline text-on-surface uppercase tracking-tighter">
|
||||
WELCOME <span class="text-primary">▌_</span>
|
||||
</h1>
|
||||
</section>
|
||||
<!-- Headline -->
|
||||
<section class="px-margin-edge mt-4 text-center">
|
||||
<h2 class="font-headline text-[22px] leading-tight text-on-surface mb-1">CHOOSE A DRAW MODE</h2>
|
||||
<p class="font-body-md text-[12px] text-on-surface-variant">You can change this any time in Settings.</p>
|
||||
</section>
|
||||
<!-- Choice Cards -->
|
||||
<section class="px-margin-edge mt-8 space-y-4">
|
||||
<!-- DRAW-3 Card -->
|
||||
<div class="h-[120px] bg-surface-container border border-primary p-4 relative flex items-start gap-4">
|
||||
<div class="absolute top-0 right-0 bg-primary px-2 py-0.5">
|
||||
<span class="font-label-caps text-[10px] text-surface font-bold">RECOMMENDED</span>
|
||||
</div>
|
||||
<div class="w-12 h-16 flex items-center justify-center border border-outline-variant bg-surface-dim">
|
||||
<span class="material-symbols-outlined text-primary" data-icon="filter_3">filter_3</span>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="font-headline text-[16px] text-primary mb-1">DRAW-3 (CLASSIC)</h3>
|
||||
<p class="font-hud-timer text-[12px] leading-snug text-on-surface-variant">
|
||||
Cycle 3 cards at a time. Standard solitaire rules for a tactical challenge.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- DRAW-1 Card -->
|
||||
<div class="h-[120px] bg-surface-container border border-outline-variant p-4 flex items-start gap-4">
|
||||
<div class="w-12 h-16 flex items-center justify-center border border-outline-variant bg-surface-dim">
|
||||
<span class="material-symbols-outlined text-on-surface-variant" data-icon="filter_1">filter_1</span>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="font-headline text-[16px] text-on-surface mb-1">DRAW-1 (EASY)</h3>
|
||||
<p class="font-hud-timer text-[12px] leading-snug text-on-surface-variant">
|
||||
Cycle one card at a time. More winnable, faster pace, perfect for quick sessions.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Step Indicator -->
|
||||
<section class="mt-12 flex flex-col items-center">
|
||||
<div class="flex gap-2 mb-2">
|
||||
<div class="w-8 h-1.5 bg-primary"></div>
|
||||
<div class="w-8 h-1.5 bg-outline-variant"></div>
|
||||
<div class="w-8 h-1.5 bg-outline-variant"></div>
|
||||
</div>
|
||||
<div class="font-hud-timer text-[12px] flex gap-4">
|
||||
<span class="text-primary font-bold">[1]</span>
|
||||
<span class="text-outline-variant">[2]</span>
|
||||
<span class="text-outline-variant">[3]</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<!-- Bottom Action Bar -->
|
||||
<footer class="h-action-bar-height bg-surface-container border-t border-outline flex items-center justify-between px-margin-edge fixed bottom-0 w-[390px] z-50">
|
||||
<!-- Back Button (Disabled/Muted) -->
|
||||
<button class="w-[48%] h-12 border border-outline-variant flex items-center justify-center gap-2 opacity-40 cursor-not-allowed">
|
||||
<span class="material-symbols-outlined text-outline-variant text-[18px]" data-icon="arrow_back">arrow_back</span>
|
||||
<span class="font-label-caps text-outline-variant">BACK</span>
|
||||
</button>
|
||||
<!-- Next Button -->
|
||||
<button class="w-[48%] h-12 bg-primary flex items-center justify-center gap-2 active:opacity-80 transition-opacity">
|
||||
<span class="font-headline text-[14px] text-surface font-bold uppercase tracking-widest">NEXT</span>
|
||||
<span class="material-symbols-outlined text-surface text-[18px]" data-icon="arrow_forward">arrow_forward</span>
|
||||
</button>
|
||||
</footer>
|
||||
<!-- Terminal Overlay (Faint scanlines for atmosphere) -->
|
||||
<div class="pointer-events-none absolute inset-0 opacity-[0.03] card-scanline z-[100]"></div>
|
||||
</main>
|
||||
</body></html>
|
||||
|
After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,211 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="dark" lang="en"><head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@400;500&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
"colors": {
|
||||
"outline-variant": "#3f484e",
|
||||
"tertiary-fixed-dim": "#f0b0fc",
|
||||
"surface-bright": "#363a3d",
|
||||
"secondary-container": "#435401",
|
||||
"info": "#12cfc0",
|
||||
"on-secondary-fixed-variant": "#3c4d00",
|
||||
"highlight-celebration": "#e1a3ee",
|
||||
"surface-container": "#202020",
|
||||
"on-primary-fixed-variant": "#004c69",
|
||||
"on-surface": "#d0d0d0",
|
||||
"on-secondary-container": "#b2c86d",
|
||||
"inverse-surface": "#e0e3e6",
|
||||
"error": "#fb9fb1",
|
||||
"background": "#101417",
|
||||
"suit-red": "#fb9fb1",
|
||||
"surface-dim": "#101417",
|
||||
"inverse-on-surface": "#2d3134",
|
||||
"on-tertiary-container": "#683476",
|
||||
"secondary": "#bad073",
|
||||
"primary": "#a1dcff",
|
||||
"on-tertiary-fixed-variant": "#653173",
|
||||
"on-error-container": "#ffdad6",
|
||||
"surface-container-high": "#272a2d",
|
||||
"on-tertiary": "#4c195b",
|
||||
"tertiary": "#f7c3ff",
|
||||
"surface": "#151515",
|
||||
"surface-tint": "#7ed0fe",
|
||||
"surface-variant": "#313538",
|
||||
"surface-container-lowest": "#0b0f11",
|
||||
"on-surface-variant": "#bfc8cf",
|
||||
"on-primary": "#003549",
|
||||
"primary-fixed-dim": "#7ed0fe",
|
||||
"on-secondary-fixed": "#161e00",
|
||||
"on-tertiary-fixed": "#340043",
|
||||
"outline": "#505050",
|
||||
"on-secondary": "#293500",
|
||||
"primary-fixed": "#c4e7ff",
|
||||
"primary-container": "#6fc2ef",
|
||||
"on-background": "#e0e3e6",
|
||||
"tertiary-fixed": "#fbd7ff",
|
||||
"error-container": "#93000a",
|
||||
"suit-red-cb": "#6fc2ef",
|
||||
"surface-container-low": "#181c1f",
|
||||
"on-primary-fixed": "#001e2c",
|
||||
"on-primary-container": "#004f6c",
|
||||
"suit-black": "#d0d0d0",
|
||||
"inverse-primary": "#00668a",
|
||||
"on-error": "#690005",
|
||||
"secondary-fixed-dim": "#bad073",
|
||||
"surface-container-highest": "#313538",
|
||||
"secondary-fixed": "#d5ec8c",
|
||||
"highlight-valid": "#acc267",
|
||||
"warning": "#ddb26f",
|
||||
"tertiary-container": "#e1a3ee"
|
||||
},
|
||||
"borderRadius": {
|
||||
"DEFAULT": "0.125rem",
|
||||
"lg": "0.25rem",
|
||||
"xl": "0.5rem",
|
||||
"full": "0.75rem"
|
||||
},
|
||||
"spacing": {
|
||||
"margin-edge": "1rem",
|
||||
"gutter-card": "0.375rem",
|
||||
"touch-target-min": "48dp",
|
||||
"stack-overlap": "2rem",
|
||||
"action-bar-height": "64px"
|
||||
},
|
||||
"fontFamily": {
|
||||
"label-caps": ["JetBrains Mono"],
|
||||
"hud-score": ["JetBrains Mono"],
|
||||
"headline": ["JetBrains Mono"],
|
||||
"card-rank": ["JetBrains Mono"],
|
||||
"hud-timer": ["JetBrains Mono"],
|
||||
"body-md": ["Inter"]
|
||||
},
|
||||
"fontSize": {
|
||||
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
|
||||
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}],
|
||||
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
|
||||
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
|
||||
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}]
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
}
|
||||
.scanline-bg {
|
||||
background: linear-gradient(to bottom, #1a1a1a 1px, transparent 1px);
|
||||
background-size: 100% 2px;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
body {
|
||||
min-height: max(884px, 100dvh);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-background text-on-surface font-body-md select-none overflow-hidden h-screen flex flex-col">
|
||||
<!-- Top Navigation Bar -->
|
||||
<header class="fixed top-0 w-full bg-background z-50 border-b border-outline flex justify-between items-center px-margin-edge h-action-bar-height">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-primary" data-icon="terminal">terminal</span>
|
||||
<span class="font-headline text-headline text-primary uppercase tracking-tighter text-sm md:text-base">▌onboard/03-demo.tsx</span>
|
||||
</div>
|
||||
<div class="font-label-caps text-label-caps text-on-surface-variant">STEP 3 OF 3</div>
|
||||
</header>
|
||||
<main class="flex-1 mt-[64px] mb-[64px] flex flex-col items-center px-margin-edge pt-6 space-y-6 overflow-y-auto">
|
||||
<!-- Header Text -->
|
||||
<div class="w-full text-center space-y-2">
|
||||
<h1 class="font-headline text-headline text-on-surface">TRY IT OUT</h1>
|
||||
<p class="font-body-md text-on-surface-variant max-w-xs mx-auto">Tap a face-up card to auto-move it to the best legal pile.</p>
|
||||
</div>
|
||||
<!-- Demo Panel -->
|
||||
<div class="w-full max-w-sm bg-surface border border-outline p-6 rounded-lg relative overflow-hidden">
|
||||
<!-- Subtle scanline background effect for "terminal" pane feel -->
|
||||
<div class="absolute inset-0 scanline-bg opacity-10 pointer-events-none"></div>
|
||||
<div class="relative z-10 flex flex-col items-center">
|
||||
<!-- Foundation Slot -->
|
||||
<div class="w-20 h-28 border border-dashed border-outline-variant flex items-center justify-center mb-12">
|
||||
<span class="material-symbols-outlined text-outline-variant opacity-40 text-4xl" data-icon="spades">playing_cards</span>
|
||||
</div>
|
||||
<!-- Path Indicator (The Arrow) -->
|
||||
<div class="absolute top-[84px] left-1/2 -translate-x-1/2 flex flex-col items-center">
|
||||
<div class="font-label-caps text-secondary text-[10px] mb-1">MOVES HERE</div>
|
||||
<span class="material-symbols-outlined text-secondary text-3xl font-bold" data-icon="arrow_upward">arrow_upward</span>
|
||||
</div>
|
||||
<!-- Mini-Cards Row -->
|
||||
<div class="flex gap-gutter-card">
|
||||
<!-- A-Spades (Target) -->
|
||||
<div class="w-20 h-28 bg-surface border-2 border-primary rounded flex flex-col justify-between p-2 relative ring-1 ring-primary ring-offset-2 ring-offset-surface">
|
||||
<div class="font-card-rank text-card-rank text-suit-black">A</div>
|
||||
<span class="material-symbols-outlined self-end text-3xl text-suit-black" data-icon="spades" style="font-variation-settings: 'FILL' 1;">playing_cards</span>
|
||||
<!-- Pulse Icon -->
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<span class="material-symbols-outlined text-primary text-4xl opacity-80" data-icon="touch_app">touch_app</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- K-Hearts -->
|
||||
<div class="w-20 h-28 bg-surface border border-suit-red rounded flex flex-col justify-between p-2 opacity-50">
|
||||
<div class="font-card-rank text-card-rank text-suit-red">K</div>
|
||||
<span class="material-symbols-outlined self-end text-3xl text-suit-red" data-icon="favorite" style="font-variation-settings: 'FILL' 1;">favorite</span>
|
||||
</div>
|
||||
<!-- Q-Clubs -->
|
||||
<div class="w-20 h-28 bg-surface border border-outline rounded flex flex-col justify-between p-2 opacity-50">
|
||||
<div class="font-card-rank text-card-rank text-on-surface">Q</div>
|
||||
<span class="material-symbols-outlined self-end text-3xl text-on-surface" data-icon="clubs">groups</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- CLI Prompt -->
|
||||
<div class="w-full max-w-sm flex items-center gap-2 font-label-caps text-on-surface py-2">
|
||||
<span class="text-primary">▌</span>
|
||||
<span class="tracking-widest">TAP THE A♠ TO CONTINUE</span>
|
||||
<span class="w-3 h-5 bg-primary animate-pulse"></span>
|
||||
</div>
|
||||
<!-- Feature List -->
|
||||
<div class="w-full max-w-sm space-y-3 pt-2">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="material-symbols-outlined text-secondary text-sm" data-icon="check_circle" style="font-variation-settings: 'FILL' 1;">check_circle</span>
|
||||
<span class="font-label-caps text-label-caps">TAP TO AUTO-MOVE</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="material-symbols-outlined text-secondary text-sm" data-icon="check_circle" style="font-variation-settings: 'FILL' 1;">check_circle</span>
|
||||
<span class="font-label-caps text-label-caps">DRAG TO TARGET PILE</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="material-symbols-outlined text-secondary text-sm" data-icon="check_circle" style="font-variation-settings: 'FILL' 1;">check_circle</span>
|
||||
<span class="font-label-caps text-label-caps">DOUBLE-TAP TO FOUNDATION</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Step Indicators -->
|
||||
<div class="flex gap-2 py-4">
|
||||
<div class="w-8 h-1 bg-primary"></div>
|
||||
<div class="w-8 h-1 bg-primary"></div>
|
||||
<div class="w-8 h-1 bg-primary"></div>
|
||||
</div>
|
||||
</main>
|
||||
<!-- Bottom Action Bar -->
|
||||
<footer class="fixed bottom-0 w-full h-[64px] bg-surface-container border-t border-outline flex items-center px-margin-edge justify-between z-50">
|
||||
<button class="px-6 py-2 border border-outline text-on-surface-variant font-label-caps text-label-caps transition-colors duration-120 active:bg-surface-bright flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-sm" data-icon="arrow_back">arrow_back</span>
|
||||
BACK
|
||||
</button>
|
||||
<button class="px-6 py-2 bg-primary text-on-primary font-label-caps text-label-caps transition-colors duration-120 active:bg-primary-container flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-sm" data-icon="play_arrow" style="font-variation-settings: 'FILL' 1;">play_arrow</span>
|
||||
START PLAYING
|
||||
</button>
|
||||
</footer>
|
||||
</body></html>
|
||||
|
After Width: | Height: | Size: 31 KiB |
@@ -0,0 +1,218 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="dark" lang="en"><head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=390, height=844, initial-scale=1.0" name="viewport"/>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700;800&family=Inter:wght@400;500;700&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
"colors": {
|
||||
"highlight-valid": "#acc267",
|
||||
"outline": "#505050",
|
||||
"highlight-celebration": "#e1a3ee",
|
||||
"error-container": "#93000a",
|
||||
"surface-container": "#202020",
|
||||
"on-secondary-fixed": "#161e00",
|
||||
"tertiary": "#f7c3ff",
|
||||
"inverse-on-surface": "#2d3134",
|
||||
"tertiary-fixed-dim": "#f0b0fc",
|
||||
"on-primary-fixed": "#001e2c",
|
||||
"info": "#12cfc0",
|
||||
"on-tertiary": "#4c195b",
|
||||
"secondary-container": "#435401",
|
||||
"surface": "#151515",
|
||||
"tertiary-container": "#e1a3ee",
|
||||
"outline-variant": "#3f484e",
|
||||
"suit-red": "#fb9fb1",
|
||||
"secondary-fixed": "#d5ec8c",
|
||||
"error": "#fb9fb1",
|
||||
"primary-container": "#6fc2ef",
|
||||
"surface-container-lowest": "#0b0f11",
|
||||
"on-surface": "#e0e3e6",
|
||||
"tertiary-fixed": "#fbd7ff",
|
||||
"on-secondary-container": "#b2c86d",
|
||||
"on-primary-fixed-variant": "#004c69",
|
||||
"on-primary": "#003549",
|
||||
"on-secondary": "#293500",
|
||||
"on-primary-container": "#004f6c",
|
||||
"secondary": "#bad073",
|
||||
"surface-container-highest": "#313538",
|
||||
"primary": "#a1dcff",
|
||||
"surface-container-low": "#181c1f",
|
||||
"secondary-fixed-dim": "#bad073",
|
||||
"warning": "#ddb26f",
|
||||
"suit-black": "#d0d0d0",
|
||||
"surface-variant": "#313538",
|
||||
"on-tertiary-container": "#683476",
|
||||
"on-tertiary-fixed": "#340043",
|
||||
"on-secondary-fixed-variant": "#3c4d00",
|
||||
"on-background": "#e0e3e6",
|
||||
"surface-bright": "#363a3d",
|
||||
"on-error": "#690005",
|
||||
"primary-fixed-dim": "#7ed0fe",
|
||||
"on-tertiary-fixed-variant": "#653173",
|
||||
"suit-red-cb": "#6fc2ef",
|
||||
"inverse-surface": "#e0e3e6",
|
||||
"on-surface-variant": "#bfc8cf",
|
||||
"background": "#101417",
|
||||
"primary-fixed": "#c4e7ff",
|
||||
"on-error-container": "#ffdad6",
|
||||
"inverse-primary": "#00668a",
|
||||
"surface-dim": "#101417",
|
||||
"surface-container-high": "#272a2d",
|
||||
"surface-tint": "#7ed0fe",
|
||||
"cyan-terminal": "#6fc2ef"
|
||||
},
|
||||
"borderRadius": {
|
||||
"DEFAULT": "0.125rem",
|
||||
"lg": "0.25rem",
|
||||
"xl": "0.5rem",
|
||||
"full": "0.75rem"
|
||||
},
|
||||
"spacing": {
|
||||
"touch-target-min": "48dp",
|
||||
"stack-overlap": "2rem",
|
||||
"gutter-card": "0.375rem",
|
||||
"action-bar-height": "64px",
|
||||
"margin-edge": "1rem"
|
||||
},
|
||||
"fontFamily": {
|
||||
"headline": ["JetBrains Mono"],
|
||||
"label-caps": ["JetBrains Mono"],
|
||||
"body-md": ["Inter"],
|
||||
"hud-score": ["JetBrains Mono"],
|
||||
"card-rank": ["JetBrains Mono"],
|
||||
"hud-timer": ["JetBrains Mono"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.scanline-pattern {
|
||||
background: repeating-linear-gradient(
|
||||
0deg,
|
||||
#151515,
|
||||
#151515 2px,
|
||||
#1a1a1a 2px,
|
||||
#1a1a1a 4px
|
||||
);
|
||||
}
|
||||
.checker-pattern {
|
||||
background-color: #ffffff;
|
||||
background-image:
|
||||
linear-gradient(45deg, #004c69 25%, transparent 25%),
|
||||
linear-gradient(-45deg, #004c69 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, #004c69 75%),
|
||||
linear-gradient(-45deg, transparent 75%, #004c69 75%);
|
||||
background-size: 8px 8px;
|
||||
background-position: 0 0, 0 4px, 4px -4px, -4px 0px;
|
||||
}
|
||||
.stripe-pattern {
|
||||
background: repeating-linear-gradient(
|
||||
0deg,
|
||||
#fb9fb1,
|
||||
#fb9fb1 4px,
|
||||
#151515 4px,
|
||||
#151515 8px
|
||||
);
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
body {
|
||||
min-height: max(884px, 100dvh);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-surface text-on-surface min-h-screen flex flex-col items-center overflow-hidden selection:bg-cyan-terminal selection:text-surface">
|
||||
<!-- 1. Status Bar -->
|
||||
<header class="w-full h-8 bg-surface-container flex items-center justify-between px-4 border-b border-outline-variant">
|
||||
<span class="font-label-caps text-[12px] text-on-surface uppercase tracking-tight">▌onboard/02-theme.tsx</span>
|
||||
<span class="font-label-caps text-[12px] text-[#a0a0a0] uppercase tracking-widest">STEP 2 OF 3</span>
|
||||
</header>
|
||||
<!-- 2. Hero Illustration Band -->
|
||||
<section class="w-full flex flex-col items-center pt-8 pb-4">
|
||||
<div class="h-[100px] flex items-center justify-center relative">
|
||||
<span class="text-cyan-terminal font-headline text-[48px] mr-4 select-none">▌</span>
|
||||
<div class="flex -space-x-4">
|
||||
<div class="w-[24px] h-[34px] border border-outline bg-surface scanline-pattern transform -rotate-12 translate-y-2"></div>
|
||||
<div class="w-[24px] h-[34px] border border-outline bg-surface checker-pattern transform rotate-0 z-10"></div>
|
||||
<div class="w-[24px] h-[34px] border border-outline bg-surface stripe-pattern transform rotate-12 translate-y-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="font-headline text-[28px] font-bold text-suit-black tracking-tight leading-none">PICK YOUR DECK</h2>
|
||||
</section>
|
||||
<!-- 3. Headline & Description -->
|
||||
<section class="w-full px-margin-edge text-center mb-6">
|
||||
<h3 class="font-headline text-[22px] font-bold text-suit-black mb-1">CHOOSE A CARD-BACK</h3>
|
||||
<p class="font-body-md text-[12px] text-[#a0a0a0] leading-tight">
|
||||
You can swap or import more themes from Settings later.
|
||||
</p>
|
||||
</section>
|
||||
<!-- 4. Theme Selection Grid -->
|
||||
<main class="w-full px-margin-edge grid grid-cols-3 gap-2 flex-grow max-h-[220px]">
|
||||
<!-- Tile 1: Terminal (Active) -->
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="w-full aspect-[110/150] bg-surface-container border-2 border-cyan-terminal rounded-lg p-3 relative flex items-center justify-center overflow-hidden">
|
||||
<div class="w-full h-full scanline-pattern border border-outline-variant relative">
|
||||
<div class="absolute top-1 left-1 w-2 h-3 bg-cyan-terminal"></div>
|
||||
<div class="absolute bottom-1 right-1 font-headline text-[10px] text-on-surface opacity-50">▌RS</div>
|
||||
</div>
|
||||
<div class="absolute top-1 right-1 bg-cyan-terminal text-surface w-4 h-4 flex items-center justify-center rounded-full">
|
||||
<span class="material-symbols-outlined text-[12px] font-bold">check</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="mt-2 font-label-caps text-[12px] font-bold text-suit-black tracking-widest uppercase">TERMINAL</span>
|
||||
</div>
|
||||
<!-- Tile 2: Classic -->
|
||||
<div class="flex flex-col items-center opacity-70">
|
||||
<div class="w-full aspect-[110/150] bg-surface-container border border-outline rounded-lg p-3 relative flex items-center justify-center overflow-hidden">
|
||||
<div class="w-full h-full checker-pattern border border-outline-variant"></div>
|
||||
</div>
|
||||
<span class="mt-2 font-label-caps text-[12px] font-bold text-suit-black tracking-widest uppercase">CLASSIC</span>
|
||||
</div>
|
||||
<!-- Tile 3: Stripes -->
|
||||
<div class="flex flex-col items-center opacity-70">
|
||||
<div class="w-full aspect-[110/150] bg-surface-container border border-outline rounded-lg p-3 relative flex items-center justify-center overflow-hidden">
|
||||
<div class="w-full h-full stripe-pattern border border-outline-variant"></div>
|
||||
</div>
|
||||
<span class="mt-2 font-label-caps text-[12px] font-bold text-suit-black tracking-widest uppercase">STRIPES</span>
|
||||
</div>
|
||||
</main>
|
||||
<!-- 5. More Info -->
|
||||
<div class="w-full text-center mt-4">
|
||||
<span class="font-label-caps text-[11px] font-medium text-[#a0a0a0] tracking-widest">
|
||||
<span class="text-cyan-terminal">+</span> MORE IN SETTINGS
|
||||
</span>
|
||||
</div>
|
||||
<!-- 6. Step Indicator -->
|
||||
<section class="w-full flex flex-col items-center mt-6">
|
||||
<div class="flex gap-1 h-2 mb-2">
|
||||
<div class="w-8 h-1 bg-cyan-terminal rounded-full"></div>
|
||||
<div class="w-8 h-1 bg-cyan-terminal rounded-full"></div>
|
||||
<div class="w-8 h-1 bg-outline rounded-full"></div>
|
||||
</div>
|
||||
<div class="font-headline text-[12px] font-medium tracking-[0.2em]">
|
||||
<span class="text-cyan-terminal">[1]</span>
|
||||
<span class="text-cyan-terminal">[2]</span>
|
||||
<span class="text-outline">[3]</span>
|
||||
</div>
|
||||
</section>
|
||||
<!-- 7. Bottom Action Bar -->
|
||||
<footer class="fixed bottom-0 w-full h-[64px] bg-surface-container flex items-center justify-between px-margin-edge z-50">
|
||||
<button class="w-[48%] h-12 border border-outline bg-transparent text-suit-black font-label-caps text-[13px] font-medium uppercase rounded-lg active:bg-surface-variant transition-colors">
|
||||
← BACK
|
||||
</button>
|
||||
<button class="w-[48%] h-12 bg-cyan-terminal text-surface font-label-caps text-[14px] font-bold uppercase rounded-lg active:opacity-80 transition-opacity">
|
||||
NEXT →
|
||||
</button>
|
||||
</footer>
|
||||
<!-- Image descriptive data for the model (hidden visually) -->
|
||||
<div class="hidden" data-alt="A detailed user interface screen for a retro-terminal themed solitaire game called Rusty Solitaire. The background is a deep black with cyan and gray accents. In the center, a card theme selection grid displays three different card back designs: a scanline pattern, a checker pattern, and a striped pattern. The visual style is crisp, technical, and uses monospaced typography to evoke a command-line interface or professional developer environment. The mood is minimalist, efficient, and technologically nostalgic."></div>
|
||||
</body></html>
|
||||
|
After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,212 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="dark" lang="en"><head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>Rouge Solitaire - Pause</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=JetBrains+Mono:wght@400;500;700;800&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
"colors": {
|
||||
"on-secondary-fixed": "#161e00",
|
||||
"secondary-fixed": "#d5ec8c",
|
||||
"warning": "#ddb26f",
|
||||
"surface-container-low": "#181c1f",
|
||||
"surface-container": "#1c2023",
|
||||
"on-primary-fixed-variant": "#004c69",
|
||||
"outline-variant": "#3f484e",
|
||||
"on-tertiary-container": "#683476",
|
||||
"surface-container-high": "#272a2d",
|
||||
"on-primary-fixed": "#001e2c",
|
||||
"primary-fixed": "#c4e7ff",
|
||||
"surface-bright": "#363a3d",
|
||||
"outline": "#505050",
|
||||
"tertiary-fixed-dim": "#f0b0fc",
|
||||
"tertiary": "#f7c3ff",
|
||||
"on-surface": "#e0e3e6",
|
||||
"secondary": "#bad073",
|
||||
"tertiary-fixed": "#fbd7ff",
|
||||
"info": "#12cfc0",
|
||||
"primary": "#a1dcff",
|
||||
"secondary-fixed-dim": "#bad073",
|
||||
"surface-tint": "#7ed0fe",
|
||||
"background": "#101417",
|
||||
"surface-container-highest": "#313538",
|
||||
"on-tertiary-fixed": "#340043",
|
||||
"highlight-valid": "#acc267",
|
||||
"inverse-primary": "#00668a",
|
||||
"surface-dim": "#101417",
|
||||
"error": "#fb9fb1",
|
||||
"on-error": "#690005",
|
||||
"inverse-surface": "#e0e3e6",
|
||||
"suit-red": "#fb9fb1",
|
||||
"suit-black": "#d0d0d0",
|
||||
"inverse-on-surface": "#2d3134",
|
||||
"highlight-celebration": "#e1a3ee",
|
||||
"on-error-container": "#ffdad6",
|
||||
"on-primary": "#003549",
|
||||
"surface": "#151515",
|
||||
"surface-container-lowest": "#0b0f11",
|
||||
"primary-fixed-dim": "#7ed0fe",
|
||||
"on-secondary": "#293500",
|
||||
"suit-red-cb": "#6fc2ef",
|
||||
"on-tertiary": "#4c195b",
|
||||
"error-container": "#93000a",
|
||||
"on-secondary-fixed-variant": "#3c4d00",
|
||||
"on-secondary-container": "#b2c86d",
|
||||
"on-surface-variant": "#bfc8cf",
|
||||
"on-primary-container": "#004f6c",
|
||||
"primary-container": "#6fc2ef",
|
||||
"on-background": "#e0e3e6",
|
||||
"surface-variant": "#313538",
|
||||
"secondary-container": "#435401",
|
||||
"on-tertiary-fixed-variant": "#653173",
|
||||
"tertiary-container": "#e1a3ee"
|
||||
},
|
||||
"borderRadius": {
|
||||
"DEFAULT": "0.125rem",
|
||||
"lg": "0.25rem",
|
||||
"xl": "0.5rem",
|
||||
"full": "0.75rem"
|
||||
},
|
||||
"spacing": {
|
||||
"touch-target-min": "48dp",
|
||||
"action-bar-height": "64px",
|
||||
"stack-overlap": "2rem",
|
||||
"margin-edge": "1rem",
|
||||
"gutter-card": "0.375rem"
|
||||
},
|
||||
"fontFamily": {
|
||||
"body-md": ["Inter"],
|
||||
"headline": ["JetBrains Mono"],
|
||||
"hud-score": ["JetBrains Mono"],
|
||||
"hud-timer": ["JetBrains Mono"],
|
||||
"card-rank": ["JetBrains Mono"],
|
||||
"label-caps": ["JetBrains Mono"]
|
||||
},
|
||||
"fontSize": {
|
||||
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
|
||||
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}],
|
||||
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
|
||||
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}]
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.scanline {
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent 50%,
|
||||
rgba(0, 0, 0, 0.05) 50%
|
||||
);
|
||||
background-size: 100% 4px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
body {
|
||||
min-height: max(884px, 100dvh);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-background text-on-surface font-body-md overflow-hidden antialiased">
|
||||
<!-- Background Tableau (Simulated by Dimmed Image Overlay) -->
|
||||
<div class="fixed inset-0 z-0">
|
||||
<img alt="Game Tableau Background" class="w-full h-full object-cover" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDJSHHDQ5Y5qul5C_xabnOSM9aS3uxcWSTk47AOHrS_KIlQi0Ur7YhtL0BomjEWTDc8vRLpytWeG4kf5xgxBzpORahTtsWyXOUPsVRg6_H_qp0QjM6DDo57rOPwjU6TFdfK3Pi7cO9rg-xnUSSu1wu29WyKVwSWDDaA5cZ4QN_9L81YMTCTMKAwDTGsY3eGsj1b1i1X2CdF211aepkhmX8xf4bnV35WSB3QuYxUwlPct0Met7iLFf-AGBeizhK6IAboW5u-Wpg8Ag"/>
|
||||
<!-- 95% Opacity Scrim -->
|
||||
<div class="absolute inset-0 bg-surface opacity-95"></div>
|
||||
<!-- Scanline Overlay for Texture -->
|
||||
<div class="absolute inset-0 scanline"></div>
|
||||
</div>
|
||||
<!-- Modal Container -->
|
||||
<div class="fixed inset-0 z-10 flex items-center justify-center p-margin-edge">
|
||||
<!-- Modal Panel -->
|
||||
<div class="w-[330px] h-[480px] bg-[#202020] border border-outline rounded-xl flex flex-col overflow-hidden">
|
||||
<!-- Title Bar -->
|
||||
<div class="h-[28px] bg-[#1a1a1a] border-b border-[#353535] px-3 flex items-center justify-between">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-primary-container font-headline text-[12px] leading-none mt-px">▌</span>
|
||||
<span class="font-headline text-[12px] text-[#a0a0a0] leading-none">pause.tsx</span>
|
||||
</div>
|
||||
<button class="flex items-center justify-center">
|
||||
<span class="material-symbols-outlined text-[16px] text-[#505050]">close</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Content Canvas -->
|
||||
<div class="flex-1 flex flex-col items-center pt-8 px-4">
|
||||
<!-- Headline -->
|
||||
<h1 class="font-headline text-[24px] font-bold text-[#d0d0d0] tracking-tight text-center">
|
||||
GAME PAUSED
|
||||
</h1>
|
||||
<!-- Subline -->
|
||||
<p class="font-headline text-[12px] text-[#a0a0a0] mt-1 text-center">
|
||||
12:34 ELAPSED · 87 MOVES · DRAW-3
|
||||
</p>
|
||||
<!-- Mini-Stat Chips -->
|
||||
<div class="flex gap-2 mt-4 justify-center">
|
||||
<div class="bg-[#1a1a1a] border border-[#353535] rounded-sm px-2 py-1 flex flex-col items-center">
|
||||
<span class="font-headline text-[11px] text-[#d0d0d0]">SCORE 247</span>
|
||||
</div>
|
||||
<div class="bg-[#1a1a1a] border border-[#353535] rounded-sm px-2 py-1 flex flex-col items-center">
|
||||
<span class="font-headline text-[11px] text-[#d0d0d0]">STOCK 18</span>
|
||||
</div>
|
||||
<div class="bg-[#1a1a1a] border border-[#353535] rounded-sm px-2 py-1 flex flex-col items-center">
|
||||
<span class="font-headline text-[11px] text-[#d0d0d0]">MOVES 87</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Action Buttons Cluster -->
|
||||
<div class="w-full mt-6 space-y-3">
|
||||
<!-- Primary CTA -->
|
||||
<button class="w-full h-[48px] bg-primary-container text-surface flex items-center justify-center rounded-lg active:scale-95 transition-transform duration-75">
|
||||
<span class="font-headline text-[14px] font-bold tracking-[0.08em] uppercase">▶ RESUME GAME</span>
|
||||
</button>
|
||||
<!-- Secondary Buttons -->
|
||||
<button class="w-full h-[48px] bg-transparent border border-outline text-[#d0d0d0] flex items-center justify-center rounded-lg hover:border-primary-container hover:text-primary-container transition-colors duration-120">
|
||||
<span class="font-headline text-[13px] font-medium tracking-[0.08em] uppercase">↻ RESTART</span>
|
||||
</button>
|
||||
<button class="w-full h-[48px] bg-transparent border border-outline text-[#d0d0d0] flex items-center justify-center rounded-lg hover:border-primary-container hover:text-primary-container transition-colors duration-120">
|
||||
<span class="font-headline text-[13px] font-medium tracking-[0.08em] uppercase">✕ FORFEIT</span>
|
||||
</button>
|
||||
<button class="w-full h-[48px] bg-transparent border border-outline text-[#d0d0d0] flex items-center justify-center rounded-lg hover:border-primary-container hover:text-primary-container transition-colors duration-120">
|
||||
<span class="font-headline text-[13px] font-medium tracking-[0.08em] uppercase">⌂ QUIT TO MENU</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Footer Status Line -->
|
||||
<div class="h-[24px] border-t border-[#353535] px-3 flex items-center justify-between">
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="text-primary-container font-headline text-[11px]">▌</span>
|
||||
<span class="font-headline text-[11px] text-[#a0a0a0]">NORMAL</span>
|
||||
<span class="text-[#505050] text-[11px]">│</span>
|
||||
<span class="font-headline text-[11px] text-[#a0a0a0]">pause</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 font-headline text-[11px]">
|
||||
<span class="text-[#a0a0a0]">[ESC]</span>
|
||||
<span class="text-[#505050]">resume</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Hidden Navigation Shell (Suppressed due to Task-Focused Modal Context) -->
|
||||
<!-- But included visually as per the brand anchor hierarchy for TopAppBar identity if it were visible -->
|
||||
<header class="hidden fixed top-0 w-full h-action-bar-height flex items-center justify-between px-margin-edge w-full bg-background border-b border-outline-variant">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-primary">terminal</span>
|
||||
<span class="font-headline text-headline text-primary uppercase tracking-tighter">▌ROUGE_SOLITAIRE</span>
|
||||
</div>
|
||||
</header>
|
||||
<!-- Bottom Nav Suppression Logic: Not rendered to prioritize the focus canvas -->
|
||||
</body></html>
|
||||
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,274 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="dark" lang="en"><head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@400;500;700&family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
}
|
||||
/* CRT Scanline Overlay Effect */
|
||||
.crt-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.1) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.03), rgba(0, 255, 0, 0.01), rgba(0, 0, 255, 0.03));
|
||||
background-size: 100% 3px, 3px 100%;
|
||||
pointer-events: none;
|
||||
z-index: 100;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: #151515;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: #505050;
|
||||
}
|
||||
</style>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
"colors": {
|
||||
"surface-container-low": "#181c1f",
|
||||
"on-primary-fixed": "#001e2c",
|
||||
"on-error": "#690005",
|
||||
"suit-black": "#d0d0d0",
|
||||
"inverse-primary": "#00668a",
|
||||
"on-primary-container": "#004f6c",
|
||||
"suit-red-cb": "#6fc2ef",
|
||||
"tertiary-container": "#e1a3ee",
|
||||
"secondary-fixed": "#d5ec8c",
|
||||
"highlight-valid": "#acc267",
|
||||
"surface-container-highest": "#313538",
|
||||
"secondary-fixed-dim": "#bad073",
|
||||
"warning": "#ddb26f",
|
||||
"on-secondary-fixed": "#161e00",
|
||||
"primary-fixed-dim": "#7ed0fe",
|
||||
"on-surface-variant": "#bfc8cf",
|
||||
"on-primary": "#003549",
|
||||
"tertiary-fixed": "#fbd7ff",
|
||||
"primary-container": "#6fc2ef",
|
||||
"on-background": "#e0e3e6",
|
||||
"error-container": "#93000a",
|
||||
"outline": "#505050",
|
||||
"on-tertiary-fixed": "#340043",
|
||||
"primary-fixed": "#c4e7ff",
|
||||
"on-surface": "#e0e3e6",
|
||||
"on-secondary": "#293500",
|
||||
"surface-container-high": "#272a2d",
|
||||
"on-tertiary": "#4c195b",
|
||||
"secondary": "#bad073",
|
||||
"on-tertiary-container": "#683476",
|
||||
"inverse-on-surface": "#2d3134",
|
||||
"surface-dim": "#101417",
|
||||
"primary": "#a1dcff",
|
||||
"on-tertiary-fixed-variant": "#653173",
|
||||
"on-error-container": "#ffdad6",
|
||||
"surface-variant": "#313538",
|
||||
"surface-tint": "#7ed0fe",
|
||||
"surface-container-lowest": "#0b0f11",
|
||||
"surface": "#151515",
|
||||
"tertiary": "#f7c3ff",
|
||||
"on-secondary-fixed-variant": "#3c4d00",
|
||||
"surface-bright": "#363a3d",
|
||||
"tertiary-fixed-dim": "#f0b0fc",
|
||||
"outline-variant": "#3f484e",
|
||||
"info": "#12cfc0",
|
||||
"secondary-container": "#435401",
|
||||
"on-secondary-container": "#b2c86d",
|
||||
"inverse-surface": "#e0e3e6",
|
||||
"error": "#fb9fb1",
|
||||
"suit-red": "#fb9fb1",
|
||||
"background": "#101417",
|
||||
"highlight-celebration": "#e1a3ee",
|
||||
"on-primary-fixed-variant": "#004c69",
|
||||
"surface-container": "#202020"
|
||||
},
|
||||
"borderRadius": {
|
||||
"DEFAULT": "0.125rem",
|
||||
"lg": "0.25rem",
|
||||
"xl": "0.5rem",
|
||||
"full": "0.75rem"
|
||||
},
|
||||
"spacing": {
|
||||
"stack-overlap": "2rem",
|
||||
"action-bar-height": "64px",
|
||||
"touch-target-min": "48dp",
|
||||
"gutter-card": "0.375rem",
|
||||
"margin-edge": "1rem"
|
||||
},
|
||||
"fontFamily": {
|
||||
"headline": ["JetBrains Mono"],
|
||||
"label-caps": ["JetBrains Mono"],
|
||||
"hud-score": ["JetBrains Mono"],
|
||||
"hud-timer": ["JetBrains Mono"],
|
||||
"body-md": ["Inter"],
|
||||
"card-rank": ["JetBrains Mono"]
|
||||
},
|
||||
"fontSize": {
|
||||
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
|
||||
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
|
||||
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}],
|
||||
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}]
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
min-height: max(884px, 100dvh);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-background text-on-background font-body-md selection:bg-primary selection:text-background overflow-x-hidden">
|
||||
<div class="crt-overlay"></div>
|
||||
<!-- Status Bar -->
|
||||
<header class="bg-surface-container h-[32px] w-full flex items-center justify-between px-margin-edge z-50">
|
||||
<div class="font-headline text-[12px] text-on-surface-variant tracking-wider">
|
||||
▌profile.tsx
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-2 h-2 rounded-full bg-info"></span>
|
||||
<span class="font-label-caps text-[10px] text-on-surface">● SYNCED</span>
|
||||
</div>
|
||||
</header>
|
||||
<!-- Main Canvas -->
|
||||
<main class="flex-1 overflow-y-auto pb-24">
|
||||
<!-- Profile Header -->
|
||||
<section class="h-[120px] bg-surface-container border-b border-outline-variant flex items-center px-margin-edge gap-4">
|
||||
<div class="w-[64px] h-[64px] bg-[#1a1a1a] border border-outline flex items-center justify-center shrink-0">
|
||||
<span class="font-headline text-[28px] text-primary-container">RS</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1 overflow-hidden">
|
||||
<h1 class="font-headline text-[18px] text-on-surface truncate">anonymous@local</h1>
|
||||
<p class="font-label-caps text-on-surface-variant text-[10px]">MEMBER SINCE 2026-04-22</p>
|
||||
<div class="flex gap-2 mt-1">
|
||||
<span class="px-2 py-0.5 bg-[#1a1a1a] font-label-caps text-[10px] text-suit-black">247 GAMES</span>
|
||||
<span class="px-2 py-0.5 bg-[#1a1a1a] font-label-caps text-[10px] text-suit-black">61% WR</span>
|
||||
<span class="px-2 py-0.5 bg-[#1a1a1a] font-label-caps text-[10px] text-suit-black">12 STREAK</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Level/XP Section -->
|
||||
<section class="p-margin-edge bg-surface-container border-b border-outline-variant">
|
||||
<div class="flex justify-between items-baseline mb-2">
|
||||
<span class="font-headline text-[24px] text-on-surface">LEVEL 12</span>
|
||||
<span class="font-hud-timer text-on-surface-variant">320/500 XP</span>
|
||||
</div>
|
||||
<div class="h-3 w-full bg-[#353535] relative overflow-hidden">
|
||||
<div class="h-full bg-primary-container" style="width: 64%;"></div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 mt-3">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-highlight-celebration"></span>
|
||||
<span class="font-label-caps text-[10px] text-on-surface-variant">180 XP TO LEVEL 13</span>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Unlocked Cards -->
|
||||
<section class="mt-6">
|
||||
<h2 class="px-margin-edge font-headline text-[14px] text-on-surface mb-4">▌ unlocked.cards</h2>
|
||||
<div class="flex overflow-x-auto gap-4 px-margin-edge pb-4 custom-scrollbar">
|
||||
<!-- Terminal (Active) -->
|
||||
<div class="shrink-0 flex flex-col gap-2">
|
||||
<div class="w-[60px] h-[84px] bg-surface-container-low border-2 border-primary-container relative flex items-center justify-center p-1">
|
||||
<div class="w-full h-full bg-[#151515] overflow-hidden flex flex-col p-1">
|
||||
<div class="w-2 h-2.5 bg-primary-container mb-auto"></div>
|
||||
<div class="self-end text-[8px] font-headline text-on-surface-variant opacity-50">▌RS</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="font-label-caps text-[9px] text-primary-container text-center">ACTIVE</span>
|
||||
</div>
|
||||
<!-- Classic -->
|
||||
<div class="shrink-0 flex flex-col gap-2">
|
||||
<div class="w-[60px] h-[84px] bg-white border border-outline relative p-1">
|
||||
<div class="w-full h-full border border-red-200 bg-red-50 opacity-20"></div>
|
||||
</div>
|
||||
<span class="font-label-caps text-[9px] text-on-surface-variant text-center opacity-50">TAP TO USE</span>
|
||||
</div>
|
||||
<!-- Stripes -->
|
||||
<div class="shrink-0 flex flex-col gap-2">
|
||||
<div class="w-[60px] h-[84px] bg-surface-container border border-outline p-1">
|
||||
<div class="w-full h-full bg-gradient-to-br from-secondary-container via-surface to-secondary-container opacity-40"></div>
|
||||
</div>
|
||||
<span class="font-label-caps text-[9px] text-on-surface-variant text-center opacity-50">TAP TO USE</span>
|
||||
</div>
|
||||
<!-- Polka -->
|
||||
<div class="shrink-0 flex flex-col gap-2">
|
||||
<div class="w-[60px] h-[84px] bg-surface-container border border-outline p-1 overflow-hidden relative">
|
||||
<div class="w-full h-full opacity-30" style="background-image: radial-gradient(#505050 1px, transparent 0); background-size: 6px 6px;"></div>
|
||||
</div>
|
||||
<span class="font-label-caps text-[9px] text-on-surface-variant text-center opacity-50">TAP TO USE</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Unlocked Backgrounds -->
|
||||
<section class="mt-6">
|
||||
<h2 class="px-margin-edge font-headline text-[14px] text-on-surface mb-4">▌ unlocked.backgrounds</h2>
|
||||
<div class="flex overflow-x-auto gap-4 px-margin-edge pb-4 custom-scrollbar">
|
||||
<!-- Default (Active) -->
|
||||
<div class="shrink-0 flex flex-col gap-2">
|
||||
<div class="w-[80px] h-[56px] bg-[#151515] border-2 border-primary-container"></div>
|
||||
<span class="font-label-caps text-[9px] text-primary-container text-center">ACTIVE</span>
|
||||
</div>
|
||||
<!-- Forest -->
|
||||
<div class="shrink-0 flex flex-col gap-2">
|
||||
<div class="w-[80px] h-[56px] bg-[#0d160d] border border-outline"></div>
|
||||
<span class="font-label-caps text-[9px] text-on-surface-variant text-center opacity-50">FOREST</span>
|
||||
</div>
|
||||
<!-- Slate -->
|
||||
<div class="shrink-0 flex flex-col gap-2">
|
||||
<div class="w-[80px] h-[56px] bg-[#1c2128] border border-outline"></div>
|
||||
<span class="font-label-caps text-[9px] text-on-surface-variant text-center opacity-50">SLATE</span>
|
||||
</div>
|
||||
<!-- Midnight -->
|
||||
<div class="shrink-0 flex flex-col gap-2">
|
||||
<div class="w-[80px] h-[56px] bg-[#09090b] border border-outline"></div>
|
||||
<span class="font-label-caps text-[9px] text-on-surface-variant text-center opacity-50">MIDNIGHT</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Sign-in Card -->
|
||||
<section class="mt-8 px-margin-edge">
|
||||
<button class="w-full h-[64px] bg-surface-container border border-dashed border-outline flex items-center justify-between px-6 hover:bg-surface-variant transition-colors group">
|
||||
<span class="font-label-caps text-on-surface-variant tracking-widest">+ SIGN IN TO SYNC PROGRESS</span>
|
||||
<span class="material-symbols-outlined text-primary-container group-hover:translate-x-1 transition-transform">arrow_forward</span>
|
||||
</button>
|
||||
</section>
|
||||
</main>
|
||||
<!-- TopAppBar (from Shared Components - as Terminal Header) -->
|
||||
<div class="fixed top-[32px] left-0 w-full z-40 bg-background border-b border-outline-variant flex items-center justify-between px-margin-edge h-action-bar-height">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="material-symbols-outlined text-primary">terminal</span>
|
||||
<span class="font-headline text-headline text-primary tracking-tighter">~/root/usr/settings</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<button class="p-2 hover:bg-surface-variant text-primary transition-colors">
|
||||
<span class="material-symbols-outlined">close</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- BottomNavBar (from Shared Components - as Terminal Footer) -->
|
||||
<nav class="fixed bottom-0 left-0 w-full z-50 h-[24px] bg-surface-container-lowest border-t border-outline-variant flex justify-between items-center px-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="font-label-caps text-[10px] text-on-surface-variant">▌ NORMAL │ profile</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<button class="flex items-center gap-1 group">
|
||||
<span class="font-label-caps text-[10px] text-on-surface-variant group-hover:text-primary">[ESC] back</span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- Decorative CRT Scanline overlay line -->
|
||||
<div class="fixed top-0 left-0 w-full h-[1px] bg-primary opacity-20 pointer-events-none animate-pulse"></div>
|
||||
</body></html>
|
||||
|
After Width: | Height: | Size: 82 KiB |
@@ -0,0 +1,271 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="dark" lang="en"><head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@400;700&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
"colors": {
|
||||
"surface-variant": "#313538",
|
||||
"surface-dim": "#101417",
|
||||
"secondary-fixed-dim": "#bad073",
|
||||
"surface-bright": "#363a3d",
|
||||
"secondary-fixed": "#d5ec8c",
|
||||
"secondary-container": "#435401",
|
||||
"on-tertiary-fixed-variant": "#653173",
|
||||
"surface-container-highest": "#313538",
|
||||
"outline-variant": "#3f484e",
|
||||
"error": "#fb9fb1",
|
||||
"surface-container": "#202020",
|
||||
"inverse-on-surface": "#2d3134",
|
||||
"on-primary-fixed-variant": "#004c69",
|
||||
"outline": "#505050",
|
||||
"on-secondary": "#293500",
|
||||
"suit-red": "#fb9fb1",
|
||||
"inverse-primary": "#00668a",
|
||||
"on-secondary-container": "#b2c86d",
|
||||
"highlight-celebration": "#e1a3ee",
|
||||
"warning": "#ddb26f",
|
||||
"primary-fixed-dim": "#7ed0fe",
|
||||
"info": "#12cfc0",
|
||||
"primary-fixed": "#c4e7ff",
|
||||
"highlight-valid": "#acc267",
|
||||
"on-surface-variant": "#bfc8cf",
|
||||
"on-tertiary": "#4c195b",
|
||||
"background": "#101417",
|
||||
"tertiary-container": "#e1a3ee",
|
||||
"suit-black": "#d0d0d0",
|
||||
"on-error-container": "#ffdad6",
|
||||
"on-surface": "#d0d0d0",
|
||||
"primary": "#a1dcff",
|
||||
"error-container": "#93000a",
|
||||
"secondary": "#bad073",
|
||||
"surface": "#151515",
|
||||
"primary-container": "#6fc2ef",
|
||||
"suit-red-cb": "#6fc2ef",
|
||||
"on-primary": "#003549",
|
||||
"surface-container-low": "#181c1f",
|
||||
"tertiary-fixed-dim": "#f0b0fc",
|
||||
"surface-tint": "#7ed0fe",
|
||||
"on-tertiary-container": "#683476",
|
||||
"on-secondary-fixed": "#161e00",
|
||||
"surface-container-lowest": "#0b0f11",
|
||||
"on-tertiary-fixed": "#340043",
|
||||
"surface-container-high": "#272a2d",
|
||||
"on-error": "#690005",
|
||||
"tertiary-fixed": "#fbd7ff",
|
||||
"tertiary": "#f7c3ff",
|
||||
"on-background": "#e0e3e6",
|
||||
"on-secondary-fixed-variant": "#3c4d00",
|
||||
"on-primary-fixed": "#001e2c",
|
||||
"on-primary-container": "#004f6c",
|
||||
"inverse-surface": "#e0e3e6"
|
||||
},
|
||||
"borderRadius": {
|
||||
"DEFAULT": "0.125rem",
|
||||
"lg": "0.25rem",
|
||||
"xl": "0.5rem",
|
||||
"full": "0.75rem"
|
||||
},
|
||||
"spacing": {
|
||||
"action-bar-height": "64px",
|
||||
"stack-overlap": "2rem",
|
||||
"gutter-card": "0.375rem",
|
||||
"touch-target-min": "48dp",
|
||||
"margin-edge": "1rem"
|
||||
},
|
||||
"fontFamily": {
|
||||
"label-caps": ["JetBrains Mono"],
|
||||
"hud-timer": ["JetBrains Mono"],
|
||||
"hud-score": ["JetBrains Mono"],
|
||||
"body-md": ["Inter"],
|
||||
"headline": ["JetBrains Mono"],
|
||||
"card-rank": ["JetBrains Mono"]
|
||||
},
|
||||
"fontSize": {
|
||||
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
|
||||
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}],
|
||||
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
|
||||
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}]
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.radial-segment {
|
||||
clip-path: polygon(50% 50%, 100% 0, 100% 100%);
|
||||
}
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.scanline-bg {
|
||||
background: repeating-linear-gradient(
|
||||
0deg,
|
||||
rgba(26, 26, 26, 1) 0px,
|
||||
rgba(26, 26, 26, 1) 2px,
|
||||
rgba(21, 21, 21, 1) 2px,
|
||||
rgba(21, 21, 21, 1) 4px
|
||||
);
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
body {
|
||||
min-height: max(884px, 100dvh);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-surface font-body-md text-on-surface select-none overflow-hidden h-screen w-screen flex flex-col">
|
||||
<!-- Underlying Game Tableau (Dimmed Background) -->
|
||||
<main class="relative flex-grow opacity-30 grid grid-cols-7 gap-2 p-margin-edge pointer-events-none">
|
||||
<!-- Top row: Foundation/Stock -->
|
||||
<div class="col-span-1 aspect-[2/3] border border-dashed border-outline-variant bg-surface-container flex items-center justify-center">
|
||||
<span class="material-symbols-outlined text-outline-variant">terminal</span>
|
||||
</div>
|
||||
<div class="col-span-1 aspect-[2/3] border border-dashed border-outline-variant"></div>
|
||||
<div class="col-span-1"></div>
|
||||
<div class="col-span-1 aspect-[2/3] border border-outline border-suit-red-cb flex items-center justify-center">
|
||||
<span class="material-symbols-outlined text-suit-red-cb">favorite</span>
|
||||
</div>
|
||||
<div class="col-span-1 aspect-[2/3] border border-outline border-suit-black flex items-center justify-center">
|
||||
<span class="material-symbols-outlined text-suit-black">backspace</span>
|
||||
</div>
|
||||
<div class="col-span-1 aspect-[2/3] border border-outline border-suit-red-cb flex items-center justify-center">
|
||||
<span class="material-symbols-outlined text-suit-red-cb">diamond</span>
|
||||
</div>
|
||||
<div class="col-span-1 aspect-[2/3] border border-outline border-suit-black flex items-center justify-center">
|
||||
<span class="material-symbols-outlined text-suit-black">spa</span>
|
||||
</div>
|
||||
<!-- Tableau piles -->
|
||||
<div class="col-span-7 grid grid-cols-7 gap-2 mt-4">
|
||||
<div class="space-y-[-120%]">
|
||||
<div class="aspect-[2/3] bg-surface-container-high border border-outline p-1 flex flex-col justify-between">
|
||||
<span class="font-card-rank text-card-rank text-suit-red-cb">K</span>
|
||||
<span class="material-symbols-outlined self-end text-3xl rotate-180 text-suit-red-cb">diamond</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-[-120%]">
|
||||
<div class="aspect-[2/3] scanline-bg border border-outline relative">
|
||||
<div class="absolute top-1 left-1 w-3 h-4 bg-primary"></div>
|
||||
<span class="absolute bottom-1 right-1 font-label-caps text-[10px] text-primary">▌RS</span>
|
||||
</div>
|
||||
<div class="aspect-[2/3] bg-surface-container-high border border-outline p-1 flex flex-col justify-between">
|
||||
<span class="font-card-rank text-card-rank text-suit-black">Q</span>
|
||||
<span class="material-symbols-outlined self-end text-3xl rotate-180 text-suit-black" style="font-variation-settings: 'FILL' 1;">spa</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-[-120%]">
|
||||
<div class="aspect-[2/3] scanline-bg border border-outline"></div>
|
||||
<div class="aspect-[2/3] scanline-bg border border-outline"></div>
|
||||
<div class="aspect-[2/3] bg-surface-container-high border border-outline p-1 flex flex-col justify-between">
|
||||
<span class="font-card-rank text-card-rank text-suit-red-cb">J</span>
|
||||
<span class="material-symbols-outlined self-end text-3xl rotate-180 text-suit-red-cb" style="font-variation-settings: 'FILL' 1;">favorite</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- More stacks... omitted for brevity as background -->
|
||||
</div>
|
||||
</main>
|
||||
<!-- Radial Menu Overlay -->
|
||||
<div class="fixed inset-0 z-50 bg-[#151515]/70 flex items-center justify-center overflow-hidden">
|
||||
<div class="relative w-[280px] h-[280px] flex items-center justify-center">
|
||||
<!-- Outer Circular Ring Shell -->
|
||||
<div class="absolute inset-0 rounded-full border border-outline bg-surface-container overflow-hidden">
|
||||
<!-- SVG Segments Construction -->
|
||||
<svg class="w-full h-full transform -rotate-22.5" viewbox="0 0 100 100">
|
||||
<!-- Slice 1 (UNDO) - Top / 12:00 -->
|
||||
<!-- Active state: bg-primary-container/15, stroke-primary -->
|
||||
<path d="M 50 50 L 50 0 A 50 50 0 0 1 85.35 14.65 Z" fill="#6fc2ef26" stroke="#6fc2ef" stroke-width="0.5" transform="rotate(-22.5, 50, 50)"></path>
|
||||
<!-- Slice 2 (REDO) -->
|
||||
<path d="M 50 50 L 85.35 14.65 A 50 50 0 0 1 100 50 Z" fill="transparent" stroke="#353535" stroke-width="0.25" transform="rotate(-22.5, 50, 50)"></path>
|
||||
<!-- Slice 3 (HINT) -->
|
||||
<path d="M 50 50 L 100 50 A 50 50 0 0 1 85.35 85.35 Z" fill="transparent" stroke="#353535" stroke-width="0.25" transform="rotate(-22.5, 50, 50)"></path>
|
||||
<!-- Slice 4 (AUTO) -->
|
||||
<path d="M 50 50 L 85.35 85.35 A 50 50 0 0 1 50 100 Z" fill="transparent" stroke="#353535" stroke-width="0.25" transform="rotate(-22.5, 50, 50)"></path>
|
||||
<!-- Slice 5 (NEW) -->
|
||||
<path d="M 50 50 L 50 100 A 50 50 0 0 1 14.65 85.35 Z" fill="transparent" stroke="#353535" stroke-width="0.25" transform="rotate(-22.5, 50, 50)"></path>
|
||||
<!-- Slice 6 (PAUSE) -->
|
||||
<path d="M 50 50 L 14.65 85.35 A 50 50 0 0 1 0 50 Z" fill="transparent" stroke="#353535" stroke-width="0.25" transform="rotate(-22.5, 50, 50)"></path>
|
||||
<!-- Slice 7 (STATS) -->
|
||||
<path d="M 50 50 L 0 50 A 50 50 0 0 1 14.65 14.65 Z" fill="transparent" stroke="#353535" stroke-width="0.25" transform="rotate(-22.5, 50, 50)"></path>
|
||||
<!-- Slice 8 (SETTINGS) -->
|
||||
<path d="M 50 50 L 14.65 14.65 A 50 50 0 0 1 50 0 Z" fill="transparent" stroke="#353535" stroke-width="0.25" transform="rotate(-22.5, 50, 50)"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- Labels and Icons Overlay -->
|
||||
<div class="absolute inset-0 pointer-events-none">
|
||||
<!-- 12:00 UNDO (ACTIVE) -->
|
||||
<div class="absolute top-[12%] left-1/2 -translate-x-1/2 flex flex-col items-center text-primary">
|
||||
<span class="material-symbols-outlined text-[24px]" data-icon="undo">undo</span>
|
||||
<span class="font-label-caps text-[11px] mt-1">UNDO</span>
|
||||
</div>
|
||||
<!-- 1:30 REDO -->
|
||||
<div class="absolute top-[22%] right-[12%] flex flex-col items-center text-on-surface">
|
||||
<span class="material-symbols-outlined text-[24px]" data-icon="redo">redo</span>
|
||||
<span class="font-label-caps text-[11px] mt-1">REDO</span>
|
||||
</div>
|
||||
<!-- 3:00 HINT -->
|
||||
<div class="absolute top-1/2 right-[8%] -translate-y-1/2 flex flex-col items-center text-on-surface">
|
||||
<span class="material-symbols-outlined text-[24px]" data-icon="lightbulb">lightbulb</span>
|
||||
<span class="font-label-caps text-[11px] mt-1">HINT</span>
|
||||
</div>
|
||||
<!-- 4:30 AUTO -->
|
||||
<div class="absolute bottom-[22%] right-[12%] flex flex-col items-center text-on-surface">
|
||||
<span class="material-symbols-outlined text-[24px]" data-icon="double_arrow">double_arrow</span>
|
||||
<span class="font-label-caps text-[11px] mt-1">AUTO</span>
|
||||
</div>
|
||||
<!-- 6:00 NEW -->
|
||||
<div class="absolute bottom-[12%] left-1/2 -translate-x-1/2 flex flex-col items-center text-on-surface">
|
||||
<span class="material-symbols-outlined text-[24px]" data-icon="add">add</span>
|
||||
<span class="font-label-caps text-[11px] mt-1">NEW</span>
|
||||
</div>
|
||||
<!-- 7:30 PAUSE -->
|
||||
<div class="absolute bottom-[22%] left-[12%] flex flex-col items-center text-on-surface">
|
||||
<span class="material-symbols-outlined text-[24px]" data-icon="pause">pause</span>
|
||||
<span class="font-label-caps text-[11px] mt-1">PAUSE</span>
|
||||
</div>
|
||||
<!-- 9:00 STATS -->
|
||||
<div class="absolute top-1/2 left-[8%] -translate-y-1/2 flex flex-col items-center text-on-surface">
|
||||
<span class="material-symbols-outlined text-[24px]" data-icon="bar_chart">bar_chart</span>
|
||||
<span class="font-label-caps text-[11px] mt-1">STATS</span>
|
||||
</div>
|
||||
<!-- 10:30 SETTINGS -->
|
||||
<div class="absolute top-[22%] left-[12%] flex flex-col items-center text-on-surface">
|
||||
<span class="material-symbols-outlined text-[24px]" data-icon="settings">settings</span>
|
||||
<span class="font-label-caps text-[11px] mt-1">SETTINGS</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Inner Hole -->
|
||||
<div class="absolute w-20 h-20 rounded-full bg-surface-container border border-outline-variant flex flex-col items-center justify-center z-10">
|
||||
<div class="font-headline text-[32px] text-primary leading-none">▌</div>
|
||||
<div class="font-label-caps text-[10px] text-on-surface-variant tracking-widest mt-1">RADIAL</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Instructions (Bottom Floating) -->
|
||||
<div class="absolute bottom-12 left-0 w-full flex flex-col items-center gap-4">
|
||||
<div class="font-label-caps text-[12px] text-on-surface-variant tracking-wider">
|
||||
DRAG TO SELECT · RELEASE TO ACTIVATE
|
||||
</div>
|
||||
<!-- Status Line (Vim style) -->
|
||||
<div class="w-full h-8 bg-surface-container border-t border-outline-variant flex items-center justify-center">
|
||||
<span class="font-label-caps text-[11px] text-on-surface-variant">
|
||||
<span class="text-primary">▌</span> NORMAL │ radial · 1/8 selected
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Hidden image for standard requirement compliance, though not visually used in this specific overlay task -->
|
||||
<div class="hidden">
|
||||
<img data-alt="A macro shot of a vintage terminal screen displaying green computer code and technical data. The lighting is low-key, with a soft glow emanating from the screen, highlighting the CRT scanlines and subtle reflections. The aesthetic is purely technical and retro-futuristic, focusing on precision and high-contrast digital artifacts. Deep blacks and vibrant green neon tones dominate the color palette, evoking a high-performance system environment." src="https://lh3.googleusercontent.com/aida-public/AB6AXuAQuJUCOQev_BN72KyX0c-ylmW3DMZD-gOUlylYo3w1SrSpGnvorMvSUwe5oGPAgBgc050cCowC8f1QaxHEDN-DUkyCynOLhzrZHXyCJh2ebCWd6x1quLQwp0ffwbHsZW1-J2zAMuUydMNpEVmpHFQDij0yjVg6lxc6JdsC0etMoAWMhb61S3HUoDffSl-Q23N8Oc77r3dSf6kLFKAMAJCbXFz4nTaJKCKAwtMs62pLr6fd1jzMZrItH43RaO28uzMzvnGGZj3Miw"/>
|
||||
</div>
|
||||
</body></html>
|
||||
|
After Width: | Height: | Size: 23 KiB |