Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da3e5423dc | |||
| a1864271de | |||
| f63db769ae | |||
| 4437a1aaf9 | |||
| e7345aed6c | |||
| 140251beae | |||
| d6f32d3154 | |||
| 8fdc41f36f | |||
| 2e25476d0a | |||
| d3cb1a51d4 | |||
| c8358f4275 | |||
| a2432dfe7a | |||
| 511550232c | |||
| e5c4f51a6e | |||
| 23902cdc44 | |||
| 3cc8eacafa | |||
| 90e24d9711 | |||
| decbe0bbd9 | |||
| 1873b3f9be | |||
| d11d97e677 | |||
| d322abf67b | |||
| c9e4c0b4cd | |||
| fe68861e10 | |||
| c33b39cf11 | |||
| 23ff62c397 | |||
| 0b2ffca016 | |||
| fbe48acef6 | |||
| cd79877933 | |||
| 52befa6199 | |||
| e63046700c | |||
| ab857bbb6e | |||
| 886e0cf8a1 | |||
| 3d92a91e3b | |||
| 9113cdb483 | |||
| c153363626 | |||
| 93b67f1d0b | |||
| 279e23d0af | |||
| 12fba2157a | |||
| f23df3b805 | |||
| 68d50b5021 | |||
| ec804d54c6 | |||
| d87761d451 | |||
| 2fb2d638bf | |||
| c9af1ead22 | |||
| ed152e2d8f | |||
| 279a834f9d | |||
| daa655a0af | |||
| 4d48cad4e3 | |||
| dd970215cc | |||
| ddb65403c2 | |||
| 62b61cc786 | |||
| 31139ae455 | |||
| 07e035771c | |||
| c5787c6953 | |||
| 716a025352 | |||
| 3eb3a26789 | |||
| 0c1cc40266 |
@@ -6,9 +6,781 @@ project follows [Semantic Versioning](https://semver.org/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
No threads in flight. v0.21.0 cut on 2026-05-08; CHANGELOG accumulates
|
No threads in flight. v0.21.6 cut on 2026-05-08; CHANGELOG accumulates
|
||||||
the next cycle here.
|
the next cycle here.
|
||||||
|
|
||||||
|
## [0.21.6] — 2026-05-08
|
||||||
|
|
||||||
|
Patch release for the post-v0.21.5 work. Through-line:
|
||||||
|
**Move Log panel + scrub-UX polish**. v0.21.5 closed out the
|
||||||
|
keyboard-accelerator surface (Space / Esc / ← / →) and the
|
||||||
|
keybind footer; v0.21.6 builds on that with two parallel
|
||||||
|
threads — accessibility + scrub-on-hold polish for the v0.21.5
|
||||||
|
surfaces, plus a brand-new Move Log panel anchored to the
|
||||||
|
viewport's bottom edge that gives players a 5-row recent-and-
|
||||||
|
upcoming move history alongside the existing top-edge banner.
|
||||||
|
|
||||||
|
The Move Log panel is the first replay-overlay surface that
|
||||||
|
*isn't* attached to the banner — it lives at a separate screen
|
||||||
|
anchor (bottom: 0) with its own spawn/despawn lifecycle.
|
||||||
|
Establishes the pattern for "multi-anchor replay UI" that the
|
||||||
|
remaining B-2 sub-piece (mini-tableau preview) will inherit.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **HC-mode coverage for the scrub track + quarter-mark notch
|
||||||
|
ticks** (`d3cb1a5`). Adds parallel primitive
|
||||||
|
`HighContrastBackground` to `ui_theme` and a paint system
|
||||||
|
`update_high_contrast_backgrounds` in `settings_plugin` that
|
||||||
|
mirrors the existing border-marker pattern but targets
|
||||||
|
`BackgroundColor` instead of `BorderColor`. Tags the 1 px
|
||||||
|
scrub track Node and all five quarter-mark notch ticks so
|
||||||
|
they bump from `BORDER_SUBTLE` (`#505050`) →
|
||||||
|
`BORDER_SUBTLE_HC` (`#a0a0a0`) under HC mode. Scrub fill
|
||||||
|
(`ACCENT_PRIMARY`) and WIN MOVE marker (`STATE_SUCCESS`)
|
||||||
|
don't get the marker — accent and state colours are already
|
||||||
|
saturated and don't need an HC luminance variant.
|
||||||
|
- **Continuous scrub on key-held arrow keys** (`2e25476`).
|
||||||
|
Holding ← or → triggers continuous step at 100 ms cadence
|
||||||
|
(10 steps/sec) — matches the mockup's `[← →] scrub`
|
||||||
|
terminology while keeping single-press = single-step
|
||||||
|
semantics. Per-key accumulators in a new
|
||||||
|
`ReplayScrubKeyHold` resource; `just_pressed` events bypass
|
||||||
|
the accumulator and fire immediately. Release resets to 0
|
||||||
|
so the next fresh press fires immediately rather than at
|
||||||
|
half-interval.
|
||||||
|
- **Move Log panel** (`d6f32d3` + `140251b` + `e7345ae` +
|
||||||
|
`4437a1a`). New bottom-edge UI panel showing a 5-row window
|
||||||
|
onto recent + upcoming moves: 2 prev rows above the active
|
||||||
|
row + active row highlighted in `ACCENT_PRIMARY` + 2 next
|
||||||
|
rows below. Header reads `▌ MOVE LOG · N/M` (or
|
||||||
|
`▌ MOVE LOG · COMPLETE` when finished). Active row carries
|
||||||
|
a `▶` focus prefix and `TEXT_PRIMARY_HC` text colour for
|
||||||
|
legible contrast against the brick-red highlight. Prev /
|
||||||
|
next rows render in `TEXT_SECONDARY` so the active row
|
||||||
|
stays the focal point.
|
||||||
|
- Sibling-of-banner pattern (separate root entity anchored
|
||||||
|
at viewport bottom, not a banner child) — same
|
||||||
|
spawn/despawn lifecycle as `ReplayFloatingProgressChip`,
|
||||||
|
different screen anchor.
|
||||||
|
- Five pure helpers handle the formatting:
|
||||||
|
`format_pile`, `format_move_body`,
|
||||||
|
`format_move_log_header`, `format_kth_recent_row` (active
|
||||||
|
+ prev), `format_kth_next_row` (next). 1-indexed display
|
||||||
|
numbers throughout (`Foundation(2)` reads as "foundation
|
||||||
|
3" rather than the enum's 0-index).
|
||||||
|
- Panel grows from 56 → 84 → 112 px across the four
|
||||||
|
move-log commits. `MOVE_LOG_PREV_ROWS` and
|
||||||
|
`MOVE_LOG_NEXT_ROWS` constants (both = 2) parameterise
|
||||||
|
the row count; `format_kth_recent_row` and
|
||||||
|
`format_kth_next_row` return empty for out-of-range k so
|
||||||
|
panels gracefully under-fill at the start (cursor=1) and
|
||||||
|
end (cursor=N-1) of a replay.
|
||||||
|
- HC marker on the panel's top border so the 1 px edge
|
||||||
|
bumps under HC mode (same pattern as the keybind footer).
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **`react_to_state_change` despawns the Move Log panel** on
|
||||||
|
`Playing → Inactive` alongside the banner root and floating
|
||||||
|
progress chip. Third query in the same defer-and-despawn
|
||||||
|
cycle.
|
||||||
|
- **Move Log panel height grew 56 → 84 → 112 px** across the
|
||||||
|
prev-rows and next-rows commits. The panel is sized to fit
|
||||||
|
the chosen row count + header + padding; tunable via the
|
||||||
|
`MOVE_LOG_PANEL_HEIGHT` const.
|
||||||
|
- **`format_active_move_row` now prefixes the `▶` focus
|
||||||
|
marker** (`e7345ae`). Wraps `format_kth_recent_row(state, 1)`
|
||||||
|
and prepends the prefix when the body is non-empty. Empty
|
||||||
|
case still returns empty — cursor=0 doesn't paint a stray
|
||||||
|
`▶` on an otherwise-empty row.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- `SESSION_HANDOFF.md` refreshed twice this cycle — once
|
||||||
|
recording the HC paint + continuous-scrub polish, then
|
||||||
|
again as the Move Log arc shipped commit-by-commit. The
|
||||||
|
Resume menu's B option now traces the full arc:
|
||||||
|
notches → labels → footer → ESC → HC → arrow keys →
|
||||||
|
HC paint → continuous scrub → move log.
|
||||||
|
|
||||||
|
### Stats
|
||||||
|
|
||||||
|
- **1273 passing tests / 0 failing** across the workspace
|
||||||
|
(net +23 from v0.21.5's 1250 baseline):
|
||||||
|
- 2 from `d3cb1a5` (HC marker on track + notches).
|
||||||
|
- 2 from `2e25476` (continuous-scrub repeat-while-held +
|
||||||
|
release-resets-accumulator).
|
||||||
|
- 8 from `d6f32d3` (move-log panel init + 5 helpers + 3
|
||||||
|
spawn / lifecycle scenarios).
|
||||||
|
- 4 from `140251b` (prev rows: helper k coverage + spawn
|
||||||
|
cardinality + spawn texts + repaint on cursor advance).
|
||||||
|
- 3 from `e7345ae` (active row highlight: wrapper bg +
|
||||||
|
text colour + focus prefix + cursor=0 stays empty).
|
||||||
|
- 4 from `4437a1a` (next rows: helper k coverage + spawn
|
||||||
|
cardinality + spawn texts + under-fill at replay end).
|
||||||
|
- Clippy clean across the workspace.
|
||||||
|
|
||||||
|
## [0.21.5] — 2026-05-08
|
||||||
|
|
||||||
|
Patch release for the post-v0.21.4 work. One through-line:
|
||||||
|
**replay-overlay scrubbing affordances + accessibility**. v0.21.4
|
||||||
|
shipped pause / resume / step + the WIN MOVE marker as the first
|
||||||
|
*scrubbing-shaped* additions to the replay overlay; v0.21.5
|
||||||
|
fills out the rest of the scrubbing UX so the player has both
|
||||||
|
visual anchor points (notches + labels) and a complete keyboard
|
||||||
|
control surface (Space / Esc / ← / →) for navigating a paused
|
||||||
|
replay.
|
||||||
|
|
||||||
|
Two of the six commits in this cycle are layout-changing — they
|
||||||
|
grow the banner height from 60 px → 76 px → 92 px to make room
|
||||||
|
for the notch labels and keybind footer. Banner geometry was
|
||||||
|
fixed for every prior B-2 commit; this release establishes the
|
||||||
|
"grow the container, add a flex-column child" pattern that the
|
||||||
|
remaining B-2 sub-pieces (move-log scroller, mini-tableau
|
||||||
|
preview) will inherit when they land.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Quarter-mark scrub-bar notches** (`fe68861`). Five 1 px
|
||||||
|
vertical ticks at 0 / 25 / 50 / 75 / 100 % give the player
|
||||||
|
visual anchor points without needing to mentally bisect the
|
||||||
|
bar. Pure helper `scrub_notch_positions()` returns the fixed
|
||||||
|
array; spawn loop sits next to the WIN MOVE marker spawn so
|
||||||
|
the lifecycles match. Notches paint in `BORDER_SUBTLE` (same
|
||||||
|
as the unfilled track) and rely on extending past the 1 px
|
||||||
|
track (5 px tall, anchored 2 px above the track top) for
|
||||||
|
visibility — same trick the WIN MOVE marker uses. Spawned
|
||||||
|
*after* the WIN MOVE marker so a notch and the marker
|
||||||
|
landing on the same percentage paint the marker on top.
|
||||||
|
- **Percentage labels under each notch** (`d322abf`). Five
|
||||||
|
`0%` / `25%` / `50%` / `75%` / `100%` labels in a new 16 px
|
||||||
|
row beneath the 1 px scrub track give the player explicit
|
||||||
|
quarter-mark readouts. Banner grew from 60 → 76 px to
|
||||||
|
accommodate the row — first **layout-changing** commit in
|
||||||
|
the B-2 arc. Pure helper `scrub_notch_labels()` returns the
|
||||||
|
fixed array, paired index-for-index with
|
||||||
|
`scrub_notch_positions()`. Spawn loop applies an "endpoints
|
||||||
|
flush, middle three percent-anchored" positioning pattern:
|
||||||
|
leftmost label gets `left: 0`, rightmost gets `right: 0`,
|
||||||
|
middle three anchor at `left: Val::Percent(p)` since Bevy
|
||||||
|
0.18 UI lacks a clean CSS-style `translate-x: -50%`
|
||||||
|
centering primitive. Label colour is `TEXT_SECONDARY`
|
||||||
|
rather than the mockup's `BORDER_SUBTLE` (the latter would
|
||||||
|
match the notches but is too low-contrast against
|
||||||
|
`BG_ELEVATED_HI` to read at 12 px).
|
||||||
|
- **Keybind-hint footer** (`1873b3f`). Vim-style mode line on
|
||||||
|
the left (`▌ NORMAL │ replay`) plus a keybind hint on the
|
||||||
|
right at the bottom edge of the banner. Banner grew from
|
||||||
|
76 → 92 px to fit the 16 px footer row. Surfaces every
|
||||||
|
wired keyboard accelerator visually so CLAUDE.md §3.3's
|
||||||
|
UI-first contract holds for keyboard accelerators too. The
|
||||||
|
footer lists *only* keybinds that are actually wired —
|
||||||
|
the only-wired-keybinds discipline means each release
|
||||||
|
cycle's hint string is a precise honest contract with the
|
||||||
|
player. Two pure helpers (`keybind_footer_mode_text`,
|
||||||
|
`keybind_footer_hint_text`) keep the static text testable.
|
||||||
|
1 px top border in `BORDER_SUBTLE` separates the footer
|
||||||
|
from the labels row.
|
||||||
|
- **ESC keyboard accelerator for replay-stop** (`90e24d9`).
|
||||||
|
New `handle_stop_keyboard` system parallels
|
||||||
|
`handle_pause_keyboard` in shape — fires only when state
|
||||||
|
is `Playing`, calls `stop_replay_playback`. Cross-plugin
|
||||||
|
coordination via `pause_plugin::toggle_pause`: added a
|
||||||
|
fourth defer-if check
|
||||||
|
(`replay_state.is_some_and(|s| s.is_playing())`) right
|
||||||
|
after the existing `other_modal_scrims` check so ESC
|
||||||
|
during active replay belongs to the replay overlay, not
|
||||||
|
the pause modal.
|
||||||
|
- **HC-mode coverage for the keybind-footer top border**
|
||||||
|
(`23902cd`).
|
||||||
|
`HighContrastBorder::with_default(BORDER_SUBTLE)` marker
|
||||||
|
on the footer's border-carrying Node so the existing
|
||||||
|
`apply_high_contrast_borders` system bumps the 1 px top
|
||||||
|
border from `#505050` → `#a0a0a0` when
|
||||||
|
`Settings::high_contrast_mode` is on. Without the marker
|
||||||
|
the footer reads as floating loose under HC because the
|
||||||
|
border that anchors it to the labels row is
|
||||||
|
near-invisible.
|
||||||
|
- **← / → keyboard accelerators for paused stepping**
|
||||||
|
(`e5c4f51`). New `step_backwards_replay_playback` in
|
||||||
|
`replay_playback.rs` decrements the cursor and dispatches
|
||||||
|
`UndoRequestEvent`; the game's `handle_undo` reads it
|
||||||
|
next frame to reverse its most-recent move. Hooks the
|
||||||
|
existing undo system rather than replaying-forward-from-
|
||||||
|
zero — every replay-applied move pushes to the undo stack
|
||||||
|
the same way a player move would, so undo is the right
|
||||||
|
reversal primitive. Both arrow keys are paused-only via
|
||||||
|
the same destructure-gate pattern the forward step uses.
|
||||||
|
The mockup labels these `[← →] scrub`; single-move step
|
||||||
|
is the closest behaviour shippable today, so the footer
|
||||||
|
hint reads `[← →] step` — only-wired-keybinds discipline.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Banner height grew 60 → 76 → 92 px** across two
|
||||||
|
layout-changing commits (`d322abf` then `1873b3f`). Top
|
||||||
|
row's `flex_grow: 1.0` still consumes 59 px so the
|
||||||
|
existing content (label / progress chip / buttons) has
|
||||||
|
the same vertical space; the new rows (16 px labels +
|
||||||
|
16 px footer) extend the banner downward into the
|
||||||
|
gameplay area. Banner geometry is now mutable — every
|
||||||
|
prior B-2 commit fit inside fixed 60 px space.
|
||||||
|
- **Keybind-footer hint text grew alongside the wirings**:
|
||||||
|
`[SPACE] pause/resume` →
|
||||||
|
`[SPACE] pause/resume · [ESC] stop` →
|
||||||
|
`[SPACE] pause/resume · [ESC] stop · [← →] step`.
|
||||||
|
- **`pause_plugin::toggle_pause` now defers when a replay
|
||||||
|
is active** (`90e24d9`). Adds a fourth defer-if check to
|
||||||
|
the existing modal-stack pattern.
|
||||||
|
- **`ReplayOverlayPlugin` registers
|
||||||
|
`add_message::<UndoRequestEvent>()`** (`e5c4f51`).
|
||||||
|
Defensive registration so the plugin runs cleanly under
|
||||||
|
`MinimalPlugins` without `GamePlugin` attached.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- `SESSION_HANDOFF.md` refreshed five times this cycle.
|
||||||
|
The B option in the Resume menu now traces the full arc:
|
||||||
|
notches → labels → footer → ESC → HC → arrow keys.
|
||||||
|
- The pre-existing `daily_challenge` warning test that
|
||||||
|
fails when wall-clock UTC is within 30 minutes of
|
||||||
|
midnight is documented in this cycle's handoff. Same
|
||||||
|
shape as the earlier `winnable_seed_search` flake —
|
||||||
|
time-dependent, deterministically passes outside the
|
||||||
|
trigger window.
|
||||||
|
|
||||||
|
### Stats
|
||||||
|
|
||||||
|
- **1250 total tests / 1249 passing / 1 pre-existing
|
||||||
|
time-dependent flake** across the workspace (net +22 from
|
||||||
|
v0.21.4's 1228 baseline):
|
||||||
|
- 4 from `fe68861` (scrub-notch coverage)
|
||||||
|
- 4 from `d322abf` (notch-label coverage)
|
||||||
|
- 4 from `1873b3f` (keybind-footer coverage)
|
||||||
|
- 3 from `90e24d9` (ESC-accelerator coverage)
|
||||||
|
- 1 from `23902cd` (HC-marker coverage)
|
||||||
|
- 6 from `e5c4f51` (arrow-keyboard coverage)
|
||||||
|
- **Pre-existing flake**:
|
||||||
|
`daily_challenge_plugin::tests::check_system_fires_warning_event_only_once_per_day`
|
||||||
|
fails when wall-clock UTC is within 30 minutes of
|
||||||
|
midnight. Verified pre-existing by stash-and-retest
|
||||||
|
before each commit. Will pass deterministically outside
|
||||||
|
the trigger window. Not introduced by this release.
|
||||||
|
- Clippy clean across the workspace.
|
||||||
|
|
||||||
|
## [0.21.4] — 2026-05-08
|
||||||
|
|
||||||
|
Patch release for the post-v0.21.3 work. One through-line:
|
||||||
|
**replay-scrubbing accessibility**. The replay overlay used to be
|
||||||
|
pure-passive — the player started a replay, watched it execute,
|
||||||
|
and waited for it to end. v0.21.4 adds the scaffolding for
|
||||||
|
*navigating within* a replay: a WIN MOVE marker on the scrub bar
|
||||||
|
so the player can see at a glance where the winning move sits,
|
||||||
|
and pause / resume / step controls so they can stop on any move
|
||||||
|
and inspect the board.
|
||||||
|
|
||||||
|
The work is also the first three commits on the B-2 replay
|
||||||
|
screen-takeover redesign arc. The remaining pieces (screen-
|
||||||
|
takeover layout, move-log scroller, mini-tableau preview) are
|
||||||
|
deferred to a future cycle because they need a layout reflow
|
||||||
|
that the existing banner-only overlay can't carry.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **`Replay::win_move_index: Option<usize>` data field**
|
||||||
|
(`ab857bb`). Additive optional field on the persisted
|
||||||
|
`Replay` shape. `#[serde(default)]` keeps older
|
||||||
|
`latest_replay.json` / `replays.json` files loadable without
|
||||||
|
bumping `REPLAY_SCHEMA_VERSION` — this is purely additive.
|
||||||
|
Populated at the live recording site
|
||||||
|
(`game_plugin::handle_game_won`) via a new builder-style
|
||||||
|
setter `Replay::with_win_move_index`. For fresh recordings
|
||||||
|
the value is always `Some(moves.len() - 1)` because recording
|
||||||
|
freezes on win, but storing it explicitly lets the playback
|
||||||
|
UI read the WIN MOVE position directly without re-deriving
|
||||||
|
on every render.
|
||||||
|
- **WIN MOVE scrub-bar marker** (`52befa6`). New
|
||||||
|
`ReplayOverlayWinMoveMarker` component spawned as a sibling
|
||||||
|
to `ReplayOverlayScrubFill` under the 1px scrub track,
|
||||||
|
absolute-positioned at `replay.win_move_index / total %` of
|
||||||
|
the bar. Painted in `STATE_SUCCESS` (green) so the marker
|
||||||
|
reads as "this is where the win lives." Pure helper
|
||||||
|
`win_move_marker_pct` returns `None` for any state where the
|
||||||
|
marker shouldn't draw (Inactive, Completed, replay missing
|
||||||
|
the field, empty move list); percentage clamps to `[0, 100]`
|
||||||
|
defensively. Spawn-time only — the position never changes
|
||||||
|
during a single playback because the underlying `Replay` is
|
||||||
|
immutable while `Playing`.
|
||||||
|
- **Pause / Resume / Step playback controls** (`fbe48ac`). New
|
||||||
|
`paused: bool` field on `ReplayPlaybackState::Playing`.
|
||||||
|
`tick_replay_playback` skips the `secs_to_next` decrement
|
||||||
|
entirely while paused so cursor and timer freeze together;
|
||||||
|
resuming starts the next move from a full interval. New
|
||||||
|
public API: `toggle_pause_replay_playback` and
|
||||||
|
`step_replay_playback` (the latter hard-gated to `Playing {
|
||||||
|
paused: true }` via the destructure pattern itself, so
|
||||||
|
manual stepping can't race the tick loop). On-screen Pause
|
||||||
|
and Step buttons sit alongside the existing Stop button;
|
||||||
|
`Space` keyboard accelerator toggles pause / resume.
|
||||||
|
- **`Replay::with_win_move_index` builder** (`ab857bb`).
|
||||||
|
Chainable setter so the recording site can write
|
||||||
|
`Replay::new(...).with_win_move_index(idx)`. Keeps
|
||||||
|
`Replay::new`'s signature stable across the 13+ existing
|
||||||
|
test-fixture call sites that don't care about the field.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **`Replay::new` writes `win_move_index: None`** (`ab857bb`).
|
||||||
|
Existing canonical constructor stays signature-compatible
|
||||||
|
with all existing callers. The field is opt-in via the
|
||||||
|
builder.
|
||||||
|
- **`game_plugin::handle_game_won` populates the new field**
|
||||||
|
(`ab857bb`). The recording site computes
|
||||||
|
`recording.moves.len().checked_sub(1)` as the win-move
|
||||||
|
index. `checked_sub` rather than direct subtraction guards
|
||||||
|
the unreachable empty-recording branch (which is also
|
||||||
|
guarded earlier in the function).
|
||||||
|
- **`tick_replay_playback` honors the new `paused` flag**
|
||||||
|
(`fbe48ac`). Skipping the timer decrement is the only
|
||||||
|
behavior change; the loop body and Completed-detection are
|
||||||
|
unchanged. Stepping fires moves directly via
|
||||||
|
`step_replay_playback`, bypassing the tick path entirely.
|
||||||
|
- **Pause / Resume button label is reactive** (`fbe48ac`).
|
||||||
|
`update_pause_button_label` walks `Children` from the
|
||||||
|
marked button to its inner `Text` and repaints the label
|
||||||
|
whenever `ReplayPlaybackState` changes. Pure helper
|
||||||
|
`pause_button_label` covers all four state arms (running,
|
||||||
|
paused, inactive, completed).
|
||||||
|
- **25 existing `Playing { ... }` construction sites gained
|
||||||
|
`paused: false`** (`fbe48ac`). Mechanical edit across
|
||||||
|
`replay_overlay`, `achievement_plugin`, and
|
||||||
|
`replay_playback` tests to satisfy the new field
|
||||||
|
requirement. No behavioral change.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- `SESSION_HANDOFF.md` refreshed three times this cycle —
|
||||||
|
once after each post-cut feature commit. The B-2 entry in
|
||||||
|
the Visual-identity follow-ups list now points at the
|
||||||
|
remaining sub-pieces (screen-takeover layout, move-log
|
||||||
|
scroller, mini-tableau preview) as a single multi-session
|
||||||
|
arc rather than three independent ones, since they share a
|
||||||
|
layout-reflow prerequisite.
|
||||||
|
|
||||||
|
### Stats
|
||||||
|
|
||||||
|
- **1228 passing tests / 0 failing** across the workspace
|
||||||
|
(net +21 from v0.21.3's 1207 baseline):
|
||||||
|
- 5 from `ab857bb`'s `win_move_index` coverage: default
|
||||||
|
constructor, builder set / set-None, on-disk round-trip,
|
||||||
|
legacy-JSON-loads-with-None backward-compat. The last
|
||||||
|
test pins the no-schema-bump claim — if a future refactor
|
||||||
|
drops the `#[serde(default)]`, that test catches it.
|
||||||
|
- 8 from `52befa6`'s WIN MOVE marker: pure-helper truth
|
||||||
|
table (Inactive / Completed / no-field / correct-position
|
||||||
|
/ clamp) + spawn-presence-with-field /
|
||||||
|
spawn-absence-without / despawn-with-overlay observables.
|
||||||
|
- 8 from `fbe48ac`'s playback controls: label truth table,
|
||||||
|
label repaint on state change, click-toggles-paused,
|
||||||
|
step advances cursor by exactly one with paused
|
||||||
|
preserved, step-while-running no-op, Space toggles
|
||||||
|
paused.
|
||||||
|
- Zero clippy warnings under `cargo clippy --workspace
|
||||||
|
--all-targets -- -D warnings`.
|
||||||
|
- `cargo test --workspace` clean.
|
||||||
|
|
||||||
|
## [0.21.3] — 2026-05-08
|
||||||
|
|
||||||
|
Patch release for the post-v0.21.2 work. One through-line:
|
||||||
|
**accessibility arc closure**. v0.21.2 explicitly carved out
|
||||||
|
"dynamic-paint sites" (HUD action buttons, modal buttons, radial
|
||||||
|
menu rim) on the assumption that their existing paint cycles would
|
||||||
|
race the central `update_high_contrast_borders` system. v0.21.3
|
||||||
|
walks the actual code, finds the carve-out was over-cautious, and
|
||||||
|
closes it. Bonus: the first real consumer of `ToastVariant::Warning`
|
||||||
|
also lands here, making the `ToastVariant` enum fully load-bearing
|
||||||
|
(every variant has at least one driver).
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **`WarningToastEvent(String)` — first `ToastVariant::Warning`
|
||||||
|
consumer** (`279e23d`). Generic carrier message that any system
|
||||||
|
can fire to spawn a 4 s amber-bordered fire-and-forget toast.
|
||||||
|
Mirrors the v0.21.2 `MoveRejectedEvent` → `Error` toast wiring:
|
||||||
|
domain message crosses the plugin boundary, the animation
|
||||||
|
plugin's `handle_warning_toast` system reads it and spawns. Not
|
||||||
|
queued (Warning is alert-shaped, not info-shaped — should never
|
||||||
|
block on a queue).
|
||||||
|
- **Daily-challenge-expiry warning** (`279e23d`). First in-engine
|
||||||
|
driver of `WarningToastEvent`. New
|
||||||
|
`daily_challenge_plugin::check_daily_expiry_warning` system
|
||||||
|
fires at most once per `DailyChallengeResource::date` when the
|
||||||
|
player is within 30 min of UTC midnight reset and today's
|
||||||
|
challenge isn't yet complete. Suppression decided by a pure
|
||||||
|
helper (`compute_expiry_warning_minutes`) covering: already-
|
||||||
|
completed-today, already-shown-for-this-date, outside the
|
||||||
|
threshold window, post-midnight rollover. Pure-helper-plus-
|
||||||
|
thin-system shape because `Utc::now()` can't be pinned without
|
||||||
|
injecting a clock resource — overkill for one consumer.
|
||||||
|
- **`radial_rim_outline` pure helper** (`c153363`). Decision
|
||||||
|
logic for the radial-menu rim outline colour. Resting outlines
|
||||||
|
always carry `BORDER_SUBTLE`; focused outlines carry
|
||||||
|
`BORDER_STRONG` normally and `BORDER_SUBTLE_HC` under HC. Naive
|
||||||
|
marker substitution would invert the focused-vs-resting
|
||||||
|
hierarchy because `BORDER_SUBTLE_HC` (`#a0a0a0`) is *lighter*
|
||||||
|
than `BORDER_STRONG` (`#505050`); folding the choice in here
|
||||||
|
keeps the focused rim more visible under HC, not less.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **HC marker pattern extended to HUD action buttons + modal
|
||||||
|
buttons** (`c153363`). Re-reading the code revealed both sites'
|
||||||
|
paint systems (`paint_action_buttons`, `paint_modal_buttons`)
|
||||||
|
only mutate `BackgroundColor` — `BorderColor` is set once at
|
||||||
|
spawn and never touched. So the existing
|
||||||
|
`HighContrastBorder::with_default(BORDER_SUBTLE)` marker
|
||||||
|
pattern works cleanly for both, no race. v0.21.2's carve-out
|
||||||
|
comment was based on assumed-but-not-actual race risk; this
|
||||||
|
cycle treats it as the doc-vs-implementation drift pattern in
|
||||||
|
the wild and verifies before trusting.
|
||||||
|
- **Radial menu rim folds HC into per-frame respawn**
|
||||||
|
(`c153363`). The rim is the only true dynamic-painter of the
|
||||||
|
three carved-out sites — `radial_redraw_overlay` despawns and
|
||||||
|
respawns all rim sprites every frame the radial is `Active`.
|
||||||
|
The `HighContrastBorder` marker can't apply (entities don't
|
||||||
|
persist across frames) so HC is read directly in the system
|
||||||
|
via `Option<Res<SettingsResource>>` and routed through
|
||||||
|
`radial_rim_outline`. The `Option<Res<...>>` shape preserves
|
||||||
|
test compatibility under `MinimalPlugins`.
|
||||||
|
- **Animation plugin registers `WarningToastEvent`** (`279e23d`).
|
||||||
|
Joins `InfoToastEvent`, `MoveRejectedEvent` etc. in
|
||||||
|
`AnimationPlugin::build`. Daily-challenge plugin also
|
||||||
|
registers it (idempotent) so the message exists when running
|
||||||
|
the daily plugin under `MinimalPlugins` without the animation
|
||||||
|
plugin attached.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- `SESSION_HANDOFF.md` refreshed twice this cycle — once after
|
||||||
|
the Toast Warning wiring (menu trimmed 5 → 4 options), and
|
||||||
|
again after the HC dynamic-paint rollout (menu trimmed 4 → 3,
|
||||||
|
with all remaining options now flagged as multi-session). The
|
||||||
|
`High-contrast accessibility mode` entry in the Visual-identity
|
||||||
|
follow-ups list is updated to reflect that no "un-tagged
|
||||||
|
because race-risk" surfaces remain.
|
||||||
|
|
||||||
|
### Stats
|
||||||
|
|
||||||
|
- **1207 passing tests / 0 failing** across the workspace
|
||||||
|
(net +12 from v0.21.2's 1195 baseline):
|
||||||
|
- 7 tests for `compute_expiry_warning_minutes` (`279e23d`)
|
||||||
|
covering each suppression rule + the inclusive boundary at
|
||||||
|
exactly 30 min remaining.
|
||||||
|
- 1 in-Bevy test (`check_system_fires_warning_event_only_once_per_day`)
|
||||||
|
pinning `DailyExpiryWarningShown`'s once-per-date
|
||||||
|
suppression and the symmetric "already-completed-today"
|
||||||
|
suppression.
|
||||||
|
- 4 truth-table tests for `radial_rim_outline` (`c153363`):
|
||||||
|
focused × HC. The "resting stays subtle under HC" test
|
||||||
|
explicitly documents *why* — it's the hierarchy-preservation
|
||||||
|
invariant a future refactor might be tempted to break.
|
||||||
|
- Zero clippy warnings under `cargo clippy --workspace
|
||||||
|
--all-targets -- -D warnings`.
|
||||||
|
- `cargo test --workspace` clean.
|
||||||
|
|
||||||
|
## [0.21.2] — 2026-05-08
|
||||||
|
|
||||||
|
Patch release for the post-v0.21.1 polish work. Three through-
|
||||||
|
lines: **accessibility extensions** (reduce-motion gating for
|
||||||
|
splash animations, full HC chrome rollout across 8 surfaces),
|
||||||
|
**replay polish** (floating MOVE chip above the focused card
|
||||||
|
during playback), and the **first real consumer of
|
||||||
|
`ToastVariant::Error`** (invalid-move feedback as the third leg
|
||||||
|
of the existing audio + visual rejection-feedback stool).
|
||||||
|
|
||||||
|
The accessibility extensions close two threads v0.21.1 left
|
||||||
|
explicitly open: reduce-motion was previously gated only on card
|
||||||
|
slide_secs, and HC borders had `BORDER_SUBTLE_HC` defined but no
|
||||||
|
consumers. v0.21.2 finishes both — non-essential motion in the
|
||||||
|
splash boot screen now respects reduce-motion, and every static-
|
||||||
|
border chrome surface (modal scaffold, tooltip, help / stats /
|
||||||
|
home / settings panels) boosts to the HC variant under high-
|
||||||
|
contrast mode. Dynamic-paint sites (HUD action buttons, modal
|
||||||
|
buttons, radial menu rim) intentionally stay un-tagged because
|
||||||
|
their existing paint cycles would race the HC system; they
|
||||||
|
remain open for a future iteration that needs a different shape.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **`sync_pile_marker_visibility` system precursor was v0.21.1's;
|
||||||
|
this cycle adds**: `update_high_contrast_borders` system in
|
||||||
|
`settings_plugin` (`c9af1ea`). Walks all entities tagged with
|
||||||
|
`HighContrastBorder` each Update tick, swaps `BorderColor` to
|
||||||
|
`BORDER_SUBTLE_HC` when high-contrast mode is on. Compares
|
||||||
|
current colour and only mutates when different so Bevy's
|
||||||
|
change-detection doesn't trigger repaints every frame. New
|
||||||
|
`HighContrastBorder { default_color: Color }` component carries
|
||||||
|
the off-state colour at each tagged site so the system can
|
||||||
|
revert correctly.
|
||||||
|
- **HC chrome rollout — 8 tagged surfaces** (`c9af1ea` modal
|
||||||
|
scaffold; `d87761d` tooltip + onboarding key chips + help
|
||||||
|
panel key chips + stats panel cells; `ec804d5` home Level/XP/
|
||||||
|
Score row + home mode-selector buttons + home mode-hotkey
|
||||||
|
chips + 4 settings panel surfaces). Each tagging is one line
|
||||||
|
on the spawn tuple. The marker-component architecture pays
|
||||||
|
back proportionally to the number of consumers — the per-
|
||||||
|
commit cost dropped from ~75 lines (foundation + first
|
||||||
|
surface) to ~13 lines (4 surfaces) to ~9 lines (7 surfaces).
|
||||||
|
- **Floating MOVE chip during replay** (`2fb2d63`). New
|
||||||
|
`ReplayFloatingProgressChip` marker on a `Text2d` entity
|
||||||
|
rendered in 2D world space above the destination pile of the
|
||||||
|
most-recently-applied move. Sibling of the banner overlay (not
|
||||||
|
a child) because it lives in world-space coordinates, not the
|
||||||
|
UI tree. Lifecycle matches the banner: `spawn_overlay` spawns
|
||||||
|
the chip alongside the banner when a replay starts;
|
||||||
|
`react_to_state_change` despawns it when the replay ends.
|
||||||
|
World-space placement (rather than UI-space + camera projection)
|
||||||
|
uses the same `LayoutResource` pile coordinates that drive
|
||||||
|
every other piece of pile geometry — stays correctly positioned
|
||||||
|
through window resizes for free. Hidden when cursor=0 (no
|
||||||
|
moves applied yet) or when the last applied move was a
|
||||||
|
`StockClick` (no destination pile to follow).
|
||||||
|
- **`handle_move_rejected_toast` system + first real
|
||||||
|
`ToastVariant::Error` consumer** (`68d50b5`). When
|
||||||
|
`MoveRejectedEvent` fires (illegal placement attempt), spawns
|
||||||
|
a 2-second pink-bordered "Invalid move" toast. Joins the
|
||||||
|
existing `card_invalid.wav` (audio cue) and destination-pile
|
||||||
|
shake (visual cue) as the accessibility-focused readable text
|
||||||
|
channel — covers deaf players (no audio reliance) and
|
||||||
|
reduce-motion players (no shake reliance) with a persistent
|
||||||
|
~2 s text cue. Drops the `#[allow(dead_code)]` from
|
||||||
|
`ToastVariant::Error` and updates its doc to point at the new
|
||||||
|
consumer.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Splash scanline overlay skipped under reduce-motion**
|
||||||
|
(`ed152e2`). `spawn_splash` reads `Settings::reduce_motion_mode`
|
||||||
|
and skips the scanline texture / overlay node entirely when
|
||||||
|
on. Without the scanlines the boot screen still reads as
|
||||||
|
terminal-themed (foreground content, borders, palette swatches
|
||||||
|
unchanged); the scanlines are decorative.
|
||||||
|
- **Splash cursor pulse held under reduce-motion** (`ed152e2`).
|
||||||
|
`pulse_splash_cursor` reads `Settings::reduce_motion_mode` and
|
||||||
|
skips the per-frame sine-pulse multiplier when on — the cursor
|
||||||
|
still fades in / out with the global splash alpha (essential
|
||||||
|
timing) but doesn't blink. Spec calls out non-essential motion
|
||||||
|
as the reduce-motion target; the global fade is essential
|
||||||
|
(otherwise the splash would hard-cut on/off, which is
|
||||||
|
jarring), and the cursor blink is decorative.
|
||||||
|
- **`AnimationPlugin::build` registers
|
||||||
|
`MoveRejectedEvent`** (`68d50b5`). Bevy's `add_message` is
|
||||||
|
idempotent, so the duplicate registration with
|
||||||
|
`feedback_anim_plugin` (which already registered the message)
|
||||||
|
coexists cleanly. Required for the new
|
||||||
|
`handle_move_rejected_toast` system to run under
|
||||||
|
MinimalPlugins (tests).
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- `docs/ui-mockups/design-system.md` and `SESSION_HANDOFF.md`
|
||||||
|
refreshed in lockstep with the rollouts. The handoff's
|
||||||
|
Resume-prompt menu trimmed twice this cycle as Options A and F
|
||||||
|
closed in v0.21.1, then this commit cycle's accessibility
|
||||||
|
extensions implicitly closed the "future scope" footnotes
|
||||||
|
v0.21.1 left on F's documentation.
|
||||||
|
|
||||||
|
### Stats
|
||||||
|
|
||||||
|
- **1195 passing tests / 0 failing** across the workspace
|
||||||
|
(net +3 from v0.21.1's 1192 baseline). New tests added by
|
||||||
|
this cycle:
|
||||||
|
- `splash_skips_scanline_overlay_under_reduce_motion`
|
||||||
|
(`ed152e2`) pins the reduce-motion gate on the splash
|
||||||
|
scanline overlay. Discovered an asset-fixture bootstrapping
|
||||||
|
detail along the way: under `MinimalPlugins`,
|
||||||
|
`Assets<Image>` isn't auto-inserted; the test had to add
|
||||||
|
`bevy::asset::AssetPlugin::default()` and
|
||||||
|
`init_asset::<bevy::image::Image>()`. Pattern flagged for
|
||||||
|
future asset-using tests.
|
||||||
|
- `floating_chip_spawns_and_despawns_with_overlay`
|
||||||
|
(`2fb2d63`) pins the floating MOVE chip's lifecycle:
|
||||||
|
absent on Inactive, exactly one on Playing, absent again
|
||||||
|
on return to Inactive.
|
||||||
|
- `move_rejected_event_spawns_error_toast` (`68d50b5`) pins
|
||||||
|
the new toast wiring: firing a `MoveRejectedEvent` spawns
|
||||||
|
exactly one `ToastOverlay` on the next tick.
|
||||||
|
- Zero clippy warnings under `cargo clippy --workspace
|
||||||
|
--all-targets -- -D warnings`.
|
||||||
|
- `cargo test --workspace` clean.
|
||||||
|
|
||||||
|
## [0.21.1] — 2026-05-08
|
||||||
|
|
||||||
|
Patch release for the post-v0.21.0 work — closes Resume-prompt
|
||||||
|
Options A (app icon) and F (high-contrast + reduce-motion
|
||||||
|
accessibility modes), plus a card-visual iteration cycle that
|
||||||
|
moved through three states: the v0.21.0 Terminal pink/gray, a
|
||||||
|
brief 4-colour-deck experiment (hearts pink, diamonds gold,
|
||||||
|
clubs lime, spades gray), and a reversion to traditional 2-colour
|
||||||
|
"Microsoft Solitaire on dark mode" pairing (saturated red +
|
||||||
|
near-white). Two visible bugs surfaced and were fixed during
|
||||||
|
the iteration: the suit-coloured border produced anti-aliasing
|
||||||
|
artifacts at rounded card corners (border dropped entirely),
|
||||||
|
and the pile-marker sprite bleed-through created visible "gray
|
||||||
|
L" shapes where cards sat on markers (markers now hide when
|
||||||
|
occupied — the documented but previously-not-enforced "remain
|
||||||
|
visible only where a pile is empty" invariant).
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Desktop window icon** (`3eb3a26`). Runtime `Window::icon`
|
||||||
|
wired via `WinitWindows`; embedded 256 px PNG decoded on
|
||||||
|
startup via `tiny_skia` and handed to winit. Plus a 9-size
|
||||||
|
PNG hierarchy at `assets/icon/icon_<size>.png` covering
|
||||||
|
Linux hicolor (16/24/32/48/64/128/256/512), Windows `.ico`
|
||||||
|
targets (16/32/48/256), and macOS `.icns` targets
|
||||||
|
(16/32/64/128/256/512/1024). All sizes generated from a
|
||||||
|
shared `icon_svg` builder (Terminal `▌RS` mark on dark
|
||||||
|
`#151515` with brick-red accent) by a new
|
||||||
|
`icon_generator` example. Pin test `icon_svg_pin` guards
|
||||||
|
rasterised RGBA bytes against `usvg`/`resvg` drift. Two
|
||||||
|
new `solitaire_app` deps target-gated to non-Android:
|
||||||
|
direct `winit = "0.30"` (for `Icon` construction —
|
||||||
|
`bevy_winit` 0.18 doesn't re-export it) and direct
|
||||||
|
`tiny-skia` (for PNG → RGBA decode). Android draws its
|
||||||
|
launcher icon from the APK manifest, so neither dep is
|
||||||
|
needed there.
|
||||||
|
- **`Settings::high_contrast_mode` flag** (`c5787c6`). Boosts
|
||||||
|
card text colours: hearts/diamonds → `RED_SUIT_COLOUR_HC`
|
||||||
|
(`#ff6868`), clubs/spades → `TEXT_PRIMARY_HC` (`#f5f5f5`).
|
||||||
|
Composes with `color_blind_mode`: CBM lime wins over HC red
|
||||||
|
on red suits when both are on; HC still applies to dark
|
||||||
|
suits independent of CBM. Six new tests pin the truth
|
||||||
|
table.
|
||||||
|
- **`Settings::reduce_motion_mode` flag** (`c5787c6`). Forces
|
||||||
|
`effective_slide_secs` to `0.0` regardless of the
|
||||||
|
`AnimSpeed` selection, making cards snap instantly to their
|
||||||
|
target. Two new tests pin the gate behaviour and the
|
||||||
|
fall-through to `anim_speed_to_secs` when off. Future
|
||||||
|
scope: gate splash scanline / cursor pulse / warning-chip
|
||||||
|
pulse on the same flag.
|
||||||
|
- **Settings UI toggle rows** (`07e0357`). Two new rows in
|
||||||
|
the Settings panel under Cosmetic (alongside Color-blind):
|
||||||
|
"High Contrast" and "Reduce Motion". `tab-walk` order
|
||||||
|
visits all three accessibility flags in one vertical run.
|
||||||
|
Same shape as the existing `ColorBlindText` toggle scaffold
|
||||||
|
with marker components, label updaters, click handlers,
|
||||||
|
and disambiguator chains.
|
||||||
|
- **`sync_pile_marker_visibility` system** (`4d48cad`).
|
||||||
|
Implements the module-level doc invariant in `table_plugin`
|
||||||
|
("pile markers ... remain visible only where a pile is
|
||||||
|
empty") that was previously declared but not enforced.
|
||||||
|
Hides the pile-marker sprite for any pile that has a card
|
||||||
|
on top, shows it for empty piles. Closes the "gray L
|
||||||
|
corners" artifact where the marker's translucent fill bled
|
||||||
|
through the rounded card corners.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Card-face suit colours** (`62b61cc` → `ddb6540`). Started
|
||||||
|
the cycle at v0.21.0's Terminal pink (`#fb9fb1`) / gray
|
||||||
|
(`#d0d0d0`), briefly experimented with a 4-colour deck
|
||||||
|
(`62b61cc` — hearts pink, diamonds gold, clubs lime, spades
|
||||||
|
gray) for faster suit recognition by hue alone, then
|
||||||
|
reverted to traditional 2-colour pairing at the player's
|
||||||
|
request (`ddb6540`). Final state: `RED_SUIT_COLOUR =
|
||||||
|
#e35353` (saturated red, replacing the v0.21.0 pink) and
|
||||||
|
`BLACK_SUIT_COLOUR = #e8e8e8` (near-white, brighter than
|
||||||
|
the v0.21.0 `#d0d0d0` foreground gray so the dark suits
|
||||||
|
read as a chromatic-neutral counterpart to the saturated
|
||||||
|
red rather than as "the same gray as body text"). Reads
|
||||||
|
like Microsoft Solitaire on dark mode. `RED_SUIT_COLOUR_HC`
|
||||||
|
rebumped to `#ff6868` (brighter saturated red) so HC stays
|
||||||
|
more chromatic than the new default red rather than the
|
||||||
|
previous pinker boost. The 4-colour experiment's commit
|
||||||
|
history is preserved in the log; net delta vs. v0.21.0 is
|
||||||
|
the new red + new near-white.
|
||||||
|
- **Card-face border dropped** (`dd97021`). The earlier 1 px
|
||||||
|
suit-coloured stroke on the card body produced
|
||||||
|
anti-aliasing artifacts at the rounded corners (the colored
|
||||||
|
stroke faded through gray pixels into the play surface).
|
||||||
|
Cards now have no border — body fill alone defines the
|
||||||
|
shape against the play surface; the 5-unit brightness gap
|
||||||
|
between `#1a1a1a` body and `#151515` surface is enough to
|
||||||
|
read as a card edge without an explicit stroke.
|
||||||
|
`design-system.md` § Game Cards line 225 updated in
|
||||||
|
lockstep.
|
||||||
|
- **Settings UI accessibility row count** (`07e0357`). Three
|
||||||
|
toggles in Cosmetic now: Color-blind, High Contrast,
|
||||||
|
Reduce Motion. Existing query-disambiguator chains in
|
||||||
|
`handle_settings_buttons` extended with `Without<HighContrastText>`
|
||||||
|
and `Without<ReduceMotionText>` so the new components
|
||||||
|
don't ambiguate the existing mutations.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Bevy 0.18 system-param validation panic on icon startup**
|
||||||
|
(`716a025`). `NonSend<WinitWindows>` failed validation on
|
||||||
|
the first few frames before winit's `Resumed` event populated
|
||||||
|
the resource. Bevy 0.18's stricter validation panics rather
|
||||||
|
than skips when a non-send resource is absent; the error
|
||||||
|
message itself spelled out the fix ("wrap the parameter in
|
||||||
|
`Option<T>` and handle `None` when it happens"). Wraps
|
||||||
|
`winit_windows` as `Option<NonSend<WinitWindows>>` and
|
||||||
|
early-returns on `None`.
|
||||||
|
- **"Gray L corners" on cards** (`4d48cad`). Two artifacts
|
||||||
|
were producing similar-looking grey at card corners: the
|
||||||
|
SVG stroke fading through gray pixels (closed by `dd97021`)
|
||||||
|
and the pile-marker sprite bleeding through the rounded
|
||||||
|
cutouts (closed by `4d48cad`). Right test target, wrong
|
||||||
|
visible-artifact target on the first attempt — the pin
|
||||||
|
test correctly drifted 52 face hashes, but the visible
|
||||||
|
gray came from a different layer. Two layers, two fixes;
|
||||||
|
the second closed the player-visible complaint.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- `docs/ui-mockups/design-system.md` § Suit Colors retitled
|
||||||
|
through three states (Terminal 2-color → "Four-color
|
||||||
|
deck" → final "Two-color traditional pairing"). Final
|
||||||
|
table records the saturated red + near-white. § Game Cards
|
||||||
|
border spec changed from "1px solid in suit color" to
|
||||||
|
"Border: none" with the artifact-rationale audit trail.
|
||||||
|
CBM section text updated through each colour-scheme
|
||||||
|
iteration.
|
||||||
|
- `SESSION_HANDOFF.md` refreshed twice this cycle (`0c1cc40`
|
||||||
|
+ `31139ae`) — the first reset the post-v0.21.0 narrative
|
||||||
|
("no threads in flight"), the second recorded Options A +
|
||||||
|
F closures and trimmed the Resume-prompt menu.
|
||||||
|
- New module-level doc strings on the new constants
|
||||||
|
(`RED_SUIT_COLOUR_HC`, `TEXT_PRIMARY_HC`, `BORDER_SUBTLE_HC`,
|
||||||
|
`RED_SUIT_COLOUR_CBM` semantic shift) record the
|
||||||
|
composability rules between CBM and HC and the "what to
|
||||||
|
use this for" rationale.
|
||||||
|
|
||||||
|
### Stats
|
||||||
|
|
||||||
|
- **1192 passing tests / 0 failing** across the workspace
|
||||||
|
(net +8 from v0.21.0's 1184 baseline). New tests added by
|
||||||
|
this release:
|
||||||
|
- `card_face_svg_pin` integration test rebaselined three
|
||||||
|
times during the suit-colour iteration; final hashes
|
||||||
|
pin the saturated-red + near-white + no-border state.
|
||||||
|
- 4 high-contrast text_colour tests + 2 reduce-motion
|
||||||
|
`effective_slide_secs` tests in `card_plugin` /
|
||||||
|
`animation_plugin` (from `c5787c6`).
|
||||||
|
- 1 `icon_svg_pin` integration test guarding the icon
|
||||||
|
rasterisation pipeline (from `48b28d2` — actually
|
||||||
|
landed in v0.21.0's accounting but worth noting for the
|
||||||
|
cycle).
|
||||||
|
- 1 `pile_markers_hide_when_pile_is_occupied` test pinning
|
||||||
|
the new visibility-by-occupancy invariant (from
|
||||||
|
`4d48cad`).
|
||||||
|
- Zero clippy warnings under `cargo clippy --workspace
|
||||||
|
--all-targets -- -D warnings`.
|
||||||
|
- `cargo test --workspace` clean.
|
||||||
|
|
||||||
## [0.21.0] — 2026-05-08
|
## [0.21.0] — 2026-05-08
|
||||||
|
|
||||||
Closes the visual-identity arc opened in v0.20.0. Three through-lines
|
Closes the visual-identity arc opened in v0.20.0. Three through-lines
|
||||||
|
|||||||
@@ -6957,6 +6957,8 @@ dependencies = [
|
|||||||
"keyring",
|
"keyring",
|
||||||
"solitaire_data",
|
"solitaire_data",
|
||||||
"solitaire_engine",
|
"solitaire_engine",
|
||||||
|
"tiny-skia 0.12.0",
|
||||||
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -1,403 +1,85 @@
|
|||||||
# Solitaire Quest — Session Handoff
|
# Solitaire Quest — Session Handoff
|
||||||
|
|
||||||
**Last updated:** 2026-05-08 — v0.20.0 cut and tagged at `41a009a`,
|
**Last updated:** 2026-05-08 — **v0.21.6 cut and tagged at
|
||||||
all post-cut commits pushed to origin (HEAD = `dd101b3`), working
|
`f63db76`**, working tree clean, all post-tag work pushed to
|
||||||
tree clean.
|
origin.
|
||||||
The cut itself shipped two through-lines: a full **Terminal visual-
|
|
||||||
identity port** (token system, modal scaffold, gameplay-feedback,
|
v0.21.6 is a patch release with through-line:
|
||||||
toasts, table / card chrome, splash cursor) and the **Android
|
**Move Log panel + scrub-UX polish**. v0.21.5 closed out the
|
||||||
persistence shim** that closes the `dirs::data_dir() = None` pitfall
|
keyboard-accelerator surface (Space / Esc / ← / →) and the
|
||||||
flagged in CLAUDE.md §10. Since the cut, the post-tag work split
|
keybind footer; v0.21.6 builds on that with two parallel
|
||||||
into two arcs: (1) splash boot-screen port + replay-overlay
|
threads — accessibility + scrub-on-hold polish for the v0.21.5
|
||||||
banner enrichments + desktop-adaptation spec — closing Resume-prompt
|
surfaces, plus a brand-new Move Log panel anchored to the
|
||||||
Options B and C (see "Since the v0.20.0 cut" entries below); and
|
viewport's bottom edge that gives players a 5-row recent-and-
|
||||||
(2) **the card-face artwork regeneration arc — Option D, closed
|
upcoming move history alongside the existing top-edge banner.
|
||||||
2026-05-08** — full Terminal cards rendering on every face, plus
|
|
||||||
three follow-up fixes that surfaced during sign-off (default-theme
|
The Move Log panel is the first replay-overlay surface that
|
||||||
SVG override, table backgrounds, top-bar overlap), plus a
|
*isn't* attached to the banner — it lives at a separate screen
|
||||||
glyph-orientation tweak (no 180° inverted-corner rotation).
|
anchor (`bottom: 0`) with its own spawn/despawn lifecycle.
|
||||||
|
Establishes the "multi-anchor replay UI" pattern that the
|
||||||
|
remaining B-2 sub-piece (mini-tableau preview) will inherit.
|
||||||
|
|
||||||
|
Six commits on the B-2 replay screen-takeover redesign arc land
|
||||||
|
here, bringing the post-v0.21.4 total to 12. The remaining B-2
|
||||||
|
piece — mini-tableau preview that dims the gameplay tableau
|
||||||
|
during replay — is the only major sub-piece still open.
|
||||||
|
|
||||||
|
Full v0.21.6 detail lives in `CHANGELOG.md` § [0.21.6]. This
|
||||||
|
file from here on focuses on what's *open* post-cut and how to
|
||||||
|
resume.
|
||||||
|
|
||||||
## Status at pause
|
## Status at pause
|
||||||
|
|
||||||
- **HEAD locally:** see `git rev-parse HEAD`. Most recent narrative
|
- **HEAD locally:** see `git rev-parse HEAD`. The cut commit is
|
||||||
entry below names the latest substantive commit; this status line
|
`f63db76`; any post-cut docs edits ride on top of that.
|
||||||
intentionally avoids hard-coding the SHA so a docs-only edit
|
- **HEAD on origin:** matches local. v0.21.6 is fully on origin.
|
||||||
doesn't immediately stale the handoff.
|
|
||||||
- **HEAD on origin:** matches local. All post-cut commits pushed
|
|
||||||
through `dd101b3`. Decide whether to roll the post-tag work
|
|
||||||
into v0.20.1 / v0.21.0-candidates the next time a release is cut.
|
|
||||||
- **Working tree:** clean. No WIP outstanding.
|
- **Working tree:** clean. No WIP outstanding.
|
||||||
- **`artwork/` directory:** still untracked. Intentional.
|
- **`artwork/` directory:** still untracked. Intentional.
|
||||||
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings`
|
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings`
|
||||||
clean.
|
clean.
|
||||||
- **Tests:** **1184 passing / 0 failing** across the workspace.
|
- **Tests:** **1273 passing / 0 failing** across the workspace.
|
||||||
Net delta from the 1180 baseline: splash polish added two
|
Detail in `CHANGELOG.md` § [0.21.6] § Stats.
|
||||||
(`build_scanline_image_has_expected_2x2_rgba_bytes`,
|
- **Tags on origin:** `v0.9.0` through `v0.21.6`. v0.21.6 is on
|
||||||
`scanline_overlay_spawns_and_fades_with_splash`); the
|
`f63db76`; v0.21.5 stays on `a2432df`; v0.21.4 stays on
|
||||||
card-face migration added one (`card_face_svg_pin` integration
|
`23ff62c`; v0.21.3 stays on `3d92a91`; v0.21.2 stays on
|
||||||
test) and consolidated two (`face_colour` CBM tests folded
|
`f23df3b`; v0.21.1 stays on `daa655a`; v0.21.0 stays on
|
||||||
into `text_colour` CBM tests, net −2 then +1 from pin);
|
`04f9bf9`; v0.20.0 stays on `41a009a`.
|
||||||
call it +4 net.
|
|
||||||
- **Tags on origin:** `v0.9.0` through `v0.20.0`. v0.20.0 is on
|
|
||||||
`41a009a`.
|
|
||||||
|
|
||||||
## Since the v0.20.0 cut (un-pushed)
|
## Since the v0.21.6 cut
|
||||||
|
|
||||||
### `39b8496` `docs(ui): add Terminal desktop-adaptation spec`
|
No threads in flight. Working tree clean as of 2026-05-08. New
|
||||||
|
work since the cut would land here as commit narratives; for
|
||||||
|
the v0.21.6 contents themselves, see `CHANGELOG.md` § [0.21.6].
|
||||||
|
|
||||||
`docs/ui-mockups/desktop-adaptation.md` — 283 lines covering
|
Open next-step menu (Move Log + scrub-UX + keyboard accelerator
|
||||||
viewport assumptions, seven universal adaptation rules, and per-
|
coverage + accessibility are all complete):
|
||||||
screen geometry rules for the priority surfaces (Game Table, Win
|
1. **Mini-tableau preview** — the only remaining major B-2
|
||||||
Summary, Settings, Help, Pause, Home, Splash, Stats, and the
|
sub-piece. Mockup shows a 240 px-tall band at 50 % opacity
|
||||||
modal-pattern screens Profile / Achievements / Theme Picker /
|
showing the gameplay tableau peeking through the replay
|
||||||
Daily Challenge). Closes the spec gap — 23 of 24 mockups were
|
chrome. Implementation needs to add a settings-aware dim
|
||||||
mobile-only, but the v0.20.0 token-port pass was already layout-
|
overlay or alpha modulation on the tableau cards during
|
||||||
agnostic so nothing shipped broken. The spec matters for *next*
|
replay. Architectural — touches `card_plugin` rendering.
|
||||||
ports.
|
Multi-session.
|
||||||
|
2. **Move Log auto-scroll** — only relevant if the panel's
|
||||||
|
row count grows beyond the current 5-row fixed window.
|
||||||
|
Currently the prev-2 / active / next-2 layout fits all
|
||||||
|
visible content, so auto-scroll is unneeded. Becomes
|
||||||
|
relevant if a future commit expands the panel's row
|
||||||
|
capacity (e.g. 10-row scrolling list).
|
||||||
|
3. **Polish: notch label centering.** Bevy 0.18 lacks a
|
||||||
|
clean `translate-x: -50%` primitive so middle three
|
||||||
|
labels sit slightly right-of-notch. Could use a child
|
||||||
|
Text wrapper with computed left-margin compensation.
|
||||||
|
Tiny commit, requires visual review.
|
||||||
|
4. **Polish: WIN MOVE marker HC bump.** Currently uses
|
||||||
|
`STATE_SUCCESS` lime which stays visible under HC, but a
|
||||||
|
contrast bump under HC would make it even more legible
|
||||||
|
alongside the bumped notches. Optional.
|
||||||
|
|
||||||
**Why rules > visual mockups for this gap:** Stitch's
|
Recommended order: option 1 (mini-tableau preview) is the
|
||||||
`generate_variants` API timed out on the layout-only adaptation
|
big remaining piece that closes B-2 — best tackled in a
|
||||||
prompt (server-side flake, not a prompt-shape issue — confirmed
|
fresh session because it crosses into `card_plugin`. Options
|
||||||
by polling `list_screens` with no new variant landing). A markdown
|
3 and 4 are visual polish that benefit from user review.
|
||||||
rules file applies to every screen including the 9 missing-plugin
|
|
||||||
surfaces (splash, challenge, time-attack, weekly-goals,
|
|
||||||
leaderboard, sync, level-up, replay-overlay, radial-menu) that
|
|
||||||
aren't in the Stitch project at all. It's also referenceable from
|
|
||||||
code comments and commit messages without loading an image.
|
|
||||||
|
|
||||||
### `cacb19c` `feat(engine): port the splash to the Terminal boot-screen treatment`
|
|
||||||
|
|
||||||
Implements the full mockup-spec splash from
|
|
||||||
`docs/ui-mockups/splash-mobile.html` plus the desktop adaptation
|
|
||||||
rules:
|
|
||||||
|
|
||||||
- **Header**: cursor block (96 px `▌`), wordmark ("Solitaire
|
|
||||||
Quest"), 192 px divider, "TERMINAL EDITION" subtitle.
|
|
||||||
- **Boot log**: three ✓ check rows (`assets loaded`,
|
|
||||||
`theme: terminal`, `progress restored`) + a `▌ ready_` line.
|
|
||||||
Capped at 480 px width on desktop (else 70 % viewport).
|
|
||||||
- **Progress bar**: 1 px track (`BORDER_SUBTLE`) with a 100 %-
|
|
||||||
width cyan (`ACCENT_PRIMARY`) fill + `DONE · 247 ASSETS`
|
|
||||||
caption. Capped at 720 px on desktop (else 80 %).
|
|
||||||
- **Footer**: `BASE16-EIGHTIES` label, eight palette swatches
|
|
||||||
(12 × 12 px each — one per named token in the design system),
|
|
||||||
version line.
|
|
||||||
|
|
||||||
**Refactored the alpha-fade scaffold** from per-marker queries
|
|
||||||
(`SplashTitle` / `SplashSubtitle` / `SplashCursor`) to a single
|
|
||||||
`SplashFadable { base_color: Color }` + `SplashFadableBg`
|
|
||||||
variant. ~15 fadable elements share one global query each;
|
|
||||||
adding more is one component-attach, not three new query types.
|
|
||||||
|
|
||||||
**Skipped, with rationale captured in the commit:**
|
|
||||||
- Scanline overlay (needs a tiled-pattern asset or custom shader).
|
|
||||||
*Open in "Visual-identity follow-ups" below.*
|
|
||||||
- Pulsing cursor on the "ready_" line (would fight the global
|
|
||||||
fade timeline). *Open in "Visual-identity follow-ups" below.*
|
|
||||||
- "RUSTY SOLITAIRE" wordmark from the mockup (the actual product
|
|
||||||
is "Solitaire Quest"; the mockup leaked the repo name). *Closed
|
|
||||||
— the in-engine wordmark stays "Solitaire Quest".*
|
|
||||||
|
|
||||||
### `c84d9f4` `feat(engine): scrub fill bar + per-frame updater for replay overlay`
|
|
||||||
|
|
||||||
Closes the WIP described in the prior handoff. Adds the 1 px cyan
|
|
||||||
scrub bar called for in `docs/ui-mockups/replay-overlay-mobile.html`:
|
|
||||||
a track in `BORDER_SUBTLE` spans the bottom edge of the banner and
|
|
||||||
the cyan `ACCENT_PRIMARY` fill mirrors `cursor / total` via a new
|
|
||||||
`ReplayOverlayScrubFill` component + `update_scrub_fill` system.
|
|
||||||
The pure `scrub_pct` helper is shared between the spawn path
|
|
||||||
(initial fill width) and the per-frame updater so the first paint
|
|
||||||
already reflects state instead of popping `0 → cursor` on the
|
|
||||||
first tick — same shape as the existing `format_progress` /
|
|
||||||
`update_progress_text` split. Two new tests cover the four corners
|
|
||||||
of `scrub_pct` and an end-to-end drive of `ReplayPlaybackState`
|
|
||||||
asserting `Node.width` on the unique scrub-fill entity. Same
|
|
||||||
change-detection guard as the text updaters, so an idle replay
|
|
||||||
leaves the node untouched.
|
|
||||||
|
|
||||||
Header text treatment (closed by `6204db8` immediately below),
|
|
||||||
move-log scroll, MOVE chip, and WIN MOVE callout from the same
|
|
||||||
mockup are still open — separate commits.
|
|
||||||
|
|
||||||
### `6204db8` `feat(engine): port replay banner label to ▌ cursor-block treatment`
|
|
||||||
|
|
||||||
Aligns the replay overlay's headline with the splash boot-screen
|
|
||||||
idiom landed in `cacb19c`: `Replay` → `▌ replay` and
|
|
||||||
`Replay complete` → `▌ replay complete`. The cursor block (`▌`,
|
|
||||||
U+258C) prefixed to a lowercased label reads as a Terminal output
|
|
||||||
line rather than a generic UI title, tightening the family
|
|
||||||
resemblance between the two top-level overlay surfaces. Pure
|
|
||||||
text-content change; no behavioural shift, no new components, no
|
|
||||||
new systems.
|
|
||||||
|
|
||||||
**Mockup deviation (intentional):** the source mockup string in
|
|
||||||
`docs/ui-mockups/replay-overlay-mobile.html` is `▌replay.tsx`. The
|
|
||||||
`.tsx` is a prototyping leak — Stitch renders in React, so the
|
|
||||||
mockup author reached for a familiar filename — and was dropped
|
|
||||||
for the in-engine version since the codebase is Rust. The `▌` +
|
|
||||||
lowercase pattern is what reads as a Terminal-output-line; the
|
|
||||||
extension is incidental. (Same shape as the "RUSTY SOLITAIRE"
|
|
||||||
wordmark deviation noted under `cacb19c` — the mockup leaked the
|
|
||||||
repo name; the actual product is "Solitaire Quest".)
|
|
||||||
|
|
||||||
### `54005d5` `feat(engine): add GAME #YYYY-DDD caption beneath the replay headline`
|
|
||||||
|
|
||||||
Adds the right-anchored game-identifier piece of the replay-overlay
|
|
||||||
mockup, adapted to live *under* the existing "▌ replay" headline as
|
|
||||||
a `TYPE_CAPTION` (11 px) / `TEXT_SECONDARY` subtitle. Format is
|
|
||||||
`GAME #{year}-{ordinal:03}` (e.g. `GAME #2026-122` for a replay
|
|
||||||
recorded 2026-05-02) — year + chrono ordinal gives a compact,
|
|
||||||
monotonically-increasing identifier matching the mockup's
|
|
||||||
`GAME #2024-127` motif. New `ReplayOverlayGameCaption` marker, new
|
|
||||||
pure helper `format_game_caption(state) -> Option<String>` (None
|
|
||||||
for Inactive / Completed since the replay is consumed in those
|
|
||||||
branches; spawn-time fall-through to empty string).
|
|
||||||
|
|
||||||
**Layout impact:** `BANNER_HEIGHT` bumped 48 → 60 px so the new
|
|
||||||
left column (headline + 2 px gap + caption ≈ 39 px content) fits
|
|
||||||
under the scrub bar with room to spare. +12 px banner mass is the
|
|
||||||
deliberate cost of the new content; no other plugin observes
|
|
||||||
`BANNER_HEIGHT` so the change is local.
|
|
||||||
|
|
||||||
Two new tests (1180 → 1182): `format_game_caption_covers_state_corners`
|
|
||||||
pins the three branches plus the zero-pad-to-3-digits invariant
|
|
||||||
for early-January ordinals; `overlay_game_caption_shows_replay_date`
|
|
||||||
drives `ReplayPlaybackState` end-to-end.
|
|
||||||
|
|
||||||
### `e080b49` `feat(engine): restyle replay progress text as Terminal MOVE chip`
|
|
||||||
|
|
||||||
Closes the centre-text half of the replay-overlay enrichments. The
|
|
||||||
plain "Move N of M" text becomes a 1px `ACCENT_PRIMARY`-bordered
|
|
||||||
chip containing "MOVE N/M" — uppercase + slash separator reads as
|
|
||||||
a Terminal output line and matches the floating-chip motif in
|
|
||||||
`docs/ui-mockups/replay-overlay-mobile.html`. The chip lives
|
|
||||||
in-banner rather than floating above the focused card (the
|
|
||||||
screen-takeover treatment that requires plumbing cursor → card
|
|
||||||
identity remains deferred).
|
|
||||||
|
|
||||||
**Implementation note:** `BorderColor` in Bevy 0.18 is a per-side
|
|
||||||
struct, not a tuple — `BorderColor::all(ACCENT_PRIMARY)` is the
|
|
||||||
correct constructor. Worth pinning for next time we touch a
|
|
||||||
border-painted UI surface. The `ReplayOverlayProgressText` marker
|
|
||||||
stays on the inner Text rather than the new chip Node so
|
|
||||||
`update_progress_text` keeps repainting unchanged — a deliberate
|
|
||||||
"markers belong on the entity that updates change" choice.
|
|
||||||
|
|
||||||
Test count unchanged (1182); `overlay_progress_text_reflects_cursor`
|
|
||||||
swapped its assertion from "Move 5 of 10" to "MOVE 5/10".
|
|
||||||
|
|
||||||
This pair (`54005d5` + `e080b49`) closes Option C from the
|
|
||||||
SESSION_HANDOFF Resume prompt's banner-local enrichments. Floating-
|
|
||||||
chip-above-focused-card and the full screen-takeover redesign
|
|
||||||
remain — both data-layer or cross-plugin and intentionally still
|
|
||||||
open.
|
|
||||||
|
|
||||||
### `29136d8` `feat(engine): add pulsing trailing cursor to splash "▌ ready_" line`
|
|
||||||
|
|
||||||
Closes the cursor-pulse half of the splash polish arc deferred in
|
|
||||||
`cacb19c`. The "▌ ready_" line now ends with a 6×12 px cyan Node
|
|
||||||
that pulses on a 1 s sine cadence, multiplied with the global
|
|
||||||
splash fade timeline so the cursor never reaches full alpha while
|
|
||||||
the rest of the splash is still fading in.
|
|
||||||
|
|
||||||
**The "multiply, don't override" pattern.** Two systems write the
|
|
||||||
same `BackgroundColor` per frame: `advance_splash` writes the
|
|
||||||
global-fade alpha, `pulse_splash_cursor` overwrites with
|
|
||||||
`global_alpha × pulse_factor`. Both derive from `SplashAge` on the
|
|
||||||
root, so the writes are commensurate — the second one isn't
|
|
||||||
"fighting" the first, just refining it. This is the cleanest fix
|
|
||||||
for the "fight the global fade timeline" warning the original
|
|
||||||
`cacb19c` skip note flagged.
|
|
||||||
|
|
||||||
**Defensive division guard.** `cursor_pulse_factor(age, period, min)`
|
|
||||||
short-circuits to `1.0` when `period <= 0.0` so a future
|
|
||||||
misconfiguration produces a steady cursor rather than NaN
|
|
||||||
propagation (NaN in alpha = invisible UI, hard to debug). Worth
|
|
||||||
mirroring on every trig/division helper, not just this one.
|
|
||||||
|
|
||||||
One new test (1182 → 1183): `cursor_pulse_factor_corners` pins the
|
|
||||||
peak (factor = 1 at age = period / 4), trough (factor = min at age =
|
|
||||||
period × 3 / 4), and the zero/negative-period guard.
|
|
||||||
|
|
||||||
### `a27cf5a` `feat(engine): add tiled scanline overlay to splash`
|
|
||||||
|
|
||||||
Closes the scanline half of the splash polish arc. A fullscreen
|
|
||||||
`ImageNode` tiles a runtime-generated 2×2 RGBA8 texture over the
|
|
||||||
splash content — top row transparent, bottom row `#1a1a1a` at
|
|
||||||
~30 % alpha — producing the 1 px-pitch horizontal scanline pattern
|
|
||||||
called for in `docs/ui-mockups/splash-mobile.html`.
|
|
||||||
|
|
||||||
**Texture-α × tint-α composite for fade integration.** The 30 %
|
|
||||||
alpha is baked into the texture pixels, not the `ImageNode.color`
|
|
||||||
tint. `advance_splash`'s new third query writes
|
|
||||||
`(1, 1, 1, global_alpha)` into the tint each tick; the GPU
|
|
||||||
multiplies texture-α by tint-α, so the visible composite is
|
|
||||||
`0.3 × global_alpha`. Cleaner than building a "multiplicative
|
|
||||||
fadable" abstraction in the ECS — the GPU already does this
|
|
||||||
multiplication for free.
|
|
||||||
|
|
||||||
**Bevy 0.18 API surprises (worth pinning):**
|
|
||||||
- `RenderAssetUsages` re-exports under `bevy::asset::`, not
|
|
||||||
`bevy::render::render_asset::`. Type name unchanged; module
|
|
||||||
path moved.
|
|
||||||
- `TextureFormat::pixel_size()` returns `Result<usize, _>` rather
|
|
||||||
than the bare `usize` you'd expect for a static format query.
|
|
||||||
Annoying enough that the `debug_assert_eq!` against the buffer
|
|
||||||
length just hard-codes the `2 × 2 × 4 = 16` literal.
|
|
||||||
|
|
||||||
Headless test fixture now also `init_resource::<Assets<Image>>()`
|
|
||||||
since `MinimalPlugins` doesn't pull `AssetPlugin` — same pattern
|
|
||||||
`settings_plugin::tests` already used. Without it, the
|
|
||||||
`Option<ResMut<Assets<Image>>>` parameter on `spawn_splash` would
|
|
||||||
fall through and the scanline overlay would silently skip,
|
|
||||||
defeating the new tests.
|
|
||||||
|
|
||||||
Two new tests (1183 → 1185):
|
|
||||||
`build_scanline_image_has_expected_2x2_rgba_bytes` locks the
|
|
||||||
texture pixels literally so a future tweak can't drift the
|
|
||||||
appearance silently; `scanline_overlay_spawns_and_fades_with_splash`
|
|
||||||
asserts spawn placement under `SplashRoot` and the new
|
|
||||||
fade-images branch's correctness end-to-end.
|
|
||||||
|
|
||||||
This pair (`29136d8` + `a27cf5a`) closes Option B from the
|
|
||||||
SESSION_HANDOFF Resume prompt — both splash polish pieces now
|
|
||||||
shipped.
|
|
||||||
|
|
||||||
### `5623368`…`dd101b3` — Option D card-face migration arc
|
|
||||||
|
|
||||||
Closed 2026-05-08 across nine commits. The full Terminal card
|
|
||||||
artwork now renders end-to-end. Detail breakdown lives in the
|
|
||||||
"Visual-identity follow-ups" punch-list entry below; the short
|
|
||||||
version:
|
|
||||||
|
|
||||||
- Migration plan + pipeline tooling: `5623368` (plan doc),
|
|
||||||
`3a4bb63` (single-card PoC proving the `usvg`/`resvg` pipeline
|
|
||||||
at per-card grain), `babe5cc` (full
|
|
||||||
`solitaire_engine/examples/card_face_generator.rs` example
|
|
||||||
emitting 52 faces + 5 backs into `assets/cards/`), `48b28d2`
|
|
||||||
(the `card_face_svg_pin` integration test pinning rasteriser
|
|
||||||
output via inline FNV-1a hashing of raw RGBA8 bytes — the
|
|
||||||
pin's bootstrap pattern, "empty `EXPECTED` → run → paste",
|
|
||||||
is the maintenance interface for future intentional changes).
|
|
||||||
- Lockstep step 4+5: `e8bf9d7`. New PNG bytes + the 5
|
|
||||||
`card_plugin` constants (`CARD_FACE_COLOUR`,
|
|
||||||
`RED_SUIT_COLOUR`, `BLACK_SUIT_COLOUR`,
|
|
||||||
`CARD_FACE_COLOUR_RED_CBM` → `RED_SUIT_COLOUR_CBM`,
|
|
||||||
`card_back_colour`) + signature shifts in one commit.
|
|
||||||
`face_colour` deleted — Terminal face is uniformly
|
|
||||||
`CARD_FACE_COLOUR` regardless of CBM, so the function
|
|
||||||
collapsed to a constant. `text_colour` gained a
|
|
||||||
`color_blind: bool` parameter (red→cyan suit-glyph swap when
|
|
||||||
CBM is on). Four `face_colour` CBM tests folded into two
|
|
||||||
`text_colour` CBM tests in lockstep.
|
|
||||||
- Three follow-ups that surfaced during sign-off, all from the
|
|
||||||
same "fallback path the migration walked past" pattern:
|
|
||||||
`a14200a` regenerated the embedded **default-theme SVGs** at
|
|
||||||
`solitaire_engine/assets/themes/default/*.svg`; those bytes
|
|
||||||
`include_bytes!()`-embed into the binary and override
|
|
||||||
`assets/cards/*.png` at startup, so the PNG migration alone
|
|
||||||
didn't change what production rendered. `8719f77`
|
|
||||||
regenerated `assets/backgrounds/bg_*.png` to flat Terminal
|
|
||||||
near-black (5 solid-colour PNGs via a new
|
|
||||||
`solitaire_engine/examples/background_generator.rs` example).
|
|
||||||
`ae84dc1` cleared the **top-bar overlap** at portrait/narrow
|
|
||||||
window widths by swapping the action-button row's hardcoded
|
|
||||||
`font_size: 16.0` to `TYPE_BODY` (a typography-migration
|
|
||||||
miss) and stepping horizontal padding from `VAL_SPACE_3`
|
|
||||||
to `VAL_SPACE_2`.
|
|
||||||
- Glyph-rendering fix: `af414b6`. The bundled `FiraMono`
|
|
||||||
doesn't carry usable U+2660-2666 glyphs at the requested
|
|
||||||
size — `usvg` was silently substituting tiny "tofu" marks.
|
|
||||||
Switched suit glyphs from `<text>` elements to inline SVG
|
|
||||||
`<path>` elements via a new `suit_path_d` helper. Path-based
|
|
||||||
rendering bypasses the font system entirely; same bytes on
|
|
||||||
every machine, no fontdb dependency, no substitution risk.
|
|
||||||
Same path data renders correctly whether filled (♥ ♠) or
|
|
||||||
outlined (♦ ♣ — the always-on color-blind glyph
|
|
||||||
differentiation).
|
|
||||||
- Glyph-orientation tweak: `dd101b3`. Removed the 180° rotation
|
|
||||||
from the bottom-right large suit glyph at user request. Both
|
|
||||||
glyphs now render upright. `design-system.md` § Game Cards
|
|
||||||
line 220 updated in lockstep — the deliberate deviation from
|
|
||||||
the traditional inverted-corner-indicator convention is
|
|
||||||
documented in the spec, not just the code.
|
|
||||||
|
|
||||||
The pin test fired exactly twice during this arc (once for the
|
|
||||||
text→path switch, once for the unrotation) and rebaselined
|
|
||||||
cleanly each time via the empty-then-paste pattern. The 5
|
|
||||||
`back_*` hashes stayed identical across both rebaselines —
|
|
||||||
secondary signal that the FNV-1a fingerprinting is purely
|
|
||||||
deterministic on rasteriser output.
|
|
||||||
|
|
||||||
This arc closes Option D from the SESSION_HANDOFF Resume prompt
|
|
||||||
and effectively completes the Terminal visual-identity port —
|
|
||||||
only the toast warning/error variant slots remain wired-but-
|
|
||||||
unused.
|
|
||||||
|
|
||||||
## What shipped in v0.20.0 (frozen at `41a009a`)
|
|
||||||
|
|
||||||
### Terminal visual-identity port
|
|
||||||
|
|
||||||
Top-down stack — every commit downstream of the token system
|
|
||||||
reads from it, so swapping the palette is now a one-file edit:
|
|
||||||
|
|
||||||
- **`ui_theme` token system** (`0d477ac`). base16-eighties
|
|
||||||
palette, 5-rung type scale, 7-rung 4-multiple spacing scale,
|
|
||||||
3-step radius, 14-rung z-index hierarchy, full motion budget,
|
|
||||||
4 invariant-pinning unit tests. Card-shadow alphas pinned to 0
|
|
||||||
(Terminal achieves depth via 1px borders + tonal layering).
|
|
||||||
- **Modal scaffold already on tokens** — `ui_modal` was ported
|
|
||||||
in the same commit's wake; three stale "loud yellow" /
|
|
||||||
"magenta secondary" doc comments fixed.
|
|
||||||
- **Gameplay feedback → semantic state tokens** (`ceec4fc`).
|
|
||||||
Selection / valid-drop tints route through `ACCENT_PRIMARY` /
|
|
||||||
`STATE_WARNING` / `STATE_SUCCESS`.
|
|
||||||
- **Toasts** (`a137607`). New `ToastVariant` enum
|
|
||||||
(Info / Warning / Error / Celebration); opaque `BG_ELEVATED`
|
|
||||||
+ 1px accent border + bottom-anchor. All ten call sites pass
|
|
||||||
their semantic variant.
|
|
||||||
- **`table_plugin` chrome** (`651f406`).
|
|
||||||
`PILE_MARKER_DEFAULT_COLOUR` promoted; `cursor_plugin` imports
|
|
||||||
it, replacing a "kept in sync" doc comment with a compile-
|
|
||||||
enforced invariant. `HINT_PILE_HIGHLIGHT_COLOUR` →
|
|
||||||
`STATE_WARNING`.
|
|
||||||
- **`card_plugin` chrome** (`d752870`). Drag-elevation shadow
|
|
||||||
routes through `CARD_SHADOW_*` tokens. `RIGHT_CLICK_HIGHLIGHT_COLOUR`
|
|
||||||
→ `STATE_SUCCESS`. Stock recycle "↺" text → `TEXT_PRIMARY @ 0.7α`.
|
|
||||||
Card-face / suit / card-back palette intentionally NOT migrated
|
|
||||||
(artwork dependency — see open-list item below).
|
|
||||||
- **Splash cursor** (`cdcadda`). The signature `▌` cyan glyph
|
|
||||||
(96 px) added above the wordmark, matching the spec.
|
|
||||||
*Subsequently expanded post-cut by `cacb19c` into the full
|
|
||||||
boot-screen treatment.*
|
|
||||||
- **Hint-source / dest pairing** (`9891ae4`). `input_plugin`'s
|
|
||||||
source-card tint now matches the destination pile's
|
|
||||||
`STATE_WARNING`.
|
|
||||||
- **Design system + 24-mockup library** (`fa7f98a`).
|
|
||||||
`docs/ui-mockups/design-system.md` + 24 Stitch mockups (HTML +
|
|
||||||
PNG) covering every screen plus 9 missing-plugin surfaces.
|
|
||||||
- **`card_shadow_params` test aligned** (`1d1543e`). Drag-vs-
|
|
||||||
idle shadow assertion loosened to `>=` to accept the Terminal
|
|
||||||
"no shadow" intent without losing the regression-guard.
|
|
||||||
|
|
||||||
### Android persistence
|
|
||||||
|
|
||||||
- **`solitaire_data::data_dir` shim** (`4b51e50`). New
|
|
||||||
`solitaire_data::platform::data_dir()` falls through to
|
|
||||||
`dirs::data_dir()` on desktop and returns the per-app sandbox
|
|
||||||
at `/data/data/com.solitairequest.app/files` on Android — no
|
|
||||||
JNI needed (package id pinned in `[package.metadata.android]`).
|
|
||||||
Six `solitaire_data` callsites + `solitaire_engine/assets/user_dir.rs`
|
|
||||||
migrated. Settings, stats, achievements, replays, game-state,
|
|
||||||
time-attack sessions, and user themes now persist on Android.
|
|
||||||
|
|
||||||
### Inherited from earlier in the cycle (pre-session)
|
|
||||||
|
|
||||||
- Android build target + APK (`fb8b2ac`), runbook (`59424a3`),
|
|
||||||
F3 FPS overlay (`690e1d2`), Smart Window Size opt-out
|
|
||||||
(`e1b8766`), Shareable badge (`9b065e5`), Help cheat-sheet
|
|
||||||
M/P/Enter rows (`35516d3`), `pull_failure_sets_error_status`
|
|
||||||
flake fix (`67c150b`).
|
|
||||||
|
|
||||||
## Open punch list
|
## Open punch list
|
||||||
|
|
||||||
@@ -422,92 +104,89 @@ reads from it, so swapping the palette is now a one-file edit:
|
|||||||
Either upstream a cargo-apk fix or document `--lib` as
|
Either upstream a cargo-apk fix or document `--lib` as
|
||||||
canonical in the runbook.
|
canonical in the runbook.
|
||||||
|
|
||||||
### Visual-identity follow-ups (opened by v0.20.0's port)
|
### Visual-identity follow-ups (post-v0.21.0)
|
||||||
|
|
||||||
- *Card-face / suit / card-back artwork regeneration — closed
|
The visual-identity arc is effectively complete: token system,
|
||||||
2026-05-08 by the commit chain `5623368` → `dd101b3`.* The
|
chrome migration, splash boot screen, replay-overlay banner,
|
||||||
Terminal spec called for dark `#1a1a1a` cards with light suit
|
card-face artwork (both rendering paths), and the `ACCENT_PRIMARY`
|
||||||
pips (pink for hearts/diamonds, foreground gray for spades/
|
palette refresh all shipped in v0.20.0 + v0.21.0. What stays open:
|
||||||
clubs). Closed across nine commits over two arcs:
|
|
||||||
- **Plan + tooling (`5623368`–`48b28d2`):** migration plan
|
- **Replay-overlay screen-takeover redesign.** The full mockup
|
||||||
doc, single-card PoC, full `card_face_generator` example
|
(`docs/ui-mockups/replay-overlay-mobile.html`) calls for a
|
||||||
(52 faces + 5 backs into `assets/cards/`), and the
|
mini-tableau preview, playback controls, move-log scroll, and
|
||||||
`card_face_svg_pin` integration test pinning rasteriser
|
a WIN MOVE marker on the scrub bar. Banner-local pieces all
|
||||||
output via FNV-1a so future `usvg`/`resvg` upgrades surface
|
shipped in v0.21.0 (`c84d9f4` + `6204db8` + `54005d5` +
|
||||||
as test failures rather than silent visual drift.
|
`e080b49`); the floating MOVE chip above the focused card
|
||||||
- **Lockstep step 4+5 (`e8bf9d7`):** PNGs + the 5 `card_plugin`
|
shipped in v0.21.2 (`2fb2d63`). The WIN MOVE scrub-bar marker
|
||||||
constants + signature shifts in one commit.
|
shipped post-v0.21.3 in `ab857bb` (data field) + `52befa6`
|
||||||
`CARD_FACE_COLOUR_RED_CBM` renamed to `RED_SUIT_COLOUR_CBM`
|
(UI). Playback controls (pause / resume / step + Space
|
||||||
and repurposed from a face-tint to a suit-glyph swap (the
|
accelerator) shipped post-v0.21.3 in `fbe48ac`. v0.21.5
|
||||||
Terminal face is uniform `CARD_FACE_COLOUR` regardless of
|
bundled six more commits under "replay-overlay scrubbing
|
||||||
CBM; CBM only swaps red suits to cyan in the glyph itself).
|
affordances + accessibility" (scrub notches + labels +
|
||||||
`face_colour` deleted, `text_colour` gained a `color_blind`
|
keybind footer + ESC and ← / → accelerators + HC border).
|
||||||
parameter.
|
v0.21.6 bundled six more under "Move Log panel + scrub-UX
|
||||||
- **Three follow-ups that surfaced during sign-off:**
|
polish" — bottom-edge Move Log panel with prev/active/next
|
||||||
`a14200a` regenerated the **default-theme SVGs** at
|
rows + active highlight, HC-mode coverage for the scrub
|
||||||
`solitaire_engine/assets/themes/default/*.svg` — those
|
track + notches, continuous scrub on key-held arrows. Banner
|
||||||
`include_bytes!()`-embed into the binary and override
|
height grew 60 → 76 → 92 px across two layout-changing
|
||||||
`assets/cards/*.png` at runtime, so the PNG migration alone
|
commits in v0.21.5; Move Log panel grew 56 → 84 → 112 px
|
||||||
didn't change what production rendered. `8719f77`
|
across the v0.21.6 move-log commits. Per-commit detail in
|
||||||
regenerated `assets/backgrounds/bg_*.png` to flat Terminal
|
`CHANGELOG.md` § [0.21.5] and § [0.21.6]. The only major
|
||||||
near-black (5 solid-colour PNGs via a new
|
B-2 piece left is the mini-tableau preview — the mockup's
|
||||||
`background_generator` example). `ae84dc1` cleared the
|
"Game Peek Band" at 50 % opacity. Architectural; touches
|
||||||
**top-bar overlap** at portrait/narrow window widths by
|
`card_plugin` rendering.
|
||||||
swapping the action-button row's hardcoded `font_size: 16.0`
|
Multi-session.
|
||||||
to `TYPE_BODY` and stepping horizontal padding from
|
- *Floating `MOVE N/M` chip above the focused card during
|
||||||
`VAL_SPACE_3` to `VAL_SPACE_2`.
|
playback — closed 2026-05-08 by `2fb2d63`.* World-space
|
||||||
- **Glyph-rendering fix (`af414b6`):** suit glyphs render as
|
`Text2d` entity sibling to the banner overlay; uses the same
|
||||||
inline SVG paths (not `<text>`) because the bundled
|
`LayoutResource` pile coordinates so it survives window
|
||||||
`FiraMono` doesn't carry usable U+2660-2666 at the
|
resizes without UI/camera math.
|
||||||
requested size — `usvg` was silently substituting tiny
|
- *Toast Warning variant wiring — closed 2026-05-08 by `279e23d`.*
|
||||||
"tofu" marks. Path-based rendering bypasses the font system
|
Daily-challenge-expiry toast fires once per `daily.date` when
|
||||||
entirely; same bytes on every machine. The pin test
|
within 30 min of UTC midnight reset and today is incomplete.
|
||||||
rebaselined cleanly via the empty-then-paste pattern.
|
`ToastVariant` is now fully load-bearing (every variant has at
|
||||||
- **Glyph-orientation tweak (`dd101b3`):** removed the 180°
|
least one real driver). Future Warning drivers can either reuse
|
||||||
rotation from the bottom-right large suit glyph at user
|
the generic `WarningToastEvent(String)` carrier or add their
|
||||||
request — both glyphs now render in the same upright
|
own domain message + `animation_plugin` handler.
|
||||||
orientation. `design-system.md` § Game Cards line 220
|
- *Toast Error variant wiring — closed 2026-05-08 by `68d50b5`.*
|
||||||
updated in lockstep to document the deliberate deviation
|
`MoveRejectedEvent` now fires a 2-second pink-bordered
|
||||||
from the traditional inverted-corner-indicator convention.
|
"Invalid move" toast as the third leg of the
|
||||||
- *Splash boot-loader scanline overlay — closed by `a27cf5a`.*
|
audio + visual + text rejection-feedback stool.
|
||||||
Runtime-generated 2 × 2 RGBA8 texture tiled via
|
- *High-contrast accessibility mode — closed 2026-05-08 by
|
||||||
`NodeImageMode::Tiled`; per-pixel alpha × tint alpha gives
|
`c5787c6` + `07e0357` (engine + UI) + v0.21.2's HC chrome
|
||||||
multiplicative fade integration without new abstractions.
|
rollout (`c9af1ea` + `d87761d` + `ec804d5`) + post-cut
|
||||||
- *Splash cursor pulse — closed by `29136d8`.* Trailing 6 × 12 px
|
dynamic-paint rollout (`c153363`).* Card text rendering plus
|
||||||
cyan Node, sine-pulsed, multiplied with the global splash fade
|
8 static-border chrome surfaces (modal scaffold, tooltip,
|
||||||
(the "multiply, don't override" pattern that resolves the
|
onboarding key chips, help panel key chips, stats panel
|
||||||
original `cacb19c` skip-rationale).
|
cells, home Level/XP/Score row, home mode buttons, home
|
||||||
- **Replay-overlay enrichments beyond the scrub bar.** Banner-local
|
mode-hotkey chips, 4 settings panel surfaces) all boost
|
||||||
pieces of the mockup (`docs/ui-mockups/replay-overlay-mobile.html`)
|
borders to `BORDER_SUBTLE_HC` under HC via the
|
||||||
all shipped: scrub bar (`c84d9f4`), `▌ replay` cursor-block label
|
`HighContrastBorder` marker. The previously-carved-out
|
||||||
(`6204db8`), `GAME #YYYY-DDD` caption (`54005d5`), `MOVE N/M`
|
dynamic-paint sites are now also covered: HUD action buttons
|
||||||
chip restyle (`e080b49`). What's still open are the cross-plugin
|
and modal buttons take the same marker (their paint cycles
|
||||||
/ data-layer pieces: a `MOVE N/M` chip *floating above the
|
only mutate `BackgroundColor`, so no race); the radial menu
|
||||||
focused card* during playback (would need to thread the cursor
|
rim folds HC into its per-frame spawn via
|
||||||
through to the card layer — `update_progress_text` writes the
|
`radial_rim_outline` so the focused rim boosts to
|
||||||
banner chip but the card-position lookup belongs in `card_plugin`).
|
`BORDER_SUBTLE_HC` under HC (preserving focused-vs-resting
|
||||||
The full mockup's screen-takeover treatment — mini-tableau
|
hierarchy that naive marker substitution would invert).
|
||||||
preview, playback controls, move-log scroll, WIN MOVE marker on
|
- *Reduced-motion mode — closed 2026-05-08 by `c5787c6` +
|
||||||
the scrub bar — is a multi-session redesign with
|
v0.21.2's `ed152e2`.* `effective_slide_secs` forces 0 on
|
||||||
data-layer impact (move-log scroller; the WIN MOVE marker
|
card animations; `pulse_splash_cursor` skips the per-frame
|
||||||
needs a `win_move_index` field on `Replay` that doesn't yet
|
pulse multiplier; `spawn_splash` skips the scanline overlay
|
||||||
exist). Banner-overlay behaviour is intentionally preserved
|
entirely. Future scope: gate any future card-lift z-bump
|
||||||
for now.
|
animation, warning-chip pulse (when one materialises).
|
||||||
- **Toast Warning / Error variants.** The `ToastVariant` enum
|
|
||||||
has slots for `Warning` (gold) and `Error` (pink) but no
|
|
||||||
in-engine event uses them yet. Wire when a warning- or error-
|
|
||||||
flavoured toast event materialises.
|
|
||||||
|
|
||||||
### Carried forward from v0.19.0
|
### Carried forward from v0.19.0
|
||||||
|
|
||||||
- **App icon round.** `Window::icon` not yet wired; no
|
- *App icon round — closed 2026-05-08 by `3eb3a26` + `716a025`.*
|
||||||
`.icns` / `.ico` / Linux hicolor PNG hierarchy. The 11-size
|
Runtime `Window::icon` wired (Linux/macOS/Windows); 9-size
|
||||||
icon export the v0.19 handoff referenced is *not* currently
|
PNG hierarchy at `assets/icon/icon_<size>.png` covers Linux
|
||||||
in `artwork/` (current `artwork/` holds the reverted Rusty
|
hicolor + downstream `.icns`/`.ico` packaging needs. The
|
||||||
Pixel card PNGs and is intentionally untracked); icon-export
|
`.ico` and `.icns` bundle-format files themselves are *not*
|
||||||
needs to be re-run before this item can be picked up.
|
generated — both would need new crate deps (`ico` and
|
||||||
Half-day task once the PNGs are back in place. No cert
|
`icns` respectively) and only matter at app-bundle time
|
||||||
dependency.
|
(cargo-bundle / packaging), not at `cargo run`. Open if the
|
||||||
|
project later ships as a packaged macOS / Windows app.
|
||||||
|
|
||||||
### Other small candidates
|
### Other small candidates
|
||||||
|
|
||||||
@@ -568,10 +247,9 @@ reads from it, so swapping the palette is now a one-file edit:
|
|||||||
### Canonical remote
|
### Canonical remote
|
||||||
|
|
||||||
`github.com/funman300/Rusty_Solitaire` is the canonical repo.
|
`github.com/funman300/Rusty_Solitaire` is the canonical repo.
|
||||||
Always push there. **Local master has unpushed post-cut commits**
|
Always push there. As of v0.21.0 origin matches local; the next
|
||||||
— run `git log --oneline origin/master..HEAD` for the live list;
|
push happens when post-cut work accumulates and is ready to roll
|
||||||
`git push` is the next durability step (or roll the post-cut
|
into a v0.21.1 / v0.22.0 cut.
|
||||||
commits into v0.20.1).
|
|
||||||
|
|
||||||
### Design direction (Terminal — base16-eighties)
|
### Design direction (Terminal — base16-eighties)
|
||||||
|
|
||||||
@@ -579,35 +257,44 @@ commits into v0.20.1).
|
|||||||
monospaced-forward typography (JetBrains Mono / FiraMono), tight
|
monospaced-forward typography (JetBrains Mono / FiraMono), tight
|
||||||
16 px edge margins, 8 px card radius.
|
16 px edge margins, 8 px card radius.
|
||||||
- **Palette:** near-black surface ramp (`#151515` / `#202020` /
|
- **Palette:** near-black surface ramp (`#151515` / `#202020` /
|
||||||
`#2a2a2a` / `#353535`), cyan primary CTA (`#6fc2ef`), lime
|
`#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 /
|
success (`#acc267`), gold warning (`#ddb26f`), pink error /
|
||||||
suit-red (`#fb9fb1`), lavender celebration (`#e1a3ee`), teal
|
suit-red (`#fb9fb1`), lavender celebration (`#e1a3ee`), teal
|
||||||
info (`#12cfc0`).
|
info (`#12cfc0`).
|
||||||
- **Two-color suits.** Red = `#fb9fb1`, black = `#d0d0d0`.
|
- **Two-color suits.** Red = `#fb9fb1`, black = `#d0d0d0`.
|
||||||
Outlined glyphs for diamonds & clubs are *always on*; the
|
Outlined glyphs for diamonds & clubs are *always on*; the
|
||||||
Settings "color-blind mode" toggle only swaps red → cyan.
|
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>.
|
Working directory: <Rusty_Solitaire clone path on this machine>.
|
||||||
Branch: master. v0.20.0 is tagged at 41a009a; the post-cut work
|
Branch: master. v0.21.6 is tagged at f63db76 (cut 2026-05-08, a
|
||||||
through dd101b3 is pushed to origin (Options B, C, D all closed).
|
patch release rolling up Move Log panel + scrub-UX polish:
|
||||||
Run `git log --oneline 41a009a..HEAD` to see what landed since the
|
brand-new bottom-edge Move Log panel with prev / active / next
|
||||||
tag — substantives: desktop-adaptation spec, splash boot-screen
|
row context + active-row highlight, plus HC-mode coverage for
|
||||||
port, replay-overlay banner enrichments, and the full card-face
|
scrub track + notches and continuous scrub on key-held arrow
|
||||||
artwork arc (52 faces + 5 backs as Terminal SVG-rasterised PNGs,
|
keys). v0.21.5 stays at a2432df, v0.21.4 at 23ff62c, v0.21.3
|
||||||
default-theme SVGs in lockstep, table backgrounds flattened,
|
at 3d92a91, v0.21.2 at f23df3b, v0.21.1 at daa655a, v0.21.0 at
|
||||||
top-bar layout fix, glyph orientation upright).
|
04f9bf9. Working tree clean. See CHANGELOG.md § [0.21.6] for
|
||||||
|
full detail.
|
||||||
|
|
||||||
State: HEAD locally — see `git rev-parse HEAD`. Working tree is
|
State: HEAD locally — see `git rev-parse HEAD`. The cut commit
|
||||||
clean. All workspace tests pass (~1180+; check with
|
is f63db76; any post-cut docs edits ride on top of that.
|
||||||
`cargo test --workspace`), clippy clean.
|
Workspace tests: 1273 passing / 0 failing. Clippy clean.
|
||||||
|
|
||||||
READ FIRST (in order, before doing anything):
|
READ FIRST (in order, before doing anything):
|
||||||
1. SESSION_HANDOFF.md — this file
|
1. SESSION_HANDOFF.md — this file
|
||||||
2. CHANGELOG.md — [0.20.0] section is the most recent cut
|
2. CHANGELOG.md — [0.21.6] section is the most recent cut
|
||||||
3. CLAUDE.md — unified-3.0 rule set
|
3. CLAUDE.md — unified-3.0 rule set
|
||||||
4. CLAUDE_SPEC.md — formal architecture spec
|
4. CLAUDE_SPEC.md — formal architecture spec
|
||||||
5. ARCHITECTURE.md — crate responsibilities + data flow
|
5. ARCHITECTURE.md — crate responsibilities + data flow
|
||||||
@@ -622,38 +309,32 @@ READ FIRST (in order, before doing anything):
|
|||||||
fresh machine)
|
fresh machine)
|
||||||
|
|
||||||
DECISION TO ASK THE PLAYER FIRST:
|
DECISION TO ASK THE PLAYER FIRST:
|
||||||
A. Push the post-cut commits to origin. Either as-is on master
|
A. APK launch verification on AVD / device — `adb install` +
|
||||||
or rolled into a v0.20.1 cut (CHANGELOG entry + tag).
|
`adb logcat` to shake out runtime bugs the build / unit
|
||||||
Mechanical, but local master diverges from origin until done.
|
tests can't catch. Likely surfaces JNI ClipboardManager
|
||||||
B. *Closed by `29136d8` + `a27cf5a`.* Both splash polish
|
and Android Keystore stubs that need real bridges. Larger
|
||||||
pieces shipped (cursor pulse + scanline overlay). No further
|
scope; needs an Android device or emulator running.
|
||||||
splash work pending unless a new mockup detail surfaces.
|
B. Replay-overlay screen-takeover redesign — nearly complete
|
||||||
C. *Closed by `54005d5` + `e080b49`.* Banner-local replay-overlay
|
after 12 commits across v0.21.4-6. Scrub bar with notches
|
||||||
pieces all shipped (scrub bar, ▌ label, GAME caption, MOVE
|
+ labels + WIN MOVE marker, pause / resume / step / stop
|
||||||
chip). Remaining are cross-plugin (floating MOVE chip above
|
buttons, Space + Esc + ← / → keyboard accelerators with
|
||||||
the focused card — needs cursor → card-position plumbing) or
|
continuous scrub on hold, keybind-hint footer, full HC-mode
|
||||||
multi-session (full screen-takeover redesign — move-log
|
coverage on the banner pieces, and a brand-new bottom-edge
|
||||||
scroll, mini tableau, WIN MOVE marker, data-layer impact).
|
Move Log panel with a 5-row prev/active/next window all
|
||||||
Either belongs in its own decision tree the next time replay
|
ship. The only remaining major B-2 sub-piece is the
|
||||||
work surfaces.
|
**mini-tableau preview** — the mockup's "Game Peek Band"
|
||||||
D. *Closed 2026-05-08 by `5623368`…`dd101b3`.* The full
|
at 50 % opacity showing the tableau through the replay
|
||||||
card-face / suit / card-back / default-theme / table-
|
chrome. Implementation needs a settings-aware dim overlay
|
||||||
background / top-bar / glyph-orientation arc landed across
|
or alpha modulation on the tableau cards during replay.
|
||||||
nine commits. Terminal cards rendering on every face (dark
|
Architectural — touches `card_plugin` rendering. Best
|
||||||
`#1a1a1a` background, pink/gray suit glyphs as inline SVG
|
tackled in a fresh session because it crosses into a
|
||||||
paths, scanline-pattern cyan-accent backs); both rendering
|
plugin the recent B-2 work hasn't touched. Mockup at
|
||||||
paths (`assets/cards/*.png` and the bundled-default theme
|
`docs/ui-mockups/replay-overlay-mobile.html`.
|
||||||
SVGs at `solitaire_engine/assets/themes/default/*.svg`) in
|
C. Phase 8 (sync) — local storage scaffolding, self-hosted
|
||||||
lockstep; pin test (`card_face_svg_pin`) guards against
|
Axum server, `SolitaireServerClient` impl, GPGS stub
|
||||||
future rasteriser drift. Visual-identity arc effectively
|
wired into Settings. The biggest open arc by scope; rolls
|
||||||
complete — only the toast warning/error variant slots
|
up several Phase Android dependencies (Keystore,
|
||||||
remain wired-but-unused.
|
ClipboardManager).
|
||||||
E. App icon round — re-run artwork/Icon Export.html (the
|
|
||||||
export PNGs are not currently in `artwork/`), then wire
|
|
||||||
Window::icon + generate .icns / .ico. Half-day task. No
|
|
||||||
cert dependency.
|
|
||||||
F. APK launch verification on AVD / device + the JNI bridges
|
|
||||||
it would shake out (ClipboardManager, Keystore).
|
|
||||||
|
|
||||||
WORKFLOW NOTES:
|
WORKFLOW NOTES:
|
||||||
- Use the system git config (already correct).
|
- Use the system git config (already correct).
|
||||||
@@ -663,6 +344,23 @@ WORKFLOW NOTES:
|
|||||||
- Every commit must pass build / clippy / test before pushing.
|
- Every commit must pass build / clippy / test before pushing.
|
||||||
- Push to GitHub (origin) — gh auth setup-git wired on
|
- Push to GitHub (origin) — gh auth setup-git wired on
|
||||||
primary dev box; verify on laptop before first push.
|
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–F. Don't pick unilaterally.
|
OPEN AT THE START: ask which of A–C. Don't pick unilaterally.
|
||||||
|
Note: every remaining option is multi-session by nature (A is
|
||||||
|
gated on Android tooling, B and C are explicitly multi-session
|
||||||
|
arcs). A fresh session is a better fit for any of them than the
|
||||||
|
tail of a long working stretch.
|
||||||
```
|
```
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.8 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 |
@@ -137,18 +137,23 @@ The palette is base16-eighties — a 16-slot terminal palette where indices 00
|
|||||||
|
|
||||||
## Suit Colors
|
## Suit Colors
|
||||||
|
|
||||||
**Two-color traditional mapping**, with mandatory color-blind support:
|
**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 |
|
| Suit | Default | Color-blind mode | Glyph differentiation |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Hearts | `#fb9fb1` (pink) | `#acc267` (lime) | Solid filled glyph |
|
| Hearts | `#e35353` (saturated red) | `#acc267` (lime) | Solid filled glyph |
|
||||||
| Diamonds | `#fb9fb1` (pink) | `#acc267` (lime) | **Outlined glyph (1.5px stroke)** |
|
| Diamonds | `#e35353` (saturated red) | `#acc267` (lime) | **Outlined glyph (1.5px stroke)** |
|
||||||
| Spades | `#d0d0d0` (foreground) | `#d0d0d0` | Solid filled glyph |
|
| Spades | `#e8e8e8` (near-white) | `#e8e8e8` (unchanged) | Solid filled glyph |
|
||||||
| Clubs | `#d0d0d0` (foreground) | `#d0d0d0` | **Outlined glyph (1.5px stroke)** |
|
| 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 outlined-glyph treatment is the **primary** differentiation mechanism. Color is supplementary. This means a player viewing the game on a monochrome display, or with severe red-green deficiency, can still distinguish all four suits without context. This is a hard requirement, not an optional setting.
|
||||||
|
|
||||||
The "color-blind mode" toggle in Settings only swaps red→lime; it does not turn the outlined glyphs on or off, because outlined glyphs are always on. (Was red→cyan before the 2026-05-08 primary-accent swap; CBM moved to lime to stay hue-distinct from the new red-family primary.)
|
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
|
## Typography
|
||||||
|
|
||||||
@@ -217,7 +222,7 @@ Selection highlights use a **2px inset stroke** in `#a54242` following the host
|
|||||||
|
|
||||||
Flat face design.
|
Flat face design.
|
||||||
- Background: `#1a1a1a`
|
- Background: `#1a1a1a`
|
||||||
- Border: 1px solid in suit color (pink for hearts/diamonds, foreground gray for spades/clubs)
|
- 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)
|
- 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)
|
- 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
|
- Corner radius: 8px
|
||||||
@@ -272,7 +277,7 @@ Top-right corner of the HUD: a 6px circular dot.
|
|||||||
|
|
||||||
## Accessibility
|
## Accessibility
|
||||||
|
|
||||||
1. **Color-blind mode** (Settings → Gameplay): swaps red suits' default `#fb9fb1` for `#acc267` (lime). Outlined-glyph differentiation remains active in *all* modes.
|
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`.
|
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.
|
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.
|
4. **Tabular figures** are mandatory for any number that updates live (timer, score, moves) so they don't reflow.
|
||||||
|
|||||||
@@ -22,16 +22,25 @@ bevy = { workspace = true }
|
|||||||
solitaire_engine = { workspace = true }
|
solitaire_engine = { workspace = true }
|
||||||
solitaire_data = { workspace = true }
|
solitaire_data = { workspace = true }
|
||||||
|
|
||||||
# `keyring`'s default-store init only matters on platforms with a
|
# Desktop-only deps. `keyring`'s default-store init only matters on
|
||||||
# real keychain backend (Linux Secret Service, macOS Keychain,
|
# platforms with a real keychain backend (Linux Secret Service,
|
||||||
# Windows Credential Store). The crate also pulls `rpassword`
|
# macOS Keychain, Windows Credential Store), and its transitive
|
||||||
# transitively, which uses `libc::__errno_location` — a symbol
|
# `rpassword` uses `libc::__errno_location` — a symbol Android's
|
||||||
# Android's bionic doesn't expose. Target-gating keeps
|
# bionic doesn't expose. `winit` is promoted from a transitive
|
||||||
# `cargo apk build` viable; the call site in `lib.rs` has its own
|
# Bevy 0.18 → bevy_winit 0.18 → winit 0.30 dep to a direct dep so
|
||||||
# `cfg(not(target_os = "android"))` guard so the desktop init path
|
# the `Window::icon` wiring in `set_window_icon` can construct
|
||||||
# is unchanged.
|
# `winit::window::Icon` values (bevy_winit 0.18 doesn't re-export
|
||||||
|
# `Icon`). Android draws its launcher icon from the APK manifest,
|
||||||
|
# so neither dep matters there. Target-gating keeps `cargo apk
|
||||||
|
# build` viable; the desktop call sites have their own
|
||||||
|
# `cfg(not(target_os = "android"))` guards.
|
||||||
[target.'cfg(not(target_os = "android"))'.dependencies]
|
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||||
keyring = { workspace = true }
|
keyring = { workspace = true }
|
||||||
|
winit = { version = "0.30", default-features = false }
|
||||||
|
# `tiny-skia` is already in the workspace deps for `solitaire_engine`;
|
||||||
|
# `solitaire_app` consumes it directly only on the desktop icon path
|
||||||
|
# (PNG → raw RGBA decode for `set_window_icon`).
|
||||||
|
tiny-skia = { workspace = true }
|
||||||
|
|
||||||
# --- Android packaging metadata (read by `cargo-apk`) -------------------
|
# --- Android packaging metadata (read by `cargo-apk`) -------------------
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ use bevy::prelude::*;
|
|||||||
use bevy::window::{
|
use bevy::window::{
|
||||||
Monitor, MonitorSelection, PresentMode, PrimaryMonitor, PrimaryWindow, WindowPosition,
|
Monitor, MonitorSelection, PresentMode, PrimaryMonitor, PrimaryWindow, WindowPosition,
|
||||||
};
|
};
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
use bevy::winit::WinitWindows;
|
||||||
use solitaire_data::{load_settings_from, provider_for_backend, settings_file_path, Settings};
|
use solitaire_data::{load_settings_from, provider_for_backend, settings_file_path, Settings};
|
||||||
use solitaire_engine::{
|
use solitaire_engine::{
|
||||||
register_theme_asset_sources, AchievementPlugin, AnimationPlugin, AssetSourcesPlugin,
|
register_theme_asset_sources, AchievementPlugin, AnimationPlugin, AssetSourcesPlugin,
|
||||||
@@ -174,6 +176,14 @@ pub fn run() {
|
|||||||
.add_plugins(SplashPlugin)
|
.add_plugins(SplashPlugin)
|
||||||
.add_plugins(DiagnosticsHudPlugin);
|
.add_plugins(DiagnosticsHudPlugin);
|
||||||
|
|
||||||
|
// Wire the runtime window icon. Bevy 0.18 has no first-class
|
||||||
|
// `Window::icon` field; the icon is set through the underlying
|
||||||
|
// `winit::window::Window` via `WinitWindows`. Android draws its
|
||||||
|
// launcher icon from the APK manifest, so the system is desktop-
|
||||||
|
// only — same target-gate as the `winit` dep itself.
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
app.add_systems(Update, set_window_icon);
|
||||||
|
|
||||||
// Smart default window sizing: when no saved geometry was loaded,
|
// Smart default window sizing: when no saved geometry was loaded,
|
||||||
// resize the freshly-opened 1280×800 window to ~70 % of the primary
|
// resize the freshly-opened 1280×800 window to ~70 % of the primary
|
||||||
// monitor's logical size on the first frame. Without this, a 4K
|
// monitor's logical size on the first frame. Without this, a 4K
|
||||||
@@ -251,6 +261,80 @@ fn apply_smart_default_window_size(
|
|||||||
*applied = true;
|
*applied = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One-shot Update system that sets the primary window's taskbar /
|
||||||
|
/// title-bar icon to the embedded 256 px Terminal-aesthetic mark
|
||||||
|
/// generated by `solitaire_engine/examples/icon_generator.rs`.
|
||||||
|
///
|
||||||
|
/// Bevy 0.18 has no `Window::icon` field — the icon is set through
|
||||||
|
/// the underlying `winit::window::Window` via the `WinitWindows`
|
||||||
|
/// resource. The system is desktop-only (Android draws its launcher
|
||||||
|
/// icon from the APK manifest, not from any runtime call). Returns
|
||||||
|
/// silently and tries again next frame until both the primary
|
||||||
|
/// window and `WinitWindows` are populated, then sets the icon
|
||||||
|
/// once and self-disables via `Local<bool>`.
|
||||||
|
///
|
||||||
|
/// Icon bytes are `include_bytes!()`-embedded at compile time, same
|
||||||
|
/// shape as the audio assets and default-theme SVGs — no runtime
|
||||||
|
/// asset-path resolution, no `cargo run` working-directory
|
||||||
|
/// assumptions. PNG → RGBA decode runs through `tiny_skia` (already
|
||||||
|
/// in the build for SVG rasterisation), so this system adds zero
|
||||||
|
/// new dependencies on top of the direct `winit` dep that's
|
||||||
|
/// already required for `Icon` construction.
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
fn set_window_icon(
|
||||||
|
mut applied: Local<bool>,
|
||||||
|
primary_window: Query<Entity, With<PrimaryWindow>>,
|
||||||
|
// `Option<NonSend<...>>` rather than `NonSend<...>` because Bevy
|
||||||
|
// 0.18's stricter system-param validation panics on the first
|
||||||
|
// few frames before `WinitWindows` is inserted (the resource is
|
||||||
|
// populated after winit's `Resumed` event, which fires after
|
||||||
|
// the first system-tick batch). The early-return below handles
|
||||||
|
// the `None` window-wrapper case for the same lifecycle reason.
|
||||||
|
winit_windows: Option<NonSend<WinitWindows>>,
|
||||||
|
) {
|
||||||
|
if *applied {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(winit_windows) = winit_windows else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Ok(primary_entity) = primary_window.single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(window_wrapper) = winit_windows.get_window(primary_entity) else {
|
||||||
|
// Primary window's underlying winit handle not yet
|
||||||
|
// populated — `WinitWindows` fills in after the first
|
||||||
|
// `Resumed` event. Try again next frame.
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The 256 × 256 PNG is sufficient for `set_window_icon`; winit
|
||||||
|
// scales it for the actual rendered size. Smaller PNGs in
|
||||||
|
// `assets/icon/` exist for downstream Linux hicolor / Windows
|
||||||
|
// `.ico` / macOS `.icns` packaging — they're not used here.
|
||||||
|
const ICON_BYTES: &[u8] = include_bytes!("../../assets/icon/icon_256.png");
|
||||||
|
|
||||||
|
let pixmap = match tiny_skia::Pixmap::decode_png(ICON_BYTES) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("warn: could not decode embedded window icon PNG: {e}");
|
||||||
|
*applied = true; // don't retry every frame
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let rgba = pixmap.data().to_vec();
|
||||||
|
let icon = match winit::window::Icon::from_rgba(rgba, pixmap.width(), pixmap.height()) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("warn: could not construct window icon: {e}");
|
||||||
|
*applied = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window_wrapper.set_window_icon(Some(icon));
|
||||||
|
*applied = true;
|
||||||
|
}
|
||||||
|
|
||||||
/// Wraps the default panic hook with one that also appends a crash log
|
/// Wraps the default panic hook with one that also appends a crash log
|
||||||
/// to `<data_dir>/crash.log` (next to `settings.json`). The default hook
|
/// to `<data_dir>/crash.log` (next to `settings.json`). The default hook
|
||||||
/// still runs afterwards, so stderr output and debugger integration are
|
/// still runs afterwards, so stderr output and debugger integration are
|
||||||
|
|||||||
@@ -147,12 +147,38 @@ pub struct Replay {
|
|||||||
/// [`REPLAY_SCHEMA_VERSION`].
|
/// [`REPLAY_SCHEMA_VERSION`].
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub share_url: Option<String>,
|
pub share_url: Option<String>,
|
||||||
|
/// Index into [`moves`](Self::moves) of the move that triggered
|
||||||
|
/// the win condition (i.e. completed the last foundation pile).
|
||||||
|
///
|
||||||
|
/// For replays recorded by the live engine this is always
|
||||||
|
/// `Some(moves.len() - 1)` because recording freezes on win — but
|
||||||
|
/// the field is stored explicitly so the playback UI can read it
|
||||||
|
/// directly without re-deriving "the last move was the win" each
|
||||||
|
/// time, and to leave room for future recording semantics that
|
||||||
|
/// might capture post-win state.
|
||||||
|
///
|
||||||
|
/// `None` for replays loaded from disk that pre-date this field.
|
||||||
|
/// `#[serde(default)]` keeps older `latest_replay.json` /
|
||||||
|
/// `replays.json` files loadable without bumping
|
||||||
|
/// [`REPLAY_SCHEMA_VERSION`] — this is an additive optional
|
||||||
|
/// field, not a schema-breaking change.
|
||||||
|
///
|
||||||
|
/// Surfaced by the replay-overlay scrub bar's WIN MOVE marker
|
||||||
|
/// (B-2 screen-takeover redesign) when present.
|
||||||
|
#[serde(default)]
|
||||||
|
pub win_move_index: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Replay {
|
impl Replay {
|
||||||
/// Construct a fresh replay with the current schema version. The
|
/// Construct a fresh replay with the current schema version. The
|
||||||
/// caller fills in the recorded fields; this is the canonical
|
/// caller fills in the recorded fields; this is the canonical
|
||||||
/// constructor used by the engine on win.
|
/// constructor used by the engine on win.
|
||||||
|
///
|
||||||
|
/// [`win_move_index`](Self::win_move_index) and
|
||||||
|
/// [`share_url`](Self::share_url) default to `None` — the engine
|
||||||
|
/// uses [`with_win_move_index`](Self::with_win_move_index) at the
|
||||||
|
/// recording site to set the former, and `sync_plugin` writes the
|
||||||
|
/// latter directly when the upload task resolves.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
seed: u64,
|
seed: u64,
|
||||||
draw_mode: DrawMode,
|
draw_mode: DrawMode,
|
||||||
@@ -172,8 +198,24 @@ impl Replay {
|
|||||||
recorded_at,
|
recorded_at,
|
||||||
moves,
|
moves,
|
||||||
share_url: None,
|
share_url: None,
|
||||||
|
win_move_index: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builder-style setter for [`win_move_index`](Self::win_move_index).
|
||||||
|
/// Returns `self` so the recording site can chain it onto
|
||||||
|
/// [`Replay::new`]:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// let replay = Replay::new(...).with_win_move_index(Some(recording.moves.len() - 1));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// `None` is a valid input — useful for tests that don't care about
|
||||||
|
/// the WIN MOVE marker's scrub-bar position.
|
||||||
|
pub fn with_win_move_index(mut self, idx: Option<usize>) -> Self {
|
||||||
|
self.win_move_index = idx;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rolling history of the player's most recent winning replays.
|
/// Rolling history of the player's most recent winning replays.
|
||||||
@@ -737,4 +779,71 @@ mod tests {
|
|||||||
|
|
||||||
let _ = fs::remove_file(&path);
|
let _ = fs::remove_file(&path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// win_move_index — additive optional field for the WIN MOVE marker
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn replay_new_defaults_win_move_index_to_none() {
|
||||||
|
let r = sample_replay();
|
||||||
|
assert_eq!(r.win_move_index, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn with_win_move_index_sets_value() {
|
||||||
|
let r = sample_replay().with_win_move_index(Some(3));
|
||||||
|
assert_eq!(r.win_move_index, Some(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn with_win_move_index_accepts_none() {
|
||||||
|
// Passing None through the builder is a valid no-op — useful for
|
||||||
|
// tests / synthetic replays that don't care about the marker.
|
||||||
|
let r = sample_replay().with_win_move_index(None);
|
||||||
|
assert_eq!(r.win_move_index, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn replay_with_win_move_index_round_trips_on_disk() {
|
||||||
|
let path = tmp_path("win_move_index_round_trip");
|
||||||
|
let _ = fs::remove_file(&path);
|
||||||
|
|
||||||
|
let original = sample_replay().with_win_move_index(Some(3));
|
||||||
|
save_latest_replay_to(&path, &original).expect("save");
|
||||||
|
let loaded = load_latest_replay_from(&path).expect("load");
|
||||||
|
assert_eq!(loaded.win_move_index, Some(3));
|
||||||
|
assert_eq!(loaded, original);
|
||||||
|
|
||||||
|
let _ = fs::remove_file(&path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Older replay files written before this field was added must still
|
||||||
|
/// load — `#[serde(default)]` keeps `win_move_index` optional and
|
||||||
|
/// defaults missing fields to `None`. This is the contract that lets
|
||||||
|
/// us add the field without bumping `REPLAY_SCHEMA_VERSION`.
|
||||||
|
#[test]
|
||||||
|
fn replay_without_win_move_index_loads_with_none() {
|
||||||
|
let path = tmp_path("legacy_no_win_move_index");
|
||||||
|
let _ = fs::remove_file(&path);
|
||||||
|
|
||||||
|
// Hand-rolled minimal v2 replay JSON with no win_move_index field.
|
||||||
|
let v2_no_field = r#"{
|
||||||
|
"schema_version": 2,
|
||||||
|
"seed": 1,
|
||||||
|
"draw_mode": "DrawOne",
|
||||||
|
"mode": "Classic",
|
||||||
|
"time_seconds": 60,
|
||||||
|
"final_score": 100,
|
||||||
|
"recorded_at": "2026-05-02",
|
||||||
|
"moves": []
|
||||||
|
}"#;
|
||||||
|
fs::write(&path, v2_no_field).expect("write fixture");
|
||||||
|
|
||||||
|
let loaded = load_latest_replay_from(&path).expect("load");
|
||||||
|
assert_eq!(loaded.win_move_index, None);
|
||||||
|
assert_eq!(loaded.schema_version, REPLAY_SCHEMA_VERSION);
|
||||||
|
|
||||||
|
let _ = fs::remove_file(&path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,24 @@ pub struct Settings {
|
|||||||
/// solely on colour.
|
/// solely on colour.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub color_blind_mode: bool,
|
pub color_blind_mode: bool,
|
||||||
|
/// When `true`, boost foreground text + suit-red glyphs to higher-
|
||||||
|
/// luminance variants for better legibility on low-quality displays
|
||||||
|
/// or for low-vision users. Per `design-system.md` §Accessibility:
|
||||||
|
/// on-surface `#d0d0d0` → `#f5f5f5`, suit-red `#fb9fb1` → `#ff8aa0`,
|
||||||
|
/// outline `#505050` → `#a0a0a0`. Older `settings.json` files
|
||||||
|
/// written before this field existed deserialize cleanly to
|
||||||
|
/// `false` thanks to `#[serde(default)]`.
|
||||||
|
#[serde(default)]
|
||||||
|
pub high_contrast_mode: bool,
|
||||||
|
/// When `true`, suppresses non-essential motion: card-lift slide
|
||||||
|
/// transitions become instant snaps, splash scanline / cursor pulse
|
||||||
|
/// animations are disabled, and the warning-chip pulse holds at
|
||||||
|
/// rest. Per `design-system.md` §Accessibility — the WCAG-required
|
||||||
|
/// reduce-motion mode. Older `settings.json` files written before
|
||||||
|
/// this field existed deserialize cleanly to `false` thanks to
|
||||||
|
/// `#[serde(default)]`.
|
||||||
|
#[serde(default)]
|
||||||
|
pub reduce_motion_mode: bool,
|
||||||
/// Window size and screen position to restore on next launch. `None`
|
/// Window size and screen position to restore on next launch. `None`
|
||||||
/// means "use platform defaults" — set on first run, then populated
|
/// means "use platform defaults" — set on first run, then populated
|
||||||
/// as the player resizes / moves the window. Older `settings.json`
|
/// as the player resizes / moves the window. Older `settings.json`
|
||||||
@@ -314,6 +332,8 @@ impl Default for Settings {
|
|||||||
selected_background: 0,
|
selected_background: 0,
|
||||||
first_run_complete: false,
|
first_run_complete: false,
|
||||||
color_blind_mode: false,
|
color_blind_mode: false,
|
||||||
|
high_contrast_mode: false,
|
||||||
|
reduce_motion_mode: false,
|
||||||
window_geometry: None,
|
window_geometry: None,
|
||||||
selected_theme_id: default_theme_id(),
|
selected_theme_id: default_theme_id(),
|
||||||
shown_achievement_onboarding: false,
|
shown_achievement_onboarding: false,
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#d0d0d0" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#d0d0d0">10</text>
|
fill="#e8e8e8">10</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#d0d0d0" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#d0d0d0">2</text>
|
fill="#e8e8e8">2</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#d0d0d0" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#d0d0d0">3</text>
|
fill="#e8e8e8">3</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#d0d0d0" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#d0d0d0">4</text>
|
fill="#e8e8e8">4</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#d0d0d0" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#d0d0d0">5</text>
|
fill="#e8e8e8">5</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#d0d0d0" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#d0d0d0">6</text>
|
fill="#e8e8e8">6</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#d0d0d0" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#d0d0d0">7</text>
|
fill="#e8e8e8">7</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#d0d0d0" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#d0d0d0">8</text>
|
fill="#e8e8e8">8</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#d0d0d0" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#d0d0d0">9</text>
|
fill="#e8e8e8">9</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#d0d0d0" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#d0d0d0">A</text>
|
fill="#e8e8e8">A</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#d0d0d0" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#d0d0d0">J</text>
|
fill="#e8e8e8">J</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#d0d0d0" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#d0d0d0">K</text>
|
fill="#e8e8e8">K</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#d0d0d0" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#d0d0d0">Q</text>
|
fill="#e8e8e8">Q</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#d0d0d0" stroke-width="3"/>
|
<path d="M16,4 C 13,4 10,7 10,10 C 10,12 11,13 12,14 C 9,14 4,17 4,21 C 4,24 7,27 10,27 C 12,27 14,26 14,24 L 13,30 L 19,30 L 18,24 C 18,26 20,27 22,27 C 25,27 28,24 28,21 C 28,17 23,14 20,14 C 21,13 22,12 22,10 C 22,7 19,4 16,4 Z" fill="none" stroke="#e8e8e8" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#fb9fb1" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#fb9fb1">10</text>
|
fill="#e35353">10</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#fb9fb1" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#fb9fb1">2</text>
|
fill="#e35353">2</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#fb9fb1" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#fb9fb1">3</text>
|
fill="#e35353">3</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#fb9fb1" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#fb9fb1">4</text>
|
fill="#e35353">4</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#fb9fb1" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#fb9fb1">5</text>
|
fill="#e35353">5</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#fb9fb1" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#fb9fb1">6</text>
|
fill="#e35353">6</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#fb9fb1" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#fb9fb1">7</text>
|
fill="#e35353">7</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#fb9fb1" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#fb9fb1">8</text>
|
fill="#e35353">8</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#fb9fb1" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#fb9fb1">9</text>
|
fill="#e35353">9</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#fb9fb1" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#fb9fb1">A</text>
|
fill="#e35353">A</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#fb9fb1" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#fb9fb1">J</text>
|
fill="#e35353">J</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#fb9fb1" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#fb9fb1">K</text>
|
fill="#e35353">K</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#fb9fb1" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#fb9fb1">Q</text>
|
fill="#e35353">Q</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#fb9fb1" stroke-width="3"/>
|
<path d="M16,2 L 29,16 L 16,30 L 3,16 Z" fill="none" stroke="#e35353" stroke-width="3"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#fb9fb1" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#fb9fb1">10</text>
|
fill="#e35353">10</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#fb9fb1"/>
|
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#e35353"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#fb9fb1"/>
|
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#e35353"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#fb9fb1" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#fb9fb1">2</text>
|
fill="#e35353">2</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#fb9fb1"/>
|
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#e35353"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#fb9fb1"/>
|
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#e35353"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#fb9fb1" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#fb9fb1">3</text>
|
fill="#e35353">3</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#fb9fb1"/>
|
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#e35353"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#fb9fb1"/>
|
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#e35353"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#fb9fb1" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#fb9fb1">4</text>
|
fill="#e35353">4</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#fb9fb1"/>
|
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#e35353"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#fb9fb1"/>
|
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#e35353"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,18 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="384" viewBox="0 0 256 384">
|
||||||
<rect x="1" y="1" width="254" height="382" rx="16" ry="16"
|
<rect x="0" y="0" width="256" height="384" rx="16" ry="16"
|
||||||
fill="#1a1a1a" stroke="#fb9fb1" stroke-width="2"/>
|
fill="#1a1a1a"/>
|
||||||
|
|
||||||
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
<!-- Top-left rank in JetBrains-Mono-styled FiraMono (rank digits
|
||||||
and letters render correctly in FiraMono; only the suit glyphs
|
and letters render correctly in FiraMono; only the suit glyphs
|
||||||
needed to escape to paths). -->
|
needed to escape to paths). -->
|
||||||
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
<text x="14" y="44" font-family="Fira Mono" font-size="36" font-weight="700"
|
||||||
fill="#fb9fb1">5</text>
|
fill="#e35353">5</text>
|
||||||
|
|
||||||
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
<!-- Top-left small suit glyph at (14, 50), 20 × 20.
|
||||||
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
`suit_path_d` is authored in a 32-unit box, so scale 0.625
|
||||||
lands the visible glyph at 20 px. -->
|
lands the visible glyph at 20 px. -->
|
||||||
<g transform="translate(14 50) scale(0.625)">
|
<g transform="translate(14 50) scale(0.625)">
|
||||||
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#fb9fb1"/>
|
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#e35353"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
<!-- Bottom-right large suit glyph at (178, 286), 64 × 64.
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
(178, 286). Same upright orientation as the top-left small
|
(178, 286). Same upright orientation as the top-left small
|
||||||
glyph — no 180° rotation applied. -->
|
glyph — no 180° rotation applied. -->
|
||||||
<g transform="translate(178 286) scale(2)">
|
<g transform="translate(178 286) scale(2)">
|
||||||
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#fb9fb1"/>
|
<path d="M16,28 C 8,22 2,17 2,11 C 2,7 5,4 9,4 C 12,4 14,6 16,9 C 18,6 20,4 23,4 C 27,4 30,7 30,11 C 30,17 24,22 16,28 Z" fill="#e35353"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.2 KiB |