chore: remove stale docs, mockups, and one-off artifacts
Build and Deploy / build-and-push (push) Failing after 43s

Delete CLAUDE_PROMPT_PACK/SPEC/WORKFLOW (superseded by CLAUDE.md),
SESSION_HANDOFF files, old android investigation notes, phase-plan docs
under docs/superpowers/, and all docs/ui-mockups/ (HTML+PNG mockups
from the pre-Terminal design pass). Also removes local artifacts:
analytics_impl_prompt.md, review_project.py, delete_runs.sh, ruvector.db
files, and the stray solitaire_wasm/solitaire_server/ build artefact.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-14 18:15:11 -07:00
parent 5559f32672
commit 51fecb24b0
61 changed files with 0 additions and 12532 deletions
-262
View File
@@ -1,262 +0,0 @@
# Ferrous Solitaire — Session Handoff (ARCHIVED)
> **This file is from Phase 2 (2026-04-25, 242 tests). It is kept for historical
> reference only. The authoritative session handoff is at the repo root:
> `SESSION_HANDOFF.md`.**
> Last updated: 2026-04-25
> Branch: `master` — pushed to https://github.com/funman300/Rusty_Solitaire.git
> Test count: **242 passing** (83 core + 60 data + 99 engine), `cargo clippy --workspace -- -D warnings` clean
---
## What Has Been Built
### Phase 1 — Workspace Setup ✅ COMPLETE
All seven Cargo crates created and compiling cleanly:
| Crate | Status | Purpose |
|---|---|---|
| `solitaire_core` | Fully implemented | Pure Rust game logic — NO Bevy, NO network |
| `solitaire_sync` | Stub | Shared API types (`SyncPayload`, `SyncResponse`) |
| `solitaire_data` | Stub | `SyncError` enum + `SyncProvider` trait |
| `solitaire_engine` | Stub | Bevy ECS systems — all plugins added in Phase 3 |
| `solitaire_server` | Stub | Axum sync server — implemented in Phase 8C |
| `solitaire_gpgs` | Compile-time stub | Google Play Games bridge — Android only, JNI in Phase: Android |
| `solitaire_app` | Working | Opens blank Bevy window titled "Ferrous Solitaire" at 1280×800 |
Fast compile profiles, `assets/` directory structure, and `.env.example` are all in place.
### Phase 2 — Core Game Engine ✅ COMPLETE
`solitaire_core` is fully implemented with 68 passing tests and zero clippy warnings.
**Modules:**
- `card.rs``Suit` (Clubs/Diamonds/Hearts/Spades, `is_red()`/`is_black()`), `Rank` (AceKing, `value() -> u8`), `Card` (id, suit, rank, face_up)
- `pile.rs``PileType` (Stock, Waste, Foundation(Suit), Tableau(usize)), `Pile` (new, top)
- `error.rs``MoveError`: InvalidSource, InvalidDestination, EmptySource, RuleViolation(String), UndoStackEmpty, GameAlreadyWon, StockEmpty
- `deck.rs``Deck::new()`, `Deck::shuffle(seed: u64)` using seeded `StdRng` (cross-platform deterministic), `deal_klondike(deck) -> ([Pile; 7], Pile)`
- `rules.rs``can_place_on_foundation(card, pile, suit)`, `can_place_on_tableau(card, pile)`
- `scoring.rs``score_move(from, to)`, `score_undo()` (-15), `compute_time_bonus(elapsed_seconds)` (700_000/s)
- `game_state.rs``DrawMode`, `GameState` with full game loop
**GameState public API:**
```rust
GameState::new(seed: u64, draw_mode: DrawMode) -> Self
GameState::draw(&mut self) -> Result<(), MoveError>
GameState::move_cards(&mut self, from: PileType, to: PileType, count: usize) -> Result<(), MoveError>
GameState::undo(&mut self) -> Result<(), MoveError>
GameState::check_win(&self) -> bool
GameState::check_auto_complete(&self) -> bool
GameState::compute_time_bonus(&self) -> i32
GameState::undo_stack_len(&self) -> usize
```
**Key GameState rules:**
- Undo stack capped at 64 entries (oldest evicted)
- Score never goes below 0
- Waste recycling is unlimited — `StockEmpty` only when both stock AND waste are simultaneously empty
- Recycle (waste → stock) pushes a snapshot so it can be undone
- Newly exposed top card of source pile is flipped face-up automatically on `move_cards`
- Win: all 4 foundations at 13 cards
- Auto-complete: stock empty + waste empty + all tableau cards face-up
---
## Commit History
```
b8dc7cb fix(core): remove stock_recycled limit, replace unwrap, snapshot on recycle, fix derives
58f1465 feat(core): add GameState with draw, move_cards, undo, win/auto-complete detection
43194b0 fix(core): use StdRng doc comment, replace expect() with debug_assert in deal_klondike
17bbec0 feat(core): add pile, error, deck, rules, scoring modules with tests
fcf878b feat(core): add Card, Suit, Rank types with tests
f84d7c5 fix(workspace): add derives/docs per code review, remove unused thiserror from solitaire_sync
684f077 feat(workspace): initialize all seven crates with stubs and blank Bevy window
```
---
### Phase 3 — Bevy Rendering & Interaction ✅ COMPLETE
All sub-phases (3A3F) done. Plugins: `GamePlugin`, `TablePlugin`, `CardPlugin`, `InputPlugin`, `AnimationPlugin`. Full game playable — drag/drop with rule validation, keyboard shortcuts (U/N/D/Esc), animated slides, win cascade. UI via `bevy::ui`, no egui.
### Phase 4 — Statistics Persistence ✅ COMPLETE
- `solitaire_data::StatsSnapshot` with `update_on_win` / `record_abandoned` / `win_rate`
- Atomic file I/O via `save_stats_to` (`.tmp` → rename)
- `StatsPlugin` in `solitaire_engine` — loads on startup, persists on `GameWonEvent` (win) and `NewGameRequestEvent` (abandoned if move_count>0 and not won)
- Full-window overlay toggled with `S` — games played/won, win rate, streak, best score, fastest, avg
- `StatsPlugin::default()` for production, `StatsPlugin::headless()` for tests (no disk I/O)
### Phase 5 — Achievements ✅ COMPLETE (14 of ~19)
- `solitaire_core::achievement``AchievementContext` + `AchievementDef` + `ALL_ACHIEVEMENTS` + `check_achievements`
- `solitaire_core::GameState.undo_count` — tracks whether undo was used (for `no_undo` / `speed_and_skill`)
- `solitaire_data::AchievementRecord` + atomic `achievements.json` persistence
- `AchievementPlugin` — on `GameWonEvent`, build context from `StatsResource` + `GameState` + `chrono::Local` hour, evaluate all conditions, persist newly-unlocked records, emit `AchievementUnlockedEvent(id)`
- `AnimationPlugin`'s toast resolves the event's ID to the achievement's name via `achievement_plugin::display_name_for`
- New `StatsUpdate` system set lets `AchievementPlugin` order itself after stats are incremented
- Deferred: `daily_devotee` (needs `PlayerProgress`), `comeback` (needs recycle counter), `zen_winner` (needs modes), `perfectionist` (needs max-score calc). Stubs can be added in later phases.
### Phase 6 (part 1) — XP, Levels, ProgressPlugin ✅ COMPLETE
- `solitaire_data::PlayerProgress` with `total_xp`, `level`, daily/weekly/unlock fields
- `level_for_xp(xp)` and `xp_for_win(time, used_undo)` helpers (per ARCHITECTURE.md §13)
- `add_xp(amount) -> prev_level` with `leveled_up_from(prev)` for level-up detection
- Atomic `progress.json` persistence via `save_progress_to` / `load_progress_from`
- `ProgressPlugin` — on `GameWonEvent`, awards XP (base 50 + speed bonus 1050 + no-undo 25), persists, emits `LevelUpEvent`
- `ProgressUpdate` system set for ordering downstream systems
- `ProgressPlugin::default()` for production, `::headless()` for tests
### Phase 6 (part 2a) — Daily Challenge + Level-Up Toast ✅ COMPLETE
- `daily_seed_for(date)` deterministic per-date seed
- `PlayerProgress::record_daily_completion(date)` with streak / reset / idempotency rules
- `DailyChallengePlugin`: today's seed in a resource; pressing **C** starts a daily-seed new game; on winning a daily-seed game, awards **+100 XP**, updates streak, persists, fires `DailyChallengeCompletedEvent`
- `LevelUpEvent` now spawns a toast through `AnimationPlugin`
- `daily_devotee` achievement wired (streak ≥ 7); `AchievementContext` gains `daily_challenge_streak` and reads from `ProgressResource`
### Phase 6 (part 2b) — Weekly Goals ✅ COMPLETE
- `solitaire_data::weekly``WeeklyGoalKind`, `WeeklyGoalDef`, `WeeklyGoalContext`, `current_iso_week_key`, three starter goals (5 wins / 3 no-undo / 3 fast)
- `PlayerProgress``weekly_goal_week_iso`, `roll_weekly_goals_if_new_week`, `record_weekly_progress`
- `WeeklyGoalsPlugin` — on `GameWonEvent`, rolls week if needed, increments matching goals, awards `WEEKLY_GOAL_XP` (75) per completion, fires `WeeklyGoalCompletedEvent`
### Phase 6 (part 3) — Completion Toasts + Progression Panel ✅ COMPLETE
- `AnimationPlugin` now surfaces `DailyChallengeCompletedEvent` (shows streak) and `WeeklyGoalCompletedEvent` (shows goal description) as 3-second toasts.
- Stats overlay (**S** key) appends a Progression section: level, total XP, daily streak, and a Weekly Goals list iterating `WEEKLY_GOALS` with `progress/target` for each.
### Phase 6 (part 4a) — Elapsed Time + Zen Mode ✅ COMPLETE
- `tick_elapsed_time` in `GamePlugin` ticks `GameState.elapsed_seconds` once per real-world second while not won; `advance_elapsed` is a pure helper for direct unit testing.
- `GameMode` enum (`Classic` / `Zen`) added to `solitaire_core::game_state`. `GameState.mode` field; `GameState::new_with_mode` ctor. Zen suppresses scoring in `move_cards` and `undo`. Field is `#[serde(default)]` for backwards-compatible saved games.
- `NewGameRequestEvent` carries an optional `mode`; `handle_new_game` falls back to the current game's mode when `None`.
- `Z` key starts a fresh Zen game.
### Phase 6 (part 4b) — Challenge Mode + Level-5 Gate ✅ COMPLETE
- `GameMode::Challenge` variant in core; `undo()` returns `RuleViolation` in Challenge.
- `solitaire_data::challenge``CHALLENGE_SEEDS` static list, `challenge_seed_for(index)` wrapping modulo length, `challenge_count()`.
- `PlayerProgress.challenge_index` (serde-default) tracks progression.
- `ChallengePlugin` advances the cursor on Challenge-mode wins, persists, fires `ChallengeAdvancedEvent`. **X** key starts a Challenge-mode game with the current seed.
- Both **Z** (Zen) and **X** (Challenge) are gated to `level >= CHALLENGE_UNLOCK_LEVEL` (5).
### Phase 6 (part 4c) — Time Attack + Unlock UI ✅ COMPLETE
- `GameMode::TimeAttack` variant added to core (no scoring/undo changes — just a session marker).
- `TimeAttackPlugin` (engine) — `TimeAttackResource { active, remaining_secs, wins }` (session-only, not persisted), `TimeAttackEndedEvent { wins }`. **T** starts a session (gated to level ≥ 5) and deals a TimeAttack-mode game; the timer (`TIME_ATTACK_DURATION_SECS = 600.0`) decrements each frame; wins during the active session bump the counter and auto-deal a fresh game.
- `AnimationPlugin` surfaces `TimeAttackEndedEvent` as a 5-second summary toast.
- `StatsPlugin` overlay (**S**) appends an "Unlocks" subsection (card backs / backgrounds, sorted/deduped, "None" when empty) and a live "Time Attack" panel showing remaining minutes/seconds + wins while a session is active.
- Helper `format_id_list` factored out + tested.
### Phase 7 (part 1) — Help Overlay + Challenge Toast ✅ COMPLETE
- `HelpPlugin`: **H** or `?` toggles a full-window cheat sheet listing all keybindings (gameplay, mode hotkeys, overlays). 3 unit tests.
- `AnimationPlugin` now surfaces `ChallengeAdvancedEvent` as a 3-second toast ("Challenge N cleared!").
### Phase 7 (part 2) — Synthesized SFX + AudioPlugin ✅ COMPLETE
- New workspace crate `solitaire_assetgen` with bin `gen_sfx`. Synthesizes five 44.1kHz mono 16-bit PCM WAVs from a deterministic LCG noise source + sine/square synths into `assets/audio/`. Run with `cargo run -p solitaire_assetgen --bin gen_sfx`. Output is committed; end users never run the generator.
- `AudioPlugin` (`solitaire_engine`): embeds the WAVs via `include_bytes!()`, decodes once via `kira::StaticSoundData::from_cursor`, plays on `DrawRequestEvent` (flip), `MoveRequestEvent` (place), `NewGameRequestEvent` (deal), `GameWonEvent` (fanfare).
- Backend handle stored as `NonSend` (cpal stream is `!Send` on some platforms). Plugin degrades gracefully if no audio device is available — logs a warning, gameplay continues silently.
- Single decode unit test (`embedded_wavs_decode_successfully`) keeps the loader and generator in sync.
### Phase 7 (part 3) — MoveRejectedEvent + Pause Menu ✅ COMPLETE
- New `MoveRejectedEvent { from, to, count }`. `end_drag` fires it when the cursor is over a real pile but `can_place_*` rejects the placement. `AudioPlugin` plays `card_invalid.wav` on it.
- New `PausePlugin` + `PausedResource(bool)`. **Esc** toggles a full-window pause overlay (ZIndex 220) and flips the resource. `tick_elapsed_time` and `advance_time_attack` skip work while paused. Input is deliberately not blocked — pause is a "stop the clock" screen, nothing more.
- `HelpPlugin` cheat sheet updated to reflect the new Esc behaviour.
### Phase 7 (part 4) — Settings + SFX Volume Control ✅ COMPLETE
- New `solitaire_data::Settings { sfx_volume, first_run_complete }` with atomic JSON persistence (`save_settings_to` / `load_settings_from`). `sanitized()` clamps out-of-range volumes after deserialization. Default `sfx_volume = 0.8`.
- New `SettingsPlugin` (engine) with `SettingsResource`, `headless()` ctor, and `SettingsChangedEvent`. **\[** / **\]** adjust SFX volume by `SFX_STEP` (0.1), clamped; persists on change. No-op + no event when already at the rail.
- `AudioPlugin` applies `sfx_volume` to kira's main track at startup and on every `SettingsChangedEvent` (so changes take effect mid-game without restart).
- `AnimationPlugin` shows a brief "SFX: 70%" toast on every change so players see the new value.
- Help cheat sheet lists the **\[** / **\]** keys.
- 4 plugin tests + 6 data tests added — defaults, clamping, round-trip persistence.
### Phase 7 (part 5) — First-Run Onboarding ✅ COMPLETE
- New `OnboardingPlugin`. At `PostStartup`, if `Settings.first_run_complete == false`, spawns a centered welcome banner pointing at the **H**/`?` cheat sheet (ZIndex 230). Any key or mouse-button press dismisses it, sets the flag, and persists `settings.json` — returning players never see it again.
- 4 unit tests cover spawn-only-on-first-run, key dismiss, and click dismiss.
## What Is Next
Phase 7 polish slate is done. Phase 8 (sync) is next.
### Phase 8 — Sync
| Phase | Scope |
|---|---|
| Phase 8A | Local storage scaffolding + `SyncProvider` plumbing in `solitaire_data` |
| Phase 8B | Self-hosted Axum server (auth, sync endpoints, SQLite schema) |
| Phase 8C | `SolitaireServerClient` (`SyncProvider` impl) + `SyncPlugin` lifecycle |
| Phase 8D | GPGS stub fully wired into the settings UI (Android-only `cfg`-gated) |
### Tiny optional polish (anytime)
- **Ambient loop**: optional sixth WAV — needs taste, deferred until artwork phase.
- **Block input while paused**: drag/hotkeys still work mid-pause; tightening this would make pause behave more like a true modal.
---
## Important Implementation Notes
### Versions (Cargo.toml workspace deps)
- `bevy = "0.15"` (resolved to 0.15.3) — UI via built-in `bevy::ui`, no bevy_egui
- `kira = "0.9"` — audio via `kira` crate directly, no bevy_kira_audio or AssetServer
- `rand = "0.8"` — note: `small_rng` feature is NOT enabled; use `StdRng`, not `SmallRng`
### Asset strategy
- No `AssetServer` — assets embedded at compile time using `include_bytes!()`
- Fonts: `Font::try_from_bytes(include_bytes!("../assets/fonts/main.ttf"))`
- Audio: load from `&[u8]` via `kira` `StaticSoundData::from_cursor()`
- Card rendering: procedural (`bevy::prelude::Sprite` + `Text2d`) — no sprite sheets required
### Hard rules (from CLAUDE.md)
- `solitaire_core` and `solitaire_sync` must NEVER gain Bevy or network dependencies
- No `unwrap()` or `panic!()` in game logic — use `Result<_, MoveError>` everywhere
- All state transitions return `Result``debug_assert!` is acceptable for structural invariants
- `SyncPlugin` must NEVER match on `SyncBackend` enum inside a Bevy system — always call through the `SyncProvider` trait
- Atomic file writes only: write to `.tmp` then `rename()`
- `cargo clippy --workspace -- -D warnings` must pass clean
- `cargo test --workspace` must pass clean
### Lessons from this session
- `rand = "0.8"` without `features = ["small_rng"]` means `SmallRng` is unavailable — use `StdRng`
- `tower-governor` uses underscores in the crate name (not hyphens in Cargo.toml)
- When implementing `draw()` in `GameState`: recycle is unlimited, stop condition is BOTH piles empty simultaneously
- Recycle must push a snapshot (so it can be undone) even though it doesn't count as a "move"
---
## Implementation Plan Document
The detailed task-by-task plan for Phases 1 and 2 is at:
`docs/superpowers/plans/2026-04-20-phase1-2-workspace-core.md`
For Phase 3 onwards, write a new plan using the `superpowers:writing-plans` skill before starting implementation.
---
## Running the Project
```bash
# Check everything compiles
cargo check --workspace
# Run all tests (214 tests, all should pass)
cargo test --workspace
# Lint (must be zero warnings)
cargo clippy --workspace -- -D warnings
# Run the game
cargo run -p solitaire_app --features bevy/dynamic_linking
```
-270
View File
@@ -1,270 +0,0 @@
# Android Playability TODO
**Started:** 2026-05-10 — first hardware screenshot of v0.22.3 APK
running on a real device showed the desktop HUD projected onto a
360 dp portrait viewport with no mobile adaptation. This list
tracks the work needed to make the APK genuinely playable, not
just "boots without crashing."
**Context:** v0.22.3 (signed release APK) builds and launches.
JNI bridges (clipboard, keystore) compile but are untested on
hardware. The work below is UI/UX port work — no architectural
rewrites required.
---
## Reading from the v0.22.3 screenshot
| Region | Observation |
|--------|-------------|
| Top ~5 % | System bar (clock, signal, battery) overlapped by game HUD — no safe-area inset |
| HUD text row | `Score:0 Pause Esc Help A Modes [] New_Game N Moves:0 0:08` all overlapping — desktop layout crammed into 360 dp |
| Keyboard hints | `Esc`, `A`, `[]`, `N` shown next to buttons — meaningless on touch |
| Foundations row | Leftmost foundation (♥) clipped left; rightmost tableau column (♠ 4) clipped right |
| Card backs | Face-down cards render as solid red squares, not back-art texture |
| Vertical use | Cards occupy top ~30 % only; bottom 70 % empty black — no portrait-aware layout |
| Bottom edge | No accommodation for Android gesture / home-indicator area |
---
## P0 — Blocking playability
- [x] **Safe-area insets (top + bottom).** *Closed 2026-05-10 by
`b9aa262`.* `SafeAreaInsets` resource + `SafeAreaInsetsPlugin`
query `WindowInsets.getInsets(systemBars())` via JNI on Android;
HUD anchors carry `SafeAreaAnchoredTop { base_top }` and the
change-detection fix-up system re-applies `base_top + insets.top`
whenever the resource updates. Bottom inset is captured but not
yet consumed (waits for bottom-anchored UI).
- [x] **Mobile HUD layout.** *Closed 2026-05-10.* Both the left HUD
column and the right action button row are now capped at
`max_width: 50 %` and the button row + tier-row child Nodes carry
`flex_wrap: Wrap`. On a 360 dp viewport the 6-button row breaks
to multiple lines (right-justified) and the tier rows wrap
individually instead of overflowing into the action column. On
desktop (≥ 1280 px) the 50 % cap is wider than any natural row
width so the existing single-line layout is unchanged.
- [x] **Card-back asset not rendering.** *Closed 2026-05-10 by
`fcc7337`.* `AssetPlugin::file_path = "../assets"` was set
unconditionally to fix the desktop `cargo run -p solitaire_app`
CWD relativity, but on Android cargo-apk packages the same
directory into the APK at `assets/` and Bevy's
AndroidAssetReader is already rooted there — prepending `../`
walked the reader out of the APK assets root and every load
failed silently. The face-down branch then fell through to the
`card_back_colour(0)` solid-red brick fallback. Gated the
override behind `#[cfg(not(target_os = "android"))]`.
- [x] **Viewport overflow.** *Closed 2026-05-10.* `compute_layout`
was clamping the input window up to `MIN_WINDOW = 800 × 600`,
so a 360 dp phone got laid out as if it were 800-wide and the
outer piles fell outside the actual viewport. Lowered the floor
to 320 × 400 (below the smallest reasonable phone) so real
Android resolutions flow through without clamping, while keeping
a sentinel to guard against degenerate / startup-zero windows.
New regression test `phone_portrait_layout_fits_horizontally`
asserts all 13 piles fit a 360 × 800 viewport.
## P1 — Touch UX
- [x] **Suppress keyboard-hint labels on Android.** *Closed
2026-05-10.* `spawn_action_button` now nulls the `hotkey`
argument on Android via a `#[cfg(target_os = "android")]` rebind,
so the U / Esc / F1 / N chips next to the action row labels
disappear on touch builds. Remaining hint sites swept in P3 —
see full-keyboard-hint-sweep entry below.
- [x] **Thumb-sized hit targets.** *Closed 2026-05-10.* Action
button Node carries `min_width: Val::Px(48.0), min_height:
Val::Px(48.0)` — meets Material's 48 dp baseline on touch and is
a no-op for buttons whose content already exceeds 48 px in
either axis. Applied universally rather than cfg-gated since
Material's guideline applies to all input modes. Cards, pile
markers, modal close buttons not yet audited — track as P3 if
they fall below threshold on hardware.
- [x] **Portrait-first card spacing.** *Closed 2026-05-11.*
`compute_layout` now derives an adaptive `tableau_fan_frac` from the
available vertical space below the tableau row. On height-limited
(desktop) windows the formula returns ≈ 0.25 and the clamp keeps the
existing behaviour. On width-limited (portrait phone) windows — where
card size is constrained by the 9-column horizontal packing — the fan
fraction expands to fill the viewport (≈ 0.84 at 360 × 800 dp).
`tableau_facedown_fan_frac` scales proportionally. Both values live in
the `Layout` struct; `card_plugin::card_positions` and
`input_plugin::card_position` / `pile_drop_rect` read from the struct
so rendering and hit-testing stay in sync across viewport sizes.
- [x] **Double-tap auto-move visible feedback.** *Closed 2026-05-11.*
On a recognised double-tap (priority 1 single-card or priority 2
stack move), the moved card(s) receive a 0.35 s lime flash
(`STATE_SUCCESS` tint + `HintHighlight { remaining: 0.35 }`) before
the move request is written. The flash persists through the card
animation and is cleaned up by the existing `tick_hint_highlight`
system. Hardware trigger-verification remains a manual step — connect
AVD or device and confirm two rapid `TouchPhase::Ended` events within
0.5 s produce the lime flash.
## P2 — Polish
- [x] **Drag responsiveness on touch.** *Closed 2026-05-11.*
Two code-side improvements shipped; final feel confirmation still needs
hardware:
1. `start_drag` (mouse path) now bails out when a touch is just-pressed
(`Touches::iter_just_pressed()`), ensuring `touch_start_drag` always
owns the drag state on touch-screen devices — including Bevy/Winit
versions that simulate `MouseButton::Left` from the primary touch.
2. Mobile drag commit threshold lowered 10 px → 8 px, matching Android's
`ViewConfiguration.getScaledTouchSlop()` spec. Smaller threshold →
smaller snap-on-commit and faster perceived response.
**Remaining:** connect AVD or device and verify drag feels responsive
with no stutter; tune threshold further if needed.
- [x] **Long-press menu.** *Closed 2026-05-11.* New system
`radial_open_on_long_press` in `radial_menu.rs` counts up while a
touch is held (`drag.active_touch_id.is_some() && !drag.committed`)
and opens `RightClickRadialState::Active` after 0.5 s — the same
state the right-click path uses. Existing radial infrastructure
then handles everything:
- `radial_track_cursor` extended to fall back to the first active
touch when no cursor position is available, so sliding the held
finger moves the hover ring.
- `radial_handle_release_or_cancel` extended to confirm/cancel on
`Touches::iter_just_released()` in addition to right-mouse release.
- `handle_double_tap` skips when the radial is active (guards a
narrow edge case where the finger lifts at exactly the same frame
the 0.5 s threshold fires).
Hardware verification needed: confirm the 0.5 s hold feel, verify
sliding to a destination and lifting confirms the move.
- [x] **HUD typography.** *Closed 2026-05-11.* New system
`update_hud_typography` fires on `WindowResized` and adjusts Tier-1
font sizes based on viewport width. Below 480 logical px: Score
`TYPE_HEADLINE` (26) → `TYPE_BODY_LG` (18), Moves/Timer
`TYPE_BODY_LG` (18) → `TYPE_CAPTION` (11), so all three items fit
in the 180 dp HUD column on a 360 dp phone. At ≥ 480 px the
original sizes are restored — desktop/tablet layout unchanged.
`add_message::<WindowResized>()` added defensively to `HudPlugin`
so the system works under `MinimalPlugins` in tests.
- [x] **Orientation lock.** *Closed 2026-05-11.* Added
`[package.metadata.android.application.activity]` section to
`solitaire_app/Cargo.toml` with `orientation = "portrait"`.
cargo-apk/ndk-build maps this to `android:screenOrientation="portrait"`
in the generated `AndroidManifest.xml`. Remove (or add a landscape
layout) before enabling auto-rotate.
## P3 — Asset density
- [x] **Density-aware card scaling.** *Closed 2026-05-11 — no code change
required.* `WindowResized` fires with **logical** pixels; sprites are
sized in world units (1 world unit = 1 logical pixel); Bevy's renderer
maps logical → physical via `scale_factor` internally. On a 360 dp
3×-DPI phone, cards are 40 logical dp = 120 physical px. The 256 × 384 px
card textures are **downscaled** to fit (256 → 120 px) — quality is fine.
Upscaling only occurs if `card_width × scale_factor > 256`, i.e. a
tablet with a logical width > 765 dp at 3× DPI — no current target
device falls in that range. Revisit if the game ships on large-screen
high-DPI tablets.
- [x] **App-icon density buckets.** *Closed 2026-05-11.* Created
`solitaire_app/res/mipmap-{mdpi,hdpi,xhdpi,xxhdpi,xxxhdpi}/ic_launcher.png`
from the existing `assets/icon/` PNGs (48→mdpi, 64→hdpi, 128→xhdpi,
256→xxhdpi+xxxhdpi). Added `resources = "res"` to
`[package.metadata.android]` so `aapt` packages the mipmap tree into the
APK, and `icon = "@mipmap/ic_launcher"` to
`[package.metadata.android.application]` so the launcher references it.
- [x] **Full keyboard-hint sweep.** *Closed 2026-05-11.* Extended the
P1 suppression to cover all remaining hint sites:
- `ui_modal.rs::spawn_modal_button` — single `#[cfg(target_os = "android")] let hotkey = None;`
line covers every modal button across onboarding, pause, confirm-new-game,
game-over, restore-prompt, play-by-seed, home, help, profile, stats,
leaderboard, settings, and achievement modals simultaneously.
- `home_plugin.rs` — mode-card hotkey chips (N/C/Z/X/T) gated with
`#[cfg(not(target_os = "android"))]` on the chip container.
- `replay_overlay.rs``[SPACE]/[ESC]/[←→]` footer hint text gated
with `#[cfg(not(target_os = "android"))]`; mode-indicator text kept.
- `help_plugin.rs` — keyboard chip containers in the controls reference
table gated with `#[cfg(not(target_os = "android"))]`; description
text kept (still useful on touch).
## P4 — Stability / runtime
- [x] **B0004 ECS hierarchy warnings.** *Investigated 2026-05-11 — no
fix required.* B0004 fires via Bevy's `validate_parent_has_component<C>`
hook when a child entity has UI component `C` (e.g. `Node`,
`InheritedVisibility`) but its parent doesn't yet. In Bevy 0.18,
`.despawn()` is recursive (docs: "When a parent is despawned, all
children will also be despawned"), so all `.despawn()` calls in the
engine are safe. The warnings seen on the Pixel 7 AVD during startup
are a component-propagation timing artifact — UI children reach the
hook before the parent's inherited components finish initialising —
not a gameplay defect. `despawn_related::<Children>()` in
`card_plugin.rs` is explicit child-only teardown (parent kept alive)
and is correct. No gameplay bugs attributed to these warnings over 2+
min AVD runtime.
- [x] **AVD functional tests for JNI bridges.** *Closed 2026-05-11.*
Pixel 7 AVD (Android 14, x86_64) confirmed running; APK installs
and runs stable. Key findings:
**Keystore JNI — verified working.** Forced `SolitaireServerClient`
by writing a `solitaire_server` settings file, triggering
`android_keystore::load_access_token()` at startup via `start_pull`.
Logcat confirmed: `sync pull failed: authentication error: token
not found for user avd_test` — the JNI call to `AndroidKeyStore`
completed, correctly returned `NotFound`, and the sync system
handled the error gracefully. No panic, no crash from the JNI layer.
**Clipboard JNI — verified working.** Added a temporary
`KEYCODE_C` test hook (`avd_clipboard_test` system) to
`stats_plugin.rs`, rebuilt the APK, pressed C on the AVD.
Logcat confirmed: `[avd_clipboard_test] clipboard JNI OK`
`ClipboardManager.setPrimaryClip()` succeeded on Android 14.
Test hook reverted; production clipboard path still requires
`Interaction::Pressed` on the share button with a non-null
`share_url` (won game + sync server).
**Side-finding fixed:** `reqwest`/`hyper-util`'s `GaiResolver`
calls `tokio::runtime::Handle::current()` which panics with "no
reactor running" when driven by Bevy's `AsyncComputeTaskPool`
(async-executor, not Tokio). Fixed in `sync_plugin.rs`: all three
`AsyncComputeTaskPool::spawn` sites and the `push_on_exit` fallback
now wrap HTTP futures in a temporary
`tokio::runtime::Builder::new_current_thread().enable_all()` runtime.
**Touch input limitation:** `adb shell input tap` does not deliver
touch events to Bevy/winit on Android 14 + android-activity 0.6.1
in headless AVD mode. Keyboard events (`KEYCODE_*`) work normally.
---
## P5 — UX polish (2026-05-12)
- [x] **UX-1 — Modal Done button unreachable in gesture zone.** *Closed
2026-05-12.* New `apply_safe_area_to_modal_scrims` system in
`safe_area.rs` pads every `ModalScrim` bottom by `insets.bottom /
window.scale_factor()` (logical pixels). Fires when `SafeAreaInsets`
changes AND when a new `ModalScrim` is spawned (`Added<ModalScrim>`
filter). Verified on device: Settings Done button reachable at physical
y ≈ 18002000 (was y ≈ 2232+, inside gesture zone).
- [x] **UX-5b — Home mode selector glyph corruption.** *Closed
2026-05-12.* `home_plugin.rs` mode glyphs changed from Geometric Shapes
block (U+25xx — absent from FiraMono, renders as rectangles) to card
suits U+2660 ♠ / U+2665 ♥ / U+2666 ♦. Affects Zen, Challenge, and
Daily mode selector buttons shown at level 5+.
- [x] **UX-7 — Help screen HUD button entry wraps to two lines.** *Closed
2026-05-12.* Android `CONTROL_SECTIONS` entry for ≡ button shortened
from `"Menu: Stats, Settings, Profile, Achievements"` to
`"Open menu (Stats, Settings, Profile...)"` in `help_plugin.rs`.
Fits on one line at 360 dp.
- [x] **BUG-3 — Multi-modal stacking (Stats + Profile simultaneously).** *Closed
2026-05-12.* `handle_menu_button` in `hud_plugin.rs` now checks
`scrims: Query<(), With<ModalScrim>>` and only calls
`spawn_menu_popover` when `scrims.is_empty()`. Tapping ≡ while any
modal is open is a no-op. Verified on device.
## Notes / decisions
* This list is screenshot-driven; expect more items to surface once
P0 unblocks actually moving cards on hardware.
* The pattern across all the bugs is "no one ran the relevant code
path on Android yet." The hard work — Bevy 0.18 on Android,
JNI bridges, signed CI builds — is done. What's left is a
coordinated pass of `#[cfg(target_os = "android")]` gates plus
making `LayoutResource` query the real surface size.
* Where possible, prefer responsive layout (query window size) over
branching `#[cfg]` blocks. Branches are fine for input methods
(touch vs. mouse) but not for screen geometry — a foldable or
desktop window of equivalent size should look the same.
-247
View File
@@ -1,247 +0,0 @@
# Android Port Investigation
> **Date:** 2026-04-28
> **Author:** Claude Code
> **Scope:** Feasibility analysis for porting Ferrous Solitaire to Android using cargo-mobile2
---
## Summary
A working Android port is feasible but not trivial. The core game logic (`solitaire_core`, `solitaire_sync`) compiles to Android without changes. Every other crate requires at least minor surgery. The biggest blockers are the `keyring` crate (no Android backend), the `kira`/`AudioManager` audio stack (`DefaultBackend` uses CPAL which targets desktop), and the `dirs` crate returning `None` on Android in its current usage. Touch input already has a solid foundation in `input_plugin.rs`. Estimated effort from a clean Android toolchain is **1218 developer-days** to reach a playable-but-rough state.
---
## 1. Bevy on Android — Current Status
Bevy's Android support is community-maintained via the `winit` backend and is usable but carries known rough edges as of the 0.15/0.16 generation.
**What works:**
- Basic rendering via Vulkan (through `wgpu`). OpenGL ES fallback is available for older devices.
- Touch input events: Bevy's `TouchInput` events and the `Touches` resource are populated from Android `MotionEvent`s via `winit`. The existing `touch_start_drag`, `touch_follow_drag`, `touch_end_drag`, and `handle_touch_stock_tap` systems in `input_plugin.rs` will function correctly — this was already written with multi-touch in mind and uses `TouchPhase::Started/Moved/Ended/Canceled` cleanly.
- Bevy UI (the `bevy::ui` module used for all overlays).
- `WindowResized` events fire correctly, so the layout system will recompute for any screen size.
**What does not work / needs attention:**
- **`bevy/dynamic_linking`**: The dynamic linking feature must be stripped from any Android build profile. Dynamic linking is a desktop-only development shortcut; Android requires static linking.
- **Fixed window size**: `main.rs` sets `resolution: (1280u32, 800u32)`. On Android the window is always the full display. This value is harmlessly overridden by the OS, but `min_width`/`min_height` constraints should be removed or set to 0 for Android to avoid Winit warnings.
- **`F11` fullscreen toggle** (`handle_fullscreen` in `input_plugin.rs`): `WindowMode::BorderlessFullscreen` is desktop-only. On Android it should be a no-op.
- **Keyboard shortcuts**: The entire `handle_keyboard_core`, `handle_keyboard_hint`, `handle_keyboard_forfeit` systems are desktop-only workflows. They will not crash, but they are dead code on Android. No touchscreen replacement for Undo (U), New Game (N), Draw (D/Space), Hint (H), Forfeit (G) exists yet — these need an on-screen UI.
- **`CursorPlugin`**: The custom cursor sprite plugin is irrelevant on Android (no cursor). Harmless to leave registered, but it uses `PrimaryWindow` cursor APIs that may panic or warn on Android.
**cargo-mobile2 integration for Bevy:**
The standard path is:
1. Install `cargo-mobile2`: `cargo install --locked cargo-mobile2`
2. Run `cargo mobile init` in the workspace root. This generates an `android/` directory with the Gradle project, `AndroidManifest.xml`, and JNI glue.
3. cargo-mobile2 targets the `solitaire_app` binary crate (the thin entry point). The generated `lib.rs` shim calls `android_main` via `bevy::winit`'s Android entry point.
4. The `solitaire_app` crate needs a `[lib]` target added alongside the existing `[[bin]]`, with `crate-type = ["cdylib"]`, used only when building for Android.
**Required `Cargo.toml` changes (workspace level):**
```toml
[target.'cfg(target_os = "android")'.dependencies]
# android_logger and ndk-glue wiring are handled by cargo-mobile2's generated shim.
# No direct ndk-glue dependency is needed in app code when using Bevy + cargo-mobile2.
```
**NDK version:** Android NDK r25c or r26 LTS is the tested range for `wgpu`/Vulkan on Android. NDK r27+ may work but has had compatibility reports with CPAL. Set `ANDROID_NDK_ROOT` to the NDK root; the minimum API level should be 26 (Android 8.0) for Vulkan stability.
---
## 2. Audio — `kira` + `DefaultBackend`
**The problem:**
`solitaire_engine/src/audio_plugin.rs` creates an `AudioManager<DefaultBackend>`. `kira`'s `DefaultBackend` is an alias for `CpalBackend`, which wraps CPAL. CPAL's Android backend uses OpenSL ES and is functional but historically fragile. As of kira 0.9+, `kira` no longer bundles its own CPAL backend by default in the same way — the `DefaultBackend` feature must be enabled explicitly and requires `cpal` with the Android feature.
**Current code behavior:**
The `AudioPlugin::build` already handles the "no audio device" case gracefully:
```rust
let mut manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default()).ok();
if manager.is_none() {
warn!("audio device unavailable; SFX disabled");
}
```
This means if the audio manager fails to initialise on Android, the game continues silently. This is acceptable as a first-pass fallback.
**What is needed for working audio on Android:**
- Add `kira` dependency with `cpal` backend enabled for Android: The `kira` workspace dependency currently specifies `version = "0.12"`. Verify that `kira/Cargo.toml` exposes a `cpal` feature (or that `DefaultBackend` compiles on Android targets with NDK). If not, a `CpalBackend` with `cpal = { features = ["oboe"] }` may be needed.
- The `NonSend` resource `AudioState` should compile fine — `NonSend` is legal in Bevy Android builds.
- `include_bytes!` for the WAV assets is compile-time and unaffected by platform.
**Recommendation:** Defer full audio verification to a device test. The graceful fallback means a silent-but-working first build is achievable without resolving this.
---
## 3. `keyring` Crate — No Android Backend
**The problem:**
`keyring = "2"` is used in `solitaire_data/src/auth_tokens.rs` to store JWT access and refresh tokens in the OS keychain. The `keyring` crate's Android backend does not exist — as of v2.x, supported backends are: macOS Keychain, Windows Credential Manager, Linux Secret Service (D-Bus), and iOS Keychain. There is no Android KeyStore backend.
On Android, `Entry::new(...)` will return `keyring::Error::NoStorageAccess`, which the existing code already maps to `TokenError::KeychainUnavailable`. So the code will not crash — it will simply fail every token store/load operation.
**Current failure mode:**
Every call to `store_tokens`, `load_access_token`, `load_refresh_token`, or `delete_tokens` will return `Err(TokenError::KeychainUnavailable(...))`. The sync client in `sync_client.rs` needs to be verified to handle this gracefully rather than propagating an error that disables sync entirely.
**Options for Android credential storage:**
| Option | Security | Effort | Notes |
|---|---|---|---|
| **In-memory only (prompt re-login each session)** | N/A | 1 day | Simplest. On `TokenError::KeychainUnavailable`, the `SyncProvider` returns `SyncError::Auth`, user is prompted to log in. Already architecturally supported. |
| **Encrypted `SharedPreferences` equivalent via JNI** | Good | 46 days | Call Android's `EncryptedSharedPreferences` (Jetpack Security) via JNI. Significant JNI boilerplate. |
| **AES-256 file encryption using Android Keystore via JNI** | Excellent | 58 days | Proper Android keychain equivalent. Complex JNI. |
| **Store in app-private file, unencrypted** | Poor | 0.5 days | Only acceptable during development. Never ship. |
**Recommended approach (first pass):** Use the in-memory / re-login-each-session path. The existing `TokenError::KeychainUnavailable` variant already exists for exactly this reason (Linux without a running secret service). The `SyncPlugin` should detect this on startup and present a "Sync unavailable — please log in" message rather than a hard error. This requires:
1. Conditional compilation: when `cfg(target_os = "android")`, replace the `keyring` calls with a no-op in-memory store (a simple `Mutex<HashMap<String, String>>`).
2. A `#[cfg(not(target_os = "android"))]` guard on the `keyring` import/dependency in `solitaire_data/Cargo.toml`.
**Required `solitaire_data/Cargo.toml` change:**
```toml
[target.'cfg(not(target_os = "android"))'.dependencies]
keyring = { workspace = true }
[target.'cfg(target_os = "android")'.dependencies]
# keyring is replaced by in-memory storage; no dependency needed
```
---
## 4. `dirs` Crate — Data Directory on Android
**The problem:**
`storage.rs` and other persistence modules use `dirs::data_dir()` to locate `~/.local/share/solitaire_quest/` (or platform equivalent). On Android, `dirs::data_dir()` returns `None` because there is no `XDG_DATA_HOME` and the `dirs` crate does not implement an Android-specific path.
**Current code behavior:**
All persistence functions already handle `None` gracefully (returning default values or `Err`), consistent with the CLAUDE.md lesson about `dirs::data_dir()`. Stats and progress will silently not persist across sessions if `data_dir()` returns `None`.
**Fix required:**
Android apps should store private data in the app's internal storage directory, obtained via JNI: `context.getFilesDir()`. This requires either:
- A thin JNI helper (via `jni` crate) called once on startup to obtain the path and store it as a global.
- Or passing the path in via the `android_main` entry point using `cargo-mobile2`'s `AndroidApp` handle, which exposes `internal_data_path()`.
The `cargo-mobile2` + Bevy path exposes an `AndroidApp` via `bevy::winit`'s Android entry point. Bevy 0.13+ passes `AndroidApp` through `WinitPlugin`, and it is accessible via a Bevy resource. A startup system can extract `app.internal_data_path()` and insert a `PlatformDataDirResource` that the storage functions read instead of calling `dirs::data_dir()`.
**Effort:** 12 days to implement the override and thread it through all `storage.rs` / `progress.rs` / `settings.rs` / `achievements.rs` call sites.
---
## 5. Touch Input — Current State and Gaps
**What already exists (strong foundation):**
The `InputPlugin` in `input_plugin.rs` has a complete parallel touch pipeline:
| System | Purpose | Status |
|---|---|---|
| `handle_touch_stock_tap` | Tap the stock pile to draw | Complete |
| `touch_start_drag` | Begin a touch drag on a face-up card | Complete |
| `touch_follow_drag` | Move card(s) with the active finger | Complete |
| `touch_end_drag` | Resolve the drag (move or reject) | Complete |
The touch systems use `TouchInput` events and the `Touches` resource, map touch IDs to `DragState.active_touch_id` to prevent multi-finger conflicts, and share the same `DragState`, `MoveRequestEvent`, `MoveRejectedEvent`, and `StateChangedEvent` infrastructure as the mouse pipeline. The drag threshold (`tuning.drag_threshold_px`) applies identically.
**Gaps for a production Android experience:**
1. **No double-tap equivalent for auto-move**: `handle_double_click` is mouse-only. Android users need a double-tap to trigger the same "move to best destination" logic. The `handle_double_click` system checks `buttons.just_pressed(MouseButton::Left)` and will be inert on Android. Estimated: 1 day.
2. **No touch equivalent for keyboard actions**: Undo, New Game, Draw (when stock is visible but tapping it is awkward), Hint, and Forfeit have no on-screen buttons. These need an Android-specific UI bar or gesture (e.g. two-finger tap for undo). Estimated: 23 days for a minimal floating action button strip.
3. **Drag threshold tuning**: The threshold is in `AnimationTuning` (`tuning.drag_threshold_px`). Touch screens typically need a larger threshold than mouse (physical screens have more accidental movement during a tap). The current value should be evaluated on a real device and likely increased for touch.
4. **No long-press for right-click equivalent**: The right-click highlight/hint glow (`HintHighlightTimer`) is triggered via right mouse button. Long-press detection is not yet implemented. This is a missing feature but not a blocker for basic play.
5. **`handle_double_click` uses `LocalDateTime`-based timing via `Time`**: This will work on Android, but `DOUBLE_CLICK_WINDOW = 0.35s` may feel too tight on touch. Should be configurable.
---
## 6. Additional Issues Not in Scope of the Four Research Areas
**`CursorPlugin`:** Uses Bevy's cursor APIs which are desktop-only. Should be conditionally compiled out on Android with `#[cfg(not(target_os = "android"))]`.
**`reqwest` with `rustls-native-certs`:** The `reqwest` dependency uses `rustls` with native root certificates. On Android, `rustls-native-certs` reads system certificates differently (via the `android_system_properties` crate internally). This generally works but should be tested; Android's certificate store is in a non-standard location vs Linux.
**App lifecycle (suspend/resume):** Android can suspend the process at any time. Bevy handles `WindowEvent::Suspended` and `WindowEvent::Resumed` via `winit`, pausing the render loop. The `SyncPlugin`'s "push on exit" path (`AppExit` event) should also trigger on `WindowEvent::Suspended` to avoid data loss when the user backgrounds the app. This is a separate feature (1 day).
**No `sqlx` on Android:** `solitaire_server` is a server binary and is never built for Android. The `sqlx` dependency only exists in `solitaire_server/Cargo.toml` and will not affect Android builds of the client crates.
**`solitaire_assetgen`:** The asset generation tool is desktop-only and not part of the client build. Unaffected.
---
## 7. Required Changes Per Crate
### `solitaire_core` and `solitaire_sync`
No changes required. Both are pure Rust with no platform dependencies.
### `solitaire_data`
| Change | Effort |
|---|---|
| Gate `keyring` dependency on `#[cfg(not(target_os = "android"))]` | 0.5 days |
| Implement `auth_tokens.rs` in-memory fallback for Android | 1 day |
| Add `internal_data_path()` override for `dirs::data_dir()` on Android | 1.5 days |
| Audit all `dirs::data_dir()` / `settings_file_path()` call sites to accept injected path | 0.5 days |
### `solitaire_engine`
| Change | Effort |
|---|---|
| Conditionally disable `CursorPlugin` on Android | 0.5 days |
| Disable `handle_fullscreen` on Android (or make it a no-op) | 0.25 days |
| Implement double-tap for auto-move (touch equivalent of `handle_double_click`) | 1 day |
| On-screen action bar for Undo, New Game, Hint (minimal floating buttons) | 2.5 days |
| Tune drag threshold for touch; expose as a platform-specific tuning constant | 0.5 days |
| Trigger sync push on `WindowEvent::Suspended` in `SyncPlugin` | 1 day |
| Verify `kira` audio on Android (test `DefaultBackend` / CPAL; implement fallback if needed) | 12 days |
### `solitaire_app`
| Change | Effort |
|---|---|
| Add `[lib]` target with `crate-type = ["cdylib"]` for Android builds | 0.25 days |
| Create `src/lib.rs` (or `src/android.rs`) Android entry point calling `android_main` | 0.5 days |
| Remove or guard fixed `resolution` / `resize_constraints` for Android | 0.25 days |
| Pass `AndroidApp::internal_data_path()` to a startup resource | 0.5 days |
### Build / Toolchain
| Change | Effort |
|---|---|
| Install cargo-mobile2, Android NDK r25c/r26, `aarch64-linux-android` target | 1 day |
| Run `cargo mobile init`, configure `android/` Gradle project | 0.5 days |
| Get a first build compiling (resolve linker / NDK issues) | 12 days |
---
## 8. Estimated Effort
| Phase | Description | Days |
|---|---|---|
| Toolchain setup | NDK, cargo-mobile2, first compile | 23 |
| `solitaire_data` Android adaptations | keyring fallback, data dir | 3 |
| `solitaire_app` Android entry point | cdylib, AndroidApp wiring | 1 |
| `solitaire_engine` guards and fixes | cursor, fullscreen, audio verify | 23 |
| Touch UX improvements | double-tap, action bar, threshold tuning | 45 |
| Testing on real device / emulator | iteration, lifecycle edge cases | 23 |
| **Total** | | **1417 days** |
This produces a playable, functionally complete Android build. It does not include Play Store preparation (signing keys, metadata, icon set, permissions manifest tuning) which would add 12 more days.
---
## 9. Recommended First Step
**Get the workspace to compile for `aarch64-linux-android` without running.**
This surfaces all the real linker and dependency errors before writing any gameplay code:
```bash
# Install toolchain
rustup target add aarch64-linux-android
cargo install --locked cargo-mobile2
# In the workspace root:
cargo mobile init # generates android/ directory
# Attempt a library build targeting Android
cargo build -p solitaire_app --target aarch64-linux-android 2>&1 | head -60
```
The first build will fail on `keyring` (no Android backend) and likely on `dirs`. Fixing those two in `solitaire_data` — gate `keyring` behind `cfg(not(target_os = "android"))` and stub the data directory — will probably get the workspace to a clean compile. From there, the path to a running APK is incremental.
Do not attempt to resolve audio or touch UX until the build compiles cleanly. Compile errors are the only true blockers; the rest are feature gaps.
File diff suppressed because it is too large Load Diff
@@ -1,172 +0,0 @@
# Phase 3 — Bevy Rendering & Interaction
> Status: In progress (started 2026-04-23)
> Crate: `solitaire_engine`
> Depends on: `solitaire_core` (complete), `bevy = 0.15` (includes `bevy::ui`), `kira = 0.9` (audio — Phase 3F+)
---
## Scope
Make the game playable with a graphical interface. This phase takes `solitaire_engine` from an empty stub to a full Bevy rendering + input layer wired to `solitaire_core::GameState`.
Out of scope (later phases):
- Persistence (`StatsSnapshot`, file I/O) — Phase 4
- Achievements toast content — Phase 5
- Audio — Phase 7
- Sync — Phase 8
---
## Sub-phases
### 3A — Plumbing & event wiring
**Modules under `solitaire_engine/src/`:**
- `lib.rs` — re-exports plugins, types
- `resources.rs`
- `GameStateResource(pub GameState)` — wraps `solitaire_core::GameState` directly (no `solitaire_data` layer yet)
- `DragState { cards: Vec<u32>, origin_pile: PileType, cursor_offset: Vec2, origin_z: f32 }` (starts empty)
- `SyncStatusResource(pub SyncStatus)` where `SyncStatus` is `Idle|Syncing|LastSynced(DateTime<Utc>)|Error(String)`
- `events.rs`
- `MoveRequestEvent { from: PileType, to: PileType, count: usize }`
- `DrawRequestEvent`
- `UndoRequestEvent`
- `NewGameRequestEvent { seed: Option<u64> }`
- `StateChangedEvent`
- `GameWonEvent { score: i32, time_seconds: u64 }`
- `CardFlippedEvent(pub u32)`
- `AchievementUnlockedEvent(pub AchievementRecord)` — placeholder, unused until Phase 5
- `game_plugin.rs``GamePlugin`:
- On `Startup`: init `GameStateResource::new(system_time_seed, DrawMode::DrawOne)`
- Systems: `handle_draw`, `handle_move`, `handle_undo`, `handle_new_game`
- Each fires `StateChangedEvent` on success; `GameWonEvent` when `check_win()` flips to true
- Errors: log via `tracing`, do not panic
- Register in [solitaire_app/src/main.rs](../../../solitaire_app/src/main.rs)
**Tests:** event-routing unit tests that drive `GamePlugin` in a headless `App::new()` and verify resource mutations.
**Exit:** `cargo test --workspace` green, `cargo clippy --workspace -- -D warnings` clean. Running the app still shows a blank window (no rendering yet), but pressing nothing crashes anything.
Commit: `feat(engine): add resources, events, and GamePlugin event routing`
---
### 3B — Layout + TablePlugin
**Modules:**
- `layout.rs` — pure function `compute_layout(window: Vec2) -> Layout`
- `Layout { card_size: Vec2, pile_positions: HashMap<PileType, Vec2> }`
- card_width = window.x / 9.0
- card_height = card_width * 1.4
- Row 1: stock, waste, [gap], 4 foundations
- Row 2: 7 tableau columns below
- `LayoutResource(pub Layout)` — a Bevy resource
- `table_plugin.rs``TablePlugin`:
- Spawns background rectangle (dark green `#0f5132`)
- Spawns 13 `PileMarker` sprite entities for empty-pile placeholders
- System `on_window_resized`: recompute `LayoutResource`, reposition pile markers
**Tests:** `compute_layout` at 800×600, 1280×800, 1920×1080 — all 13 piles within bounds, non-overlapping.
**Exit:** Window shows a green table with 13 translucent pile outlines that resize with the window.
Commit: `feat(engine): add layout, LayoutResource, and TablePlugin`
---
### 3C — CardPlugin rendering (procedural)
**Decision:** Phase 3 uses procedural cards (rounded white rectangle + rank/suit text). Real PNG assets can be slotted in later by replacing the sprite setup; API shape stays stable.
**Modules:**
- `card_plugin.rs``CardPlugin`:
- Component `CardEntity { card_id: u32 }`
- `StateChangedEvent` handler: sync entities with `GameStateResource` — spawn missing, despawn removed, reposition all
- Position: `LayoutResource.pile_positions[pile] + Vec3::Z * stack_index`
- Face-up: white rect + text of rank+suit glyph (red for hearts/diamonds, black for clubs/spades)
- Face-down: blue rect with a subtle pattern overlay
- No assets loaded — text uses Bevy's default font (or shipped system font if needed)
**Exit:** A freshly dealt game renders — stock (24 cards face-down), 7 tableau columns in standard 1/2/3/.../7 face-down + 1 face-up, empty foundations.
Commit: `feat(engine): add CardPlugin with procedural card rendering`
---
### 3D — Keyboard input & click-to-draw
**Modules:**
- `input_plugin.rs``InputPlugin`:
- Keyboard system: `KeyCode::KeyU``UndoRequestEvent`, `KeyN``NewGameRequestEvent{seed: None}`, `KeyD``DrawRequestEvent`, `Escape` → pause-stub event
- Mouse system: on left-click, if cursor over stock pile → `DrawRequestEvent`
**Exit:** Pressing D cycles stock↔waste on-screen; N deals a new game; U undoes.
Commit: `feat(engine): add InputPlugin with keyboard and stock-click`
---
### 3E — Drag & drop
**Modules:**
- Extend `input_plugin.rs` with drag systems:
- `start_drag`: on left mouse-down, ray-hit the top card (or run of face-up cards) of a pile; populate `DragState`; elevate z
- `follow_cursor`: while `DragState.cards` non-empty, move those entities to cursor position + per-card stack offset
- `end_drag`: on mouse-up, determine target pile; early-validate with `can_place_on_tableau` / `can_place_on_foundation`; fire `MoveRequestEvent` (backend also validates)
- On `MoveError` via `StateChangedEvent` non-emission: snap cards back with a short lerp (uses `CardAnim` from 3F)
- Multi-card tableau drag: grabbing card N pulls N..=top if all face-up
**Exit:** Full game playable with mouse. `GameWonEvent` fires on a win. No animations yet on invalid drop (just snap back instantly in 3E, smooth in 3F).
Commit: `feat(engine): add drag-and-drop input with multi-card tableau support`
---
### 3F — AnimationPlugin (polish)
**Modules:**
- `animation_plugin.rs``AnimationPlugin`:
- Component `CardAnim { start: Vec3, target: Vec3, elapsed: f32, duration: f32 }` — linear lerp 0.15s for moves
- Flip: `CardFlip { elapsed: f32, duration: f32, flips_to_face_up: bool }` — scale-X 1→0→1 over 0.2s, toggle `face_up` at midpoint, fire `CardFlippedEvent`
- Win cascade: on `GameWonEvent`, iterate foundation cards and schedule `CardAnim` to random off-screen targets with staggered 0.05s starts
- Toast component scaffold: bevy_ui `Node`/`Text` overlay, wired to `AchievementUnlockedEvent` (no content yet)
**Exit:** Valid moves animate smoothly; flipping a tableau card shows a flip; winning plays a cascade.
Commit: `feat(engine): add AnimationPlugin with slide, flip, and win cascade`
---
## Cross-cutting rules
- `solitaire_core` and `solitaire_sync` gain NO new dependencies.
- No `unwrap()` / `panic!()` in new Bevy systems — log errors via `tracing::warn!` and continue.
- `cargo test --workspace` and `cargo clippy --workspace -- -D warnings` green after EVERY sub-phase.
- Every commit follows `type(scope): description` convention.
- One `Plugin` per responsibility; cross-system communication is Events only.
---
## Open questions resolved
- **Procedural vs. sourced card art**: procedural for Phase 3.
- **`GameStateResource` layer**: wraps `solitaire_core::GameState` directly.
- **Phases 48 plugins** (Audio/UI/Achievement/Sync): not in Phase 3.
- **New-game seed**: system time when `None`, explicit when `Some(u64)`.
- **Commit cadence**: one per sub-phase.
---
## Risks
- Bevy 0.15 API drift from older tutorials — verify each API call as written.
- Procedural card text depends on Bevy's default font; if rendering is unreadable, embed a `.ttf` via `include_bytes!()` as a follow-up (still Phase 3, not 3F).
- `kira` audio API is async-friendly but requires careful thread management — initialise the `AudioManager` once at startup and store it in a Bevy `NonSend` resource.
File diff suppressed because it is too large Load Diff
-293
View File
@@ -1,293 +0,0 @@
<!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&amp;family=Inter:wght@400;500;700&amp;family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

-251
View File
@@ -1,251 +0,0 @@
# 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 214233) 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 214233):
### 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 17491750: red-suit text colour assertions (♥ + ♦).
- Line 17671768: 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`.
-219
View File
@@ -1,219 +0,0 @@
<!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&amp;family=Inter:wght@400;500;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

-258
View File
@@ -1,258 +0,0 @@
<!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&amp;family=Inter:wght@400;500;600&amp;family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

-285
View File
@@ -1,285 +0,0 @@
---
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 0007 form a monochrome ramp and 080F provide saturated accents. We map base16 slots to Material Design 3 token roles below.
### Source palette (base16-eighties)
| Slot | Hex | Role |
|---|---|---|
| base00 | `#151515` | background |
| base01 | `#202020` | surface-container |
| base02 | `#303030` | line-highlight (subtle) |
| base03 | `#505050` | outline / muted text |
| base04 | `#b0b0b0` | secondary text |
| base05 | `#d0d0d0` | foreground / on-surface |
| base06 | `#e0e0e0` | bright text |
| base07 | `#f5f5f5` | brightest highlight |
| base08 | `#fb9fb1` | red — used for `error`, `suit-red` |
| base09 | `#ddb26f` | orange — used for warning chips |
| base0A | `#acc267` | yellow/lime — used for `highlight-valid` (drag targets, valid moves) |
| base0B | `#12cfc0` | green/teal — used for `highlight-info` (toasts, neutral status) |
| base0C | `#6fc2ef` | cyan/sky — historically the primary CTA; now reserved for ad-hoc accents only |
| base0D | `#6fc2ef` | (alias) |
| base08 (project) | `#a54242` | brick red — primary CTA, focus ring, `selection` (project-specific extension; the base16-eighties `base08` slot is `#fb9fb1` pink which we keep as `error`/`suit-red`) |
| `suit-red-cb` slot | `#acc267` | lime — color-blind-mode swap for red suits (was `#6fc2ef` cyan before the 2026-05-08 primary-accent swap; lime is the next-best non-red base16-eighties accent) |
| base0E | `#e1a3ee` | violet — used for celebration (level-up, achievement unlock) |
| base0F | `#fb9fb1` | (alias) |
### Semantic assignments
- **CTA / Primary action**: brick red `#a54242`. Reserved for "Play," "New Game," "Save," "Resume," and the focus ring on selected cards. Never used decoratively. (Was cyan `#6fc2ef` before the 2026-05-08 swap.)
- **Valid-move / drag-target highlight**: lime `#acc267`. Reserved for in-game feedback only. Never appears in chrome.
- **Celebration**: lavender `#e1a3ee`. Used for level-up flashes, achievement unlock cards, and the daily-streak chip when the streak is active. Quiet otherwise.
- **Warning / soft alert**: gold `#ddb26f`. Used for "challenge expires in N minutes" chips, sync-pending status, and the daily-seed countdown.
- **Info**: teal `#12cfc0`. Used for neutral system toasts and the sync-connected indicator.
- **Error**: pink `#fb9fb1`. Used for sync conflict, server unreachable, invalid move shake.
## Suit Colors
**Two-color traditional pairing**, with mandatory color-blind
support. Saturated red for hearts + diamonds, near-white for clubs
+ spades — the "Microsoft Solitaire on dark mode" feel of a real
playing-card deck. (A brief 4-color-deck experiment shipped between
v0.21.0 and the next post-cut commit; reverted to traditional
2-color at the player's request.)
| Suit | Default | Color-blind mode | Glyph differentiation |
|---|---|---|---|
| Hearts | `#e35353` (saturated red) | `#acc267` (lime) | Solid filled glyph |
| Diamonds | `#e35353` (saturated red) | `#acc267` (lime) | **Outlined glyph (1.5px stroke)** |
| Spades | `#e8e8e8` (near-white) | `#e8e8e8` (unchanged) | Solid filled glyph |
| Clubs | `#e8e8e8` (near-white) | `#e8e8e8` (unchanged) | **Outlined glyph (1.5px stroke)** |
The outlined-glyph treatment is the **primary** differentiation mechanism. Color is supplementary. This means a player viewing the game on a monochrome display, or with severe red-green deficiency, can still distinguish all four suits without context. This is a hard requirement, not an optional setting.
The "color-blind mode" toggle in Settings swaps both red suits (hearts + diamonds) from `#e35353` to `#acc267` (lime); clubs + spades stay at the near-white. The toggle does not turn the outlined glyphs on or off, because outlined glyphs are always on. (Was red→cyan before the 2026-05-08 primary-accent swap; CBM moved to lime to stay hue-distinct from the new red-family primary.)
## Typography
**Monospace-forward, dual-font system.**
- **JetBrains Mono** is used for: HUD (score, timer, moves), card rank/value text, all labels, all headlines, all numerals anywhere in the app, and any chip-style component. This is the dominant face.
- **Inter** is used only for: long-form body copy (Help screen, Settings descriptions, achievement tooltips, onboarding copy). It is the *exception*, not the default.
Weights: 400 regular, 500 medium for labels, 700 bold for HUD numbers and headlines. No 600 / no italics anywhere — the terminal aesthetic doesn't have them.
Letter spacing: tight (`-0.02em`) on HUD score for visual mass; wide (`+0.08em`) on uppercase labels for readability at 12px. Body uses default (0).
HUD numbers must use **tabular figures** (`font-feature-settings: 'tnum'`) so the timer and score don't reflow as digits change.
## Layout & Spacing
Optimized for **Android portrait, 390×844 (Pixel 6 baseline), API 34**.
- **Margins**: 16px (1rem) edge safety margin. *Tighter than the previous system's 24px.* Eighties palettes are dense by nature; over-padding fights the aesthetic.
- **Tableau**: 7-column layout, 32px (2rem) vertical card overlap. Tighter than before to fit a longer cascade on phone screens.
- **HUD position**: top of screen, in the system safe area. Bottom 64px holds the action bar (Undo / Hint / New Game / Auto-complete). Action bar is **always visible** in-game — no hover-fade — because there is no hover on touch.
- **Touch target minimum**: 48dp on all interactive elements. Cards in the tableau may be smaller visually but use a 48dp invisible hit area centered on the visible glyph.
## Elevation & Depth
Depth is created through **tonal layering and 1px outlines**, not blur shadows. (Synthwave-flat, not Material-soft.)
- **Level 0 (Background)**: the `#151515` base canvas.
- **Level 1 (Tableau slots, empty piles)**: 1px dashed outline in `#353535`. Empty foundations show a faint suit glyph at 12% opacity inside the outline.
- **Level 2 (Cards at rest)**: solid `#1a1a1a` fill, 1px solid border in the suit color (so the suit is detectable at a glance even if the card is partially obscured).
- **Level 3 (Active / dragged card)**: same border, but glow effect: 0 0 12px of `#a54242` at 40% opacity. **No scale transform** — flatness preserved. Z-index lifts above siblings.
- **Modals**: full-screen with backdrop `#151515` at 95% opacity (just enough to dim the table without blurring it). Modal panel is `#202020` with a 1px `#505050` border — like a terminal pane.
- **Toasts**: bottom of screen, `#202020` fill, 1px border in the toast's accent color (info=teal, warning=gold, error=pink, celebration=lavender). 16px monospaced caption.
No `box-shadow` is used anywhere. **All depth is achieved with borders and tonal value.** This is a hard constraint.
## Shapes
The shape language is **soft-rounded but tight**:
- **Cards**: `rounded-md` (8px) — slightly less rounded than the previous system's 16px to read more "technical."
- **Buttons / chips / inputs**: `rounded` (4px) default, `rounded-sm` (2px) for the smallest chips.
- **Modals / sheets**: `rounded-lg` (12px).
- **Avatars / circular indicators**: `rounded-full`.
- **Card-back pattern corners**: matches the card's `rounded-md`.
Selection highlights use a **2px inset stroke** in `#a54242` following the host shape's corner radius. Never an outer stroke — the outer stroke is reserved for the suit-color hairline.
## Motion
**Snappy, no spring.** All transitions use `ease-out` with a 120ms duration unless specified.
- Card lift (start drag): 80ms.
- Card place (drop): 120ms with a 16ms holdframe (no bounce).
- Modal enter: 200ms ease-out, fade + 8px translate-up.
- Modal exit: 120ms ease-in, fade only.
- Selection ring appear: 80ms.
- Win-summary stat reveal: 60ms each, staggered 40ms.
- HUD number tick: instant (no transition) — terminal counters don't ease.
**Optional CRT effect**: a 1-frame scanline sweep across the screen on game-state transitions (start, win, restart). User-toggleable in Settings. Off by default.
## Components
### Game Cards
Flat face design.
- Background: `#1a1a1a`
- Border: none. The card shape is defined by the body fill alone against the play surface. The earlier 1px suit-coloured border was removed because it produced visible anti-aliasing artifacts at the rounded corners (a "gray sliver" where the colored stroke faded through gray pixels into the dark play surface). The 5-unit brightness gap between `#1a1a1a` body and `#151515` surface is enough to read as a card edge without an explicit stroke.
- Top-left: rank in JetBrains Mono Bold 18px + small suit glyph (10px)
- Bottom-right: large suit glyph (32px), upright (same orientation as the top-left small glyph — single-orientation digital play does not benefit from the traditional 180° inverted-corner indicator)
- Corner radius: 8px
- Suit differentiation: hearts and spades have **filled** glyphs; diamonds and clubs have **outlined** glyphs (1.5px stroke)
### Card Back ("Terminal" theme)
- Theme name: `"Terminal"`
- Author: `"Rusty Solitaire"`
- Background: `#151515`
- Pattern: horizontal scanlines at 2px pitch in `#1a1a1a` (1px line, 1px gap), full bleed
- Border: 1px solid `#353535`
- Top-left badge: a 12×16px solid `#a54242` block (the "terminal cursor"), 6px from the corner
- Bottom-right monogram: the characters `▌RS` in JetBrains Mono 12px, color `#505050`, 6px from the corner
- Corner radius: 8px (matches face)
### Primary Buttons
Solid `#a54242` fill, `#151515` text, JetBrains Mono Medium 14px uppercase with `+0.08em` tracking. 4px corner radius. Pressed state: darken to `#7a3030`. Disabled: `#353535` fill, `#505050` text.
### Secondary Buttons
Transparent fill, 1px `#505050` border, `#d0d0d0` text. Hover/press: border becomes `#a54242`, text becomes `#a54242`.
### HUD Chips
`#202020` fill, no border, 4px radius. Monospaced 16px text. Score chip pulses to `#acc267` for 200ms when score increases.
### Drag Targets
When a card is being dragged over a valid pile, the pile's empty-slot dashed outline becomes:
- Solid 1px in `#acc267`
- Plus a 0 0 8px outer glow in `#acc267` at 30% opacity
This is the *only* place glow effects appear in the system.
### Modals
Full-screen backdrop at 95% opacity. Centered panel: `#202020` fill, 1px `#505050` border, 12px corner radius. Title bar shows the screen name in monospaced 14px, color `#a0a0a0`, with a single `▌` cursor character prefix to reinforce the terminal pane motif.
### Navigation Bar
Fixed at the bottom of in-game screens. Height: 64px. `#202020` fill, 1px top border in `#353535`. Four icon buttons: Undo / Hint / New / Auto-complete. Icons: 24px, 1.5px stroke weight, color `#d0d0d0`. Active/pressed: icon color `#a54242`.
### Status / Sync Indicator
Top-right corner of the HUD: a 6px circular dot.
- Connected & synced: `#12cfc0`
- Pending: `#ddb26f` (pulsing 1.5s)
- Error: `#fb9fb1` (steady)
- Offline: `#505050`
## Accessibility
1. **Color-blind mode** (Settings → Gameplay): swaps the red suits' default `#e35353` for `#acc267` (lime). Outlined-glyph differentiation remains active in *all* modes.
2. **High-contrast mode** (Settings → Gameplay): boosts on-surface from `#d0d0d0` to `#f5f5f5`, outline from `#505050` to `#a0a0a0`, suit-red from `#fb9fb1` to `#ff8aa0`.
3. **Reduce-motion mode** (Settings → Gameplay): disables card-lift transition (instant z-lift), disables CRT scanline effect, disables the warning-chip pulse animation.
4. **Tabular figures** are mandatory for any number that updates live (timer, score, moves) so they don't reflow.
5. **Touch targets** are 48dp minimum even when the visual element is smaller.
6. **Text contrast**: all body text on background passes WCAG AA at minimum (`#d0d0d0` on `#151515` = 9.5:1; `#a0a0a0` on `#151515` = 5.7:1).
-283
View File
@@ -1,283 +0,0 @@
# 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 "Ferrous Solitaire *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 16002400 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 14402400; `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 (6090 cm). Mobile uses the same
rungs at a closer reading distance (3040 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.
-253
View File
@@ -1,253 +0,0 @@
<!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&amp;family=Inter:wght@400;500&amp;family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

-200
View File
@@ -1,200 +0,0 @@
<!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&amp;family=Inter:wght@400&amp;family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;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 &amp; 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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

-343
View File
@@ -1,343 +0,0 @@
<!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&amp;family=Inter:wght@400;500&amp;family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

-225
View File
@@ -1,225 +0,0 @@
<!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&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&amp;family=Inter:wght@400&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

-315
View File
@@ -1,315 +0,0 @@
<!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&amp;family=Inter:wght@400;500&amp;family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;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 &amp; 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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

-259
View File
@@ -1,259 +0,0 @@
<!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&amp;family=Inter:wght@400;500&amp;family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

-206
View File
@@ -1,206 +0,0 @@
<!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&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700;800&amp;family=Inter:wght@400;500&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

-211
View File
@@ -1,211 +0,0 @@
<!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&amp;family=Inter:wght@400;500&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

@@ -1,218 +0,0 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=390, height=844, initial-scale=1.0" name="viewport"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700;800&amp;family=Inter:wght@400;500;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"highlight-valid": "#acc267",
"outline": "#505050",
"highlight-celebration": "#e1a3ee",
"error-container": "#93000a",
"surface-container": "#202020",
"on-secondary-fixed": "#161e00",
"tertiary": "#f7c3ff",
"inverse-on-surface": "#2d3134",
"tertiary-fixed-dim": "#f0b0fc",
"on-primary-fixed": "#001e2c",
"info": "#12cfc0",
"on-tertiary": "#4c195b",
"secondary-container": "#435401",
"surface": "#151515",
"tertiary-container": "#e1a3ee",
"outline-variant": "#3f484e",
"suit-red": "#fb9fb1",
"secondary-fixed": "#d5ec8c",
"error": "#fb9fb1",
"primary-container": "#6fc2ef",
"surface-container-lowest": "#0b0f11",
"on-surface": "#e0e3e6",
"tertiary-fixed": "#fbd7ff",
"on-secondary-container": "#b2c86d",
"on-primary-fixed-variant": "#004c69",
"on-primary": "#003549",
"on-secondary": "#293500",
"on-primary-container": "#004f6c",
"secondary": "#bad073",
"surface-container-highest": "#313538",
"primary": "#a1dcff",
"surface-container-low": "#181c1f",
"secondary-fixed-dim": "#bad073",
"warning": "#ddb26f",
"suit-black": "#d0d0d0",
"surface-variant": "#313538",
"on-tertiary-container": "#683476",
"on-tertiary-fixed": "#340043",
"on-secondary-fixed-variant": "#3c4d00",
"on-background": "#e0e3e6",
"surface-bright": "#363a3d",
"on-error": "#690005",
"primary-fixed-dim": "#7ed0fe",
"on-tertiary-fixed-variant": "#653173",
"suit-red-cb": "#6fc2ef",
"inverse-surface": "#e0e3e6",
"on-surface-variant": "#bfc8cf",
"background": "#101417",
"primary-fixed": "#c4e7ff",
"on-error-container": "#ffdad6",
"inverse-primary": "#00668a",
"surface-dim": "#101417",
"surface-container-high": "#272a2d",
"surface-tint": "#7ed0fe",
"cyan-terminal": "#6fc2ef"
},
"borderRadius": {
"DEFAULT": "0.125rem",
"lg": "0.25rem",
"xl": "0.5rem",
"full": "0.75rem"
},
"spacing": {
"touch-target-min": "48dp",
"stack-overlap": "2rem",
"gutter-card": "0.375rem",
"action-bar-height": "64px",
"margin-edge": "1rem"
},
"fontFamily": {
"headline": ["JetBrains Mono"],
"label-caps": ["JetBrains Mono"],
"body-md": ["Inter"],
"hud-score": ["JetBrains Mono"],
"card-rank": ["JetBrains Mono"],
"hud-timer": ["JetBrains Mono"]
}
}
}
}
</script>
<style>
.scanline-pattern {
background: repeating-linear-gradient(
0deg,
#151515,
#151515 2px,
#1a1a1a 2px,
#1a1a1a 4px
);
}
.checker-pattern {
background-color: #ffffff;
background-image:
linear-gradient(45deg, #004c69 25%, transparent 25%),
linear-gradient(-45deg, #004c69 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #004c69 75%),
linear-gradient(-45deg, transparent 75%, #004c69 75%);
background-size: 8px 8px;
background-position: 0 0, 0 4px, 4px -4px, -4px 0px;
}
.stripe-pattern {
background: repeating-linear-gradient(
0deg,
#fb9fb1,
#fb9fb1 4px,
#151515 4px,
#151515 8px
);
}
</style>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-surface text-on-surface min-h-screen flex flex-col items-center overflow-hidden selection:bg-cyan-terminal selection:text-surface">
<!-- 1. Status Bar -->
<header class="w-full h-8 bg-surface-container flex items-center justify-between px-4 border-b border-outline-variant">
<span class="font-label-caps text-[12px] text-on-surface uppercase tracking-tight">▌onboard/02-theme.tsx</span>
<span class="font-label-caps text-[12px] text-[#a0a0a0] uppercase tracking-widest">STEP 2 OF 3</span>
</header>
<!-- 2. Hero Illustration Band -->
<section class="w-full flex flex-col items-center pt-8 pb-4">
<div class="h-[100px] flex items-center justify-center relative">
<span class="text-cyan-terminal font-headline text-[48px] mr-4 select-none"></span>
<div class="flex -space-x-4">
<div class="w-[24px] h-[34px] border border-outline bg-surface scanline-pattern transform -rotate-12 translate-y-2"></div>
<div class="w-[24px] h-[34px] border border-outline bg-surface checker-pattern transform rotate-0 z-10"></div>
<div class="w-[24px] h-[34px] border border-outline bg-surface stripe-pattern transform rotate-12 translate-y-2"></div>
</div>
</div>
<h2 class="font-headline text-[28px] font-bold text-suit-black tracking-tight leading-none">PICK YOUR DECK</h2>
</section>
<!-- 3. Headline & Description -->
<section class="w-full px-margin-edge text-center mb-6">
<h3 class="font-headline text-[22px] font-bold text-suit-black mb-1">CHOOSE A CARD-BACK</h3>
<p class="font-body-md text-[12px] text-[#a0a0a0] leading-tight">
You can swap or import more themes from Settings later.
</p>
</section>
<!-- 4. Theme Selection Grid -->
<main class="w-full px-margin-edge grid grid-cols-3 gap-2 flex-grow max-h-[220px]">
<!-- Tile 1: Terminal (Active) -->
<div class="flex flex-col items-center">
<div class="w-full aspect-[110/150] bg-surface-container border-2 border-cyan-terminal rounded-lg p-3 relative flex items-center justify-center overflow-hidden">
<div class="w-full h-full scanline-pattern border border-outline-variant relative">
<div class="absolute top-1 left-1 w-2 h-3 bg-cyan-terminal"></div>
<div class="absolute bottom-1 right-1 font-headline text-[10px] text-on-surface opacity-50">▌RS</div>
</div>
<div class="absolute top-1 right-1 bg-cyan-terminal text-surface w-4 h-4 flex items-center justify-center rounded-full">
<span class="material-symbols-outlined text-[12px] font-bold">check</span>
</div>
</div>
<span class="mt-2 font-label-caps text-[12px] font-bold text-suit-black tracking-widest uppercase">TERMINAL</span>
</div>
<!-- Tile 2: Classic -->
<div class="flex flex-col items-center opacity-70">
<div class="w-full aspect-[110/150] bg-surface-container border border-outline rounded-lg p-3 relative flex items-center justify-center overflow-hidden">
<div class="w-full h-full checker-pattern border border-outline-variant"></div>
</div>
<span class="mt-2 font-label-caps text-[12px] font-bold text-suit-black tracking-widest uppercase">CLASSIC</span>
</div>
<!-- Tile 3: Stripes -->
<div class="flex flex-col items-center opacity-70">
<div class="w-full aspect-[110/150] bg-surface-container border border-outline rounded-lg p-3 relative flex items-center justify-center overflow-hidden">
<div class="w-full h-full stripe-pattern border border-outline-variant"></div>
</div>
<span class="mt-2 font-label-caps text-[12px] font-bold text-suit-black tracking-widest uppercase">STRIPES</span>
</div>
</main>
<!-- 5. More Info -->
<div class="w-full text-center mt-4">
<span class="font-label-caps text-[11px] font-medium text-[#a0a0a0] tracking-widest">
<span class="text-cyan-terminal">+</span> MORE IN SETTINGS
</span>
</div>
<!-- 6. Step Indicator -->
<section class="w-full flex flex-col items-center mt-6">
<div class="flex gap-1 h-2 mb-2">
<div class="w-8 h-1 bg-cyan-terminal rounded-full"></div>
<div class="w-8 h-1 bg-cyan-terminal rounded-full"></div>
<div class="w-8 h-1 bg-outline rounded-full"></div>
</div>
<div class="font-headline text-[12px] font-medium tracking-[0.2em]">
<span class="text-cyan-terminal">[1]</span>
<span class="text-cyan-terminal">[2]</span>
<span class="text-outline">[3]</span>
</div>
</section>
<!-- 7. Bottom Action Bar -->
<footer class="fixed bottom-0 w-full h-[64px] bg-surface-container flex items-center justify-between px-margin-edge z-50">
<button class="w-[48%] h-12 border border-outline bg-transparent text-suit-black font-label-caps text-[13px] font-medium uppercase rounded-lg active:bg-surface-variant transition-colors">
← BACK
</button>
<button class="w-[48%] h-12 bg-cyan-terminal text-surface font-label-caps text-[14px] font-bold uppercase rounded-lg active:opacity-80 transition-opacity">
NEXT →
</button>
</footer>
<!-- Image descriptive data for the model (hidden visually) -->
<div class="hidden" data-alt="A detailed user interface screen for a retro-terminal themed solitaire game called Rusty Solitaire. The background is a deep black with cyan and gray accents. In the center, a card theme selection grid displays three different card back designs: a scanline pattern, a checker pattern, and a striped pattern. The visual style is crisp, technical, and uses monospaced typography to evoke a command-line interface or professional developer environment. The mood is minimalist, efficient, and technologically nostalgic."></div>
</body></html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

-212
View File
@@ -1,212 +0,0 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Rouge Solitaire - Pause</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&amp;family=JetBrains+Mono:wght@400;500;700;800&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"on-secondary-fixed": "#161e00",
"secondary-fixed": "#d5ec8c",
"warning": "#ddb26f",
"surface-container-low": "#181c1f",
"surface-container": "#1c2023",
"on-primary-fixed-variant": "#004c69",
"outline-variant": "#3f484e",
"on-tertiary-container": "#683476",
"surface-container-high": "#272a2d",
"on-primary-fixed": "#001e2c",
"primary-fixed": "#c4e7ff",
"surface-bright": "#363a3d",
"outline": "#505050",
"tertiary-fixed-dim": "#f0b0fc",
"tertiary": "#f7c3ff",
"on-surface": "#e0e3e6",
"secondary": "#bad073",
"tertiary-fixed": "#fbd7ff",
"info": "#12cfc0",
"primary": "#a1dcff",
"secondary-fixed-dim": "#bad073",
"surface-tint": "#7ed0fe",
"background": "#101417",
"surface-container-highest": "#313538",
"on-tertiary-fixed": "#340043",
"highlight-valid": "#acc267",
"inverse-primary": "#00668a",
"surface-dim": "#101417",
"error": "#fb9fb1",
"on-error": "#690005",
"inverse-surface": "#e0e3e6",
"suit-red": "#fb9fb1",
"suit-black": "#d0d0d0",
"inverse-on-surface": "#2d3134",
"highlight-celebration": "#e1a3ee",
"on-error-container": "#ffdad6",
"on-primary": "#003549",
"surface": "#151515",
"surface-container-lowest": "#0b0f11",
"primary-fixed-dim": "#7ed0fe",
"on-secondary": "#293500",
"suit-red-cb": "#6fc2ef",
"on-tertiary": "#4c195b",
"error-container": "#93000a",
"on-secondary-fixed-variant": "#3c4d00",
"on-secondary-container": "#b2c86d",
"on-surface-variant": "#bfc8cf",
"on-primary-container": "#004f6c",
"primary-container": "#6fc2ef",
"on-background": "#e0e3e6",
"surface-variant": "#313538",
"secondary-container": "#435401",
"on-tertiary-fixed-variant": "#653173",
"tertiary-container": "#e1a3ee"
},
"borderRadius": {
"DEFAULT": "0.125rem",
"lg": "0.25rem",
"xl": "0.5rem",
"full": "0.75rem"
},
"spacing": {
"touch-target-min": "48dp",
"action-bar-height": "64px",
"stack-overlap": "2rem",
"margin-edge": "1rem",
"gutter-card": "0.375rem"
},
"fontFamily": {
"body-md": ["Inter"],
"headline": ["JetBrains Mono"],
"hud-score": ["JetBrains Mono"],
"hud-timer": ["JetBrains Mono"],
"card-rank": ["JetBrains Mono"],
"label-caps": ["JetBrains Mono"]
},
"fontSize": {
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}],
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}]
}
},
},
}
</script>
<style>
.scanline {
background: linear-gradient(
to bottom,
transparent 50%,
rgba(0, 0, 0, 0.05) 50%
);
background-size: 100% 4px;
pointer-events: none;
}
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
</style>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-background text-on-surface font-body-md overflow-hidden antialiased">
<!-- Background Tableau (Simulated by Dimmed Image Overlay) -->
<div class="fixed inset-0 z-0">
<img alt="Game Tableau Background" class="w-full h-full object-cover" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDJSHHDQ5Y5qul5C_xabnOSM9aS3uxcWSTk47AOHrS_KIlQi0Ur7YhtL0BomjEWTDc8vRLpytWeG4kf5xgxBzpORahTtsWyXOUPsVRg6_H_qp0QjM6DDo57rOPwjU6TFdfK3Pi7cO9rg-xnUSSu1wu29WyKVwSWDDaA5cZ4QN_9L81YMTCTMKAwDTGsY3eGsj1b1i1X2CdF211aepkhmX8xf4bnV35WSB3QuYxUwlPct0Met7iLFf-AGBeizhK6IAboW5u-Wpg8Ag"/>
<!-- 95% Opacity Scrim -->
<div class="absolute inset-0 bg-surface opacity-95"></div>
<!-- Scanline Overlay for Texture -->
<div class="absolute inset-0 scanline"></div>
</div>
<!-- Modal Container -->
<div class="fixed inset-0 z-10 flex items-center justify-center p-margin-edge">
<!-- Modal Panel -->
<div class="w-[330px] h-[480px] bg-[#202020] border border-outline rounded-xl flex flex-col overflow-hidden">
<!-- Title Bar -->
<div class="h-[28px] bg-[#1a1a1a] border-b border-[#353535] px-3 flex items-center justify-between">
<div class="flex items-center gap-1.5">
<span class="text-primary-container font-headline text-[12px] leading-none mt-px"></span>
<span class="font-headline text-[12px] text-[#a0a0a0] leading-none">pause.tsx</span>
</div>
<button class="flex items-center justify-center">
<span class="material-symbols-outlined text-[16px] text-[#505050]">close</span>
</button>
</div>
<!-- Content Canvas -->
<div class="flex-1 flex flex-col items-center pt-8 px-4">
<!-- Headline -->
<h1 class="font-headline text-[24px] font-bold text-[#d0d0d0] tracking-tight text-center">
GAME PAUSED
</h1>
<!-- Subline -->
<p class="font-headline text-[12px] text-[#a0a0a0] mt-1 text-center">
12:34 ELAPSED · 87 MOVES · DRAW-3
</p>
<!-- Mini-Stat Chips -->
<div class="flex gap-2 mt-4 justify-center">
<div class="bg-[#1a1a1a] border border-[#353535] rounded-sm px-2 py-1 flex flex-col items-center">
<span class="font-headline text-[11px] text-[#d0d0d0]">SCORE 247</span>
</div>
<div class="bg-[#1a1a1a] border border-[#353535] rounded-sm px-2 py-1 flex flex-col items-center">
<span class="font-headline text-[11px] text-[#d0d0d0]">STOCK 18</span>
</div>
<div class="bg-[#1a1a1a] border border-[#353535] rounded-sm px-2 py-1 flex flex-col items-center">
<span class="font-headline text-[11px] text-[#d0d0d0]">MOVES 87</span>
</div>
</div>
<!-- Action Buttons Cluster -->
<div class="w-full mt-6 space-y-3">
<!-- Primary CTA -->
<button class="w-full h-[48px] bg-primary-container text-surface flex items-center justify-center rounded-lg active:scale-95 transition-transform duration-75">
<span class="font-headline text-[14px] font-bold tracking-[0.08em] uppercase">▶ RESUME GAME</span>
</button>
<!-- Secondary Buttons -->
<button class="w-full h-[48px] bg-transparent border border-outline text-[#d0d0d0] flex items-center justify-center rounded-lg hover:border-primary-container hover:text-primary-container transition-colors duration-120">
<span class="font-headline text-[13px] font-medium tracking-[0.08em] uppercase">↻ RESTART</span>
</button>
<button class="w-full h-[48px] bg-transparent border border-outline text-[#d0d0d0] flex items-center justify-center rounded-lg hover:border-primary-container hover:text-primary-container transition-colors duration-120">
<span class="font-headline text-[13px] font-medium tracking-[0.08em] uppercase">✕ FORFEIT</span>
</button>
<button class="w-full h-[48px] bg-transparent border border-outline text-[#d0d0d0] flex items-center justify-center rounded-lg hover:border-primary-container hover:text-primary-container transition-colors duration-120">
<span class="font-headline text-[13px] font-medium tracking-[0.08em] uppercase">⌂ QUIT TO MENU</span>
</button>
</div>
</div>
<!-- Footer Status Line -->
<div class="h-[24px] border-t border-[#353535] px-3 flex items-center justify-between">
<div class="flex items-center gap-1">
<span class="text-primary-container font-headline text-[11px]"></span>
<span class="font-headline text-[11px] text-[#a0a0a0]">NORMAL</span>
<span class="text-[#505050] text-[11px]"></span>
<span class="font-headline text-[11px] text-[#a0a0a0]">pause</span>
</div>
<div class="flex items-center gap-1 font-headline text-[11px]">
<span class="text-[#a0a0a0]">[ESC]</span>
<span class="text-[#505050]">resume</span>
</div>
</div>
</div>
</div>
<!-- Hidden Navigation Shell (Suppressed due to Task-Focused Modal Context) -->
<!-- But included visually as per the brand anchor hierarchy for TopAppBar identity if it were visible -->
<header class="hidden fixed top-0 w-full h-action-bar-height flex items-center justify-between px-margin-edge w-full bg-background border-b border-outline-variant">
<div class="flex items-center gap-2">
<span class="material-symbols-outlined text-primary">terminal</span>
<span class="font-headline text-headline text-primary uppercase tracking-tighter">▌ROUGE_SOLITAIRE</span>
</div>
</header>
<!-- Bottom Nav Suppression Logic: Not rendered to prioritize the focus canvas -->
</body></html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

