feat(game): finished-game export as a PNG image behind a format chooser
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 59s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m16s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 59s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m16s
The history header's export button now opens a chooser — Telegram's native popup inside Telegram, the app's own modal elsewhere — offering the GCG file and a new client-rendered PNG of the final position (lib/gameimage, Canvas 2D, lazy dynamic import, zero dependencies): light theme, classic A..O/1..15 axes, label-free premium fills, and a fixed-typography per-seat scoresheet with GCG-style move coordinates, multi-word sub-lines, endgame rack-settlement row, winner trophy and a hostname + device-locale finish date footer; a long game stretches the board, never the typography. Delivery mirrors the GCG rules (Web Share with no blob fallback, else download) except on Android Telegram/VK WebViews and the desktop VK iframe, where a binary PNG has no clipboard-text fallback: those get a preview modal with a long-press/right-click save hint and a copy-image button where ClipboardItem exists.
This commit is contained in:
+25
-8
@@ -403,15 +403,32 @@ enabled on the first, uncached load) and flip in place when an event refreshes t
|
||||
lobby with a calm modal pointing at the bot (`@<username>`), not a red error on the
|
||||
Friends tab. Flex text inputs carry `min-width:0` so they shrink instead of overflowing in
|
||||
Safari.
|
||||
- **History / GCG**: the in-game slide-down history lays each move out in a per-seat grid
|
||||
(the word(s) and the move score, no running total); *Export GCG* (the 📤 in the history
|
||||
header) delivers the `.gcg` file — Web Share where available, a clipboard copy in an Android in-app
|
||||
WebView (Telegram / VK, which has neither Web Share nor a working download), else a Blob download —
|
||||
and appears only once the game is finished — and
|
||||
never in an honest-AI game (throwaway practice). Confirming a resign reveals the full board:
|
||||
it closes the history drawer (portrait) and zooms the board out.
|
||||
- **History / export**: the in-game slide-down history lays each move out in a per-seat grid
|
||||
(the word(s) and the move score, no running total); the 📤 in the history header appears
|
||||
only once the game is finished — never in an honest-AI game (throwaway practice) — and
|
||||
opens a **format chooser**: Telegram's native popup inside Telegram (image / GCG / cancel),
|
||||
the app's own modal elsewhere (the accent button leads with the image, the GCG file is the
|
||||
quiet alternative; GCG needs the connection, the image renders locally). The GCG file goes
|
||||
by Web Share where available, a clipboard copy in an Android in-app WebView (Telegram / VK,
|
||||
which has neither Web Share nor a working download), else a Blob download. The image goes by
|
||||
Web Share / Blob download too, but on those in-app WebViews (and the desktop VK iframe) it
|
||||
opens a **preview modal** instead — the PNG as a data: URL with a long-press / right-click
|
||||
save hint and a copy-image button where `ClipboardItem` exists. Confirming a resign reveals
|
||||
the full board: it closes the history drawer (portrait) and zooms the board out.
|
||||
- **Export image** (`lib/gameimage.ts`, Canvas 2D, dynamically imported): always the light
|
||||
palette (pinned constants mirroring `app.css`), the final board with classic axes A..O /
|
||||
1..15, premium squares as plain colour fills (no text labels), tiles in the in-game style
|
||||
(bevel, letter top-left, value bottom-right, ✻ for an Erudit blank), no last-move
|
||||
highlight. The right column is the scoresheet: per-seat name over the final score (🏆 by
|
||||
the winner), one fixed-typography row per move — muted classic coordinate (across =
|
||||
row-first `8G`, down = column-first `H8`), the main word, points right-aligned; extra
|
||||
words of a multi-word play on a smaller second line; italic localized notes for
|
||||
pass/exchange/resign/timeout; a closing muted ± row for the endgame rack settlement when
|
||||
present. Footer: `hostname · finish date` in the **device** locale. The scoresheet's
|
||||
height drives the board side (never below its minimum): a long game stretches the board,
|
||||
never the typography, so the image has no dead space.
|
||||
- **Finished game**: the board keeps no last-word highlight and no zoom; the history header
|
||||
offers *Export GCG* (not *Drop game*; an AI game offers neither) and the comms hub hides the 🔎 *Dictionary* tab — and a **finished AI game has no comms at all** (no chat, dictionary closed), so its 💬 entry is dropped from the header too; and
|
||||
offers *Export game* (not *Drop game*; an AI game offers neither) and the comms hub hides the 🔎 *Dictionary* tab — and a **finished AI game has no comms at all** (no chat, dictionary closed), so its 💬 entry is dropped from the header too; and
|
||||
the footer (rack + tab bar) is **drawn but inert** (greyed, non-interactive) rather than
|
||||
hidden, so the layout does not jump. Chat send / nudge are the ⬆️ / 🛎️ icons. The input
|
||||
row is turn-driven: on your turn the message field shows until your one allowed message is
|
||||
|
||||
Reference in New Issue
Block a user