Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f23df3b805 | |||
| 68d50b5021 | |||
| ec804d54c6 | |||
| d87761d451 | |||
| 2fb2d638bf | |||
| c9af1ead22 | |||
| ed152e2d8f | |||
| 279a834f9d | |||
| daa655a0af | |||
| 4d48cad4e3 | |||
| dd970215cc | |||
| ddb65403c2 | |||
| 62b61cc786 | |||
| 31139ae455 | |||
| 07e035771c | |||
| c5787c6953 | |||
| 716a025352 | |||
| 3eb3a26789 | |||
| 0c1cc40266 | |||
| 04f9bf9be3 | |||
| a292a7ead0 | |||
| d109c32b75 | |||
| dd101b3d54 | |||
| af414b6aed | |||
| ae84dc1504 | |||
| 8719f77ec2 | |||
| a14200ac2f | |||
| e8bf9d79da | |||
| 48b28d29f8 | |||
| babe5cc9c8 | |||
| 3a4bb63a6f | |||
| 56233687b0 | |||
| 73ac67d76b | |||
| a27cf5a020 | |||
| 29136d815d | |||
| ef54cdeb65 | |||
| e080b49914 | |||
| 54005d5494 | |||
| 44f5972edd | |||
| 13ae16051d | |||
| a65e5b8c7b | |||
| 6204db8bb1 | |||
| c84d9f445c | |||
| cacb19c03f | |||
| 39b84965b6 | |||
| 41a009a693 | |||
| fa7f98ac52 | |||
| 9891ae4ba3 | |||
| cdcaddaabe | |||
| d752870007 | |||
| 1d1543e4bc | |||
| 651f4060e6 | |||
| a1376075bd | |||
| ceec4fc486 | |||
| 0d477ac9fd | |||
| 4b51e50203 | |||
| f2d2119db5 | |||
| 59424a370c | |||
| fb8b2ac684 | |||
| 690e1d2ad6 | |||
| 35516d31f6 | |||
| 9b065e5ac6 | |||
| e1b8766e15 | |||
| 67c150bd7b | |||
| aa2a021712 | |||
| 6037596cc0 | |||
| d7ffb16df5 | |||
| b57db017d3 | |||
| 0b3140ad6d | |||
| e41def8c89 | |||
| aad8bb9c83 | |||
| 55c235b55f | |||
| 21ec03b157 | |||
| 17e3112502 | |||
| de4751115f | |||
| 9ff48ace5b | |||
| 91b7605b9f | |||
| 42d90b199c | |||
| 3e11e9e79a | |||
| bfcd05fbb5 | |||
| c497c3193c | |||
| 9aa0dd23b1 | |||
| d065d49fe7 | |||
| c30b04ec72 | |||
| 40d6e0ab17 | |||
| 9fe650fa20 | |||
| b73d246b4c | |||
| ae40a1db7a | |||
| b7c3a4996f | |||
| d48b9489db | |||
| 08b006ff30 | |||
| 17e0737a10 | |||
| dd63261999 | |||
| 93660c2217 | |||
| 56e2e6f151 | |||
| cc635328be | |||
| a4bc063497 | |||
| 540869c851 | |||
| bdac754b26 | |||
| f863d85c35 | |||
| 3c7a0eb4fb | |||
| d489e7a31b | |||
| f2f30c8002 | |||
| a49a340a30 |
@@ -47,11 +47,10 @@ Solitaire Quest is a cross-platform Klondike Solitaire game written in Rust, tar
|
|||||||
### Design Principles
|
### Design Principles
|
||||||
|
|
||||||
- **Offline first.** The local file is always the source of truth. Sync is additive, never destructive.
|
- **Offline first.** The local file is always the source of truth. Sync is additive, never destructive.
|
||||||
- **Pure core.** All game logic lives in a dependency-free Rust crate with no Bevy, no network, and no I/O. This keeps it fully unit-testable and portable.
|
|
||||||
- **No panics in game logic.** Every state transition returns `Result<_, MoveError>`. Panics are only acceptable in startup/configuration code.
|
|
||||||
- **One language, one repo.** The game client, sync client, shared types, and sync server are all Rust crates in a single Cargo workspace.
|
- **One language, one repo.** The game client, sync client, shared types, and sync server are all Rust crates in a single Cargo workspace.
|
||||||
- **Plugin-based Bevy architecture.** Each major feature is a Bevy `Plugin`. Systems are small and single-purpose. Cross-system communication uses Bevy `Event`s.
|
- **Plugin-based Bevy architecture.** Each major feature is a Bevy `Plugin`. Systems are small and single-purpose. Cross-system communication uses Bevy `Event`s.
|
||||||
- **UI-first interaction.** Every player-triggered action — new game, undo, draw, pause, open stats / settings / help / profile / leaderboard, etc. — must be reachable from a visible UI control. Keyboard shortcuts exist only as optional accelerators for power users; they are never the sole entry point. A player using only mouse or touch must be able to perform every action. New gameplay features ship with the UI control alongside the system that backs it.
|
|
||||||
|
Pure-core, no-panics-in-game-logic, and UI-first-interaction constraints are enforced by CLAUDE.md §2.1, §2.3, and §3.3 respectively — those are the canonical statements; this file describes the design that motivates them.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,114 +1,571 @@
|
|||||||
# Solitaire Quest — Claude Code Instructions
|
# CLAUDE.md
|
||||||
|
|
||||||
See @ARCHITECTURE.md for full project design, crate responsibilities, data models, and API reference.
|
version: unified-3.0
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Project Layout
|
# 0. Role of This File
|
||||||
|
|
||||||
```text
|
This document defines:
|
||||||
solitaire_core/ # Pure Rust game logic — NO Bevy, NO network, NO I/O
|
|
||||||
solitaire_sync/ # Shared API types — NO Bevy, serde/uuid/chrono only
|
* **Execution rules (what Claude must do)**
|
||||||
solitaire_data/ # Persistence + SyncProvider trait + server client
|
* **System constraints (what Claude must never violate)**
|
||||||
solitaire_engine/ # Bevy ECS systems, components, plugins
|
* **Operational architecture (how code is structured)**
|
||||||
solitaire_server/ # Axum sync server binary
|
|
||||||
solitaire_app/ # Thin binary entry point
|
For full system design details:
|
||||||
assets/ # Source assets — embedded at compile time via include_bytes!()
|
→ `ARCHITECTURE.md` (authoritative source of truth)
|
||||||
|
|
||||||
|
This file overrides all conversational assumptions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 1. System Architecture (Authoritative Mapping)
|
||||||
|
|
||||||
|
## 1.1 Crates
|
||||||
|
|
||||||
|
```text id="crate_map"
|
||||||
|
solitaire_core/ # PURE logic (no IO, no Bevy, deterministic)
|
||||||
|
solitaire_sync/ # Shared API + merge logic
|
||||||
|
solitaire_data/ # Persistence + sync client
|
||||||
|
solitaire_engine/ # Bevy ECS + UI + gameplay orchestration
|
||||||
|
solitaire_server/ # Axum backend (optional sync layer)
|
||||||
|
solitaire_app/ # Entry binary
|
||||||
|
assets/ # Runtime assets (except audio)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Build & Test Commands
|
## 1.2 Architecture Source of Truth
|
||||||
|
|
||||||
```bash
|
* Full system design: `ARCHITECTURE.md`
|
||||||
# Dev run (fast compile via dynamic linking)
|
* This file NEVER redefines system design
|
||||||
cargo run -p solitaire_app --features bevy/dynamic_linking
|
* This file ONLY enforces behavior
|
||||||
|
|
||||||
# Release build
|
---
|
||||||
cargo build --workspace --release
|
|
||||||
|
|
||||||
# All tests — MUST pass before any commit
|
# 2. Hard Global Constraints (NON-NEGOTIABLE)
|
||||||
|
|
||||||
|
These override all other instructions.
|
||||||
|
|
||||||
|
## 2.1 Core Determinism
|
||||||
|
|
||||||
|
* `solitaire_core` MUST:
|
||||||
|
|
||||||
|
* be deterministic
|
||||||
|
* be side-effect free
|
||||||
|
* never depend on Bevy / IO / async
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.2 Sync Isolation
|
||||||
|
|
||||||
|
* `solitaire_sync`:
|
||||||
|
|
||||||
|
* no Bevy
|
||||||
|
* no IO
|
||||||
|
* no engine dependencies
|
||||||
|
* merge logic must be pure functions only
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.3 Error Policy
|
||||||
|
|
||||||
|
* NO `unwrap()`
|
||||||
|
* NO `panic!()` in runtime/game logic
|
||||||
|
* All state transitions:
|
||||||
|
|
||||||
|
```rust id="err_model"
|
||||||
|
Result<T, MoveError>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.4 Threading Rules
|
||||||
|
|
||||||
|
* Sync must run on `AsyncComputeTaskPool`
|
||||||
|
* NEVER block Bevy main thread
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.5 Persistence Rules
|
||||||
|
|
||||||
|
* atomic writes only:
|
||||||
|
|
||||||
|
* write `.tmp`
|
||||||
|
* rename atomically
|
||||||
|
* no partial state writes allowed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.6 Security Rules
|
||||||
|
|
||||||
|
* credentials ONLY via `keyring`
|
||||||
|
* NEVER store secrets in:
|
||||||
|
|
||||||
|
* files
|
||||||
|
* logs
|
||||||
|
* source code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.7 Sync System Rules
|
||||||
|
|
||||||
|
* All sync backends implement:
|
||||||
|
|
||||||
|
```rust id="sync_trait"
|
||||||
|
trait SyncProvider
|
||||||
|
```
|
||||||
|
|
||||||
|
* `SyncPlugin` MUST be backend-agnostic
|
||||||
|
* NEVER match on backend inside ECS systems
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 3. Engine Rules (Bevy Layer)
|
||||||
|
|
||||||
|
## 3.1 ECS Design
|
||||||
|
|
||||||
|
* systems = single responsibility
|
||||||
|
* communication = Events only
|
||||||
|
* shared state = Resources only
|
||||||
|
* per-entity state = Components only
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3.2 Game State Authority
|
||||||
|
|
||||||
|
* ONLY `GameStateResource` can mutate game state
|
||||||
|
* UI systems MUST NOT directly modify core logic
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3.3 UI-First Constraint (CRITICAL)
|
||||||
|
|
||||||
|
Every player action MUST:
|
||||||
|
|
||||||
|
* have a visible UI control
|
||||||
|
* NOT rely solely on keyboard shortcuts
|
||||||
|
|
||||||
|
Keyboard shortcuts are:
|
||||||
|
→ optional accelerators only
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3.4 Layout System
|
||||||
|
|
||||||
|
* recompute on `WindowResized`
|
||||||
|
* no fixed resolution assumptions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 4. Asset System Rules
|
||||||
|
|
||||||
|
## 4.1 Runtime Assets (AssetServer)
|
||||||
|
|
||||||
|
Loaded via:
|
||||||
|
|
||||||
|
* `CardImageSet`
|
||||||
|
* `BackgroundImageSet`
|
||||||
|
* `FontResource`
|
||||||
|
|
||||||
|
Includes:
|
||||||
|
|
||||||
|
* cards
|
||||||
|
* backgrounds
|
||||||
|
* fonts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4.2 Embedded Assets
|
||||||
|
|
||||||
|
Only audio:
|
||||||
|
|
||||||
|
```text id="audio_rule"
|
||||||
|
include_bytes!()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4.3 Test Compatibility Rule
|
||||||
|
|
||||||
|
All asset loaders MUST accept:
|
||||||
|
|
||||||
|
```rust id="asset_fallback"
|
||||||
|
Option<Res<AssetServer>>
|
||||||
|
```
|
||||||
|
|
||||||
|
Must degrade gracefully under `MinimalPlugins`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 5. Code Standards
|
||||||
|
|
||||||
|
## 5.1 Error Handling
|
||||||
|
|
||||||
|
* use `thiserror`
|
||||||
|
* no `Box<dyn Error>` in libraries
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5.2 Public API Rules
|
||||||
|
|
||||||
|
* prefer `Into<T>` over concrete types
|
||||||
|
* all public items require doc comments
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5.3 Derive Order
|
||||||
|
|
||||||
|
```rust id="derive_order"
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5.4 Performance Rules
|
||||||
|
|
||||||
|
* NO `clone()` in hot paths
|
||||||
|
* profile before optimizing
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5.5 SQL Rules
|
||||||
|
|
||||||
|
* ONLY `sqlx::query!`
|
||||||
|
* NO raw SQL strings
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 6. Build & Verification Rules
|
||||||
|
|
||||||
|
These are mandatory before ANY commit.
|
||||||
|
|
||||||
|
```bash id="build_rules"
|
||||||
cargo test --workspace
|
cargo test --workspace
|
||||||
|
|
||||||
# Lint — MUST pass clean (zero warnings)
|
|
||||||
cargo clippy --workspace -- -D warnings
|
cargo clippy --workspace -- -D warnings
|
||||||
|
|
||||||
# Run sync server locally
|
|
||||||
cargo run -p solitaire_server
|
|
||||||
|
|
||||||
# Check a single crate
|
|
||||||
cargo test -p solitaire_core
|
|
||||||
cargo clippy -p solitaire_core -- -D warnings
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Hard Rules
|
# 7. Git Workflow Rules
|
||||||
|
|
||||||
- `solitaire_core` and `solitaire_sync` must never gain Bevy or network dependencies.
|
## Commit format
|
||||||
- No `unwrap()` or `panic!()` in game logic. All state transitions return `Result<_, MoveError>`.
|
|
||||||
- Audio assets are embedded at compile time using `include_bytes!()` in `audio_plugin.rs`.
|
```text id="commit_fmt"
|
||||||
- Card faces (52 PNGs in `assets/cards/faces/`), card backs (`assets/cards/backs/back_N.png`), board backgrounds (`assets/backgrounds/bg_N.png`), and the UI font (`assets/fonts/main.ttf`) are loaded at runtime via `AssetServer::load()` and stored as `Handle<Image>`/`Handle<Font>` in the `CardImageSet`, `BackgroundImageSet`, and `FontResource` resources. The `assets/` directory must ship alongside the binary.
|
type(scope): description
|
||||||
- Asset-loading systems take `Option<Res<AssetServer>>` so they degrade cleanly under `MinimalPlugins` (tests). When `CardImageSet` is absent, `card_plugin` falls back to a `Text2d` rank+suit overlay; when `BackgroundImageSet` is absent, the board falls back to a solid colour.
|
```
|
||||||
- Atomic file writes only: write to `filename.json.tmp`, then `rename()`.
|
|
||||||
- Passwords and tokens are stored in the OS keychain via the `keyring` crate — never in plaintext files or logs.
|
Examples:
|
||||||
- Sync runs on `AsyncComputeTaskPool` — never block the Bevy main thread.
|
|
||||||
- All sync backends implement the `SyncProvider` trait. The `SyncPlugin` is backend-agnostic — never `match` on `SyncBackend` inside a Bevy system.
|
* feat(core): add draw-three rules
|
||||||
- `cargo clippy --workspace -- -D warnings` must pass clean after every change.
|
* fix(engine): correct drag z-order
|
||||||
- `cargo test --workspace` must pass after every change.
|
* test(core): undo boundary cases
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Code Style
|
## Commit conditions
|
||||||
|
|
||||||
- Use `thiserror` for error types. Never `Box<dyn Error>` in library crates.
|
* tests must pass
|
||||||
- Prefer `Into<T>` over concrete types in public API function parameters.
|
* clippy must be clean
|
||||||
- All public items must have doc comments (`///`). Private items: comment only when non-obvious.
|
|
||||||
- Derive order convention: `#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]`
|
NEVER commit otherwise
|
||||||
- Bevy systems: one responsibility per system. Use `Events` for cross-system communication, never shared mutable state.
|
|
||||||
- SQL queries: use `sqlx::query!` macros (compile-time checked), not raw string queries.
|
|
||||||
- No `clone()` calls in hot paths (game loop systems). Profile before optimising elsewhere.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Bevy Conventions
|
# 8. Change Control (ASK BEFORE DOING)
|
||||||
|
|
||||||
- One `Plugin` per major feature: `CardPlugin`, `AudioPlugin`, `AchievementPlugin`, `UIPlugin`, `SyncPlugin`.
|
Claude must request confirmation before:
|
||||||
- Resources own shared state. Events communicate between systems. Components own per-entity data.
|
|
||||||
- All UI screens are built with Bevy UI (`bevy::ui`). Never mix UI layout and game logic in the same system.
|
* adding dependencies
|
||||||
- Layout is recomputed on `WindowResized` — never assume a fixed window size.
|
* modifying `solitaire_sync`
|
||||||
- **UI-first.** Every player-triggered action (new game, undo, draw, pause, open stats / settings / help / profile / leaderboard, switch mode, etc.) must be reachable from a visible UI control. Keyboard shortcuts are optional accelerators — never the sole entry point. New gameplay features ship with the UI control alongside the system that backs it; do not merge a feature that is keyboard-only.
|
* changing DB schema
|
||||||
|
* introducing `unsafe`
|
||||||
|
* changing merge strategy
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Git Workflow
|
# 9. System Mental Model (IMPORTANT)
|
||||||
|
|
||||||
- Commit after each passing phase, not after every file change.
|
```text id="mental_model"
|
||||||
- Commit message format: `type(scope): description`
|
Core (rules + deterministic logic)
|
||||||
- `feat(core): add draw-three mode validation`
|
↓
|
||||||
- `fix(engine): card z-order during drag`
|
Engine (Bevy orchestration)
|
||||||
- `test(core): undo stack boundary conditions`
|
↓
|
||||||
- `chore(server): add sqlx migration 002`
|
Data layer (persistence + sync)
|
||||||
- Never commit with failing tests or clippy warnings.
|
↓
|
||||||
- Never commit secrets, `.env` files, or `*.db` files.
|
Server (optional external system)
|
||||||
|
```
|
||||||
|
|
||||||
|
Core is always the source of truth.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Ask Before Doing
|
# 10. Known Platform Pitfalls
|
||||||
|
|
||||||
- Adding a new crate dependency (discuss alternatives first).
|
Must always be handled explicitly:
|
||||||
- Changing a type in `solitaire_sync` (breaking change on both client and server).
|
|
||||||
- Altering the database schema (requires a new sqlx migration).
|
* Bevy `Time` uses `f32`
|
||||||
- Introducing `unsafe` code anywhere.
|
* `sqlx::migrate!()` path is crate-relative
|
||||||
- Changing the merge strategy in `solitaire_sync::merge()`.
|
* `dirs::data_dir()` may return `None`
|
||||||
|
* Linux may lack keyring backend
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Lessons Learned
|
# 11. Forbidden Patterns
|
||||||
|
|
||||||
> Add entries here when Claude makes a mistake so it isn't repeated.
|
* game logic inside Bevy systems
|
||||||
|
* duplication across crates
|
||||||
|
* blocking async calls in ECS
|
||||||
|
* insecure credential storage
|
||||||
|
* bypassing core logic layer
|
||||||
|
|
||||||
- Bevy's `Time` resource uses `f32` seconds; convert to `u64` only when writing to `StatsSnapshot`.
|
---
|
||||||
- `sqlx::migrate!()` macro path is relative to the crate root, not the workspace root.
|
|
||||||
- `keyring` on Linux requires a running secret service (e.g. GNOME Keyring or KWallet) — handle `Error::NoStorageAccess` gracefully and fall back to prompting the user.
|
# 12. Execution Rules for Claude
|
||||||
- `dirs::data_dir()` returns `None` on some minimal Linux environments — always handle the `None` case explicitly, do not unwrap.
|
|
||||||
|
When generating code:
|
||||||
|
|
||||||
|
1. respect crate boundaries
|
||||||
|
2. minimize diff size
|
||||||
|
3. do not expand scope
|
||||||
|
4. follow existing patterns
|
||||||
|
5. preserve invariants
|
||||||
|
|
||||||
|
If unclear:
|
||||||
|
→ ask before acting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 13. Relationship to ARCHITECTURE.md
|
||||||
|
|
||||||
|
| File | Role |
|
||||||
|
| --------------- | ------------------------- |
|
||||||
|
| CLAUDE.md | execution + constraints |
|
||||||
|
| ARCHITECTURE.md | system design truth |
|
||||||
|
| Both combined | full system understanding |
|
||||||
|
|
||||||
|
---
|
||||||
|
# 14. Context Injection System (AUTOMATIC SCOPE FILTER)
|
||||||
|
|
||||||
|
## 14.1 Purpose
|
||||||
|
|
||||||
|
Before generating any response, Claude MUST construct a **minimal relevant context set**.
|
||||||
|
|
||||||
|
This prevents:
|
||||||
|
|
||||||
|
* architectural drift
|
||||||
|
* irrelevant spec loading
|
||||||
|
* over-engineering
|
||||||
|
* cross-crate confusion
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14.2 Input Classification Step (MANDATORY)
|
||||||
|
|
||||||
|
Every request MUST be classified into exactly one task type:
|
||||||
|
|
||||||
|
```text id="task_types"
|
||||||
|
feature
|
||||||
|
bugfix
|
||||||
|
refactor
|
||||||
|
system_design
|
||||||
|
bevy_system
|
||||||
|
core_logic
|
||||||
|
sync
|
||||||
|
optimization
|
||||||
|
test
|
||||||
|
debug
|
||||||
|
```
|
||||||
|
|
||||||
|
If uncertain → ask clarification.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14.3 Context Selection Engine
|
||||||
|
|
||||||
|
After classification, Claude MUST include ONLY the relevant sections below.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14.4 Context Map (CORE RULESET)
|
||||||
|
|
||||||
|
### feature
|
||||||
|
|
||||||
|
Include:
|
||||||
|
|
||||||
|
* §2 Hard Global Constraints
|
||||||
|
* §3 Engine Rules
|
||||||
|
* ARCHITECTURE.md (crate of target feature only)
|
||||||
|
* relevant data models (GameState, SyncPayload if needed)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### bugfix
|
||||||
|
|
||||||
|
Include:
|
||||||
|
|
||||||
|
* §2 Hard Global Constraints
|
||||||
|
* §5 Code Standards
|
||||||
|
* affected crate boundaries
|
||||||
|
* relevant system (engine/core/sync only)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### refactor
|
||||||
|
|
||||||
|
Include:
|
||||||
|
|
||||||
|
* §3 Engine Rules
|
||||||
|
* §5 Code Standards
|
||||||
|
* §11 Forbidden Patterns
|
||||||
|
* target crate boundaries
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### system_design
|
||||||
|
|
||||||
|
Include:
|
||||||
|
|
||||||
|
* ARCHITECTURE.md (FULL)
|
||||||
|
* §9 Mental Model
|
||||||
|
* §1 System Architecture Mapping
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### core_logic
|
||||||
|
|
||||||
|
Include:
|
||||||
|
|
||||||
|
* solitaire_core rules only
|
||||||
|
* GameState model
|
||||||
|
* MoveError model
|
||||||
|
* §2.1–2.3 constraints
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### bevy_system
|
||||||
|
|
||||||
|
Include:
|
||||||
|
|
||||||
|
* §3 Engine Rules
|
||||||
|
* ECS rules (Events/Resources/Components)
|
||||||
|
* UI-first constraint
|
||||||
|
* relevant plugin system only
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### sync
|
||||||
|
|
||||||
|
Include:
|
||||||
|
|
||||||
|
* SyncProvider trait
|
||||||
|
* merge strategy rules
|
||||||
|
* solitaire_sync models
|
||||||
|
* §2.6 Sync Rules
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### optimization
|
||||||
|
|
||||||
|
Include:
|
||||||
|
|
||||||
|
* target crate only
|
||||||
|
* §5.4 Performance Rules
|
||||||
|
* hot path constraints
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### test
|
||||||
|
|
||||||
|
Include:
|
||||||
|
|
||||||
|
* §6 Build Rules
|
||||||
|
* relevant module
|
||||||
|
* expected invariants
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### debug
|
||||||
|
|
||||||
|
Include:
|
||||||
|
|
||||||
|
* target file/module only
|
||||||
|
* §2.3 Error Policy
|
||||||
|
* runtime assumptions relevant to failure
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14.5 Context Compression Rules
|
||||||
|
|
||||||
|
Claude MUST obey:
|
||||||
|
|
||||||
|
* never include full ARCHITECTURE.md unless system_design
|
||||||
|
* max 2 crates per response unless explicitly required
|
||||||
|
* prefer function-level context over file-level context
|
||||||
|
* exclude unrelated plugins/systems
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14.6 Context Priority Order
|
||||||
|
|
||||||
|
When space is limited:
|
||||||
|
|
||||||
|
1. Hard Constraints (§2)
|
||||||
|
2. Target crate rules
|
||||||
|
3. Data models
|
||||||
|
4. Only then: architecture snippets
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14.7 “No Context Pollution” Rule
|
||||||
|
|
||||||
|
Claude must NOT include:
|
||||||
|
|
||||||
|
* unrelated crates
|
||||||
|
* unrelated plugins
|
||||||
|
* unused data models
|
||||||
|
* full architecture dumps
|
||||||
|
* speculative systems
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14.8 Self-Check Before Execution
|
||||||
|
|
||||||
|
Before writing code, Claude MUST verify:
|
||||||
|
|
||||||
|
* [ ] Is only relevant context included?
|
||||||
|
* [ ] Is at least one hard constraint present?
|
||||||
|
* [ ] Am I touching more than one crate unnecessarily?
|
||||||
|
* [ ] Am I duplicating ARCHITECTURE.md content?
|
||||||
|
|
||||||
|
If any fail → revise context selection.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14.9 Injection Output Format (Internal Model)
|
||||||
|
|
||||||
|
Claude should behave as if it constructed:
|
||||||
|
|
||||||
|
```text id="ctx_format"
|
||||||
|
[SELECTED TASK TYPE]
|
||||||
|
|
||||||
|
[MINIMAL REQUIRED RULES]
|
||||||
|
|
||||||
|
[MINIMAL ARCHITECTURE SLICES]
|
||||||
|
|
||||||
|
[RELEVANT MODELS]
|
||||||
|
|
||||||
|
[REQUEST]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14.10 Relationship to ARCHITECTURE.md
|
||||||
|
|
||||||
|
* ARCHITECTURE.md = source of truth
|
||||||
|
* CLAUDE.md = execution constraints
|
||||||
|
* THIS SECTION = filtering layer between them
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# END CONTEXT INJECTION SYSTEM
|
||||||
|
|||||||
@@ -0,0 +1,497 @@
|
|||||||
|
# CLAUDE_PROMPT_PACK.md
|
||||||
|
|
||||||
|
version: 1.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 0. GLOBAL INSTRUCTION (prepend to every prompt)
|
||||||
|
|
||||||
|
```
|
||||||
|
You must follow CLAUDE_SPEC.md strictly.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- Do not expand scope beyond what is defined
|
||||||
|
- Do not refactor unrelated code
|
||||||
|
- Do not introduce new dependencies
|
||||||
|
- Prefer minimal, surgical changes
|
||||||
|
- Use existing patterns in the codebase
|
||||||
|
- Return minimal diffs or changed functions only
|
||||||
|
|
||||||
|
Before writing code:
|
||||||
|
1. List relevant constraints from CLAUDE_SPEC.md
|
||||||
|
2. Identify risks
|
||||||
|
3. Then implement
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 1. FEATURE IMPLEMENTATION
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Feature Implementation
|
||||||
|
|
||||||
|
feature: "<name>"
|
||||||
|
|
||||||
|
goal:
|
||||||
|
"<clear outcome>"
|
||||||
|
|
||||||
|
scope:
|
||||||
|
crates: []
|
||||||
|
systems: []
|
||||||
|
files: []
|
||||||
|
|
||||||
|
non_goals:
|
||||||
|
- ""
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
- must follow CLAUDE_SPEC.md
|
||||||
|
- event-driven architecture required
|
||||||
|
- no blocking operations
|
||||||
|
- no cross-crate leakage
|
||||||
|
|
||||||
|
acceptance_criteria:
|
||||||
|
- ""
|
||||||
|
- ""
|
||||||
|
|
||||||
|
edge_cases:
|
||||||
|
- ""
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Required Patterns
|
||||||
|
|
||||||
|
Use this pattern for systems:
|
||||||
|
<PASTE EXISTING SYSTEM SNIPPET HERE>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
intent:
|
||||||
|
plan:
|
||||||
|
constraints_used:
|
||||||
|
risks:
|
||||||
|
|
||||||
|
code_changes:
|
||||||
|
(minimal diffs only)
|
||||||
|
|
||||||
|
notes:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2. BUGFIX
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Bug Fix
|
||||||
|
|
||||||
|
bug_description:
|
||||||
|
"<what is broken>"
|
||||||
|
|
||||||
|
expected_behavior:
|
||||||
|
"<correct behavior>"
|
||||||
|
|
||||||
|
root_cause_hint (optional):
|
||||||
|
""
|
||||||
|
|
||||||
|
scope:
|
||||||
|
crates: []
|
||||||
|
files: []
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
- minimal fix only
|
||||||
|
- no refactors unless required
|
||||||
|
- must add regression protection if applicable
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
1. Identify root cause
|
||||||
|
2. Fix it minimally
|
||||||
|
3. Preserve all invariants
|
||||||
|
4. Do not change unrelated logic
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
analysis:
|
||||||
|
root_cause:
|
||||||
|
fix_strategy:
|
||||||
|
|
||||||
|
code_changes:
|
||||||
|
(minimal diff)
|
||||||
|
|
||||||
|
regression_test (only if high-value):
|
||||||
|
|
||||||
|
notes:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 3. REFACTOR
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Refactor
|
||||||
|
|
||||||
|
target:
|
||||||
|
"<what is being improved>"
|
||||||
|
|
||||||
|
goal:
|
||||||
|
"<what improves>"
|
||||||
|
|
||||||
|
scope:
|
||||||
|
crates: []
|
||||||
|
files: []
|
||||||
|
|
||||||
|
non_goals:
|
||||||
|
- no behavior changes
|
||||||
|
- no new features
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
- must preserve behavior exactly
|
||||||
|
- must respect crate boundaries
|
||||||
|
- must not duplicate logic
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Refactor Type
|
||||||
|
|
||||||
|
- [ ] simplify logic
|
||||||
|
- [ ] reduce duplication
|
||||||
|
- [ ] improve readability
|
||||||
|
- [ ] performance (non-invasive)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
analysis:
|
||||||
|
issues_found:
|
||||||
|
|
||||||
|
refactor_plan:
|
||||||
|
|
||||||
|
code_changes:
|
||||||
|
(diff only)
|
||||||
|
|
||||||
|
verification:
|
||||||
|
- behavior unchanged: yes/no
|
||||||
|
- invariants preserved: yes/no
|
||||||
|
|
||||||
|
notes:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 4. SYSTEM DESIGN (NEW FEATURE)
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: System Design
|
||||||
|
|
||||||
|
feature:
|
||||||
|
"<name>"
|
||||||
|
|
||||||
|
goal:
|
||||||
|
"<what problem it solves>"
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
- must fit existing architecture
|
||||||
|
- must follow plugin + event model
|
||||||
|
- must not violate crate boundaries
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Required Output
|
||||||
|
|
||||||
|
design:
|
||||||
|
|
||||||
|
components:
|
||||||
|
- plugins:
|
||||||
|
- systems:
|
||||||
|
- events:
|
||||||
|
- resources:
|
||||||
|
|
||||||
|
data_flow:
|
||||||
|
(step-by-step)
|
||||||
|
|
||||||
|
integration_points:
|
||||||
|
- where it connects to existing systems
|
||||||
|
|
||||||
|
risks:
|
||||||
|
- ""
|
||||||
|
|
||||||
|
tradeoffs:
|
||||||
|
- ""
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## DO NOT
|
||||||
|
|
||||||
|
- write full implementation
|
||||||
|
- modify unrelated systems
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 5. NEW BEVY SYSTEM
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Add Bevy System
|
||||||
|
|
||||||
|
system_name:
|
||||||
|
""
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
(event or condition)
|
||||||
|
|
||||||
|
reads:
|
||||||
|
[Resources]
|
||||||
|
|
||||||
|
writes:
|
||||||
|
[Resources]
|
||||||
|
|
||||||
|
emits:
|
||||||
|
[Events]
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
- must be event-driven
|
||||||
|
- must not directly mutate unrelated state
|
||||||
|
- must be single responsibility
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
system_signature:
|
||||||
|
|
||||||
|
implementation:
|
||||||
|
(code only)
|
||||||
|
|
||||||
|
notes:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 6. CORE LOGIC FUNCTION (solitaire_core)
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Core Logic Implementation
|
||||||
|
|
||||||
|
function:
|
||||||
|
"<name>"
|
||||||
|
|
||||||
|
goal:
|
||||||
|
"<what it does>"
|
||||||
|
|
||||||
|
rules:
|
||||||
|
- no IO
|
||||||
|
- no async
|
||||||
|
- no Bevy
|
||||||
|
- deterministic
|
||||||
|
|
||||||
|
invariants:
|
||||||
|
- ""
|
||||||
|
- ""
|
||||||
|
|
||||||
|
errors:
|
||||||
|
- ""
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
constraints_checked:
|
||||||
|
|
||||||
|
implementation:
|
||||||
|
(code only)
|
||||||
|
|
||||||
|
edge_case_handling:
|
||||||
|
|
||||||
|
notes:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 7. SYNC / MERGE LOGIC
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Sync Logic
|
||||||
|
|
||||||
|
goal:
|
||||||
|
"<what is being merged or synced>"
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
- must be deterministic
|
||||||
|
- must be idempotent
|
||||||
|
- must be lossless
|
||||||
|
- must not delete data
|
||||||
|
|
||||||
|
rules:
|
||||||
|
- counters → max
|
||||||
|
- times → min
|
||||||
|
- collections → union
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
analysis:
|
||||||
|
|
||||||
|
merge_logic:
|
||||||
|
|
||||||
|
code_changes:
|
||||||
|
|
||||||
|
invariants_verified:
|
||||||
|
- deterministic
|
||||||
|
- idempotent
|
||||||
|
- lossless
|
||||||
|
|
||||||
|
notes:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 8. PERFORMANCE OPTIMIZATION
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Optimization
|
||||||
|
|
||||||
|
target:
|
||||||
|
"<what is slow>"
|
||||||
|
|
||||||
|
constraints:CLAUDE_WORKFLOW.md
|
||||||
|
- no behavior change
|
||||||
|
- no architecture change
|
||||||
|
- minimal code changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
analysis:
|
||||||
|
bottleneck:
|
||||||
|
|
||||||
|
optimization_strategy:
|
||||||
|
|
||||||
|
code_changes:
|
||||||
|
|
||||||
|
impact_estimate:
|
||||||
|
|
||||||
|
notes:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 9. TEST GENERATION (STRICT MODE)
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Test Generation
|
||||||
|
|
||||||
|
target:
|
||||||
|
"<function/system>"
|
||||||
|
|
||||||
|
reason:
|
||||||
|
- bugfix | complex logic | invariant protection
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
- no redundant tests
|
||||||
|
- must test real behavior
|
||||||
|
- must fail if logic breaks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
test_cases:
|
||||||
|
- ""
|
||||||
|
|
||||||
|
test_code:
|
||||||
|
|
||||||
|
notes:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 10. DEBUGGING / INVESTIGATION
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Debug
|
||||||
|
|
||||||
|
problem:
|
||||||
|
"<symptom>"
|
||||||
|
|
||||||
|
context:
|
||||||
|
"<relevant code or system>"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Required Steps
|
||||||
|
|
||||||
|
1. List possible causes
|
||||||
|
2. Narrow down most likely
|
||||||
|
3. Suggest verification steps
|
||||||
|
4. Provide minimal fix
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
hypotheses:
|
||||||
|
|
||||||
|
most_likely:
|
||||||
|
|
||||||
|
verification_steps:
|
||||||
|
|
||||||
|
fix:
|
||||||
|
|
||||||
|
notes:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 11. HARD CONSTRAINT OVERRIDE (RARE)
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Exception Handling
|
||||||
|
|
||||||
|
reason:
|
||||||
|
"<why constraints must be bent>"
|
||||||
|
|
||||||
|
requested_exception:
|
||||||
|
"<rule being broken>"
|
||||||
|
|
||||||
|
justification:
|
||||||
|
"<why unavoidable>"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
analysis:
|
||||||
|
|
||||||
|
alternatives_considered:
|
||||||
|
|
||||||
|
final_decision:
|
||||||
|
|
||||||
|
risk:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 12. STOP CONDITIONS (always append)
|
||||||
|
|
||||||
|
```
|
||||||
|
Stop when:
|
||||||
|
- acceptance criteria are met
|
||||||
|
- code is minimal and correct
|
||||||
|
|
||||||
|
Do NOT:
|
||||||
|
- expand scope
|
||||||
|
- refactor unrelated code
|
||||||
|
- optimize prematurely
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# END
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
# CLAUDE_SPEC.md
|
||||||
|
|
||||||
|
version: 1.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Global Rules
|
||||||
|
|
||||||
|
(Core determinism, panic policy, and event-driven engine constraints live in CLAUDE.md §2.1, §2.3, §3.1. Listed here only when they add information CLAUDE.md doesn't carry.)
|
||||||
|
|
||||||
|
rules:
|
||||||
|
|
||||||
|
* id: single_source_of_truth
|
||||||
|
description: "GameStateResource is the only mutable game state in runtime"
|
||||||
|
|
||||||
|
* id: sync_is_additive
|
||||||
|
description: "Remote data must never destructively overwrite local data"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Crate Graph
|
||||||
|
|
||||||
|
crates:
|
||||||
|
solitaire_core:
|
||||||
|
depends_on: [rand, serde, chrono]
|
||||||
|
forbidden_deps: [bevy, reqwest, tokio, std::fs]
|
||||||
|
|
||||||
|
solitaire_sync:
|
||||||
|
depends_on: [serde, serde_json, uuid, chrono]
|
||||||
|
role: "shared_types"
|
||||||
|
|
||||||
|
solitaire_data:
|
||||||
|
depends_on: [solitaire_core, solitaire_sync, reqwest, tokio, keyring]
|
||||||
|
role: "persistence_and_sync"
|
||||||
|
|
||||||
|
solitaire_engine:
|
||||||
|
depends_on: [bevy, kira, solitaire_core, solitaire_data]
|
||||||
|
role: "runtime_engine"
|
||||||
|
|
||||||
|
solitaire_server:
|
||||||
|
depends_on: [solitaire_sync, axum, sqlx, jsonwebtoken]
|
||||||
|
role: "backend"
|
||||||
|
|
||||||
|
solitaire_app:
|
||||||
|
depends_on: [solitaire_engine]
|
||||||
|
role: "entrypoint"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Data Ownership
|
||||||
|
|
||||||
|
ownership:
|
||||||
|
GameState:
|
||||||
|
owner: solitaire_core
|
||||||
|
mutable_in: solitaire_engine
|
||||||
|
access_pattern: "via GameStateResource only"
|
||||||
|
|
||||||
|
StatsSnapshot:
|
||||||
|
owner: solitaire_data
|
||||||
|
|
||||||
|
PlayerProgress:
|
||||||
|
owner: solitaire_data
|
||||||
|
|
||||||
|
AchievementRecord:
|
||||||
|
owner: solitaire_data
|
||||||
|
|
||||||
|
SyncPayload:
|
||||||
|
owner: solitaire_sync
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. State Transitions
|
||||||
|
|
||||||
|
state_machine:
|
||||||
|
GameState:
|
||||||
|
transitions:
|
||||||
|
- action: move_cards
|
||||||
|
returns: Result<GameState, MoveError>
|
||||||
|
|
||||||
|
```
|
||||||
|
- action: draw
|
||||||
|
returns: Result<GameState, MoveError>
|
||||||
|
|
||||||
|
- action: undo
|
||||||
|
returns: Result<GameState, MoveError>
|
||||||
|
|
||||||
|
invariants:
|
||||||
|
- "52 cards always exist"
|
||||||
|
- "no duplicate card IDs"
|
||||||
|
- "all cards belong to exactly one pile"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Event System
|
||||||
|
|
||||||
|
events:
|
||||||
|
|
||||||
|
input:
|
||||||
|
- MoveRequestEvent
|
||||||
|
- DrawRequestEvent
|
||||||
|
- UndoRequestEvent
|
||||||
|
- NewGameRequestEvent
|
||||||
|
|
||||||
|
state:
|
||||||
|
- StateChangedEvent
|
||||||
|
- GameWonEvent
|
||||||
|
|
||||||
|
meta:
|
||||||
|
- AchievementUnlockedEvent
|
||||||
|
- SyncCompleteEvent
|
||||||
|
|
||||||
|
rules:
|
||||||
|
|
||||||
|
* "Input events trigger core logic"
|
||||||
|
* "Core logic emits state events"
|
||||||
|
* "UI reacts to state events only"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Sync Contract
|
||||||
|
|
||||||
|
sync:
|
||||||
|
|
||||||
|
provider_trait:
|
||||||
|
methods:
|
||||||
|
- pull() -> SyncPayload
|
||||||
|
- push(payload) -> SyncResponse
|
||||||
|
|
||||||
|
guarantees:
|
||||||
|
- "non-blocking during gameplay"
|
||||||
|
- "blocking allowed on exit only"
|
||||||
|
|
||||||
|
merge:
|
||||||
|
rules:
|
||||||
|
counters: "max"
|
||||||
|
best_times: "min"
|
||||||
|
collections: "union"
|
||||||
|
achievements: "never removed"
|
||||||
|
|
||||||
|
```
|
||||||
|
properties:
|
||||||
|
- deterministic
|
||||||
|
- idempotent
|
||||||
|
- lossless
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Persistence
|
||||||
|
|
||||||
|
storage:
|
||||||
|
|
||||||
|
format: json
|
||||||
|
|
||||||
|
files:
|
||||||
|
- stats.json
|
||||||
|
- progress.json
|
||||||
|
- achievements.json
|
||||||
|
- settings.json
|
||||||
|
- game_state.json
|
||||||
|
|
||||||
|
guarantees:
|
||||||
|
- atomic_write: true
|
||||||
|
- crash_safe: true
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Engine Rules
|
||||||
|
|
||||||
|
engine:
|
||||||
|
|
||||||
|
mutation_rules:
|
||||||
|
- "Only GameLogicSystem mutates GameState"
|
||||||
|
- "UI systems are read-only"
|
||||||
|
|
||||||
|
threading:
|
||||||
|
- "sync runs on AsyncComputeTaskPool"
|
||||||
|
- "main thread must never block"
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
pattern: "feature_isolation"
|
||||||
|
communication: "events"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Server Contract
|
||||||
|
|
||||||
|
server:
|
||||||
|
|
||||||
|
auth:
|
||||||
|
method: jwt
|
||||||
|
access_expiry: 24h
|
||||||
|
refresh_expiry: 30d
|
||||||
|
|
||||||
|
endpoints:
|
||||||
|
- POST /api/auth/register
|
||||||
|
- POST /api/auth/login
|
||||||
|
- GET /api/sync/pull
|
||||||
|
- POST /api/sync/push
|
||||||
|
|
||||||
|
limits:
|
||||||
|
payload_max: 1MB
|
||||||
|
rate_limit: "10 req/min auth routes"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Achievement System
|
||||||
|
|
||||||
|
achievements:
|
||||||
|
|
||||||
|
definition_location: solitaire_core
|
||||||
|
state_location: solitaire_data
|
||||||
|
|
||||||
|
types:
|
||||||
|
- condition_based
|
||||||
|
- event_driven
|
||||||
|
|
||||||
|
rule:
|
||||||
|
- "achievements cannot be revoked"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Testing Rules
|
||||||
|
|
||||||
|
testing:
|
||||||
|
|
||||||
|
philosophy:
|
||||||
|
- "test real failures"
|
||||||
|
- "avoid redundant tests"
|
||||||
|
|
||||||
|
required_coverage:
|
||||||
|
solitaire_core:
|
||||||
|
- move_validation
|
||||||
|
- undo_integrity
|
||||||
|
- win_detection
|
||||||
|
|
||||||
|
```
|
||||||
|
solitaire_sync:
|
||||||
|
- merge_correctness
|
||||||
|
- idempotency
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Prohibited Patterns
|
||||||
|
|
||||||
|
(See CLAUDE.md §11 for the canonical forbidden-patterns list.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Extension Points
|
||||||
|
|
||||||
|
extensibility:
|
||||||
|
|
||||||
|
sync_backends:
|
||||||
|
pattern: "implement SyncProvider"
|
||||||
|
|
||||||
|
game_modes:
|
||||||
|
location: solitaire_core::GameMode
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
rule: "new feature = new plugin"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Validation Checklist (for Claude)
|
||||||
|
|
||||||
|
validation:
|
||||||
|
|
||||||
|
* check: "crate dependency rules respected"
|
||||||
|
* check: "no panics in core"
|
||||||
|
* check: "events used for cross-system communication"
|
||||||
|
* check: "GameState mutations centralized"
|
||||||
|
* check: "merge function properties preserved"
|
||||||
|
* check: "no blocking operations in main loop"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Mental Model
|
||||||
|
|
||||||
|
model:
|
||||||
|
|
||||||
|
layers:
|
||||||
|
- core
|
||||||
|
- engine
|
||||||
|
- data
|
||||||
|
- server
|
||||||
|
|
||||||
|
flow:
|
||||||
|
- input -> engine -> core -> engine -> ui
|
||||||
|
- data <-> sync <-> server
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
# CLAUDE_WORKFLOW.md
|
||||||
|
|
||||||
|
version: 1.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Overview
|
||||||
|
|
||||||
|
This workflow defines a **two-agent system**:
|
||||||
|
|
||||||
|
* **Builder Agent** → writes and modifies code
|
||||||
|
* **Guardian Agent** → enforces architecture + rejects invalid changes
|
||||||
|
|
||||||
|
No code is considered valid unless it passes Guardian validation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Agent Roles
|
||||||
|
|
||||||
|
### 1.1 Builder Agent
|
||||||
|
|
||||||
|
role: "code_generation"
|
||||||
|
|
||||||
|
responsibilities:
|
||||||
|
|
||||||
|
* implement features
|
||||||
|
* refactor code
|
||||||
|
* generate tests (only when justified)
|
||||||
|
* follow CLAUDE_SPEC.md
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
|
||||||
|
* cannot bypass validation
|
||||||
|
* must declare intent before writing code
|
||||||
|
|
||||||
|
output_contract:
|
||||||
|
must_produce:
|
||||||
|
- change_summary
|
||||||
|
- files_modified
|
||||||
|
- reasoning (short)
|
||||||
|
- code_diff
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1.2 Guardian Agent
|
||||||
|
|
||||||
|
role: "architecture_enforcement"
|
||||||
|
|
||||||
|
responsibilities:
|
||||||
|
|
||||||
|
* validate against CLAUDE_SPEC.md
|
||||||
|
* detect violations
|
||||||
|
* reject or approve changes
|
||||||
|
* suggest minimal fixes (not full rewrites)
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
|
||||||
|
* no feature implementation
|
||||||
|
* no large rewrites
|
||||||
|
* must be deterministic
|
||||||
|
|
||||||
|
output_contract:
|
||||||
|
must_produce:
|
||||||
|
- status: APPROVED | REJECTED
|
||||||
|
- violations[]
|
||||||
|
- required_fixes[]
|
||||||
|
- optional_improvements[]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Workflow Pipeline
|
||||||
|
|
||||||
|
```text
|
||||||
|
User Request
|
||||||
|
↓
|
||||||
|
Builder Agent (proposal + code)
|
||||||
|
↓
|
||||||
|
Guardian Agent (validation)
|
||||||
|
↓
|
||||||
|
IF approved → commit
|
||||||
|
IF rejected → feedback → Builder retry
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Builder Protocol
|
||||||
|
|
||||||
|
### Step 1 — Intent Declaration
|
||||||
|
|
||||||
|
Builder MUST start with:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
intent:
|
||||||
|
feature: "<name>"
|
||||||
|
crates_touched: []
|
||||||
|
systems_affected: []
|
||||||
|
risk_level: low|medium|high
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 2 — Plan
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
plan:
|
||||||
|
- step: "..."
|
||||||
|
- step: "..."
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 3 — Implementation
|
||||||
|
|
||||||
|
* Only modify declared crates
|
||||||
|
* Follow ownership rules
|
||||||
|
* Use events for cross-system communication
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 4 — Output
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
change_summary: "..."
|
||||||
|
|
||||||
|
files_modified:
|
||||||
|
- path: ...
|
||||||
|
change: "..."
|
||||||
|
|
||||||
|
violations_self_check:
|
||||||
|
- none | list
|
||||||
|
|
||||||
|
notes: "short reasoning"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Guardian Protocol
|
||||||
|
|
||||||
|
### Step 1 — Spec Validation
|
||||||
|
|
||||||
|
Check against:
|
||||||
|
|
||||||
|
* crate boundaries
|
||||||
|
* mutation rules
|
||||||
|
* event system usage
|
||||||
|
* sync guarantees
|
||||||
|
* forbidden patterns
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 2 — Invariant Validation
|
||||||
|
|
||||||
|
Must verify:
|
||||||
|
|
||||||
|
* GameState invariants preserved
|
||||||
|
* no new panic paths
|
||||||
|
* no blocking calls in engine
|
||||||
|
* merge properties unchanged
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 3 — Output Decision
|
||||||
|
|
||||||
|
#### APPROVED
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
status: APPROVED
|
||||||
|
|
||||||
|
notes:
|
||||||
|
- "no violations"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### REJECTED
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
status: REJECTED
|
||||||
|
|
||||||
|
violations:
|
||||||
|
- id: core_purity_violation
|
||||||
|
file: "solitaire_core/src/..."
|
||||||
|
reason: "uses std::fs"
|
||||||
|
|
||||||
|
required_fixes:
|
||||||
|
- "move IO to solitaire_data"
|
||||||
|
|
||||||
|
optional_improvements:
|
||||||
|
- "simplify event naming"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Enforcement Rules
|
||||||
|
|
||||||
|
### Hard Fail (automatic rejection)
|
||||||
|
|
||||||
|
* core crate uses IO / Bevy / network
|
||||||
|
* GameState mutated outside GameLogicSystem
|
||||||
|
* blocking async on main thread
|
||||||
|
* duplicate logic across crates
|
||||||
|
* merge function altered incorrectly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Soft Fail (allowed but flagged)
|
||||||
|
|
||||||
|
* unnecessary complexity
|
||||||
|
* redundant tests
|
||||||
|
* minor architectural drift
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Iteration Loop
|
||||||
|
|
||||||
|
Max attempts per task: **3**
|
||||||
|
|
||||||
|
```text
|
||||||
|
Attempt 1 → Reject → Fix
|
||||||
|
Attempt 2 → Reject → Fix
|
||||||
|
Attempt 3 → Final decision
|
||||||
|
```
|
||||||
|
|
||||||
|
If still failing:
|
||||||
|
→ escalate to user
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Diff Strategy
|
||||||
|
|
||||||
|
Builder MUST produce:
|
||||||
|
|
||||||
|
* minimal diffs
|
||||||
|
* no unrelated refactors
|
||||||
|
* no formatting-only changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Test Strategy Integration
|
||||||
|
|
||||||
|
Builder rules:
|
||||||
|
|
||||||
|
* only add tests if:
|
||||||
|
|
||||||
|
* fixing a bug
|
||||||
|
* protecting complex logic
|
||||||
|
* validating invariants
|
||||||
|
|
||||||
|
Guardian rejects:
|
||||||
|
|
||||||
|
* redundant tests
|
||||||
|
* no-op tests
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Optional Extensions
|
||||||
|
|
||||||
|
### 9.1 Third Agent (Optimizer)
|
||||||
|
|
||||||
|
role: performance + cleanup
|
||||||
|
|
||||||
|
runs AFTER approval:
|
||||||
|
|
||||||
|
* reduce allocations
|
||||||
|
* simplify logic
|
||||||
|
* improve ECS scheduling
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9.2 CI Integration
|
||||||
|
|
||||||
|
Pipeline:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Builder → Guardian → cargo check → clippy → tests
|
||||||
|
```
|
||||||
|
|
||||||
|
Guardian runs BEFORE compilation to catch structural issues early.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Example Interaction
|
||||||
|
|
||||||
|
### Builder
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
intent:
|
||||||
|
feature: "undo stack limit fix"
|
||||||
|
crates_touched: [solitaire_core]
|
||||||
|
risk_level: low
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
change_summary: "limit undo stack to 64 entries"
|
||||||
|
|
||||||
|
files_modified:
|
||||||
|
- solitaire_core/src/game_state.rs
|
||||||
|
|
||||||
|
notes: "prevents unbounded memory growth"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Guardian
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
status: APPROVED
|
||||||
|
|
||||||
|
notes:
|
||||||
|
- "respects core constraints"
|
||||||
|
- "no invariant violations"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Mental Model
|
||||||
|
|
||||||
|
* Builder = **creative**
|
||||||
|
* Guardian = **strict**
|
||||||
|
|
||||||
|
Builder explores
|
||||||
|
Guardian enforces
|
||||||
|
|
||||||
|
Neither replaces the other.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Success Criteria
|
||||||
|
|
||||||
|
System is working if:
|
||||||
|
|
||||||
|
* architectural violations go to ~0
|
||||||
|
* code stays consistent across features
|
||||||
|
* refactors become safe
|
||||||
|
* complexity grows sub-linearly
|
||||||
@@ -44,7 +44,7 @@ dependencies = [
|
|||||||
"accesskit_consumer",
|
"accesskit_consumer",
|
||||||
"hashbrown 0.15.5",
|
"hashbrown 0.15.5",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit 0.2.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -126,6 +126,19 @@ dependencies = [
|
|||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ahash"
|
||||||
|
version = "0.8.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"getrandom 0.3.4",
|
||||||
|
"once_cell",
|
||||||
|
"version_check",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.4"
|
version = "1.1.4"
|
||||||
@@ -313,6 +326,23 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arboard"
|
||||||
|
version = "3.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf"
|
||||||
|
dependencies = [
|
||||||
|
"clipboard-win",
|
||||||
|
"log",
|
||||||
|
"objc2 0.6.4",
|
||||||
|
"objc2-app-kit 0.3.2",
|
||||||
|
"objc2-foundation 0.3.2",
|
||||||
|
"parking_lot",
|
||||||
|
"percent-encoding",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
"x11rb",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arc-swap"
|
name = "arc-swap"
|
||||||
version = "1.9.1"
|
version = "1.9.1"
|
||||||
@@ -702,7 +732,7 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"ctrlc",
|
"ctrlc",
|
||||||
"downcast-rs",
|
"downcast-rs 2.0.2",
|
||||||
"log",
|
"log",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"variadics_please",
|
"variadics_please",
|
||||||
@@ -736,7 +766,7 @@ dependencies = [
|
|||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"disqualified",
|
"disqualified",
|
||||||
"downcast-rs",
|
"downcast-rs 2.0.2",
|
||||||
"either",
|
"either",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-lite",
|
"futures-lite",
|
||||||
@@ -784,7 +814,7 @@ dependencies = [
|
|||||||
"bevy_utils",
|
"bevy_utils",
|
||||||
"bevy_window",
|
"bevy_window",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"downcast-rs",
|
"downcast-rs 2.0.2",
|
||||||
"serde",
|
"serde",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
@@ -1183,7 +1213,7 @@ dependencies = [
|
|||||||
"bevy_utils",
|
"bevy_utils",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"disqualified",
|
"disqualified",
|
||||||
"downcast-rs",
|
"downcast-rs 2.0.2",
|
||||||
"erased-serde",
|
"erased-serde",
|
||||||
"foldhash 0.2.0",
|
"foldhash 0.2.0",
|
||||||
"glam 0.30.10",
|
"glam 0.30.10",
|
||||||
@@ -1242,7 +1272,7 @@ dependencies = [
|
|||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"downcast-rs",
|
"downcast-rs 2.0.2",
|
||||||
"encase",
|
"encase",
|
||||||
"fixedbitset",
|
"fixedbitset",
|
||||||
"glam 0.30.10",
|
"glam 0.30.10",
|
||||||
@@ -1837,6 +1867,18 @@ dependencies = [
|
|||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "calloop-wayland-source"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
|
||||||
|
dependencies = [
|
||||||
|
"calloop",
|
||||||
|
"rustix 0.38.44",
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-client",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cbc"
|
name = "cbc"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -1972,6 +2014,15 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clipboard-win"
|
||||||
|
version = "5.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
|
||||||
|
dependencies = [
|
||||||
|
"error-code",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cmake"
|
name = "cmake"
|
||||||
version = "0.1.58"
|
version = "0.1.58"
|
||||||
@@ -2683,6 +2734,12 @@ version = "0.15.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "downcast-rs"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "downcast-rs"
|
name = "downcast-rs"
|
||||||
version = "2.0.2"
|
version = "2.0.2"
|
||||||
@@ -2882,6 +2939,12 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "error-code"
|
||||||
|
version = "3.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "etcetera"
|
name = "etcetera"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -4968,6 +5031,18 @@ dependencies = [
|
|||||||
"objc2-quartz-core",
|
"objc2-quartz-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-app-kit"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.1",
|
||||||
|
"objc2 0.6.4",
|
||||||
|
"objc2-core-graphics",
|
||||||
|
"objc2-foundation 0.3.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-audio-toolbox"
|
name = "objc2-audio-toolbox"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@@ -5065,6 +5140,19 @@ dependencies = [
|
|||||||
"objc2 0.6.4",
|
"objc2 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-graphics"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.1",
|
||||||
|
"dispatch2",
|
||||||
|
"objc2 0.6.4",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-io-surface",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-core-image"
|
name = "objc2-core-image"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -5121,6 +5209,17 @@ dependencies = [
|
|||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-io-surface"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.1",
|
||||||
|
"objc2 0.6.4",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-link-presentation"
|
name = "objc2-link-presentation"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -5129,7 +5228,7 @@ checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"block2 0.5.1",
|
"block2 0.5.1",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit 0.2.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5724,6 +5823,15 @@ version = "2.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-xml"
|
||||||
|
version = "0.39.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quinn"
|
name = "quinn"
|
||||||
version = "0.11.9"
|
version = "0.11.9"
|
||||||
@@ -6097,7 +6205,7 @@ dependencies = [
|
|||||||
"pico-args",
|
"pico-args",
|
||||||
"rgb",
|
"rgb",
|
||||||
"svgtypes",
|
"svgtypes",
|
||||||
"tiny-skia",
|
"tiny-skia 0.12.0",
|
||||||
"usvg",
|
"usvg",
|
||||||
"zune-jpeg",
|
"zune-jpeg",
|
||||||
]
|
]
|
||||||
@@ -6434,6 +6542,19 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sctk-adwaita"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec"
|
||||||
|
dependencies = [
|
||||||
|
"ab_glyph",
|
||||||
|
"log",
|
||||||
|
"memmap2",
|
||||||
|
"smithay-client-toolkit",
|
||||||
|
"tiny-skia 0.11.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sec1"
|
name = "sec1"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
@@ -6778,6 +6899,31 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smithay-client-toolkit"
|
||||||
|
version = "0.19.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.1",
|
||||||
|
"calloop",
|
||||||
|
"calloop-wayland-source",
|
||||||
|
"cursor-icon",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"memmap2",
|
||||||
|
"rustix 0.38.44",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-client",
|
||||||
|
"wayland-csd-frame",
|
||||||
|
"wayland-cursor",
|
||||||
|
"wayland-protocols",
|
||||||
|
"wayland-protocols-wlr",
|
||||||
|
"wayland-scanner",
|
||||||
|
"xkeysym",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smol_str"
|
name = "smol_str"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -6811,6 +6957,8 @@ dependencies = [
|
|||||||
"keyring",
|
"keyring",
|
||||||
"solitaire_data",
|
"solitaire_data",
|
||||||
"solitaire_engine",
|
"solitaire_engine",
|
||||||
|
"tiny-skia 0.12.0",
|
||||||
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6856,6 +7004,7 @@ dependencies = [
|
|||||||
name = "solitaire_engine"
|
name = "solitaire_engine"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"arboard",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bevy",
|
"bevy",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -6869,7 +7018,7 @@ dependencies = [
|
|||||||
"solitaire_sync",
|
"solitaire_sync",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tiny-skia",
|
"tiny-skia 0.12.0",
|
||||||
"tokio",
|
"tokio",
|
||||||
"usvg",
|
"usvg",
|
||||||
"uuid",
|
"uuid",
|
||||||
@@ -7456,7 +7605,7 @@ dependencies = [
|
|||||||
"crc32fast",
|
"crc32fast",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"datasketches",
|
"datasketches",
|
||||||
"downcast-rs",
|
"downcast-rs 2.0.2",
|
||||||
"fastdivide",
|
"fastdivide",
|
||||||
"fnv",
|
"fnv",
|
||||||
"fs4",
|
"fs4",
|
||||||
@@ -7508,7 +7657,7 @@ version = "0.7.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c57166f5bcfd478f370ab8445afb4678dce44801fa5ce5c451aaf8595583c5dc"
|
checksum = "c57166f5bcfd478f370ab8445afb4678dce44801fa5ce5c451aaf8595583c5dc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"downcast-rs",
|
"downcast-rs 2.0.2",
|
||||||
"fastdivide",
|
"fastdivide",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -7696,6 +7845,20 @@ dependencies = [
|
|||||||
"time-core",
|
"time-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiny-skia"
|
||||||
|
version = "0.11.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
|
||||||
|
dependencies = [
|
||||||
|
"arrayref",
|
||||||
|
"arrayvec",
|
||||||
|
"bytemuck",
|
||||||
|
"cfg-if",
|
||||||
|
"log",
|
||||||
|
"tiny-skia-path 0.11.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiny-skia"
|
name = "tiny-skia"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
@@ -7708,7 +7871,18 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
"log",
|
"log",
|
||||||
"png 0.18.1",
|
"png 0.18.1",
|
||||||
"tiny-skia-path",
|
"tiny-skia-path 0.12.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiny-skia-path"
|
||||||
|
version = "0.11.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
|
||||||
|
dependencies = [
|
||||||
|
"arrayref",
|
||||||
|
"bytemuck",
|
||||||
|
"strict-num",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -8479,7 +8653,7 @@ dependencies = [
|
|||||||
"siphasher",
|
"siphasher",
|
||||||
"strict-num",
|
"strict-num",
|
||||||
"svgtypes",
|
"svgtypes",
|
||||||
"tiny-skia-path",
|
"tiny-skia-path 0.12.0",
|
||||||
"ttf-parser",
|
"ttf-parser",
|
||||||
"unicode-bidi",
|
"unicode-bidi",
|
||||||
"unicode-script",
|
"unicode-script",
|
||||||
@@ -8685,6 +8859,114 @@ dependencies = [
|
|||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-backend"
|
||||||
|
version = "0.3.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"downcast-rs 1.2.1",
|
||||||
|
"rustix 1.1.4",
|
||||||
|
"scoped-tls",
|
||||||
|
"smallvec",
|
||||||
|
"wayland-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-client"
|
||||||
|
version = "0.31.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.1",
|
||||||
|
"rustix 1.1.4",
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-scanner",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-csd-frame"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.1",
|
||||||
|
"cursor-icon",
|
||||||
|
"wayland-backend",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-cursor"
|
||||||
|
version = "0.31.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d"
|
||||||
|
dependencies = [
|
||||||
|
"rustix 1.1.4",
|
||||||
|
"wayland-client",
|
||||||
|
"xcursor",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-protocols"
|
||||||
|
version = "0.32.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.1",
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-client",
|
||||||
|
"wayland-scanner",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-protocols-plasma"
|
||||||
|
version = "0.3.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.1",
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-client",
|
||||||
|
"wayland-protocols",
|
||||||
|
"wayland-scanner",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-protocols-wlr"
|
||||||
|
version = "0.3.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.1",
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-client",
|
||||||
|
"wayland-protocols",
|
||||||
|
"wayland-scanner",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-scanner"
|
||||||
|
version = "0.31.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quick-xml",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-sys"
|
||||||
|
version = "0.31.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
|
||||||
|
dependencies = [
|
||||||
|
"dlib",
|
||||||
|
"log",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.97"
|
version = "0.3.97"
|
||||||
@@ -9500,6 +9782,7 @@ version = "0.30.13"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d"
|
checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
"android-activity",
|
"android-activity",
|
||||||
"atomic-waker",
|
"atomic-waker",
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
@@ -9514,9 +9797,10 @@ dependencies = [
|
|||||||
"dpi",
|
"dpi",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
|
"memmap2",
|
||||||
"ndk",
|
"ndk",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit 0.2.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
"objc2-ui-kit",
|
"objc2-ui-kit",
|
||||||
"orbclient",
|
"orbclient",
|
||||||
@@ -9525,11 +9809,17 @@ dependencies = [
|
|||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"redox_syscall 0.4.1",
|
"redox_syscall 0.4.1",
|
||||||
"rustix 0.38.44",
|
"rustix 0.38.44",
|
||||||
|
"sctk-adwaita",
|
||||||
|
"smithay-client-toolkit",
|
||||||
"smol_str",
|
"smol_str",
|
||||||
"tracing",
|
"tracing",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-client",
|
||||||
|
"wayland-protocols",
|
||||||
|
"wayland-protocols-plasma",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"web-time",
|
"web-time",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
@@ -9697,6 +9987,12 @@ version = "0.13.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xcursor"
|
||||||
|
version = "0.3.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xkbcommon-dl"
|
name = "xkbcommon-dl"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ dirs = "6"
|
|||||||
keyring = "4"
|
keyring = "4"
|
||||||
keyring-core = "1"
|
keyring-core = "1"
|
||||||
reqwest = { version = "0.13", features = ["json", "rustls", "rustls-native-certs"], default-features = false }
|
reqwest = { version = "0.13", features = ["json", "rustls", "rustls-native-certs"], default-features = false }
|
||||||
|
arboard = { version = "3", default-features = false }
|
||||||
|
|
||||||
solitaire_core = { path = "solitaire_core" }
|
solitaire_core = { path = "solitaire_core" }
|
||||||
solitaire_sync = { path = "solitaire_sync" }
|
solitaire_sync = { path = "solitaire_sync" }
|
||||||
@@ -53,12 +54,24 @@ bevy = { version = "0.18", default-features = false, features = [
|
|||||||
"bevy_window",
|
"bevy_window",
|
||||||
"custom_cursor",
|
"custom_cursor",
|
||||||
"reflect_auto_register",
|
"reflect_auto_register",
|
||||||
# default_platform (desktop subset; no android/wayland/webgl/gilrs/sysinfo)
|
# default_platform (desktop subset)
|
||||||
"std",
|
"std",
|
||||||
"bevy_winit",
|
"bevy_winit",
|
||||||
"default_font",
|
"default_font",
|
||||||
"multi_threaded",
|
"multi_threaded",
|
||||||
|
# winit prefers Wayland when WAYLAND_DISPLAY is set on the
|
||||||
|
# session and falls through to X11 otherwise. Without `wayland`,
|
||||||
|
# winit-on-Wayland-session falls back to XWayland which renders
|
||||||
|
# the game in an X11 frame inside the Wayland compositor.
|
||||||
|
"wayland",
|
||||||
"x11",
|
"x11",
|
||||||
|
# Android: NativeActivity glue. The feature is target-gated inside
|
||||||
|
# bevy_internal — desktop builds compile it out, so leaving it on
|
||||||
|
# the always-on list is harmless on Linux/macOS/Windows. Pairs with
|
||||||
|
# cargo-apk's NativeActivity wrapper (cargo-apk 0.10+ uses this by
|
||||||
|
# default). Switch to `android-game-activity` later if we want
|
||||||
|
# AndroidX GameActivity for Google Play Games integration.
|
||||||
|
"android-native-activity",
|
||||||
# common_api
|
# common_api
|
||||||
"bevy_color",
|
"bevy_color",
|
||||||
"bevy_image",
|
"bevy_image",
|
||||||
|
|||||||
@@ -1,111 +1,285 @@
|
|||||||
# Solitaire Quest — Session Handoff
|
# Solitaire Quest — Session Handoff
|
||||||
|
|
||||||
**Last updated:** 2026-05-06 (post-v0.17.0) — v0.17.0 cut on top of v0.16.0 bundling the solver-driven hints (`87275bf`) and the replay-rate slider (`53e3b81`). An async-solver attempt earlier in the session was rolled back when an agent left 3 failing tests during interruption — flagged as carryover. Test-to-work ratio noted as a quality signal: future agent briefs scale back to behaviour-level tests only, not stdlib/serde-derive coverage.
|
**Last updated:** 2026-05-08 — **v0.21.1 cut and tagged at `daa655a`**,
|
||||||
|
working tree clean, all post-tag work pushed to origin.
|
||||||
|
|
||||||
|
v0.21.1 is a patch release for the post-v0.21.0 work: closes
|
||||||
|
Resume-prompt Options A (app icon — runtime `Window::icon` plus
|
||||||
|
the 9-size PNG hierarchy) and F (high-contrast + reduce-motion
|
||||||
|
accessibility modes — Settings flags wired through engine and
|
||||||
|
UI). Plus a card-visual iteration cycle that moved through three
|
||||||
|
states (v0.21.0 Terminal pink/gray → brief 4-colour-deck
|
||||||
|
experiment → traditional 2-colour Microsoft-Solitaire-on-dark-mode
|
||||||
|
red/near-white) and two visible-bug fixes (suit-coloured border
|
||||||
|
anti-aliasing artifact at rounded corners, pile-marker
|
||||||
|
bleed-through producing "gray L" shapes at occupied piles —
|
||||||
|
the latter implemented the previously-documented-but-not-enforced
|
||||||
|
"markers visible only at empty piles" invariant).
|
||||||
|
|
||||||
|
Full v0.21.1 detail lives in `CHANGELOG.md` § [0.21.1]. This
|
||||||
|
file from here on focuses on what's *open* post-cut and how to
|
||||||
|
resume.
|
||||||
|
|
||||||
## Status at pause
|
## Status at pause
|
||||||
|
|
||||||
- **HEAD on origin:** v0.17.0's tag commit.
|
- **HEAD locally:** see `git rev-parse HEAD`. The cut commit is
|
||||||
- **Working tree:** clean apart from untracked `CARD_PLAN.md` (intentional).
|
`daa655a`; any post-cut docs edits ride on top of that.
|
||||||
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings` clean.
|
- **HEAD on origin:** matches local. v0.21.1 is fully on origin.
|
||||||
- **Tests:** **1208 passed / 0 failed** across the workspace.
|
- **Working tree:** clean. No WIP outstanding.
|
||||||
- **Tags on origin:** `v0.9.0` through `v0.17.0`.
|
- **`artwork/` directory:** still untracked. Intentional.
|
||||||
|
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings`
|
||||||
|
clean.
|
||||||
|
- **Tests:** **1192 passing / 0 failing** across the workspace
|
||||||
|
(net +8 from v0.21.0's 1184 baseline). Detail in
|
||||||
|
`CHANGELOG.md` § [0.21.1] § Stats.
|
||||||
|
- **Tags on origin:** `v0.9.0` through `v0.21.1`. v0.21.1 is on
|
||||||
|
`daa655a`; v0.21.0 stays on `04f9bf9`; v0.20.0 stays on
|
||||||
|
`41a009a`.
|
||||||
|
|
||||||
## Where we are
|
## Since the v0.21.1 cut
|
||||||
|
|
||||||
v0.16.0 is the smallest meaningful release in a while — a focused round on how modals feel rather than what they contain. The originating bug was "I can't scroll on the Achievements list"; the sweep that followed found four other modals with the same problem plus three smaller modal-feel gaps (no pointer cursor on buttons, focus arriving a frame late, no click-outside-to-dismiss).
|
No threads in flight. Working tree clean as of 2026-05-08. New
|
||||||
|
work since the cut would land here as commit narratives; for
|
||||||
Every overlay screen now: scrolls if its content can overflow at 800×600, shows a hand cursor when you hover any button, has its primary auto-focused the moment the modal appears so the very first Tab/Enter is meaningful, and (for read-only screens) dismisses when you click outside the card.
|
the v0.21.1 contents themselves, see `CHANGELOG.md` § [0.21.1].
|
||||||
|
|
||||||
The post-v0.15.0 next-round candidates are still mostly open — solver-driven hints, replay-rate slider, solver progress overlay, async solver, "won previously" indicator, replay sharing. Direction is open.
|
|
||||||
|
|
||||||
### Design direction (unchanged)
|
|
||||||
|
|
||||||
- **Tone:** Balatro — chunky readable type, theatrical hierarchy, satisfying micro-interactions.
|
|
||||||
- **Palette:** Midnight Purple base + Balatro yellow primary + warm magenta secondary.
|
|
||||||
- See `~/.claude/projects/-home-manage-Rusty-Solitare/memory/project_ux_overhaul_2026-04.md` (machine-local).
|
|
||||||
|
|
||||||
### Canonical remote
|
|
||||||
|
|
||||||
`github.com/funman300/Rusty_Solitaire` is the canonical repo. Always push there.
|
|
||||||
|
|
||||||
## v0.17.0 (shipped 2026-05-06)
|
|
||||||
|
|
||||||
| Area | Commit | What landed |
|
|
||||||
|---|---|---|
|
|
||||||
| Solver-driven hints | `87275bf` | The H-key hint asks the solver for the actual best first move via `try_solve_with_first_move` / `try_solve_from_state`. Heuristic stays as fallback. Median 2 ms per H press. |
|
|
||||||
| Replay-rate slider | `53e3b81` | Settings → Gameplay slider tunes `replay_move_interval_secs` 0.10–1.00 s in 0.05 s steps; default 0.45 s. Read per frame from `SettingsResource`. |
|
|
||||||
|
|
||||||
## v0.16.0 (shipped 2026-05-06)
|
|
||||||
|
|
||||||
| Area | Commit | What landed |
|
|
||||||
|---|---|---|
|
|
||||||
| Modal scroll | `7a3032b` | Achievements / Help / Stats / Profile / Leaderboard bodies now carry `Overflow::scroll_y()` + a `max_height` constraint + a per-plugin `*Scrollable` marker. Sibling `scroll_*_panel` systems route `MouseWheel` into the body's `ScrollPosition`. Mirrors the existing `SettingsPanelScrollable` pattern. Home modal not scrolled — five mode cards + Cancel are sized to fit by design. |
|
|
||||||
| Pointer cursor | `cd54ce1` | `update_cursor_icon` gains a fourth branch: `SystemCursorIcon::Pointer` whenever any `Interaction::Hovered`/`Pressed` button is detected and no card drag is active. Branch order Grabbing → Pointer → Grab → Default. Pure `pick_cursor_icon(is_dragging, any_button_hovered, any_card_hovered)` helper unit-tests the priority. |
|
|
||||||
| Same-frame focus | `48e4121` | `attach_focusable_to_modal_buttons` and `auto_focus_on_modal_open` moved from `Update` to `PostUpdate`. The schedule boundary supplies the sync point so a click-handler in `Update` that spawns a modal has its `Commands` materialised before attach runs. `FocusedButton` is populated before `app.update()` returns; the very first Tab/Enter after open lands on a populated resource. |
|
|
||||||
| Scrim dismiss core | `a54201e` | New `ScrimDismissible` marker on `ModalScrim` opts a modal into click-outside-to-close. `dismiss_modal_on_scrim_click` system in `ui_modal` despawns the topmost dismissible scrim on a left-mouse press whose cursor lands on the scrim and outside every `ModalCard`. Stats / Achievements / Help opted in. |
|
|
||||||
| Scrim dismiss tail | `cbf2483` | One-line opt-in (capture scrim + insert marker) for Profile / Leaderboard / Home, completing all six read-only modals. |
|
|
||||||
|
|
||||||
## Open punch list
|
## Open punch list
|
||||||
|
|
||||||
### Release prep
|
### Phase Android (build + persistence shipped; runtime gaps remain)
|
||||||
|
|
||||||
1. **Desktop packaging** per `ARCHITECTURE.md §17`. Arch PKGBUILD exists in `/home/manage/solitaire-quest-pkgbuild/` (separate repo). Pending: app icon, macOS `.icns` + notarisation cert, Windows `.ico` + Authenticode cert, AppImage recipe.
|
- **APK launch verification on AVD / device.** `adb install` then
|
||||||
|
`adb logcat` against the `bevy_test` AVD or an x86_64 device.
|
||||||
|
The build works and persistence is wired, but no end-to-end
|
||||||
|
device run has been logged. Shakes out runtime bugs the build +
|
||||||
|
unit tests can't catch.
|
||||||
|
- **JNI ClipboardManager bridge.** Replaces the Android stub for
|
||||||
|
the Stats "Copy share link" toast. `arboard` doesn't ship an
|
||||||
|
Android backend; small custom JNI call.
|
||||||
|
- **Android Keystore for credentials.** `keyring` is target-gated
|
||||||
|
to a stub returning `KeychainUnavailable`; replace with Android
|
||||||
|
Keystore via JNI when sync auth ships on mobile.
|
||||||
|
- **Google Play Games (gpgs) integration.** Listed as a
|
||||||
|
Phase-Android target since Phase 1; now unblocked by the build
|
||||||
|
target.
|
||||||
|
- **Cosmetic `cargo apk build --lib` workaround.** Post-sign
|
||||||
|
panic doesn't affect the APK on disk but produces noisy stderr.
|
||||||
|
Either upstream a cargo-apk fix or document `--lib` as
|
||||||
|
canonical in the runbook.
|
||||||
|
|
||||||
### Process note (raised this session)
|
### Visual-identity follow-ups (post-v0.21.0)
|
||||||
|
|
||||||
Recent agent briefs reflexively asked for ≥3 tests per feature, which produced low-value coverage on trivial settings fields (default-value tests, serde-derive round-trips, clamp tests that just exercise stdlib `clamp`). Future agent briefs should ask only for tests that pin **behaviour contracts or regressions on real bugs** — not coverage of language/library mechanics.
|
The visual-identity arc is effectively complete: token system,
|
||||||
|
chrome migration, splash boot screen, replay-overlay banner,
|
||||||
|
card-face artwork (both rendering paths), and the `ACCENT_PRIMARY`
|
||||||
|
palette refresh all shipped in v0.20.0 + v0.21.0. What stays open:
|
||||||
|
|
||||||
### Carryover candidates — still open
|
- **Replay-overlay screen-takeover redesign.** The full mockup
|
||||||
|
(`docs/ui-mockups/replay-overlay-mobile.html`) calls for a
|
||||||
|
mini-tableau preview, playback controls, move-log scroll, and
|
||||||
|
a WIN MOVE marker on the scrub bar. Banner-local pieces all
|
||||||
|
shipped in v0.21.0 (`c84d9f4` + `6204db8` + `54005d5` +
|
||||||
|
`e080b49`); the screen-takeover is a multi-session redesign
|
||||||
|
with data-layer impact (move-log scroller; WIN MOVE needs a
|
||||||
|
`win_move_index` field on `Replay` that doesn't yet exist).
|
||||||
|
- **Floating `MOVE N/M` chip above the focused card during
|
||||||
|
playback.** Cross-plugin work — `update_progress_text` writes
|
||||||
|
the banner chip but the card-position lookup belongs in
|
||||||
|
`card_plugin`. Smaller scope than the screen-takeover.
|
||||||
|
- **Toast Warning / Error variants.** `ToastVariant` has slots
|
||||||
|
for `Warning` (gold) and `Error` (pink) but no in-engine
|
||||||
|
event uses them yet. Wire when a warning- or error-flavoured
|
||||||
|
toast event materialises.
|
||||||
|
- *High-contrast accessibility mode — closed 2026-05-08 by
|
||||||
|
`c5787c6` + `07e0357`.* Card text rendering picks up
|
||||||
|
`TEXT_PRIMARY_HC` (`#f5f5f5`) and `RED_SUIT_COLOUR_HC`
|
||||||
|
(`#ff8aa0`); Settings panel has a toggle. Future scope:
|
||||||
|
extend HC through chrome borders (`BORDER_SUBTLE_HC` already
|
||||||
|
defined, not yet consumed), buttons, popover edges.
|
||||||
|
- *Reduced-motion mode — closed 2026-05-08 by the same pair.*
|
||||||
|
`effective_slide_secs` forces 0 when on, regardless of the
|
||||||
|
`AnimSpeed` setting. Future scope: gate splash scanline
|
||||||
|
overlay + cursor pulse animation on the same flag, gate
|
||||||
|
warning-chip pulse, gate any future card-lift z-bump
|
||||||
|
animation.
|
||||||
|
|
||||||
- **Solver-on-AsyncComputeTaskPool** — current solver runs synchronously on the main thread. Worst-case 50 attempts × 120 ms = 6 s of UI stall on pathological seeds. **An attempt this session was rolled back** when an agent was interrupted leaving 3 failing tests; redoing this needs more careful scoping (smaller pieces, real cancel-and-test flow, NOT a parallel agent split). Worth taking next.
|
### Carried forward from v0.19.0
|
||||||
- **Per-deal "won previously" indicator** — the rolling replay history's seeds make this easy: when a new game starts on a seed the player has already won, surface a tiny indicator on the HUD.
|
|
||||||
- **Replay sharing** — `replays.json` is per-machine. Allow a player to copy a replay's URL (already wired via `solitaire_server`) and post it elsewhere. The web-viewer already exists.
|
- *App icon round — closed 2026-05-08 by `3eb3a26` + `716a025`.*
|
||||||
|
Runtime `Window::icon` wired (Linux/macOS/Windows); 9-size
|
||||||
|
PNG hierarchy at `assets/icon/icon_<size>.png` covers Linux
|
||||||
|
hicolor + downstream `.icns`/`.ico` packaging needs. The
|
||||||
|
`.ico` and `.icns` bundle-format files themselves are *not*
|
||||||
|
generated — both would need new crate deps (`ico` and
|
||||||
|
`icns` respectively) and only matter at app-bundle time
|
||||||
|
(cargo-bundle / packaging), not at `cargo run`. Open if the
|
||||||
|
project later ships as a packaged macOS / Windows app.
|
||||||
|
|
||||||
|
### Other small candidates
|
||||||
|
|
||||||
|
- **Prev/Next selector chips spawn site.** v0.19.0's `9b065e5`
|
||||||
|
noted Prev/Next markers exist in `stats_plugin` but no spawn
|
||||||
|
site renders them today — the Shareable badge therefore lands
|
||||||
|
on the single-replay caption. If/when Prev/Next is plumbed,
|
||||||
|
the badge will need to follow.
|
||||||
|
- **Toast queue / immediate unification.** The two toast paths
|
||||||
|
(`spawn_queued_toast` for `InfoToastEvent` queue; `spawn_toast`
|
||||||
|
for fire-and-forget) now share visual treatment but remain
|
||||||
|
separate functions because they serve different temporal
|
||||||
|
needs (sequential vs. parallel). If overlap becomes a UX
|
||||||
|
issue, merge into one queue with priority lanes.
|
||||||
|
|
||||||
|
### Process notes
|
||||||
|
|
||||||
|
- **The desktop-adaptation spec is the canonical reference for
|
||||||
|
geometry decisions** when porting any future plugin. Read
|
||||||
|
`docs/ui-mockups/desktop-adaptation.md` first; apply the
|
||||||
|
universal rules to every surface; consult the per-screen
|
||||||
|
table for the priority surfaces. The 9 missing-plugin screens
|
||||||
|
(splash now ported; eight remaining) inherit the universal
|
||||||
|
rules without dedicated guidance.
|
||||||
|
- **Stitch `generate_variants` is unreliable for layout-only
|
||||||
|
adaptation prompts** as of 2026-05-07. The first call timed
|
||||||
|
out and no variant ever landed in `list_screens`. If a future
|
||||||
|
session wants visual desktop mockups, prefer
|
||||||
|
`generate_screen_from_text` with a fresh narrow prompt per
|
||||||
|
screen rather than `generate_variants` against existing
|
||||||
|
mobile screens.
|
||||||
|
- **Token-port pattern.** v0.20.0's chrome-migration commits
|
||||||
|
set a reusable shape for "centralised design system applied
|
||||||
|
across N plugins":
|
||||||
|
1. Constants module (`ui_theme.rs`) is the source of truth.
|
||||||
|
2. Const sites that can't call `Alpha::with_alpha` (not yet
|
||||||
|
`const` on stable) use a literal RGB matching the token,
|
||||||
|
with a unit test pinning the RGB to the token (e.g.
|
||||||
|
`MARKER_VALID`, `HINT_PILE_HIGHLIGHT_COLOUR`,
|
||||||
|
`RIGHT_CLICK_HIGHLIGHT_COLOUR`).
|
||||||
|
3. Cross-plugin duplication (e.g. `MARKER_DEFAULT` ↔
|
||||||
|
`PILE_MARKER_DEFAULT_COLOUR`) collapses to a single
|
||||||
|
promoted const re-exported from one plugin and imported
|
||||||
|
by the other — replaces "kept in sync" doc comments with a
|
||||||
|
compile-time invariant.
|
||||||
|
4. Domain colours (suit pips, card faces, lerp helpers) stay
|
||||||
|
as literals with a comment naming the rationale; only UI
|
||||||
|
chrome routes through tokens.
|
||||||
|
- **`SplashFadable` scaffolding pattern** (introduced in
|
||||||
|
`cacb19c`). Any future overlay that needs to fade `N >> 3`
|
||||||
|
elements together should follow the same shape: one tiny
|
||||||
|
marker carrying the full-alpha base colour, one global query
|
||||||
|
that lerps every marker's alpha each frame, no per-element
|
||||||
|
query plumbing. Cleanly outscales the `Without<X>, Without<Y>`
|
||||||
|
query exclusion pattern that the old splash was hitting at
|
||||||
|
three siblings.
|
||||||
|
|
||||||
|
### Canonical remote
|
||||||
|
|
||||||
|
`github.com/funman300/Rusty_Solitaire` is the canonical repo.
|
||||||
|
Always push there. As of v0.21.0 origin matches local; the next
|
||||||
|
push happens when post-cut work accumulates and is ready to roll
|
||||||
|
into a v0.21.1 / v0.22.0 cut.
|
||||||
|
|
||||||
|
### Design direction (Terminal — base16-eighties)
|
||||||
|
|
||||||
|
- **Tone:** retro-terminal / synthwave — flat depth (no box-shadows),
|
||||||
|
monospaced-forward typography (JetBrains Mono / FiraMono), tight
|
||||||
|
16 px edge margins, 8 px card radius.
|
||||||
|
- **Palette:** near-black surface ramp (`#151515` / `#202020` /
|
||||||
|
`#2a2a2a` / `#353535`), brick-red primary CTA (`#a54242` —
|
||||||
|
swapped from cyan `#6fc2ef` in v0.21.0 commit `a292a7e`), lime
|
||||||
|
success (`#acc267`), gold warning (`#ddb26f`), pink error /
|
||||||
|
suit-red (`#fb9fb1`), lavender celebration (`#e1a3ee`), teal
|
||||||
|
info (`#12cfc0`).
|
||||||
|
- **Two-color suits.** Red = `#fb9fb1`, black = `#d0d0d0`.
|
||||||
|
Outlined glyphs for diamonds & clubs are *always on*; the
|
||||||
|
Settings "color-blind mode" toggle swaps red → lime `#acc267`
|
||||||
|
(was red → cyan pre-v0.21.0; lime is the next-best non-red
|
||||||
|
base16-eighties accent now that the primary itself is red).
|
||||||
|
- **Card glyphs render upright in both corners** — no 180°
|
||||||
|
inverted-corner-indicator rotation. Single-orientation
|
||||||
|
digital play doesn't benefit from the traditional flip-
|
||||||
|
readback convention. `design-system.md` § Game Cards
|
||||||
|
documents this deliberate deviation.
|
||||||
|
|
||||||
## Resume prompt
|
## Resume prompt
|
||||||
|
|
||||||
```
|
```
|
||||||
You are a senior Rust + Bevy developer working on Solitaire Quest.
|
You are a senior Rust + Bevy developer working on Solitaire Quest.
|
||||||
Working directory: <Rusty_Solitaire clone path on this machine — local
|
Working directory: <Rusty_Solitaire clone path on this machine>.
|
||||||
directory may still be named Rusty_Solitare from earlier; that's fine>.
|
Branch: master. v0.21.1 is tagged at daa655a (cut 2026-05-08, a
|
||||||
Branch: master. Direction is OPEN — v0.16.0 just shipped covering
|
patch release rolling up app-icon, accessibility modes, and the
|
||||||
modal scroll fixes, pointer cursor, same-frame focus, and scrim-click
|
card-visual iteration cycle that closed Resume-prompt Options A
|
||||||
dismiss across all six read-only modals.
|
and F). v0.21.0 stays at 04f9bf9. Working tree clean. See
|
||||||
|
CHANGELOG.md § [0.21.1] for full detail of what shipped in the
|
||||||
|
patch release.
|
||||||
|
|
||||||
State: HEAD at v0.17.0 (solver hints + replay-rate slider on top
|
State: HEAD locally — see `git rev-parse HEAD`. All workspace tests
|
||||||
of v0.16.0). Working tree clean apart from untracked CARD_PLAN.md
|
pass (1192+; check with `cargo test --workspace`), clippy clean.
|
||||||
(intentional).
|
|
||||||
Build: cargo clippy --workspace --all-targets -- -D warnings clean.
|
|
||||||
Tests: 1208 passed / 0 failed.
|
|
||||||
|
|
||||||
READ FIRST (in order, before doing anything):
|
READ FIRST (in order, before doing anything):
|
||||||
1. SESSION_HANDOFF.md — v0.16.0 changelog + open punch list
|
1. SESSION_HANDOFF.md — this file
|
||||||
2. CHANGELOG.md — release-by-release record
|
2. CHANGELOG.md — [0.21.1] section is the most recent cut
|
||||||
3. CLAUDE.md — hard rules (UI-first, no panics, etc.)
|
3. CLAUDE.md — unified-3.0 rule set
|
||||||
4. ARCHITECTURE.md — crate responsibilities + data flow
|
4. CLAUDE_SPEC.md — formal architecture spec
|
||||||
5. ~/.claude/projects/<this-project>/memory/MEMORY.md
|
5. ARCHITECTURE.md — crate responsibilities + data flow
|
||||||
— saved feedback / project context (machine-local;
|
6. docs/ui-mockups/ — design system + 24-mockup library +
|
||||||
may be missing on a fresh machine)
|
desktop-adaptation.md (the rules-based
|
||||||
|
companion to the mockups; read this
|
||||||
|
before any plugin port)
|
||||||
|
7. docs/android/* — Android setup + build runbook
|
||||||
|
8. ~/.claude/projects/<this-project>/memory/MEMORY.md
|
||||||
|
— saved feedback / project context
|
||||||
|
(machine-local; may be missing on a
|
||||||
|
fresh machine)
|
||||||
|
|
||||||
DECISION TO ASK THE PLAYER FIRST:
|
DECISION TO ASK THE PLAYER FIRST:
|
||||||
A. Solver-on-AsyncComputeTaskPool with progress toast + cancel.
|
A. APK launch verification on AVD / device — `adb install` +
|
||||||
A previous attempt was rolled back when an agent left 3
|
`adb logcat` to shake out runtime bugs the build / unit
|
||||||
failing tests; redoing it needs smaller pieces. Eliminates the
|
tests can't catch. Likely surfaces JNI ClipboardManager
|
||||||
worst-case 6 s UI stall — highest gameplay impact left.
|
and Android Keystore stubs that need real bridges. Larger
|
||||||
B. Per-deal "won previously" HUD indicator using the rolling
|
scope; needs an Android device or emulator running.
|
||||||
replay history's seeds.
|
(Was Resume-prompt B before the post-v0.21.1 menu trim.)
|
||||||
C. Replay sharing — copyable URL via the existing web viewer.
|
B. Replay-overlay extensions — either the floating `MOVE N/M`
|
||||||
D. Take the deferred desktop-packaging item (needs artwork +
|
chip above the focused card (smaller, cross-plugin; needs
|
||||||
signing certs from the user).
|
cursor → card-position plumbing in `card_plugin`) or the
|
||||||
|
full screen-takeover redesign (multi-session: move-log
|
||||||
|
scroll, mini tableau preview, WIN MOVE marker, data-layer
|
||||||
|
impact for `Replay::win_move_index`).
|
||||||
|
C. Toast Warning / Error variant wiring. UI infrastructure
|
||||||
|
exists in `ToastVariant`; no in-engine event uses Warning
|
||||||
|
(gold) or Error (pink) yet. Wire when a real warning- or
|
||||||
|
error-flavoured event materialises.
|
||||||
|
D. Phase 8 (sync) — local storage scaffolding, self-hosted
|
||||||
|
Axum server, `SolitaireServerClient` impl, GPGS stub
|
||||||
|
wired into Settings. The biggest open arc by scope; rolls
|
||||||
|
up several Phase Android dependencies (Keystore,
|
||||||
|
ClipboardManager).
|
||||||
|
E. Extend high-contrast through chrome — `BORDER_SUBTLE_HC`
|
||||||
|
was defined in v0.21.1 but isn't yet consumed; popover
|
||||||
|
edges, button borders, focus rings still use the default
|
||||||
|
non-HC tokens. Plus reduce-motion still doesn't gate
|
||||||
|
splash scanline / cursor pulse / warning-chip pulse —
|
||||||
|
v0.21.1 only gated card slide_secs. Both are small,
|
||||||
|
finite, half-day scope.
|
||||||
|
|
||||||
WORKFLOW NOTES:
|
WORKFLOW NOTES:
|
||||||
- Commits use:
|
- Use the system git config (already correct).
|
||||||
git -c user.name=funman300 -c user.email=root@vscode.infinity \
|
- When attributing playtester feedback in commits/docs, use
|
||||||
commit -m "..."
|
"Quat" not "Rhys" (saved feedback memory).
|
||||||
- When attributing playtester feedback in commits/docs, use "Quat"
|
|
||||||
not "Rhys" (saved feedback memory).
|
|
||||||
- Sub-agents stage + verify only; orchestrator commits.
|
- Sub-agents stage + verify only; orchestrator commits.
|
||||||
- Every commit must pass build / clippy / test before pushing.
|
- Every commit must pass build / clippy / test before pushing.
|
||||||
- Push to GitHub (origin) — that is the canonical remote.
|
- Push to GitHub (origin) — gh auth setup-git wired on
|
||||||
|
primary dev box; verify on laptop before first push.
|
||||||
|
- Token-port pattern: when migrating tokens, walk every
|
||||||
|
concrete artifact downstream of the token (PNG textures,
|
||||||
|
embedded SVGs, hardcoded literals, comment color names),
|
||||||
|
not just the token name. v0.21.0 surfaced three "the
|
||||||
|
migration walked past this" follow-ups that all matched
|
||||||
|
this shape — codified here so future similar work can
|
||||||
|
pattern-match instead of rediscovering.
|
||||||
|
- Doc-vs-implementation drift pattern: v0.21.1's pile-marker
|
||||||
|
visibility fix (`4d48cad`) implemented an invariant that
|
||||||
|
had been declared in a module doc comment but was never
|
||||||
|
enforced in code. When future work touches a module with
|
||||||
|
a "this does X" doc comment, verify the code actually does
|
||||||
|
X and add a test if not. Two layers, two checks.
|
||||||
|
|
||||||
OPEN AT THE START: ask which of A–D. Don't pick unilaterally.
|
OPEN AT THE START: ask which of A–E. Don't pick unilaterally.
|
||||||
```
|
```
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 469 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 469 B |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 469 B |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 472 B |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 472 B |
|
Before Width: | Height: | Size: 2.3 MiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 283 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 300 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 357 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 300 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 318 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 365 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 179 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 256 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 263 B |
|
After Width: | Height: | Size: 369 B |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 489 B |
|
After Width: | Height: | Size: 759 B |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 927 B |
@@ -0,0 +1,228 @@
|
|||||||
|
# Android build — developer setup
|
||||||
|
|
||||||
|
This doc captures the toolchain install + build invocation for the
|
||||||
|
Android target. Steps are runnable on a fresh Debian 13 (trixie) box;
|
||||||
|
later sections document what's known to compile, what's stubbed, and
|
||||||
|
the next milestones.
|
||||||
|
|
||||||
|
> **Status (2026-05-07):** First working APK at `fb8b2ac`. 54 MB
|
||||||
|
> debug-signed `solitaire-quest.apk` for `x86_64-linux-android`. Has
|
||||||
|
> NOT yet been verified to launch on a device or emulator — that's
|
||||||
|
> the next milestone.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Toolchain install (Debian 13 / trixie)
|
||||||
|
|
||||||
|
Run as one block. Will pull ~15-20 GB of disk between APT, the SDK,
|
||||||
|
the NDK, the system image, and Rust target sysroots. Requires sudo.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. JDK 21 (Android tooling needs JDK 17+; Debian 13 default is 21).
|
||||||
|
sudo apt update && sudo apt install -y openjdk-21-jdk-headless unzip wget
|
||||||
|
|
||||||
|
# 2. SDK directory + Google's cmdline-tools bootstrap.
|
||||||
|
export ANDROID_HOME="$HOME/Android/Sdk"
|
||||||
|
mkdir -p "$ANDROID_HOME/cmdline-tools"
|
||||||
|
wget -O /tmp/cmdline-tools.zip \
|
||||||
|
https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip
|
||||||
|
unzip -q /tmp/cmdline-tools.zip -d "$ANDROID_HOME/cmdline-tools"
|
||||||
|
mv "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/latest"
|
||||||
|
rm /tmp/cmdline-tools.zip
|
||||||
|
|
||||||
|
# 3. Persist env vars.
|
||||||
|
{
|
||||||
|
echo ''
|
||||||
|
echo '# Android dev'
|
||||||
|
echo 'export ANDROID_HOME="$HOME/Android/Sdk"'
|
||||||
|
echo 'export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/26.3.11579264"'
|
||||||
|
echo 'export JAVA_HOME="$(dirname $(dirname $(readlink -f $(which java))))"'
|
||||||
|
echo 'export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator"'
|
||||||
|
} >> ~/.bashrc
|
||||||
|
source ~/.bashrc
|
||||||
|
|
||||||
|
# 4. Accept SDK licences (interactive prompts answered by `yes |`).
|
||||||
|
yes | sdkmanager --licenses
|
||||||
|
|
||||||
|
# 5. Platform packages — ~5 GB.
|
||||||
|
sdkmanager \
|
||||||
|
"platform-tools" \
|
||||||
|
"platforms;android-34" \
|
||||||
|
"build-tools;34.0.0" \
|
||||||
|
"ndk;26.3.11579264" \
|
||||||
|
"emulator" \
|
||||||
|
"system-images;android-34;google_apis;x86_64"
|
||||||
|
|
||||||
|
# 6. AVD for testing (one-time).
|
||||||
|
echo no | avdmanager create avd \
|
||||||
|
-n bevy_test \
|
||||||
|
-k "system-images;android-34;google_apis;x86_64" \
|
||||||
|
-d pixel_7
|
||||||
|
|
||||||
|
# 7. Rust cross-compile targets.
|
||||||
|
rustup target add \
|
||||||
|
aarch64-linux-android \
|
||||||
|
armv7-linux-androideabi \
|
||||||
|
x86_64-linux-android \
|
||||||
|
i686-linux-android
|
||||||
|
|
||||||
|
# 8. cargo-apk.
|
||||||
|
cargo install cargo-apk
|
||||||
|
```
|
||||||
|
|
||||||
|
Sanity:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
java --version | head -1 # openjdk 21.0.x
|
||||||
|
adb --version | head -1 # 35.x or higher
|
||||||
|
sdkmanager --list_installed | head # build-tools, emulator, ndk, platforms, system-images
|
||||||
|
avdmanager list avd | head # bevy_test
|
||||||
|
rustup target list --installed | grep android # 4 targets
|
||||||
|
cargo apk --help | head -5
|
||||||
|
```
|
||||||
|
|
||||||
|
If `sdkmanager --version` errors with `JAVA_HOME is not set`, the env
|
||||||
|
section in step 3 didn't apply to your shell — `source ~/.bashrc`
|
||||||
|
again or open a new terminal.
|
||||||
|
|
||||||
|
### Optional: emulator runtime libs
|
||||||
|
|
||||||
|
The Android emulator is dynamically linked against X11/GL/audio. If
|
||||||
|
`emulator -list-avds` works but `emulator -avd bevy_test` complains
|
||||||
|
about `libX11.so.6`, install:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install -y \
|
||||||
|
libx11-6 libxcursor1 libxrandr2 libxi6 libxinerama1 libxxf86vm1 \
|
||||||
|
libgl1 libnss3 libpulse0 libxcomposite1
|
||||||
|
```
|
||||||
|
|
||||||
|
Headless emulator launch:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
emulator -avd bevy_test -no-window -gpu swiftshader_indirect &
|
||||||
|
adb wait-for-device && adb devices
|
||||||
|
# Stop later:
|
||||||
|
# adb -s emulator-5554 emu kill
|
||||||
|
```
|
||||||
|
|
||||||
|
Headless + software rendering is fine for "does it boot" smoke tests
|
||||||
|
but useless for perf measurement — use a physical Pixel-class device
|
||||||
|
over USB for real numbers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Build the APK
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo apk build -p solitaire_app --target x86_64-linux-android
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
target/debug/apk/solitaire-quest.apk
|
||||||
|
```
|
||||||
|
|
||||||
|
Targets shipped via `[package.metadata.android].build_targets` in
|
||||||
|
`solitaire_app/Cargo.toml`:
|
||||||
|
|
||||||
|
| Target | Use |
|
||||||
|
|--------|-----|
|
||||||
|
| `aarch64-linux-android` | Real phones (modern 64-bit ARM) |
|
||||||
|
| `armv7-linux-androideabi` | Older 32-bit ARM phones |
|
||||||
|
| `x86_64-linux-android` | The `bevy_test` AVD on this dev box |
|
||||||
|
|
||||||
|
Build any of them with `--target <triple>`.
|
||||||
|
|
||||||
|
### Known cosmetic warning
|
||||||
|
|
||||||
|
After the APK is signed cargo-apk panics with:
|
||||||
|
|
||||||
|
```
|
||||||
|
thread 'main' panicked: Bin is not compatible with Cdylib
|
||||||
|
```
|
||||||
|
|
||||||
|
This happens AFTER the APK is on disk and signed. cargo-apk is
|
||||||
|
trying to also wrap the desktop `[[bin]]` target. The APK is still
|
||||||
|
valid. Work around with `--lib`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo apk build -p solitaire_app --target x86_64-linux-android --lib
|
||||||
|
```
|
||||||
|
|
||||||
|
(Permanent fix to come — likely a `[[bin]] required-features = ["desktop"]`
|
||||||
|
gate so cargo-apk skips the bin target on Android.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Install + run
|
||||||
|
|
||||||
|
Physical device:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb devices # confirm connection
|
||||||
|
adb install target/debug/apk/solitaire-quest.apk
|
||||||
|
adb shell am start -n com.solitairequest.app/android.app.NativeActivity
|
||||||
|
adb logcat | grep -iE "RustStdoutStderr|solitaire|panic"
|
||||||
|
```
|
||||||
|
|
||||||
|
Emulator:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
emulator -avd bevy_test -no-window -gpu swiftshader_indirect &
|
||||||
|
adb wait-for-device
|
||||||
|
adb install target/debug/apk/solitaire-quest.apk
|
||||||
|
# ... same start + logcat steps as above.
|
||||||
|
```
|
||||||
|
|
||||||
|
If `adb install` errors with `INSTALL_FAILED_NO_MATCHING_ABIS`, the
|
||||||
|
emulator is x86_64 but the APK was built for arm — rebuild with the
|
||||||
|
`x86_64-linux-android` target, or add an x86_64 system image to the
|
||||||
|
AVD.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. What's wired vs. what's stubbed
|
||||||
|
|
||||||
|
The first build pass (commit `fb8b2ac`) gates four desktop-only
|
||||||
|
crates / call sites so the workspace cross-compiles. Each gate is
|
||||||
|
documented at its call site.
|
||||||
|
|
||||||
|
| Surface | Desktop | Android |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| Bevy windowing | x11 + wayland | `android-native-activity` (NativeActivity glue) |
|
||||||
|
| Clipboard ("Copy share link") | `arboard` writes URL | Toast surfaces the URL inline |
|
||||||
|
| OS keychain (JWT tokens) | `keyring` v4 → Secret Service / Keychain / Credential Store | Stub returning `KeychainUnavailable`; sync requires fresh login each launch |
|
||||||
|
| App entry point | `bin` target → `solitaire_app::run()` | `cdylib` target loaded by NativeActivity |
|
||||||
|
|
||||||
|
What's NOT yet ported / not yet measured:
|
||||||
|
|
||||||
|
- `dirs::data_dir()` returns `None` on Android. Callers in
|
||||||
|
`solitaire_data/src/storage.rs`, `progress.rs`, `replay.rs`,
|
||||||
|
`achievements.rs`, `settings.rs` all need an Android-aware
|
||||||
|
helper (likely `/data/data/com.solitairequest.app/files`).
|
||||||
|
- Touch UX pass — hit-target sizes, modal scaling on small screens,
|
||||||
|
app lifecycle (suspend / resume), font scaling.
|
||||||
|
- Android Keystore via JNI for `auth_tokens`.
|
||||||
|
- JNI ClipboardManager for share links.
|
||||||
|
- Google Play Games sign-in (the `solitaire_gpgs` crate referenced
|
||||||
|
in older docs doesn't yet exist).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Iteration loop
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Edit code…
|
||||||
|
cargo build -p solitaire_app # desktop sanity
|
||||||
|
cargo clippy --workspace --all-targets -- -D warnings # gate
|
||||||
|
cargo test --workspace # gate
|
||||||
|
cargo apk build -p solitaire_app --target x86_64-linux-android --lib
|
||||||
|
adb install -r target/debug/apk/solitaire-quest.apk # `-r` reinstalls
|
||||||
|
adb logcat -c && adb shell am start -n com.solitairequest.app/android.app.NativeActivity
|
||||||
|
adb logcat | grep -iE "RustStdoutStderr|solitaire"
|
||||||
|
```
|
||||||
|
|
||||||
|
`adb logcat` is the canonical way to see Bevy / Rust panic output —
|
||||||
|
they end up in the `RustStdoutStderr` tag.
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html class="dark" lang="en"><head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
|
<title>Rusty Solitaire - Achievements</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700;800&family=Inter:wght@400;500;700&family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<script id="tailwind-config">
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: "class",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
"colors": {
|
||||||
|
"tertiary-fixed": "#fbd7ff",
|
||||||
|
"on-tertiary-container": "#683476",
|
||||||
|
"surface-container-lowest": "#0b0f11",
|
||||||
|
"error": "#fb9fb1",
|
||||||
|
"secondary-fixed-dim": "#bad073",
|
||||||
|
"on-primary-fixed-variant": "#004c69",
|
||||||
|
"background": "#101417",
|
||||||
|
"error-container": "#93000a",
|
||||||
|
"tertiary-container": "#e1a3ee",
|
||||||
|
"inverse-primary": "#00668a",
|
||||||
|
"highlight-valid": "#acc267",
|
||||||
|
"suit-red": "#fb9fb1",
|
||||||
|
"on-surface-variant": "#bfc8cf",
|
||||||
|
"on-secondary": "#293500",
|
||||||
|
"on-primary-container": "#004f6c",
|
||||||
|
"surface-tint": "#7ed0fe",
|
||||||
|
"on-surface": "#e0e3e6",
|
||||||
|
"outline-variant": "#3f484e",
|
||||||
|
"on-background": "#e0e3e6",
|
||||||
|
"primary-fixed": "#c4e7ff",
|
||||||
|
"inverse-surface": "#e0e3e6",
|
||||||
|
"info": "#12cfc0",
|
||||||
|
"inverse-on-surface": "#2d3134",
|
||||||
|
"warning": "#ddb26f",
|
||||||
|
"on-tertiary-fixed-variant": "#653173",
|
||||||
|
"on-secondary-container": "#b2c86d",
|
||||||
|
"on-secondary-fixed-variant": "#3c4d00",
|
||||||
|
"highlight-celebration": "#e1a3ee",
|
||||||
|
"surface": "#151515",
|
||||||
|
"surface-container-highest": "#313538",
|
||||||
|
"outline": "#505050",
|
||||||
|
"on-primary": "#003549",
|
||||||
|
"on-error-container": "#ffdad6",
|
||||||
|
"surface-variant": "#313538",
|
||||||
|
"on-error": "#690005",
|
||||||
|
"suit-black": "#d0d0d0",
|
||||||
|
"primary": "#a1dcff",
|
||||||
|
"suit-red-cb": "#6fc2ef",
|
||||||
|
"surface-bright": "#363a3d",
|
||||||
|
"on-tertiary": "#4c195b",
|
||||||
|
"surface-dim": "#101417",
|
||||||
|
"primary-container": "#6fc2ef",
|
||||||
|
"tertiary": "#f7c3ff",
|
||||||
|
"primary-fixed-dim": "#7ed0fe",
|
||||||
|
"surface-container-high": "#272a2d",
|
||||||
|
"on-secondary-fixed": "#161e00",
|
||||||
|
"surface-container": "#1c2023",
|
||||||
|
"tertiary-fixed-dim": "#f0b0fc",
|
||||||
|
"secondary-fixed": "#d5ec8c",
|
||||||
|
"secondary-container": "#435401",
|
||||||
|
"on-tertiary-fixed": "#340043",
|
||||||
|
"on-primary-fixed": "#001e2c",
|
||||||
|
"secondary": "#bad073",
|
||||||
|
"surface-container-low": "#181c1f"
|
||||||
|
},
|
||||||
|
"borderRadius": {
|
||||||
|
"DEFAULT": "0.125rem",
|
||||||
|
"lg": "0.25rem",
|
||||||
|
"xl": "0.5rem",
|
||||||
|
"full": "0.75rem"
|
||||||
|
},
|
||||||
|
"spacing": {
|
||||||
|
"stack-overlap": "2rem",
|
||||||
|
"gutter-card": "0.375rem",
|
||||||
|
"touch-target-min": "48dp",
|
||||||
|
"margin-edge": "1rem",
|
||||||
|
"action-bar-height": "64px"
|
||||||
|
},
|
||||||
|
"fontFamily": {
|
||||||
|
"card-rank": ["JetBrains Mono"],
|
||||||
|
"body-md": ["Inter"],
|
||||||
|
"label-caps": ["JetBrains Mono"],
|
||||||
|
"hud-score": ["JetBrains Mono"],
|
||||||
|
"headline": ["JetBrains Mono"],
|
||||||
|
"hud-timer": ["JetBrains Mono"]
|
||||||
|
},
|
||||||
|
"fontSize": {
|
||||||
|
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
|
||||||
|
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||||
|
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
|
||||||
|
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}],
|
||||||
|
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
|
||||||
|
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.material-symbols-outlined {
|
||||||
|
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-color: #151515;
|
||||||
|
color: #e0e3e6;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
.scanline {
|
||||||
|
background: linear-gradient(to bottom, transparent 50%, rgba(0,0,0,0.1) 50%);
|
||||||
|
background-size: 100% 2px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
min-height: max(884px, 100dvh);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="font-body-md text-body-md overflow-x-hidden pb-[action-bar-height]">
|
||||||
|
<!-- Status Bar -->
|
||||||
|
<header class="fixed top-0 w-full h-[32px] bg-[#202020] flex items-center justify-between px-margin-edge z-[60] border-b border-outline-variant">
|
||||||
|
<div class="flex items-center gap-2 font-label-caps text-on-surface">
|
||||||
|
<span class="text-primary">▌</span>achievements.json
|
||||||
|
</div>
|
||||||
|
<div class="font-label-caps text-[#a0a0a0]">
|
||||||
|
8/19 UNLOCKED
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<!-- Top App Bar (Shared Component Reference) -->
|
||||||
|
<nav class="fixed top-[32px] w-full h-[64px] bg-surface flex items-center justify-between px-margin-edge z-50 border-b border-outline-variant">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="material-symbols-outlined text-primary" data-icon="terminal">terminal</span>
|
||||||
|
<h1 class="font-headline text-[20px] text-primary uppercase tracking-widest">Rusty Solitaire</h1>
|
||||||
|
</div>
|
||||||
|
<button class="w-10 h-10 flex items-center justify-center hover:bg-surface-container-highest transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-on-surface-variant" data-icon="settings">settings</span>
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
<main class="mt-[112px] px-margin-edge">
|
||||||
|
<!-- Hero Progress Card -->
|
||||||
|
<section class="w-full h-[100px] bg-[#202020] border border-[#353535] rounded-lg p-4 mb-6">
|
||||||
|
<div class="flex flex-col justify-between h-full">
|
||||||
|
<span class="font-label-caps text-[10px] text-[#a0a0a0]">PROGRESS</span>
|
||||||
|
<div class="flex items-baseline gap-2">
|
||||||
|
<span class="font-headline text-[28px] font-bold text-[#d0d0d0]">8/19</span>
|
||||||
|
<span class="font-label-caps text-[14px] text-highlight-celebration">(42%)</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-full h-[4px] bg-[#353535] rounded-full overflow-hidden mt-1">
|
||||||
|
<div class="h-full bg-highlight-celebration" style="width: 42%;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- Filter Chip Row -->
|
||||||
|
<section class="flex gap-2 mb-6 overflow-x-auto no-scrollbar">
|
||||||
|
<button class="h-[32px] px-3 flex items-center justify-center border border-[#6fc2ef] text-[#6fc2ef] rounded-[4px] font-label-caps text-[11px]">
|
||||||
|
[ ALL ]
|
||||||
|
</button>
|
||||||
|
<button class="h-[32px] px-3 flex items-center justify-center border border-outline text-[#a0a0a0] rounded-[4px] font-label-caps text-[11px] hover:border-primary hover:text-primary transition-colors">
|
||||||
|
UNLOCKED
|
||||||
|
</button>
|
||||||
|
<button class="h-[32px] px-3 flex items-center justify-center border border-outline text-[#a0a0a0] rounded-[4px] font-label-caps text-[11px] hover:border-primary hover:text-primary transition-colors">
|
||||||
|
LOCKED
|
||||||
|
</button>
|
||||||
|
<button class="h-[32px] px-3 flex items-center justify-center border border-outline text-[#a0a0a0] rounded-[4px] font-label-caps text-[11px] hover:border-primary hover:text-primary transition-colors">
|
||||||
|
SECRET
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
<!-- Achievements Grid -->
|
||||||
|
<section class="grid grid-cols-2 gap-3 mb-10">
|
||||||
|
<!-- FIRST WIN -->
|
||||||
|
<div class="h-[100px] bg-[#202020] border border-[#353535] p-3 flex flex-col justify-between rounded-sm">
|
||||||
|
<div class="w-8 h-8 rounded-full bg-[#6fc2ef] flex items-center justify-center">
|
||||||
|
<span class="material-symbols-outlined text-[#151515] text-[20px]" data-icon="emoji_events" style="font-variation-settings: 'FILL' 1;">emoji_events</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="font-label-caps text-[13px] font-bold text-[#d0d0d0] leading-none mb-1">FIRST WIN</h3>
|
||||||
|
<p class="font-label-caps text-[10px] text-[#a0a0a0] leading-tight">Win your first game</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- SPEED DEMON -->
|
||||||
|
<div class="h-[100px] bg-[#202020] border border-highlight-celebration p-3 flex flex-col justify-between rounded-sm relative">
|
||||||
|
<div class="absolute inset-0 border border-highlight-celebration opacity-20 pointer-events-none"></div>
|
||||||
|
<div class="w-8 h-8 rounded-full bg-[#6fc2ef] flex items-center justify-center">
|
||||||
|
<span class="material-symbols-outlined text-[#151515] text-[20px]" data-icon="speed" style="font-variation-settings: 'FILL' 1;">speed</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="font-label-caps text-[13px] font-bold text-[#d0d0d0] leading-none mb-1">SPEED DEMON</h3>
|
||||||
|
<p class="font-label-caps text-[10px] text-[#a0a0a0] leading-tight">Win in under 3:00</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- STREAK 10 -->
|
||||||
|
<div class="h-[100px] bg-[#202020] border border-[#353535] p-3 flex flex-col justify-between rounded-sm">
|
||||||
|
<div class="w-8 h-8 rounded-full bg-[#6fc2ef] flex items-center justify-center">
|
||||||
|
<span class="material-symbols-outlined text-[#151515] text-[20px]" data-icon="bolt" style="font-variation-settings: 'FILL' 1;">bolt</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="font-label-caps text-[13px] font-bold text-[#d0d0d0] leading-none mb-1">STREAK 10</h3>
|
||||||
|
<p class="font-label-caps text-[10px] text-[#a0a0a0] leading-tight">10 wins in a row</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- DAILY DEFENDER -->
|
||||||
|
<div class="h-[100px] bg-[#202020] border border-[#353535] p-3 flex flex-col justify-between rounded-sm">
|
||||||
|
<div class="w-8 h-8 rounded-full bg-[#6fc2ef] flex items-center justify-center">
|
||||||
|
<span class="material-symbols-outlined text-[#151515] text-[20px]" data-icon="calendar_today" style="font-variation-settings: 'FILL' 1;">calendar_today</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="font-label-caps text-[13px] font-bold text-[#d0d0d0] leading-none mb-1">DAILY DEFENDER</h3>
|
||||||
|
<p class="font-label-caps text-[10px] text-[#a0a0a0] leading-tight">Complete 7 daily seeds</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- PERFECTIONIST (LOCKED) -->
|
||||||
|
<div class="h-[100px] bg-[#0d0d0d] border border-[#353535] p-3 flex flex-col justify-between rounded-sm opacity-80">
|
||||||
|
<div class="w-8 h-8 rounded-full border border-[#505050] flex items-center justify-center">
|
||||||
|
<span class="material-symbols-outlined text-[#505050] text-[20px]" data-icon="undo">undo</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="font-label-caps text-[13px] font-bold text-[#505050] leading-none mb-1">PERFECTIONIST</h3>
|
||||||
|
<p class="font-label-caps text-[10px] text-[#353535] leading-tight">Win without using undo</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- CHALLENGE BEATEN (LOCKED) -->
|
||||||
|
<div class="h-[100px] bg-[#0d0d0d] border border-[#353535] p-3 flex flex-col justify-between rounded-sm opacity-80">
|
||||||
|
<div class="w-8 h-8 rounded-full border border-[#505050] flex items-center justify-center">
|
||||||
|
<span class="material-symbols-outlined text-[#505050] text-[20px]" data-icon="military_tech">military_tech</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="font-label-caps text-[13px] font-bold text-[#505050] leading-none mb-1">CHALLENGE BEATEN</h3>
|
||||||
|
<p class="font-label-caps text-[10px] text-[#353535] leading-tight">Complete CHALLENGE mode</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- SECRET (LOCKED) -->
|
||||||
|
<div class="h-[100px] bg-[#0d0d0d] border border-[#353535] p-3 flex flex-col justify-between rounded-sm opacity-80">
|
||||||
|
<div class="w-8 h-8 rounded-full border border-[#505050] flex items-center justify-center">
|
||||||
|
<span class="material-symbols-outlined text-[#505050] text-[20px]" data-icon="help_outline">help_outline</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="font-label-caps text-[13px] font-bold text-[#505050] leading-none mb-1">????</h3>
|
||||||
|
<p class="font-label-caps text-[10px] text-[#353535] leading-tight">SECRET · Hidden until unlocked</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- PAR HUNTER (LOCKED) -->
|
||||||
|
<div class="h-[100px] bg-[#0d0d0d] border border-[#353535] p-3 flex flex-col justify-between rounded-sm opacity-80">
|
||||||
|
<div class="w-8 h-8 rounded-full border border-[#505050] flex items-center justify-center">
|
||||||
|
<span class="material-symbols-outlined text-[#505050] text-[20px]" data-icon="golf_course">golf_course</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="font-label-caps text-[13px] font-bold text-[#505050] leading-none mb-1">PAR HUNTER</h3>
|
||||||
|
<p class="font-label-caps text-[10px] text-[#353535] leading-tight">Beat par on 50 games</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<!-- Footer Status -->
|
||||||
|
<footer class="fixed bottom-[action-bar-height] w-full h-[24px] bg-background border-t border-outline-variant flex items-center justify-between px-margin-edge z-40 text-[10px] font-label-caps">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="text-primary mr-1">▌</span>
|
||||||
|
<span class="text-on-surface-variant">NORMAL</span>
|
||||||
|
<span class="mx-2 text-outline">│</span>
|
||||||
|
<span class="text-on-surface-variant">achievements</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-3">
|
||||||
|
<div><span class="text-[#a0a0a0]">[F]</span> <span class="text-[#505050]">filter</span></div>
|
||||||
|
<div><span class="text-[#a0a0a0]">[/]</span> <span class="text-[#505050]">search</span></div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<!-- Bottom Navigation Bar (Shared Component Reference) -->
|
||||||
|
<nav class="fixed bottom-0 w-full h-action-bar-height bg-surface-container flex justify-around items-center px-margin-edge z-50 border-t border-outline-variant">
|
||||||
|
<button class="flex flex-col items-center justify-center text-on-surface-variant p-2 hover:text-primary hover:bg-surface-container-highest transition-colors active:scale-95">
|
||||||
|
<span class="material-symbols-outlined" data-icon="power_settings_new">power_settings_new</span>
|
||||||
|
<span class="font-label-caps text-[10px] mt-1">[Q] QUIT</span>
|
||||||
|
</button>
|
||||||
|
<button class="flex flex-col items-center justify-center text-on-surface-variant p-2 hover:text-primary hover:bg-surface-container-highest transition-colors active:scale-95">
|
||||||
|
<span class="material-symbols-outlined" data-icon="add_box">add_box</span>
|
||||||
|
<span class="font-label-caps text-[10px] mt-1">[N] NEW</span>
|
||||||
|
</button>
|
||||||
|
<button class="flex flex-col items-center justify-center text-on-surface-variant p-2 hover:text-primary hover:bg-surface-container-highest transition-colors active:scale-95">
|
||||||
|
<span class="material-symbols-outlined" data-icon="undo">undo</span>
|
||||||
|
<span class="font-label-caps text-[10px] mt-1">[U] UNDO</span>
|
||||||
|
</button>
|
||||||
|
<button class="flex flex-col items-center justify-center text-on-surface-variant p-2 hover:text-primary hover:bg-surface-container-highest transition-colors active:scale-95">
|
||||||
|
<span class="material-symbols-outlined" data-icon="lightbulb">lightbulb</span>
|
||||||
|
<span class="font-label-caps text-[10px] mt-1">[H] HINT</span>
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
<!-- CRT Overlay Effect (Visual Decoration) -->
|
||||||
|
<div class="fixed inset-0 pointer-events-none z-[100] opacity-[0.03] scanline"></div>
|
||||||
|
</body></html>
|
||||||
|
After Width: | Height: | Size: 41 KiB |
@@ -0,0 +1,251 @@
|
|||||||
|
# Card-face artwork migration plan
|
||||||
|
|
||||||
|
**Status:** planning artifact (no code changed by this document).
|
||||||
|
**Tracks:** the "Card-face / suit / card-back artwork regeneration"
|
||||||
|
item in `SESSION_HANDOFF.md` → "Visual-identity follow-ups"
|
||||||
|
(SESSION_HANDOFF Resume prompt option D).
|
||||||
|
**Companion to:** `docs/ui-mockups/design-system.md` (Game Cards
|
||||||
|
spec, lines 214–233) and `docs/ui-mockups/desktop-adaptation.md`
|
||||||
|
(rules-based companion to the mockups).
|
||||||
|
|
||||||
|
## Why this is a multi-session arc
|
||||||
|
|
||||||
|
Every post-v0.20.0 visual-identity port to date (modal scaffold,
|
||||||
|
toasts, table chrome, splash boot screen, replay overlay) was a
|
||||||
|
**single rendering path** — change tokens, change comments, ship.
|
||||||
|
Cards have **two** rendering paths that are visually identical
|
||||||
|
today and would visually disagree the moment one moves:
|
||||||
|
|
||||||
|
1. **PNG path (production).** `assets/cards/faces/<rank><suit>.png`
|
||||||
|
loaded into `CardImageSet.faces[suit][rank]` at startup; card
|
||||||
|
sprites blit the texture. 52 face PNGs + 5 back PNGs already
|
||||||
|
in `assets/`, all the legacy white-card aesthetic from the
|
||||||
|
pre-Terminal design system.
|
||||||
|
2. **Constant fallback (tests + asset-missing edge).** When
|
||||||
|
`CardImageSet` isn't a registered resource (the case under
|
||||||
|
`MinimalPlugins` test fixtures, and the bare-bones path the
|
||||||
|
first-frame of production hits before assets resolve), the
|
||||||
|
renderer falls back to solid-colour sprites driven by the
|
||||||
|
`card_plugin` constants:
|
||||||
|
- `CARD_FACE_COLOUR` — `(0.98, 0.98, 0.95)` cream-ish white.
|
||||||
|
- `RED_SUIT_COLOUR` — `(0.78, 0.12, 0.15)` warm red.
|
||||||
|
- `BLACK_SUIT_COLOUR` — `(0.08, 0.08, 0.08)` near-black.
|
||||||
|
- `CARD_FACE_COLOUR_RED_CBM` — `(0.85, 0.92, 1.0, 1.0)` light
|
||||||
|
blue (the legacy color-blind tint).
|
||||||
|
- `card_back_colour(idx)` — five legacy back themes.
|
||||||
|
|
||||||
|
A single-path migration leaves a known-broken state where tests
|
||||||
|
pass against Terminal constants while a human sees legacy artwork
|
||||||
|
on screen — the exact bisection-hostile drift the handoff's
|
||||||
|
"in lockstep" warning preempts.
|
||||||
|
|
||||||
|
## Target state — Terminal aesthetic
|
||||||
|
|
||||||
|
Per `design-system.md` § Game Cards (lines 214–233):
|
||||||
|
|
||||||
|
### Card face
|
||||||
|
|
||||||
|
| Element | Spec |
|
||||||
|
|---|---|
|
||||||
|
| Background | `#1a1a1a` |
|
||||||
|
| Border | 1 px solid in **suit colour** (pink for ♥/♦, foreground gray for ♠/♣) |
|
||||||
|
| Corner radius | 8 px |
|
||||||
|
| Top-left | rank in JetBrains Mono **Bold 18 px** + small suit glyph (10 px) |
|
||||||
|
| Bottom-right | large suit glyph (32 px), rotated 180° |
|
||||||
|
| Glyph fill rule | ♥ ♠ filled; ♦ ♣ outlined (1.5 px stroke). Always on, not a toggle. |
|
||||||
|
|
||||||
|
### Suit colours (always-on glyph differentiation is the *primary*
|
||||||
|
distinguishing mechanism; colour is supplementary):
|
||||||
|
|
||||||
|
| Suit | Default | Color-blind mode |
|
||||||
|
|---|---|---|
|
||||||
|
| Hearts | `#fb9fb1` (pink) | `#6fc2ef` (cyan) |
|
||||||
|
| Diamonds | `#fb9fb1` (pink) | `#6fc2ef` (cyan) |
|
||||||
|
| Spades | `#d0d0d0` (gray) | `#d0d0d0` (unchanged) |
|
||||||
|
| Clubs | `#d0d0d0` (gray) | `#d0d0d0` (unchanged) |
|
||||||
|
|
||||||
|
### Card back ("Terminal" theme)
|
||||||
|
|
||||||
|
| Element | Spec |
|
||||||
|
|---|---|
|
||||||
|
| Background | `#151515` |
|
||||||
|
| Pattern | horizontal scanlines at 2 px pitch in `#1a1a1a` (1 px line, 1 px gap), full bleed |
|
||||||
|
| Border | 1 px solid `#353535` |
|
||||||
|
| Top-left badge | 12×16 px solid `#6fc2ef` block, 6 px from corner |
|
||||||
|
| Bottom-right monogram | `▌RS` in JetBrains Mono 12 px `#505050`, 6 px from corner |
|
||||||
|
| Corner radius | 8 px |
|
||||||
|
| Theme name / author | `"Terminal"` / `"Rusty Solitaire"` |
|
||||||
|
|
||||||
|
## Generation pipeline — programmatic SVG via the existing
|
||||||
|
`resvg` stack
|
||||||
|
|
||||||
|
### Why this path (vs. external tooling or direct `tiny_skia`)
|
||||||
|
|
||||||
|
The codebase already ships an SVG-to-PNG rasteriser at
|
||||||
|
`solitaire_engine/src/assets/svg_loader.rs`:
|
||||||
|
|
||||||
|
- Public `rasterize_svg(svg_bytes: &[u8], target: UVec2) -> Result<Image, _>`
|
||||||
|
- Backed by `usvg` (parser) + `resvg` (renderer) + `tiny_skia`
|
||||||
|
(CPU pixmap)
|
||||||
|
- Bundled font db includes JetBrains-style mono (FiraMono — same
|
||||||
|
face the splash uses; close enough to JetBrains Mono for
|
||||||
|
rasterisation purposes, and identical to what the Bevy UI
|
||||||
|
consumes in the rest of the app)
|
||||||
|
- `RenderAssetUsages::default()` is the call-site convention here
|
||||||
|
|
||||||
|
This means: **generating new card PNGs is one new file
|
||||||
|
(`solitaire_engine/examples/card_face_generator.rs`) calling an
|
||||||
|
existing public function.** No new dependencies, no asset-pipeline
|
||||||
|
changes, no build-script machinery. Anyone who runs the example
|
||||||
|
gets bit-identical artwork.
|
||||||
|
|
||||||
|
The two alternatives are weaker:
|
||||||
|
|
||||||
|
- **External tool (Inkscape / Figma / hand-design)** — produces
|
||||||
|
one-off PNGs that can't be re-generated reproducibly without
|
||||||
|
re-opening the source files in a specific tool. Iteration cost
|
||||||
|
is high; design tweaks (e.g. "make the suit glyph 2 px larger")
|
||||||
|
require a designer-in-the-loop.
|
||||||
|
- **Direct `tiny_skia` painting calls** — bypasses SVG entirely,
|
||||||
|
but loses the readability of "open the SVG to see exactly what
|
||||||
|
the card looks like." Also reinvents primitives (rounded
|
||||||
|
rectangles, text layout) that `usvg` already handles.
|
||||||
|
|
||||||
|
### Output format
|
||||||
|
|
||||||
|
PNG, RGBA8 sRGB, **dimensions 256 × 384** (2:3 aspect, half the
|
||||||
|
default `SvgLoaderSettings` of 512 × 768).
|
||||||
|
|
||||||
|
Rationale: cards never exceed ~250 px wide on desktop windows
|
||||||
|
today, and 256 × 384 PNGs are ~6 KB each at this content density
|
||||||
|
(13.4 KB total for a full deck of 52 + 5 backs). The default 512 ×
|
||||||
|
768 is 2× what's needed and quadruples the on-disk asset weight.
|
||||||
|
The existing legacy PNGs are 512 × 768 — reducing the new ones
|
||||||
|
halves the runtime asset size.
|
||||||
|
|
||||||
|
## Lockstep migration — recommended order
|
||||||
|
|
||||||
|
Each step is a separate commit; the constraint is that **steps 4
|
||||||
|
and 5 must land in the same commit** (or at most adjacent commits
|
||||||
|
on the same branch) so the rendered output never diverges between
|
||||||
|
the two paths.
|
||||||
|
|
||||||
|
1. **(Done — this commit)** Land the migration plan doc.
|
||||||
|
2. **Land the SVG generator example.** New
|
||||||
|
`solitaire_engine/examples/card_face_generator.rs`. Output
|
||||||
|
goes to `assets/cards/faces/` and `assets/cards/backs/`. Run
|
||||||
|
once locally to seed the new artwork. The example file stays
|
||||||
|
in-tree as a regenerator for future tweaks.
|
||||||
|
3. **(Optional — can land separately)** Add a one-shot regression
|
||||||
|
test that re-runs the generator into a `tempdir` and compares
|
||||||
|
the resulting bytes against the on-disk artwork; pinning the
|
||||||
|
generator output prevents silent drift if `usvg`/`resvg` ever
|
||||||
|
tweak rendering. Skip if the test runtime cost is unacceptable.
|
||||||
|
4. **Land the new artwork** (PNG bytes from step 2 committed to
|
||||||
|
`assets/cards/`) **and** the constant migration in the *same
|
||||||
|
commit*:
|
||||||
|
- `CARD_FACE_COLOUR` → `Color::srgb(0.102, 0.102, 0.102)` (`#1a1a1a`)
|
||||||
|
- `RED_SUIT_COLOUR` → `Color::srgb(0.984, 0.624, 0.694)` (`#fb9fb1`)
|
||||||
|
- `BLACK_SUIT_COLOUR` → `Color::srgb(0.816, 0.816, 0.816)` (`#d0d0d0`)
|
||||||
|
- `CARD_FACE_COLOUR_RED_CBM` → `Color::srgb(0.435, 0.761, 0.937)` (`#6fc2ef`) — note this is now the colour-blind *suit* colour, not a face tint; semantics shift slightly.
|
||||||
|
- `card_back_colour(idx)` — re-author for the Terminal palette;
|
||||||
|
index 0 stays the canonical "Terminal" back from `design-system.md`.
|
||||||
|
5. **Test updates land in step 4's commit.** The pinning tests at
|
||||||
|
`card_plugin.rs` lines 1749, 1750, 1767, 1768, 2057, 2063,
|
||||||
|
2071, 2081 all assert against the old constants. New
|
||||||
|
assertions update in lockstep with the constant changes.
|
||||||
|
|
||||||
|
## CBM (color-blind mode) semantics shift — flag
|
||||||
|
|
||||||
|
The **legacy** `CARD_FACE_COLOUR_RED_CBM` was a *face tint* — red
|
||||||
|
suits got a light-blue background wash. The **Terminal** spec
|
||||||
|
moves CBM into the *suit colour* itself (red glyphs swap to cyan).
|
||||||
|
Step 4 will rename / repurpose this constant; it's not a 1:1
|
||||||
|
replacement.
|
||||||
|
|
||||||
|
Two options:
|
||||||
|
|
||||||
|
- **Rename + repurpose:** `CARD_FACE_COLOUR_RED_CBM` →
|
||||||
|
`RED_SUIT_COLOUR_CBM`. Communicates the semantic shift in the
|
||||||
|
symbol name. Requires touching every callsite.
|
||||||
|
- **Keep the name, change the meaning:** less code churn but
|
||||||
|
worse for greppability — a future reader hitting the legacy
|
||||||
|
name will assume face-tint behaviour.
|
||||||
|
|
||||||
|
Recommendation: **rename**. The CBM swap is a one-frame operation
|
||||||
|
even if it touches every existing callsite (currently lines 642,
|
||||||
|
2071, 2081 per `grep -n CARD_FACE_COLOUR_RED_CBM`).
|
||||||
|
|
||||||
|
## Theme system — out of scope here
|
||||||
|
|
||||||
|
The card-theme system (`docs/CARD_PLAN.md`, `theme/plugin.rs`)
|
||||||
|
already supports user-supplied themes via `assets/themes/<theme>/`
|
||||||
|
SVG files rasterised by `svg_loader.rs`. The new Terminal artwork
|
||||||
|
is the **default theme**, not a new entry in the theme picker —
|
||||||
|
the theme system continues to overlay user themes on top of the
|
||||||
|
default at runtime.
|
||||||
|
|
||||||
|
If the next session wants to also ship Terminal as a *named theme
|
||||||
|
slot* (so a user can switch back to the legacy artwork via the
|
||||||
|
theme picker), that's an additive change after step 4 and lives
|
||||||
|
in `theme::plugin::apply_theme_to_card_image_set`.
|
||||||
|
|
||||||
|
## Test impact summary
|
||||||
|
|
||||||
|
`grep -n CARD_FACE_COLOUR\\b\|RED_SUIT_COLOUR\\b\|BLACK_SUIT_COLOUR\\b` in
|
||||||
|
`card_plugin.rs`:
|
||||||
|
|
||||||
|
- Line 1749–1750: red-suit text colour assertions (♥ + ♦).
|
||||||
|
- Line 1767–1768: black-suit text colour assertions (♠ + ♣).
|
||||||
|
- Line 2057, 2063: face-colour assertion in default mode.
|
||||||
|
- Line 2071, 2081: face-colour assertion in CBM.
|
||||||
|
|
||||||
|
The four suit-colour and two face-colour tests are **invariant
|
||||||
|
guards** — they exist precisely so a constant tweak surfaces here
|
||||||
|
rather than in a visual review. Step 4 updates each in lockstep
|
||||||
|
with the constant value change. No new test infrastructure
|
||||||
|
needed.
|
||||||
|
|
||||||
|
## Open questions to resolve before step 4
|
||||||
|
|
||||||
|
1. **Border colour conflict.** The spec (line 218) says "Border:
|
||||||
|
1 px solid in suit colour." The fallback path doesn't draw a
|
||||||
|
border today — it draws solid-colour sprites. Step 4 either:
|
||||||
|
(a) leaves the fallback as solid-colour squares (the test
|
||||||
|
environment doesn't visually validate borders anyway), or
|
||||||
|
(b) extends the fallback renderer to paint a 1 px outline.
|
||||||
|
Recommend (a) — fallback fidelity isn't load-bearing.
|
||||||
|
2. **Glyph rendering in the constant fallback.** The fallback
|
||||||
|
today doesn't render suit glyphs at all — it's a coloured
|
||||||
|
square. The spec's filled-vs-outlined glyph differentiation
|
||||||
|
only matters in the PNG path. No change to the constant
|
||||||
|
fallback for glyphs.
|
||||||
|
3. **High-contrast mode.** `design-system.md` line 274 mentions
|
||||||
|
a high-contrast accessibility mode (boosts foreground from
|
||||||
|
`#d0d0d0` to `#f5f5f5`, suit-red from `#fb9fb1` to `#ff8aa0`).
|
||||||
|
Not currently implemented anywhere; out of scope for this
|
||||||
|
migration but worth flagging for a future accessibility pass.
|
||||||
|
|
||||||
|
## Post-migration — what's still open
|
||||||
|
|
||||||
|
- **High-contrast mode** (above).
|
||||||
|
- **Reduced-motion mode** for card lift / drop transitions
|
||||||
|
(also a `design-system.md` accessibility item, separate from
|
||||||
|
artwork).
|
||||||
|
- **The 9 missing-plugin screens** (splash, challenge,
|
||||||
|
time-attack, weekly-goals, leaderboard, sync, level-up,
|
||||||
|
replay, radial-menu) per `project_ui_overhaul` memory still
|
||||||
|
need their plugin ports — separate from the cards arc.
|
||||||
|
|
||||||
|
## Sign-off criteria for "D closed"
|
||||||
|
|
||||||
|
D from the SESSION_HANDOFF Resume prompt is closed when **all of
|
||||||
|
the following hold simultaneously**:
|
||||||
|
|
||||||
|
- The 52 face PNGs + 5 back PNGs in `assets/cards/` are the
|
||||||
|
Terminal-aesthetic artwork (regeneratable via the example).
|
||||||
|
- The five `card_plugin` constants reflect the Terminal palette.
|
||||||
|
- All pinning tests pass against the new values.
|
||||||
|
- A human boots the game and sees Terminal cards (not white
|
||||||
|
cards). This sign-off needs a real `cargo run`, not just
|
||||||
|
`cargo test`.
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html class="dark" lang="en"><head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta content="width=390, height=844, initial-scale=1.0" name="viewport"/>
|
||||||
|
<title>Challenge Mode Menu</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@400;500;700&display=swap" rel="stylesheet"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<script id="tailwind-config">
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: "class",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
"colors": {
|
||||||
|
"inverse-surface": "#e0e3e6",
|
||||||
|
"error-container": "#93000a",
|
||||||
|
"tertiary": "#f7c3ff",
|
||||||
|
"on-primary-container": "#004f6c",
|
||||||
|
"on-surface": "#e0e3e6",
|
||||||
|
"surface-dim": "#101417",
|
||||||
|
"surface-container-high": "#272a2d",
|
||||||
|
"surface-container-lowest": "#0b0f11",
|
||||||
|
"secondary-container": "#435401",
|
||||||
|
"suit-red": "#fb9fb1",
|
||||||
|
"on-error": "#690005",
|
||||||
|
"surface-container-low": "#181c1f",
|
||||||
|
"surface-variant": "#313538",
|
||||||
|
"surface-tint": "#7ed0fe",
|
||||||
|
"primary-container": "#6fc2ef",
|
||||||
|
"background": "#101417",
|
||||||
|
"primary": "#a1dcff",
|
||||||
|
"outline": "#505050",
|
||||||
|
"suit-black": "#d0d0d0",
|
||||||
|
"secondary-fixed": "#d5ec8c",
|
||||||
|
"surface-container": "#202020",
|
||||||
|
"on-tertiary-fixed": "#340043",
|
||||||
|
"on-tertiary-fixed-variant": "#653173",
|
||||||
|
"outline-variant": "#3f484e",
|
||||||
|
"on-surface-variant": "#bfc8cf",
|
||||||
|
"error": "#fb9fb1",
|
||||||
|
"on-primary-fixed": "#001e2c",
|
||||||
|
"highlight-celebration": "#e1a3ee",
|
||||||
|
"highlight-valid": "#acc267",
|
||||||
|
"suit-red-cb": "#6fc2ef",
|
||||||
|
"primary-fixed-dim": "#7ed0fe",
|
||||||
|
"tertiary-fixed-dim": "#f0b0fc",
|
||||||
|
"primary-fixed": "#c4e7ff",
|
||||||
|
"on-error-container": "#ffdad6",
|
||||||
|
"tertiary-container": "#e1a3ee",
|
||||||
|
"on-secondary": "#293500",
|
||||||
|
"on-tertiary": "#4c195b",
|
||||||
|
"on-background": "#e0e3e6",
|
||||||
|
"secondary-fixed-dim": "#bad073",
|
||||||
|
"secondary": "#bad073",
|
||||||
|
"inverse-primary": "#00668a",
|
||||||
|
"surface-bright": "#363a3d",
|
||||||
|
"surface": "#151515",
|
||||||
|
"on-tertiary-container": "#683476",
|
||||||
|
"on-secondary-fixed": "#161e00",
|
||||||
|
"inverse-on-surface": "#2d3134",
|
||||||
|
"warning": "#ddb26f",
|
||||||
|
"info": "#12cfc0",
|
||||||
|
"surface-container-highest": "#313538",
|
||||||
|
"on-primary-fixed-variant": "#004c69",
|
||||||
|
"tertiary-fixed": "#fbd7ff",
|
||||||
|
"on-secondary-fixed-variant": "#3c4d00",
|
||||||
|
"on-secondary-container": "#b2c86d",
|
||||||
|
"on-primary": "#003549"
|
||||||
|
},
|
||||||
|
"fontFamily": {
|
||||||
|
"mono": ["JetBrains Mono", "monospace"],
|
||||||
|
"label-caps": ["JetBrains Mono"],
|
||||||
|
"hud-score": ["JetBrains Mono"],
|
||||||
|
"body-md": ["Inter"],
|
||||||
|
"card-rank": ["JetBrains Mono"],
|
||||||
|
"hud-timer": ["JetBrains Mono"],
|
||||||
|
"headline": ["JetBrains Mono"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.material-symbols-outlined {
|
||||||
|
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-color: #101417;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
}
|
||||||
|
.retro-scanline {
|
||||||
|
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.1) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.03), rgba(0, 255, 0, 0.01), rgba(0, 0, 255, 0.03));
|
||||||
|
background-size: 100% 2px, 3px 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
min-height: max(884px, 100dvh);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="flex items-center justify-center min-h-screen text-on-background overflow-hidden">
|
||||||
|
<!-- Mobile Container (390x844) -->
|
||||||
|
<div class="relative w-[390px] h-[844px] bg-background flex flex-col overflow-hidden border border-outline-variant">
|
||||||
|
<!-- Status Bar -->
|
||||||
|
<div class="h-[32px] bg-surface-container flex items-center justify-between px-4 text-[11px] font-mono border-b border-outline-variant shrink-0">
|
||||||
|
<span class="text-suit-black">▌challenge.tsx</span>
|
||||||
|
<span class="text-[#a0a0a0]">LV 12 · UNLOCKED</span>
|
||||||
|
</div>
|
||||||
|
<!-- Header -->
|
||||||
|
<header class="h-[80px] px-margin-edge flex flex-col justify-center border-b border-outline-variant shrink-0">
|
||||||
|
<h1 class="text-[24px] font-bold leading-tight text-suit-black">CHALLENGE MODE</h1>
|
||||||
|
<p class="text-[12px] text-[#a0a0a0] mt-1">Curated puzzles · Beat par for bonus XP</p>
|
||||||
|
</header>
|
||||||
|
<!-- Stats Row -->
|
||||||
|
<div class="mx-margin-edge mt-4 bg-surface-container rounded-[4px] p-3 flex items-center justify-between border border-outline-variant shrink-0">
|
||||||
|
<div class="flex items-baseline gap-1">
|
||||||
|
<span class="text-[14px] font-bold text-suit-black">DONE 8/24</span>
|
||||||
|
<span class="text-[14px] font-bold text-highlight-celebration">(33%)</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-outline-variant text-[14px]">│</span>
|
||||||
|
<div class="text-[14px] font-bold text-suit-black">BEST AVG 03:42</div>
|
||||||
|
<span class="text-outline-variant text-[14px]">│</span>
|
||||||
|
<div class="text-[14px] font-bold text-highlight-valid">+1240 XP</div>
|
||||||
|
</div>
|
||||||
|
<!-- Scrollable List Area -->
|
||||||
|
<div class="flex-1 overflow-y-auto px-margin-edge pt-4 space-y-3 pb-6">
|
||||||
|
<!-- Card 1 -->
|
||||||
|
<div class="h-[80px] bg-surface-container border border-outline rounded-[4px] flex relative overflow-hidden">
|
||||||
|
<div class="w-[6px] h-full bg-warning"></div>
|
||||||
|
<div class="flex-1 flex items-center justify-between px-4">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-[14px] font-bold text-suit-black">DEEP STACK</span>
|
||||||
|
<span class="text-[12px] text-on-surface-variant">Win with 0 stock · ★★★☆☆</span>
|
||||||
|
</div>
|
||||||
|
<div class="bg-highlight-valid px-2 py-1 rounded-[2px] text-background text-[11px] font-bold">
|
||||||
|
✓ DONE
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Card 2 -->
|
||||||
|
<div class="h-[80px] bg-surface-container border border-primary rounded-[4px] flex relative overflow-hidden">
|
||||||
|
<div class="w-[6px] h-full bg-highlight-valid"></div>
|
||||||
|
<div class="flex-1 flex items-center justify-between px-4">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-[14px] font-bold text-suit-black">SPEED RUN</span>
|
||||||
|
<span class="text-[12px] text-on-surface-variant">Win under 2:30 · ★★☆☆☆</span>
|
||||||
|
</div>
|
||||||
|
<div class="bg-primary px-2 py-1 rounded-[2px] text-background text-[11px] font-bold">
|
||||||
|
▶ ACTIVE
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Card 3 -->
|
||||||
|
<div class="h-[80px] bg-surface-container border border-outline rounded-[4px] flex relative overflow-hidden">
|
||||||
|
<div class="w-[6px] h-full bg-suit-red"></div>
|
||||||
|
<div class="flex-1 flex items-center justify-between px-4">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-[14px] font-bold text-suit-black">NO UNDO</span>
|
||||||
|
<span class="text-[12px] text-on-surface-variant">Win without undo · ★★★★☆</span>
|
||||||
|
</div>
|
||||||
|
<div class="bg-primary px-2 py-1 rounded-[2px] text-background text-[11px] font-bold">
|
||||||
|
▶ ACTIVE
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Card 4 -->
|
||||||
|
<div class="h-[80px] bg-surface-container border border-outline rounded-[4px] flex relative overflow-hidden">
|
||||||
|
<div class="w-[6px] h-full bg-info"></div>
|
||||||
|
<div class="flex-1 flex items-center justify-between px-4">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-[14px] font-bold text-suit-black">FOUR SUITS</span>
|
||||||
|
<span class="text-[12px] text-on-surface-variant">1 card per suit · ★☆☆☆☆</span>
|
||||||
|
</div>
|
||||||
|
<div class="bg-highlight-valid px-2 py-1 rounded-[2px] text-background text-[11px] font-bold">
|
||||||
|
✓ DONE
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Card 5 (Locked) -->
|
||||||
|
<div class="h-[80px] bg-surface-container border border-outline-variant rounded-[4px] flex relative overflow-hidden opacity-60">
|
||||||
|
<div class="w-[6px] h-full bg-highlight-celebration"></div>
|
||||||
|
<div class="flex-1 flex items-center justify-between px-4">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-[14px] font-bold text-suit-black">PERFECT RUN</span>
|
||||||
|
<span class="text-[12px] text-on-surface-variant">Below par moves · ★★★★★</span>
|
||||||
|
</div>
|
||||||
|
<div class="bg-outline px-2 py-1 rounded-[2px] text-on-surface text-[11px] font-bold">
|
||||||
|
🔒 LOCKED
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Filler Graphic for retro feel -->
|
||||||
|
<div class="flex items-center justify-center py-4">
|
||||||
|
<div class="h-[1px] flex-1 bg-outline-variant"></div>
|
||||||
|
<span class="px-4 text-[10px] text-outline text-label-caps">END OF LIST</span>
|
||||||
|
<div class="h-[1px] flex-1 bg-outline-variant"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Shared Component: Terminal Context (Used as Footer) -->
|
||||||
|
<div class="h-[24px] bg-surface px-4 flex items-center justify-between text-[10px] font-mono border-t border-outline-variant shrink-0">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-primary">▌ NORMAL</span>
|
||||||
|
<span class="text-outline">│</span>
|
||||||
|
<span class="text-on-surface-variant uppercase tracking-widest">challenge</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-[#a0a0a0] flex items-center gap-3">
|
||||||
|
<span>[ENTER] select</span>
|
||||||
|
<span>[F] filter</span>
|
||||||
|
<span class="text-suit-red">[ESC] back</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Retro Scanline Overlay -->
|
||||||
|
<div class="absolute inset-0 retro-scanline z-50"></div>
|
||||||
|
</div>
|
||||||
|
</body></html>
|
||||||
|
After Width: | Height: | Size: 67 KiB |
@@ -0,0 +1,258 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html class="dark" lang="en"><head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta content="width=390, height=844, initial-scale=1.0" name="viewport"/>
|
||||||
|
<title>Rusty Solitaire - Daily Challenge</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700;800&family=Inter:wght@400;500;600&family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<style>
|
||||||
|
.material-symbols-outlined {
|
||||||
|
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-color: #101417;
|
||||||
|
color: #e0e3e6;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
.scanline-bg {
|
||||||
|
background: linear-gradient(to bottom, transparent 50%, rgba(26, 26, 26, 0.5) 50%);
|
||||||
|
background-size: 100% 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script id="tailwind-config">
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: "class",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
"colors": {
|
||||||
|
"on-secondary-fixed": "#161e00",
|
||||||
|
"on-error": "#690005",
|
||||||
|
"on-primary-fixed": "#001e2c",
|
||||||
|
"tertiary": "#f7c3ff",
|
||||||
|
"secondary-fixed-dim": "#bad073",
|
||||||
|
"primary-container": "#6fc2ef",
|
||||||
|
"surface-dim": "#101417",
|
||||||
|
"surface-variant": "#313538",
|
||||||
|
"on-error-container": "#ffdad6",
|
||||||
|
"warning": "#ddb26f",
|
||||||
|
"on-surface": "#e0e3e6",
|
||||||
|
"inverse-on-surface": "#2d3134",
|
||||||
|
"surface-tint": "#7ed0fe",
|
||||||
|
"error-container": "#93000a",
|
||||||
|
"on-tertiary": "#4c195b",
|
||||||
|
"info": "#12cfc0",
|
||||||
|
"tertiary-fixed": "#fbd7ff",
|
||||||
|
"tertiary-fixed-dim": "#f0b0fc",
|
||||||
|
"primary": "#a1dcff",
|
||||||
|
"on-primary": "#003549",
|
||||||
|
"inverse-surface": "#e0e3e6",
|
||||||
|
"highlight-valid": "#acc267",
|
||||||
|
"surface-container-low": "#181c1f",
|
||||||
|
"surface-container": "#1c2023",
|
||||||
|
"on-surface-variant": "#bfc8cf",
|
||||||
|
"secondary-container": "#435401",
|
||||||
|
"error": "#fb9fb1",
|
||||||
|
"surface": "#151515",
|
||||||
|
"primary-fixed": "#c4e7ff",
|
||||||
|
"outline": "#505050",
|
||||||
|
"surface-container-highest": "#313538",
|
||||||
|
"on-secondary": "#293500",
|
||||||
|
"on-primary-container": "#004f6c",
|
||||||
|
"secondary-fixed": "#d5ec8c",
|
||||||
|
"background": "#101417",
|
||||||
|
"surface-container-high": "#272a2d",
|
||||||
|
"suit-red-cb": "#6fc2ef",
|
||||||
|
"surface-container-lowest": "#0b0f11",
|
||||||
|
"suit-red": "#fb9fb1",
|
||||||
|
"on-secondary-container": "#b2c86d",
|
||||||
|
"outline-variant": "#3f484e",
|
||||||
|
"on-secondary-fixed-variant": "#3c4d00",
|
||||||
|
"inverse-primary": "#00668a",
|
||||||
|
"surface-bright": "#363a3d",
|
||||||
|
"primary-fixed-dim": "#7ed0fe",
|
||||||
|
"tertiary-container": "#e1a3ee",
|
||||||
|
"on-background": "#e0e3e6",
|
||||||
|
"on-tertiary-container": "#683476",
|
||||||
|
"suit-black": "#d0d0d0",
|
||||||
|
"on-primary-fixed-variant": "#004c69",
|
||||||
|
"secondary": "#bad073",
|
||||||
|
"on-tertiary-fixed-variant": "#653173",
|
||||||
|
"on-tertiary-fixed": "#340043",
|
||||||
|
"highlight-celebration": "#e1a3ee"
|
||||||
|
},
|
||||||
|
"borderRadius": {
|
||||||
|
"DEFAULT": "0.125rem",
|
||||||
|
"lg": "0.25rem",
|
||||||
|
"xl": "0.5rem",
|
||||||
|
"full": "0.75rem"
|
||||||
|
},
|
||||||
|
"spacing": {
|
||||||
|
"gutter-card": "0.375rem",
|
||||||
|
"stack-overlap": "2rem",
|
||||||
|
"margin-edge": "1rem",
|
||||||
|
"action-bar-height": "64px",
|
||||||
|
"touch-target-min": "48dp"
|
||||||
|
},
|
||||||
|
"fontFamily": {
|
||||||
|
"label-caps": ["JetBrains Mono"],
|
||||||
|
"hud-timer": ["JetBrains Mono"],
|
||||||
|
"card-rank": ["JetBrains Mono"],
|
||||||
|
"hud-score": ["JetBrains Mono"],
|
||||||
|
"body-md": ["Inter"],
|
||||||
|
"headline": ["JetBrains Mono"]
|
||||||
|
},
|
||||||
|
"fontSize": {
|
||||||
|
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
|
||||||
|
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||||
|
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
|
||||||
|
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}],
|
||||||
|
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||||
|
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
min-height: max(884px, 100dvh);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="flex flex-col min-h-screen max-w-[390px] mx-auto overflow-hidden shadow-2xl border-x border-outline">
|
||||||
|
<!-- 1. Status Bar -->
|
||||||
|
<div class="h-[32px] bg-[#202020] flex items-center justify-between px-margin-edge border-b border-outline">
|
||||||
|
<span class="font-hud-timer text-[12px] text-on-surface-variant">▌daily/2024-127.json</span>
|
||||||
|
<div class="bg-warning/10 border border-warning px-2 py-0.5 rounded-sm">
|
||||||
|
<span class="font-hud-timer text-[11px] text-warning font-bold tracking-tighter">EXPIRES 11:42:30</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Main Content Canvas -->
|
||||||
|
<main class="flex-1 p-margin-edge space-y-4 overflow-y-auto pb-8">
|
||||||
|
<!-- 2. Header Card -->
|
||||||
|
<section class="h-[130px] bg-[#1a1a1a] border border-[#353535] rounded-lg p-4 flex flex-col justify-between">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="font-headline font-bold text-[24px] text-suit-black leading-none">MAY 07 · 2026</span>
|
||||||
|
<span class="font-headline font-extrabold text-[32px] text-highlight-valid -tracking-[0.01em] leading-tight">#2024-127</span>
|
||||||
|
</div>
|
||||||
|
<span class="font-label-caps text-[11px] text-on-surface-variant/70">DRAW-3 · DIFFICULTY ★★★☆☆ · PAR 04:30</span>
|
||||||
|
</section>
|
||||||
|
<!-- 3. Primary CTA -->
|
||||||
|
<button class="w-full h-[64px] bg-primary-container text-surface font-headline font-bold text-[14px] uppercase tracking-wider rounded-lg active:scale-95 transition-transform duration-80 flex items-center justify-center gap-2">
|
||||||
|
<span class="material-symbols-outlined text-[18px]">play_arrow</span>
|
||||||
|
ATTEMPT TODAY'S SEED
|
||||||
|
</button>
|
||||||
|
<!-- 4. Your Attempts Card -->
|
||||||
|
<section class="h-[96px] bg-[#202020] rounded-lg p-4 flex flex-col justify-between">
|
||||||
|
<span class="font-label-caps text-[11px] text-on-surface-variant/60 uppercase">YOUR ATTEMPTS</span>
|
||||||
|
<div class="flex justify-between items-end">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="font-hud-score text-[16px] text-suit-black">BEST 04:12</span>
|
||||||
|
<div class="flex items-center gap-2 mt-1">
|
||||||
|
<span class="bg-warning text-surface text-[10px] font-bold px-1.5 py-0.5 rounded-sm">WIN</span>
|
||||||
|
<span class="font-label-caps text-[11px] text-warning">RANK 17/2843</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="font-hud-timer text-[13px] text-error mb-1">LAST: FAILED at move 47</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- 5. Leaderboard Card -->
|
||||||
|
<section class="bg-[#202020] rounded-lg p-4 flex flex-col flex-grow">
|
||||||
|
<span class="font-label-caps text-[11px] text-on-surface-variant/60 uppercase mb-4">TOP TODAY · 2,843 PLAYERS</span>
|
||||||
|
<div class="space-y-0 divide-y divide-[#353535]">
|
||||||
|
<!-- Row 1 -->
|
||||||
|
<div class="h-[32px] flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="w-5 h-5 flex items-center justify-center bg-warning text-surface text-[10px] font-bold rounded-full">01</span>
|
||||||
|
<span class="font-hud-timer text-[14px]">swift_jaguar</span>
|
||||||
|
</div>
|
||||||
|
<span class="font-hud-timer text-[14px] text-on-surface-variant">02:47</span>
|
||||||
|
</div>
|
||||||
|
<!-- Row 2 -->
|
||||||
|
<div class="h-[32px] flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="w-5 h-5 flex items-center justify-center bg-[#a0a0a0] text-surface text-[10px] font-bold rounded-full">02</span>
|
||||||
|
<span class="font-hud-timer text-[14px]">base16_fan</span>
|
||||||
|
</div>
|
||||||
|
<span class="font-hud-timer text-[14px] text-on-surface-variant">03:12</span>
|
||||||
|
</div>
|
||||||
|
<!-- Row 3 -->
|
||||||
|
<div class="h-[32px] flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="w-5 h-5 flex items-center justify-center bg-[#7a5d3b] text-surface text-[10px] font-bold rounded-full">03</span>
|
||||||
|
<span class="font-hud-timer text-[14px]">cli_player</span>
|
||||||
|
</div>
|
||||||
|
<span class="font-hud-timer text-[14px] text-on-surface-variant">03:54</span>
|
||||||
|
</div>
|
||||||
|
<!-- Row 4 -->
|
||||||
|
<div class="h-[32px] flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="w-5 h-5 flex items-center justify-center bg-[#353535] text-on-surface-variant text-[10px] font-bold rounded-full">04</span>
|
||||||
|
<span class="font-hud-timer text-[14px]">tablejockey</span>
|
||||||
|
</div>
|
||||||
|
<span class="font-hud-timer text-[14px] text-on-surface-variant">04:01</span>
|
||||||
|
</div>
|
||||||
|
<!-- Row 5 -->
|
||||||
|
<div class="h-[32px] flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="w-5 h-5 flex items-center justify-center bg-[#353535] text-on-surface-variant text-[10px] font-bold rounded-full">05</span>
|
||||||
|
<span class="font-hud-timer text-[14px]">vim_motions</span>
|
||||||
|
</div>
|
||||||
|
<span class="font-hud-timer text-[14px] text-on-surface-variant">04:05</span>
|
||||||
|
</div>
|
||||||
|
<!-- Row 17 (YOU) -->
|
||||||
|
<div class="h-[36px] flex items-center justify-between bg-primary-container/10 -mx-4 px-4 border-y border-primary-container/20">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="w-5 h-5 flex items-center justify-center bg-primary-container text-surface text-[10px] font-bold rounded-full">17</span>
|
||||||
|
<span class="font-hud-timer text-[14px] text-primary-container font-bold">(YOU) anonymous</span>
|
||||||
|
</div>
|
||||||
|
<span class="font-hud-timer text-[14px] text-primary-container font-bold">04:12</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 flex-1 border-t border-[#353535] pt-4 flex flex-col items-center justify-center opacity-30 select-none">
|
||||||
|
<span class="material-symbols-outlined text-[48px]">terminal</span>
|
||||||
|
<span class="font-label-caps text-[10px] mt-2">END OF VISIBLE LOG</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<!-- 6. Footer Navigation -->
|
||||||
|
<footer class="h-[24px] bg-background border-t border-outline flex items-center justify-between px-3">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="font-label-caps text-[10px] text-on-surface-variant">▌ NORMAL │ daily</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="font-label-caps text-[9px] text-on-surface-variant/70"><span class="text-primary-container">[ENTER]</span> attempt</span>
|
||||||
|
<span class="font-label-caps text-[9px] text-on-surface-variant/70"><span class="text-primary-container">[L]</span> full leaderboard</span>
|
||||||
|
<span class="font-label-caps text-[9px] text-on-surface-variant/70"><span class="text-primary-container">[ESC]</span> back</span>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<!-- Shared Component Shell Rendering Logic -->
|
||||||
|
<header class="w-full top-0 sticky bg-background border-b border-outline flex justify-between items-center px-margin-edge h-action-bar-height hidden">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="material-symbols-outlined text-primary">terminal</span>
|
||||||
|
<h1 class="font-headline text-headline text-primary uppercase tracking-widest">RUSTY SOLITAIRE</h1>
|
||||||
|
</div>
|
||||||
|
<span class="material-symbols-outlined text-on-surface-variant hover:text-primary transition-colors duration-120 cursor-pointer">settings</span>
|
||||||
|
</header>
|
||||||
|
<nav class="fixed bottom-0 w-full h-action-bar-height z-50 bg-surface-container border-t border-outline flex justify-around items-center px-2 hidden">
|
||||||
|
<div class="flex flex-col items-center justify-center text-on-surface-variant hover:bg-surface-container-highest transition-colors duration-120 cursor-pointer w-full h-full">
|
||||||
|
<span class="material-symbols-outlined">refresh</span>
|
||||||
|
<span class="font-label-caps text-label-caps">DEAL</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center justify-center text-on-surface-variant hover:bg-surface-container-highest transition-colors duration-120 cursor-pointer w-full h-full">
|
||||||
|
<span class="material-symbols-outlined">undo</span>
|
||||||
|
<span class="font-label-caps text-label-caps">UNDO</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center justify-center text-on-surface-variant hover:bg-surface-container-highest transition-colors duration-120 cursor-pointer w-full h-full">
|
||||||
|
<span class="material-symbols-outlined">lightbulb</span>
|
||||||
|
<span class="font-label-caps text-label-caps">HINT</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center justify-center text-primary dark:text-primary-fixed-dim hover:bg-surface-container-highest transition-colors duration-120 cursor-pointer w-full h-full">
|
||||||
|
<span class="material-symbols-outlined">menu</span>
|
||||||
|
<span class="font-label-caps text-label-caps">MENU</span>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</body></html>
|
||||||
|
After Width: | Height: | Size: 42 KiB |
@@ -0,0 +1,285 @@
|
|||||||
|
---
|
||||||
|
name: Terminal
|
||||||
|
colors:
|
||||||
|
surface: '#151515'
|
||||||
|
surface-dim: '#0d0d0d'
|
||||||
|
surface-bright: '#2a2a2a'
|
||||||
|
surface-container-lowest: '#0a0a0a'
|
||||||
|
surface-container-low: '#1a1a1a'
|
||||||
|
surface-container: '#202020'
|
||||||
|
surface-container-high: '#2a2a2a'
|
||||||
|
surface-container-highest: '#353535'
|
||||||
|
on-surface: '#d0d0d0'
|
||||||
|
on-surface-variant: '#a0a0a0'
|
||||||
|
inverse-surface: '#d0d0d0'
|
||||||
|
inverse-on-surface: '#151515'
|
||||||
|
outline: '#505050'
|
||||||
|
outline-variant: '#353535'
|
||||||
|
surface-tint: '#a54242'
|
||||||
|
primary: '#a54242'
|
||||||
|
on-primary: '#151515'
|
||||||
|
primary-container: '#3a1f1f'
|
||||||
|
on-primary-container: '#d5a8a8'
|
||||||
|
inverse-primary: '#993e3e'
|
||||||
|
secondary: '#acc267'
|
||||||
|
on-secondary: '#151515'
|
||||||
|
secondary-container: '#2a3320'
|
||||||
|
on-secondary-container: '#c5d585'
|
||||||
|
tertiary: '#e1a3ee'
|
||||||
|
on-tertiary: '#151515'
|
||||||
|
tertiary-container: '#3a2a40'
|
||||||
|
on-tertiary-container: '#eec3f5'
|
||||||
|
error: '#fb9fb1'
|
||||||
|
on-error: '#151515'
|
||||||
|
error-container: '#4a2530'
|
||||||
|
on-error-container: '#fdc3ce'
|
||||||
|
background: '#151515'
|
||||||
|
on-background: '#d0d0d0'
|
||||||
|
surface-variant: '#353535'
|
||||||
|
suit-red: '#fb9fb1'
|
||||||
|
suit-black: '#d0d0d0'
|
||||||
|
suit-red-cb: '#acc267'
|
||||||
|
highlight-valid: '#acc267'
|
||||||
|
highlight-celebration: '#e1a3ee'
|
||||||
|
highlight-warning: '#ddb26f'
|
||||||
|
highlight-info: '#12cfc0'
|
||||||
|
typography:
|
||||||
|
hud-score:
|
||||||
|
fontFamily: JetBrains Mono
|
||||||
|
fontSize: 24px
|
||||||
|
fontWeight: '700'
|
||||||
|
lineHeight: 32px
|
||||||
|
letterSpacing: '-0.02em'
|
||||||
|
hud-timer:
|
||||||
|
fontFamily: JetBrains Mono
|
||||||
|
fontSize: 16px
|
||||||
|
fontWeight: '400'
|
||||||
|
lineHeight: 24px
|
||||||
|
card-rank:
|
||||||
|
fontFamily: JetBrains Mono
|
||||||
|
fontSize: 18px
|
||||||
|
fontWeight: '700'
|
||||||
|
lineHeight: 18px
|
||||||
|
body-md:
|
||||||
|
fontFamily: Inter
|
||||||
|
fontSize: 16px
|
||||||
|
fontWeight: '400'
|
||||||
|
lineHeight: 24px
|
||||||
|
label-caps:
|
||||||
|
fontFamily: JetBrains Mono
|
||||||
|
fontSize: 12px
|
||||||
|
fontWeight: '500'
|
||||||
|
lineHeight: 16px
|
||||||
|
letterSpacing: '0.08em'
|
||||||
|
headline:
|
||||||
|
fontFamily: JetBrains Mono
|
||||||
|
fontSize: 28px
|
||||||
|
fontWeight: '700'
|
||||||
|
lineHeight: 32px
|
||||||
|
letterSpacing: '-0.01em'
|
||||||
|
rounded:
|
||||||
|
sm: 0.125rem
|
||||||
|
DEFAULT: 0.25rem
|
||||||
|
md: 0.5rem
|
||||||
|
lg: 0.75rem
|
||||||
|
xl: 1rem
|
||||||
|
full: 9999px
|
||||||
|
spacing:
|
||||||
|
margin-edge: 1rem
|
||||||
|
gutter-card: 0.375rem
|
||||||
|
stack-overlap: 2rem
|
||||||
|
touch-target-min: 48dp
|
||||||
|
---
|
||||||
|
|
||||||
|
## Brand & Style
|
||||||
|
|
||||||
|
The "Terminal" design system replaces the previous "Premium Solitaire" calm-indie aesthetic with a **retro-terminal / synthwave** identity. The intent is the visual confidence of a well-tuned terminal emulator (think Berkeley Mono dotfiles, base16-eighties, CRT phosphor): monospaced, dense, legible, snappy. It is *not* casino-glitz, *not* skeuomorphic felt, and *not* whimsical.
|
||||||
|
|
||||||
|
The personality is **technical, deliberate, slightly playful**. Cards are flat with thin colored strokes; the HUD reads like a status bar; modals look like terminal panes. Motion is short and snap-easing — no bouncy springs. Long-session calm is preserved by keeping the chroma low and reserving saturated accents for *meaning* (CTAs, feedback, celebrations) rather than decoration.
|
||||||
|
|
||||||
|
Influences: base16-eighties (Chris Kempson), Berkeley Mono, Vim/Neovim status lines, the iA Writer aesthetic, classic CRT phosphor with no chromatic aberration.
|
||||||
|
|
||||||
|
## Palette
|
||||||
|
|
||||||
|
The palette is base16-eighties — a 16-slot terminal palette where indices 00–07 form a monochrome ramp and 08–0F provide saturated accents. We map base16 slots to Material Design 3 token roles below.
|
||||||
|
|
||||||
|
### Source palette (base16-eighties)
|
||||||
|
|
||||||
|
| Slot | Hex | Role |
|
||||||
|
|---|---|---|
|
||||||
|
| base00 | `#151515` | background |
|
||||||
|
| base01 | `#202020` | surface-container |
|
||||||
|
| base02 | `#303030` | line-highlight (subtle) |
|
||||||
|
| base03 | `#505050` | outline / muted text |
|
||||||
|
| base04 | `#b0b0b0` | secondary text |
|
||||||
|
| base05 | `#d0d0d0` | foreground / on-surface |
|
||||||
|
| base06 | `#e0e0e0` | bright text |
|
||||||
|
| base07 | `#f5f5f5` | brightest highlight |
|
||||||
|
| base08 | `#fb9fb1` | red — used for `error`, `suit-red` |
|
||||||
|
| base09 | `#ddb26f` | orange — used for warning chips |
|
||||||
|
| base0A | `#acc267` | yellow/lime — used for `highlight-valid` (drag targets, valid moves) |
|
||||||
|
| base0B | `#12cfc0` | green/teal — used for `highlight-info` (toasts, neutral status) |
|
||||||
|
| base0C | `#6fc2ef` | cyan/sky — historically the primary CTA; now reserved for ad-hoc accents only |
|
||||||
|
| base0D | `#6fc2ef` | (alias) |
|
||||||
|
| base08 (project) | `#a54242` | brick red — primary CTA, focus ring, `selection` (project-specific extension; the base16-eighties `base08` slot is `#fb9fb1` pink which we keep as `error`/`suit-red`) |
|
||||||
|
| `suit-red-cb` slot | `#acc267` | lime — color-blind-mode swap for red suits (was `#6fc2ef` cyan before the 2026-05-08 primary-accent swap; lime is the next-best non-red base16-eighties accent) |
|
||||||
|
| base0E | `#e1a3ee` | violet — used for celebration (level-up, achievement unlock) |
|
||||||
|
| base0F | `#fb9fb1` | (alias) |
|
||||||
|
|
||||||
|
### Semantic assignments
|
||||||
|
|
||||||
|
- **CTA / Primary action**: brick red `#a54242`. Reserved for "Play," "New Game," "Save," "Resume," and the focus ring on selected cards. Never used decoratively. (Was cyan `#6fc2ef` before the 2026-05-08 swap.)
|
||||||
|
- **Valid-move / drag-target highlight**: lime `#acc267`. Reserved for in-game feedback only. Never appears in chrome.
|
||||||
|
- **Celebration**: lavender `#e1a3ee`. Used for level-up flashes, achievement unlock cards, and the daily-streak chip when the streak is active. Quiet otherwise.
|
||||||
|
- **Warning / soft alert**: gold `#ddb26f`. Used for "challenge expires in N minutes" chips, sync-pending status, and the daily-seed countdown.
|
||||||
|
- **Info**: teal `#12cfc0`. Used for neutral system toasts and the sync-connected indicator.
|
||||||
|
- **Error**: pink `#fb9fb1`. Used for sync conflict, server unreachable, invalid move shake.
|
||||||
|
|
||||||
|
## Suit Colors
|
||||||
|
|
||||||
|
**Two-color traditional pairing**, with mandatory color-blind
|
||||||
|
support. Saturated red for hearts + diamonds, near-white for clubs
|
||||||
|
+ spades — the "Microsoft Solitaire on dark mode" feel of a real
|
||||||
|
playing-card deck. (A brief 4-color-deck experiment shipped between
|
||||||
|
v0.21.0 and the next post-cut commit; reverted to traditional
|
||||||
|
2-color at the player's request.)
|
||||||
|
|
||||||
|
| Suit | Default | Color-blind mode | Glyph differentiation |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Hearts | `#e35353` (saturated red) | `#acc267` (lime) | Solid filled glyph |
|
||||||
|
| Diamonds | `#e35353` (saturated red) | `#acc267` (lime) | **Outlined glyph (1.5px stroke)** |
|
||||||
|
| Spades | `#e8e8e8` (near-white) | `#e8e8e8` (unchanged) | Solid filled glyph |
|
||||||
|
| Clubs | `#e8e8e8` (near-white) | `#e8e8e8` (unchanged) | **Outlined glyph (1.5px stroke)** |
|
||||||
|
|
||||||
|
The outlined-glyph treatment is the **primary** differentiation mechanism. Color is supplementary. This means a player viewing the game on a monochrome display, or with severe red-green deficiency, can still distinguish all four suits without context. This is a hard requirement, not an optional setting.
|
||||||
|
|
||||||
|
The "color-blind mode" toggle in Settings swaps both red suits (hearts + diamonds) from `#e35353` to `#acc267` (lime); clubs + spades stay at the near-white. The toggle does not turn the outlined glyphs on or off, because outlined glyphs are always on. (Was red→cyan before the 2026-05-08 primary-accent swap; CBM moved to lime to stay hue-distinct from the new red-family primary.)
|
||||||
|
|
||||||
|
## Typography
|
||||||
|
|
||||||
|
**Monospace-forward, dual-font system.**
|
||||||
|
|
||||||
|
- **JetBrains Mono** is used for: HUD (score, timer, moves), card rank/value text, all labels, all headlines, all numerals anywhere in the app, and any chip-style component. This is the dominant face.
|
||||||
|
- **Inter** is used only for: long-form body copy (Help screen, Settings descriptions, achievement tooltips, onboarding copy). It is the *exception*, not the default.
|
||||||
|
|
||||||
|
Weights: 400 regular, 500 medium for labels, 700 bold for HUD numbers and headlines. No 600 / no italics anywhere — the terminal aesthetic doesn't have them.
|
||||||
|
|
||||||
|
Letter spacing: tight (`-0.02em`) on HUD score for visual mass; wide (`+0.08em`) on uppercase labels for readability at 12px. Body uses default (0).
|
||||||
|
|
||||||
|
HUD numbers must use **tabular figures** (`font-feature-settings: 'tnum'`) so the timer and score don't reflow as digits change.
|
||||||
|
|
||||||
|
## Layout & Spacing
|
||||||
|
|
||||||
|
Optimized for **Android portrait, 390×844 (Pixel 6 baseline), API 34**.
|
||||||
|
|
||||||
|
- **Margins**: 16px (1rem) edge safety margin. *Tighter than the previous system's 24px.* Eighties palettes are dense by nature; over-padding fights the aesthetic.
|
||||||
|
- **Tableau**: 7-column layout, 32px (2rem) vertical card overlap. Tighter than before to fit a longer cascade on phone screens.
|
||||||
|
- **HUD position**: top of screen, in the system safe area. Bottom 64px holds the action bar (Undo / Hint / New Game / Auto-complete). Action bar is **always visible** in-game — no hover-fade — because there is no hover on touch.
|
||||||
|
- **Touch target minimum**: 48dp on all interactive elements. Cards in the tableau may be smaller visually but use a 48dp invisible hit area centered on the visible glyph.
|
||||||
|
|
||||||
|
## Elevation & Depth
|
||||||
|
|
||||||
|
Depth is created through **tonal layering and 1px outlines**, not blur shadows. (Synthwave-flat, not Material-soft.)
|
||||||
|
|
||||||
|
- **Level 0 (Background)**: the `#151515` base canvas.
|
||||||
|
- **Level 1 (Tableau slots, empty piles)**: 1px dashed outline in `#353535`. Empty foundations show a faint suit glyph at 12% opacity inside the outline.
|
||||||
|
- **Level 2 (Cards at rest)**: solid `#1a1a1a` fill, 1px solid border in the suit color (so the suit is detectable at a glance even if the card is partially obscured).
|
||||||
|
- **Level 3 (Active / dragged card)**: same border, but glow effect: 0 0 12px of `#a54242` at 40% opacity. **No scale transform** — flatness preserved. Z-index lifts above siblings.
|
||||||
|
- **Modals**: full-screen with backdrop `#151515` at 95% opacity (just enough to dim the table without blurring it). Modal panel is `#202020` with a 1px `#505050` border — like a terminal pane.
|
||||||
|
- **Toasts**: bottom of screen, `#202020` fill, 1px border in the toast's accent color (info=teal, warning=gold, error=pink, celebration=lavender). 16px monospaced caption.
|
||||||
|
|
||||||
|
No `box-shadow` is used anywhere. **All depth is achieved with borders and tonal value.** This is a hard constraint.
|
||||||
|
|
||||||
|
## Shapes
|
||||||
|
|
||||||
|
The shape language is **soft-rounded but tight**:
|
||||||
|
|
||||||
|
- **Cards**: `rounded-md` (8px) — slightly less rounded than the previous system's 16px to read more "technical."
|
||||||
|
- **Buttons / chips / inputs**: `rounded` (4px) default, `rounded-sm` (2px) for the smallest chips.
|
||||||
|
- **Modals / sheets**: `rounded-lg` (12px).
|
||||||
|
- **Avatars / circular indicators**: `rounded-full`.
|
||||||
|
- **Card-back pattern corners**: matches the card's `rounded-md`.
|
||||||
|
|
||||||
|
Selection highlights use a **2px inset stroke** in `#a54242` following the host shape's corner radius. Never an outer stroke — the outer stroke is reserved for the suit-color hairline.
|
||||||
|
|
||||||
|
## Motion
|
||||||
|
|
||||||
|
**Snappy, no spring.** All transitions use `ease-out` with a 120ms duration unless specified.
|
||||||
|
|
||||||
|
- Card lift (start drag): 80ms.
|
||||||
|
- Card place (drop): 120ms with a 16ms holdframe (no bounce).
|
||||||
|
- Modal enter: 200ms ease-out, fade + 8px translate-up.
|
||||||
|
- Modal exit: 120ms ease-in, fade only.
|
||||||
|
- Selection ring appear: 80ms.
|
||||||
|
- Win-summary stat reveal: 60ms each, staggered 40ms.
|
||||||
|
- HUD number tick: instant (no transition) — terminal counters don't ease.
|
||||||
|
|
||||||
|
**Optional CRT effect**: a 1-frame scanline sweep across the screen on game-state transitions (start, win, restart). User-toggleable in Settings. Off by default.
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### Game Cards
|
||||||
|
|
||||||
|
Flat face design.
|
||||||
|
- Background: `#1a1a1a`
|
||||||
|
- Border: none. The card shape is defined by the body fill alone against the play surface. The earlier 1px suit-coloured border was removed because it produced visible anti-aliasing artifacts at the rounded corners (a "gray sliver" where the colored stroke faded through gray pixels into the dark play surface). The 5-unit brightness gap between `#1a1a1a` body and `#151515` surface is enough to read as a card edge without an explicit stroke.
|
||||||
|
- Top-left: rank in JetBrains Mono Bold 18px + small suit glyph (10px)
|
||||||
|
- Bottom-right: large suit glyph (32px), upright (same orientation as the top-left small glyph — single-orientation digital play does not benefit from the traditional 180° inverted-corner indicator)
|
||||||
|
- Corner radius: 8px
|
||||||
|
- Suit differentiation: hearts and spades have **filled** glyphs; diamonds and clubs have **outlined** glyphs (1.5px stroke)
|
||||||
|
|
||||||
|
### Card Back ("Terminal" theme)
|
||||||
|
|
||||||
|
- Theme name: `"Terminal"`
|
||||||
|
- Author: `"Rusty Solitaire"`
|
||||||
|
- Background: `#151515`
|
||||||
|
- Pattern: horizontal scanlines at 2px pitch in `#1a1a1a` (1px line, 1px gap), full bleed
|
||||||
|
- Border: 1px solid `#353535`
|
||||||
|
- Top-left badge: a 12×16px solid `#a54242` block (the "terminal cursor"), 6px from the corner
|
||||||
|
- Bottom-right monogram: the characters `▌RS` in JetBrains Mono 12px, color `#505050`, 6px from the corner
|
||||||
|
- Corner radius: 8px (matches face)
|
||||||
|
|
||||||
|
### Primary Buttons
|
||||||
|
|
||||||
|
Solid `#a54242` fill, `#151515` text, JetBrains Mono Medium 14px uppercase with `+0.08em` tracking. 4px corner radius. Pressed state: darken to `#7a3030`. Disabled: `#353535` fill, `#505050` text.
|
||||||
|
|
||||||
|
### Secondary Buttons
|
||||||
|
|
||||||
|
Transparent fill, 1px `#505050` border, `#d0d0d0` text. Hover/press: border becomes `#a54242`, text becomes `#a54242`.
|
||||||
|
|
||||||
|
### HUD Chips
|
||||||
|
|
||||||
|
`#202020` fill, no border, 4px radius. Monospaced 16px text. Score chip pulses to `#acc267` for 200ms when score increases.
|
||||||
|
|
||||||
|
### Drag Targets
|
||||||
|
|
||||||
|
When a card is being dragged over a valid pile, the pile's empty-slot dashed outline becomes:
|
||||||
|
- Solid 1px in `#acc267`
|
||||||
|
- Plus a 0 0 8px outer glow in `#acc267` at 30% opacity
|
||||||
|
|
||||||
|
This is the *only* place glow effects appear in the system.
|
||||||
|
|
||||||
|
### Modals
|
||||||
|
|
||||||
|
Full-screen backdrop at 95% opacity. Centered panel: `#202020` fill, 1px `#505050` border, 12px corner radius. Title bar shows the screen name in monospaced 14px, color `#a0a0a0`, with a single `▌` cursor character prefix to reinforce the terminal pane motif.
|
||||||
|
|
||||||
|
### Navigation Bar
|
||||||
|
|
||||||
|
Fixed at the bottom of in-game screens. Height: 64px. `#202020` fill, 1px top border in `#353535`. Four icon buttons: Undo / Hint / New / Auto-complete. Icons: 24px, 1.5px stroke weight, color `#d0d0d0`. Active/pressed: icon color `#a54242`.
|
||||||
|
|
||||||
|
### Status / Sync Indicator
|
||||||
|
|
||||||
|
Top-right corner of the HUD: a 6px circular dot.
|
||||||
|
- Connected & synced: `#12cfc0`
|
||||||
|
- Pending: `#ddb26f` (pulsing 1.5s)
|
||||||
|
- Error: `#fb9fb1` (steady)
|
||||||
|
- Offline: `#505050`
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
1. **Color-blind mode** (Settings → Gameplay): swaps the red suits' default `#e35353` for `#acc267` (lime). Outlined-glyph differentiation remains active in *all* modes.
|
||||||
|
2. **High-contrast mode** (Settings → Gameplay): boosts on-surface from `#d0d0d0` to `#f5f5f5`, outline from `#505050` to `#a0a0a0`, suit-red from `#fb9fb1` to `#ff8aa0`.
|
||||||
|
3. **Reduce-motion mode** (Settings → Gameplay): disables card-lift transition (instant z-lift), disables CRT scanline effect, disables the warning-chip pulse animation.
|
||||||
|
4. **Tabular figures** are mandatory for any number that updates live (timer, score, moves) so they don't reflow.
|
||||||
|
5. **Touch targets** are 48dp minimum even when the visual element is smaller.
|
||||||
|
6. **Text contrast**: all body text on background passes WCAG AA at minimum (`#d0d0d0` on `#151515` = 9.5:1; `#a0a0a0` on `#151515` = 5.7:1).
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
# Terminal — Desktop Adaptation Spec
|
||||||
|
|
||||||
|
> **Why this exists.** The 24 mockups in this directory are mobile
|
||||||
|
> (390 × 844 logical, iPhone 14 Pro frame) with one exception
|
||||||
|
> (`home-menu-desktop.html`). The Stitch project that produced them
|
||||||
|
> is named "Solitaire Quest *Mobile* Redesign" — the mobile-first
|
||||||
|
> framing was deliberate when the new Android target opened, but
|
||||||
|
> desktop is still the primary delivery surface. Porting the mobile
|
||||||
|
> mockups 1:1 would land a 390-px-wide column floating in the middle
|
||||||
|
> of an 1800 × 1100 window. This file is the rules-based desktop
|
||||||
|
> companion — apply these adaptations whenever you port a Bevy
|
||||||
|
> plugin against a mobile mockup in this directory.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
* **Token system.** All tokens (palette, type scale, spacing,
|
||||||
|
radii, motion) in `design-system.md` are layout-agnostic and
|
||||||
|
apply unchanged on both targets. Do **not** introduce desktop-
|
||||||
|
specific token variants — adapt geometry, not tokens.
|
||||||
|
* **Already adapted in code.** v0.20.0's port is layout-agnostic
|
||||||
|
(modal scaffold, toasts, table chrome, card chrome, gameplay-
|
||||||
|
feedback, splash cursor). Those surfaces already adapt
|
||||||
|
correctly because their Bevy UI nodes use flex / percent /
|
||||||
|
stretch sizing rather than fixed pixel widths from the
|
||||||
|
mockups.
|
||||||
|
* **Not yet adapted in code.** Any future plugin port that
|
||||||
|
copies layout from a mobile mockup must apply the rules below.
|
||||||
|
|
||||||
|
## Viewport assumptions
|
||||||
|
|
||||||
|
| Range | Width × height | Source |
|
||||||
|
|---|---|---|
|
||||||
|
| Mobile target | 390 × 844 | iPhone 14 Pro logical, Stitch mockup canvas |
|
||||||
|
| Desktop minimum | 1024 × 600 | Smaller windows degrade to mobile rules |
|
||||||
|
| Desktop default | ~70 % of monitor | `apply_smart_default_window_size` (since v0.19.0) |
|
||||||
|
| Desktop typical | 1600 × 900 to 2560 × 1440 | The range we tune for |
|
||||||
|
| Desktop max | 3840 × 2160 | 4K, with HiDPI scaling already applied |
|
||||||
|
|
||||||
|
The "smart default" sizer means a 1080p monitor opens a ~1344 × 756
|
||||||
|
window, a 1440p monitor opens ~1792 × 1008, a 4K monitor opens
|
||||||
|
~2688 × 1512. Tune for the 1600–2400 width band as the centre of
|
||||||
|
the distribution; below 1024 width, fall back to the mobile rules
|
||||||
|
verbatim.
|
||||||
|
|
||||||
|
## Universal adaptation rules
|
||||||
|
|
||||||
|
Apply these to every screen unless the per-screen section
|
||||||
|
overrides them.
|
||||||
|
|
||||||
|
### 1. Edge margins
|
||||||
|
|
||||||
|
| Mobile | Desktop |
|
||||||
|
|---|---|
|
||||||
|
| `margin-edge: 16px` (`SPACE_4`) | `SPACE_5` (24 px) for windows < 1440 wide; `SPACE_6` (32 px) for 1440–2400; `SPACE_7` (48 px) for ≥ 2400 |
|
||||||
|
|
||||||
|
Engine: drive from `LayoutResource` based on `Window` size, not a
|
||||||
|
constant.
|
||||||
|
|
||||||
|
### 2. Modal max-width
|
||||||
|
|
||||||
|
| Mobile | Desktop |
|
||||||
|
|---|---|
|
||||||
|
| `100% - 2 × edge-margin` | `min(720 px, 50 % of viewport)` |
|
||||||
|
|
||||||
|
The 720 px cap is already in `ui_modal::spawn_modal`. No code
|
||||||
|
change needed; this rule documents *why* it's there.
|
||||||
|
|
||||||
|
### 3. Vertical content stacks
|
||||||
|
|
||||||
|
A mobile screen often stacks `Header → Body → Footer` vertically
|
||||||
|
to fit a tall narrow column. On desktop, prefer horizontal
|
||||||
|
distribution where the content allows:
|
||||||
|
|
||||||
|
* **Header rows that stack vertically on mobile** (title above
|
||||||
|
count above timer) → keep them in one horizontal row on
|
||||||
|
desktop.
|
||||||
|
* **Two-column flex layouts** (e.g. Settings rows: label left,
|
||||||
|
control right) — already work on both targets; no change.
|
||||||
|
* **Cards stacking with `mt-48`-style fixed gaps** — replace with
|
||||||
|
flex / percent gaps so the layout breathes.
|
||||||
|
|
||||||
|
### 4. Touch-target minimums
|
||||||
|
|
||||||
|
Mobile spec mandates 48 dp minimum touch targets. Desktop has no
|
||||||
|
such floor (mouse precision is finer), but **don't shrink below
|
||||||
|
mobile's 48 px** for primary actions — keyboard / gamepad focus
|
||||||
|
rings still need a visible target.
|
||||||
|
|
||||||
|
Secondary controls (chip-style toggles, hotkey hints, etc.) can
|
||||||
|
shrink to `TYPE_BODY` (14 px) text + `SPACE_3` (12 px) padding on
|
||||||
|
desktop where they were larger on mobile.
|
||||||
|
|
||||||
|
### 5. Bottom-anchored elements
|
||||||
|
|
||||||
|
Mobile mockups often anchor key controls (action bar, primary CTA,
|
||||||
|
toast position) to the bottom of the viewport for thumb reach.
|
||||||
|
Desktop has no thumb-reach concern:
|
||||||
|
|
||||||
|
* **Toasts** — keep bottom-anchored (already done in `a137607`),
|
||||||
|
the design language is consistent across targets and the
|
||||||
|
bottom is still the least-disruptive overlay zone.
|
||||||
|
* **Action bars** — top of viewport on desktop unless the
|
||||||
|
per-screen section says otherwise. The HUD already sits on
|
||||||
|
top.
|
||||||
|
* **Single primary CTA** — modals already right-align in the
|
||||||
|
actions row; no change.
|
||||||
|
|
||||||
|
### 6. Typography rungs unchanged
|
||||||
|
|
||||||
|
Do **not** shift `TYPE_*` tokens up a rung for desktop. The
|
||||||
|
spec's 14 / 18 / 26 / 40 progression is already calibrated for
|
||||||
|
the desktop reading distance (60–90 cm). Mobile uses the same
|
||||||
|
rungs at a closer reading distance (30–40 cm); same physical
|
||||||
|
angular size on the eye.
|
||||||
|
|
||||||
|
### 7. Hotkey hints become full strings
|
||||||
|
|
||||||
|
Mobile cells like `▌Esc` — the cursor block plus key letter — can
|
||||||
|
expand to `[Esc] cancel` style on desktop where horizontal
|
||||||
|
real-estate is cheap. Drives discoverability of keyboard-only
|
||||||
|
flows. Optional; only apply where horizontal space exists.
|
||||||
|
|
||||||
|
## Per-screen adaptation rules
|
||||||
|
|
||||||
|
### Game Table
|
||||||
|
|
||||||
|
Mockup: `game-table-mobile.html` (390 × 844).
|
||||||
|
|
||||||
|
| Element | Mobile | Desktop |
|
||||||
|
|---|---|---|
|
||||||
|
| HUD band | full width, 56 px tall | full width, 48 px tall |
|
||||||
|
| Foundation row | 4 piles centred, fan-tight | 4 piles centred, **gutter doubled** so the row fills ~50 % of viewport width |
|
||||||
|
| Stock + waste | left of foundations, stacked | left of foundations, **horizontal pair**: stock on the left, waste to its immediate right (the mobile vertical pair feels cramped on a wide canvas) |
|
||||||
|
| Tableau row | 7 columns, 4 % gutter | 7 columns, **6 % gutter**, total tableau block ≤ 70 % viewport width |
|
||||||
|
| Card aspect | 2 : 3 (already in `Layout::card_size`) | unchanged — card aspect is domain |
|
||||||
|
| Tableau fan | `TABLEAU_FAN_FRAC = 0.25` | unchanged — fan is in card-height units, not viewport units |
|
||||||
|
| Drag-shadow offset | small | unchanged — pinned to 0 alpha under Terminal anyway |
|
||||||
|
|
||||||
|
**Engine impact:** `solitaire_engine/src/layout.rs::compute_layout`
|
||||||
|
already drives most of this from `Window::size()`. The mobile vs.
|
||||||
|
desktop difference is the gutter percentages — bake desktop
|
||||||
|
gutters when window width ≥ 1024.
|
||||||
|
|
||||||
|
### Win Summary
|
||||||
|
|
||||||
|
Mockup: `win-summary-mobile.html` (390 × 858).
|
||||||
|
|
||||||
|
| Element | Mobile | Desktop |
|
||||||
|
|---|---|---|
|
||||||
|
| Modal width | 100 % − 2 × edge | **`min(720 px, 50 % viewport)`** (already done by `ui_modal`) |
|
||||||
|
| Score row | stacked vertically (line per metric) | **3-column grid**: Score / Time / Moves in one row, breakdown rows below in single-line per row |
|
||||||
|
| Action buttons | full-width stacked (Play Again, Continue, Stats) | **right-aligned action row** — the existing `spawn_modal_actions` already does this on both targets |
|
||||||
|
|
||||||
|
**Engine impact:** `solitaire_engine/src/win_summary_plugin.rs`. The
|
||||||
|
score-breakdown-stagger animation (`MOTION_SCORE_BREAKDOWN_*`) is
|
||||||
|
unchanged across targets.
|
||||||
|
|
||||||
|
### Settings
|
||||||
|
|
||||||
|
Mockup: `settings-mobile.html` (390 × 4330 — long scroll).
|
||||||
|
|
||||||
|
| Element | Mobile | Desktop |
|
||||||
|
|---|---|---|
|
||||||
|
| Modal width | 100 % − 2 × edge | `min(720 px, 50 % viewport)` |
|
||||||
|
| Sections | full-width labels above stacked controls | **section labels left, control widget right** — already the engine's pattern; no change |
|
||||||
|
| Long page | scroll the whole modal | **two-column layout**: nav (sections list) on left ~30 %, current section on right ~70 %. Reduces scroll distance on desktop |
|
||||||
|
| Sliders | full-width on mobile | cap at 320 px on desktop |
|
||||||
|
|
||||||
|
**Engine impact:** if a desktop port wants the two-column nav, it's
|
||||||
|
a `settings_plugin` rewrite. Keep the existing single-column
|
||||||
|
stacked-modal layout for now — it works on both targets and the
|
||||||
|
two-column variant is a polish item, not a blocker.
|
||||||
|
|
||||||
|
### Help & Controls
|
||||||
|
|
||||||
|
Mockup: `help-mobile.html` (390 × 2544).
|
||||||
|
|
||||||
|
| Element | Mobile | Desktop |
|
||||||
|
|---|---|---|
|
||||||
|
| Modal width | 100 % − 2 × edge | `min(720 px, 50 % viewport)` |
|
||||||
|
| Section list | one column of `Heading → 2-col rows` | **two columns of section blocks** for windows ≥ 1280 wide; halves vertical scroll distance |
|
||||||
|
| Hotkey rows | `key | description` 2-col flex | unchanged; 2-col already adapts |
|
||||||
|
|
||||||
|
**Engine impact:** `help_plugin`. Single-column on mobile, 2-col
|
||||||
|
on desktop windows ≥ 1280 wide is a flex-wrap option.
|
||||||
|
|
||||||
|
### Pause Menu
|
||||||
|
|
||||||
|
Mockup: `pause-menu-mobile.html` (390 × 1768).
|
||||||
|
|
||||||
|
Already a small modal; no significant geometry change. Modal
|
||||||
|
already uses `ui_modal::spawn_modal` which caps width and centres.
|
||||||
|
No desktop-specific rule.
|
||||||
|
|
||||||
|
### Home Menu
|
||||||
|
|
||||||
|
Mockup: `home-menu-mobile.html` and `home-menu-desktop.html`
|
||||||
|
(both already in this directory — desktop variant is the
|
||||||
|
authoritative reference).
|
||||||
|
|
||||||
|
The desktop mockup already specifies the layout. Cross-check it
|
||||||
|
against the mobile version when porting; differences are
|
||||||
|
deliberate (more horizontal real-estate, larger primary CTA, the
|
||||||
|
secondary actions row).
|
||||||
|
|
||||||
|
### Splash
|
||||||
|
|
||||||
|
Mockup: `splash-mobile.html` (390 × 844).
|
||||||
|
|
||||||
|
| Element | Mobile | Desktop |
|
||||||
|
|---|---|---|
|
||||||
|
| Full-screen overlay | `inset-0` | unchanged — splash always covers the viewport |
|
||||||
|
| Cursor block (`▌`) | 96 px JetBrains Mono | unchanged — already done in `cdcadda`. The 96 px size scales fine on desktop because the splash is a brand beat, not a layout-driven element |
|
||||||
|
| Title `RUSTY SOLITAIRE` | 32 px | scale to 40 px (`TYPE_DISPLAY`) on desktop |
|
||||||
|
| Subtitle `TERMINAL EDITION` | 12 px | unchanged |
|
||||||
|
| Boot log lines | 70 % width column | cap at 480 px so the column doesn't stretch on a wide window |
|
||||||
|
| Progress bar | 100 % − 2 × edge | cap at 720 px |
|
||||||
|
| Palette swatch row + version footer | bottom-anchored | unchanged; bottom-anchor still reads correctly on desktop |
|
||||||
|
|
||||||
|
**Engine impact:** `splash_plugin` already has the cursor block
|
||||||
|
(`cdcadda`). The boot log / progress bar / palette swatch rows
|
||||||
|
are the next polish increment when option D is picked up.
|
||||||
|
|
||||||
|
### Stats
|
||||||
|
|
||||||
|
Mockup: `stats-mobile.html` (390 × 2624).
|
||||||
|
|
||||||
|
| Element | Mobile | Desktop |
|
||||||
|
|---|---|---|
|
||||||
|
| Modal width | 100 % − 2 × edge | `min(720 px, 50 % viewport)` |
|
||||||
|
| Big-number cards | 2 × 2 grid | **4 × 1 row** for windows ≥ 1024 wide (the four headline metrics fit in a single horizontal row at desktop scale) |
|
||||||
|
| Latest-win caption | full-width line | unchanged |
|
||||||
|
| Replay clip / share row | full-width row | unchanged |
|
||||||
|
|
||||||
|
### Profile / Achievements / Theme Picker / Daily Challenge
|
||||||
|
|
||||||
|
These follow the **standard modal pattern** (`spawn_modal` with
|
||||||
|
header / body / actions). They already work on desktop because
|
||||||
|
`ui_modal` handles modal-width capping. Per-screen tweaks are
|
||||||
|
small and listed below; no structural changes:
|
||||||
|
|
||||||
|
* **Profile** — avatar + level / streak chips can flow into a
|
||||||
|
single horizontal row on desktop instead of stacking.
|
||||||
|
* **Achievements** — 3 × N grid on mobile becomes 4 × N or 5 × N
|
||||||
|
on desktop where windows ≥ 1280 wide.
|
||||||
|
* **Theme Picker** — 2-col grid of theme cards on mobile becomes
|
||||||
|
3- or 4-col on desktop.
|
||||||
|
* **Daily Challenge** — single-column scroll on both; no change.
|
||||||
|
|
||||||
|
## Mockup parity gap
|
||||||
|
|
||||||
|
The 9 missing-plugin screens (`splash`, `challenge`, `time-attack`,
|
||||||
|
`weekly-goals`, `leaderboard`, `sync`, `level-up`, `replay-overlay`,
|
||||||
|
`radial-menu`) have only mobile mockups. When porting any of these
|
||||||
|
plugins:
|
||||||
|
|
||||||
|
1. Read the mobile mockup for content + visual hierarchy.
|
||||||
|
2. Apply the universal adaptation rules above.
|
||||||
|
3. Apply the closest matching per-screen rule (e.g. an info modal
|
||||||
|
uses the same shape as Win Summary or Stats).
|
||||||
|
4. **No new layout pattern without explicit user approval.**
|
||||||
|
Adapting an existing pattern is in scope; inventing a desktop-
|
||||||
|
specific component is design work and should be flagged as such.
|
||||||
|
|
||||||
|
## Process notes
|
||||||
|
|
||||||
|
* **Smart-default sizer is the layout's source of truth.** Before
|
||||||
|
reading the mockup, always re-read `Window::size()` —
|
||||||
|
`apply_smart_default_window_size` runs at startup and the
|
||||||
|
player can resize freely. Hardcoded breakpoints in plugin code
|
||||||
|
should reference the *current* `Window` width via a
|
||||||
|
`LayoutResource` lookup, not the launch size.
|
||||||
|
* **`WindowResized` already drives layout recomputes** (CLAUDE.md
|
||||||
|
§3.4). Any per-window-width adaptation in this file should hook
|
||||||
|
into the existing recompute path, not a new system.
|
||||||
|
* **Mobile rules win at narrow desktop windows.** A user dragging
|
||||||
|
their desktop window down to 600 px width is closer to the
|
||||||
|
mobile use-case than the desktop one. Below 1024 px width,
|
||||||
|
apply the mobile rules verbatim.
|
||||||
|
* **Run on a 4K monitor before declaring a port done.** HiDPI
|
||||||
|
scaling routes through Bevy's logical sizing, but visual
|
||||||
|
polish (border thickness, motion budgets at high refresh rate)
|
||||||
|
is worth eyeballing.
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html class="dark" lang="en"><head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@400;500&family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<style>
|
||||||
|
.material-symbols-outlined {
|
||||||
|
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||||
|
}
|
||||||
|
.outlined-glyph {
|
||||||
|
-webkit-text-stroke: 1.5px currentColor;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
.scanline-pattern {
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
#1a1a1a,
|
||||||
|
#1a1a1a 2px,
|
||||||
|
#151515 2px,
|
||||||
|
#151515 4px
|
||||||
|
);
|
||||||
|
}
|
||||||
|
.tabular-nums {
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script id="tailwind-config">
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: "class",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
"colors": {
|
||||||
|
"surface": "#151515",
|
||||||
|
"secondary-fixed": "#d5ec8c",
|
||||||
|
"warning": "#ddb26f",
|
||||||
|
"tertiary-fixed": "#fbd7ff",
|
||||||
|
"on-tertiary-fixed-variant": "#653173",
|
||||||
|
"on-background": "#e0e3e6",
|
||||||
|
"on-primary-container": "#004f6c",
|
||||||
|
"surface-container-lowest": "#0b0f11",
|
||||||
|
"on-surface": "#e0e3e6",
|
||||||
|
"error": "#fb9fb1",
|
||||||
|
"primary-fixed-dim": "#7ed0fe",
|
||||||
|
"inverse-primary": "#00668a",
|
||||||
|
"surface-container-high": "#272a2d",
|
||||||
|
"suit-red-cb": "#6fc2ef",
|
||||||
|
"surface-bright": "#363a3d",
|
||||||
|
"on-primary": "#003549",
|
||||||
|
"on-tertiary": "#4c195b",
|
||||||
|
"error-container": "#93000a",
|
||||||
|
"on-tertiary-fixed": "#340043",
|
||||||
|
"surface-container": "#202020",
|
||||||
|
"tertiary-container": "#e1a3ee",
|
||||||
|
"on-primary-fixed-variant": "#004c69",
|
||||||
|
"surface-container-highest": "#313538",
|
||||||
|
"highlight-celebration": "#e1a3ee",
|
||||||
|
"highlight-valid": "#acc267",
|
||||||
|
"primary": "#a1dcff",
|
||||||
|
"secondary-fixed-dim": "#bad073",
|
||||||
|
"on-primary-fixed": "#001e2c",
|
||||||
|
"on-error-container": "#ffdad6",
|
||||||
|
"secondary": "#bad073",
|
||||||
|
"on-tertiary": "#293500",
|
||||||
|
"on-secondary-container": "#b2c86d",
|
||||||
|
"inverse-on-surface": "#2d3134",
|
||||||
|
"on-error": "#690005",
|
||||||
|
"info": "#12cfc0",
|
||||||
|
"suit-red": "#fb9fb1",
|
||||||
|
"surface-dim": "#101417",
|
||||||
|
"surface-tint": "#7ed0fe",
|
||||||
|
"background": "#101417",
|
||||||
|
"secondary-container": "#435401",
|
||||||
|
"surface-variant": "#313538",
|
||||||
|
"outline-variant": "#3f484e",
|
||||||
|
"on-surface-variant": "#bfc8cf",
|
||||||
|
"primary-fixed": "#c4e7ff",
|
||||||
|
"on-secondary-fixed-variant": "#3c4d00",
|
||||||
|
"on-secondary-fixed": "#161e00",
|
||||||
|
"suit-black": "#d0d0d0"
|
||||||
|
},
|
||||||
|
"borderRadius": {
|
||||||
|
"DEFAULT": "0.125rem",
|
||||||
|
"lg": "0.25rem",
|
||||||
|
"xl": "0.5rem",
|
||||||
|
"full": "0.75rem"
|
||||||
|
},
|
||||||
|
"spacing": {
|
||||||
|
"touch-target-min": "48px",
|
||||||
|
"margin-edge": "1rem",
|
||||||
|
"action-bar-height": "64px",
|
||||||
|
"stack-overlap": "2rem",
|
||||||
|
"gutter-card": "0.375rem"
|
||||||
|
},
|
||||||
|
"fontFamily": {
|
||||||
|
"label-caps": ["JetBrains Mono"],
|
||||||
|
"headline": ["JetBrains Mono"],
|
||||||
|
"card-rank": ["JetBrains Mono"],
|
||||||
|
"body-md": ["Inter"],
|
||||||
|
"hud-score": ["JetBrains Mono"],
|
||||||
|
"hud-timer": ["JetBrains Mono"]
|
||||||
|
},
|
||||||
|
"fontSize": {
|
||||||
|
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
|
||||||
|
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
|
||||||
|
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
|
||||||
|
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||||
|
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}],
|
||||||
|
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
min-height: max(884px, 100dvh);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-surface text-on-surface font-body-md overflow-hidden selection:bg-primary selection:text-surface">
|
||||||
|
<!-- TopAppBar -->
|
||||||
|
<header class="fixed top-0 w-full flex justify-between items-center px-margin-edge h-[56px] bg-surface-container border-b border-outline dark:border-outline z-50">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="material-symbols-outlined text-primary">terminal</span>
|
||||||
|
<h1 class="font-hud-score text-[18px] text-primary">solitaire.sh</h1>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<div class="w-[6px] h-[6px] rounded-full bg-info"></div>
|
||||||
|
<span class="material-symbols-outlined text-on-surface-variant">settings</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<!-- HUD Band -->
|
||||||
|
<div class="fixed top-[56px] left-0 w-full h-[56px] bg-surface-container border-b border-outline-variant flex items-center justify-around px-margin-edge z-40">
|
||||||
|
<div class="bg-surface p-1 px-3 rounded flex flex-col items-center">
|
||||||
|
<span class="font-label-caps text-[10px] text-on-surface-variant">SCORE</span>
|
||||||
|
<span class="font-hud-score text-primary tabular-nums">247</span>
|
||||||
|
</div>
|
||||||
|
<div class="bg-surface p-1 px-3 rounded flex flex-col items-center border border-outline">
|
||||||
|
<span class="font-label-caps text-[10px] text-on-surface-variant">TIME</span>
|
||||||
|
<span class="font-hud-timer text-on-surface tabular-nums">12:34</span>
|
||||||
|
</div>
|
||||||
|
<div class="bg-surface p-1 px-3 rounded flex flex-col items-center">
|
||||||
|
<span class="font-label-caps text-[10px] text-on-surface-variant">MOVES</span>
|
||||||
|
<span class="font-hud-score text-secondary tabular-nums">87</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Main Game Table -->
|
||||||
|
<main class="pt-[124px] px-margin-edge h-screen w-full relative">
|
||||||
|
<!-- Top Row: Stock, Waste, Foundations -->
|
||||||
|
<div class="grid grid-cols-7 gap-gutter-card h-[110px]">
|
||||||
|
<!-- Stock -->
|
||||||
|
<div class="relative w-full h-full rounded-xl border border-outline-variant bg-surface overflow-hidden scanline-pattern">
|
||||||
|
<div class="absolute top-1 left-1 w-3 h-4 bg-suit-red-cb"></div>
|
||||||
|
<div class="absolute bottom-1 right-1 font-label-caps text-[8px] text-suit-black">▌RS</div>
|
||||||
|
<div class="absolute bottom-[-16px] left-0 w-full text-center font-label-caps text-[10px] text-on-surface-variant">STOCK · 18</div>
|
||||||
|
</div>
|
||||||
|
<!-- Waste -->
|
||||||
|
<div class="relative w-full h-full rounded-xl border border-suit-red bg-[#1a1a1a] flex flex-col justify-between p-1.5">
|
||||||
|
<div class="font-card-rank text-suit-red leading-none">10<br/><span class="font-normal">♥</span></div>
|
||||||
|
<div class="self-end text-[32px] font-card-rank text-suit-red rotate-180">♥</div>
|
||||||
|
</div>
|
||||||
|
<!-- Empty Gap -->
|
||||||
|
<div></div>
|
||||||
|
<!-- Foundation S -->
|
||||||
|
<div class="relative w-full h-full rounded-xl border border-dashed border-outline-variant flex items-center justify-center">
|
||||||
|
<span class="text-on-surface-variant opacity-20 text-[32px] font-card-rank">♠</span>
|
||||||
|
</div>
|
||||||
|
<!-- Foundation H -->
|
||||||
|
<div class="relative w-full h-full rounded-xl border border-suit-red bg-[#1a1a1a] flex flex-col justify-between p-1.5">
|
||||||
|
<div class="font-card-rank text-suit-red leading-none">2<br/><span class="font-normal">♥</span></div>
|
||||||
|
<div class="self-end text-[32px] font-card-rank text-suit-red rotate-180">♥</div>
|
||||||
|
</div>
|
||||||
|
<!-- Foundation C -->
|
||||||
|
<div class="relative w-full h-full rounded-xl border border-dashed border-outline-variant flex items-center justify-center">
|
||||||
|
<span class="text-on-surface-variant opacity-20 text-[32px] font-card-rank outlined-glyph">♣</span>
|
||||||
|
</div>
|
||||||
|
<!-- Foundation D -->
|
||||||
|
<div class="relative w-full h-full rounded-xl border border-dashed border-outline-variant flex items-center justify-center">
|
||||||
|
<span class="text-on-surface-variant opacity-20 text-[32px] font-card-rank outlined-glyph">♦</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Tableau -->
|
||||||
|
<div class="mt-8 grid grid-cols-7 gap-gutter-card items-start relative h-[400px]">
|
||||||
|
<!-- Col 1 -->
|
||||||
|
<div class="relative w-full h-full">
|
||||||
|
<div class="w-full h-[96px] rounded-xl border border-suit-black bg-[#1a1a1a] p-1.5">
|
||||||
|
<div class="font-card-rank text-suit-black leading-none">K<br/><span class="font-normal">♠</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Col 2 -->
|
||||||
|
<div class="relative w-full h-full">
|
||||||
|
<div class="w-full h-[96px] rounded-xl border border-outline bg-[#1a1a1a] scanline-pattern"></div>
|
||||||
|
<div class="absolute top-[32px] w-full h-[96px] rounded-xl border border-suit-red bg-[#1a1a1a] p-1.5">
|
||||||
|
<div class="font-card-rank text-suit-red leading-none">Q<br/><span class="font-normal">♥</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Col 3 -->
|
||||||
|
<div class="relative w-full h-full">
|
||||||
|
<div class="w-full h-[96px] rounded-xl border border-outline bg-[#1a1a1a] scanline-pattern"></div>
|
||||||
|
<div class="absolute top-[32px] w-full h-[96px] rounded-xl border border-outline bg-[#1a1a1a] scanline-pattern"></div>
|
||||||
|
<div class="absolute top-[64px] w-full h-[96px] rounded-xl border border-suit-red bg-[#1a1a1a] p-1.5">
|
||||||
|
<div class="font-card-rank text-suit-red leading-none">10<br/><span class="font-normal outlined-glyph">♦</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Col 4 -->
|
||||||
|
<div class="relative w-full h-full">
|
||||||
|
<div class="w-full h-[96px] rounded-xl border border-outline bg-[#1a1a1a] scanline-pattern"></div>
|
||||||
|
<div class="w-full h-[96px] rounded-xl border border-outline bg-[#1a1a1a] scanline-pattern absolute top-[32px]"></div>
|
||||||
|
<!-- Valid Drop Target Glow -->
|
||||||
|
<div class="absolute top-[64px] w-full h-[96px] rounded-xl border border-suit-black bg-[#1a1a1a] p-1.5 ring-4 ring-highlight-valid/30">
|
||||||
|
<div class="font-card-rank text-suit-black leading-none">9<br/><span class="font-normal">♠</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Col 5, 6 (Empty/Filler) -->
|
||||||
|
<div class="relative w-full"></div>
|
||||||
|
<div class="relative w-full"></div>
|
||||||
|
<!-- Col 7 -->
|
||||||
|
<div class="relative w-full">
|
||||||
|
<!-- Original Position Placeholder -->
|
||||||
|
<div class="w-full h-[96px] rounded-xl border border-dashed border-outline"></div>
|
||||||
|
<!-- Being Dragged Card -->
|
||||||
|
<div class="absolute top-[-20px] left-[30px] w-full h-[96px] rounded-xl border border-suit-red bg-[#1a1a1a] p-1.5 shadow-[0_0_20px_rgba(111,194,239,0.4)] z-50 ring-1 ring-primary/40">
|
||||||
|
<div class="font-card-rank text-suit-red leading-none">4<br/><span class="font-normal outlined-glyph">♦</span></div>
|
||||||
|
<div class="absolute bottom-1 right-1 text-[24px] font-card-rank text-suit-red rotate-180 outlined-glyph">♦</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<!-- BottomNavBar / Action Bar -->
|
||||||
|
<nav class="fixed bottom-0 left-0 w-full h-action-bar-height bg-surface-container border-t border-outline-variant flex justify-around items-center px-margin-edge z-50">
|
||||||
|
<button class="flex flex-col items-center justify-center text-on-surface-variant hover:text-info transition-colors duration-120 active:opacity-80">
|
||||||
|
<span class="material-symbols-outlined" data-icon="menu">menu</span>
|
||||||
|
<span class="font-label-caps text-[10px] mt-1">[ESC] MENU</span>
|
||||||
|
</button>
|
||||||
|
<button class="flex flex-col items-center justify-center text-info font-bold active:opacity-80">
|
||||||
|
<span class="material-symbols-outlined" data-icon="undo">undo</span>
|
||||||
|
<span class="font-label-caps text-[10px] mt-1">[U] UNDO</span>
|
||||||
|
</button>
|
||||||
|
<button class="flex flex-col items-center justify-center text-on-surface-variant hover:text-info transition-colors duration-120 active:opacity-80">
|
||||||
|
<span class="material-symbols-outlined" data-icon="lightbulb">lightbulb</span>
|
||||||
|
<span class="font-label-caps text-[10px] mt-1">[H] HINT</span>
|
||||||
|
</button>
|
||||||
|
<button class="flex flex-col items-center justify-center text-on-surface-variant hover:text-info transition-colors duration-120 active:opacity-80">
|
||||||
|
<span class="material-symbols-outlined" data-icon="add_box">add_box</span>
|
||||||
|
<span class="font-label-caps text-[10px] mt-1">[N] NEW</span>
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
<!-- Drag & CRT Overlay (Visual Decoration) -->
|
||||||
|
<div class="pointer-events-none fixed inset-0 z-[100] opacity-[0.03] scanline-pattern mix-blend-overlay"></div>
|
||||||
|
</body></html>
|
||||||
|
After Width: | Height: | Size: 26 KiB |
@@ -0,0 +1,200 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html class="dark" lang="en"><head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@400&family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
|
<script id="tailwind-config">
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: "class",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
"colors": {
|
||||||
|
"on-secondary-container": "#b2c86d",
|
||||||
|
"secondary-fixed-dim": "#bad073",
|
||||||
|
"surface-tint": "#7ed0fe",
|
||||||
|
"on-surface-variant": "#bfc8cf",
|
||||||
|
"surface-container-low": "#181c1f",
|
||||||
|
"secondary-fixed": "#d5ec8c",
|
||||||
|
"primary-fixed-dim": "#7ed0fe",
|
||||||
|
"secondary": "#bad073",
|
||||||
|
"tertiary-container": "#e1a3ee",
|
||||||
|
"inverse-on-surface": "#2d3134",
|
||||||
|
"surface-container-lowest": "#0b0f11",
|
||||||
|
"on-error-container": "#ffdad6",
|
||||||
|
"on-primary-fixed-variant": "#004c69",
|
||||||
|
"secondary-container": "#435401",
|
||||||
|
"background": "#101417",
|
||||||
|
"surface-variant": "#313538",
|
||||||
|
"on-primary-container": "#004f6c",
|
||||||
|
"highlight-valid": "#acc267",
|
||||||
|
"outline-variant": "#3f484e",
|
||||||
|
"on-background": "#e0e3e6",
|
||||||
|
"surface-bright": "#363a3d",
|
||||||
|
"on-tertiary-fixed-variant": "#653173",
|
||||||
|
"on-secondary-fixed": "#161e00",
|
||||||
|
"surface-dim": "#101417",
|
||||||
|
"on-surface": "#e0e3e6",
|
||||||
|
"info": "#12cfc0",
|
||||||
|
"on-secondary": "#293500",
|
||||||
|
"suit-red": "#fb9fb1",
|
||||||
|
"error": "#fb9fb1",
|
||||||
|
"error-container": "#93000a",
|
||||||
|
"surface-container": "#202020",
|
||||||
|
"primary-fixed": "#c4e7ff",
|
||||||
|
"warning": "#ddb26f",
|
||||||
|
"tertiary": "#f7c3ff",
|
||||||
|
"highlight-celebration": "#e1a3ee",
|
||||||
|
"tertiary-fixed": "#fbd7ff",
|
||||||
|
"inverse-surface": "#e0e3e6",
|
||||||
|
"tertiary-fixed-dim": "#f0b0fc",
|
||||||
|
"primary-container": "#6fc2ef",
|
||||||
|
"on-secondary-fixed-variant": "#3c4d00",
|
||||||
|
"on-tertiary": "#4c195b",
|
||||||
|
"suit-red-cb": "#6fc2ef",
|
||||||
|
"surface-container-highest": "#313538",
|
||||||
|
"on-primary-fixed": "#001e2c",
|
||||||
|
"surface-container-high": "#272a2d",
|
||||||
|
"primary": "#a1dcff",
|
||||||
|
"suit-black": "#d0d0d0",
|
||||||
|
"on-tertiary-container": "#683476",
|
||||||
|
"on-error": "#690005",
|
||||||
|
"inverse-primary": "#00668a",
|
||||||
|
"on-tertiary-fixed": "#340043",
|
||||||
|
"outline": "#505050",
|
||||||
|
"on-primary": "#003549",
|
||||||
|
"surface": "#151515"
|
||||||
|
},
|
||||||
|
"fontFamily": {
|
||||||
|
"jetbrains": ["JetBrains Mono", "monospace"],
|
||||||
|
"inter": ["Inter", "sans-serif"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.material-symbols-outlined {
|
||||||
|
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.tabular-nums { font-variant-numeric: tabular-nums; }
|
||||||
|
body { background-color: #151515; }
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
min-height: max(884px, 100dvh);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="flex items-center justify-center min-h-screen p-4">
|
||||||
|
<!-- Mobile Container (390x844) -->
|
||||||
|
<div class="w-[390px] h-[844px] bg-surface flex flex-col overflow-hidden relative border border-outline/20">
|
||||||
|
<!-- 1. Status Bar -->
|
||||||
|
<header class="h-[32px] bg-surface-container flex items-center justify-between px-4 shrink-0">
|
||||||
|
<span class="font-jetbrains text-[12px] font-bold text-suit-black tracking-tight">▌rusty-solitaire(1) · MAN PAGE</span>
|
||||||
|
<button class="font-jetbrains text-[12px] font-bold text-suit-black/60 hover:text-primary transition-colors">× CLOSE</button>
|
||||||
|
</header>
|
||||||
|
<!-- 2. Heading Band -->
|
||||||
|
<div class="h-[120px] px-4 pt-10 pb-4 shrink-0">
|
||||||
|
<h1 class="font-jetbrains font-bold text-[24px] text-suit-black leading-none mb-1">GESTURES & SHORTCUTS</h1>
|
||||||
|
<p class="font-inter text-[13px] text-on-surface-variant/80">Touch gestures and keyboard equivalents.</p>
|
||||||
|
</div>
|
||||||
|
<!-- Scrollable Content Section -->
|
||||||
|
<main class="flex-1 overflow-y-auto px-4 pb-8 space-y-6">
|
||||||
|
<!-- 3a. TOUCH GESTURES -->
|
||||||
|
<section class="space-y-3">
|
||||||
|
<h2 class="font-jetbrains text-[11px] font-medium tracking-widest text-on-surface-variant/60 uppercase">TOUCH GESTURES</h2>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<!-- Row 1 -->
|
||||||
|
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||||
|
<div class="w-[40%] flex items-center gap-2">
|
||||||
|
<span class="material-symbols-outlined text-suit-black" data-icon="square">square</span>
|
||||||
|
<span class="font-jetbrains text-[13px] font-medium text-suit-black uppercase">TAP card</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">Select / unselect for move</div>
|
||||||
|
</div>
|
||||||
|
<!-- Row 2 -->
|
||||||
|
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||||
|
<div class="w-[40%] flex items-center gap-2">
|
||||||
|
<span class="material-symbols-outlined text-suit-black" data-icon="east">east</span>
|
||||||
|
<span class="font-jetbrains text-[13px] font-medium text-suit-black uppercase">DRAG stack</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">Move with translucent ghost preview</div>
|
||||||
|
</div>
|
||||||
|
<!-- Row 3 -->
|
||||||
|
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||||
|
<div class="w-[40%] flex items-center gap-2">
|
||||||
|
<span class="material-symbols-outlined text-suit-black" data-icon="double_arrow">double_arrow</span>
|
||||||
|
<span class="font-jetbrains text-[13px] font-medium text-suit-black uppercase">DOUBLE-TAP</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">Auto-send to best foundation</div>
|
||||||
|
</div>
|
||||||
|
<!-- Row 4 -->
|
||||||
|
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||||
|
<div class="w-[40%] flex items-center gap-2">
|
||||||
|
<span class="material-symbols-outlined text-suit-black" data-icon="touch_app">touch_app</span>
|
||||||
|
<span class="font-jetbrains text-[13px] font-medium text-suit-black uppercase">LONG-PRESS</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">Highlight all legal moves for card</div>
|
||||||
|
</div>
|
||||||
|
<!-- Row 5 -->
|
||||||
|
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||||
|
<div class="w-[40%] flex items-center gap-2">
|
||||||
|
<span class="material-symbols-outlined text-suit-black" data-icon="south">south</span>
|
||||||
|
<span class="font-jetbrains text-[13px] font-medium text-suit-black uppercase">SWIPE DOWN</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">Reveal hidden action bar</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- 3b. KEYBOARD SHORTCUTS -->
|
||||||
|
<section class="space-y-3">
|
||||||
|
<h2 class="font-jetbrains text-[11px] font-medium tracking-widest text-on-surface-variant/60 uppercase">KEYBOARD SHORTCUTS</h2>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<!-- Row 1 -->
|
||||||
|
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||||
|
<div class="w-[40%] font-jetbrains text-[13px] font-medium text-suit-black uppercase">[U]</div>
|
||||||
|
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">Undo last move</div>
|
||||||
|
</div>
|
||||||
|
<!-- Row 2 -->
|
||||||
|
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||||
|
<div class="w-[40%] font-jetbrains text-[13px] font-medium text-suit-black uppercase">[H]</div>
|
||||||
|
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">Show hint</div>
|
||||||
|
</div>
|
||||||
|
<!-- Row 3 -->
|
||||||
|
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||||
|
<div class="w-[40%] font-jetbrains text-[13px] font-medium text-suit-black uppercase">[N]</div>
|
||||||
|
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">New game</div>
|
||||||
|
</div>
|
||||||
|
<!-- Row 4 -->
|
||||||
|
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||||
|
<div class="w-[40%] font-jetbrains text-[13px] font-medium text-suit-black uppercase">[A]</div>
|
||||||
|
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">Auto-complete (when possible)</div>
|
||||||
|
</div>
|
||||||
|
<!-- Row 5 -->
|
||||||
|
<div class="h-[56px] bg-surface-container rounded-lg px-3 flex items-center border border-outline/10">
|
||||||
|
<div class="w-[40%] font-jetbrains text-[13px] font-medium text-suit-black uppercase">[ESC]</div>
|
||||||
|
<div class="w-[60%] font-jetbrains text-[12px] text-on-surface-variant leading-tight">Pause / back</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<!-- 4. Footer -->
|
||||||
|
<footer class="h-[24px] bg-surface-container border-t border-outline/20 flex items-center justify-between px-2 shrink-0">
|
||||||
|
<div class="font-jetbrains text-[10px] text-suit-black">
|
||||||
|
<span class="opacity-80">▌ NORMAL │ help</span>
|
||||||
|
</div>
|
||||||
|
<div class="font-jetbrains text-[10px] uppercase tracking-wider flex items-center gap-1">
|
||||||
|
<span class="text-outline">PRESS</span>
|
||||||
|
<span class="text-on-surface-variant">[ESC]</span>
|
||||||
|
<span class="text-outline">OR TAP</span>
|
||||||
|
<span class="text-on-surface-variant">×</span>
|
||||||
|
<span class="text-outline">TO RETURN</span>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</body></html>
|
||||||
|
After Width: | Height: | Size: 42 KiB |
@@ -0,0 +1,343 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html class="dark" lang="en"><head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
|
<title>RS_TERMINAL_OS - Rusty Solitaire</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700;800&family=Inter:wght@400;500&family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<style>
|
||||||
|
.material-symbols-outlined {
|
||||||
|
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-color: #151515;
|
||||||
|
color: #d0d0d0;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.scanline {
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: rgba(26, 26, 26, 0.5);
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.custom-scrollbar::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-track {
|
||||||
|
background: #151515;
|
||||||
|
}
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||||
|
background: #353535;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script id="tailwind-config">
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: "class",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
"colors": {
|
||||||
|
"on-tertiary-container": "#683476",
|
||||||
|
"surface-dim": "#101417",
|
||||||
|
"primary-fixed": "#c4e7ff",
|
||||||
|
"on-error": "#690005",
|
||||||
|
"on-secondary-fixed": "#161e00",
|
||||||
|
"on-tertiary": "#4c195b",
|
||||||
|
"primary-fixed-dim": "#7ed0fe",
|
||||||
|
"outline-variant": "#3f484e",
|
||||||
|
"tertiary": "#f7c3ff",
|
||||||
|
"surface": "#151515",
|
||||||
|
"tertiary-container": "#e1a3ee",
|
||||||
|
"highlight-celebration": "#e1a3ee",
|
||||||
|
"background": "#101417",
|
||||||
|
"surface-container": "#202020",
|
||||||
|
"primary-container": "#6fc2ef",
|
||||||
|
"on-secondary-fixed-variant": "#3c4d00",
|
||||||
|
"on-surface": "#d0d0d0",
|
||||||
|
"inverse-on-surface": "#2d3134",
|
||||||
|
"on-error-container": "#ffdad6",
|
||||||
|
"surface-container-low": "#181c1f",
|
||||||
|
"on-tertiary-fixed": "#340043",
|
||||||
|
"on-secondary-container": "#b2c86d",
|
||||||
|
"on-background": "#e0e3e6",
|
||||||
|
"secondary-container": "#435401",
|
||||||
|
"error": "#fb9fb1",
|
||||||
|
"info": "#12cfc0",
|
||||||
|
"on-surface-variant": "#bfc8cf",
|
||||||
|
"warning": "#ddb26f",
|
||||||
|
"inverse-primary": "#00668a",
|
||||||
|
"tertiary-fixed-dim": "#f0b0fc",
|
||||||
|
"surface-tint": "#7ed0fe",
|
||||||
|
"suit-black": "#d0d0d0",
|
||||||
|
"tertiary-fixed": "#fbd7ff",
|
||||||
|
"on-secondary": "#293500",
|
||||||
|
"on-primary-fixed": "#001e2c",
|
||||||
|
"surface-container-highest": "#313538",
|
||||||
|
"error-container": "#93000a",
|
||||||
|
"surface-container-high": "#272a2d",
|
||||||
|
"on-primary-container": "#004f6c",
|
||||||
|
"inverse-surface": "#e0e3e6",
|
||||||
|
"on-primary": "#003549",
|
||||||
|
"suit-red-cb": "#6fc2ef",
|
||||||
|
"on-primary-fixed-variant": "#004c69",
|
||||||
|
"on-tertiary-fixed-variant": "#653173",
|
||||||
|
"secondary-fixed": "#d5ec8c",
|
||||||
|
"highlight-valid": "#acc267",
|
||||||
|
"surface-variant": "#313538",
|
||||||
|
"secondary": "#bad073",
|
||||||
|
"secondary-fixed-dim": "#bad073",
|
||||||
|
"outline": "#505050",
|
||||||
|
"surface-container-lowest": "#0b0f11",
|
||||||
|
"primary": "#a1dcff",
|
||||||
|
"surface-bright": "#363a3d",
|
||||||
|
"suit-red": "#fb9fb1"
|
||||||
|
},
|
||||||
|
"borderRadius": {
|
||||||
|
"DEFAULT": "0px",
|
||||||
|
"lg": "0px",
|
||||||
|
"xl": "0px",
|
||||||
|
"full": "0px"
|
||||||
|
},
|
||||||
|
"spacing": {
|
||||||
|
"stack-overlap": "2rem",
|
||||||
|
"touch-target-min": "48px",
|
||||||
|
"margin-edge": "1rem",
|
||||||
|
"gutter-card": "0.375rem",
|
||||||
|
"action-bar-height": "64px"
|
||||||
|
},
|
||||||
|
"fontFamily": {
|
||||||
|
"card-rank": ["JetBrains Mono"],
|
||||||
|
"headline": ["JetBrains Mono"],
|
||||||
|
"hud-timer": ["JetBrains Mono"],
|
||||||
|
"hud-score": ["JetBrains Mono"],
|
||||||
|
"body-md": ["Inter"],
|
||||||
|
"label-caps": ["JetBrains Mono"]
|
||||||
|
},
|
||||||
|
"fontSize": {
|
||||||
|
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
|
||||||
|
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
|
||||||
|
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||||
|
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}],
|
||||||
|
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||||
|
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body class="bg-surface text-on-surface h-screen flex flex-col antialiased">
|
||||||
|
<!-- TOP BAR (32px) -->
|
||||||
|
<header class="h-8 bg-surface-container border-b border-outline flex items-center justify-between px-4 z-50">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-primary-container font-bold">▌</span>
|
||||||
|
<h1 class="font-headline text-[14px] font-bold tracking-tight text-on-surface">RS_TERMINAL_OS</h1>
|
||||||
|
</div>
|
||||||
|
<nav class="flex gap-4 font-label-caps text-[12px] uppercase tracking-widest">
|
||||||
|
<span class="text-primary-container">[ HOME ]</span>
|
||||||
|
<span class="text-on-surface-variant hover:text-primary transition-colors cursor-pointer">· PLAY</span>
|
||||||
|
<span class="text-on-surface-variant hover:text-primary transition-colors cursor-pointer">· STATS</span>
|
||||||
|
<span class="text-on-surface-variant hover:text-primary transition-colors cursor-pointer">· SETTINGS</span>
|
||||||
|
</nav>
|
||||||
|
<div class="flex items-center gap-3 font-label-caps text-[11px] text-on-surface-variant">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<span>LV 12</span>
|
||||||
|
<span class="text-outline">|</span>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span>XP 320/500</span>
|
||||||
|
<div class="w-[60px] h-1 bg-surface-container-highest">
|
||||||
|
<div class="h-full bg-primary-container w-[64%]"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="text-outline">|</span>
|
||||||
|
<div class="flex items-center gap-1 text-info">
|
||||||
|
<span class="w-2 h-2 rounded-full bg-info"></span>
|
||||||
|
<span class="uppercase">Synced</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-outline">|</span>
|
||||||
|
<span class="text-outline">v0.20.0</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<!-- MAIN CONTENT AREA -->
|
||||||
|
<main class="flex-1 flex overflow-hidden">
|
||||||
|
<!-- LEFT PANE (40%) -->
|
||||||
|
<section class="w-[40%] border-r border-outline flex flex-col p-8 gap-8 overflow-y-auto custom-scrollbar">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<p class="text-outline font-label-caps text-xs">▌play.tsx</p>
|
||||||
|
<h2 class="font-headline text-[32px] font-bold text-on-surface leading-none uppercase">Ready to play?</h2>
|
||||||
|
<p class="text-on-surface-variant font-label-caps text-sm tracking-wide">RESUME · 12:34 ELAPSED · DRAW-3</p>
|
||||||
|
</div>
|
||||||
|
<button class="w-full h-24 bg-primary-container text-surface font-headline text-[24px] font-bold flex items-center justify-center gap-4 hover:brightness-110 active:scale-[0.98] transition-all">
|
||||||
|
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 1;">play_arrow</span>
|
||||||
|
CONTINUE GAME
|
||||||
|
</button>
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<button class="h-12 border border-outline bg-transparent text-on-surface font-label-caps text-sm hover:border-primary-container hover:text-primary-container transition-all flex items-center justify-center gap-2">
|
||||||
|
<span class="material-symbols-outlined">add</span>
|
||||||
|
NEW GAME
|
||||||
|
</button>
|
||||||
|
<button class="h-12 border border-outline bg-transparent text-on-surface font-label-caps text-sm hover:border-primary-container hover:text-primary-container transition-all flex items-center justify-center gap-2">
|
||||||
|
<span class="material-symbols-outlined">refresh</span>
|
||||||
|
RESTART RUN
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<p class="text-outline font-label-caps text-xs uppercase tracking-widest">Game Modes</p>
|
||||||
|
<div class="grid grid-cols-3 gap-3">
|
||||||
|
<!-- Zen -->
|
||||||
|
<div class="aspect-square border border-outline flex flex-col items-center justify-center gap-2 hover:bg-surface-container transition-colors cursor-pointer group">
|
||||||
|
<span class="material-symbols-outlined text-outline group-hover:text-primary-container">spa</span>
|
||||||
|
<span class="font-label-caps text-[10px] uppercase">Zen</span>
|
||||||
|
</div>
|
||||||
|
<!-- Time Attack -->
|
||||||
|
<div class="aspect-square border border-outline flex flex-col items-center justify-center gap-2 hover:bg-surface-container transition-colors cursor-pointer group">
|
||||||
|
<span class="material-symbols-outlined text-outline group-hover:text-primary-container">timer</span>
|
||||||
|
<span class="font-label-caps text-[10px] uppercase text-center">Time<br/>Attack</span>
|
||||||
|
</div>
|
||||||
|
<!-- Locked Challenge -->
|
||||||
|
<div class="aspect-square bg-[#0d0d0d] border border-outline/30 flex flex-col items-center justify-center gap-2 relative opacity-60">
|
||||||
|
<span class="material-symbols-outlined text-outline">lock</span>
|
||||||
|
<span class="font-label-caps text-[10px] uppercase">Challenge</span>
|
||||||
|
<div class="absolute -top-2 -right-2 bg-warning text-surface px-1 py-0.5 text-[8px] font-bold">LV 5</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- VISUAL DECORATION (IMAGE PLACEHOLDER) -->
|
||||||
|
<div class="mt-auto pt-8">
|
||||||
|
<div class="w-full h-40 border border-outline overflow-hidden">
|
||||||
|
<img class="w-full h-full object-cover opacity-40 grayscale hover:grayscale-0 transition-all duration-700" data-alt="A dark, high-contrast digital art piece showing an abstract terminal interface with glowing cyan scanlines and retro-futuristic grid patterns. The composition is geometric and minimalist, following a synthwave aesthetic with deep black backgrounds and crisp cyan light elements. The lighting is moody and artificial, suggesting a high-performance computer screen in a dimly lit server room. Professional, sharp-edged UI design style." src="https://lh3.googleusercontent.com/aida-public/AB6AXuAet8SrRWSacZfwd8ISRQdDC7CDGixBwRnPAVMmMcjbifq1jnHSzCGWgSSL6YPSRfCkLNWr91BxTzV4zigGjMBLlk7rCLo5I7X7F6ydinDrKJVqZkRbvHJeSo90BPANoQwZtzPvhKXVEA9C2DbBaj8KPR4ObCo24Mj25NXPvGNThOE-3BSpuU6MPC-hrUMPVCPJpZnJdI_OmSz8mT021vjTxFERN12S1PFOzXKmNUDleoTDIat-8UifyKmKg4eKilecrBW6sFqaBw"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- CENTER PANE (30%) -->
|
||||||
|
<section class="w-[30%] border-r border-outline flex flex-col p-8 gap-8 overflow-y-auto custom-scrollbar">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<p class="text-outline font-label-caps text-xs">▌daily.json</p>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h3 class="font-headline text-[18px] font-bold text-on-surface">MAY 07 · 2026</h3>
|
||||||
|
<span class="bg-warning/20 text-warning px-2 py-1 text-[10px] font-bold border border-warning/40">EXPIRES 11:42:30</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-surface-container p-6 border border-outline space-y-4">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<p class="text-on-surface-variant font-label-caps text-[10px] uppercase tracking-tighter">Current Seed</p>
|
||||||
|
<p class="font-headline text-[24px] font-extrabold text-highlight-valid">#2024-127</p>
|
||||||
|
</div>
|
||||||
|
<button class="w-full py-3 bg-primary-container text-surface font-label-caps text-xs font-bold uppercase tracking-widest hover:brightness-110 active:scale-95 transition-all">
|
||||||
|
▶ Attempt Today
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<p class="text-outline font-label-caps text-xs uppercase tracking-widest">Global Standings</p>
|
||||||
|
<div class="space-y-1 text-xs font-label-caps">
|
||||||
|
<div class="flex justify-between py-2 border-b border-outline/30 text-highlight-valid">
|
||||||
|
<span>01 │ swift_jaguar</span>
|
||||||
|
<span>02:47</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between py-2 border-b border-outline/30 text-on-surface-variant">
|
||||||
|
<span>02 │ pixel_drifter</span>
|
||||||
|
<span>03:12</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between py-2 border-b border-outline/30 text-on-surface-variant">
|
||||||
|
<span>03 │ null_ptr</span>
|
||||||
|
<span>03:15</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between py-2 border-b border-outline/30 text-on-surface-variant">
|
||||||
|
<span>04 │ core_dump_88</span>
|
||||||
|
<span>03:44</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between py-2 text-primary-container bg-primary-container/10 px-2 -mx-2">
|
||||||
|
<span>12 │ YOU (anon)</span>
|
||||||
|
<span>--:--</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- RIGHT PANE (30%) -->
|
||||||
|
<section class="w-[30%] flex flex-col p-8 gap-8 overflow-y-auto custom-scrollbar">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<p class="text-outline font-label-caps text-xs">▌stats.log</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div class="border border-outline p-4 space-y-1">
|
||||||
|
<p class="text-on-surface-variant text-[10px] uppercase">Games</p>
|
||||||
|
<p class="font-hud-score text-[28px] text-on-surface">247</p>
|
||||||
|
</div>
|
||||||
|
<div class="border border-outline p-4 space-y-1 text-highlight-valid">
|
||||||
|
<p class="text-on-surface-variant text-[10px] uppercase">Win Rate</p>
|
||||||
|
<p class="font-hud-score text-[28px]">61%</p>
|
||||||
|
</div>
|
||||||
|
<div class="border border-outline p-4 space-y-1">
|
||||||
|
<p class="text-on-surface-variant text-[10px] uppercase">Best Time</p>
|
||||||
|
<p class="font-hud-score text-[28px]">01:54</p>
|
||||||
|
</div>
|
||||||
|
<div class="border border-outline p-4 space-y-1 text-primary-container">
|
||||||
|
<p class="text-on-surface-variant text-[10px] uppercase">Streak</p>
|
||||||
|
<p class="font-hud-score text-[28px]">7</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<p class="text-outline font-label-caps text-xs uppercase tracking-widest">Achievements (8/19)</p>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<!-- Filled Cyan Dots -->
|
||||||
|
<div class="w-3 h-3 bg-primary-container"></div>
|
||||||
|
<div class="w-3 h-3 bg-primary-container"></div>
|
||||||
|
<div class="w-3 h-3 bg-primary-container"></div>
|
||||||
|
<div class="w-3 h-3 bg-primary-container"></div>
|
||||||
|
<div class="w-3 h-3 bg-primary-container"></div>
|
||||||
|
<div class="w-3 h-3 bg-primary-container"></div>
|
||||||
|
<div class="w-3 h-3 bg-primary-container"></div>
|
||||||
|
<div class="w-3 h-3 bg-primary-container"></div>
|
||||||
|
<!-- Empty Dots -->
|
||||||
|
<div class="w-3 h-3 border border-outline"></div>
|
||||||
|
<div class="w-3 h-3 border border-outline"></div>
|
||||||
|
<div class="w-3 h-3 border border-outline"></div>
|
||||||
|
<div class="w-3 h-3 border border-outline"></div>
|
||||||
|
<div class="w-3 h-3 border border-outline"></div>
|
||||||
|
<div class="w-3 h-3 border border-outline"></div>
|
||||||
|
<div class="w-3 h-3 border border-outline"></div>
|
||||||
|
<div class="w-3 h-3 border border-outline"></div>
|
||||||
|
<div class="w-3 h-3 border border-outline"></div>
|
||||||
|
<div class="w-3 h-3 border border-outline"></div>
|
||||||
|
<div class="w-3 h-3 border border-outline"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-auto border border-outline bg-surface-container p-4 flex items-center justify-between hover:border-primary-container transition-colors cursor-pointer group">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="w-10 h-10 bg-primary-container text-surface flex items-center justify-center font-bold text-lg">RS</div>
|
||||||
|
<div class="space-y-0.5">
|
||||||
|
<p class="text-on-surface font-bold text-xs">anonymous@local</p>
|
||||||
|
<p class="text-on-surface-variant text-[10px]">Session: Active</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="material-symbols-outlined text-primary-container group-hover:translate-x-1 transition-transform">arrow_forward</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<!-- BOTTOM BAR (24px) -->
|
||||||
|
<footer class="h-6 bg-surface-container border-t border-outline flex items-center justify-between px-4 text-[10px] font-label-caps">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-primary-container">▌ NORMAL</span>
|
||||||
|
<span class="text-outline">│</span>
|
||||||
|
<span class="text-on-surface-variant">~/rusty-solitaire/home</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-4 text-on-surface-variant">
|
||||||
|
<div class="flex items-center gap-1"><span class="text-primary-container">[SPACE]</span> play</div>
|
||||||
|
<div class="flex items-center gap-1"><span class="text-primary-container">[D]</span> daily</div>
|
||||||
|
<div class="flex items-center gap-1"><span class="text-primary-container">[S]</span> settings</div>
|
||||||
|
<div class="flex items-center gap-1"><span class="text-primary-container">[?]</span> help</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-outline">
|
||||||
|
2026-05-07 17:42 EDT
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<!-- GLOBAL SCANLINE EFFECT -->
|
||||||
|
<div class="fixed inset-0 pointer-events-none z-[100] overflow-hidden opacity-10">
|
||||||
|
<div class="absolute inset-0" style="background: repeating-linear-gradient(0deg, #151515, #151515 2px, #202020 4px);"></div>
|
||||||
|
</div>
|
||||||
|
</body></html>
|
||||||
|
After Width: | Height: | Size: 47 KiB |
@@ -0,0 +1,225 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html class="dark" lang="en"><head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
|
<title>Rusty Solitaire - Main Menu</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@400&display=swap" rel="stylesheet"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<script id="tailwind-config">
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: "class",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
"colors": {
|
||||||
|
"outline": "#505050",
|
||||||
|
"suit-red-cb": "#6fc2ef",
|
||||||
|
"suit-black": "#d0d0d0",
|
||||||
|
"surface-container-high": "#272a2d",
|
||||||
|
"primary-fixed": "#c4e7ff",
|
||||||
|
"on-secondary-container": "#b2c86d",
|
||||||
|
"secondary-fixed": "#d5ec8c",
|
||||||
|
"on-tertiary-container": "#683476",
|
||||||
|
"surface-tint": "#7ed0fe",
|
||||||
|
"background": "#101417",
|
||||||
|
"primary-container": "#6fc2ef",
|
||||||
|
"inverse-surface": "#e0e3e6",
|
||||||
|
"highlight-celebration": "#e1a3ee",
|
||||||
|
"surface-container-low": "#181c1f",
|
||||||
|
"on-surface": "#d0d0d0",
|
||||||
|
"primary": "#a1dcff",
|
||||||
|
"on-tertiary-fixed": "#340043",
|
||||||
|
"secondary-container": "#435401",
|
||||||
|
"inverse-primary": "#00668a",
|
||||||
|
"tertiary-fixed": "#fbd7ff",
|
||||||
|
"surface-bright": "#363a3d",
|
||||||
|
"on-secondary-fixed-variant": "#3c4d00",
|
||||||
|
"warning": "#ddb26f",
|
||||||
|
"tertiary-container": "#e1a3ee",
|
||||||
|
"suit-red": "#fb9fb1",
|
||||||
|
"primary-fixed-dim": "#7ed0fe",
|
||||||
|
"info": "#12cfc0",
|
||||||
|
"on-primary-fixed": "#001e2c",
|
||||||
|
"surface-container-lowest": "#0b0f11",
|
||||||
|
"error": "#fb9fb1",
|
||||||
|
"surface-variant": "#313538",
|
||||||
|
"on-error": "#690005",
|
||||||
|
"surface": "#151515",
|
||||||
|
"surface-container": "#202020",
|
||||||
|
"on-primary-container": "#004f6c",
|
||||||
|
"inverse-on-surface": "#2d3134",
|
||||||
|
"on-primary-fixed-variant": "#004c69",
|
||||||
|
"on-secondary": "#293500",
|
||||||
|
"error-container": "#93000a",
|
||||||
|
"secondary": "#bad073",
|
||||||
|
"tertiary": "#f7c3ff",
|
||||||
|
"outline-variant": "#3f484e",
|
||||||
|
"on-secondary-fixed": "#161e00",
|
||||||
|
"secondary-fixed-dim": "#bad073",
|
||||||
|
"surface-container-highest": "#313538",
|
||||||
|
"on-surface-variant": "#bfc8cf",
|
||||||
|
"tertiary-fixed-dim": "#f0b0fc",
|
||||||
|
"on-tertiary-fixed-variant": "#653173",
|
||||||
|
"on-error-container": "#ffdad6",
|
||||||
|
"on-primary": "#003549",
|
||||||
|
"on-background": "#e0e3e6",
|
||||||
|
"surface-dim": "#101417",
|
||||||
|
"on-tertiary": "#4c195b",
|
||||||
|
"highlight-valid": "#acc267"
|
||||||
|
},
|
||||||
|
"borderRadius": {
|
||||||
|
"DEFAULT": "0.125rem",
|
||||||
|
"lg": "0.25rem",
|
||||||
|
"xl": "0.5rem",
|
||||||
|
"full": "0.75rem"
|
||||||
|
},
|
||||||
|
"spacing": {
|
||||||
|
"margin-edge": "1rem",
|
||||||
|
"touch-target-min": "48px",
|
||||||
|
"stack-overlap": "2rem",
|
||||||
|
"action-bar-height": "64px",
|
||||||
|
"gutter-card": "0.375rem"
|
||||||
|
},
|
||||||
|
"fontFamily": {
|
||||||
|
"hud-timer": ["JetBrains Mono"],
|
||||||
|
"headline": ["JetBrains Mono"],
|
||||||
|
"label-caps": ["JetBrains Mono"],
|
||||||
|
"hud-score": ["JetBrains Mono"],
|
||||||
|
"body-md": ["Inter"],
|
||||||
|
"card-rank": ["JetBrains Mono"]
|
||||||
|
},
|
||||||
|
"fontSize": {
|
||||||
|
"hud-timer": ["16px", { "lineHeight": "24px", "fontWeight": "400" }],
|
||||||
|
"headline": ["28px", { "lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700" }],
|
||||||
|
"label-caps": ["12px", { "lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500" }],
|
||||||
|
"hud-score": ["24px", { "lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700" }],
|
||||||
|
"body-md": ["16px", { "lineHeight": "24px", "fontWeight": "400" }],
|
||||||
|
"card-rank": ["18px", { "lineHeight": "18px", "fontWeight": "700" }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.scanline {
|
||||||
|
background: linear-gradient(to bottom, rgba(255,255,255,0), rgba(255,255,255,0) 50%, rgba(0,0,0,0.1) 50%, rgba(0,0,0,0.1));
|
||||||
|
background-size: 100% 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
min-height: max(884px, 100dvh);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-surface text-on-surface font-hud-timer min-h-screen flex flex-col relative overflow-hidden">
|
||||||
|
<!-- Subtle CRT scanline overlay -->
|
||||||
|
<div class="absolute inset-0 pointer-events-none scanline opacity-20 z-0"></div>
|
||||||
|
<!-- Status Bar Zone -->
|
||||||
|
<div class="h-6 w-full flex justify-end items-center px-margin-edge pt-2 z-10 relative">
|
||||||
|
<div class="w-2 h-2 rounded-full bg-info"></div>
|
||||||
|
</div>
|
||||||
|
<!-- Header -->
|
||||||
|
<header class="px-margin-edge pt-4 pb-6 flex justify-between items-center z-10 relative">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<span class="font-headline text-headline text-on-surface">▌RUSTY SOLITAIRE</span>
|
||||||
|
<div class="w-2 h-6 bg-primary-container inline-block ml-1 animate-pulse"></div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-surface-container px-3 py-1 flex items-center gap-2 border border-outline">
|
||||||
|
<span class="font-label-caps text-label-caps text-on-surface">LV 12</span>
|
||||||
|
<div class="w-2 h-2 rounded-full bg-highlight-celebration"></div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<!-- Main Content Canvas -->
|
||||||
|
<main class="flex-1 px-margin-edge flex flex-col gap-8 z-10 relative pb-24 overflow-y-auto">
|
||||||
|
<!-- XP Section -->
|
||||||
|
<section class="flex flex-col gap-2">
|
||||||
|
<div class="w-full h-1 bg-surface-container border border-outline relative">
|
||||||
|
<div class="absolute top-0 left-0 h-full bg-primary-container w-[64%]"></div>
|
||||||
|
</div>
|
||||||
|
<div class="font-label-caps text-label-caps text-on-surface-variant text-right">
|
||||||
|
320 / 500 XP
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- Primary Action -->
|
||||||
|
<section class="flex flex-col gap-2">
|
||||||
|
<button class="w-full h-[56px] bg-primary-container text-surface flex items-center justify-center gap-2 hover:bg-surface-tint transition-colors duration-120">
|
||||||
|
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 1;">play_arrow</span>
|
||||||
|
<span class="font-label-caps text-[14px] uppercase tracking-widest font-bold">PLAY</span>
|
||||||
|
</button>
|
||||||
|
<div class="font-label-caps text-label-caps text-on-surface-variant text-center">
|
||||||
|
RESUME LAST GAME · 12:34 ELAPSED
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- Daily Challenge Tile -->
|
||||||
|
<section>
|
||||||
|
<div class="bg-surface-container border border-outline p-4 flex justify-between items-center hover:bg-surface-container-high transition-colors cursor-pointer group">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<span class="font-label-caps text-label-caps text-primary">DAILY CHALLENGE</span>
|
||||||
|
<span class="font-body-md text-body-md text-on-surface">DRAW-3 · SEED #2024-127</span>
|
||||||
|
<div class="inline-flex">
|
||||||
|
<span class="bg-surface px-2 py-0.5 border border-warning text-warning font-label-caps text-[10px]">EXPIRES 11:42:30</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="material-symbols-outlined text-primary group-hover:translate-x-1 transition-transform">chevron_right</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- Special Modes Grid -->
|
||||||
|
<section class="flex flex-col gap-4">
|
||||||
|
<h2 class="font-label-caps text-label-caps text-on-surface-variant">SPECIAL MODES</h2>
|
||||||
|
<div class="grid grid-cols-3 gap-gutter-card">
|
||||||
|
<!-- ZEN -->
|
||||||
|
<button class="aspect-square bg-surface border border-outline flex flex-col items-center justify-center gap-2 hover:border-primary hover:text-primary transition-colors text-on-surface">
|
||||||
|
<span class="material-symbols-outlined text-[32px]">self_improvement</span>
|
||||||
|
<span class="font-label-caps text-label-caps">ZEN</span>
|
||||||
|
</button>
|
||||||
|
<!-- TIME ATTACK -->
|
||||||
|
<button class="aspect-square bg-surface border border-outline flex flex-col items-center justify-center gap-2 hover:border-primary hover:text-primary transition-colors text-on-surface">
|
||||||
|
<span class="material-symbols-outlined text-[32px]">timer</span>
|
||||||
|
<span class="font-label-caps text-label-caps">TIME ATTACK</span>
|
||||||
|
</button>
|
||||||
|
<!-- CHALLENGE (Locked) -->
|
||||||
|
<button class="aspect-square bg-[#0d0d0d] border border-surface-container-high flex flex-col items-center justify-center gap-2 text-on-surface-variant opacity-75 cursor-not-allowed relative">
|
||||||
|
<span class="material-symbols-outlined text-[32px]">lock</span>
|
||||||
|
<span class="font-label-caps text-label-caps">CHALLENGE</span>
|
||||||
|
<div class="absolute top-2 right-2 bg-surface px-1 py-0.5 border border-warning text-warning font-label-caps text-[10px]">
|
||||||
|
LV 5
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- Secondary Nav Grid -->
|
||||||
|
<section class="grid grid-cols-2 gap-y-4 gap-x-6 pb-6">
|
||||||
|
<button class="flex items-center gap-3 h-[56px] border-l-2 border-outline pl-3 hover:border-primary hover:text-primary transition-colors text-on-surface justify-start">
|
||||||
|
<span class="material-symbols-outlined">bar_chart</span>
|
||||||
|
<span class="font-label-caps text-label-caps">STATS</span>
|
||||||
|
</button>
|
||||||
|
<button class="flex items-center gap-3 h-[56px] border-l-2 border-outline pl-3 hover:border-primary hover:text-primary transition-colors text-on-surface justify-start relative">
|
||||||
|
<span class="material-symbols-outlined">emoji_events</span>
|
||||||
|
<span class="font-label-caps text-label-caps">ACHIEVEMENTS</span>
|
||||||
|
<div class="absolute right-2 top-1/2 -translate-y-1/2 w-2 h-2 rounded-full bg-highlight-celebration"></div>
|
||||||
|
</button>
|
||||||
|
<button class="flex items-center gap-3 h-[56px] border-l-2 border-outline pl-3 hover:border-primary hover:text-primary transition-colors text-on-surface justify-start">
|
||||||
|
<span class="material-symbols-outlined">format_list_numbered</span>
|
||||||
|
<span class="font-label-caps text-label-caps">LEADERBOARD</span>
|
||||||
|
</button>
|
||||||
|
<button class="flex items-center gap-3 h-[56px] border-l-2 border-outline pl-3 hover:border-primary hover:text-primary transition-colors text-on-surface justify-start">
|
||||||
|
<span class="material-symbols-outlined">account_circle</span>
|
||||||
|
<span class="font-label-caps text-label-caps">PROFILE</span>
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
<!-- Footer Links -->
|
||||||
|
<footer class="flex flex-col items-center gap-4 mt-auto">
|
||||||
|
<div class="flex items-center gap-4 font-label-caps text-label-caps text-primary cursor-pointer hover:text-surface-tint">
|
||||||
|
<span>SETTINGS</span>
|
||||||
|
<span class="text-on-surface-variant">·</span>
|
||||||
|
<span>HELP</span>
|
||||||
|
</div>
|
||||||
|
<div class="font-label-caps text-[10px] text-on-surface-variant text-center opacity-60">
|
||||||
|
v0.20.0 — TERMINAL THEME · BUILD 2026.05
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</main>
|
||||||
|
</body></html>
|
||||||
|
After Width: | Height: | Size: 28 KiB |
@@ -0,0 +1,315 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html class="dark" lang="en"><head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
|
<title>Rusty Solitaire - Leaderboard</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700;800&family=Inter:wght@400;500&family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: #151515;
|
||||||
|
color: #e0e3e6;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
.material-symbols-outlined {
|
||||||
|
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.scanline-overlay {
|
||||||
|
background: linear-gradient(to bottom, rgba(21, 21, 21, 0) 50%, rgba(26, 26, 26, 0.2) 50%);
|
||||||
|
background-size: 100% 4px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.terminal-glow {
|
||||||
|
box-shadow: 0 0 10px rgba(111, 194, 239, 0.1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script id="tailwind-config">
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: "class",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
"colors": {
|
||||||
|
"outline": "#505050",
|
||||||
|
"on-surface-variant": "#bfc8cf",
|
||||||
|
"secondary-container": "#435401",
|
||||||
|
"surface-container-lowest": "#0b0f11",
|
||||||
|
"primary": "#a1dcff",
|
||||||
|
"secondary-fixed": "#d5ec8c",
|
||||||
|
"on-secondary-fixed": "#161e00",
|
||||||
|
"on-error": "#690005",
|
||||||
|
"inverse-primary": "#00668a",
|
||||||
|
"surface-container": "#202020",
|
||||||
|
"highlight-valid": "#acc267",
|
||||||
|
"suit-black": "#d0d0d0",
|
||||||
|
"on-secondary-fixed-variant": "#3c4d00",
|
||||||
|
"on-primary-fixed": "#001e2c",
|
||||||
|
"on-tertiary-fixed-variant": "#653173",
|
||||||
|
"primary-fixed": "#c4e7ff",
|
||||||
|
"inverse-on-surface": "#2d3134",
|
||||||
|
"secondary-fixed-dim": "#bad073",
|
||||||
|
"on-secondary": "#293500",
|
||||||
|
"on-surface": "#e0e3e6",
|
||||||
|
"on-tertiary-container": "#683476",
|
||||||
|
"secondary": "#bad073",
|
||||||
|
"surface-bright": "#363a3d",
|
||||||
|
"tertiary-container": "#e1a3ee",
|
||||||
|
"surface-variant": "#313538",
|
||||||
|
"suit-red": "#fb9fb1",
|
||||||
|
"primary-fixed-dim": "#7ed0fe",
|
||||||
|
"surface-container-low": "#181c1f",
|
||||||
|
"surface": "#151515",
|
||||||
|
"suit-red-cb": "#6fc2ef",
|
||||||
|
"on-primary": "#003549",
|
||||||
|
"primary-container": "#6fc2ef",
|
||||||
|
"on-background": "#e0e3e6",
|
||||||
|
"tertiary": "#f7c3ff",
|
||||||
|
"surface-container-highest": "#313538",
|
||||||
|
"tertiary-fixed-dim": "#f0b0fc",
|
||||||
|
"tertiary-fixed": "#fbd7ff",
|
||||||
|
"info": "#12cfc0",
|
||||||
|
"error": "#fb9fb1",
|
||||||
|
"warning": "#ddb26f",
|
||||||
|
"on-primary-container": "#004f6c",
|
||||||
|
"surface-container-high": "#272a2d",
|
||||||
|
"inverse-surface": "#e0e3e6",
|
||||||
|
"error-container": "#93000a",
|
||||||
|
"on-tertiary-fixed": "#340043",
|
||||||
|
"surface-tint": "#7ed0fe",
|
||||||
|
"on-tertiary": "#4c195b",
|
||||||
|
"background": "#101417",
|
||||||
|
"on-error-container": "#ffdad6",
|
||||||
|
"on-secondary-container": "#b2c86d",
|
||||||
|
"outline-variant": "#3f484e",
|
||||||
|
"highlight-celebration": "#e1a3ee",
|
||||||
|
"surface-dim": "#101417",
|
||||||
|
"on-primary-fixed-variant": "#004c69"
|
||||||
|
},
|
||||||
|
"borderRadius": {
|
||||||
|
"DEFAULT": "0.125rem",
|
||||||
|
"lg": "0.25rem",
|
||||||
|
"xl": "0.5rem",
|
||||||
|
"full": "0.75rem"
|
||||||
|
},
|
||||||
|
"spacing": {
|
||||||
|
"touch-target-min": "48px",
|
||||||
|
"action-bar-height": "64px",
|
||||||
|
"stack-overlap": "2rem",
|
||||||
|
"gutter-card": "0.375rem",
|
||||||
|
"margin-edge": "1rem"
|
||||||
|
},
|
||||||
|
"fontFamily": {
|
||||||
|
"card-rank": ["JetBrains Mono"],
|
||||||
|
"body-md": ["Inter"],
|
||||||
|
"headline": ["JetBrains Mono"],
|
||||||
|
"label-caps": ["JetBrains Mono"],
|
||||||
|
"hud-timer": ["JetBrains Mono"],
|
||||||
|
"hud-score": ["JetBrains Mono"]
|
||||||
|
},
|
||||||
|
"fontSize": {
|
||||||
|
"card-rank": ["18px", {"lineHeight": "18px", "fontWeight": "700"}],
|
||||||
|
"body-md": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||||
|
"headline": ["28px", {"lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "700"}],
|
||||||
|
"label-caps": ["12px", {"lineHeight": "16px", "letterSpacing": "0.08em", "fontWeight": "500"}],
|
||||||
|
"hud-timer": ["16px", {"lineHeight": "24px", "fontWeight": "400"}],
|
||||||
|
"hud-score": ["24px", {"lineHeight": "32px", "letterSpacing": "-0.02em", "fontWeight": "700"}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
min-height: max(884px, 100dvh);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="font-body-md overflow-hidden h-[844px] w-[390px] mx-auto relative border-x border-outline/20">
|
||||||
|
<div class="scanline-overlay absolute inset-0 z-0"></div>
|
||||||
|
<!-- Top AppBar (Identity Anchor) -->
|
||||||
|
<header class="fixed top-0 w-full h-action-bar-height z-50 flex items-center px-margin-edge justify-between bg-surface dark:bg-surface text-primary dark:text-primary border-b border-outline dark:border-outline">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="material-symbols-outlined text-primary">terminal</span>
|
||||||
|
<h1 class="font-headline text-headline text-primary dark:text-primary uppercase tracking-tighter">Rusty Solitaire</h1>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<span class="material-symbols-outlined text-on-surface-variant hover:bg-surface-variant transition-colors duration-120 p-2 rounded-lg cursor-pointer">sync</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main class="pt-[64px] h-[calc(100%-64px)] flex flex-col z-10 relative">
|
||||||
|
<!-- Pseudo Status Bar -->
|
||||||
|
<div class="h-[32px] bg-surface-container flex items-center justify-between px-4 font-label-caps text-[10px] tracking-tight">
|
||||||
|
<div class="text-[#a0a0a0]">▌leaderboard.tsx</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="flex items-center gap-1">
|
||||||
|
<span class="w-1.5 h-1.5 rounded-full bg-info"></span>
|
||||||
|
<span class="text-on-surface-variant">SYNCED</span>
|
||||||
|
</span>
|
||||||
|
<span class="text-outline">v0.20.0</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Tab Strip -->
|
||||||
|
<nav class="h-[40px] bg-[#1a1a1a] border-b border-[#353535] flex items-center">
|
||||||
|
<div class="flex-1 flex flex-col items-center justify-center relative">
|
||||||
|
<span class="font-label-caps text-[11px] text-[#6fc2ef]">[ TODAY ]</span>
|
||||||
|
<div class="absolute bottom-0 w-full h-[2px] bg-[#6fc2ef]"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 flex items-center justify-center">
|
||||||
|
<span class="font-label-caps text-[11px] text-[#a0a0a0]">WEEK</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 flex items-center justify-center">
|
||||||
|
<span class="font-label-caps text-[11px] text-[#a0a0a0]">ALL-TIME</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 flex items-center justify-center">
|
||||||
|
<span class="font-label-caps text-[11px] text-[#a0a0a0]">FRIENDS</span>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="flex-1 overflow-y-auto px-margin-edge pt-4 space-y-4 pb-[88px]">
|
||||||
|
<!-- Hero Podium Card -->
|
||||||
|
<section class="h-[120px] bg-surface-container border border-[#353535] rounded-lg p-2 flex flex-col justify-between">
|
||||||
|
<div class="font-label-caps text-[10px] text-[#a0a0a0]">TOP 3 · TODAY</div>
|
||||||
|
<div class="flex gap-2 items-end justify-between flex-1 mt-1">
|
||||||
|
<!-- 2nd -->
|
||||||
|
<div class="flex-1 border border-[#a0a0a0] h-full rounded flex flex-col items-center justify-center relative py-1">
|
||||||
|
<span class="font-card-rank text-[16px] text-[#a0a0a0]">02</span>
|
||||||
|
<span class="text-[9px] font-mono text-[#d0d0d0] truncate w-full text-center px-1">base16_fan</span>
|
||||||
|
<span class="text-[10px] font-mono text-[#a0a0a0]">03:12</span>
|
||||||
|
</div>
|
||||||
|
<!-- 1st -->
|
||||||
|
<div class="flex-[1.2] border border-warning h-[110%] mb-[-2px] rounded-lg bg-surface flex flex-col items-center justify-center relative py-1 terminal-glow">
|
||||||
|
<span class="absolute top-1 right-1 text-warning material-symbols-outlined text-[14px]">star</span>
|
||||||
|
<span class="font-card-rank text-[24px] text-warning leading-none">01</span>
|
||||||
|
<span class="text-[11px] font-mono text-[#d0d0d0] font-bold truncate w-full text-center px-1">swift_jaguar</span>
|
||||||
|
<span class="text-[12px] font-mono text-[#d0d0d0]">02:47</span>
|
||||||
|
</div>
|
||||||
|
<!-- 3rd -->
|
||||||
|
<div class="flex-1 border border-[#7a5d3b] h-full rounded flex flex-col items-center justify-center relative py-1">
|
||||||
|
<span class="font-card-rank text-[16px] text-[#7a5d3b]">03</span>
|
||||||
|
<span class="text-[9px] font-mono text-[#d0d0d0] truncate w-full text-center px-1">cli_player</span>
|
||||||
|
<span class="text-[10px] font-mono text-[#a0a0a0]">03:54</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- Search/Filter Row -->
|
||||||
|
<div class="flex items-center gap-2 h-[40px]">
|
||||||
|
<div class="px-3 h-8 border border-outline rounded flex items-center justify-center bg-surface-container-low">
|
||||||
|
<span class="font-label-caps text-[10px] text-[#6fc2ef]">[ ALL TIMES ]</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 h-8 border border-outline rounded flex items-center px-2 bg-surface gap-2">
|
||||||
|
<span class="font-mono text-[12px] text-outline">/ search players</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Leaderboard List -->
|
||||||
|
<div class="space-y-0.5 font-mono text-[12px]">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flex justify-between px-2 pb-1 border-b border-outline/20 text-outline text-[10px] uppercase font-bold tracking-widest">
|
||||||
|
<span>Rank & User</span>
|
||||||
|
<span>Time</span>
|
||||||
|
</div>
|
||||||
|
<!-- Rank 04 -->
|
||||||
|
<div class="flex items-center justify-between px-2 py-2 border-b border-[#353535]">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<span class="text-[#a0a0a0] w-8">004</span>
|
||||||
|
<span class="text-on-surface">tablejockey</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-[#a0a0a0]">04:01</span>
|
||||||
|
</div>
|
||||||
|
<!-- Rank 05 -->
|
||||||
|
<div class="flex items-center justify-between px-2 py-2 border-b border-[#353535]">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<span class="text-[#a0a0a0] w-8">005</span>
|
||||||
|
<span class="text-on-surface">vim_motions</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-[#a0a0a0]">04:05</span>
|
||||||
|
</div>
|
||||||
|
<!-- Rank 06 -->
|
||||||
|
<div class="flex items-center justify-between px-2 py-2 border-b border-[#353535]">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<span class="text-[#a0a0a0] w-8">006</span>
|
||||||
|
<span class="text-on-surface">tmux_lover</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-[#a0a0a0]">04:18</span>
|
||||||
|
</div>
|
||||||
|
<!-- Rank 07 -->
|
||||||
|
<div class="flex items-center justify-between px-2 py-2 border-b border-[#353535]">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<span class="text-[#a0a0a0] w-8">007</span>
|
||||||
|
<span class="text-on-surface">nvim_dotfiles</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-[#a0a0a0]">04:23</span>
|
||||||
|
</div>
|
||||||
|
<!-- Rank 08 -->
|
||||||
|
<div class="flex items-center justify-between px-2 py-2 border-b border-[#353535]">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<span class="text-[#a0a0a0] w-8">008</span>
|
||||||
|
<span class="text-on-surface">dark_theme</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-[#a0a0a0]">04:31</span>
|
||||||
|
</div>
|
||||||
|
<!-- Spacer for truncated view -->
|
||||||
|
<div class="flex justify-center py-2 text-outline/30 tracking-[1em]">...</div>
|
||||||
|
<!-- YOU (Rank 17) -->
|
||||||
|
<div class="flex items-center justify-between px-2 py-2 bg-[#1f3a4a]/30 border border-[#6fc2ef]/40 rounded-sm">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<span class="text-[#6fc2ef] w-8 font-bold">▶ 017</span>
|
||||||
|
<span class="text-[#6fc2ef] font-bold">anonymous (YOU)</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-[#6fc2ef] font-bold">04:12</span>
|
||||||
|
</div>
|
||||||
|
<!-- Rank 18 -->
|
||||||
|
<div class="flex items-center justify-between px-2 py-2 border-b border-[#353535]">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<span class="text-[#a0a0a0] w-8">018</span>
|
||||||
|
<span class="text-on-surface">bash_brawler</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-[#a0a0a0]">05:01</span>
|
||||||
|
</div>
|
||||||
|
<!-- Rank 19 -->
|
||||||
|
<div class="flex items-center justify-between px-2 py-2 border-b border-[#353535]">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<span class="text-[#a0a0a0] w-8">019</span>
|
||||||
|
<span class="text-on-surface">curl_master</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-[#a0a0a0]">05:14</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- CLI Style Footer -->
|
||||||
|
<footer class="fixed bottom-0 w-full h-[24px] bg-[#202020] border-t border-[#353535] px-2 flex items-center justify-between font-mono text-[9px] z-50">
|
||||||
|
<div class="text-[#a0a0a0]">
|
||||||
|
<span class="text-info font-bold">▌</span> NORMAL │ leaderboard
|
||||||
|
</div>
|
||||||
|
<div class="text-[#a0a0a0] flex gap-3">
|
||||||
|
<span>[1-4] tab</span>
|
||||||
|
<span>[/] search</span>
|
||||||
|
<span>[ESC] back</span>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<!-- Shared Component: BottomNavBar -->
|
||||||
|
<nav class="fixed bottom-[24px] w-full h-action-bar-height z-50 flex justify-around items-center bg-surface-container dark:bg-surface-container border-t border-outline dark:border-outline">
|
||||||
|
<button class="flex flex-col items-center justify-center text-on-surface-variant dark:text-on-surface-variant p-2 hover:text-primary dark:hover:text-primary transition-all duration-120 ease-linear active:bg-surface-container-highest">
|
||||||
|
<span class="material-symbols-outlined">playing_cards</span>
|
||||||
|
<span class="font-label-caps text-label-caps">DEAL [F1]</span>
|
||||||
|
</button>
|
||||||
|
<button class="flex flex-col items-center justify-center text-on-surface-variant dark:text-on-surface-variant p-2 hover:text-primary dark:hover:text-primary transition-all duration-120 ease-linear active:bg-surface-container-highest">
|
||||||
|
<span class="material-symbols-outlined">undo</span>
|
||||||
|
<span class="font-label-caps text-label-caps">UNDO [Z]</span>
|
||||||
|
</button>
|
||||||
|
<button class="flex flex-col items-center justify-center text-on-surface-variant dark:text-on-surface-variant p-2 hover:text-primary dark:hover:text-primary transition-all duration-120 ease-linear active:bg-surface-container-highest">
|
||||||
|
<span class="material-symbols-outlined">lightbulb</span>
|
||||||
|
<span class="font-label-caps text-label-caps">HINT [H]</span>
|
||||||
|
</button>
|
||||||
|
<button class="flex flex-col items-center justify-center bg-primary-container dark:bg-primary-container text-on-primary-container dark:text-on-primary-container rounded-none p-2 transition-all duration-120 ease-linear active:bg-surface-container-highest">
|
||||||
|
<span class="material-symbols-outlined">analytics</span>
|
||||||
|
<span class="font-label-caps text-label-caps">STATS [S]</span>
|
||||||
|
</button>
|
||||||
|
<button class="flex flex-col items-center justify-center text-on-surface-variant dark:text-on-surface-variant p-2 hover:text-primary dark:hover:text-primary transition-all duration-120 ease-linear active:bg-surface-container-highest">
|
||||||
|
<span class="material-symbols-outlined">menu</span>
|
||||||
|
<span class="font-label-caps text-label-caps">MENU [ESC]</span>
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
</main>
|
||||||
|
</body></html>
|
||||||
|
After Width: | Height: | Size: 43 KiB |