-274
View File
@@ -1,274 +0,0 @@
<!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&amp;family=Inter:wght@400;500;700&amp;family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
/* CRT Scanline Overlay Effect */
.crt-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.1) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.03), rgba(0, 255, 0, 0.01), rgba(0, 0, 255, 0.03));
background-size: 100% 3px, 3px 100%;
pointer-events: none;
z-index: 100;
}
.custom-scrollbar::-webkit-scrollbar {
height: 4px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #151515;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #505050;
}
</style>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"surface-container-low": "#181c1f",
"on-primary-fixed": "#001e2c",
"on-error": "#690005",
"suit-black": "#d0d0d0",
"inverse-primary": "#00668a",
"on-primary-container": "#004f6c",
"suit-red-cb": "#6fc2ef",
"tertiary-container": "#e1a3ee",
"secondary-fixed": "#d5ec8c",
"highlight-valid": "#acc267",
"surface-container-highest": "#313538",
"secondary-fixed-dim": "#bad073",
"warning": "#ddb26f",
"on-secondary-fixed": "#161e00",
"primary-fixed-dim": "#7ed0fe",
"on-surface-variant": "#bfc8cf",
"on-primary": "#003549",
"tertiary-fixed": "#fbd7ff",
"primary-container": "#6fc2ef",
"on-background": "#e0e3e6",
"error-container": "#93000a",
"outline": "#505050",
"on-tertiary-fixed": "#340043",
"primary-fixed": "#c4e7ff",
"on-surface": "#e0e3e6",
"on-secondary": "#293500",
"surface-container-high": "#272a2d",
"on-tertiary": "#4c195b",
"secondary": "#bad073",
"on-tertiary-container": "#683476",
"inverse-on-surface": "#2d3134",
"surface-dim": "#101417",
"primary": "#a1dcff",
"on-tertiary-fixed-variant": "#653173",
"on-error-container": "#ffdad6",
"surface-variant": "#313538",
"surface-tint": "#7ed0fe",
"surface-container-lowest": "#0b0f11",
"surface": "#151515",
"tertiary": "#f7c3ff",
"on-secondary-fixed-variant": "#3c4d00",
"surface-bright": "#363a3d",
"tertiary-fixed-dim": "#f0b0fc",
"outline-variant": "#3f484e",
"info": "#12cfc0",
"secondary-container": "#435401",
"on-secondary-container": "#b2c86d",
"inverse-surface": "#e0e3e6",
"error": "#fb9fb1",
"suit-red": "#fb9fb1",
"background": "#101417",
"highlight-celebration": "#e1a3ee",
"on-primary-fixed-variant": "#004c69",
"surface-container": "#202020"
},
"borderRadius": {
"DEFAULT": "0.125rem",
"lg": "0.25rem",
"xl": "0.5rem",
"full": "0.75rem"
},
"spacing": {
"stack-overlap": "2rem",
"action-bar-height": "64px",
"touch-target-min": "48dp",
"gutter-card": "0.375rem",
"margin-edge": "1rem"
},
"fontFamily": {
"headline": ["JetBrains Mono"],
"label-caps": ["JetBrains Mono"],
"hud-score": ["JetBrains Mono"],
"hud-timer": ["JetBrains Mono"],
"body-md": ["Inter"],
"card-rank": ["JetBrains Mono"]
},
"fontSize": {
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}],
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}]
}
},
},
}
</script>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-background text-on-background font-body-md selection:bg-primary selection:text-background overflow-x-hidden">
<div class="crt-overlay"></div>
<!-- Status Bar -->
<header class="bg-surface-container h-[32px] w-full flex items-center justify-between px-margin-edge z-50">
<div class="font-headline text-[12px] text-on-surface-variant tracking-wider">
▌profile.tsx
</div>
<div class="flex items-center gap-2">
<span class="w-2 h-2 rounded-full bg-info"></span>
<span class="font-label-caps text-[10px] text-on-surface">● SYNCED</span>
</div>
</header>
<!-- Main Canvas -->
<main class="flex-1 overflow-y-auto pb-24">
<!-- Profile Header -->
<section class="h-[120px] bg-surface-container border-b border-outline-variant flex items-center px-margin-edge gap-4">
<div class="w-[64px] h-[64px] bg-[#1a1a1a] border border-outline flex items-center justify-center shrink-0">
<span class="font-headline text-[28px] text-primary-container">RS</span>
</div>
<div class="flex flex-col gap-1 overflow-hidden">
<h1 class="font-headline text-[18px] text-on-surface truncate">anonymous@local</h1>
<p class="font-label-caps text-on-surface-variant text-[10px]">MEMBER SINCE 2026-04-22</p>
<div class="flex gap-2 mt-1">
<span class="px-2 py-0.5 bg-[#1a1a1a] font-label-caps text-[10px] text-suit-black">247 GAMES</span>
<span class="px-2 py-0.5 bg-[#1a1a1a] font-label-caps text-[10px] text-suit-black">61% WR</span>
<span class="px-2 py-0.5 bg-[#1a1a1a] font-label-caps text-[10px] text-suit-black">12 STREAK</span>
</div>
</div>
</section>
<!-- Level/XP Section -->
<section class="p-margin-edge bg-surface-container border-b border-outline-variant">
<div class="flex justify-between items-baseline mb-2">
<span class="font-headline text-[24px] text-on-surface">LEVEL 12</span>
<span class="font-hud-timer text-on-surface-variant">320/500 XP</span>
</div>
<div class="h-3 w-full bg-[#353535] relative overflow-hidden">
<div class="h-full bg-primary-container" style="width: 64%;"></div>
</div>
<div class="flex items-center gap-2 mt-3">
<span class="w-1.5 h-1.5 rounded-full bg-highlight-celebration"></span>
<span class="font-label-caps text-[10px] text-on-surface-variant">180 XP TO LEVEL 13</span>
</div>
</section>
<!-- Unlocked Cards -->
<section class="mt-6">
<h2 class="px-margin-edge font-headline text-[14px] text-on-surface mb-4">▌ unlocked.cards</h2>
<div class="flex overflow-x-auto gap-4 px-margin-edge pb-4 custom-scrollbar">
<!-- Terminal (Active) -->
<div class="shrink-0 flex flex-col gap-2">
<div class="w-[60px] h-[84px] bg-surface-container-low border-2 border-primary-container relative flex items-center justify-center p-1">
<div class="w-full h-full bg-[#151515] overflow-hidden flex flex-col p-1">
<div class="w-2 h-2.5 bg-primary-container mb-auto"></div>
<div class="self-end text-[8px] font-headline text-on-surface-variant opacity-50">▌RS</div>
</div>
</div>
<span class="font-label-caps text-[9px] text-primary-container text-center">ACTIVE</span>
</div>
<!-- Classic -->
<div class="shrink-0 flex flex-col gap-2">
<div class="w-[60px] h-[84px] bg-white border border-outline relative p-1">
<div class="w-full h-full border border-red-200 bg-red-50 opacity-20"></div>
</div>
<span class="font-label-caps text-[9px] text-on-surface-variant text-center opacity-50">TAP TO USE</span>
</div>
<!-- Stripes -->
<div class="shrink-0 flex flex-col gap-2">
<div class="w-[60px] h-[84px] bg-surface-container border border-outline p-1">
<div class="w-full h-full bg-gradient-to-br from-secondary-container via-surface to-secondary-container opacity-40"></div>
</div>
<span class="font-label-caps text-[9px] text-on-surface-variant text-center opacity-50">TAP TO USE</span>
</div>
<!-- Polka -->
<div class="shrink-0 flex flex-col gap-2">
<div class="w-[60px] h-[84px] bg-surface-container border border-outline p-1 overflow-hidden relative">
<div class="w-full h-full opacity-30" style="background-image: radial-gradient(#505050 1px, transparent 0); background-size: 6px 6px;"></div>
</div>
<span class="font-label-caps text-[9px] text-on-surface-variant text-center opacity-50">TAP TO USE</span>
</div>
</div>
</section>
<!-- Unlocked Backgrounds -->
<section class="mt-6">
<h2 class="px-margin-edge font-headline text-[14px] text-on-surface mb-4">▌ unlocked.backgrounds</h2>
<div class="flex overflow-x-auto gap-4 px-margin-edge pb-4 custom-scrollbar">
<!-- Default (Active) -->
<div class="shrink-0 flex flex-col gap-2">
<div class="w-[80px] h-[56px] bg-[#151515] border-2 border-primary-container"></div>
<span class="font-label-caps text-[9px] text-primary-container text-center">ACTIVE</span>
</div>
<!-- Forest -->
<div class="shrink-0 flex flex-col gap-2">
<div class="w-[80px] h-[56px] bg-[#0d160d] border border-outline"></div>
<span class="font-label-caps text-[9px] text-on-surface-variant text-center opacity-50">FOREST</span>
</div>
<!-- Slate -->
<div class="shrink-0 flex flex-col gap-2">
<div class="w-[80px] h-[56px] bg-[#1c2128] border border-outline"></div>
<span class="font-label-caps text-[9px] text-on-surface-variant text-center opacity-50">SLATE</span>
</div>
<!-- Midnight -->
<div class="shrink-0 flex flex-col gap-2">
<div class="w-[80px] h-[56px] bg-[#09090b] border border-outline"></div>
<span class="font-label-caps text-[9px] text-on-surface-variant text-center opacity-50">MIDNIGHT</span>
</div>
</div>
</section>
<!-- Sign-in Card -->
<section class="mt-8 px-margin-edge">
<button class="w-full h-[64px] bg-surface-container border border-dashed border-outline flex items-center justify-between px-6 hover:bg-surface-variant transition-colors group">
<span class="font-label-caps text-on-surface-variant tracking-widest">+ SIGN IN TO SYNC PROGRESS</span>
<span class="material-symbols-outlined text-primary-container group-hover:translate-x-1 transition-transform">arrow_forward</span>
</button>
</section>
</main>
<!-- TopAppBar (from Shared Components - as Terminal Header) -->
<div class="fixed top-[32px] left-0 w-full z-40 bg-background border-b border-outline-variant flex items-center justify-between px-margin-edge h-action-bar-height">
<div class="flex items-center gap-3">
<span class="material-symbols-outlined text-primary">terminal</span>
<span class="font-headline text-headline text-primary tracking-tighter">~/root/usr/settings</span>
</div>
<div class="flex items-center">
<button class="p-2 hover:bg-surface-variant text-primary transition-colors">
<span class="material-symbols-outlined">close</span>
</button>
</div>
</div>
<!-- BottomNavBar (from Shared Components - as Terminal Footer) -->
<nav class="fixed bottom-0 left-0 w-full z-50 h-[24px] bg-surface-container-lowest border-t border-outline-variant flex justify-between items-center px-4">
<div class="flex items-center gap-4">
<span class="font-label-caps text-[10px] text-on-surface-variant">▌ NORMAL │ profile</span>
</div>
<div class="flex items-center gap-4">
<button class="flex items-center gap-1 group">
<span class="font-label-caps text-[10px] text-on-surface-variant group-hover:text-primary">[ESC] back</span>
</button>
</div>
</nav>
<!-- Decorative CRT Scanline overlay line -->
<div class="fixed top-0 left-0 w-full h-[1px] bg-primary opacity-20 pointer-events-none animate-pulse"></div>
</body></html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

