8325bf6cf7
Replace all display-name occurrences across web pages, Rust source, docs, and Cargo metadata. Update localStorage token key from sq_token to fs_token. Tagline "Klondike Solitaire" retained as genre descriptor. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
284 lines
12 KiB
Markdown
284 lines
12 KiB
Markdown
# Terminal — Desktop Adaptation Spec
|
||
|
||
> **Why this exists.** The 24 mockups in this directory are mobile
|
||
> (390 × 844 logical, iPhone 14 Pro frame) with one exception
|
||
> (`home-menu-desktop.html`). The Stitch project that produced them
|
||
> is named "Ferrous Solitaire *Mobile* Redesign" — the mobile-first
|
||
> framing was deliberate when the new Android target opened, but
|
||
> desktop is still the primary delivery surface. Porting the mobile
|
||
> mockups 1:1 would land a 390-px-wide column floating in the middle
|
||
> of an 1800 × 1100 window. This file is the rules-based desktop
|
||
> companion — apply these adaptations whenever you port a Bevy
|
||
> plugin against a mobile mockup in this directory.
|
||
|
||
## Status
|
||
|
||
* **Token system.** All tokens (palette, type scale, spacing,
|
||
radii, motion) in `design-system.md` are layout-agnostic and
|
||
apply unchanged on both targets. Do **not** introduce desktop-
|
||
specific token variants — adapt geometry, not tokens.
|
||
* **Already adapted in code.** v0.20.0's port is layout-agnostic
|
||
(modal scaffold, toasts, table chrome, card chrome, gameplay-
|
||
feedback, splash cursor). Those surfaces already adapt
|
||
correctly because their Bevy UI nodes use flex / percent /
|
||
stretch sizing rather than fixed pixel widths from the
|
||
mockups.
|
||
* **Not yet adapted in code.** Any future plugin port that
|
||
copies layout from a mobile mockup must apply the rules below.
|
||
|
||
## Viewport assumptions
|
||
|
||
| Range | Width × height | Source |
|
||
|---|---|---|
|
||
| Mobile target | 390 × 844 | iPhone 14 Pro logical, Stitch mockup canvas |
|
||
| Desktop minimum | 1024 × 600 | Smaller windows degrade to mobile rules |
|
||
| Desktop default | ~70 % of monitor | `apply_smart_default_window_size` (since v0.19.0) |
|
||
| Desktop typical | 1600 × 900 to 2560 × 1440 | The range we tune for |
|
||
| Desktop max | 3840 × 2160 | 4K, with HiDPI scaling already applied |
|
||
|
||
The "smart default" sizer means a 1080p monitor opens a ~1344 × 756
|
||
window, a 1440p monitor opens ~1792 × 1008, a 4K monitor opens
|
||
~2688 × 1512. Tune for the 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.
|