Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f23df3b805 | |||
| 68d50b5021 | |||
| ec804d54c6 | |||
| d87761d451 | |||
| 2fb2d638bf | |||
| c9af1ead22 | |||
| ed152e2d8f | |||
| 279a834f9d | |||
| daa655a0af | |||
| 4d48cad4e3 | |||
| dd970215cc | |||
| ddb65403c2 | |||
| 62b61cc786 | |||
| 31139ae455 | |||
| 07e035771c | |||
| c5787c6953 | |||
| 716a025352 | |||
| 3eb3a26789 | |||
| 0c1cc40266 | |||
| 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,920 @@ project follows [Semantic Versioning](https://semver.org/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
_Nothing yet._
|
No threads in flight. v0.21.2 cut on 2026-05-08; CHANGELOG accumulates
|
||||||
|
the next cycle here.
|
||||||
|
|
||||||
|
## [0.21.2] — 2026-05-08
|
||||||
|
|
||||||
|
Patch release for the post-v0.21.1 polish work. Three through-
|
||||||
|
lines: **accessibility extensions** (reduce-motion gating for
|
||||||
|
splash animations, full HC chrome rollout across 8 surfaces),
|
||||||
|
**replay polish** (floating MOVE chip above the focused card
|
||||||
|
during playback), and the **first real consumer of
|
||||||
|
`ToastVariant::Error`** (invalid-move feedback as the third leg
|
||||||
|
of the existing audio + visual rejection-feedback stool).
|
||||||
|
|
||||||
|
The accessibility extensions close two threads v0.21.1 left
|
||||||
|
explicitly open: reduce-motion was previously gated only on card
|
||||||
|
slide_secs, and HC borders had `BORDER_SUBTLE_HC` defined but no
|
||||||
|
consumers. v0.21.2 finishes both — non-essential motion in the
|
||||||
|
splash boot screen now respects reduce-motion, and every static-
|
||||||
|
border chrome surface (modal scaffold, tooltip, help / stats /
|
||||||
|
home / settings panels) boosts to the HC variant under high-
|
||||||
|
contrast mode. Dynamic-paint sites (HUD action buttons, modal
|
||||||
|
buttons, radial menu rim) intentionally stay un-tagged because
|
||||||
|
their existing paint cycles would race the HC system; they
|
||||||
|
remain open for a future iteration that needs a different shape.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **`sync_pile_marker_visibility` system precursor was v0.21.1's;
|
||||||
|
this cycle adds**: `update_high_contrast_borders` system in
|
||||||
|
`settings_plugin` (`c9af1ea`). Walks all entities tagged with
|
||||||
|
`HighContrastBorder` each Update tick, swaps `BorderColor` to
|
||||||
|
`BORDER_SUBTLE_HC` when high-contrast mode is on. Compares
|
||||||
|
current colour and only mutates when different so Bevy's
|
||||||
|
change-detection doesn't trigger repaints every frame. New
|
||||||
|
`HighContrastBorder { default_color: Color }` component carries
|
||||||
|
the off-state colour at each tagged site so the system can
|
||||||
|
revert correctly.
|
||||||
|
- **HC chrome rollout — 8 tagged surfaces** (`c9af1ea` modal
|
||||||
|
scaffold; `d87761d` tooltip + onboarding key chips + help
|
||||||
|
panel key chips + stats panel cells; `ec804d5` home Level/XP/
|
||||||
|
Score row + home mode-selector buttons + home mode-hotkey
|
||||||
|
chips + 4 settings panel surfaces). Each tagging is one line
|
||||||
|
on the spawn tuple. The marker-component architecture pays
|
||||||
|
back proportionally to the number of consumers — the per-
|
||||||
|
commit cost dropped from ~75 lines (foundation + first
|
||||||
|
surface) to ~13 lines (4 surfaces) to ~9 lines (7 surfaces).
|
||||||
|
- **Floating MOVE chip during replay** (`2fb2d63`). New
|
||||||
|
`ReplayFloatingProgressChip` marker on a `Text2d` entity
|
||||||
|
rendered in 2D world space above the destination pile of the
|
||||||
|
most-recently-applied move. Sibling of the banner overlay (not
|
||||||
|
a child) because it lives in world-space coordinates, not the
|
||||||
|
UI tree. Lifecycle matches the banner: `spawn_overlay` spawns
|
||||||
|
the chip alongside the banner when a replay starts;
|
||||||
|
`react_to_state_change` despawns it when the replay ends.
|
||||||
|
World-space placement (rather than UI-space + camera projection)
|
||||||
|
uses the same `LayoutResource` pile coordinates that drive
|
||||||
|
every other piece of pile geometry — stays correctly positioned
|
||||||
|
through window resizes for free. Hidden when cursor=0 (no
|
||||||
|
moves applied yet) or when the last applied move was a
|
||||||
|
`StockClick` (no destination pile to follow).
|
||||||
|
- **`handle_move_rejected_toast` system + first real
|
||||||
|
`ToastVariant::Error` consumer** (`68d50b5`). When
|
||||||
|
`MoveRejectedEvent` fires (illegal placement attempt), spawns
|
||||||
|
a 2-second pink-bordered "Invalid move" toast. Joins the
|
||||||
|
existing `card_invalid.wav` (audio cue) and destination-pile
|
||||||
|
shake (visual cue) as the accessibility-focused readable text
|
||||||
|
channel — covers deaf players (no audio reliance) and
|
||||||
|
reduce-motion players (no shake reliance) with a persistent
|
||||||
|
~2 s text cue. Drops the `#[allow(dead_code)]` from
|
||||||
|
`ToastVariant::Error` and updates its doc to point at the new
|
||||||
|
consumer.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Splash scanline overlay skipped under reduce-motion**
|
||||||
|
(`ed152e2`). `spawn_splash` reads `Settings::reduce_motion_mode`
|
||||||
|
and skips the scanline texture / overlay node entirely when
|
||||||
|
on. Without the scanlines the boot screen still reads as
|
||||||
|
terminal-themed (foreground content, borders, palette swatches
|
||||||
|
unchanged); the scanlines are decorative.
|
||||||
|
- **Splash cursor pulse held under reduce-motion** (`ed152e2`).
|
||||||
|
`pulse_splash_cursor` reads `Settings::reduce_motion_mode` and
|
||||||
|
skips the per-frame sine-pulse multiplier when on — the cursor
|
||||||
|
still fades in / out with the global splash alpha (essential
|
||||||
|
timing) but doesn't blink. Spec calls out non-essential motion
|
||||||
|
as the reduce-motion target; the global fade is essential
|
||||||
|
(otherwise the splash would hard-cut on/off, which is
|
||||||
|
jarring), and the cursor blink is decorative.
|
||||||
|
- **`AnimationPlugin::build` registers
|
||||||
|
`MoveRejectedEvent`** (`68d50b5`). Bevy's `add_message` is
|
||||||
|
idempotent, so the duplicate registration with
|
||||||
|
`feedback_anim_plugin` (which already registered the message)
|
||||||
|
coexists cleanly. Required for the new
|
||||||
|
`handle_move_rejected_toast` system to run under
|
||||||
|
MinimalPlugins (tests).
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- `docs/ui-mockups/design-system.md` and `SESSION_HANDOFF.md`
|
||||||
|
refreshed in lockstep with the rollouts. The handoff's
|
||||||
|
Resume-prompt menu trimmed twice this cycle as Options A and F
|
||||||
|
closed in v0.21.1, then this commit cycle's accessibility
|
||||||
|
extensions implicitly closed the "future scope" footnotes
|
||||||
|
v0.21.1 left on F's documentation.
|
||||||
|
|
||||||
|
### Stats
|
||||||
|
|
||||||
|
- **1195 passing tests / 0 failing** across the workspace
|
||||||
|
(net +3 from v0.21.1's 1192 baseline). New tests added by
|
||||||
|
this cycle:
|
||||||
|
- `splash_skips_scanline_overlay_under_reduce_motion`
|
||||||
|
(`ed152e2`) pins the reduce-motion gate on the splash
|
||||||
|
scanline overlay. Discovered an asset-fixture bootstrapping
|
||||||
|
detail along the way: under `MinimalPlugins`,
|
||||||
|
`Assets<Image>` isn't auto-inserted; the test had to add
|
||||||
|
`bevy::asset::AssetPlugin::default()` and
|
||||||
|
`init_asset::<bevy::image::Image>()`. Pattern flagged for
|
||||||
|
future asset-using tests.
|
||||||
|
- `floating_chip_spawns_and_despawns_with_overlay`
|
||||||
|
(`2fb2d63`) pins the floating MOVE chip's lifecycle:
|
||||||
|
absent on Inactive, exactly one on Playing, absent again
|
||||||
|
on return to Inactive.
|
||||||
|
- `move_rejected_event_spawns_error_toast` (`68d50b5`) pins
|
||||||
|
the new toast wiring: firing a `MoveRejectedEvent` spawns
|
||||||
|
exactly one `ToastOverlay` on the next tick.
|
||||||
|
- Zero clippy warnings under `cargo clippy --workspace
|
||||||
|
--all-targets -- -D warnings`.
|
||||||
|
- `cargo test --workspace` clean.
|
||||||
|
|
||||||
|
## [0.21.1] — 2026-05-08
|
||||||
|
|
||||||
|
Patch release for the post-v0.21.0 work — closes Resume-prompt
|
||||||
|
Options A (app icon) and F (high-contrast + reduce-motion
|
||||||
|
accessibility modes), plus a card-visual iteration cycle that
|
||||||
|
moved through three states: the v0.21.0 Terminal pink/gray, a
|
||||||
|
brief 4-colour-deck experiment (hearts pink, diamonds gold,
|
||||||
|
clubs lime, spades gray), and a reversion to traditional 2-colour
|
||||||
|
"Microsoft Solitaire on dark mode" pairing (saturated red +
|
||||||
|
near-white). Two visible bugs surfaced and were fixed during
|
||||||
|
the iteration: the suit-coloured border produced anti-aliasing
|
||||||
|
artifacts at rounded card corners (border dropped entirely),
|
||||||
|
and the pile-marker sprite bleed-through created visible "gray
|
||||||
|
L" shapes where cards sat on markers (markers now hide when
|
||||||
|
occupied — the documented but previously-not-enforced "remain
|
||||||
|
visible only where a pile is empty" invariant).
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Desktop window icon** (`3eb3a26`). Runtime `Window::icon`
|
||||||
|
wired via `WinitWindows`; embedded 256 px PNG decoded on
|
||||||
|
startup via `tiny_skia` and handed to winit. Plus a 9-size
|
||||||
|
PNG hierarchy at `assets/icon/icon_<size>.png` covering
|
||||||
|
Linux hicolor (16/24/32/48/64/128/256/512), Windows `.ico`
|
||||||
|
targets (16/32/48/256), and macOS `.icns` targets
|
||||||
|
(16/32/64/128/256/512/1024). All sizes generated from a
|
||||||
|
shared `icon_svg` builder (Terminal `▌RS` mark on dark
|
||||||
|
`#151515` with brick-red accent) by a new
|
||||||
|
`icon_generator` example. Pin test `icon_svg_pin` guards
|
||||||
|
rasterised RGBA bytes against `usvg`/`resvg` drift. Two
|
||||||
|
new `solitaire_app` deps target-gated to non-Android:
|
||||||
|
direct `winit = "0.30"` (for `Icon` construction —
|
||||||
|
`bevy_winit` 0.18 doesn't re-export it) and direct
|
||||||
|
`tiny-skia` (for PNG → RGBA decode). Android draws its
|
||||||
|
launcher icon from the APK manifest, so neither dep is
|
||||||
|
needed there.
|
||||||
|
- **`Settings::high_contrast_mode` flag** (`c5787c6`). Boosts
|
||||||
|
card text colours: hearts/diamonds → `RED_SUIT_COLOUR_HC`
|
||||||
|
(`#ff6868`), clubs/spades → `TEXT_PRIMARY_HC` (`#f5f5f5`).
|
||||||
|
Composes with `color_blind_mode`: CBM lime wins over HC red
|
||||||
|
on red suits when both are on; HC still applies to dark
|
||||||
|
suits independent of CBM. Six new tests pin the truth
|
||||||
|
table.
|
||||||
|
- **`Settings::reduce_motion_mode` flag** (`c5787c6`). Forces
|
||||||
|
`effective_slide_secs` to `0.0` regardless of the
|
||||||
|
`AnimSpeed` selection, making cards snap instantly to their
|
||||||
|
target. Two new tests pin the gate behaviour and the
|
||||||
|
fall-through to `anim_speed_to_secs` when off. Future
|
||||||
|
scope: gate splash scanline / cursor pulse / warning-chip
|
||||||
|
pulse on the same flag.
|
||||||
|
- **Settings UI toggle rows** (`07e0357`). Two new rows in
|
||||||
|
the Settings panel under Cosmetic (alongside Color-blind):
|
||||||
|
"High Contrast" and "Reduce Motion". `tab-walk` order
|
||||||
|
visits all three accessibility flags in one vertical run.
|
||||||
|
Same shape as the existing `ColorBlindText` toggle scaffold
|
||||||
|
with marker components, label updaters, click handlers,
|
||||||
|
and disambiguator chains.
|
||||||
|
- **`sync_pile_marker_visibility` system** (`4d48cad`).
|
||||||
|
Implements the module-level doc invariant in `table_plugin`
|
||||||
|
("pile markers ... remain visible only where a pile is
|
||||||
|
empty") that was previously declared but not enforced.
|
||||||
|
Hides the pile-marker sprite for any pile that has a card
|
||||||
|
on top, shows it for empty piles. Closes the "gray L
|
||||||
|
corners" artifact where the marker's translucent fill bled
|
||||||
|
through the rounded card corners.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Card-face suit colours** (`62b61cc` → `ddb6540`). Started
|
||||||
|
the cycle at v0.21.0's Terminal pink (`#fb9fb1`) / gray
|
||||||
|
(`#d0d0d0`), briefly experimented with a 4-colour deck
|
||||||
|
(`62b61cc` — hearts pink, diamonds gold, clubs lime, spades
|
||||||
|
gray) for faster suit recognition by hue alone, then
|
||||||
|
reverted to traditional 2-colour pairing at the player's
|
||||||
|
request (`ddb6540`). Final state: `RED_SUIT_COLOUR =
|
||||||
|
#e35353` (saturated red, replacing the v0.21.0 pink) and
|
||||||
|
`BLACK_SUIT_COLOUR = #e8e8e8` (near-white, brighter than
|
||||||
|
the v0.21.0 `#d0d0d0` foreground gray so the dark suits
|
||||||
|
read as a chromatic-neutral counterpart to the saturated
|
||||||
|
red rather than as "the same gray as body text"). Reads
|
||||||
|
like Microsoft Solitaire on dark mode. `RED_SUIT_COLOUR_HC`
|
||||||
|
rebumped to `#ff6868` (brighter saturated red) so HC stays
|
||||||
|
more chromatic than the new default red rather than the
|
||||||
|
previous pinker boost. The 4-colour experiment's commit
|
||||||
|
history is preserved in the log; net delta vs. v0.21.0 is
|
||||||
|
the new red + new near-white.
|
||||||
|
- **Card-face border dropped** (`dd97021`). The earlier 1 px
|
||||||
|
suit-coloured stroke on the card body produced
|
||||||
|
anti-aliasing artifacts at the rounded corners (the colored
|
||||||
|
stroke faded through gray pixels into the play surface).
|
||||||
|
Cards now have no border — body fill alone defines the
|
||||||
|
shape against the play surface; the 5-unit brightness gap
|
||||||
|
between `#1a1a1a` body and `#151515` surface is enough to
|
||||||
|
read as a card edge without an explicit stroke.
|
||||||
|
`design-system.md` § Game Cards line 225 updated in
|
||||||
|
lockstep.
|
||||||
|
- **Settings UI accessibility row count** (`07e0357`). Three
|
||||||
|
toggles in Cosmetic now: Color-blind, High Contrast,
|
||||||
|
Reduce Motion. Existing query-disambiguator chains in
|
||||||
|
`handle_settings_buttons` extended with `Without<HighContrastText>`
|
||||||
|
and `Without<ReduceMotionText>` so the new components
|
||||||
|
don't ambiguate the existing mutations.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Bevy 0.18 system-param validation panic on icon startup**
|
||||||
|
(`716a025`). `NonSend<WinitWindows>` failed validation on
|
||||||
|
the first few frames before winit's `Resumed` event populated
|
||||||
|
the resource. Bevy 0.18's stricter validation panics rather
|
||||||
|
than skips when a non-send resource is absent; the error
|
||||||
|
message itself spelled out the fix ("wrap the parameter in
|
||||||
|
`Option<T>` and handle `None` when it happens"). Wraps
|
||||||
|
`winit_windows` as `Option<NonSend<WinitWindows>>` and
|
||||||
|
early-returns on `None`.
|
||||||
|
- **"Gray L corners" on cards** (`4d48cad`). Two artifacts
|
||||||
|
were producing similar-looking grey at card corners: the
|
||||||
|
SVG stroke fading through gray pixels (closed by `dd97021`)
|
||||||
|
and the pile-marker sprite bleeding through the rounded
|
||||||
|
cutouts (closed by `4d48cad`). Right test target, wrong
|
||||||
|
visible-artifact target on the first attempt — the pin
|
||||||
|
test correctly drifted 52 face hashes, but the visible
|
||||||
|
gray came from a different layer. Two layers, two fixes;
|
||||||
|
the second closed the player-visible complaint.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- `docs/ui-mockups/design-system.md` § Suit Colors retitled
|
||||||
|
through three states (Terminal 2-color → "Four-color
|
||||||
|
deck" → final "Two-color traditional pairing"). Final
|
||||||
|
table records the saturated red + near-white. § Game Cards
|
||||||
|
border spec changed from "1px solid in suit color" to
|
||||||
|
"Border: none" with the artifact-rationale audit trail.
|
||||||
|
CBM section text updated through each colour-scheme
|
||||||
|
iteration.
|
||||||
|
- `SESSION_HANDOFF.md` refreshed twice this cycle (`0c1cc40`
|
||||||
|
+ `31139ae`) — the first reset the post-v0.21.0 narrative
|
||||||
|
("no threads in flight"), the second recorded Options A +
|
||||||
|
F closures and trimmed the Resume-prompt menu.
|
||||||
|
- New module-level doc strings on the new constants
|
||||||
|
(`RED_SUIT_COLOUR_HC`, `TEXT_PRIMARY_HC`, `BORDER_SUBTLE_HC`,
|
||||||
|
`RED_SUIT_COLOUR_CBM` semantic shift) record the
|
||||||
|
composability rules between CBM and HC and the "what to
|
||||||
|
use this for" rationale.
|
||||||
|
|
||||||
|
### Stats
|
||||||
|
|
||||||
|
- **1192 passing tests / 0 failing** across the workspace
|
||||||
|
(net +8 from v0.21.0's 1184 baseline). New tests added by
|
||||||
|
this release:
|
||||||
|
- `card_face_svg_pin` integration test rebaselined three
|
||||||
|
times during the suit-colour iteration; final hashes
|
||||||
|
pin the saturated-red + near-white + no-border state.
|
||||||
|
- 4 high-contrast text_colour tests + 2 reduce-motion
|
||||||
|
`effective_slide_secs` tests in `card_plugin` /
|
||||||
|
`animation_plugin` (from `c5787c6`).
|
||||||
|
- 1 `icon_svg_pin` integration test guarding the icon
|
||||||
|
rasterisation pipeline (from `48b28d2` — actually
|
||||||
|
landed in v0.21.0's accounting but worth noting for the
|
||||||
|
cycle).
|
||||||
|
- 1 `pile_markers_hide_when_pile_is_occupied` test pinning
|
||||||
|
the new visibility-by-occupancy invariant (from
|
||||||
|
`4d48cad`).
|
||||||
|
- Zero clippy warnings under `cargo clippy --workspace
|
||||||
|
--all-targets -- -D warnings`.
|
||||||
|
- `cargo test --workspace` clean.
|
||||||
|
|
||||||
|
## [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
|
## [0.18.0] — 2026-05-06
|
||||||
|
|
||||||
|
|||||||
@@ -126,6 +126,19 @@ dependencies = [
|
|||||||
"subtle",
|
"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]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.4"
|
version = "1.1.4"
|
||||||
@@ -719,7 +732,7 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"ctrlc",
|
"ctrlc",
|
||||||
"downcast-rs",
|
"downcast-rs 2.0.2",
|
||||||
"log",
|
"log",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"variadics_please",
|
"variadics_please",
|
||||||
@@ -753,7 +766,7 @@ dependencies = [
|
|||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"disqualified",
|
"disqualified",
|
||||||
"downcast-rs",
|
"downcast-rs 2.0.2",
|
||||||
"either",
|
"either",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-lite",
|
"futures-lite",
|
||||||
@@ -801,7 +814,7 @@ dependencies = [
|
|||||||
"bevy_utils",
|
"bevy_utils",
|
||||||
"bevy_window",
|
"bevy_window",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"downcast-rs",
|
"downcast-rs 2.0.2",
|
||||||
"serde",
|
"serde",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
@@ -1200,7 +1213,7 @@ dependencies = [
|
|||||||
"bevy_utils",
|
"bevy_utils",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"disqualified",
|
"disqualified",
|
||||||
"downcast-rs",
|
"downcast-rs 2.0.2",
|
||||||
"erased-serde",
|
"erased-serde",
|
||||||
"foldhash 0.2.0",
|
"foldhash 0.2.0",
|
||||||
"glam 0.30.10",
|
"glam 0.30.10",
|
||||||
@@ -1259,7 +1272,7 @@ dependencies = [
|
|||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"downcast-rs",
|
"downcast-rs 2.0.2",
|
||||||
"encase",
|
"encase",
|
||||||
"fixedbitset",
|
"fixedbitset",
|
||||||
"glam 0.30.10",
|
"glam 0.30.10",
|
||||||
@@ -1854,6 +1867,18 @@ dependencies = [
|
|||||||
"thiserror 1.0.69",
|
"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]]
|
[[package]]
|
||||||
name = "cbc"
|
name = "cbc"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -2709,6 +2734,12 @@ version = "0.15.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "downcast-rs"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "downcast-rs"
|
name = "downcast-rs"
|
||||||
version = "2.0.2"
|
version = "2.0.2"
|
||||||
@@ -5792,6 +5823,15 @@ version = "2.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
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]]
|
[[package]]
|
||||||
name = "quinn"
|
name = "quinn"
|
||||||
version = "0.11.9"
|
version = "0.11.9"
|
||||||
@@ -6165,7 +6205,7 @@ dependencies = [
|
|||||||
"pico-args",
|
"pico-args",
|
||||||
"rgb",
|
"rgb",
|
||||||
"svgtypes",
|
"svgtypes",
|
||||||
"tiny-skia",
|
"tiny-skia 0.12.0",
|
||||||
"usvg",
|
"usvg",
|
||||||
"zune-jpeg",
|
"zune-jpeg",
|
||||||
]
|
]
|
||||||
@@ -6502,6 +6542,19 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
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]]
|
[[package]]
|
||||||
name = "sec1"
|
name = "sec1"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
@@ -6846,6 +6899,31 @@ dependencies = [
|
|||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
name = "smol_str"
|
name = "smol_str"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -6879,6 +6957,8 @@ dependencies = [
|
|||||||
"keyring",
|
"keyring",
|
||||||
"solitaire_data",
|
"solitaire_data",
|
||||||
"solitaire_engine",
|
"solitaire_engine",
|
||||||
|
"tiny-skia 0.12.0",
|
||||||
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6938,7 +7018,7 @@ dependencies = [
|
|||||||
"solitaire_sync",
|
"solitaire_sync",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tiny-skia",
|
"tiny-skia 0.12.0",
|
||||||
"tokio",
|
"tokio",
|
||||||
"usvg",
|
"usvg",
|
||||||
"uuid",
|
"uuid",
|
||||||
@@ -7525,7 +7605,7 @@ dependencies = [
|
|||||||
"crc32fast",
|
"crc32fast",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"datasketches",
|
"datasketches",
|
||||||
"downcast-rs",
|
"downcast-rs 2.0.2",
|
||||||
"fastdivide",
|
"fastdivide",
|
||||||
"fnv",
|
"fnv",
|
||||||
"fs4",
|
"fs4",
|
||||||
@@ -7577,7 +7657,7 @@ version = "0.7.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c57166f5bcfd478f370ab8445afb4678dce44801fa5ce5c451aaf8595583c5dc"
|
checksum = "c57166f5bcfd478f370ab8445afb4678dce44801fa5ce5c451aaf8595583c5dc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"downcast-rs",
|
"downcast-rs 2.0.2",
|
||||||
"fastdivide",
|
"fastdivide",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -7765,6 +7845,20 @@ dependencies = [
|
|||||||
"time-core",
|
"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]]
|
[[package]]
|
||||||
name = "tiny-skia"
|
name = "tiny-skia"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
@@ -7777,7 +7871,18 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
"log",
|
"log",
|
||||||
"png 0.18.1",
|
"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]]
|
[[package]]
|
||||||
@@ -8548,7 +8653,7 @@ dependencies = [
|
|||||||
"siphasher",
|
"siphasher",
|
||||||
"strict-num",
|
"strict-num",
|
||||||
"svgtypes",
|
"svgtypes",
|
||||||
"tiny-skia-path",
|
"tiny-skia-path 0.12.0",
|
||||||
"ttf-parser",
|
"ttf-parser",
|
||||||
"unicode-bidi",
|
"unicode-bidi",
|
||||||
"unicode-script",
|
"unicode-script",
|
||||||
@@ -8754,6 +8859,114 @@ dependencies = [
|
|||||||
"semver",
|
"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]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.97"
|
version = "0.3.97"
|
||||||
@@ -9569,6 +9782,7 @@ version = "0.30.13"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d"
|
checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
"android-activity",
|
"android-activity",
|
||||||
"atomic-waker",
|
"atomic-waker",
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
@@ -9583,6 +9797,7 @@ dependencies = [
|
|||||||
"dpi",
|
"dpi",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
|
"memmap2",
|
||||||
"ndk",
|
"ndk",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-app-kit 0.2.2",
|
"objc2-app-kit 0.2.2",
|
||||||
@@ -9594,11 +9809,17 @@ dependencies = [
|
|||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"redox_syscall 0.4.1",
|
"redox_syscall 0.4.1",
|
||||||
"rustix 0.38.44",
|
"rustix 0.38.44",
|
||||||
|
"sctk-adwaita",
|
||||||
|
"smithay-client-toolkit",
|
||||||
"smol_str",
|
"smol_str",
|
||||||
"tracing",
|
"tracing",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-client",
|
||||||
|
"wayland-protocols",
|
||||||
|
"wayland-protocols-plasma",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"web-time",
|
"web-time",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
@@ -9766,6 +9987,12 @@ version = "0.13.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xcursor"
|
||||||
|
version = "0.3.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xkbcommon-dl"
|
name = "xkbcommon-dl"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
|||||||
@@ -54,12 +54,24 @@ bevy = { version = "0.18", default-features = false, features = [
|
|||||||
"bevy_window",
|
"bevy_window",
|
||||||
"custom_cursor",
|
"custom_cursor",
|
||||||
"reflect_auto_register",
|
"reflect_auto_register",
|
||||||
# default_platform (desktop subset; no android/wayland/webgl/gilrs/sysinfo)
|
# default_platform (desktop subset)
|
||||||
"std",
|
"std",
|
||||||
"bevy_winit",
|
"bevy_winit",
|
||||||
"default_font",
|
"default_font",
|
||||||
"multi_threaded",
|
"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",
|
"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
|
# common_api
|
||||||
"bevy_color",
|
"bevy_color",
|
||||||
"bevy_image",
|
"bevy_image",
|
||||||
|
|||||||
@@ -1,179 +1,285 @@
|
|||||||
# Solitaire Quest — Session Handoff
|
# Solitaire Quest — Session Handoff
|
||||||
|
|
||||||
**Last updated:** 2026-05-06 (post-v0.18.0 draft) — 24 commits since
|
**Last updated:** 2026-05-08 — **v0.21.1 cut and tagged at `daa655a`**,
|
||||||
the v0.17.0 tag bundle the launch-experience round (Restore prompt +
|
working tree clean, all post-tag work pushed to origin.
|
||||||
auto-show Home / mode picker), the MSSC-style Home picker rework
|
|
||||||
(header chips, draw-mode chips, picture-tile mode cards, Today's
|
v0.21.1 is a patch release for the post-v0.21.0 work: closes
|
||||||
Event callout, glyph fixes), the last solver hot path moving onto
|
Resume-prompt Options A (app icon — runtime `Window::icon` plus
|
||||||
`AsyncComputeTaskPool`, "Won before" HUD chip, "Copy share link"
|
the 9-size PNG hierarchy) and F (high-contrast + reduce-motion
|
||||||
Stats button, the `N` keybinding finally routing through the real
|
accessibility modes — Settings flags wired through engine and
|
||||||
Confirm/Cancel modal, Esc-on-modal layering fixes, and the
|
UI). Plus a card-visual iteration cycle that moved through three
|
||||||
unified-3.0 Claude rule set (CLAUDE.md / CLAUDE_SPEC.md /
|
states (v0.21.0 Terminal pink/gray → brief 4-colour-deck
|
||||||
CLAUDE_WORKFLOW.md / CLAUDE_PROMPT_PACK.md). Test-discipline prune
|
experiment → traditional 2-colour Microsoft-Solitaire-on-dark-mode
|
||||||
removed 43 low-value tests in the same window.
|
red/near-white) and two visible-bug fixes (suit-coloured border
|
||||||
|
anti-aliasing artifact at rounded corners, pile-marker
|
||||||
|
bleed-through producing "gray L" shapes at occupied piles —
|
||||||
|
the latter implemented the previously-documented-but-not-enforced
|
||||||
|
"markers visible only at empty piles" invariant).
|
||||||
|
|
||||||
|
Full v0.21.1 detail lives in `CHANGELOG.md` § [0.21.1]. This
|
||||||
|
file from here on focuses on what's *open* post-cut and how to
|
||||||
|
resume.
|
||||||
|
|
||||||
## Status at pause
|
## Status at pause
|
||||||
|
|
||||||
- **HEAD on origin:** `v0.17.0-24-gc497c31` (24 ahead of v0.17.0,
|
- **HEAD locally:** see `git rev-parse HEAD`. The cut commit is
|
||||||
not yet tagged).
|
`daa655a`; any post-cut docs edits ride on top of that.
|
||||||
- **Working tree:** clean.
|
- **HEAD on origin:** matches local. v0.21.1 is fully on origin.
|
||||||
|
- **Working tree:** clean. No WIP outstanding.
|
||||||
|
- **`artwork/` directory:** still untracked. Intentional.
|
||||||
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings`
|
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings`
|
||||||
clean (verified this session).
|
clean.
|
||||||
- **Tests:** **1166 passing / 0 failing** across the workspace
|
- **Tests:** **1192 passing / 0 failing** across the workspace
|
||||||
(verified this session). The first run flaked once on
|
(net +8 from v0.21.0's 1184 baseline). Detail in
|
||||||
`solitaire_engine::game_plugin::tests::auto_save_writes_after_30_seconds`
|
`CHANGELOG.md` § [0.21.1] § Stats.
|
||||||
— a one-frame `app.update()` test that depends on `time.delta_secs()`
|
- **Tags on origin:** `v0.9.0` through `v0.21.1`. v0.21.1 is on
|
||||||
on an otherwise-fresh `App`. Reproduced clean on the second run;
|
`daa655a`; v0.21.0 stays on `04f9bf9`; v0.20.0 stays on
|
||||||
passes in isolation. Worth tightening if it flakes again, but
|
`41a009a`.
|
||||||
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.
|
|
||||||
|
|
||||||
## Where we are
|
## Since the v0.21.1 cut
|
||||||
|
|
||||||
v0.17.0's punch list had four candidates (A–D); two of the three
|
No threads in flight. Working tree clean as of 2026-05-08. New
|
||||||
non-packaging items shipped in this round:
|
work since the cut would land here as commit narratives; for
|
||||||
|
the v0.21.1 contents themselves, see `CHANGELOG.md` § [0.21.1].
|
||||||
|
|
||||||
- **B — "Won previously" HUD indicator:** shipped in `bdac754`.
|
## Open punch list
|
||||||
- **C — Replay sharing:** shipped in `540869c` ("Copy share link"
|
|
||||||
Stats button + clipboard via `arboard`, in-memory `LastSharedReplayUrl`).
|
|
||||||
|
|
||||||
Item **A** (solver-on-`AsyncComputeTaskPool`) shipped *partially* in
|
### Phase Android (build + persistence shipped; runtime gaps remain)
|
||||||
`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.
|
|
||||||
|
|
||||||
Item **D** (desktop packaging) is unchanged — still gated on
|
- **APK launch verification on AVD / device.** `adb install` then
|
||||||
artwork + signing certs from the player.
|
`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.
|
||||||
|
|
||||||
The launch experience is also substantially different from v0.17.0:
|
### Visual-identity follow-ups (post-v0.21.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.
|
|
||||||
|
|
||||||
### Design direction (unchanged)
|
The visual-identity arc is effectively complete: token system,
|
||||||
|
chrome migration, splash boot screen, replay-overlay banner,
|
||||||
|
card-face artwork (both rendering paths), and the `ACCENT_PRIMARY`
|
||||||
|
palette refresh all shipped in v0.20.0 + v0.21.0. What stays open:
|
||||||
|
|
||||||
- **Tone:** Balatro — chunky readable type, theatrical hierarchy,
|
- **Replay-overlay screen-takeover redesign.** The full mockup
|
||||||
satisfying micro-interactions.
|
(`docs/ui-mockups/replay-overlay-mobile.html`) calls for a
|
||||||
- **Palette:** Midnight Purple base + Balatro yellow primary + warm
|
mini-tableau preview, playback controls, move-log scroll, and
|
||||||
magenta secondary.
|
a WIN MOVE marker on the scrub bar. Banner-local pieces all
|
||||||
- See `~/.claude/projects/-home-manage-Rusty-Solitare/memory/project_ux_overhaul_2026-04.md`
|
shipped in v0.21.0 (`c84d9f4` + `6204db8` + `54005d5` +
|
||||||
(machine-local).
|
`e080b49`); the screen-takeover is a multi-session redesign
|
||||||
|
with data-layer impact (move-log scroller; WIN MOVE needs a
|
||||||
|
`win_move_index` field on `Replay` that doesn't yet exist).
|
||||||
|
- **Floating `MOVE N/M` chip above the focused card during
|
||||||
|
playback.** Cross-plugin work — `update_progress_text` writes
|
||||||
|
the banner chip but the card-position lookup belongs in
|
||||||
|
`card_plugin`. Smaller scope than the screen-takeover.
|
||||||
|
- **Toast Warning / Error variants.** `ToastVariant` 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.
|
||||||
|
- *High-contrast accessibility mode — closed 2026-05-08 by
|
||||||
|
`c5787c6` + `07e0357`.* Card text rendering picks up
|
||||||
|
`TEXT_PRIMARY_HC` (`#f5f5f5`) and `RED_SUIT_COLOUR_HC`
|
||||||
|
(`#ff8aa0`); Settings panel has a toggle. Future scope:
|
||||||
|
extend HC through chrome borders (`BORDER_SUBTLE_HC` already
|
||||||
|
defined, not yet consumed), buttons, popover edges.
|
||||||
|
- *Reduced-motion mode — closed 2026-05-08 by the same pair.*
|
||||||
|
`effective_slide_secs` forces 0 when on, regardless of the
|
||||||
|
`AnimSpeed` setting. Future scope: gate splash scanline
|
||||||
|
overlay + cursor pulse animation on the same flag, gate
|
||||||
|
warning-chip pulse, gate any future card-lift z-bump
|
||||||
|
animation.
|
||||||
|
|
||||||
|
### Carried forward from v0.19.0
|
||||||
|
|
||||||
|
- *App icon round — closed 2026-05-08 by `3eb3a26` + `716a025`.*
|
||||||
|
Runtime `Window::icon` wired (Linux/macOS/Windows); 9-size
|
||||||
|
PNG hierarchy at `assets/icon/icon_<size>.png` covers Linux
|
||||||
|
hicolor + downstream `.icns`/`.ico` packaging needs. The
|
||||||
|
`.ico` and `.icns` bundle-format files themselves are *not*
|
||||||
|
generated — both would need new crate deps (`ico` and
|
||||||
|
`icns` respectively) and only matter at app-bundle time
|
||||||
|
(cargo-bundle / packaging), not at `cargo run`. Open if the
|
||||||
|
project later ships as a packaged macOS / Windows app.
|
||||||
|
|
||||||
|
### 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
|
### Canonical remote
|
||||||
|
|
||||||
`github.com/funman300/Rusty_Solitaire` is the canonical repo.
|
`github.com/funman300/Rusty_Solitaire` is the canonical repo.
|
||||||
Always push there.
|
Always push there. As of v0.21.0 origin matches local; the next
|
||||||
|
push happens when post-cut work accumulates and is ready to roll
|
||||||
|
into a v0.21.1 / v0.22.0 cut.
|
||||||
|
|
||||||
## v0.18.0 (drafted 2026-05-06, not yet tagged)
|
### Design direction (Terminal — base16-eighties)
|
||||||
|
|
||||||
| Area | Commit | What landed |
|
- **Tone:** retro-terminal / synthwave — flat depth (no box-shadows),
|
||||||
|---|---|---|
|
monospaced-forward typography (JetBrains Mono / FiraMono), tight
|
||||||
| Restore prompt | `3c7a0eb` + `f863d85` | Welcome-back modal on launch when an in-progress save exists; save preserved across exits while the prompt is unanswered. |
|
16 px edge margins, 8 px card radius.
|
||||||
| 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. |
|
- **Palette:** near-black surface ramp (`#151515` / `#202020` /
|
||||||
| Won-before HUD chip | `bdac754` | Reads `ReplayHistoryResource`; lights `✓ Won before` on tier-2 row when current `(seed, draw_mode, mode)` is in history. |
|
`#2a2a2a` / `#353535`), brick-red primary CTA (`#a54242` —
|
||||||
| Copy share link | `540869c` | `arboard` clipboard + new Stats button + `SyncProvider::push_replay` returning the share URL. In-memory only; per-session sharing. |
|
swapped from cyan `#6fc2ef` in v0.21.0 commit `a292a7e`), lime
|
||||||
| 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 (♣ ◆ ○ ▲ →). |
|
success (`#acc267`), gold warning (`#ddb26f`), pink error /
|
||||||
| Auto-show Home | `dd63261`, `b7c3a49`, `c497c31` | Auto-shows after splash; gated on Restore prompt; freezes timers (elapsed + Time Attack) while up. |
|
suit-red (`#fb9fb1`), lavender celebration (`#e1a3ee`), teal
|
||||||
| `N` opens real modal | `93660c2` | Removes the "Press N again" double-tap; routes through `ConfirmNewGameScreen`. `Shift+N` retains the bypass. |
|
info (`#12cfc0`).
|
||||||
| Win Summary keyboard | `17e0737` | Enter dismisses + starts a fresh deal. |
|
- **Two-color suits.** Red = `#fb9fb1`, black = `#d0d0d0`.
|
||||||
| 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. |
|
Outlined glyphs for diamonds & clubs are *always on*; the
|
||||||
| Layout fixes | `a4bc063`, `cc63532` | Settings rows full-width with label-spacer-cluster; popover rows excluded from action-bar auto-fade. |
|
Settings "color-blind mode" toggle swaps red → lime `#acc267`
|
||||||
| Empty-state copy | `56e2e6f` | Leaderboard / Achievements onboarding hints; volume hotkeys emit toast feedback. |
|
(was red → cyan pre-v0.21.0; lime is the next-best non-red
|
||||||
| Test prune | `a49a340` | −43 low-value tests; future briefs request behaviour contracts only. |
|
base16-eighties accent now that the primary itself is red).
|
||||||
| Docs unified-3.0 | `f2f30c8` | Adopts CLAUDE.md / CLAUDE_SPEC.md / CLAUDE_WORKFLOW.md / CLAUDE_PROMPT_PACK.md; trims duplicated rule passages. |
|
- **Card glyphs render upright in both corners** — no 180°
|
||||||
|
inverted-corner-indicator rotation. Single-orientation
|
||||||
## Open punch list
|
digital play doesn't benefit from the traditional flip-
|
||||||
|
readback convention. `design-system.md` § Game Cards
|
||||||
### Carried forward from v0.17.0
|
documents this deliberate deviation.
|
||||||
|
|
||||||
- **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.
|
|
||||||
|
|
||||||
## Resume prompt
|
## Resume prompt
|
||||||
|
|
||||||
```
|
```
|
||||||
You are a senior Rust + Bevy developer working on Solitaire Quest.
|
You are a senior Rust + Bevy developer working on Solitaire Quest.
|
||||||
Working directory: <Rusty_Solitaire clone path on this machine>.
|
Working directory: <Rusty_Solitaire clone path on this machine>.
|
||||||
Branch: master. Direction is OPEN — v0.18.0 has been drafted but
|
Branch: master. v0.21.1 is tagged at daa655a (cut 2026-05-08, a
|
||||||
not tagged: 24 commits past v0.17.0 cover the launch-experience
|
patch release rolling up app-icon, accessibility modes, and the
|
||||||
round, MSSC Home picker, async winnable-only seeds, Won-before
|
card-visual iteration cycle that closed Resume-prompt Options A
|
||||||
HUD, Copy share link, N-key flow rework, Esc-layering fixes, and
|
and F). v0.21.0 stays at 04f9bf9. Working tree clean. See
|
||||||
the unified-3.0 Claude rule set.
|
CHANGELOG.md § [0.21.1] for full detail of what shipped in the
|
||||||
|
patch release.
|
||||||
|
|
||||||
State: HEAD at v0.17.0-24-gc497c31. Working tree clean.
|
State: HEAD locally — see `git rev-parse HEAD`. All workspace tests
|
||||||
CHANGELOG.md has the v0.18.0 entry slotted under [Unreleased].
|
pass (1192+; check with `cargo test --workspace`), clippy clean.
|
||||||
|
|
||||||
READ FIRST (in order, before doing anything):
|
READ FIRST (in order, before doing anything):
|
||||||
1. SESSION_HANDOFF.md — this file
|
1. SESSION_HANDOFF.md — this file
|
||||||
2. CHANGELOG.md — v0.18.0 draft entry
|
2. CHANGELOG.md — [0.21.1] section is the most recent cut
|
||||||
3. CLAUDE.md — unified-3.0 rule set
|
3. CLAUDE.md — unified-3.0 rule set
|
||||||
4. CLAUDE_SPEC.md — formal architecture spec
|
4. CLAUDE_SPEC.md — formal architecture spec
|
||||||
5. ARCHITECTURE.md — crate responsibilities + data flow
|
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
|
— saved feedback / project context
|
||||||
(machine-local; may be missing on a
|
(machine-local; may be missing on a
|
||||||
fresh machine)
|
fresh machine)
|
||||||
|
|
||||||
DECISION TO ASK THE PLAYER FIRST:
|
DECISION TO ASK THE PLAYER FIRST:
|
||||||
A. Tag v0.18.0 — promote `[Unreleased]` to `[0.18.0]` (already
|
A. APK launch verification on AVD / device — `adb install` +
|
||||||
done in this session's draft), reverify build + clippy +
|
`adb logcat` to shake out runtime bugs the build / unit
|
||||||
tests, tag, push. Mechanical close-out.
|
tests can't catch. Likely surfaces JNI ClipboardManager
|
||||||
B. Solver-on-AsyncComputeTaskPool for the H-key hint, using the
|
and Android Keystore stubs that need real bridges. Larger
|
||||||
`d489e7a` seed-selection port as template. Last synchronous
|
scope; needs an Android device or emulator running.
|
||||||
solver hot path. Smallest delta on the open punch list.
|
(Was Resume-prompt B before the post-v0.21.1 menu trim.)
|
||||||
C. Desktop packaging — needs artwork + signing certs from the
|
B. Replay-overlay extensions — either the floating `MOVE N/M`
|
||||||
player; can't be driven by the agent alone.
|
chip above the focused card (smaller, cross-plugin; needs
|
||||||
D. Persistent share link — store the URL alongside replay
|
cursor → card-position plumbing in `card_plugin`) or the
|
||||||
history so cross-session sharing works.
|
full screen-takeover redesign (multi-session: move-log
|
||||||
|
scroll, mini tableau preview, WIN MOVE marker, data-layer
|
||||||
|
impact for `Replay::win_move_index`).
|
||||||
|
C. Toast Warning / Error variant wiring. UI infrastructure
|
||||||
|
exists in `ToastVariant`; no in-engine event uses Warning
|
||||||
|
(gold) or Error (pink) yet. Wire when a real warning- or
|
||||||
|
error-flavoured event materialises.
|
||||||
|
D. Phase 8 (sync) — local storage scaffolding, self-hosted
|
||||||
|
Axum server, `SolitaireServerClient` impl, GPGS stub
|
||||||
|
wired into Settings. The biggest open arc by scope; rolls
|
||||||
|
up several Phase Android dependencies (Keystore,
|
||||||
|
ClipboardManager).
|
||||||
|
E. Extend high-contrast through chrome — `BORDER_SUBTLE_HC`
|
||||||
|
was defined in v0.21.1 but isn't yet consumed; popover
|
||||||
|
edges, button borders, focus rings still use the default
|
||||||
|
non-HC tokens. Plus reduce-motion still doesn't gate
|
||||||
|
splash scanline / cursor pulse / warning-chip pulse —
|
||||||
|
v0.21.1 only gated card slide_secs. Both are small,
|
||||||
|
finite, half-day scope.
|
||||||
|
|
||||||
WORKFLOW NOTES:
|
WORKFLOW NOTES:
|
||||||
- Commits use:
|
- Use the system git config (already correct).
|
||||||
git -c user.name=funman300 -c user.email=root@vscode.infinity \
|
|
||||||
commit -m "..."
|
|
||||||
- When attributing playtester feedback in commits/docs, use
|
- When attributing playtester feedback in commits/docs, use
|
||||||
"Quat" not "Rhys" (saved feedback memory).
|
"Quat" not "Rhys" (saved feedback memory).
|
||||||
- Sub-agents stage + verify only; orchestrator commits.
|
- Sub-agents stage + verify only; orchestrator commits.
|
||||||
- Every commit must pass build / clippy / test before pushing.
|
- 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.
|
||||||
|
- Token-port pattern: when migrating tokens, walk every
|
||||||
|
concrete artifact downstream of the token (PNG textures,
|
||||||
|
embedded SVGs, hardcoded literals, comment color names),
|
||||||
|
not just the token name. v0.21.0 surfaced three "the
|
||||||
|
migration walked past this" follow-ups that all matched
|
||||||
|
this shape — codified here so future similar work can
|
||||||
|
pattern-match instead of rediscovering.
|
||||||
|
- Doc-vs-implementation drift pattern: v0.21.1's pile-marker
|
||||||
|
visibility fix (`4d48cad`) implemented an invariant that
|
||||||
|
had been declared in a module doc comment but was never
|
||||||
|
enforced in code. When future work touches a module with
|
||||||
|
a "this does X" doc comment, verify the code actually does
|
||||||
|
X and add a test if not. Two layers, two checks.
|
||||||
|
|
||||||
OPEN AT THE START: ask which of A–D. Don't pick unilaterally.
|
OPEN AT THE START: ask which of A–E. 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.3 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 283 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 300 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 357 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 300 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 318 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 365 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 179 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 256 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 263 B |
|
After Width: | Height: | Size: 369 B |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 489 B |
|
After Width: | Height: | Size: 759 B |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 927 B |
@@ -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,285 @@
|
|||||||
|
---
|
||||||
|
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 pairing**, with mandatory color-blind
|
||||||
|
support. Saturated red for hearts + diamonds, near-white for clubs
|
||||||
|
+ spades — the "Microsoft Solitaire on dark mode" feel of a real
|
||||||
|
playing-card deck. (A brief 4-color-deck experiment shipped between
|
||||||
|
v0.21.0 and the next post-cut commit; reverted to traditional
|
||||||
|
2-color at the player's request.)
|
||||||
|
|
||||||
|
| Suit | Default | Color-blind mode | Glyph differentiation |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Hearts | `#e35353` (saturated red) | `#acc267` (lime) | Solid filled glyph |
|
||||||
|
| Diamonds | `#e35353` (saturated red) | `#acc267` (lime) | **Outlined glyph (1.5px stroke)** |
|
||||||
|
| Spades | `#e8e8e8` (near-white) | `#e8e8e8` (unchanged) | Solid filled glyph |
|
||||||
|
| Clubs | `#e8e8e8` (near-white) | `#e8e8e8` (unchanged) | **Outlined glyph (1.5px stroke)** |
|
||||||
|
|
||||||
|
The outlined-glyph treatment is the **primary** differentiation mechanism. Color is supplementary. This means a player viewing the game on a monochrome display, or with severe red-green deficiency, can still distinguish all four suits without context. This is a hard requirement, not an optional setting.
|
||||||
|
|
||||||
|
The "color-blind mode" toggle in Settings swaps both red suits (hearts + diamonds) from `#e35353` to `#acc267` (lime); clubs + spades stay at the near-white. The toggle does not turn the outlined glyphs on or off, because outlined glyphs are always on. (Was red→cyan before the 2026-05-08 primary-accent swap; CBM moved to lime to stay hue-distinct from the new red-family primary.)
|
||||||
|
|
||||||
|
## Typography
|
||||||
|
|
||||||
|
**Monospace-forward, dual-font system.**
|
||||||
|
|
||||||
|
- **JetBrains Mono** is used for: HUD (score, timer, moves), card rank/value text, all labels, all headlines, all numerals anywhere in the app, and any chip-style component. This is the dominant face.
|
||||||
|
- **Inter** is used only for: long-form body copy (Help screen, Settings descriptions, achievement tooltips, onboarding copy). It is the *exception*, not the default.
|
||||||
|
|
||||||
|
Weights: 400 regular, 500 medium for labels, 700 bold for HUD numbers and headlines. No 600 / no italics anywhere — the terminal aesthetic doesn't have them.
|
||||||
|
|
||||||
|
Letter spacing: tight (`-0.02em`) on HUD score for visual mass; wide (`+0.08em`) on uppercase labels for readability at 12px. Body uses default (0).
|
||||||
|
|
||||||
|
HUD numbers must use **tabular figures** (`font-feature-settings: 'tnum'`) so the timer and score don't reflow as digits change.
|
||||||
|
|
||||||
|
## Layout & Spacing
|
||||||
|
|
||||||
|
Optimized for **Android portrait, 390×844 (Pixel 6 baseline), API 34**.
|
||||||
|
|
||||||
|
- **Margins**: 16px (1rem) edge safety margin. *Tighter than the previous system's 24px.* Eighties palettes are dense by nature; over-padding fights the aesthetic.
|
||||||
|
- **Tableau**: 7-column layout, 32px (2rem) vertical card overlap. Tighter than before to fit a longer cascade on phone screens.
|
||||||
|
- **HUD position**: top of screen, in the system safe area. Bottom 64px holds the action bar (Undo / Hint / New Game / Auto-complete). Action bar is **always visible** in-game — no hover-fade — because there is no hover on touch.
|
||||||
|
- **Touch target minimum**: 48dp on all interactive elements. Cards in the tableau may be smaller visually but use a 48dp invisible hit area centered on the visible glyph.
|
||||||
|
|
||||||
|
## Elevation & Depth
|
||||||
|
|
||||||
|
Depth is created through **tonal layering and 1px outlines**, not blur shadows. (Synthwave-flat, not Material-soft.)
|
||||||
|
|
||||||
|
- **Level 0 (Background)**: the `#151515` base canvas.
|
||||||
|
- **Level 1 (Tableau slots, empty piles)**: 1px dashed outline in `#353535`. Empty foundations show a faint suit glyph at 12% opacity inside the outline.
|
||||||
|
- **Level 2 (Cards at rest)**: solid `#1a1a1a` fill, 1px solid border in the suit color (so the suit is detectable at a glance even if the card is partially obscured).
|
||||||
|
- **Level 3 (Active / dragged card)**: same border, but glow effect: 0 0 12px of `#a54242` at 40% opacity. **No scale transform** — flatness preserved. Z-index lifts above siblings.
|
||||||
|
- **Modals**: full-screen with backdrop `#151515` at 95% opacity (just enough to dim the table without blurring it). Modal panel is `#202020` with a 1px `#505050` border — like a terminal pane.
|
||||||
|
- **Toasts**: bottom of screen, `#202020` fill, 1px border in the toast's accent color (info=teal, warning=gold, error=pink, celebration=lavender). 16px monospaced caption.
|
||||||
|
|
||||||
|
No `box-shadow` is used anywhere. **All depth is achieved with borders and tonal value.** This is a hard constraint.
|
||||||
|
|
||||||
|
## Shapes
|
||||||
|
|
||||||
|
The shape language is **soft-rounded but tight**:
|
||||||
|
|
||||||
|
- **Cards**: `rounded-md` (8px) — slightly less rounded than the previous system's 16px to read more "technical."
|
||||||
|
- **Buttons / chips / inputs**: `rounded` (4px) default, `rounded-sm` (2px) for the smallest chips.
|
||||||
|
- **Modals / sheets**: `rounded-lg` (12px).
|
||||||
|
- **Avatars / circular indicators**: `rounded-full`.
|
||||||
|
- **Card-back pattern corners**: matches the card's `rounded-md`.
|
||||||
|
|
||||||
|
Selection highlights use a **2px inset stroke** in `#a54242` following the host shape's corner radius. Never an outer stroke — the outer stroke is reserved for the suit-color hairline.
|
||||||
|
|
||||||
|
## Motion
|
||||||
|
|
||||||
|
**Snappy, no spring.** All transitions use `ease-out` with a 120ms duration unless specified.
|
||||||
|
|
||||||
|
- Card lift (start drag): 80ms.
|
||||||
|
- Card place (drop): 120ms with a 16ms holdframe (no bounce).
|
||||||
|
- Modal enter: 200ms ease-out, fade + 8px translate-up.
|
||||||
|
- Modal exit: 120ms ease-in, fade only.
|
||||||
|
- Selection ring appear: 80ms.
|
||||||
|
- Win-summary stat reveal: 60ms each, staggered 40ms.
|
||||||
|
- HUD number tick: instant (no transition) — terminal counters don't ease.
|
||||||
|
|
||||||
|
**Optional CRT effect**: a 1-frame scanline sweep across the screen on game-state transitions (start, win, restart). User-toggleable in Settings. Off by default.
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### Game Cards
|
||||||
|
|
||||||
|
Flat face design.
|
||||||
|
- Background: `#1a1a1a`
|
||||||
|
- Border: none. The card shape is defined by the body fill alone against the play surface. The earlier 1px suit-coloured border was removed because it produced visible anti-aliasing artifacts at the rounded corners (a "gray sliver" where the colored stroke faded through gray pixels into the dark play surface). The 5-unit brightness gap between `#1a1a1a` body and `#151515` surface is enough to read as a card edge without an explicit stroke.
|
||||||
|
- Top-left: rank in JetBrains Mono Bold 18px + small suit glyph (10px)
|
||||||
|
- Bottom-right: large suit glyph (32px), upright (same orientation as the top-left small glyph — single-orientation digital play does not benefit from the traditional 180° inverted-corner indicator)
|
||||||
|
- Corner radius: 8px
|
||||||
|
- Suit differentiation: hearts and spades have **filled** glyphs; diamonds and clubs have **outlined** glyphs (1.5px stroke)
|
||||||
|
|
||||||
|
### Card Back ("Terminal" theme)
|
||||||
|
|
||||||
|
- Theme name: `"Terminal"`
|
||||||
|
- Author: `"Rusty Solitaire"`
|
||||||
|
- Background: `#151515`
|
||||||
|
- Pattern: horizontal scanlines at 2px pitch in `#1a1a1a` (1px line, 1px gap), full bleed
|
||||||
|
- Border: 1px solid `#353535`
|
||||||
|
- Top-left badge: a 12×16px solid `#a54242` block (the "terminal cursor"), 6px from the corner
|
||||||
|
- Bottom-right monogram: the characters `▌RS` in JetBrains Mono 12px, color `#505050`, 6px from the corner
|
||||||
|
- Corner radius: 8px (matches face)
|
||||||
|
|
||||||
|
### Primary Buttons
|
||||||
|
|
||||||
|
Solid `#a54242` fill, `#151515` text, JetBrains Mono Medium 14px uppercase with `+0.08em` tracking. 4px corner radius. Pressed state: darken to `#7a3030`. Disabled: `#353535` fill, `#505050` text.
|
||||||
|
|
||||||
|
### Secondary Buttons
|
||||||
|
|
||||||
|
Transparent fill, 1px `#505050` border, `#d0d0d0` text. Hover/press: border becomes `#a54242`, text becomes `#a54242`.
|
||||||
|
|
||||||
|
### HUD Chips
|
||||||
|
|
||||||
|
`#202020` fill, no border, 4px radius. Monospaced 16px text. Score chip pulses to `#acc267` for 200ms when score increases.
|
||||||
|
|
||||||
|
### Drag Targets
|
||||||
|
|
||||||
|
When a card is being dragged over a valid pile, the pile's empty-slot dashed outline becomes:
|
||||||
|
- Solid 1px in `#acc267`
|
||||||
|
- Plus a 0 0 8px outer glow in `#acc267` at 30% opacity
|
||||||
|
|
||||||
|
This is the *only* place glow effects appear in the system.
|
||||||
|
|
||||||
|
### Modals
|
||||||
|
|
||||||
|
Full-screen backdrop at 95% opacity. Centered panel: `#202020` fill, 1px `#505050` border, 12px corner radius. Title bar shows the screen name in monospaced 14px, color `#a0a0a0`, with a single `▌` cursor character prefix to reinforce the terminal pane motif.
|
||||||
|
|
||||||
|
### Navigation Bar
|
||||||
|
|
||||||
|
Fixed at the bottom of in-game screens. Height: 64px. `#202020` fill, 1px top border in `#353535`. Four icon buttons: Undo / Hint / New / Auto-complete. Icons: 24px, 1.5px stroke weight, color `#d0d0d0`. Active/pressed: icon color `#a54242`.
|
||||||
|
|
||||||
|
### Status / Sync Indicator
|
||||||
|
|
||||||
|
Top-right corner of the HUD: a 6px circular dot.
|
||||||
|
- Connected & synced: `#12cfc0`
|
||||||
|
- Pending: `#ddb26f` (pulsing 1.5s)
|
||||||
|
- Error: `#fb9fb1` (steady)
|
||||||
|
- Offline: `#505050`
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
1. **Color-blind mode** (Settings → Gameplay): swaps the red suits' default `#e35353` for `#acc267` (lime). Outlined-glyph differentiation remains active in *all* modes.
|
||||||
|
2. **High-contrast mode** (Settings → Gameplay): boosts on-surface from `#d0d0d0` to `#f5f5f5`, outline from `#505050` to `#a0a0a0`, suit-red from `#fb9fb1` to `#ff8aa0`.
|
||||||
|
3. **Reduce-motion mode** (Settings → Gameplay): disables card-lift transition (instant z-lift), disables CRT scanline effect, disables the warning-chip pulse animation.
|
||||||
|
4. **Tabular figures** are mandatory for any number that updates live (timer, score, moves) so they don't reflow.
|
||||||
|
5. **Touch targets** are 48dp minimum even when the visual element is smaller.
|
||||||
|
6. **Text contrast**: all body text on background passes WCAG AA at minimum (`#d0d0d0` on `#151515` = 9.5:1; `#a0a0a0` on `#151515` = 5.7:1).
|
||||||
@@ -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>
|
||||||