-271
View File
@@ -1,271 +0,0 @@
<!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&amp;family=Inter:wght@400;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"surface-variant": "#313538",
"surface-dim": "#101417",
"secondary-fixed-dim": "#bad073",
"surface-bright": "#363a3d",
"secondary-fixed": "#d5ec8c",
"secondary-container": "#435401",
"on-tertiary-fixed-variant": "#653173",
"surface-container-highest": "#313538",
"outline-variant": "#3f484e",
"error": "#fb9fb1",
"surface-container": "#202020",
"inverse-on-surface": "#2d3134",
"on-primary-fixed-variant": "#004c69",
"outline": "#505050",
"on-secondary": "#293500",
"suit-red": "#fb9fb1",
"inverse-primary": "#00668a",
"on-secondary-container": "#b2c86d",
"highlight-celebration": "#e1a3ee",
"warning": "#ddb26f",
"primary-fixed-dim": "#7ed0fe",
"info": "#12cfc0",
"primary-fixed": "#c4e7ff",
"highlight-valid": "#acc267",
"on-surface-variant": "#bfc8cf",
"on-tertiary": "#4c195b",
"background": "#101417",
"tertiary-container": "#e1a3ee",
"suit-black": "#d0d0d0",
"on-error-container": "#ffdad6",
"on-surface": "#d0d0d0",
"primary": "#a1dcff",
"error-container": "#93000a",
"secondary": "#bad073",
"surface": "#151515",
"primary-container": "#6fc2ef",
"suit-red-cb": "#6fc2ef",
"on-primary": "#003549",
"surface-container-low": "#181c1f",
"tertiary-fixed-dim": "#f0b0fc",
"surface-tint": "#7ed0fe",
"on-tertiary-container": "#683476",
"on-secondary-fixed": "#161e00",
"surface-container-lowest": "#0b0f11",
"on-tertiary-fixed": "#340043",
"surface-container-high": "#272a2d",
"on-error": "#690005",
"tertiary-fixed": "#fbd7ff",
"tertiary": "#f7c3ff",
"on-background": "#e0e3e6",
"on-secondary-fixed-variant": "#3c4d00",
"on-primary-fixed": "#001e2c",
"on-primary-container": "#004f6c",
"inverse-surface": "#e0e3e6"
},
"borderRadius": {
"DEFAULT": "0.125rem",
"lg": "0.25rem",
"xl": "0.5rem",
"full": "0.75rem"
},
"spacing": {
"action-bar-height": "64px",
"stack-overlap": "2rem",
"gutter-card": "0.375rem",
"touch-target-min": "48dp",
"margin-edge": "1rem"
},
"fontFamily": {
"label-caps": ["JetBrains Mono"],
"hud-timer": ["JetBrains Mono"],
"hud-score": ["JetBrains Mono"],
"body-md": ["Inter"],
"headline": ["JetBrains Mono"],
"card-rank": ["JetBrains Mono"]
},
"fontSize": {
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}],
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}]
}
},
},
}
</script>
<style>
.radial-segment {
clip-path: polygon(50% 50%, 100% 0, 100% 100%);
}
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
display: inline-block;
vertical-align: middle;
}
.scanline-bg {
background: repeating-linear-gradient(
0deg,
rgba(26, 26, 26, 1) 0px,
rgba(26, 26, 26, 1) 2px,
rgba(21, 21, 21, 1) 2px,
rgba(21, 21, 21, 1) 4px
);
}
</style>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-surface font-body-md text-on-surface select-none overflow-hidden h-screen w-screen flex flex-col">
<!-- Underlying Game Tableau (Dimmed Background) -->
<main class="relative flex-grow opacity-30 grid grid-cols-7 gap-2 p-margin-edge pointer-events-none">
<!-- Top row: Foundation/Stock -->
<div class="col-span-1 aspect-[2/3] border border-dashed border-outline-variant bg-surface-container flex items-center justify-center">
<span class="material-symbols-outlined text-outline-variant">terminal</span>
</div>
<div class="col-span-1 aspect-[2/3] border border-dashed border-outline-variant"></div>
<div class="col-span-1"></div>
<div class="col-span-1 aspect-[2/3] border border-outline border-suit-red-cb flex items-center justify-center">
<span class="material-symbols-outlined text-suit-red-cb">favorite</span>
</div>
<div class="col-span-1 aspect-[2/3] border border-outline border-suit-black flex items-center justify-center">
<span class="material-symbols-outlined text-suit-black">backspace</span>
</div>
<div class="col-span-1 aspect-[2/3] border border-outline border-suit-red-cb flex items-center justify-center">
<span class="material-symbols-outlined text-suit-red-cb">diamond</span>
</div>
<div class="col-span-1 aspect-[2/3] border border-outline border-suit-black flex items-center justify-center">
<span class="material-symbols-outlined text-suit-black">spa</span>
</div>
<!-- Tableau piles -->
<div class="col-span-7 grid grid-cols-7 gap-2 mt-4">
<div class="space-y-[-120%]">
<div class="aspect-[2/3] bg-surface-container-high border border-outline p-1 flex flex-col justify-between">
<span class="font-card-rank text-card-rank text-suit-red-cb">K</span>
<span class="material-symbols-outlined self-end text-3xl rotate-180 text-suit-red-cb">diamond</span>
</div>
</div>
<div class="space-y-[-120%]">
<div class="aspect-[2/3] scanline-bg border border-outline relative">
<div class="absolute top-1 left-1 w-3 h-4 bg-primary"></div>
<span class="absolute bottom-1 right-1 font-label-caps text-[10px] text-primary">▌RS</span>
</div>
<div class="aspect-[2/3] bg-surface-container-high border border-outline p-1 flex flex-col justify-between">
<span class="font-card-rank text-card-rank text-suit-black">Q</span>
<span class="material-symbols-outlined self-end text-3xl rotate-180 text-suit-black" style="font-variation-settings: 'FILL' 1;">spa</span>
</div>
</div>
<div class="space-y-[-120%]">
<div class="aspect-[2/3] scanline-bg border border-outline"></div>
<div class="aspect-[2/3] scanline-bg border border-outline"></div>
<div class="aspect-[2/3] bg-surface-container-high border border-outline p-1 flex flex-col justify-between">
<span class="font-card-rank text-card-rank text-suit-red-cb">J</span>
<span class="material-symbols-outlined self-end text-3xl rotate-180 text-suit-red-cb" style="font-variation-settings: 'FILL' 1;">favorite</span>
</div>
</div>
<!-- More stacks... omitted for brevity as background -->
</div>
</main>
<!-- Radial Menu Overlay -->
<div class="fixed inset-0 z-50 bg-[#151515]/70 flex items-center justify-center overflow-hidden">
<div class="relative w-[280px] h-[280px] flex items-center justify-center">
<!-- Outer Circular Ring Shell -->
<div class="absolute inset-0 rounded-full border border-outline bg-surface-container overflow-hidden">
<!-- SVG Segments Construction -->
<svg class="w-full h-full transform -rotate-22.5" viewbox="0 0 100 100">
<!-- Slice 1 (UNDO) - Top / 12:00 -->
<!-- Active state: bg-primary-container/15, stroke-primary -->
<path d="M 50 50 L 50 0 A 50 50 0 0 1 85.35 14.65 Z" fill="#6fc2ef26" stroke="#6fc2ef" stroke-width="0.5" transform="rotate(-22.5, 50, 50)"></path>
<!-- Slice 2 (REDO) -->
<path d="M 50 50 L 85.35 14.65 A 50 50 0 0 1 100 50 Z" fill="transparent" stroke="#353535" stroke-width="0.25" transform="rotate(-22.5, 50, 50)"></path>
<!-- Slice 3 (HINT) -->
<path d="M 50 50 L 100 50 A 50 50 0 0 1 85.35 85.35 Z" fill="transparent" stroke="#353535" stroke-width="0.25" transform="rotate(-22.5, 50, 50)"></path>
<!-- Slice 4 (AUTO) -->
<path d="M 50 50 L 85.35 85.35 A 50 50 0 0 1 50 100 Z" fill="transparent" stroke="#353535" stroke-width="0.25" transform="rotate(-22.5, 50, 50)"></path>
<!-- Slice 5 (NEW) -->
<path d="M 50 50 L 50 100 A 50 50 0 0 1 14.65 85.35 Z" fill="transparent" stroke="#353535" stroke-width="0.25" transform="rotate(-22.5, 50, 50)"></path>
<!-- Slice 6 (PAUSE) -->
<path d="M 50 50 L 14.65 85.35 A 50 50 0 0 1 0 50 Z" fill="transparent" stroke="#353535" stroke-width="0.25" transform="rotate(-22.5, 50, 50)"></path>
<!-- Slice 7 (STATS) -->
<path d="M 50 50 L 0 50 A 50 50 0 0 1 14.65 14.65 Z" fill="transparent" stroke="#353535" stroke-width="0.25" transform="rotate(-22.5, 50, 50)"></path>
<!-- Slice 8 (SETTINGS) -->
<path d="M 50 50 L 14.65 14.65 A 50 50 0 0 1 50 0 Z" fill="transparent" stroke="#353535" stroke-width="0.25" transform="rotate(-22.5, 50, 50)"></path>
</svg>
</div>
<!-- Labels and Icons Overlay -->
<div class="absolute inset-0 pointer-events-none">
<!-- 12:00 UNDO (ACTIVE) -->
<div class="absolute top-[12%] left-1/2 -translate-x-1/2 flex flex-col items-center text-primary">
<span class="material-symbols-outlined text-[24px]" data-icon="undo">undo</span>
<span class="font-label-caps text-[11px] mt-1">UNDO</span>
</div>
<!-- 1:30 REDO -->
<div class="absolute top-[22%] right-[12%] flex flex-col items-center text-on-surface">
<span class="material-symbols-outlined text-[24px]" data-icon="redo">redo</span>
<span class="font-label-caps text-[11px] mt-1">REDO</span>
</div>
<!-- 3:00 HINT -->
<div class="absolute top-1/2 right-[8%] -translate-y-1/2 flex flex-col items-center text-on-surface">
<span class="material-symbols-outlined text-[24px]" data-icon="lightbulb">lightbulb</span>
<span class="font-label-caps text-[11px] mt-1">HINT</span>
</div>
<!-- 4:30 AUTO -->
<div class="absolute bottom-[22%] right-[12%] flex flex-col items-center text-on-surface">
<span class="material-symbols-outlined text-[24px]" data-icon="double_arrow">double_arrow</span>
<span class="font-label-caps text-[11px] mt-1">AUTO</span>
</div>
<!-- 6:00 NEW -->
<div class="absolute bottom-[12%] left-1/2 -translate-x-1/2 flex flex-col items-center text-on-surface">
<span class="material-symbols-outlined text-[24px]" data-icon="add">add</span>
<span class="font-label-caps text-[11px] mt-1">NEW</span>
</div>
<!-- 7:30 PAUSE -->
<div class="absolute bottom-[22%] left-[12%] flex flex-col items-center text-on-surface">
<span class="material-symbols-outlined text-[24px]" data-icon="pause">pause</span>
<span class="font-label-caps text-[11px] mt-1">PAUSE</span>
</div>
<!-- 9:00 STATS -->
<div class="absolute top-1/2 left-[8%] -translate-y-1/2 flex flex-col items-center text-on-surface">
<span class="material-symbols-outlined text-[24px]" data-icon="bar_chart">bar_chart</span>
<span class="font-label-caps text-[11px] mt-1">STATS</span>
</div>
<!-- 10:30 SETTINGS -->
<div class="absolute top-[22%] left-[12%] flex flex-col items-center text-on-surface">
<span class="material-symbols-outlined text-[24px]" data-icon="settings">settings</span>
<span class="font-label-caps text-[11px] mt-1">SETTINGS</span>
</div>
</div>
<!-- Inner Hole -->
<div class="absolute w-20 h-20 rounded-full bg-surface-container border border-outline-variant flex flex-col items-center justify-center z-10">
<div class="font-headline text-[32px] text-primary leading-none"></div>
<div class="font-label-caps text-[10px] text-on-surface-variant tracking-widest mt-1">RADIAL</div>
</div>
</div>
<!-- Instructions (Bottom Floating) -->
<div class="absolute bottom-12 left-0 w-full flex flex-col items-center gap-4">
<div class="font-label-caps text-[12px] text-on-surface-variant tracking-wider">
DRAG TO SELECT · RELEASE TO ACTIVATE
</div>
<!-- Status Line (Vim style) -->
<div class="w-full h-8 bg-surface-container border-t border-outline-variant flex items-center justify-center">
<span class="font-label-caps text-[11px] text-on-surface-variant">
<span class="text-primary"></span> NORMAL │ radial · 1/8 selected
</span>
</div>
</div>
</div>
<!-- Hidden image for standard requirement compliance, though not visually used in this specific overlay task -->
<div class="hidden">
<img data-alt="A macro shot of a vintage terminal screen displaying green computer code and technical data. The lighting is low-key, with a soft glow emanating from the screen, highlighting the CRT scanlines and subtle reflections. The aesthetic is purely technical and retro-futuristic, focusing on precision and high-contrast digital artifacts. Deep blacks and vibrant green neon tones dominate the color palette, evoking a high-performance system environment." src="https://lh3.googleusercontent.com/aida-public/AB6AXuAQuJUCOQev_BN72KyX0c-ylmW3DMZD-gOUlylYo3w1SrSpGnvorMvSUwe5oGPAgBgc050cCowC8f1QaxHEDN-DUkyCynOLhzrZHXyCJh2ebCWd6x1quLQwp0ffwbHsZW1-J2zAMuUydMNpEVmpHFQDij0yjVg6lxc6JdsC0etMoAWMhb61S3HUoDffSl-Q23N8Oc77r3dSf6kLFKAMAJCbXFz4nTaJKCKAwtMs62pLr6fd1jzMZrItH43RaO28uzMzvnGGZj3Miw"/>
</div>
</body></html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

-284
View File
@@ -1,284 +0,0 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Solitaire Replay Overlay</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&amp;family=Inter:wght@400;500&amp;family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"on-background": "#e0e3e6",
"suit-red-cb": "#6fc2ef",
"surface-container": "#202020",
"primary": "#a1dcff",
"tertiary-fixed-dim": "#f0b0fc",
"on-tertiary-fixed-variant": "#653173",
"surface-tint": "#7ed0fe",
"outline": "#505050",
"suit-black": "#d0d0d0",
"secondary": "#bad073",
"on-surface": "#d0d0d0",
"on-primary": "#003549",
"on-error-container": "#ffdad6",
"on-secondary-fixed-variant": "#3c4d00",
"surface-bright": "#363a3d",
"surface-variant": "#313538",
"secondary-container": "#435401",
"surface-container-highest": "#313538",
"surface-container-low": "#181c1f",
"primary-container": "#6fc2ef",
"on-error": "#690005",
"primary-fixed-dim": "#7ed0fe",
"tertiary-container": "#e1a3ee",
"on-tertiary-container": "#683476",
"suit-red": "#fb9fb1",
"highlight-celebration": "#e1a3ee",
"on-primary-fixed-variant": "#004c69",
"secondary-fixed": "#d5ec8c",
"primary-fixed": "#c4e7ff",
"on-primary-container": "#004f6c",
"secondary-fixed-dim": "#bad073",
"warning": "#ddb26f",
"on-secondary": "#293500",
"info": "#12cfc0",
"on-tertiary-fixed": "#340043",
"background": "#101417",
"surface-container-high": "#272a2d",
"surface-dim": "#101417",
"surface": "#151515",
"inverse-surface": "#e0e3e6",
"on-surface-variant": "#bfc8cf",
"error-container": "#93000a",
"tertiary": "#f7c3ff",
"inverse-primary": "#00668a",
"surface-container-lowest": "#0b0f11",
"inverse-on-surface": "#2d3134",
"on-primary-fixed": "#001e2c",
"highlight-valid": "#acc267",
"outline-variant": "#3f484e",
"tertiary-fixed": "#fbd7ff",
"on-secondary-fixed": "#161e00",
"error": "#fb9fb1",
"on-tertiary": "#4c195b",
"on-secondary-container": "#b2c86d"
},
"borderRadius": {
"DEFAULT": "0.125rem",
"lg": "0.25rem",
"xl": "0.5rem",
"full": "0.75rem"
},
"spacing": {
"touch-target-min": "48dp",
"stack-overlap": "2rem",
"margin-edge": "1rem",
"action-bar-height": "64px",
"gutter-card": "0.375rem"
},
"fontFamily": {
"hud-timer": ["JetBrains Mono"],
"body-md": ["Inter"],
"label-caps": ["JetBrains Mono"],
"hud-score": ["JetBrains Mono"],
"headline": ["JetBrains Mono"],
"card-rank": ["JetBrains Mono"]
},
"fontSize": {
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"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"}],
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}]
}
},
},
}
</script>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
.card-back-pattern {
background-color: #151515;
background-image: repeating-linear-gradient(0deg, transparent, transparent 2px, #1a1a1a 2px, #1a1a1a 4px);
}
/* Custom mechanical transition style */
.mechanical-transition {
transition: all 120ms cubic-bezier(0.4, 0, 0.2, 1);
}
</style>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-surface text-on-surface font-body-md select-none overflow-hidden flex flex-col h-[844px] w-[390px] mx-auto border-x border-outline-variant">
<!-- Status Bar -->
<header class="h-8 bg-surface-container flex items-center justify-between px-4 border-b border-outline-variant flex-shrink-0">
<div class="flex items-center gap-2">
<span class="text-primary font-headline text-[14px]">▌replay.tsx</span>
</div>
<div class="flex items-center text-on-surface-variant font-label-caps text-[10px] tracking-wider uppercase">
GAME #2024-127 · 87 MOVES
</div>
</header>
<!-- Game Peek Band (Tableau) -->
<main class="h-[240px] relative bg-background overflow-hidden border-b border-outline-variant">
<!-- 7-Column Tableau (Dimmed 50%) -->
<div class="absolute inset-0 opacity-50 flex justify-around p-2 gap-1">
<!-- Tableau Columns 1-7 -->
<div class="flex-1 flex flex-col items-center">
<div class="w-full aspect-[2/3] border border-dashed border-outline-variant mb-1"></div>
<div class="w-full aspect-[2/3] bg-surface-container-low border border-outline"></div>
</div>
<div class="flex-1 flex flex-col items-center">
<div class="w-full aspect-[2/3] bg-surface-container-low border border-outline"></div>
</div>
<div class="flex-1 flex flex-col items-center">
<div class="w-full aspect-[2/3] bg-surface-container-low border border-outline"></div>
</div>
<div class="flex-1 flex flex-col items-center">
<div class="w-full aspect-[2/3] bg-surface-container-low border border-outline"></div>
</div>
<div class="flex-1 flex flex-col items-center">
<div class="w-full aspect-[2/3] bg-surface-container-low border border-outline"></div>
</div>
<div class="flex-1 flex flex-col items-center">
<div class="w-full aspect-[2/3] bg-surface-container-low border border-outline"></div>
</div>
<div class="flex-1 flex flex-col items-center">
<div class="w-full aspect-[2/3] bg-surface-container-low border border-outline relative">
<!-- Central Focused Card (Move 47) -->
<div class="absolute inset-0 z-20 opacity-100">
<!-- Shadow-less highlight using glow outline -->
<div class="w-full h-full bg-[#1a1a1a] border border-suit-red-cb ring-2 ring-suit-red-cb/40 flex flex-col justify-between p-1">
<div class="flex flex-col">
<span class="font-card-rank text-card-rank text-suit-red-cb">4</span>
<span class="material-symbols-outlined text-[14px] text-suit-red-cb" data-icon="diamond">diamond</span>
</div>
<div class="self-end rotate-180 flex flex-col">
<span class="font-card-rank text-card-rank text-suit-red-cb">4</span>
<span class="material-symbols-outlined text-[14px] text-suit-red-cb" data-icon="diamond">diamond</span>
</div>
</div>
<!-- Move Chip -->
<div class="absolute -top-6 left-1/2 -translate-x-1/2 bg-suit-red-cb px-2 py-0.5 rounded-sm">
<span class="text-surface font-label-caps text-[9px] font-bold">MOVE 47/87</span>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- Playback Toolbar -->
<div class="h-16 bg-surface-container flex items-center justify-between px-4 border-b border-outline-variant">
<!-- Left: Timer -->
<div class="flex flex-col">
<span class="text-on-surface font-hud-timer text-[18px] font-bold leading-none">00:42</span>
<span class="text-[11px] text-[#a0a0a0] font-label-caps tracking-tighter">/ 02:18</span>
</div>
<!-- Center: Controls -->
<div class="flex items-center gap-4">
<button class="material-symbols-outlined text-on-surface-variant hover:text-primary mechanical-transition" data-icon="skip_previous">skip_previous</button>
<button class="material-symbols-outlined text-on-surface-variant hover:text-primary mechanical-transition" data-icon="arrow_left">arrow_left</button>
<button class="material-symbols-outlined text-suit-red-cb text-[32px] mechanical-transition" data-icon="play_arrow">play_arrow</button>
<button class="material-symbols-outlined text-on-surface-variant hover:text-primary mechanical-transition" data-icon="arrow_right">arrow_right</button>
<button class="material-symbols-outlined text-on-surface-variant hover:text-primary mechanical-transition" data-icon="skip_next">skip_next</button>
</div>
<!-- Right: Speed -->
<div class="flex items-center bg-surface-variant border border-outline px-2 py-1 gap-1">
<span class="font-label-caps text-[14px] font-bold text-on-surface">1.0x</span>
<span class="material-symbols-outlined text-[16px] text-on-surface-variant" data-icon="unfold_more">unfold_more</span>
</div>
</div>
<!-- Scrub Bar Area -->
<div class="px-margin-edge pt-6 pb-8 bg-surface-container-low border-b border-outline-variant">
<div class="relative w-full h-1 bg-outline rounded-full">
<!-- Cyan Progress Track -->
<div class="absolute left-0 top-0 h-full bg-suit-red-cb" style="width: 54%;"></div>
<!-- Notches & Labels -->
<div class="absolute inset-0 flex justify-between">
<div class="relative">
<div class="w-[1px] h-3 bg-outline -mt-1"></div>
<span class="absolute -bottom-5 left-1/2 -translate-x-1/2 text-[9px] font-label-caps text-outline">0%</span>
</div>
<div class="relative">
<div class="w-[1px] h-3 bg-outline -mt-1"></div>
<span class="absolute -bottom-5 left-1/2 -translate-x-1/2 text-[9px] font-label-caps text-outline">25%</span>
</div>
<div class="relative">
<div class="w-[1px] h-3 bg-outline -mt-1"></div>
<span class="absolute -bottom-5 left-1/2 -translate-x-1/2 text-[9px] font-label-caps text-outline">50%</span>
</div>
<div class="relative">
<div class="w-[1px] h-3 bg-outline -mt-1"></div>
<span class="absolute -bottom-5 left-1/2 -translate-x-1/2 text-[9px] font-label-caps text-outline">75%</span>
</div>
<div class="relative">
<div class="w-[1px] h-3 bg-outline -mt-1"></div>
<span class="absolute -bottom-5 left-1/2 -translate-x-1/2 text-[9px] font-label-caps text-outline">100%</span>
</div>
</div>
<!-- Current Marker (54%) -->
<div class="absolute top-1/2 -translate-y-1/2 w-3 h-3 bg-suit-red-cb border border-surface" style="left: 54%;"></div>
<div class="absolute -top-4 left-[54%] -translate-x-1/2 text-[10px] text-suit-red-cb font-label-caps font-bold">47/87</div>
<!-- Win Marker (72%) -->
<div class="absolute top-0 w-[2px] h-3 bg-highlight-valid -translate-y-1" style="left: 72%;"></div>
<div class="absolute -bottom-5 left-[72%] -translate-x-1/2 text-[8px] text-highlight-valid font-label-caps font-bold whitespace-nowrap">WIN MOVE</div>
</div>
</div>
<!-- Move Log Card -->
<section class="flex-1 bg-surface-container p-4 overflow-y-auto">
<h3 class="font-label-caps text-label-caps text-on-surface-variant mb-4 flex items-center gap-2">
<span class="w-1.5 h-3 bg-primary block"></span>
MOVE LOG · 47/87
</h3>
<div class="flex flex-col font-label-caps text-[12px]">
<!-- Log Rows -->
<div class="flex items-center h-6 px-2 text-[#a0a0a0] border-b border-outline-variant/30">
<span class="w-8">44 |</span>
<span>5♥ → tableau col 3</span>
</div>
<div class="flex items-center h-6 px-2 text-[#a0a0a0] border-b border-outline-variant/30">
<span class="w-8">45 |</span>
<span>8♣ → tableau col 1</span>
</div>
<div class="flex items-center h-6 px-2 text-[#a0a0a0] border-b border-outline-variant/30">
<span class="w-8">46 |</span>
<span>stock cycle</span>
</div>
<!-- Highlighted Active Move -->
<div class="flex items-center h-6 px-2 bg-suit-red-cb text-surface-container font-bold">
<span class="w-8">▶ 47 |</span>
<span>4♦ → 5♣ on col 7</span>
</div>
<div class="flex items-center h-6 px-2 text-on-surface border-b border-outline-variant/30">
<span class="w-8">48 |</span>
<span class="material-symbols-outlined text-[14px] align-middle mr-1" data-icon="foundation">foundation</span> A♠ → foundation
</div>
<div class="flex items-center h-6 px-2 text-on-surface border-b border-outline-variant/30">
<span class="w-8">49 |</span>
<span class="material-symbols-outlined text-[14px] align-middle mr-1" data-icon="foundation">foundation</span> 2♠ → foundation
</div>
</div>
</section>
<!-- Footer -->
<footer class="h-6 bg-surface-container flex items-center justify-between px-4 border-t border-outline-variant flex-shrink-0 text-[10px] font-label-caps tracking-wider">
<div class="text-on-surface-variant">
<span class="text-primary"></span> NORMAL │ replay
</div>
<div class="text-on-surface-variant opacity-70">
[SPACE] play · [← →] scrub · [ESC]
</div>
</footer>
<!-- Overlay Background (For visualization of depth) -->
<div class="fixed inset-0 pointer-events-none border-[16px] border-surface-dim/40 z-50"></div>
</body></html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

-258
View File
@@ -1,258 +0,0 @@
<!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&amp;family=Inter:wght@400;500;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"on-secondary": "#293500",
"surface-container": "#1c2023",
"on-primary-fixed": "#001e2c",
"surface-tint": "#7ed0fe",
"suit-black": "#d0d0d0",
"on-background": "#e0e3e6",
"tertiary-fixed": "#fbd7ff",
"on-tertiary": "#4c195b",
"on-error": "#690005",
"secondary-fixed-dim": "#bad073",
"tertiary-fixed-dim": "#f0b0fc",
"suit-red-cb": "#6fc2ef",
"on-primary-container": "#004f6c",
"on-error-container": "#ffdad6",
"surface-container-lowest": "#0b0f11",
"on-surface": "#e0e3e6",
"secondary-container": "#435401",
"inverse-on-surface": "#2d3134",
"suit-red": "#fb9fb1",
"tertiary": "#f7c3ff",
"surface-dim": "#101417",
"background": "#101417",
"error": "#fb9fb1",
"on-secondary-container": "#b2c86d",
"on-primary": "#003549",
"on-tertiary-container": "#683476",
"surface-container-low": "#181c1f",
"warning": "#ddb26f",
"surface-variant": "#313538",
"primary-fixed": "#c4e7ff",
"primary": "#a1dcff",
"on-secondary-fixed-variant": "#3c4d00",
"surface": "#151515",
"secondary": "#bad073",
"outline-variant": "#3f484e",
"on-tertiary-fixed-variant": "#653173",
"highlight-valid": "#acc267",
"primary-fixed-dim": "#7ed0fe",
"surface-bright": "#363a3d",
"secondary-fixed": "#d5ec8c",
"primary-container": "#6fc2ef",
"surface-container-highest": "#313538",
"on-secondary-fixed": "#161e00",
"surface-container-high": "#272a2d",
"inverse-primary": "#00668a",
"on-tertiary-fixed": "#340043",
"tertiary-container": "#e1a3ee",
"on-surface-variant": "#bfc8cf",
"highlight-celebration": "#e1a3ee",
"inverse-surface": "#e0e3e6",
"outline": "#505050",
"info": "#12cfc0",
"on-primary-fixed-variant": "#004c69",
"error-container": "#93000a"
},
"borderRadius": {
"DEFAULT": "0.125rem",
"lg": "0.25rem",
"xl": "0.5rem",
"full": "0.75rem"
},
"spacing": {
"gutter-card": "0.375rem",
"stack-overlap": "2rem",
"margin-edge": "1rem",
"touch-target-min": "48dp",
"action-bar-height": "64px",
"top-action-bar-height": "32px",
"bottom-action-bar-height": "24px"
},
"fontFamily": {
"body-md": ["Inter"],
"label-caps": ["JetBrains Mono"],
"card-rank": ["JetBrains Mono"],
"hud-timer": ["JetBrains Mono"],
"hud-score": ["JetBrains Mono"],
"headline": ["JetBrains Mono"]
},
"fontSize": {
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}],
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}]
}
},
},
}
</script>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
}
.card-back-pattern {
background: repeating-linear-gradient(
0deg,
#151515,
#151515 2px,
#1a1a1a 2px,
#1a1a1a 4px
);
}
</style>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-background text-on-background font-body-md selection:bg-primary selection:text-on-primary-container overflow-hidden flex flex-col h-[844px] w-[390px] mx-auto border-x border-outline-variant">
<!-- Status Bar / TopAppBar Logic -->
<header class="bg-[#202020] h-8 flex items-center justify-between px-4 w-full z-50">
<div class="flex items-center gap-1">
<span class="text-primary font-headline text-[12px]"></span>
<span class="text-on-surface font-label-caps text-[12px]">settings.toml</span>
</div>
<div class="text-outline font-label-caps text-[12px]">v0.20.0</div>
</header>
<!-- NavigationDrawer (Tab Strip Mapping) -->
<nav class="bg-[#1a1a1a] h-10 border-b border-[#353535] flex items-center w-full">
<!-- COSMETIC Tab (Active) -->
<div class="h-full flex-1 flex flex-col items-center justify-center relative">
<span class="text-primary font-label-caps text-[12px] px-2">[ COSMETIC ]</span>
<div class="absolute bottom-0 left-0 right-0 h-[2px] bg-primary"></div>
</div>
<!-- GAMEPLAY Tab -->
<div class="h-full flex-1 flex items-center justify-center">
<span class="text-[#a0a0a0] font-label-caps text-[12px]">GAMEPLAY</span>
</div>
<!-- SYNC Tab -->
<div class="h-full flex-1 flex items-center justify-center">
<span class="text-[#a0a0a0] font-label-caps text-[12px]">SYNC</span>
</div>
<!-- AUDIO Tab -->
<div class="h-full flex-1 flex items-center justify-center">
<span class="text-[#a0a0a0] font-label-caps text-[12px]">AUDIO</span>
</div>
</nav>
<!-- Content Area (Canvas) -->
<main class="flex-1 bg-[#151515] p-margin-edge overflow-y-auto space-y-2">
<!-- row: card_theme -->
<div class="bg-[#202020] h-14 rounded-[4px] px-3 flex items-center justify-between">
<div class="flex flex-col">
<span class="text-[#a0a0a0] font-label-caps text-[11px]">card_theme</span>
<span class="text-suit-black font-label-caps text-[14px]">Terminal</span>
</div>
<span class="text-primary material-symbols-outlined" data-icon="arrow_forward">arrow_forward</span>
</div>
<!-- row: background -->
<div class="bg-[#202020] h-14 rounded-[4px] px-3 flex items-center justify-between">
<div class="flex flex-col">
<span class="text-[#a0a0a0] font-label-caps text-[11px]">background</span>
<span class="text-suit-black font-label-caps text-[14px]">Solid #151515</span>
</div>
<span class="text-primary material-symbols-outlined" data-icon="arrow_forward">arrow_forward</span>
</div>
<!-- row: card_back -->
<div class="bg-[#202020] h-14 rounded-[4px] px-3 flex items-center justify-between">
<div class="flex flex-col">
<span class="text-[#a0a0a0] font-label-caps text-[11px]">card_back</span>
<span class="text-suit-black font-label-caps text-[14px]">Terminal</span>
</div>
<div class="flex items-center gap-3">
<div class="w-4 h-6 border border-outline card-back-pattern relative overflow-hidden">
<div class="absolute top-0.5 left-0.5 w-1.5 h-2 bg-primary"></div>
</div>
<span class="text-primary material-symbols-outlined" data-icon="arrow_forward">arrow_forward</span>
</div>
</div>
<!-- row: color_blind_mode -->
<div class="bg-[#202020] h-14 rounded-[4px] px-3 flex items-center justify-between">
<div class="flex flex-col">
<span class="text-[#a0a0a0] font-label-caps text-[11px]">color_blind_mode</span>
<span class="text-suit-black font-label-caps text-[14px]">false</span>
</div>
<!-- Toggle OFF -->
<div class="w-10 h-5 bg-[#202020] border border-outline rounded-full relative">
<div class="absolute left-0.5 top-0.5 w-3.5 h-3.5 bg-outline rounded-full"></div>
</div>
</div>
<!-- row: high_contrast -->
<div class="bg-[#202020] h-14 rounded-[4px] px-3 flex items-center justify-between">
<div class="flex flex-col">
<span class="text-[#a0a0a0] font-label-caps text-[11px]">high_contrast</span>
<span class="text-suit-black font-label-caps text-[14px]">false</span>
</div>
<!-- Toggle OFF -->
<div class="w-10 h-5 bg-[#202020] border border-outline rounded-full relative">
<div class="absolute left-0.5 top-0.5 w-3.5 h-3.5 bg-outline rounded-full"></div>
</div>
</div>
<!-- row: reduce_motion -->
<div class="bg-[#202020] h-14 rounded-[4px] px-3 flex items-center justify-between">
<div class="flex flex-col">
<span class="text-[#a0a0a0] font-label-caps text-[11px]">reduce_motion</span>
<span class="text-suit-black font-label-caps text-[14px]">true</span>
</div>
<!-- Toggle ON -->
<div class="w-10 h-5 bg-[#1f3a4a] border border-primary/50 rounded-full relative">
<div class="absolute right-0.5 top-0.5 w-3.5 h-3.5 bg-primary-container rounded-full"></div>
</div>
</div>
<!-- row: crt_scanline_effect -->
<div class="bg-[#202020] h-14 rounded-[4px] px-3 flex items-center justify-between">
<div class="flex flex-col">
<span class="text-[#a0a0a0] font-label-caps text-[11px]">crt_scanline_effect</span>
<span class="text-suit-black font-label-caps text-[14px]">false</span>
</div>
<!-- Toggle OFF -->
<div class="w-10 h-5 bg-[#202020] border border-outline rounded-full relative">
<div class="absolute left-0.5 top-0.5 w-3.5 h-3.5 bg-outline rounded-full"></div>
</div>
</div>
</main>
<!-- BottomNavBar (Footer Logic) -->
<footer class="h-6 bg-[#202020] border-t border-[#353535] flex items-center justify-between px-4 w-full z-50">
<div class="flex items-center gap-1 font-label-caps text-[11px]">
<span class="text-primary"></span>
<span class="text-suit-black">NORMAL</span>
<span class="text-outline"></span>
<span class="text-on-surface">settings</span>
</div>
<div class="flex items-center gap-2 font-label-caps text-[11px]">
<div class="flex items-center">
<span class="text-[#a0a0a0]">[1-4]</span>
<span class="text-outline ml-1">tab</span>
</div>
<span class="text-outline">·</span>
<div class="flex items-center">
<span class="text-[#a0a0a0]">[ESC]</span>
<span class="text-outline ml-1">back</span>
</div>
</div>
</footer>
</body></html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

-213
View File
@@ -1,213 +0,0 @@
<!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 - Terminal Edition</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&amp;family=Inter:wght@400;500;700&amp;family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"primary-fixed": "#c4e7ff",
"on-tertiary-fixed": "#340043",
"surface-dim": "#101417",
"on-error": "#690005",
"inverse-primary": "#00668a",
"highlight-celebration": "#e1a3ee",
"surface-variant": "#313538",
"on-background": "#e0e3e6",
"secondary-fixed": "#d5ec8c",
"surface-bright": "#363a3d",
"on-primary-container": "#004f6c",
"on-surface": "#d0d0d0",
"surface-container-lowest": "#0b0f11",
"on-tertiary-container": "#683476",
"surface-container-high": "#272a2d",
"error": "#fb9fb1",
"highlight-valid": "#acc267",
"primary-container": "#6fc2ef",
"on-secondary": "#293500",
"tertiary-fixed-dim": "#f0b0fc",
"on-primary-fixed": "#001e2c",
"outline-variant": "#3f484e",
"secondary-container": "#435401",
"background": "#101417",
"on-primary-fixed-variant": "#004c69",
"suit-black": "#d0d0d0",
"surface-container": "#1c2023",
"on-primary": "#003549",
"primary": "#a1dcff",
"error-container": "#93000a",
"on-tertiary-fixed-variant": "#653173",
"info": "#12cfc0",
"warning": "#ddb26f",
"tertiary-container": "#e1a3ee",
"tertiary-fixed": "#fbd7ff",
"on-tertiary": "#4c195b",
"surface": "#151515",
"secondary-fixed-dim": "#bad073",
"on-secondary-fixed-variant": "#3c4d00",
"surface-container-highest": "#313538",
"inverse-on-surface": "#2d3134",
"suit-red": "#fb9fb1",
"surface-container-low": "#181c1f",
"on-secondary-container": "#b2c86d",
"on-error-container": "#ffdad6",
"tertiary": "#f7c3ff",
"suit-red-cb": "#6fc2ef",
"inverse-surface": "#e0e3e6",
"surface-tint": "#7ed0fe",
"primary-fixed-dim": "#7ed0fe",
"outline": "#505050",
"on-surface-variant": "#bfc8cf",
"secondary": "#bad073",
"on-secondary-fixed": "#161e00",
"boot-cyan": "#a1dcff",
"boot-lime": "#acc267"
},
"borderRadius": {
"DEFAULT": "0.125rem",
"lg": "0.25rem",
"xl": "0.5rem",
"full": "0.75rem"
},
"spacing": {
"stack-overlap": "2rem",
"action-bar-height": "64px",
"margin-edge": "1rem",
"gutter-card": "0.375rem"
},
"fontFamily": {
"hud-timer": ["JetBrains Mono"],
"body-md": ["Inter"],
"card-rank": ["JetBrains Mono"],
"label-caps": ["JetBrains Mono"],
"headline": ["JetBrains Mono"],
"hud-score": ["JetBrains Mono"]
},
"fontSize": {
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}]
}
},
},
}
</script>
<style>
body {
background-color: #151515;
color: #d0d0d0;
margin: 0;
padding: 0;
height: 100vh;
overflow: hidden;
-webkit-font-smoothing: antialiased;
}
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
.cyan-halo {
box-shadow: 0 0 40px 4px rgba(161, 220, 255, 0.15);
}
.scanlines {
background: linear-gradient(to bottom, rgba(21, 21, 21, 0) 50%, rgba(0, 0, 0, 0.2) 50%);
background-size: 100% 4px;
pointer-events: none;
}
</style>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="flex flex-col items-center justify-between font-body-md w-[390px] h-[844px] mx-auto relative overflow-hidden">
<div class="absolute inset-0 scanlines opacity-30 z-10"></div>
<!-- Top Safe Area (Blank) -->
<div class="h-10 w-full"></div>
<!-- Main Content Stack -->
<main class="flex-1 w-full flex flex-col items-center z-20">
<!-- Header Section (~30% Top) -->
<div class="mt-[10%] flex flex-col items-center">
<div class="w-24 h-24 flex items-center justify-center relative mb-4">
<div class="text-[96px] text-boot-cyan font-headline cyan-halo leading-none"></div>
</div>
<div class="flex flex-col items-center">
<h1 class="font-headline text-[32px] font-bold text-on-surface tracking-tight mb-1">RUSTY SOLITAIRE</h1>
<div class="w-48 h-[1px] bg-outline-variant"></div>
<span class="font-label-caps text-[12px] text-outline mt-2 tracking-[0.1em] uppercase">TERMINAL EDITION</span>
</div>
</div>
<!-- Terminal Boot Log (~60% Top Position) -->
<div class="mt-48 w-[70%] flex flex-col items-start font-hud-timer text-[11px] space-y-1">
<div class="flex items-center gap-2">
<span class="text-boot-lime"></span>
<span class="text-outline">assets loaded</span>
</div>
<div class="flex items-center gap-2">
<span class="text-boot-lime"></span>
<span class="text-outline">theme: terminal</span>
</div>
<div class="flex items-center gap-2">
<span class="text-boot-lime"></span>
<span class="text-outline">progress restored</span>
</div>
<div class="flex items-center gap-1 mt-1">
<span class="text-on-surface-variant">▌ ready_</span>
<span class="w-[6px] h-3 bg-boot-cyan animate-pulse"></span>
</div>
</div>
<!-- Progress Bar (~75% Top Position) -->
<div class="mt-20 w-full px-8">
<div class="w-full h-[1px] bg-surface-container-highest relative">
<div class="absolute top-0 left-0 h-full bg-boot-cyan w-full"></div>
</div>
<div class="w-full text-right mt-2">
<span class="font-label-caps text-[10px] text-outline tracking-wider uppercase">DONE · 247 ASSETS</span>
</div>
</div>
</main>
<!-- Bottom Strip & Footer -->
<footer class="w-full flex flex-col items-center z-20 pb-10">
<div class="flex flex-col items-center mb-8">
<span class="font-label-caps text-[9px] text-outline mb-3 tracking-widest uppercase">BASE16-EIGHTIES</span>
<div class="flex gap-1.5">
<div class="w-3 h-3 bg-[#fb9fb1]"></div>
<div class="w-3 h-3 bg-[#ddb26f]"></div>
<div class="w-3 h-3 bg-[#acc267]"></div>
<div class="w-3 h-3 bg-[#12cfc0]"></div>
<div class="w-3 h-3 bg-[#6fc2ef]"></div>
<div class="w-3 h-3 bg-[#e1a3ee]"></div>
<div class="w-3 h-3 bg-[#d0d0d0]"></div>
<div class="w-3 h-3 bg-[#505050]"></div>
</div>
</div>
<div class="font-hud-timer text-[11px] text-outline">
v0.20.0
</div>
</footer>
<!-- Top App Bar Content (Hidden as per requirement, but following Shared Components Logic) -->
<div class="hidden">
<header class="fixed top-0 w-full z-50 flex justify-between items-center px-margin-edge h-action-bar-height border-b border-outline-variant bg-background">
<div class="font-headline text-headline text-primary uppercase tracking-tighter">ROOT@SOLITAIRE:~</div>
<div class="flex gap-4 text-primary">
<span class="material-symbols-outlined">memory</span>
<span class="material-symbols-outlined">settings_ethernet</span>
<span class="material-symbols-outlined">wifi_tethering</span>
</div>
</header>
</div>
<!-- Placeholder for data-alt requirements even if visual images are minimal -->
<div class="hidden">
<img data-alt="A cinematic, low-angle digital render of a retro-terminal interface appearing on a high-end mobile display in a dark room. The screen glows with a soft cyan light, illuminating a minimalist layout with sharp, 1px white lines and a large block cursor symbol. The atmosphere is quiet, technical, and high-performance, reflecting a dark-mode synthwave aesthetic with muted grays and vibrant cyan accents. The focus is on the crisp, high-density typography and the mechanical precision of the digital environment." src="https://lh3.googleusercontent.com/aida-public/AB6AXuBeZymuyGd_-VJr-zgC8p08qBLD4pk0WyRtxuIhVT5kY6cc3y_qSkZ-P_EYYwKIliGysN5rDgqbCsXLxksfslVnB4nj4BYktu4d5EAKi1zEQ8t8MId17UzIgKujbGqebDo0FWO51Snqxt9AvrjX_afEsvACaaeAyIfTKgoAB8MBOUnanIre26Y1tNTftn1y9jxKfrXgi9eCYiJn6zoiaRmNmdLwo_s7RenmSlloPdIURVb3KKHKaBZHldPaStbWcyMYNR877R6O_A"/>
</div>
</body></html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

-279
View File
@@ -1,279 +0,0 @@
<!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&amp;family=Inter:wght@400;500;600&amp;family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
/* CRT Scanline effect overlay */
.crt-overlay::before {
content: " ";
display: block;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.1) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.02), rgba(0, 255, 0, 0.01), rgba(0, 0, 255, 0.02));
z-index: 100;
background-size: 100% 2px, 3px 100%;
pointer-events: none;
}
</style>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"tertiary": "#f7c3ff",
"surface-container": "#202020",
"primary-container": "#6fc2ef",
"surface": "#151515",
"tertiary-fixed": "#fbd7ff",
"info": "#12cfc0",
"inverse-on-surface": "#2d3134",
"suit-red": "#fb9fb1",
"secondary-fixed-dim": "#bad073",
"on-background": "#e0e3e6",
"tertiary-container": "#e1a3ee",
"suit-black": "#d0d0d0",
"on-primary": "#003549",
"secondary-fixed": "#d5ec8c",
"tertiary-fixed-dim": "#f0b0fc",
"suit-red-cb": "#6fc2ef",
"primary": "#a1dcff",
"on-primary-container": "#004f6c",
"primary-fixed": "#c4e7ff",
"on-surface-variant": "#bfc8cf",
"surface-tint": "#7ed0fe",
"surface-dim": "#101417",
"on-tertiary-container": "#683476",
"inverse-primary": "#00668a",
"background": "#101417",
"on-surface": "#d0d0d0",
"primary-fixed-dim": "#7ed0fe",
"on-error": "#690005",
"surface-container-highest": "#313538",
"outline-variant": "#3f484e",
"inverse-surface": "#e0e3e6",
"warning": "#ddb26f",
"surface-variant": "#313538",
"on-tertiary-fixed": "#340043",
"outline": "#505050",
"surface-container-lowest": "#0b0f11",
"on-secondary-container": "#b2c86d",
"surface-container-high": "#272a2d",
"secondary": "#bad073",
"error": "#fb9fb1",
"highlight-valid": "#acc267",
"surface-container-low": "#181c1f",
"on-secondary": "#293500",
"surface-bright": "#363a3d",
"on-secondary-fixed": "#161e00",
"on-error-container": "#ffdad6",
"highlight-celebration": "#e1a3ee",
"on-primary-fixed-variant": "#004c69",
"on-tertiary": "#4c195b",
"on-primary-fixed": "#001e2c",
"secondary-container": "#435401",
"error-container": "#93000a",
"on-tertiary-fixed-variant": "#653173",
"on-secondary-fixed-variant": "#3c4d00"
},
"borderRadius": {
"DEFAULT": "0.125rem",
"lg": "0.25rem",
"xl": "0.5rem",
"full": "0.75rem"
},
"spacing": {
"touch-target-min": "48dp",
"stack-overlap": "2rem",
"margin-edge": "1rem",
"action-bar-height": "64px",
"gutter-card": "0.375rem"
},
"fontFamily": {
"hud-timer": ["JetBrains Mono"],
"headline": ["JetBrains Mono"],
"card-rank": ["JetBrains Mono"],
"body-md": ["Inter"],
"hud-score": ["JetBrains Mono"],
"label-caps": ["JetBrains Mono"]
},
"fontSize": {
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"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"}],
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}]
}
},
},
}
</script>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-surface text-on-surface font-body-md min-h-screen selection:bg-primary-container selection:text-on-primary-container">
<!-- Top Navigation Shell -->
<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>
<h1 class="font-headline text-headline text-primary uppercase tracking-tighter">STATISTICS.LOG</h1>
</div>
<div class="flex items-center">
<button class="material-symbols-outlined text-on-surface-variant hover:bg-surface-container-highest transition-colors duration-120 p-2 rounded" data-icon="settings_input_component">settings_input_component</button>
</div>
</header>
<main class="pt-action-bar-height pb-[88px] crt-overlay">
<!-- Status Bar (Emulated Retro Style) -->
<div class="h-[32px] bg-surface-container flex items-center justify-between px-margin-edge text-[11px] font-hud-timer">
<span class="text-on-surface">▌stats.log</span>
<span class="text-on-surface-variant">247 GAMES TRACKED</span>
</div>
<!-- Sub-tab Strip -->
<nav class="h-[40px] bg-[#1a1a1a] border-b border-outline flex items-center px-margin-edge overflow-x-auto whitespace-nowrap scrollbar-hide">
<div class="flex items-center h-full gap-4">
<button class="h-full px-1 flex flex-col justify-center font-label-caps text-[11px] text-primary-container border-b-2 border-primary-container">
[ OVERVIEW ]
</button>
<button class="h-full px-1 flex flex-col justify-center font-label-caps text-[11px] text-on-surface-variant hover:text-primary transition-colors">
DRAW-1
</button>
<button class="h-full px-1 flex flex-col justify-center font-label-caps text-[11px] text-on-surface-variant hover:text-primary transition-colors">
DRAW-3
</button>
<button class="h-full px-1 flex flex-col justify-center font-label-caps text-[11px] text-on-surface-variant hover:text-primary transition-colors">
DAILY
</button>
</div>
</nav>
<!-- Main Content Grid -->
<section class="p-4 flex flex-col gap-4">
<!-- Hero Stat Card -->
<div class="h-24 bg-surface-container border border-outline rounded-lg p-4 flex justify-between items-center">
<div>
<span class="font-headline text-[48px] text-on-surface">61%</span>
</div>
<div class="text-right">
<p class="font-label-caps text-[11px] text-on-surface-variant">WIN RATE</p>
<p class="font-hud-timer text-[13px] text-highlight-valid">▲ +3% vs last 30</p>
</div>
</div>
<!-- 2x2 Grid -->
<div class="grid grid-cols-2 gap-2">
<div class="h-[88px] bg-surface-container border border-outline rounded-lg p-3 flex flex-col justify-between">
<p class="font-label-caps text-[11px] text-on-surface-variant">GAMES</p>
<p class="font-hud-score text-hud-score">247</p>
</div>
<div class="h-[88px] bg-surface-container border border-outline rounded-lg p-3 flex flex-col justify-between">
<p class="font-label-caps text-[11px] text-on-surface-variant">WINS</p>
<p class="font-hud-score text-hud-score">151</p>
</div>
<div class="h-[88px] bg-surface-container border border-outline rounded-lg p-3 flex flex-col justify-between">
<p class="font-label-caps text-[11px] text-on-surface-variant">BEST TIME</p>
<p class="font-hud-score text-hud-score text-primary">01:54</p>
</div>
<div class="h-[88px] bg-surface-container border border-outline rounded-lg p-3 flex flex-col justify-between">
<p class="font-label-caps text-[11px] text-on-surface-variant">STREAK</p>
<p class="font-hud-score text-hud-score">12</p>
</div>
</div>
<!-- Sparkline Card -->
<div class="bg-surface-container border border-outline rounded-lg p-4 h-[200px] flex flex-col">
<p class="font-label-caps text-[11px] text-on-surface-variant mb-4">WIN RATE · LAST 30 DAYS</p>
<div class="relative flex-1 flex items-end justify-between border-b border-outline pb-1">
<!-- Y-Axis Labels -->
<span class="absolute top-0 left-0 text-[9px] font-hud-timer text-outline">100%</span>
<span class="absolute bottom-1 left-0 text-[9px] font-hud-timer text-outline">0%</span>
<!-- Pixel Bar Chart -->
<div class="flex items-end justify-between w-full h-[80%] gap-[2px]">
<!-- Generated bars with upward trend -->
<div class="bg-primary-container w-full" style="height: 35%"></div>
<div class="bg-primary-container w-full" style="height: 30%"></div>
<div class="bg-primary-container w-full" style="height: 40%"></div>
<div class="bg-primary-container w-full" style="height: 38%"></div>
<div class="bg-primary-container w-full" style="height: 45%"></div>
<div class="bg-primary-container w-full" style="height: 42%"></div>
<div class="bg-primary-container w-full" style="height: 50%"></div>
<div class="bg-primary-container w-full" style="height: 48%"></div>
<div class="bg-primary-container w-full" style="height: 55%"></div>
<div class="bg-primary-container w-full" style="height: 52%"></div>
<div class="bg-primary-container w-full" style="height: 58%"></div>
<div class="bg-primary-container w-full" style="height: 60%"></div>
<div class="bg-primary-container w-full" style="height: 55%"></div>
<div class="bg-primary-container w-full" style="height: 62%"></div>
<div class="bg-primary-container w-full" style="height: 65%"></div>
<div class="bg-primary-container w-full" style="height: 63%"></div>
<div class="bg-primary-container w-full" style="height: 68%"></div>
<div class="bg-primary-container w-full" style="height: 70%"></div>
<div class="bg-primary-container w-full" style="height: 65%"></div>
<div class="bg-primary-container w-full" style="height: 72%"></div>
<div class="bg-primary-container w-full" style="height: 75%"></div>
<div class="bg-primary-container w-full" style="height: 78%"></div>
<div class="bg-primary-container w-full" style="height: 74%"></div>
<div class="bg-primary-container w-full" style="height: 80%"></div>
<div class="bg-primary-container w-full" style="height: 82%"></div>
<div class="bg-primary-container w-full" style="height: 85%"></div>
<div class="bg-primary-container w-full" style="height: 88%"></div>
<div class="bg-primary-container w-full" style="height: 86%"></div>
<div class="bg-primary-container w-full" style="height: 90%"></div>
<div class="bg-primary-container w-full" style="height: 95%"></div>
</div>
</div>
<div class="flex justify-between mt-1">
<span class="text-[9px] font-hud-timer text-outline uppercase">30d ago</span>
<span class="text-[9px] font-hud-timer text-outline uppercase">today</span>
</div>
</div>
<!-- Draw-Mode Split Card -->
<div class="h-20 bg-surface-container border border-outline rounded-lg p-4 flex flex-col justify-between">
<p class="font-label-caps text-[11px] text-on-surface-variant">DRAW MODE SPLIT</p>
<div class="h-3 w-full flex rounded-sm overflow-hidden bg-outline">
<div class="h-full bg-highlight-valid" style="width: 60%"></div>
<div class="h-full bg-primary-container" style="width: 40%"></div>
</div>
<div class="flex justify-between">
<span class="font-hud-timer text-[11px] text-highlight-valid">DRAW-1 · 60%</span>
<span class="font-hud-timer text-[11px] text-primary-container">DRAW-3 · 40%</span>
</div>
</div>
</section>
<!-- Retro Footer Strip -->
<footer class="h-[24px] px-margin-edge flex items-center justify-between font-hud-timer text-[10px] text-outline mt-2">
<div>▌ NORMAL │ stats</div>
<div>[1-4] view</div>
</footer>
</main>
<!-- Bottom Navigation Shell -->
<nav class="fixed bottom-0 w-full z-50 h-[64px] bg-surface-container border-t border-outline flex justify-around items-center">
<div class="flex flex-col items-center justify-center text-on-surface-variant pt-1 hover:text-primary transition-colors duration-120">
<span class="material-symbols-outlined" data-icon="playing_cards">playing_cards</span>
<span class="font-label-caps text-label-caps">DECK</span>
</div>
<!-- ACTIVE TAB: STATS -->
<div class="flex flex-col items-center justify-center text-primary border-t-2 border-primary pt-1 hover:text-primary transition-colors duration-120">
<span class="material-symbols-outlined" data-icon="query_stats" style="font-variation-settings: 'FILL' 1;">query_stats</span>
<span class="font-label-caps text-label-caps">STATS</span>
</div>
<div class="flex flex-col items-center justify-center text-on-surface-variant pt-1 hover:text-primary transition-colors duration-120">
<span class="material-symbols-outlined" data-icon="history_edu">history_edu</span>
<span class="font-label-caps text-label-caps">LOGS</span>
</div>
<div class="flex flex-col items-center justify-center text-on-surface-variant pt-1 hover:text-primary transition-colors duration-120">
<span class="material-symbols-outlined" data-icon="terminal">terminal</span>
<span class="font-label-caps text-label-caps">SYS</span>
</div>
</nav>
</body></html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

-262
View File
@@ -1,262 +0,0 @@
<!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 - Sync Progress</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&amp;family=Inter:wght@400;500&amp;family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"surface-container-low": "#181c1f",
"highlight-valid": "#acc267",
"on-secondary-container": "#b2c86d",
"on-tertiary": "#4c195b",
"suit-black": "#d0d0d0",
"surface-container": "#1c2023",
"warning": "#ddb26f",
"outline": "#505050",
"error-container": "#93000a",
"on-secondary": "#293500",
"surface-dim": "#101417",
"on-primary-fixed": "#001e2c",
"secondary": "#bad073",
"on-surface": "#e0e3e6",
"primary": "#a1dcff",
"tertiary-fixed": "#fbd7ff",
"on-tertiary-container": "#683476",
"inverse-primary": "#00668a",
"on-surface-variant": "#bfc8cf",
"tertiary-fixed-dim": "#f0b0fc",
"surface-tint": "#7ed0fe",
"primary-container": "#6fc2ef",
"on-primary-container": "#004f6c",
"secondary-container": "#435401",
"suit-red-cb": "#6fc2ef",
"surface-container-high": "#272a2d",
"on-background": "#e0e3e6",
"on-primary": "#003549",
"highlight-celebration": "#e1a3ee",
"surface": "#151515",
"secondary-fixed": "#d5ec8c",
"on-primary-fixed-variant": "#004c69",
"surface-container-lowest": "#0b0f11",
"suit-red": "#fb9fb1",
"inverse-on-surface": "#2d3134",
"surface-variant": "#313538",
"on-tertiary-fixed-variant": "#653173",
"tertiary-container": "#e1a3ee",
"outline-variant": "#3f484e",
"inverse-surface": "#e0e3e6",
"primary-fixed-dim": "#7ed0fe",
"on-secondary-fixed": "#161e00",
"info": "#12cfc0",
"primary-fixed": "#c4e7ff",
"surface-container-highest": "#313538",
"on-tertiary-fixed": "#340043",
"on-error": "#690005",
"background": "#101417",
"secondary-fixed-dim": "#bad073",
"error": "#fb9fb1",
"tertiary": "#f7c3ff",
"on-error-container": "#ffdad6",
"on-secondary-fixed-variant": "#3c4d00",
"surface-bright": "#363a3d"
},
"borderRadius": {
"DEFAULT": "0.125rem",
"lg": "0.25rem",
"xl": "0.5rem",
"full": "0.75rem"
},
"spacing": {
"touch-target-min": "48dp",
"gutter-card": "0.375rem",
"action-bar-height": "64px",
"stack-overlap": "2rem",
"margin-edge": "1rem"
},
"fontFamily": {
"hud-timer": ["JetBrains Mono"],
"headline": ["JetBrains Mono"],
"card-rank": ["JetBrains Mono"],
"label-caps": ["JetBrains Mono"],
"hud-score": ["JetBrains Mono"],
"body-md": ["Inter"]
},
"fontSize": {
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
"card-rank": ["18px", {"lineHeight": "18px", "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"}]
}
},
},
}
</script>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
display: inline-block;
vertical-align: middle;
}
.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="bg-surface text-on-surface font-body-md min-h-screen flex flex-col overflow-x-hidden selection:bg-primary-container selection:text-on-primary-container">
<!-- 1. Status Bar (Top) -->
<nav class="h-8 bg-[#202020] flex items-center justify-between px-4 border-b border-outline-variant z-50">
<div class="flex items-center gap-2">
<span class="font-headline text-[13px] tracking-tight text-on-surface-variant">▌sync.config</span>
</div>
<div class="flex items-center gap-3">
<div class="flex items-center gap-1.5">
<span class="w-2 h-2 rounded-full bg-warning animate-pulse"></span>
<span class="font-label-caps text-[11px] text-warning">PENDING</span>
</div>
<span class="font-label-caps text-[11px] text-outline">v0.20.0</span>
</div>
</nav>
<!-- Shared TopAppBar Component -->
<header class="bg-surface flex justify-between items-center w-full px-margin-edge h-action-bar-height max-w-full border-b border-outline-variant">
<div class="font-headline text-headline text-primary tracking-tighter uppercase">▌RS_TERMINAL_OS</div>
<div class="hidden md:flex gap-6 items-center">
<a class="font-label-caps text-label-caps uppercase tracking-widest text-on-surface-variant hover:text-primary transition-colors duration-120" href="#">PLAY</a>
<a class="font-label-caps text-label-caps uppercase tracking-widest text-on-surface-variant hover:text-primary transition-colors duration-120" href="#">DAILY</a>
<a class="font-label-caps text-label-caps uppercase tracking-widest text-primary border-b-2 border-primary pb-1" href="#">STATS</a>
</div>
<div class="flex gap-4">
<span class="material-symbols-outlined text-primary cursor-pointer">account_circle</span>
<span class="material-symbols-outlined text-primary cursor-pointer">sync</span>
<span class="material-symbols-outlined text-primary cursor-pointer">settings</span>
</div>
</header>
<main class="flex-grow p-4 md:p-8 max-w-3xl mx-auto w-full space-y-6">
<!-- 2. Header Area -->
<div class="h-20 flex flex-col justify-center border-l-2 border-outline-variant pl-4">
<h1 class="font-headline text-[24px] font-bold text-suit-black leading-tight">SYNC PROGRESS</h1>
<p class="font-body-md text-[12px] text-[#a0a0a0]">Connect to a server to sync games across devices.</p>
</div>
<!-- 3. Status Card -->
<section class="bg-[#202020] rounded-[4px] p-4 border-l-[4px] border-warning flex flex-col gap-1">
<h2 class="font-label-caps text-[10px] text-[#a0a0a0] uppercase tracking-widest">STATUS</h2>
<div class="flex flex-col">
<span class="font-headline text-[16px] font-bold text-[#a0a0a0]">○ NOT SIGNED IN</span>
<span class="text-[11px] text-outline font-medium">Local progress only · Last attempt: never</span>
</div>
</section>
<!-- 4. Login Form Card -->
<section class="bg-[#202020] border border-outline-variant rounded-[4px] p-4 flex flex-col gap-5">
<div class="flex items-center gap-2 border-b border-outline-variant pb-2 mb-1">
<span class="font-headline text-[12px] text-[#a0a0a0] flex items-center">
▌ AUTH.toml <span class="ml-1 w-1.5 h-3 bg-primary-container animate-pulse"></span>
</span>
</div>
<!-- Server URL -->
<div class="flex flex-col gap-1.5">
<label class="font-label-caps text-[10px] text-[#a0a0a0] uppercase tracking-widest">server_url</label>
<div class="h-12 bg-[#1a1a1a] border border-outline-variant rounded-[2px] flex items-center px-4">
<span class="font-headline text-[13px] text-suit-black">https://sync.rusty-solitaire.app</span>
</div>
</div>
<!-- Email -->
<div class="flex flex-col gap-1.5">
<label class="font-label-caps text-[10px] text-[#a0a0a0] uppercase tracking-widest">email</label>
<div class="h-12 bg-[#1a1a1a] border border-outline-variant rounded-[2px] flex items-center px-4">
<span class="font-headline text-[13px] text-outline">/ user@example.com</span>
</div>
</div>
<!-- Passcode -->
<div class="flex flex-col gap-1.5">
<label class="font-label-caps text-[10px] text-[#a0a0a0] uppercase tracking-widest">passcode</label>
<div class="h-12 bg-[#1a1a1a] border border-outline-variant rounded-[2px] flex items-center justify-between px-4">
<span class="font-headline text-[13px] text-outline">•••••••• (12 chars)</span>
<span class="material-symbols-outlined text-outline cursor-pointer text-[18px]">visibility</span>
</div>
</div>
</section>
<!-- 5. Form Actions -->
<div class="grid grid-cols-2 gap-4">
<button class="h-12 bg-primary-container text-surface flex items-center justify-center rounded-[2px] font-headline text-[14px] font-bold active:scale-95 transition-transform">
▶ SIGN IN
</button>
<button class="h-12 border border-outline text-suit-black flex items-center justify-center rounded-[2px] font-headline text-[13px] font-medium hover:border-primary-container hover:text-primary-container transition-all">
+ CREATE ACCOUNT
</button>
</div>
<!-- 6. Recent History Panel -->
<section class="bg-[#202020] rounded-[4px] p-4 flex flex-col gap-3">
<h2 class="font-label-caps text-[12px] text-[#a0a0a0] uppercase tracking-widest">RECENT</h2>
<div class="space-y-2 border-l border-outline-variant pl-4">
<div class="font-headline text-[11px] text-[#a0a0a0] flex items-center gap-2">
<span class="text-outline">2026-05-07 17:38</span>
<span class="text-outline">·</span>
<span>○ no auth</span>
<span class="text-outline">·</span>
<span>skip</span>
</div>
<div class="font-headline text-[11px] text-[#a0a0a0] flex items-center gap-2">
<span class="text-outline">2026-05-07 14:12</span>
<span class="text-outline">·</span>
<span>○ no auth</span>
<span class="text-outline">·</span>
<span>skip</span>
</div>
<div class="font-headline text-[11px] text-[#a0a0a0] flex items-center gap-2">
<span class="text-outline">2026-05-06 09:01</span>
<span class="text-outline">·</span>
<span class="text-highlight-valid">✓ synced 12 games</span>
</div>
</div>
</section>
</main>
<!-- 7. Footer -->
<footer class="h-10 bg-surface border-t border-outline-variant flex items-center justify-between px-4 fixed bottom-0 w-full z-50">
<div class="flex items-center gap-2 font-headline text-[11px]">
<span class="text-primary-container">▌ NORMAL</span>
<span class="text-outline"></span>
<span class="text-on-surface-variant">sync</span>
</div>
<div class="flex items-center gap-4 font-headline text-[11px] text-outline uppercase tracking-wider">
<span>[ENTER] <span class="text-on-surface-variant">sign in</span></span>
<span>[ESC] <span class="text-on-surface-variant">cancel</span></span>
</div>
</footer>
<!-- Shared BottomNavBar Component (Mobile Only) -->
<nav class="md:hidden fixed bottom-10 w-full z-50 flex justify-around items-center h-action-bar-height px-margin-edge bg-surface-container border-t border-outline-variant">
<div class="flex flex-col items-center justify-center text-on-surface-variant p-2 hover:bg-surface-bright transition-all duration-120">
<span class="material-symbols-outlined" data-icon="videogame_asset">videogame_asset</span>
<span class="font-label-caps text-[10px] uppercase tracking-widest mt-1">F1_NEW_GAME</span>
</div>
<div class="flex flex-col items-center justify-center text-on-surface-variant p-2 hover:bg-surface-bright transition-all duration-120">
<span class="material-symbols-outlined" data-icon="event_upcoming">event_upcoming</span>
<span class="font-label-caps text-[10px] uppercase tracking-widest mt-1">F2_CHALLENGE</span>
</div>
<div class="flex flex-col items-center justify-center text-primary dark:text-primary-fixed-dim bg-surface-container-highest rounded-none p-2 transition-all duration-120 scale-95">
<span class="material-symbols-outlined" data-icon="query_stats">query_stats</span>
<span class="font-label-caps text-[10px] uppercase tracking-widest mt-1">F5_STATS</span>
</div>
<div class="flex flex-col items-center justify-center text-on-surface-variant p-2 hover:bg-surface-bright transition-all duration-120">
<span class="material-symbols-outlined" data-icon="power_settings_new">power_settings_new</span>
<span class="font-label-caps text-[10px] uppercase tracking-widest mt-1">ESC_EXIT</span>
</div>
</nav>
<!-- Decorative Screen Texture -->
<div class="fixed inset-0 pointer-events-none opacity-[0.03] scanline z-[100]"></div>
</body></html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

-250
View File
@@ -1,250 +0,0 @@
<!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;800&amp;family=Inter:wght@400;500;600&amp;family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<style>.material-symbols-outlined {
font-variation-settings: "FILL" 0, "wght" 400, "GRAD" 0, "opsz" 24
}
.scanline-pattern {
background: repeating-linear-gradient(0deg, #1a1a1a, #1a1a1a 2px, #151515 2px, #151515 4px)
}
.checker-pattern {
background-color: #001e2c;
background-image: radial-gradient(#a1dcff 10%, transparent 10%), radial-gradient(#a1dcff 10%, transparent 10%);
background-size: 8px 8px;
background-position: 0 0, 4px 4px
}
.stripe-pattern {
background: repeating-linear-gradient(0deg, #fb9fb1, #fb9fb1 4px, #202020 4px, #202020 8px)
}
.polka-pattern {
background-color: #001e2c;
background-image: radial-gradient(#e0e3e6 15%, transparent 15%);
background-size: 12px 12px
}
.vintage-pattern {
background-color: #d5ec8c;
background-image: url(https://lh3.googleusercontent.com/aida-public/AB6AXuD8S9vQTpEh-DjYtjB5CUHqi2CO326ZEjVVLJoOqG1AA6b92NZ6ctGoD4yZHKV7oHJnSFdvp3z3Wei9zfTI2EGAdrQfHxFYJ-h1DaeiZQY3vTa5khIQ83Sf-bjz2xiudHsjs3RyhSKC5bHv2c8_9t6YjepJdQnJa4GelCetFEs_agpN6u2IfMS1M9RrGxGKLl4K18fj0Pg3BW8IptX_ladhVFR5Hk8F0Reu5WHY8eQt1Nr-p9NNXl-w3C9Jz0uGSxi_Wb7R771lgQ);
opacity: 0.8
}</style>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"surface-variant": "#313538",
"on-error-container": "#ffdad6",
"warning": "#ddb26f",
"on-surface": "#e0e3e6",
"inverse-on-surface": "#2d3134",
"on-secondary-fixed": "#161e00",
"on-error": "#690005",
"on-primary-fixed": "#001e2c",
"primary-container": "#6fc2ef",
"secondary-fixed-dim": "#bad073",
"tertiary": "#f7c3ff",
"surface-dim": "#101417",
"surface-container-low": "#181c1f",
"on-surface-variant": "#bfc8cf",
"surface-container": "#1c2023",
"secondary-container": "#435401",
"error": "#fb9fb1",
"on-tertiary": "#4c195b",
"surface-tint": "#7ed0fe",
"error-container": "#93000a",
"tertiary-fixed": "#fbd7ff",
"info": "#12cfc0",
"tertiary-fixed-dim": "#f0b0fc",
"highlight-valid": "#acc267",
"on-primary": "#003549",
"inverse-surface": "#e0e3e6",
"primary": "#a1dcff",
"surface-container-high": "#272a2d",
"background": "#101417",
"surface-container-lowest": "#0b0f11",
"suit-red": "#fb9fb1",
"suit-red-cb": "#6fc2ef",
"surface": "#151515",
"primary-fixed": "#c4e7ff",
"outline": "#505050",
"on-secondary": "#293500",
"surface-container-highest": "#313538",
"secondary-fixed": "#d5ec8c",
"on-primary-container": "#004f6c",
"suit-black": "#d0d0d0",
"on-primary-fixed-variant": "#004c69",
"on-tertiary-container": "#683476",
"secondary": "#bad073",
"highlight-celebration": "#e1a3ee",
"on-tertiary-fixed": "#340043",
"on-tertiary-fixed-variant": "#653173",
"on-secondary-container": "#b2c86d",
"surface-bright": "#363a3d",
"primary-fixed-dim": "#7ed0fe",
"on-secondary-fixed-variant": "#3c4d00",
"inverse-primary": "#00668a",
"outline-variant": "#3f484e",
"on-background": "#e0e3e6",
"tertiary-container": "#e1a3ee"
},
"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",
"action-bar-height": "64px",
"margin-edge": "1rem"
},
"fontFamily": {
"hud-score": ["JetBrains Mono"],
"label-caps": ["JetBrains Mono"],
"hud-timer": ["JetBrains Mono"],
"card-rank": ["JetBrains Mono"],
"headline": ["JetBrains Mono"],
"body-md": ["Inter"]
},
"fontSize": {
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}],
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}]
}
},
},
}
</script>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-background text-on-surface selection:bg-primary-container selection:text-on-primary-container min-h-screen flex flex-col font-body-md overflow-x-hidden">
<!-- TopAppBar Semantic Shell -->
<header class="fixed top-0 w-full z-50 flex justify-between items-center h-[32px] px-margin-edge bg-surface-container border-b border-outline-variant">
<div class="flex items-center gap-2">
<span class="material-symbols-outlined text-[16px] text-primary" data-icon="terminal">terminal</span>
<span class="font-headline text-[14px] font-bold text-on-surface">▌theme.picker</span>
</div>
<div class="flex items-center gap-2 cursor-pointer hover:opacity-80 transition-opacity">
<span class="font-label-caps text-[12px] text-on-surface-variant uppercase">× CLOSE</span>
</div>
</header>
<main class="mt-[32px] mb-[64px] flex-grow flex flex-col px-margin-edge py-6">
<!-- Header Section -->
<div class="flex justify-between items-start mb-8">
<div class="flex-1">
<h1 class="font-headline text-[24px] font-bold text-on-surface mb-1">CARD THEMES</h1>
<p class="font-body-md text-[13px] text-on-surface-variant max-w-[280px]">Choose a card-face theme. Imported themes appear at the bottom.</p>
</div>
<div class="bg-surface-container-high px-2 py-1 border border-outline-variant">
<span class="font-label-caps text-[11px] text-primary">5 INSTALLED</span>
</div>
</div>
<!-- Theme Grid -->
<div class="grid grid-cols-2 gap-4">
<!-- Active Theme: Terminal -->
<div class="relative flex flex-col bg-surface border-2 border-primary-container">
<div class="aspect-[2.5/3.5] w-full p-2 overflow-hidden flex items-center justify-center bg-background">
<!-- Card Preview -->
<div class="w-24 h-36 bg-surface border border-primary-container scanline-pattern relative">
<div class="absolute top-1 left-1 w-3 h-4 bg-primary-container"></div>
<div class="absolute bottom-1 right-1 font-headline text-[10px] text-on-surface">▌RS</div>
</div>
</div>
<div class="p-3 bg-surface-container-high border-t border-primary-container">
<div class="flex justify-between items-center mb-1">
<span class="font-headline text-[14px] font-bold truncate">Terminal</span>
<span class="font-label-caps text-[10px] text-primary-container">✓ ACTIVE</span>
</div>
<span class="font-body-md text-[11px] text-on-surface-variant">by Rusty Solitaire</span>
</div>
</div>
<!-- Theme: Classic -->
<div class="relative flex flex-col bg-surface border border-outline">
<div class="aspect-[2.5/3.5] w-full p-2 overflow-hidden flex items-center justify-center bg-background">
<div class="w-24 h-36 bg-surface border border-outline checker-pattern"></div>
</div>
<div class="p-3 bg-surface-container-low border-t border-outline">
<span class="font-headline text-[14px] font-bold block truncate">Classic</span>
<span class="font-body-md text-[11px] text-on-surface-variant">by Rusty Solitaire</span>
</div>
</div>
<!-- Theme: Stripes -->
<div class="relative flex flex-col bg-surface border border-outline">
<div class="aspect-[2.5/3.5] w-full p-2 overflow-hidden flex items-center justify-center bg-background">
<div class="w-24 h-36 bg-surface border border-outline stripe-pattern"></div>
</div>
<div class="p-3 bg-surface-container-low border-t border-outline">
<span class="font-headline text-[14px] font-bold block truncate">Stripes</span>
<span class="font-body-md text-[11px] text-on-surface-variant">by hayeah</span>
</div>
</div>
<!-- Theme: Polka -->
<div class="relative flex flex-col bg-surface border border-outline">
<div class="aspect-[2.5/3.5] w-full p-2 overflow-hidden flex items-center justify-center bg-background">
<div class="w-24 h-36 bg-surface border border-outline polka-pattern"></div>
</div>
<div class="p-3 bg-surface-container-low border-t border-outline">
<span class="font-headline text-[14px] font-bold block truncate">Polka</span>
<span class="font-body-md text-[11px] text-on-surface-variant">by hayeah</span>
</div>
</div>
<!-- Theme: Vintage -->
<div class="relative flex flex-col bg-surface border border-outline">
<div class="aspect-[2.5/3.5] w-full p-2 overflow-hidden flex items-center justify-center bg-background">
<div class="w-24 h-36 bg-surface border border-outline vintage-pattern"></div>
</div>
<div class="p-3 bg-surface-container-low border-t border-outline">
<span class="font-headline text-[14px] font-bold block truncate">Vintage</span>
<span class="font-body-md text-[11px] text-on-surface-variant">by hayeah</span>
</div>
</div>
<!-- Import Theme -->
<div class="relative flex flex-col bg-surface border-2 border-dashed border-outline-variant hover:border-primary-container transition-colors cursor-pointer">
<div class="aspect-[2.5/3.5] w-full flex flex-col items-center justify-center gap-3">
<span class="material-symbols-outlined text-[32px] text-primary-container" data-icon="add">add</span>
<span class="font-label-caps text-[10px] text-on-surface-variant tracking-widest text-center px-4">IMPORT FROM .ZIP</span>
</div>
<div class="p-3 bg-surface-container-low border-t border-outline-variant">
<span class="font-headline text-[14px] font-bold block truncate">+ IMPORT THEME</span>
<span class="font-body-md text-[11px] opacity-0">spacer</span>
</div>
</div>
</div>
</main>
<!-- BottomNavBar Semantic Shell -->
<footer class="fixed bottom-0 left-0 w-full h-[64px] z-50 bg-surface-container border-t border-outline-variant flex justify-between items-center px-margin-edge pb-safe">
<div class="flex items-center gap-2">
<span class="font-headline text-[14px] font-bold text-on-surface">▌ NORMAL</span>
<span class="text-on-surface-variant"></span>
<span class="font-label-caps text-[12px] text-on-surface-variant">theme</span>
</div>
<div class="flex items-center gap-3">
<div class="flex items-center">
<span class="text-on-surface-variant font-label-caps text-[11px]">[ENTER]</span>
<span class="text-on-surface-variant font-body-md text-[11px] ml-1">activate</span>
</div>
<div class="flex items-center">
<span class="text-on-surface-variant font-label-caps text-[11px]">[I]</span>
<span class="text-on-surface-variant font-body-md text-[11px] ml-1">import</span>
</div>
<div class="flex items-center">
<span class="text-on-surface-variant font-label-caps text-[11px]">[ESC]</span>
<span class="text-on-surface-variant font-body-md text-[11px] ml-1">back</span>
</div>
</div>
</footer>
</body></html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

-215
View File
@@ -1,215 +0,0 @@
<!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 - Time Attack Configuration</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&amp;family=Inter:wght@400;500&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"surface-tint": "#7ed0fe",
"on-secondary-fixed-variant": "#3c4d00",
"primary-container": "#6fc2ef",
"surface-container-high": "#272a2d",
"tertiary": "#f7c3ff",
"suit-black": "#d0d0d0",
"secondary-container": "#435401",
"on-error-container": "#ffdad6",
"on-surface-variant": "#bfc8cf",
"surface-dim": "#101417",
"on-primary-container": "#004f6c",
"info": "#12cfc0",
"outline-variant": "#3f484e",
"surface": "#151515",
"inverse-on-surface": "#2d3134",
"surface-container-low": "#181c1f",
"on-secondary-container": "#b2c86d",
"on-primary-fixed-variant": "#004c69",
"inverse-primary": "#00668a",
"suit-red": "#fb9fb1",
"tertiary-fixed": "#fbd7ff",
"surface-container-lowest": "#0b0f11",
"on-surface": "#d0d0d0",
"on-secondary": "#293500",
"error-container": "#93000a",
"highlight-valid": "#acc267",
"surface-container-highest": "#313538",
"primary": "#a1dcff",
"secondary-fixed": "#d5ec8c",
"error": "#fb9fb1",
"outline": "#505050",
"tertiary-container": "#e1a3ee",
"tertiary-fixed-dim": "#f0b0fc",
"highlight-celebration": "#e1a3ee",
"surface-container": "#202020",
"on-primary": "#003549",
"on-error": "#690005",
"warning": "#ddb26f",
"suit-red-cb": "#6fc2ef",
"on-primary-fixed": "#001e2c",
"on-secondary-fixed": "#161e00",
"on-tertiary-fixed": "#340043",
"on-background": "#e0e3e6",
"on-tertiary": "#4c195b",
"inverse-surface": "#e0e3e6",
"secondary-fixed-dim": "#bad073",
"on-tertiary-fixed-variant": "#653173",
"background": "#101417",
"surface-variant": "#313538",
"secondary": "#bad073",
"on-tertiary-container": "#683476",
"surface-bright": "#363a3d",
"primary-fixed-dim": "#7ed0fe",
"primary-fixed": "#c4e7ff"
},
"borderRadius": {
"DEFAULT": "0.125rem",
"lg": "0.25rem",
"xl": "0.5rem",
"full": "0.75rem"
},
"spacing": {
"margin-edge": "1rem",
"touch-target-min": "48px",
"stack-overlap": "2rem",
"gutter-card": "0.375rem",
"action-bar-height": "64px"
},
"fontFamily": {
"label-caps": ["JetBrains Mono"],
"hud-score": ["JetBrains Mono"],
"headline": ["JetBrains Mono"],
"card-rank": ["JetBrains Mono"],
"body-md": ["Inter"],
"hud-timer": ["JetBrains Mono"]
},
"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"}],
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}]
}
},
},
}
</script>
<style>
body { background-color: #151515; color: #d0d0d0; }
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
.scanline {
background: linear-gradient(to bottom, rgba(255,255,255,0) 50%, rgba(0,0,0,0.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 Container (390x844) -->
<div class="w-[390px] h-[844px] bg-surface relative flex flex-col overflow-hidden border border-outline-variant">
<!-- STATUS BAR -->
<header class="h-8 bg-surface-container flex items-center justify-between px-4 z-10">
<span class="font-headline text-[12px] tracking-tight text-primary">▌time-attack.tsx</span>
<span class="font-label-caps text-[10px] text-on-surface-variant">MODE · TIMED</span>
</header>
<!-- TOP APP BAR (from JSON) -->
<nav class="flex justify-between items-center w-full px-margin-edge h-action-bar-height max-w-full bg-surface text-primary font-headline text-headline font-bold border-b border-outline-variant">
<div class="font-headline text-headline text-primary tracking-tighter uppercase">▌RS_TERMINAL_OS</div>
<div class="flex gap-4">
<span class="material-symbols-outlined cursor-pointer hover:text-primary-fixed transition-colors duration-120">account_circle</span>
<span class="material-symbols-outlined cursor-pointer hover:text-primary-fixed transition-colors duration-120">sync</span>
<span class="material-symbols-outlined cursor-pointer hover:text-primary-fixed transition-colors duration-120">settings</span>
</div>
</nav>
<!-- MAIN CONTENT -->
<main class="flex-1 px-4 py-4 flex flex-col gap-4 overflow-y-auto">
<!-- HERO BAND -->
<section class="flex flex-col items-center justify-center h-[100px] text-center">
<h1 class="font-headline text-[32px] font-bold tracking-tighter text-on-surface uppercase">TIME ATTACK</h1>
<p class="font-body-md text-[12px] text-on-surface-variant max-w-[280px]">Race the clock. The faster you finish, the higher your score.</p>
</section>
<!-- TIMER DISPLAY -->
<section class="w-full h-[120px] bg-surface-dim border border-outline-variant flex flex-col items-center justify-center relative overflow-hidden">
<div class="absolute inset-0 scanline opacity-10 pointer-events-none"></div>
<div class="font-headline text-[64px] font-bold tracking-tight text-primary tabular-nums leading-none">05:00</div>
<div class="font-label-caps text-[11px] uppercase tracking-[0.2em] text-on-surface-variant mt-2">MINUTES</div>
</section>
<!-- DURATION PICKER -->
<section class="grid grid-cols-4 gap-px bg-outline-variant border border-outline-variant">
<button class="h-12 bg-surface-container font-label-caps text-[12px] text-on-surface hover:bg-surface-bright transition-all">1 MIN</button>
<button class="h-12 bg-surface-container font-label-caps text-[12px] text-on-surface hover:bg-surface-bright transition-all">3 MIN</button>
<button class="h-12 bg-primary text-on-primary font-bold font-label-caps text-[12px]">5 MIN</button>
<button class="h-12 bg-surface-container font-label-caps text-[12px] text-on-surface hover:bg-surface-bright transition-all">10 MIN</button>
</section>
<!-- RULES CARD -->
<section class="bg-surface-container h-20 p-3 border border-outline-variant flex flex-col justify-between">
<div class="font-label-caps text-[10px] text-on-surface-variant uppercase tracking-widest">RULES</div>
<div class="font-headline text-[12px] text-on-surface flex items-center gap-2">
<span class="w-1 h-1 bg-primary"></span> DRAW-3
<span class="w-1 h-1 bg-primary ml-2"></span> NO HINT PENALTY
<span class="w-1 h-1 bg-primary ml-2"></span> +50 XP / WIN
</div>
</section>
<!-- BEST RUN CARD -->
<section class="bg-surface-container h-16 p-3 border border-outline-variant flex items-center justify-between">
<div class="flex flex-col">
<div class="font-label-caps text-[10px] text-on-surface-variant uppercase tracking-widest">PERSONAL BEST · 5 MIN</div>
<div class="font-headline text-[24px] font-bold text-on-surface leading-tight">02:47 <span class="text-[12px] font-normal text-on-surface-variant">WIN</span></div>
</div>
<div class="flex flex-col items-end">
<div class="font-label-caps text-[10px] text-on-surface-variant">GLOBAL RANK 142</div>
<div class="bg-warning text-on-surface text-[9px] px-1.5 py-0.5 font-bold mt-1">TOP 5%</div>
</div>
</section>
<!-- PRIMARY CTA -->
<section class="mt-auto pt-4">
<button class="w-full h-20 bg-primary text-on-primary flex flex-col items-center justify-center transition-all active:scale-[0.98] duration-80">
<div class="font-headline text-[18px] font-extrabold uppercase tracking-[0.2em] flex items-center gap-2">
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 1;">play_arrow</span> BEGIN COUNTDOWN
</div>
</button>
<p class="font-body-md text-[11px] text-on-surface-variant text-center mt-3">Game starts after a 3-second countdown.</p>
</section>
</main>
<!-- FOOTER -->
<footer class="h-6 bg-surface-container-lowest flex items-center justify-between px-4 border-t border-outline-variant">
<span class="font-headline text-[10px] text-on-surface-variant">▌ NORMAL │ time-attack</span>
<span class="font-label-caps text-[9px] text-on-surface-variant uppercase">[ENTER] begin · [ESC] back</span>
</footer>
<!-- BOTTOM NAV BAR (from JSON) -->
<nav class="fixed bottom-0 w-[390px] z-50 flex justify-around items-center h-action-bar-height px-margin-edge bg-surface-container border-t border-outline-variant">
<div class="flex flex-col items-center justify-center text-primary bg-surface-container-highest rounded-none p-2 transition-transform duration-80 active:scale-95">
<span class="material-symbols-outlined text-primary" style="font-variation-settings: 'FILL' 1;">videogame_asset</span>
<span class="font-label-caps text-label-caps uppercase tracking-widest">F1_NEW_GAME</span>
</div>
<div class="flex flex-col items-center justify-center text-on-surface-variant p-2 hover:bg-surface-bright transition-all duration-120">
<span class="material-symbols-outlined">event_upcoming</span>
<span class="font-label-caps text-label-caps uppercase tracking-widest">F2_CHALLENGE</span>
</div>
<div class="flex flex-col items-center justify-center text-on-surface-variant p-2 hover:bg-surface-bright transition-all duration-120">
<span class="material-symbols-outlined">query_stats</span>
<span class="font-label-caps text-label-caps uppercase tracking-widest">F5_STATS</span>
</div>
<div class="flex flex-col items-center justify-center text-on-surface-variant p-2 hover:bg-surface-bright transition-all duration-120">
<span class="material-symbols-outlined">power_settings_new</span>
<span class="font-label-caps text-label-caps uppercase tracking-widest">ESC_EXIT</span>
</div>
</nav>
<!-- PADDING FOR FIXED NAV -->
<div class="h-action-bar-height"></div>
</div>
</body></html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

-265
View File
@@ -1,265 +0,0 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Weekly Goals - 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&amp;family=Inter:wght@400;500&amp;family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"surface-container-high": "#272a2d",
"on-primary": "#003549",
"highlight-valid": "#acc267",
"primary-container": "#6fc2ef",
"on-error-container": "#ffdad6",
"surface-tint": "#7ed0fe",
"outline": "#505050",
"on-primary-container": "#004f6c",
"on-background": "#e0e3e6",
"on-secondary-fixed-variant": "#3c4d00",
"error": "#fb9fb1",
"suit-black": "#d0d0d0",
"secondary": "#bad073",
"on-secondary-fixed": "#161e00",
"surface-container": "#202020",
"primary": "#a1dcff",
"error-container": "#93000a",
"secondary-fixed": "#d5ec8c",
"surface-container-highest": "#313538",
"surface-dim": "#101417",
"suit-red": "#fb9fb1",
"warning": "#ddb26f",
"secondary-fixed-dim": "#bad073",
"highlight-celebration": "#e1a3ee",
"on-tertiary-container": "#683476",
"on-surface": "#e0e3e6",
"on-surface-variant": "#bfc8cf",
"inverse-on-surface": "#2d3134",
"primary-fixed-dim": "#7ed0fe",
"tertiary-fixed": "#fbd7ff",
"info": "#12cfc0",
"tertiary-fixed-dim": "#f0b0fc",
"surface-variant": "#313538",
"inverse-primary": "#00668a",
"suit-red-cb": "#6fc2ef",
"tertiary-container": "#e1a3ee",
"secondary-container": "#435401",
"primary-fixed": "#c4e7ff",
"surface-container-low": "#181c1f",
"background": "#101417",
"inverse-surface": "#e0e3e6",
"surface-bright": "#363a3d",
"on-primary-fixed": "#001e2c",
"surface-container-lowest": "#0b0f11",
"on-secondary": "#293500",
"on-secondary-container": "#b2c86d",
"on-tertiary": "#4c195b",
"tertiary": "#f7c3ff",
"outline-variant": "#3f484e",
"on-tertiary-fixed-variant": "#653173",
"on-tertiary-fixed": "#340043",
"on-primary-fixed-variant": "#004c69",
"surface": "#151515",
"on-error": "#690005"
},
"borderRadius": {
"DEFAULT": "0.125rem",
"lg": "0.25rem",
"xl": "0.5rem",
"full": "0.75rem"
},
"spacing": {
"margin-edge": "1rem",
"touch-target-min": "48dp",
"gutter-card": "0.375rem",
"stack-overlap": "2rem",
"action-bar-height": "64px"
},
"fontFamily": {
"headline": ["JetBrains Mono"],
"body-md": ["Inter"],
"label-caps": ["JetBrains Mono"],
"card-rank": ["JetBrains Mono"],
"hud-timer": ["JetBrains Mono"],
"hud-score": ["JetBrains Mono"]
},
"fontSize": {
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"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;
vertical-align: middle;
}
.tabular-nums { font-variant-numeric: tabular-nums; }
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: #151515; }
::-webkit-scrollbar-thumb { background: #353535; border-radius: 2px; }
/* Scanline Overlay Effect */
.crt-overlay {
pointer-events: none;
position: fixed;
top: 0; left: 0; bottom: 0; right: 0;
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.1) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.03), rgba(0, 255, 0, 0.01), rgba(0, 0, 255, 0.03));
background-size: 100% 3px, 3px 100%;
z-index: 100;
}
</style>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-background text-on-surface font-body-md selection:bg-primary-container selection:text-on-primary-container overflow-hidden h-screen flex flex-col">
<!-- Top Status Bar -->
<div class="h-[32px] bg-surface flex items-center justify-between px-margin-edge z-50 shrink-0">
<div class="font-label-caps text-[12px] text-[#a0a0a0] flex items-center">
<span class="mr-1"></span>weekly-goals.json
</div>
<div class="bg-warning/10 text-warning px-2 py-0.5 rounded-sm flex items-center gap-1.5">
<span class="material-symbols-outlined text-[14px]">timer</span>
<span class="font-headline text-[10px] font-bold tabular-nums tracking-wider uppercase">RESETS IN 2D 14H</span>
</div>
</div>
<!-- Main Header Band -->
<header class="h-[80px] px-margin-edge flex flex-col justify-center border-b border-outline/20 shrink-0">
<h1 class="font-headline text-[24px] font-bold text-suit-black tracking-tight leading-none">WEEKLY GOALS</h1>
<p class="font-body-md text-[12px] text-[#a0a0a0] mt-1">Complete goals before reset to claim XP and rewards.</p>
</header>
<!-- Main Content Canvas -->
<main class="flex-1 overflow-y-auto px-margin-edge py-4 space-y-4">
<!-- Overall Progress Card -->
<section class="h-[80px] bg-surface-container border border-outline/30 rounded-[4px] p-4 flex flex-col justify-between">
<div class="flex justify-between items-center">
<span class="font-label-caps text-[10px] text-[#a0a0a0] tracking-widest uppercase">OVERALL · 3/5</span>
<span class="font-headline text-[12px] text-highlight-celebration tabular-nums">(60%)</span>
</div>
<div class="w-full h-[6px] bg-[#353535] rounded-full overflow-hidden">
<div class="h-full bg-highlight-celebration w-[60%]"></div>
</div>
<div class="flex justify-end">
<div class="font-label-caps text-[11px] text-highlight-valid flex items-center gap-1">
<span class="material-symbols-outlined text-[14px]">stars</span>
+220 XP CLAIMED
</div>
</div>
</section>
<!-- Goal List -->
<div class="space-y-2 pb-16">
<!-- Goal 1: COMPLETED -->
<div class="h-[88px] bg-surface-container border border-outline/30 rounded-[4px] p-3 flex flex-col justify-between">
<div class="flex justify-between items-start">
<h3 class="font-headline text-[14px] font-bold text-suit-black">PLAY 10 GAMES</h3>
<div class="bg-highlight-valid/10 text-highlight-valid px-2 py-0.5 rounded-sm font-label-caps text-[11px]">+50 XP</div>
</div>
<div class="w-full h-[4px] bg-[#353535] rounded-full">
<div class="h-full bg-highlight-valid w-full"></div>
</div>
<div class="flex justify-between items-center font-label-caps text-[10px]">
<span class="text-[#a0a0a0]">10/10 GAMES</span>
<span class="text-highlight-valid flex items-center gap-1">✓ CLAIMED</span>
</div>
</div>
<!-- Goal 2: COMPLETED -->
<div class="h-[88px] bg-surface-container border border-outline/30 rounded-[4px] p-3 flex flex-col justify-between">
<div class="flex justify-between items-start">
<h3 class="font-headline text-[14px] font-bold text-suit-black">WIN 5 DAILY SEEDS</h3>
<div class="bg-highlight-celebration/10 text-highlight-celebration px-2 py-0.5 rounded-sm font-label-caps text-[11px]">+100 XP</div>
</div>
<div class="w-full h-[4px] bg-[#353535] rounded-full">
<div class="h-full bg-highlight-valid w-full"></div>
</div>
<div class="flex justify-between items-center font-label-caps text-[10px]">
<span class="text-[#a0a0a0]">5/5 DONE</span>
<span class="text-highlight-valid flex items-center gap-1">✓ CLAIMED</span>
</div>
</div>
<!-- Goal 3: IN PROGRESS -->
<div class="h-[88px] bg-surface-container border border-outline/30 rounded-[4px] p-3 flex flex-col justify-between border-l-2 border-l-primary-container">
<div class="flex justify-between items-start">
<h3 class="font-headline text-[14px] font-bold text-suit-black">WIN UNDER 4:00 (3 TIMES)</h3>
<div class="bg-highlight-valid/10 text-highlight-valid px-2 py-0.5 rounded-sm font-label-caps text-[11px]">+75 XP</div>
</div>
<div class="w-full h-[4px] bg-[#353535] rounded-full">
<div class="h-full bg-primary-container w-[66%]"></div>
</div>
<div class="flex justify-between items-center font-label-caps text-[10px]">
<span class="text-[#a0a0a0]">2/3</span>
<span class="text-primary-container flex items-center gap-1">▶ IN PROGRESS</span>
</div>
</div>
<!-- Goal 4: NOT STARTED -->
<div class="h-[88px] bg-surface-container border border-outline/30 rounded-[4px] p-3 flex flex-col justify-between opacity-70">
<div class="flex justify-between items-start">
<h3 class="font-headline text-[14px] font-bold text-suit-black">PERFECT GAME (NO UNDO)</h3>
<div class="bg-warning/10 text-warning px-2 py-0.5 rounded-sm font-label-caps text-[11px]">+150 XP</div>
</div>
<div class="w-full h-[4px] bg-[#353535] rounded-full">
<div class="h-full bg-outline w-0"></div>
</div>
<div class="flex justify-between items-center font-label-caps text-[10px]">
<span class="text-[#a0a0a0]">0/1</span>
<span class="text-outline flex items-center gap-1">○ NOT STARTED</span>
</div>
</div>
<!-- Goal 5: IN PROGRESS -->
<div class="h-[88px] bg-surface-container border border-outline/30 rounded-[4px] p-3 flex flex-col justify-between border-l-2 border-l-primary-container">
<div class="flex justify-between items-start">
<h3 class="font-headline text-[14px] font-bold text-suit-black">STREAK OF 5 WINS</h3>
<div class="bg-highlight-valid/10 text-highlight-valid px-2 py-0.5 rounded-sm font-label-caps text-[11px]">+50 XP</div>
</div>
<div class="w-full h-[4px] bg-[#353535] rounded-full">
<div class="h-full bg-primary-container w-[60%]"></div>
</div>
<div class="flex justify-between items-center font-label-caps text-[10px]">
<span class="text-[#a0a0a0]">3/5</span>
<span class="text-primary-container flex items-center gap-1">▶ IN PROGRESS</span>
</div>
</div>
</div>
</main>
<!-- Navigation Shell from JSON -->
<nav class="fixed bottom-0 left-0 w-full z-50 flex justify-around items-center px-margin-edge bg-surface-container border-t border-outline-variant h-action-bar-height">
<button class="flex flex-col items-center justify-center text-on-surface-variant hover:text-primary transition-colors duration-120 active:scale-95 transition-transform duration-80">
<span class="material-symbols-outlined">refresh</span>
</button>
<button class="flex flex-col items-center justify-center text-on-surface-variant hover:text-primary transition-colors duration-120 active:scale-95 transition-transform duration-80">
<span class="material-symbols-outlined">undo</span>
</button>
<button class="flex flex-col items-center justify-center text-primary active:scale-95 transition-transform duration-80">
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 1;">style</span>
</button>
<button class="flex flex-col items-center justify-center text-on-surface-variant hover:text-primary transition-colors duration-120 active:scale-95 transition-transform duration-80">
<span class="material-symbols-outlined">help_outline</span>
</button>
</nav>
<!-- Bottom Terminal Status Footer -->
<footer class="fixed bottom-[64px] left-0 w-full h-[24px] bg-surface flex items-center justify-between px-margin-edge z-50 border-t border-outline/10">
<div class="font-label-caps text-[10px] text-[#a0a0a0]">
<span class="text-primary"></span> NORMAL │ weekly
</div>
<div class="font-label-caps text-[10px] flex gap-3">
<span class="text-[#505050]"><span class="text-[#a0a0a0]">[C]</span> claim all</span>
<span class="text-[#505050]"><span class="text-[#a0a0a0]">[ESC]</span> back</span>
</div>
</footer>
<!-- CRT Overlay -->
<div class="crt-overlay"></div>
</body></html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

-200
View File
@@ -1,200 +0,0 @@
<!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:~ | Win Summary</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&amp;family=Inter:wght@400;500;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"primary-fixed": "#c4e7ff",
"inverse-surface": "#e0e3e6",
"on-tertiary": "#4c195b",
"suit-red-cb": "#6fc2ef",
"secondary-fixed-dim": "#bad073",
"on-secondary-fixed-variant": "#3c4d00",
"on-secondary-fixed": "#161e00",
"surface-container-highest": "#313538",
"primary-fixed-dim": "#7ed0fe",
"on-secondary": "#293500",
"on-background": "#e0e3e6",
"highlight-celebration": "#e1a3ee",
"warning": "#ddb26f",
"on-tertiary-fixed-variant": "#653173",
"background": "#101417",
"surface-container-lowest": "#0b0f11",
"info": "#12cfc0",
"tertiary-container": "#e1a3ee",
"surface-container": "#202020",
"secondary": "#bad073",
"outline": "#505050",
"tertiary-fixed-dim": "#f0b0fc",
"secondary-container": "#435401",
"inverse-primary": "#00668a",
"surface": "#151515",
"on-error-container": "#ffdad6",
"error-container": "#93000a",
"surface-bright": "#363a3d",
"surface-dim": "#101417",
"on-primary-fixed-variant": "#004c69",
"on-tertiary-fixed": "#340043",
"inverse-on-surface": "#2d3134",
"surface-container-high": "#272a2d",
"secondary-fixed": "#d5ec8c",
"on-tertiary-container": "#683476",
"on-secondary-container": "#b2c86d",
"surface-tint": "#7ed0fe",
"on-primary-container": "#004f6c",
"on-error": "#690005",
"on-surface": "#e0e3e6",
"surface-variant": "#313538",
"highlight-valid": "#acc267",
"primary": "#a1dcff"
},
"borderRadius": {
"DEFAULT": "0.125rem",
"lg": "0.25rem",
"xl": "0.5rem",
"full": "0.75rem"
},
"spacing": {
"margin-edge": "1rem",
"action-bar-height": "64px",
"gutter-card": "0.375rem",
"touch-target-min": "48dp",
"stack-overlap": "2rem"
},
"fontFamily": {
"body-md": ["Inter"],
"label-caps": ["JetBrains Mono"],
"card-rank": ["JetBrains Mono"],
"hud-timer": ["JetBrains Mono"],
"headline": ["JetBrains Mono"],
"hud-score": ["JetBrains Mono"]
},
"fontSize": {
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}]
}
},
},
}
</script>
<style>
.crt-scanlines {
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.1) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.02), rgba(0, 255, 0, 0.01), rgba(0, 0, 255, 0.02));
background-size: 100% 2px, 3px 100%;
pointer-events: none;
}
.lavender-glow {
text-shadow: 0 0 16px rgba(225, 163, 238, 0.3);
}
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
</style>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-background text-on-background font-body-md selection:bg-primary-container selection:text-on-primary-container min-h-screen flex flex-col relative overflow-hidden">
<!-- CRT Overlay -->
<div class="fixed inset-0 crt-scanlines z-[100] opacity-30"></div>
<!-- Status Bar (Emulating TopAppBar context) -->
<header class="h-[32px] bg-surface-container flex items-center justify-between px-margin-edge z-50 border-b border-outline-variant">
<div class="flex items-center gap-2">
<span class="font-label-caps text-[11px] text-on-surface">▌win.tsx</span>
</div>
<div class="flex items-center gap-4">
<div class="flex items-center gap-1">
<span class="w-2 h-2 rounded-full bg-info"></span>
<span class="font-label-caps text-[11px] text-info uppercase">Synced</span>
</div>
<span class="font-label-caps text-[11px] text-outline uppercase tracking-tight">v0.20.0</span>
</div>
</header>
<!-- Content Canvas -->
<main class="flex-1 flex flex-col px-margin-edge pt-12 pb-8 gap-8 relative z-10">
<!-- Hero Band -->
<section class="flex flex-col items-center text-center space-y-2">
<h1 class="font-headline text-[48px] leading-tight text-highlight-celebration uppercase lavender-glow tracking-tighter">
█ COMPLETE
</h1>
<p class="font-label-caps text-[12px] text-outline-variant tracking-[0.2em]">
GAME #2024-127 · DRAW-3
</p>
</section>
<!-- Stats Card -->
<section class="bg-surface-container border border-outline-variant rounded-lg overflow-hidden p-6 grid grid-cols-2 gap-y-8 gap-x-4">
<div class="space-y-1">
<span class="block font-label-caps text-outline uppercase text-[10px]">Final Score</span>
<span class="block font-hud-score text-on-background">1,024</span>
</div>
<div class="space-y-1">
<span class="block font-label-caps text-outline uppercase text-[10px]">Time</span>
<span class="block font-hud-timer text-on-background">12:34</span>
</div>
<div class="space-y-1">
<span class="block font-label-caps text-outline uppercase text-[10px]">Moves</span>
<span class="block font-hud-timer text-on-background">87</span>
</div>
<div class="space-y-1">
<span class="block font-label-caps text-outline uppercase text-[10px]">Par Delta</span>
<span class="block font-hud-timer text-highlight-valid">13</span>
</div>
</section>
<!-- Achievement Card -->
<section class="bg-surface-container border-l-2 border-highlight-celebration rounded-r-lg p-4 flex items-center justify-between">
<div class="space-y-1">
<span class="font-label-caps text-highlight-celebration text-[10px] flex items-center gap-1">
<span class="text-[8px]"></span> ACHIEVEMENT UNLOCKED
</span>
<p class="font-label-caps text-suit-black uppercase tracking-wider text-[14px]">FIRST DAILY WIN</p>
</div>
<div class="w-12 h-12 rounded-full border border-outline-variant flex items-center justify-center bg-surface-container-low">
<span class="material-symbols-outlined text-highlight-celebration text-2xl" data-icon="military_tech">military_tech</span>
</div>
</section>
<!-- Action Buttons -->
<div class="mt-auto space-y-3">
<button class="w-full h-[56px] bg-info text-surface font-label-caps text-[14px] font-bold tracking-widest flex items-center justify-center gap-2 hover:opacity-90 active:opacity-80 transition-all uppercase">
<span class="text-lg"></span> New Game
</button>
<div class="grid grid-cols-2 gap-3">
<button class="h-[48px] border border-outline text-on-surface-variant font-label-caps text-[12px] flex items-center justify-center gap-2 hover:border-info hover:text-info transition-colors uppercase">
<span class="text-sm"></span> Replay Seed
</button>
<button class="h-[48px] border border-outline text-on-surface-variant font-label-caps text-[12px] flex items-center justify-center gap-2 hover:border-info hover:text-info transition-colors uppercase">
<span class="text-sm"></span> Home
</button>
</div>
</div>
</main>
<!-- Footer Keys -->
<footer class="h-action-bar-height flex flex-col items-center justify-center px-margin-edge border-t border-outline-variant bg-surface-container-low">
<div class="flex gap-4">
<div class="flex items-center gap-1.5">
<span class="font-label-caps text-outline-variant">[ S ]</span>
<span class="font-label-caps text-outline uppercase text-[10px]">share screenshot</span>
</div>
<div class="flex items-center gap-1.5">
<span class="font-label-caps text-outline-variant">[ X ]</span>
<span class="font-label-caps text-outline uppercase text-[10px]">copy seed</span>
</div>
</div>
</footer>
<!-- Bottom Nav Suppression Logic: This is a task-focused confirmation screen, so the global BottomNavBar from JSON is suppressed to focus on primary actions. -->
</body></html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB