aperture architecture: lift the page-locking foundation (html/body/.container overflow:hidden + flex-column + min-height:0; .row flex-shrink:0) from 5 per-page SCSS files into _base.scss — was opt-in per page via `body.page-billboard` / `page-dashboard` / `page-gameboard` / `page-sky` / `page-wallet` etc., with 5 near-identical `html:has(body.page-X) { overflow: hidden }` + `body.page-X { … }` blocks duplicating the same rules; any page that forgot to set `page_class` in its view context (e.g. `epic.tarot_deck` — never set) rendered without the aperture, letting applet borders + titles clip past the fixed navbar/footer sidebars at narrower viewports; foundation now universal, page-specific overrides stay scoped — gameboard keeps `.container { overflow: clip }` (Firefox seat-tooltip scroll-anchoring quirk) + billboard/dashboard/gameboard keep `.row { margin-bottom: -1rem }` (h2-row tightening); page_class context vars + body class hooks preserved (FTs at test_bud_btn.py:370 / :379 still assert on them); regression gate: 60 layout-sensitive FTs (billboard, my_buds, bud_btn, applet_my_posts, dashboard, wallet, gameboard, layout_and_styling, jasmine) + 43 room FTs (gatekeeper_bud_btn, room_gatekeeper, room_sky_select, sharing) all green
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:16:12 -04:00
|
|
|
|
// Aperture foundation (html/body/.container overflow + flex-column) lives
|
|
|
|
|
|
// universally in _base.scss. Gameboard's only divergence: `overflow: clip`
|
|
|
|
|
|
// on .container instead of `hidden` — `clip` prevents the seat tooltip
|
|
|
|
|
|
// scroll-anchoring quirk Firefox triggers under overflow:hidden. The
|
|
|
|
|
|
// `.row { margin-bottom: -1rem }` pull mirrors the billboard/dashboard
|
|
|
|
|
|
// h2-row tightening.
|
2026-03-09 21:52:54 -04:00
|
|
|
|
|
|
|
|
|
|
body.page-gameboard {
|
|
|
|
|
|
.container {
|
2026-03-16 00:07:52 -04:00
|
|
|
|
overflow: clip;
|
2026-03-09 21:52:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.row {
|
|
|
|
|
|
margin-bottom: -1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.gameboard-page {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-width: 425px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 550px) {
|
|
|
|
|
|
.gameboard-page {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (min-width: 738px) {
|
|
|
|
|
|
.gameboard-page {
|
|
|
|
|
|
min-width: 666px;
|
|
|
|
|
|
}
|
2026-03-11 00:58:24 -04:00
|
|
|
|
|
|
|
|
|
|
body.page-gameboard .container {
|
|
|
|
|
|
overflow: visible;
|
|
|
|
|
|
}
|
2026-03-09 21:52:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-06 01:30:31 -04:00
|
|
|
|
@media (orientation: landscape) {
|
2026-03-23 01:06:14 -04:00
|
|
|
|
// Restore clip in landscape — overrides the >738px overflow:visible above,
|
|
|
|
|
|
// preventing the gameboard applets from bleeding into the footer sidebar.
|
|
|
|
|
|
body.page-gameboard .container {
|
|
|
|
|
|
overflow: clip;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Reset the 666px min-width so gameboard-page shrinks to fit within the
|
|
|
|
|
|
// sidebar-bounded container rather than overflowing into the footer sidebar.
|
|
|
|
|
|
.gameboard-page {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 23:48:20 -04:00
|
|
|
|
#id_applet_game_kit {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
|
|
|
|
|
|
#id_game_kit {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
position: relative;
|
2026-03-09 21:52:54 -04:00
|
|
|
|
display: flex;
|
2026-03-09 23:48:20 -04:00
|
|
|
|
flex-direction: row;
|
2026-03-24 22:25:25 -04:00
|
|
|
|
flex-wrap: wrap;
|
2026-03-09 23:48:20 -04:00
|
|
|
|
align-items: center;
|
2026-03-24 22:25:25 -04:00
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 0.75rem;
|
2026-03-09 23:48:20 -04:00
|
|
|
|
overflow-x: visible;
|
|
|
|
|
|
scrollbar-width: none;
|
|
|
|
|
|
&::-webkit-scrollbar { display: none; }
|
2026-03-09 22:42:30 -04:00
|
|
|
|
|
2026-03-09 23:48:20 -04:00
|
|
|
|
.token { position: static; }
|
2026-03-09 22:42:30 -04:00
|
|
|
|
|
2026-04-15 22:39:01 -04:00
|
|
|
|
.token:hover .token-tooltip,
|
|
|
|
|
|
.token:hover .tt { display: none; } // JS portal handles show/hide
|
2026-03-09 23:48:20 -04:00
|
|
|
|
|
|
|
|
|
|
.token,
|
|
|
|
|
|
.kit-item { font-size: 1.5rem; }
|
|
|
|
|
|
|
|
|
|
|
|
.kit-item { opacity: 0.6; }
|
2026-03-09 22:42:30 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-12 22:48:32 -04:00
|
|
|
|
#id_applet_new_game {
|
2026-03-09 22:42:30 -04:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2026-03-09 21:52:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-14 00:34:07 -04:00
|
|
|
|
#id_applet_my_games {
|
2026-05-12 22:48:32 -04:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2026-03-14 00:34:07 -04:00
|
|
|
|
|
2026-05-12 22:48:32 -04:00
|
|
|
|
.applet-list {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
padding-top: 0.25rem;
|
2026-03-14 00:34:07 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 23:48:20 -04:00
|
|
|
|
#id_tooltip_portal {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
z-index: 9999;
|
|
|
|
|
|
|
2026-04-16 00:14:47 -04:00
|
|
|
|
padding: 0.75rem 1.5rem;
|
|
|
|
|
|
|
2026-04-21 15:46:30 -04:00
|
|
|
|
@extend %tt-token-fields;
|
2026-04-16 00:14:47 -04:00
|
|
|
|
|
|
|
|
|
|
.tt-equip-btns {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: -1rem;
|
|
|
|
|
|
top: -1rem;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 0.25rem;
|
|
|
|
|
|
z-index: 1;
|
|
|
|
|
|
|
|
|
|
|
|
.btn { margin: 0; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-03 21:18:09 -04:00
|
|
|
|
// Tray sig-card tooltip (Phase 2) — PRV / NXT btns pinned to the bottom
|
|
|
|
|
|
// corners of the portal, 1rem outside the panel so the btn centres land
|
|
|
|
|
|
// exactly on the corners. The shared @stat-block-shared mixin in
|
|
|
|
|
|
// _card-deck.scss already does this for fan / sig / sea contexts; the
|
|
|
|
|
|
// portal isn't covered by that mixin so we re-state the rules here.
|
|
|
|
|
|
.fyi-prev,
|
|
|
|
|
|
.fyi-next {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
bottom: -1rem;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
z-index: 70;
|
|
|
|
|
|
}
|
|
|
|
|
|
.fyi-prev { left: -1rem; }
|
|
|
|
|
|
.fyi-next { right: -1rem; }
|
|
|
|
|
|
|
2026-03-09 23:48:20 -04:00
|
|
|
|
&.active { display: block; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-16 00:07:52 -04:00
|
|
|
|
#id_mini_tooltip_portal {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
z-index: 9999;
|
|
|
|
|
|
font-size: 0.8em;
|
|
|
|
|
|
font-style: italic;
|
|
|
|
|
|
width: fit-content;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
|
|
|
|
|
|
&.active { display: block; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 21:52:54 -04:00
|
|
|
|
@media (max-height: 500px) {
|
|
|
|
|
|
body.page-gameboard {
|
|
|
|
|
|
.container {
|
|
|
|
|
|
.row {
|
|
|
|
|
|
padding: 0.25rem 0;
|
|
|
|
|
|
.col-lg-6 h2 {
|
|
|
|
|
|
margin-bottom: 0.5rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-19 01:38:55 -04:00
|
|
|
|
|
|
|
|
|
|
// ─── My Sea sign-gate ────────────────────────────────────────────────────────
|
|
|
|
|
|
// Sprint 4b of [[project-my-sea-roadmap]]. Renders when User.significator
|
|
|
|
|
|
// is None, on both the standalone /gameboard/my-sea/ page AND the
|
|
|
|
|
|
// /gameboard/ My Sea applet. Look!-formatted Brief-style line w. FYI
|
|
|
|
|
|
// (→ /billboard/my-sign/) + BACK (→ /gameboard/) action buttons. Inline
|
|
|
|
|
|
// content (not portaled like .note-banner) — it IS the page content
|
|
|
|
|
|
// until a sig is picked, not a transient nudge.
|
|
|
|
|
|
.my-sea-sign-gate {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
|
padding: 1.5rem;
|
|
|
|
|
|
color: rgba(var(--terUser), 1);
|
|
|
|
|
|
|
|
|
|
|
|
.my-sea-sign-gate__line {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-size: 1.1rem;
|
|
|
|
|
|
line-height: 1.4;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
// --terUser ink mirrors the gate's accent + signals "do this
|
|
|
|
|
|
// first" visually distinct from the body's standard --secUser.
|
|
|
|
|
|
color: rgba(var(--terUser), 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.my-sea-sign-gate__actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-19 15:15:37 -04:00
|
|
|
|
// Applet variant — denser layout, omits NVM (the user is already on
|
2026-05-19 01:38:55 -04:00
|
|
|
|
// the gameboard). Smaller line + just the FYI action surviving.
|
|
|
|
|
|
&.my-sea-sign-gate--applet {
|
|
|
|
|
|
padding: 0.5rem;
|
|
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
|
|
|
|
|
|
|
.my-sea-sign-gate__line {
|
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-19 15:15:37 -04:00
|
|
|
|
|
|
|
|
|
|
// ─── My Sea DRAW SEA landing ─────────────────────────────────────────────────
|
|
|
|
|
|
// Sprint 5 iter 1 of [[project-my-sea-roadmap]]. When a user has a saved
|
|
|
|
|
|
// significator (gate passed), /gameboard/my-sea/ renders this landing
|
|
|
|
|
|
// screen: DRY table hex w. 6 chair seats labeled 1C-6C + central DRAW
|
|
|
|
|
|
// SEA btn. Mirrors my-sign's `.my-sign-page` + `.my-sign-landing`
|
|
|
|
|
|
// structure — same room-shell chain so room.js's scaleTable() can size
|
|
|
|
|
|
// the hex; same flex setup so the container chain propagates real
|
|
|
|
|
|
// height down for the scale calc.
|
|
|
|
|
|
.my-sea-page {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.my-sea-landing {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
2026-05-19 15:48:07 -04:00
|
|
|
|
// FREE DRAW btn — centered in the hex, mirrors SCAN SIGN's 2-line
|
|
|
|
|
|
// font sizing so "FREE/DRAW" sits cleanly inside the 4rem circle.
|
2026-05-19 15:15:37 -04:00
|
|
|
|
#id_draw_sea_btn {
|
|
|
|
|
|
font-size: 0.75rem;
|
|
|
|
|
|
line-height: 1.1;
|
|
|
|
|
|
white-space: normal;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-19 15:48:07 -04:00
|
|
|
|
// Chair-position labels (1C-6C). Mirrors the room's `.seat-role-
|
|
|
|
|
|
// label` grid placement (col 2, row 1 by default; flips to col 1
|
|
|
|
|
|
// for left-side seats 3/4/5 so the label sits closest to the hex)
|
|
|
|
|
|
// but uses a role-free class name — my-sea is the solo draw flow,
|
|
|
|
|
|
// no role-pick phase, so the room's role-grammar doesn't apply.
|
|
|
|
|
|
.table-seat .seat-position-label {
|
|
|
|
|
|
grid-column: 2;
|
|
|
|
|
|
grid-row: 1;
|
2026-05-19 15:15:37 -04:00
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
|
font-weight: 600;
|
2026-05-19 15:48:07 -04:00
|
|
|
|
letter-spacing: 0.05em;
|
|
|
|
|
|
color: rgba(var(--secUser), 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
.table-seat[data-slot="3"] .seat-position-label,
|
|
|
|
|
|
.table-seat[data-slot="4"] .seat-position-label,
|
|
|
|
|
|
.table-seat[data-slot="5"] .seat-position-label {
|
|
|
|
|
|
grid-column: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Seated chair (post-FREE DRAW). Visual transition mirrors
|
|
|
|
|
|
// `.table-seat.active .fa-chair` from _room.scss line 626 —
|
|
|
|
|
|
// --terUser color + --ninUser drop-shadow glow — but uses a stable
|
|
|
|
|
|
// `.seated` class (semantically distinct from `.active`: active =
|
|
|
|
|
|
// current turn in a multi-user room; seated = draw-locked occupant
|
|
|
|
|
|
// in this solo-flow). _room.scss line 596 makes the colour change
|
|
|
|
|
|
// a 0.6s ease transition so the chair animates rather than snaps.
|
|
|
|
|
|
// Status icon (.position-status-icon) colour swap fa-ban red →
|
|
|
|
|
|
// fa-circle-check green is handled by _room.scss lines 615-616.
|
|
|
|
|
|
.table-seat.seated .fa-chair {
|
2026-05-19 15:15:37 -04:00
|
|
|
|
color: rgba(var(--terUser), 1);
|
2026-05-19 15:48:07 -04:00
|
|
|
|
filter: drop-shadow(0 0 4px rgba(var(--ninUser), 1));
|
2026-05-19 15:15:37 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-19 16:06:14 -04:00
|
|
|
|
// Picker phase bg — `--duoUser` matches the table hex's interior so
|
|
|
|
|
|
// the landing→picker swap reads as a continuous surface (parallels
|
|
|
|
|
|
// `.my-sign-page[data-phase="picker"]` in _card-deck.scss line 704).
|
|
|
|
|
|
.my-sea-page[data-phase="picker"] {
|
|
|
|
|
|
background: rgba(var(--duoUser), 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
My Sea iter 4c: drop LOCK HAND → AUTO DRAW + GATE VIEW; quota committed at first card draw (irrevocable); DEL clears hand but preserves row as quota tracker; per-placement /lock POST upsert; lazy stale-row cleanup; sig polarity + .btn-disabled → ×; landing aperture bg revert to --priUser — Sprint 5 iter 4c of My Sea roadmap — TDD
Major refactor of the iter-4b skeleton ahead of Sprint 6's token costs. Iter 4b's LOCK HAND model let users freely DEL + LOCK in a loop, bypassing the 1/day quota; iter 4c closes that loophole by committing quota at first-card-draw (manual via FLIP OR auto via AUTO DRAW) + preserving the MySeaDraw row through DEL so the 24h clock keeps running.
## Server
`MySeaDraw` now plays double-duty: hand storage AND 24h quota tracker.
- `HAND_SIZE_BY_SPREAD` module dict maps each spread slug to its expected hand size (mirrors DRAW_ORDER in JS).
- `is_hand_complete` / `is_hand_empty` props drive view branching + template button states.
- `delete_stale()` classmethod hard-deletes rows older than FREE_DRAW_COOLDOWN_HOURS. Called lazily from `active_draw_for` on every view access (rides user traffic; no scheduler needed) + via the new `delete_stale_my_sea_draws` management command (cron backstop).
- `active_draw_for` prunes user's stale rows before lookup — auto-cleanup at the 24h mark per user spec ("sink 'em all at the 24hr mark and reinstate the FREE DRAW btn").
`my_sea_lock` is now a true upsert:
- First POST creates the row (quota commit).
- Subsequent POSTs UPDATE the existing row's hand (per-placement cadence — server stays current so navigate-away mid-draw still persists).
- Spread-mismatch (attempted spread switch within quota window) → 409.
- Empty/malformed hand → 400.
- Response carries `{ok, next_free_draw_at, hand_complete}` for JS state transitions.
`my_sea_delete` no longer deletes the row — clears the `hand` JSON only. `created_at` preserved so landing renders GATE VIEW (not FREE DRAW) until the row expires. Idempotent.
`my_sea_gate` new stub view — returns 404 for now; lets the template wire up GATE VIEW button URLs in advance. Sprint 6 will replace this w. the gatekeeper token-deposit UX.
`my_sea` view branches:
1. No sig → sign-gate
2. Active draw + non-empty hand (mid or complete) → picker phase w. saved hand
3. Active draw + empty hand (post-DEL) → landing phase w. GATE VIEW btn
4. No active draw → landing phase w. FREE DRAW btn
## Template + UX
- Picker form col: removed LOCK HAND. Replaced w. `#id_sea_action_btn` — same DOM node, label + behavior keyed on `data-state`:
- `auto-draw` → label "AUTO DRAW"; click opens shared guard portal ("Auto deal cards?"); OK → fill remaining slots client-side + single-POST commit to server (per user spec: "commit all six draws in the same POST" so navigate-away mid-animation still persists).
- `gate-view` → label "GATE VIEW"; click navigates to /gameboard/my-sea/gate/ (Sprint 6).
- JS transitions auto-draw → gate-view automatically when the hand fills (via FLIP or AUTO DRAW completion).
- DEL btn: server-renders `.btn-disabled` pre-completion (per spec, the 1/day quota commits at first-card-draw — can't be refunded by an early DEL). JS removes `.btn-disabled` on hand completion. Post-completion click opens the shared guard portal; CONFIRM POSTs the delete endpoint (which clears hand server-side) + reloads to GATE VIEW landing.
- Deck stacks remain click-responsive post-completion so the user sees the disabled-FLIP feedback (signalling "no more draws"); the FLIP click is gated on `_locked` flag.
- Landing: primary nav btn is FREE DRAW (no active draw) or GATE VIEW (active draw exists w. empty hand). Both render as `<button>` (not `<a>`) so the typography matches across states — `<a>`'s UA-default serif typeface was bleeding into GATE VIEW under iter 4b polish.
## Other polish bundled
- **Sig polarity rendered in picker** — added `.my-sea-page[data-polarity]` to the existing `.sig-overlay[data-polarity]` + `.my-sign-page[data-polarity]` selector list in `_card-deck.scss`. Template wires `data-polarity` on the page wrapper based on `significator_reversed`. Previously the picker's center sig card was always gravity-themed regardless of the user's actual sig polarity.
- **`.btn-disabled` → × overlay** — universal CSS rule: any `.btn-disabled` button reads as × regardless of its native inner text/icons (DEL → ×, FLIP → ×, etc.). Hides inner content via `visibility: hidden` on children + paints × via `::before` pseudo-element. Templates that already render `×` explicitly (don/doff toggle pairs) get the pseudo overlay on top of their hidden inner ×; no double-× regression.
- **Landing aperture bg → `--priUser`** — explicit override on `.my-sea-page[data-phase="landing"]` so any bf-cache / stale-CSS state can't leak the picker-phase `--duoUser` green bg onto a landing render. Per user spec (2026-05-20): "Keep --duoUser on the hex, not on the aperture bg."
- **Dynamic combobox state** — `aria-selected` + `.sea-select-current` visible label both branch on `default_spread` (previously hardcoded SAO). Matters when the saved spread is non-SAO (e.g., Celtic Cross resumed mid-draw).
## Test coverage
- ITs (1100 IT/UT green in 57s):
- `MySeaDrawModelTest` — `is_hand_complete`, `is_hand_empty`, `delete_stale`, lazy cleanup in `active_draw_for`.
- `MySeaLockHandViewTest` — upsert same-row (rewrote 409 test), spread-mismatch 409, hand_complete flag in response.
- `MySeaDeleteDrawViewTest` — clears hand but preserves row (rewrote "deletes row" test).
- `MySeaViewWithSavedDrawTest` — picker w. complete hand renders GATE VIEW state.
- `MySeaViewWithEmptyHandTest` (new) — empty-hand post-DEL renders landing w. GATE VIEW btn, no FREE DRAW.
- `MySeaViewWithPartialHandTest` (new) — partial-hand renders picker w. AUTO DRAW + DEL btn-disabled.
- `MySeaGateStubViewTest` (new) — 404 stub + login required.
- FTs (35 my_sea FTs green in 5m):
- Iter-4b `test_del_confirm_clears_saved_draw_and_returns_to_landing` rewrote → `test_del_confirm_clears_hand_and_returns_to_gate_view_landing` (row preserved, landing renders GATE VIEW).
- Iter-4a `test_lock_hand_enables_when_sao_hand_is_complete` → `test_action_btn_transitions_to_gate_view_on_hand_complete`.
- Iter-4a `test_del_click_resets_hand_and_disables_lock_hand` → `test_del_btn_is_disabled_until_hand_complete`.
- Iter-4a `test_lock_hand_click_disables_further_interaction` → `test_hand_completion_locks_picker_state` (no LOCK HAND click; transition is automatic).
- Iter-4a `test_first_draw_locks_spread_combobox` trimmed — DEL no longer unlocks (DEL is `.btn-disabled` pre-completion).
- Iter-4a `test_form_col_renders_decks_lock_hand_del_and_reversal_pct` → action btn + DEL btn-disabled assertions.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 01:34:03 -04:00
|
|
|
|
// Landing phase bg — explicit `--priUser` revert per user spec
|
|
|
|
|
|
// (2026-05-20). The hex INTERIOR is `--duoUser` (set on `.table-hex`
|
|
|
|
|
|
// in _room.scss); the aperture AROUND the hex should be the default
|
|
|
|
|
|
// body color. Defensive override so any bf-cache / stale-CSS state
|
|
|
|
|
|
// can't leak the picker-phase green bg onto a landing render.
|
|
|
|
|
|
.my-sea-page[data-phase="landing"] {
|
|
|
|
|
|
background: rgba(var(--priUser), 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
My Sea iter 6a: gatekeeper page + INSERT/REFUND/PAID DRAW endpoints + MySeaDraw deposit fields + `_select_my_sea_token` / `debit_my_sea_token` helpers (CARTE blocked, COIN 24h cooldown not 7-day) + Sprint 6 FT skeleton — Sprint 5 iter 6a of My Sea roadmap — TDD
First of three Sprint 6 commits per [[sprint-my-sea-iter-6-plan]]. Replaces the iter-4c 404 stub at `/gameboard/my-sea/gate/` w. a real token-deposit-to-redraw UI. Iter 6b will wire the navbar GATE VIEW swap + landing PAID DRAW state + seat-1 persistence; iter 6c will land the bud-btn stub.
## Server
`MySeaDraw` gains two fields: `deposit_token_id` (int, nullable) + `deposit_reserved_at` (datetime, nullable). Migration 0002. The row plays triple duty now: hand storage + 24h quota tracker + deposit reservation slot.
`_select_my_sea_token(user)` mirrors `apps.epic.models.select_token` priority (PASS > COIN > FREE > TITHE) w. two adaptations:
- CARTE excluded outright (door-spell trinket, not valid for my-sea draws).
- COIN cooldown-respecting: filters out COINs w. `next_ready_at > now`. Standard `select_token` doesn't apply this filter — room logic unchanged.
`debit_my_sea_token(user, token)` is the my-sea variant of `apps.epic.models.debit_token`:
- CARTE → ValueError (defensive; caller validates upstream).
- COIN: `next_ready_at = now + 24h` (not 7-day room cycle) + unequip from kit if equipped.
- PASS: no consumption (auto-admit, unlimited redraws).
- FREE / TITHE: deleted.
`my_sea_gate` view replaces the 404 stub. Renders the gatekeeper template w. branching on `deposit_reserved` (token reserved on row vs not).
`my_sea_insert_token` POST: picks a token via `_select_my_sea_token` + sets `deposit_token_id + deposit_reserved_at`. Creates the row if missing (so a fresh user can deposit without first using their free draw). Idempotent w.r.t. an already-reserved deposit.
`my_sea_refund_token` POST: clears deposit fields. Token isn't consumed at INSERT (refund-aware design), so this is purely a row update — no inventory side effects.
`my_sea_paid_draw` POST: commits via `debit_my_sea_token` + resets row (hand=[], created_at=now, deposit fields cleared). Redirects to `/gameboard/my-sea/` for a fresh quota cycle.
## Template + UX
`apps/gameboard/my_sea_gate.html` (new) — per user spec 2026-05-20, the gatekeeper is a darkened-modal-over-`--duoUser` bg matching the room gatekeeper's chrome (`.gate-backdrop` + `.gate-overlay` + `.gate-modal`). No hex / chair-seats — those live on the my-sea picker page itself; the gatekeeper is a transient in-flight UI for token deposit.
Coin-slot rails (mirrors room's `.token-slot`):
- Pre-deposit: form-wrapped `.token-rails` button → POSTs to `my_sea_insert_token`. Coin-panel labels read INSERT TOKEN TO PLAY.
- Post-deposit: rails inert (no form); `.token-return-btn` form → POSTs to `my_sea_refund_token`. Coin-panel labels swap to PUSH TO RETURN.
- Post-deposit: PAID DRAW btn (`#id_my_sea_paid_draw_btn`, `.btn-primary`) → POSTs to `my_sea_paid_draw`. Mirrors the room's PICK ROLES btn shape.
SCSS minimal — page bg `rgba(--duoUser, 1)` on `.my-sea-page[data-phase="gate"]`; everything else reuses the room gatekeeper's existing rules.
## FT skeleton
Per user TDD directive (2026-05-20: "Also via TDD so if we run out we're adhering to FT-described behavior"), wrote the FULL Sprint 6 FT skeleton up front (covers iter 6a + 6b + 6c). Five new FT classes in `test_game_my_sea.py`:
- `MySeaGatekeeperPageTest` (5 tests) — iter 6a; pre-deposit / INSERT / REFUND / PAID DRAW paths.
- `MySeaLandingPaidDrawTest` (1 test) — iter 6b; landing renders PAID DRAW btn when deposit reserved (red until iter 6b lands).
- `MySeaNavbarGateViewTest` (1 test) — iter 6b; navbar GATE VIEW swap (red until iter 6b).
- `MySeaSeatOnePersistenceTest` (2 tests) — iter 6b; seat 1 banned for fresh user + empty-hand active draw (red until iter 6b).
- `MySeaBudBtnStubTest` (2 tests) — iter 6c; panel opens + OK shows coming-soon Brief (red until iter 6c).
## ITs (iter 6a — 22 new + 153 total green)
- `MySeaGateViewTest` (4) — view branching pre/post deposit.
- `MySeaInsertTokenViewTest` (4) — row creation, existing row, idempotency, GET=405.
- `MySeaRefundTokenViewTest` (3) — clears fields, no token consumption, idempotent.
- `MySeaPaidDrawViewTest` (6) — FREE consumed, COIN cooldown + unequip, PASS no-op, hand reset, created_at reset, redirect.
- `SelectMySeaTokenTest` (3) — CARTE excluded, COIN cooldown excluded, PASS priority for staff.
- `DebitMySeaTokenTest` (4) — CARTE ValueError, FREE/TITHE consumed, PASS preserved.
## Trap caught
Existing User `post_save` signal auto-creates COIN + FREE tokens (`apps.lyric.models:309`). Sprint 6 ITs that assert "user has only the token I seeded" must `self.user.tokens.all().delete()` after User.create. Without it, `_select_my_sea_token` returns the auto-COIN instead of None for the CARTE-excluded test. Worth a future feedback memory if it bites again.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 02:29:08 -04:00
|
|
|
|
// Sprint 6 iter 6a — gatekeeper page bg + modal chrome. The page bg
|
|
|
|
|
|
// is uniform `--duoUser` (matches the hex interior on landing /
|
|
|
|
|
|
// picker so the visual transitions read as a continuous surface);
|
|
|
|
|
|
// the `.gate-overlay`/`.gate-modal` rules in `_room.scss` already
|
|
|
|
|
|
// give us the darkened Gaussian-glass modal centered over it. No hex
|
|
|
|
|
|
// or chair-seats on this page — the gatekeeper is a transient in-
|
|
|
|
|
|
// flight UI per user spec 2026-05-20.
|
|
|
|
|
|
.my-sea-page[data-phase="gate"] {
|
|
|
|
|
|
background: rgba(var(--duoUser), 1);
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-19 15:15:37 -04:00
|
|
|
|
.my-sea-picker {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
My Sea form col + SPREAD dropdown w. 3-card/6-card section dividers — Sprint 5 iter 3 of My Sea roadmap — TDD
Picker phase form col: SPREAD combobox w. 6 spread options under 2 horizontal section dividers ("3-card spreads" / "6-card spreads"), reversal-% caption, GRAVITY + LEVITY deck swatches, LOCK HAND + DEL btns. Default = Situation, Action, Outcome (a 3-card spread). Selecting a 6-card spread (Celtic Cross Waite-Smith or Escape Velocity) swaps `.my-sea-cross[data-spread-shape]` from `three-card` to `six-card` — revealing the crown / lay / cross cells that the default 3-card variants hide.
Naming correction (user-locked): the spread itself is a "three-card spread" not a "three-card cross" — "cross" stays scoped to the Celtic Cross variants (6-card spreads). CSS class `.my-sea-cross` carries grid-container semantics regardless of which spread shape is active; the spread-vs-cross distinction lives at the spread-name layer only.
- **View** (gameboard/views.py): `my_sea` adds `default_spread = "situation-action-outcome"` + `reversals_pct = 25` context keys.
- **Template** (my_sea.html): renders all 7 cross cells (crown/leave/core+cover+cross/loom/lay) unconditionally + adds `data-spread-shape="three-card"` to `.my-sea-cross`. Form col DRY-reuses gameroom `_sea_overlay.html`'s `.sea-form-col` shape — `.sea-form-main` w. `.sea-field` (SPREAD label + reversal hint + custom combobox) + `.sea-stacks` (GRAVITY + LEVITY swatches) + `.sea-form-actions` (LOCK HAND + DEL). 6 options + 2 dividers in the combobox `<ul>`; dividers are `role="presentation"` so `combobox.js` skips them naturally. Inline IIFE listens for the hidden `<input id="id_sea_spread">`'s `change` event + sets `.my-sea-cross`'s `data-spread-shape` based on whether the value is in `['waite-smith', 'escape-velocity']`. No new combobox.js wiring — the existing module's `change`-bubbling contract feeds straight in.
- **SCSS** (_gameboard.scss):
- `.my-sea-cross[data-spread-shape="three-card"]` — single-row `"leave core loom"` grid + `display: none` on crown/lay/cross.
- `.my-sea-cross[data-spread-shape="six-card"]` — inherits the gameroom `.sea-cross`'s 3×3 grid + reveals all cells.
- `.sea-select-divider` — section header style mirrors `.kit-bag-label`'s small-uppercase-underlined-letter-spaced --quaUser/0.75 treatment but HORIZONTAL (kit-bag uses `writing-mode: vertical-rl`; dropdown menus are flat). `pointer-events: none` belt-and-braces against accidental click/hover.
- `.my-sea-form-col` — width-constrains the form col so the picker's cross + form sit side-by-side.
**Iter-2 contract updated** (cells in DOM, hidden via CSS for 3-card default):
- FT `test_picker_does_not_render_forsaken_positions` → renamed to `test_picker_hides_six_card_only_positions_by_default` — asserts the 3 cells are in the DOM but `is_displayed() == False` so iter-3's spread switch can reveal them via CSS without re-rendering.
- IT `test_picker_does_not_render_forsaken_positions` → renamed to `test_picker_renders_six_card_only_positions_for_spread_switch` — assertContains the classes (server now renders them unconditionally).
**Tests**:
- 4 FTs in new `MySeaSpreadFormTest`: combobox renders 6 options + 2 dividers w. correct labels, default is Situation/Action/Outcome (hidden input value + visible current-label span + cross's data-spread-shape), picking Celtic Cross flips data-spread-shape to six-card + reveals crown/lay/cross, form col carries DECKS swatches + LOCK HAND + DEL + reversal-% caption. Combobox `<li>` options are inside `aria-expanded='false'` listbox → use `get_attribute("textContent")` not `.text` (which returns "" for Selenium-hidden elements).
- 7 ITs in new `MySeaSpreadFormTemplateTest`: default_spread + reversals_pct context keys, all 6 options + both labels render, 2 dividers render w. expected text, default option carries aria-selected="true", cross's initial data-spread-shape="three-card", form col DECKS + buttons + reversal hint render.
Tests: 32/32 FT green across test_bill_my_sign + test_game_my_sea; 1048/1048 IT/UT green in 52s.
Card-draw mechanics (clicking a deck swatch deposits a card into the next empty slot; LOCK HAND commits the draw) defer to iter 4 — this iter ships the spread-selection + layout-shape switch UI; the buttons are stubs (LOCK HAND starts disabled, DEL is a placeholder).
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 17:23:25 -04:00
|
|
|
|
gap: 1rem;
|
2026-05-20 11:52:17 -04:00
|
|
|
|
|
|
|
|
|
|
// Portrait — stack the cross spread above the form col (mirrors the
|
|
|
|
|
|
// gameroom SEA SELECT modal's `@media (max-width: 600px)` stack
|
|
|
|
|
|
// pattern in `_card-deck.scss`). Landscape keeps the side-by-side
|
|
|
|
|
|
// layout since horizontal real-estate is the abundant axis there.
|
|
|
|
|
|
// User-spec 2026-05-20.
|
|
|
|
|
|
@media (orientation: portrait) {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
2026-05-19 15:15:37 -04:00
|
|
|
|
}
|
2026-05-19 16:06:14 -04:00
|
|
|
|
|
2026-05-19 19:38:53 -04:00
|
|
|
|
// .my-sea-cross renders all 6 surrounding positions (crown/leave/lay/
|
|
|
|
|
|
// loom + cover/cross overlaid on core) unconditionally. The SPREAD
|
|
|
|
|
|
// dropdown sets `data-spread="<name>"` on this element; per-spread
|
|
|
|
|
|
// rules below hide the positions each spread doesn't use. Inherits
|
|
|
|
|
|
// the 3×3 `grid-template-areas` from _card-deck.scss line 1189-1200
|
|
|
|
|
|
// so visible cells land in their canonical positions; hidden cells
|
|
|
|
|
|
// just leave their grid slots empty.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Per-spread position subsets — user-locked 2026-05-19:
|
|
|
|
|
|
// PPF: leave (1) cover (2) loom (3) — horizontal middle row
|
|
|
|
|
|
// SAO: lay (1) cover (2) crown (3) — vertical center column
|
|
|
|
|
|
// MBS: crown (1) lay (2) loom (3) — T-shape (crown + lay vertical, loom right)
|
My Sea iter-4a follow-up batch: modal port + draw polish + label positioning + Major Arcana fix — TDD
User-driven bug-squash + UX-polish cycle on top of iter 4a (ca2a62f). All 14 fixes ship behind the same iter-4a banner since they close the substage's UX gaps without expanding scope to iter 4b's persistence layer.
SeaDeal modal port — extracted apps/gameboard/_partials/_sea_stage.html shared by gameroom + my-sea; aliased .my-sea-picker w. id=id_sea_overlay so SeaDeal.init() finds it; FLIP click → SeaDeal.openStage delegation instead of bare _fillSlot. Fixes the user-reported 'thumbnail disappears' bug — slot was landing at opacity 0 (.--filled w.o .--visible) because SeaDeal's _hideStage (which adds --visible on modal dismiss) was never running. 3 new FTs cover the modal flow.
Spread lock + DEL reshuffle — _lockSpread/_unlockSpread toggle .sea-select--locked class on the combobox; first deposit locks, _resetHand unlocks. _reshuffleDeck Fisher-Yates over combined piles + re-rolls 25% reversal axis on DEL so successive DELs don't re-deal the same hand. Verified Claudezilla: 3 DEL cycles produced distinct lay cards (150 → 114 → 155).
Cover/cross empty slots — subtle dotted outline (transparent bg + 0.25 alpha border) w. --duoUser mask reveal on hover/touch. Per the user spec; rule lives in _card-deck.scss (shared between gameroom + my-sea). Plus matching label-opacity (0.25 idle → 0.6 hover) via CSS :hover ancestor propagation.
DOS spec — Solution moved from cover → crown per user correction. DRAW_ORDER ['loom', 'cross', 'crown']; POSITION_LABELS {loom: Desire, cross: Obstacle, crown: Solution}; SCSS hide list flipped from [leave, crown, lay] → [leave, cover, lay]; FT/IT assertions updated.
SAO → DOS soft-reload bug — Firefox autofill on hidden input restored the previous-session DOS value, tripping combobox.js's change-event guard. Fix: autocomplete=off + force-sync hidden.value from server-rendered aria-selected option in init. Captured as feedback_firefox_autofill_hidden_inputs (generalizable trap).
.sea-pos-label outside .sea-card-slot — moved label to be a sibling of the slot in the cell, so SeaDeal innerHTML clobber on draw doesn't erase it. Per-position absolute positioning touching slot borders: crown/cover above (translate -50%, 0.1rem, scaleY 1.2); lay/cross below (translate -50%, -0.1rem, scaleY 1.2); leave left, CCW (writing-mode vertical-rl + rotate 180deg + scaleX 1.2); loom right, CW (writing-mode vertical-rl + scaleX 1.2). scaleX for rotated labels (not scaleY) — perpendicular to text-flow is the visible-width direction after rotation. .my-sea-cross gap bumped to 1.75rem for label clearance.
Escape Velocity label swaps — POSITION_LABELS for escape-velocity: {crown: Crown, leave: Lay, cover: Cover, cross: Cross, loom: Loom, lay: Leave}. Replaces the Waite-Smith Behind/Beneath/Before per user spec.
SPREAD dropdown portal — .my-sea-form-col .sea-form-main { overflow: visible } + .sea-select-list { z-index: 1000 } so the dropdown extends past the form-main scroll area + sits above the picker stacking ints. Gameroom .sea-form-main still scrolls (only my-sea opts out).
Major Arcana polarity-split rendering — added 9 missing _card_dict keys to my-sea's _my_sea_deck_data to match gameroom epic.views.sea_deck's contract: levity_emanation, gravity_emanation, levity_reversal, gravity_reversal, italic_word, keywords_upright, keywords_reversed, energies, operations. Without these StageCard.populateCard falls through to plain name_title for trumps 19-21 + cards 48-49. Iter 4b cleanup candidate: extract apps.epic.utils.card_dict() to DRY the now-identical helpers.
Tests deferred — user explicitly belayed FT runs during the bug-fix substage. Iter 4b will re-establish a green sweep before its commit lands.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:02:27 -04:00
|
|
|
|
// DOS: loom (1) cross (2) crown (3) — loom right · cross overlay · crown above
|
2026-05-19 19:38:53 -04:00
|
|
|
|
// CC variants: all 6 positions (Waite-Smith / Escape Velocity differ in DRAW ORDER only,
|
|
|
|
|
|
// not in position visibility).
|
|
|
|
|
|
|
My Sea iter-4a follow-up batch: modal port + draw polish + label positioning + Major Arcana fix — TDD
User-driven bug-squash + UX-polish cycle on top of iter 4a (ca2a62f). All 14 fixes ship behind the same iter-4a banner since they close the substage's UX gaps without expanding scope to iter 4b's persistence layer.
SeaDeal modal port — extracted apps/gameboard/_partials/_sea_stage.html shared by gameroom + my-sea; aliased .my-sea-picker w. id=id_sea_overlay so SeaDeal.init() finds it; FLIP click → SeaDeal.openStage delegation instead of bare _fillSlot. Fixes the user-reported 'thumbnail disappears' bug — slot was landing at opacity 0 (.--filled w.o .--visible) because SeaDeal's _hideStage (which adds --visible on modal dismiss) was never running. 3 new FTs cover the modal flow.
Spread lock + DEL reshuffle — _lockSpread/_unlockSpread toggle .sea-select--locked class on the combobox; first deposit locks, _resetHand unlocks. _reshuffleDeck Fisher-Yates over combined piles + re-rolls 25% reversal axis on DEL so successive DELs don't re-deal the same hand. Verified Claudezilla: 3 DEL cycles produced distinct lay cards (150 → 114 → 155).
Cover/cross empty slots — subtle dotted outline (transparent bg + 0.25 alpha border) w. --duoUser mask reveal on hover/touch. Per the user spec; rule lives in _card-deck.scss (shared between gameroom + my-sea). Plus matching label-opacity (0.25 idle → 0.6 hover) via CSS :hover ancestor propagation.
DOS spec — Solution moved from cover → crown per user correction. DRAW_ORDER ['loom', 'cross', 'crown']; POSITION_LABELS {loom: Desire, cross: Obstacle, crown: Solution}; SCSS hide list flipped from [leave, crown, lay] → [leave, cover, lay]; FT/IT assertions updated.
SAO → DOS soft-reload bug — Firefox autofill on hidden input restored the previous-session DOS value, tripping combobox.js's change-event guard. Fix: autocomplete=off + force-sync hidden.value from server-rendered aria-selected option in init. Captured as feedback_firefox_autofill_hidden_inputs (generalizable trap).
.sea-pos-label outside .sea-card-slot — moved label to be a sibling of the slot in the cell, so SeaDeal innerHTML clobber on draw doesn't erase it. Per-position absolute positioning touching slot borders: crown/cover above (translate -50%, 0.1rem, scaleY 1.2); lay/cross below (translate -50%, -0.1rem, scaleY 1.2); leave left, CCW (writing-mode vertical-rl + rotate 180deg + scaleX 1.2); loom right, CW (writing-mode vertical-rl + scaleX 1.2). scaleX for rotated labels (not scaleY) — perpendicular to text-flow is the visible-width direction after rotation. .my-sea-cross gap bumped to 1.75rem for label clearance.
Escape Velocity label swaps — POSITION_LABELS for escape-velocity: {crown: Crown, leave: Lay, cover: Cover, cross: Cross, loom: Loom, lay: Leave}. Replaces the Waite-Smith Behind/Beneath/Before per user spec.
SPREAD dropdown portal — .my-sea-form-col .sea-form-main { overflow: visible } + .sea-select-list { z-index: 1000 } so the dropdown extends past the form-main scroll area + sits above the picker stacking ints. Gameroom .sea-form-main still scrolls (only my-sea opts out).
Major Arcana polarity-split rendering — added 9 missing _card_dict keys to my-sea's _my_sea_deck_data to match gameroom epic.views.sea_deck's contract: levity_emanation, gravity_emanation, levity_reversal, gravity_reversal, italic_word, keywords_upright, keywords_reversed, energies, operations. Without these StageCard.populateCard falls through to plain name_title for trumps 19-21 + cards 48-49. Iter 4b cleanup candidate: extract apps.epic.utils.card_dict() to DRY the now-identical helpers.
Tests deferred — user explicitly belayed FT runs during the bug-fix substage. Iter 4b will re-establish a green sweep before its commit lands.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:02:27 -04:00
|
|
|
|
// Bump grid gap on my-sea (gameroom .sea-cross stays at 0.5rem since
|
|
|
|
|
|
// gameroom slots have no per-position labels). The vertical leave/loom
|
|
|
|
|
|
// labels need ~1.5rem of horizontal clearance from adjacent cells, and
|
|
|
|
|
|
// the horizontal crown/cover/lay/cross labels need ~1rem of vertical
|
|
|
|
|
|
// clearance so they don't overlap into the next row.
|
|
|
|
|
|
.my-sea-cross {
|
|
|
|
|
|
gap: 1rem !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-19 19:38:53 -04:00
|
|
|
|
.my-sea-cross[data-spread="past-present-future"] {
|
My Sea form col + SPREAD dropdown w. 3-card/6-card section dividers — Sprint 5 iter 3 of My Sea roadmap — TDD
Picker phase form col: SPREAD combobox w. 6 spread options under 2 horizontal section dividers ("3-card spreads" / "6-card spreads"), reversal-% caption, GRAVITY + LEVITY deck swatches, LOCK HAND + DEL btns. Default = Situation, Action, Outcome (a 3-card spread). Selecting a 6-card spread (Celtic Cross Waite-Smith or Escape Velocity) swaps `.my-sea-cross[data-spread-shape]` from `three-card` to `six-card` — revealing the crown / lay / cross cells that the default 3-card variants hide.
Naming correction (user-locked): the spread itself is a "three-card spread" not a "three-card cross" — "cross" stays scoped to the Celtic Cross variants (6-card spreads). CSS class `.my-sea-cross` carries grid-container semantics regardless of which spread shape is active; the spread-vs-cross distinction lives at the spread-name layer only.
- **View** (gameboard/views.py): `my_sea` adds `default_spread = "situation-action-outcome"` + `reversals_pct = 25` context keys.
- **Template** (my_sea.html): renders all 7 cross cells (crown/leave/core+cover+cross/loom/lay) unconditionally + adds `data-spread-shape="three-card"` to `.my-sea-cross`. Form col DRY-reuses gameroom `_sea_overlay.html`'s `.sea-form-col` shape — `.sea-form-main` w. `.sea-field` (SPREAD label + reversal hint + custom combobox) + `.sea-stacks` (GRAVITY + LEVITY swatches) + `.sea-form-actions` (LOCK HAND + DEL). 6 options + 2 dividers in the combobox `<ul>`; dividers are `role="presentation"` so `combobox.js` skips them naturally. Inline IIFE listens for the hidden `<input id="id_sea_spread">`'s `change` event + sets `.my-sea-cross`'s `data-spread-shape` based on whether the value is in `['waite-smith', 'escape-velocity']`. No new combobox.js wiring — the existing module's `change`-bubbling contract feeds straight in.
- **SCSS** (_gameboard.scss):
- `.my-sea-cross[data-spread-shape="three-card"]` — single-row `"leave core loom"` grid + `display: none` on crown/lay/cross.
- `.my-sea-cross[data-spread-shape="six-card"]` — inherits the gameroom `.sea-cross`'s 3×3 grid + reveals all cells.
- `.sea-select-divider` — section header style mirrors `.kit-bag-label`'s small-uppercase-underlined-letter-spaced --quaUser/0.75 treatment but HORIZONTAL (kit-bag uses `writing-mode: vertical-rl`; dropdown menus are flat). `pointer-events: none` belt-and-braces against accidental click/hover.
- `.my-sea-form-col` — width-constrains the form col so the picker's cross + form sit side-by-side.
**Iter-2 contract updated** (cells in DOM, hidden via CSS for 3-card default):
- FT `test_picker_does_not_render_forsaken_positions` → renamed to `test_picker_hides_six_card_only_positions_by_default` — asserts the 3 cells are in the DOM but `is_displayed() == False` so iter-3's spread switch can reveal them via CSS without re-rendering.
- IT `test_picker_does_not_render_forsaken_positions` → renamed to `test_picker_renders_six_card_only_positions_for_spread_switch` — assertContains the classes (server now renders them unconditionally).
**Tests**:
- 4 FTs in new `MySeaSpreadFormTest`: combobox renders 6 options + 2 dividers w. correct labels, default is Situation/Action/Outcome (hidden input value + visible current-label span + cross's data-spread-shape), picking Celtic Cross flips data-spread-shape to six-card + reveals crown/lay/cross, form col carries DECKS swatches + LOCK HAND + DEL + reversal-% caption. Combobox `<li>` options are inside `aria-expanded='false'` listbox → use `get_attribute("textContent")` not `.text` (which returns "" for Selenium-hidden elements).
- 7 ITs in new `MySeaSpreadFormTemplateTest`: default_spread + reversals_pct context keys, all 6 options + both labels render, 2 dividers render w. expected text, default option carries aria-selected="true", cross's initial data-spread-shape="three-card", form col DECKS + buttons + reversal hint render.
Tests: 32/32 FT green across test_bill_my_sign + test_game_my_sea; 1048/1048 IT/UT green in 52s.
Card-draw mechanics (clicking a deck swatch deposits a card into the next empty slot; LOCK HAND commits the draw) defer to iter 4 — this iter ships the spread-selection + layout-shape switch UI; the buttons are stubs (LOCK HAND starts disabled, DEL is a placeholder).
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 17:23:25 -04:00
|
|
|
|
.sea-pos-crown,
|
2026-05-19 19:38:53 -04:00
|
|
|
|
.sea-pos-cross,
|
|
|
|
|
|
.sea-pos-lay { display: none; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.my-sea-cross[data-spread="situation-action-outcome"] {
|
|
|
|
|
|
.sea-pos-leave,
|
|
|
|
|
|
.sea-pos-loom,
|
|
|
|
|
|
.sea-pos-cross { display: none; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.my-sea-cross[data-spread="mind-body-spirit"] {
|
|
|
|
|
|
.sea-pos-leave,
|
|
|
|
|
|
.sea-pos-cover,
|
My Sea form col + SPREAD dropdown w. 3-card/6-card section dividers — Sprint 5 iter 3 of My Sea roadmap — TDD
Picker phase form col: SPREAD combobox w. 6 spread options under 2 horizontal section dividers ("3-card spreads" / "6-card spreads"), reversal-% caption, GRAVITY + LEVITY deck swatches, LOCK HAND + DEL btns. Default = Situation, Action, Outcome (a 3-card spread). Selecting a 6-card spread (Celtic Cross Waite-Smith or Escape Velocity) swaps `.my-sea-cross[data-spread-shape]` from `three-card` to `six-card` — revealing the crown / lay / cross cells that the default 3-card variants hide.
Naming correction (user-locked): the spread itself is a "three-card spread" not a "three-card cross" — "cross" stays scoped to the Celtic Cross variants (6-card spreads). CSS class `.my-sea-cross` carries grid-container semantics regardless of which spread shape is active; the spread-vs-cross distinction lives at the spread-name layer only.
- **View** (gameboard/views.py): `my_sea` adds `default_spread = "situation-action-outcome"` + `reversals_pct = 25` context keys.
- **Template** (my_sea.html): renders all 7 cross cells (crown/leave/core+cover+cross/loom/lay) unconditionally + adds `data-spread-shape="three-card"` to `.my-sea-cross`. Form col DRY-reuses gameroom `_sea_overlay.html`'s `.sea-form-col` shape — `.sea-form-main` w. `.sea-field` (SPREAD label + reversal hint + custom combobox) + `.sea-stacks` (GRAVITY + LEVITY swatches) + `.sea-form-actions` (LOCK HAND + DEL). 6 options + 2 dividers in the combobox `<ul>`; dividers are `role="presentation"` so `combobox.js` skips them naturally. Inline IIFE listens for the hidden `<input id="id_sea_spread">`'s `change` event + sets `.my-sea-cross`'s `data-spread-shape` based on whether the value is in `['waite-smith', 'escape-velocity']`. No new combobox.js wiring — the existing module's `change`-bubbling contract feeds straight in.
- **SCSS** (_gameboard.scss):
- `.my-sea-cross[data-spread-shape="three-card"]` — single-row `"leave core loom"` grid + `display: none` on crown/lay/cross.
- `.my-sea-cross[data-spread-shape="six-card"]` — inherits the gameroom `.sea-cross`'s 3×3 grid + reveals all cells.
- `.sea-select-divider` — section header style mirrors `.kit-bag-label`'s small-uppercase-underlined-letter-spaced --quaUser/0.75 treatment but HORIZONTAL (kit-bag uses `writing-mode: vertical-rl`; dropdown menus are flat). `pointer-events: none` belt-and-braces against accidental click/hover.
- `.my-sea-form-col` — width-constrains the form col so the picker's cross + form sit side-by-side.
**Iter-2 contract updated** (cells in DOM, hidden via CSS for 3-card default):
- FT `test_picker_does_not_render_forsaken_positions` → renamed to `test_picker_hides_six_card_only_positions_by_default` — asserts the 3 cells are in the DOM but `is_displayed() == False` so iter-3's spread switch can reveal them via CSS without re-rendering.
- IT `test_picker_does_not_render_forsaken_positions` → renamed to `test_picker_renders_six_card_only_positions_for_spread_switch` — assertContains the classes (server now renders them unconditionally).
**Tests**:
- 4 FTs in new `MySeaSpreadFormTest`: combobox renders 6 options + 2 dividers w. correct labels, default is Situation/Action/Outcome (hidden input value + visible current-label span + cross's data-spread-shape), picking Celtic Cross flips data-spread-shape to six-card + reveals crown/lay/cross, form col carries DECKS swatches + LOCK HAND + DEL + reversal-% caption. Combobox `<li>` options are inside `aria-expanded='false'` listbox → use `get_attribute("textContent")` not `.text` (which returns "" for Selenium-hidden elements).
- 7 ITs in new `MySeaSpreadFormTemplateTest`: default_spread + reversals_pct context keys, all 6 options + both labels render, 2 dividers render w. expected text, default option carries aria-selected="true", cross's initial data-spread-shape="three-card", form col DECKS + buttons + reversal hint render.
Tests: 32/32 FT green across test_bill_my_sign + test_game_my_sea; 1048/1048 IT/UT green in 52s.
Card-draw mechanics (clicking a deck swatch deposits a card into the next empty slot; LOCK HAND commits the draw) defer to iter 4 — this iter ships the spread-selection + layout-shape switch UI; the buttons are stubs (LOCK HAND starts disabled, DEL is a placeholder).
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 17:23:25 -04:00
|
|
|
|
.sea-pos-cross { display: none; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-19 19:38:53 -04:00
|
|
|
|
.my-sea-cross[data-spread="desire-obstacle-solution"] {
|
|
|
|
|
|
.sea-pos-leave,
|
My Sea iter-4a follow-up batch: modal port + draw polish + label positioning + Major Arcana fix — TDD
User-driven bug-squash + UX-polish cycle on top of iter 4a (ca2a62f). All 14 fixes ship behind the same iter-4a banner since they close the substage's UX gaps without expanding scope to iter 4b's persistence layer.
SeaDeal modal port — extracted apps/gameboard/_partials/_sea_stage.html shared by gameroom + my-sea; aliased .my-sea-picker w. id=id_sea_overlay so SeaDeal.init() finds it; FLIP click → SeaDeal.openStage delegation instead of bare _fillSlot. Fixes the user-reported 'thumbnail disappears' bug — slot was landing at opacity 0 (.--filled w.o .--visible) because SeaDeal's _hideStage (which adds --visible on modal dismiss) was never running. 3 new FTs cover the modal flow.
Spread lock + DEL reshuffle — _lockSpread/_unlockSpread toggle .sea-select--locked class on the combobox; first deposit locks, _resetHand unlocks. _reshuffleDeck Fisher-Yates over combined piles + re-rolls 25% reversal axis on DEL so successive DELs don't re-deal the same hand. Verified Claudezilla: 3 DEL cycles produced distinct lay cards (150 → 114 → 155).
Cover/cross empty slots — subtle dotted outline (transparent bg + 0.25 alpha border) w. --duoUser mask reveal on hover/touch. Per the user spec; rule lives in _card-deck.scss (shared between gameroom + my-sea). Plus matching label-opacity (0.25 idle → 0.6 hover) via CSS :hover ancestor propagation.
DOS spec — Solution moved from cover → crown per user correction. DRAW_ORDER ['loom', 'cross', 'crown']; POSITION_LABELS {loom: Desire, cross: Obstacle, crown: Solution}; SCSS hide list flipped from [leave, crown, lay] → [leave, cover, lay]; FT/IT assertions updated.
SAO → DOS soft-reload bug — Firefox autofill on hidden input restored the previous-session DOS value, tripping combobox.js's change-event guard. Fix: autocomplete=off + force-sync hidden.value from server-rendered aria-selected option in init. Captured as feedback_firefox_autofill_hidden_inputs (generalizable trap).
.sea-pos-label outside .sea-card-slot — moved label to be a sibling of the slot in the cell, so SeaDeal innerHTML clobber on draw doesn't erase it. Per-position absolute positioning touching slot borders: crown/cover above (translate -50%, 0.1rem, scaleY 1.2); lay/cross below (translate -50%, -0.1rem, scaleY 1.2); leave left, CCW (writing-mode vertical-rl + rotate 180deg + scaleX 1.2); loom right, CW (writing-mode vertical-rl + scaleX 1.2). scaleX for rotated labels (not scaleY) — perpendicular to text-flow is the visible-width direction after rotation. .my-sea-cross gap bumped to 1.75rem for label clearance.
Escape Velocity label swaps — POSITION_LABELS for escape-velocity: {crown: Crown, leave: Lay, cover: Cover, cross: Cross, loom: Loom, lay: Leave}. Replaces the Waite-Smith Behind/Beneath/Before per user spec.
SPREAD dropdown portal — .my-sea-form-col .sea-form-main { overflow: visible } + .sea-select-list { z-index: 1000 } so the dropdown extends past the form-main scroll area + sits above the picker stacking ints. Gameroom .sea-form-main still scrolls (only my-sea opts out).
Major Arcana polarity-split rendering — added 9 missing _card_dict keys to my-sea's _my_sea_deck_data to match gameroom epic.views.sea_deck's contract: levity_emanation, gravity_emanation, levity_reversal, gravity_reversal, italic_word, keywords_upright, keywords_reversed, energies, operations. Without these StageCard.populateCard falls through to plain name_title for trumps 19-21 + cards 48-49. Iter 4b cleanup candidate: extract apps.epic.utils.card_dict() to DRY the now-identical helpers.
Tests deferred — user explicitly belayed FT runs during the bug-fix substage. Iter 4b will re-establish a green sweep before its commit lands.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:02:27 -04:00
|
|
|
|
.sea-pos-cover,
|
2026-05-19 19:38:53 -04:00
|
|
|
|
.sea-pos-lay { display: none; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Celtic Cross variants (waite-smith / escape-velocity) — all positions
|
|
|
|
|
|
// visible by default. No `display: none` overrides needed.
|
|
|
|
|
|
|
My Sea iter-4a follow-up batch: modal port + draw polish + label positioning + Major Arcana fix — TDD
User-driven bug-squash + UX-polish cycle on top of iter 4a (ca2a62f). All 14 fixes ship behind the same iter-4a banner since they close the substage's UX gaps without expanding scope to iter 4b's persistence layer.
SeaDeal modal port — extracted apps/gameboard/_partials/_sea_stage.html shared by gameroom + my-sea; aliased .my-sea-picker w. id=id_sea_overlay so SeaDeal.init() finds it; FLIP click → SeaDeal.openStage delegation instead of bare _fillSlot. Fixes the user-reported 'thumbnail disappears' bug — slot was landing at opacity 0 (.--filled w.o .--visible) because SeaDeal's _hideStage (which adds --visible on modal dismiss) was never running. 3 new FTs cover the modal flow.
Spread lock + DEL reshuffle — _lockSpread/_unlockSpread toggle .sea-select--locked class on the combobox; first deposit locks, _resetHand unlocks. _reshuffleDeck Fisher-Yates over combined piles + re-rolls 25% reversal axis on DEL so successive DELs don't re-deal the same hand. Verified Claudezilla: 3 DEL cycles produced distinct lay cards (150 → 114 → 155).
Cover/cross empty slots — subtle dotted outline (transparent bg + 0.25 alpha border) w. --duoUser mask reveal on hover/touch. Per the user spec; rule lives in _card-deck.scss (shared between gameroom + my-sea). Plus matching label-opacity (0.25 idle → 0.6 hover) via CSS :hover ancestor propagation.
DOS spec — Solution moved from cover → crown per user correction. DRAW_ORDER ['loom', 'cross', 'crown']; POSITION_LABELS {loom: Desire, cross: Obstacle, crown: Solution}; SCSS hide list flipped from [leave, crown, lay] → [leave, cover, lay]; FT/IT assertions updated.
SAO → DOS soft-reload bug — Firefox autofill on hidden input restored the previous-session DOS value, tripping combobox.js's change-event guard. Fix: autocomplete=off + force-sync hidden.value from server-rendered aria-selected option in init. Captured as feedback_firefox_autofill_hidden_inputs (generalizable trap).
.sea-pos-label outside .sea-card-slot — moved label to be a sibling of the slot in the cell, so SeaDeal innerHTML clobber on draw doesn't erase it. Per-position absolute positioning touching slot borders: crown/cover above (translate -50%, 0.1rem, scaleY 1.2); lay/cross below (translate -50%, -0.1rem, scaleY 1.2); leave left, CCW (writing-mode vertical-rl + rotate 180deg + scaleX 1.2); loom right, CW (writing-mode vertical-rl + scaleX 1.2). scaleX for rotated labels (not scaleY) — perpendicular to text-flow is the visible-width direction after rotation. .my-sea-cross gap bumped to 1.75rem for label clearance.
Escape Velocity label swaps — POSITION_LABELS for escape-velocity: {crown: Crown, leave: Lay, cover: Cover, cross: Cross, loom: Loom, lay: Leave}. Replaces the Waite-Smith Behind/Beneath/Before per user spec.
SPREAD dropdown portal — .my-sea-form-col .sea-form-main { overflow: visible } + .sea-select-list { z-index: 1000 } so the dropdown extends past the form-main scroll area + sits above the picker stacking ints. Gameroom .sea-form-main still scrolls (only my-sea opts out).
Major Arcana polarity-split rendering — added 9 missing _card_dict keys to my-sea's _my_sea_deck_data to match gameroom epic.views.sea_deck's contract: levity_emanation, gravity_emanation, levity_reversal, gravity_reversal, italic_word, keywords_upright, keywords_reversed, energies, operations. Without these StageCard.populateCard falls through to plain name_title for trumps 19-21 + cards 48-49. Iter 4b cleanup candidate: extract apps.epic.utils.card_dict() to DRY the now-identical helpers.
Tests deferred — user explicitly belayed FT runs during the bug-fix substage. Iter 4b will re-establish a green sweep before its commit lands.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:02:27 -04:00
|
|
|
|
// Position-name caption — re-appropriates the GRAVITY/LEVITY
|
|
|
|
|
|
// `.sea-stack-name` typographic look (_card-deck.scss line 1557):
|
|
|
|
|
|
// small uppercase letter-spaced w. a subtle scaleY stretch,
|
|
|
|
|
|
// --terUser ink at 0.6 opacity. No polarity coloring — these are
|
|
|
|
|
|
// spread-position labels, not deck identifiers.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Labels live OUTSIDE the .sea-card-slot (sibling, inside the crucifix
|
|
|
|
|
|
// cell or the cover/cross wrapper) so they survive SeaDeal._fillSlot's
|
|
|
|
|
|
// `slot.innerHTML = …` clobber on draw. Each label is absolute-
|
|
|
|
|
|
// positioned to nearly touch the slot's nearest border per the user-
|
|
|
|
|
|
// locked spec:
|
|
|
|
|
|
// crown / cover — above top border
|
|
|
|
|
|
// lay / cross — below bottom border
|
|
|
|
|
|
// leave — left of left border, rotated 90° CCW
|
|
|
|
|
|
// loom — right of right border, rotated 90° CW
|
2026-05-19 19:38:53 -04:00
|
|
|
|
.sea-pos-label {
|
|
|
|
|
|
font-size: 0.65rem;
|
|
|
|
|
|
letter-spacing: 0.08em;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
font-weight: 600;
|
My Sea iter-4a follow-up batch: modal port + draw polish + label positioning + Major Arcana fix — TDD
User-driven bug-squash + UX-polish cycle on top of iter 4a (ca2a62f). All 14 fixes ship behind the same iter-4a banner since they close the substage's UX gaps without expanding scope to iter 4b's persistence layer.
SeaDeal modal port — extracted apps/gameboard/_partials/_sea_stage.html shared by gameroom + my-sea; aliased .my-sea-picker w. id=id_sea_overlay so SeaDeal.init() finds it; FLIP click → SeaDeal.openStage delegation instead of bare _fillSlot. Fixes the user-reported 'thumbnail disappears' bug — slot was landing at opacity 0 (.--filled w.o .--visible) because SeaDeal's _hideStage (which adds --visible on modal dismiss) was never running. 3 new FTs cover the modal flow.
Spread lock + DEL reshuffle — _lockSpread/_unlockSpread toggle .sea-select--locked class on the combobox; first deposit locks, _resetHand unlocks. _reshuffleDeck Fisher-Yates over combined piles + re-rolls 25% reversal axis on DEL so successive DELs don't re-deal the same hand. Verified Claudezilla: 3 DEL cycles produced distinct lay cards (150 → 114 → 155).
Cover/cross empty slots — subtle dotted outline (transparent bg + 0.25 alpha border) w. --duoUser mask reveal on hover/touch. Per the user spec; rule lives in _card-deck.scss (shared between gameroom + my-sea). Plus matching label-opacity (0.25 idle → 0.6 hover) via CSS :hover ancestor propagation.
DOS spec — Solution moved from cover → crown per user correction. DRAW_ORDER ['loom', 'cross', 'crown']; POSITION_LABELS {loom: Desire, cross: Obstacle, crown: Solution}; SCSS hide list flipped from [leave, crown, lay] → [leave, cover, lay]; FT/IT assertions updated.
SAO → DOS soft-reload bug — Firefox autofill on hidden input restored the previous-session DOS value, tripping combobox.js's change-event guard. Fix: autocomplete=off + force-sync hidden.value from server-rendered aria-selected option in init. Captured as feedback_firefox_autofill_hidden_inputs (generalizable trap).
.sea-pos-label outside .sea-card-slot — moved label to be a sibling of the slot in the cell, so SeaDeal innerHTML clobber on draw doesn't erase it. Per-position absolute positioning touching slot borders: crown/cover above (translate -50%, 0.1rem, scaleY 1.2); lay/cross below (translate -50%, -0.1rem, scaleY 1.2); leave left, CCW (writing-mode vertical-rl + rotate 180deg + scaleX 1.2); loom right, CW (writing-mode vertical-rl + scaleX 1.2). scaleX for rotated labels (not scaleY) — perpendicular to text-flow is the visible-width direction after rotation. .my-sea-cross gap bumped to 1.75rem for label clearance.
Escape Velocity label swaps — POSITION_LABELS for escape-velocity: {crown: Crown, leave: Lay, cover: Cover, cross: Cross, loom: Loom, lay: Leave}. Replaces the Waite-Smith Behind/Beneath/Before per user spec.
SPREAD dropdown portal — .my-sea-form-col .sea-form-main { overflow: visible } + .sea-select-list { z-index: 1000 } so the dropdown extends past the form-main scroll area + sits above the picker stacking ints. Gameroom .sea-form-main still scrolls (only my-sea opts out).
Major Arcana polarity-split rendering — added 9 missing _card_dict keys to my-sea's _my_sea_deck_data to match gameroom epic.views.sea_deck's contract: levity_emanation, gravity_emanation, levity_reversal, gravity_reversal, italic_word, keywords_upright, keywords_reversed, energies, operations. Without these StageCard.populateCard falls through to plain name_title for trumps 19-21 + cards 48-49. Iter 4b cleanup candidate: extract apps.epic.utils.card_dict() to DRY the now-identical helpers.
Tests deferred — user explicitly belayed FT runs during the bug-fix substage. Iter 4b will re-establish a green sweep before its commit lands.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:02:27 -04:00
|
|
|
|
opacity: 1;
|
|
|
|
|
|
color: rgba(var(--seciUser), 1);
|
2026-05-19 19:38:53 -04:00
|
|
|
|
text-align: center;
|
|
|
|
|
|
pointer-events: none;
|
My Sea iter-4a follow-up batch: modal port + draw polish + label positioning + Major Arcana fix — TDD
User-driven bug-squash + UX-polish cycle on top of iter 4a (ca2a62f). All 14 fixes ship behind the same iter-4a banner since they close the substage's UX gaps without expanding scope to iter 4b's persistence layer.
SeaDeal modal port — extracted apps/gameboard/_partials/_sea_stage.html shared by gameroom + my-sea; aliased .my-sea-picker w. id=id_sea_overlay so SeaDeal.init() finds it; FLIP click → SeaDeal.openStage delegation instead of bare _fillSlot. Fixes the user-reported 'thumbnail disappears' bug — slot was landing at opacity 0 (.--filled w.o .--visible) because SeaDeal's _hideStage (which adds --visible on modal dismiss) was never running. 3 new FTs cover the modal flow.
Spread lock + DEL reshuffle — _lockSpread/_unlockSpread toggle .sea-select--locked class on the combobox; first deposit locks, _resetHand unlocks. _reshuffleDeck Fisher-Yates over combined piles + re-rolls 25% reversal axis on DEL so successive DELs don't re-deal the same hand. Verified Claudezilla: 3 DEL cycles produced distinct lay cards (150 → 114 → 155).
Cover/cross empty slots — subtle dotted outline (transparent bg + 0.25 alpha border) w. --duoUser mask reveal on hover/touch. Per the user spec; rule lives in _card-deck.scss (shared between gameroom + my-sea). Plus matching label-opacity (0.25 idle → 0.6 hover) via CSS :hover ancestor propagation.
DOS spec — Solution moved from cover → crown per user correction. DRAW_ORDER ['loom', 'cross', 'crown']; POSITION_LABELS {loom: Desire, cross: Obstacle, crown: Solution}; SCSS hide list flipped from [leave, crown, lay] → [leave, cover, lay]; FT/IT assertions updated.
SAO → DOS soft-reload bug — Firefox autofill on hidden input restored the previous-session DOS value, tripping combobox.js's change-event guard. Fix: autocomplete=off + force-sync hidden.value from server-rendered aria-selected option in init. Captured as feedback_firefox_autofill_hidden_inputs (generalizable trap).
.sea-pos-label outside .sea-card-slot — moved label to be a sibling of the slot in the cell, so SeaDeal innerHTML clobber on draw doesn't erase it. Per-position absolute positioning touching slot borders: crown/cover above (translate -50%, 0.1rem, scaleY 1.2); lay/cross below (translate -50%, -0.1rem, scaleY 1.2); leave left, CCW (writing-mode vertical-rl + rotate 180deg + scaleX 1.2); loom right, CW (writing-mode vertical-rl + scaleX 1.2). scaleX for rotated labels (not scaleY) — perpendicular to text-flow is the visible-width direction after rotation. .my-sea-cross gap bumped to 1.75rem for label clearance.
Escape Velocity label swaps — POSITION_LABELS for escape-velocity: {crown: Crown, leave: Lay, cover: Cover, cross: Cross, loom: Loom, lay: Leave}. Replaces the Waite-Smith Behind/Beneath/Before per user spec.
SPREAD dropdown portal — .my-sea-form-col .sea-form-main { overflow: visible } + .sea-select-list { z-index: 1000 } so the dropdown extends past the form-main scroll area + sits above the picker stacking ints. Gameroom .sea-form-main still scrolls (only my-sea opts out).
Major Arcana polarity-split rendering — added 9 missing _card_dict keys to my-sea's _my_sea_deck_data to match gameroom epic.views.sea_deck's contract: levity_emanation, gravity_emanation, levity_reversal, gravity_reversal, italic_word, keywords_upright, keywords_reversed, energies, operations. Without these StageCard.populateCard falls through to plain name_title for trumps 19-21 + cards 48-49. Iter 4b cleanup candidate: extract apps.epic.utils.card_dict() to DRY the now-identical helpers.
Tests deferred — user explicitly belayed FT runs during the bug-fix substage. Iter 4b will re-establish a green sweep before its commit lands.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:02:27 -04:00
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
z-index: 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Cells need `position: relative` so absolute label children anchor
|
|
|
|
|
|
// to them. `.sea-pos-core` already has `position: relative` per the
|
|
|
|
|
|
// existing rule in _card-deck.scss line 1311; the other crucifix
|
|
|
|
|
|
// cells need it added.
|
|
|
|
|
|
.my-sea-cross .sea-crucifix-cell { position: relative; }
|
|
|
|
|
|
|
|
|
|
|
|
// Above top border — overlaps slot's top edge by 0.1rem (per the
|
|
|
|
|
|
// `.sea-stack-name` "tuck under" treatment in _card-deck.scss:1564).
|
|
|
|
|
|
.sea-pos-crown > .sea-pos-label,
|
|
|
|
|
|
.sea-pos-cover > .sea-pos-label {
|
|
|
|
|
|
bottom: 100%;
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
transform: translate(-50%, 0.1rem) scaleY(1.2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Cover + cross labels dim w. their slots — they sit on top of the
|
|
|
|
|
|
// sig card so a vivid label would compete w. the sig at idle. Default
|
|
|
|
|
|
// 0.25 opacity matches the slot's faint dotted-outline at idle; the
|
|
|
|
|
|
// parent's :hover state (propagated up when the inside `.sea-card-
|
|
|
|
|
|
// slot:hover` fires per CSS hover-ancestor rules) boosts to the
|
|
|
|
|
|
// `.sea-pos-label` baseline 0.6, matching the slot's `--duoUser` mask
|
|
|
|
|
|
// reveal.
|
|
|
|
|
|
.sea-pos-cover > .sea-pos-label,
|
|
|
|
|
|
.sea-pos-cross > .sea-pos-label {
|
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
|
transition: opacity 0.15s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sea-pos-cover:hover > .sea-pos-label,
|
|
|
|
|
|
.sea-pos-cross:hover > .sea-pos-label {
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Below bottom border — same `0.1rem` overlap but downward.
|
|
|
|
|
|
.sea-pos-lay > .sea-pos-label,
|
|
|
|
|
|
.sea-pos-cross > .sea-pos-label {
|
|
|
|
|
|
top: 100%;
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
transform: translate(-50%, -0.1rem) scaleY(1.2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Left of left border, rotated 90° CCW — text reads bottom-to-top.
|
|
|
|
|
|
// `writing-mode: vertical-rl` puts text top-to-bottom (CW); a 180°
|
|
|
|
|
|
// rotation flips it to read bottom-to-top (CCW), satisfying the user-
|
|
|
|
|
|
// locked "Leave: counterclockwise" spec.
|
|
|
|
|
|
//
|
|
|
|
|
|
// `scaleX(1.2)` (instead of the horizontal labels' scaleY) widens the
|
|
|
|
|
|
// character column (perpendicular to text-flow) — for vertical-rl
|
|
|
|
|
|
// labels, that's the visible "width" the user noticed had been lost
|
|
|
|
|
|
// at this angle. Without it, the rotated labels look squat.
|
|
|
|
|
|
.sea-pos-leave > .sea-pos-label {
|
|
|
|
|
|
right: 100%;
|
|
|
|
|
|
top: 50%;
|
|
|
|
|
|
writing-mode: vertical-rl;
|
|
|
|
|
|
transform: translate(0.1rem, -50%) rotate(180deg) scaleX(1.2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Right of right border, rotated 90° CW — text reads top-to-bottom.
|
|
|
|
|
|
// Native `writing-mode: vertical-rl` direction; no extra rotation.
|
|
|
|
|
|
.sea-pos-loom > .sea-pos-label {
|
|
|
|
|
|
left: 100%;
|
|
|
|
|
|
top: 50%;
|
|
|
|
|
|
writing-mode: vertical-rl;
|
|
|
|
|
|
transform: translate(-0.1rem, -50%) scaleX(1.2);
|
My Sea form col + SPREAD dropdown w. 3-card/6-card section dividers — Sprint 5 iter 3 of My Sea roadmap — TDD
Picker phase form col: SPREAD combobox w. 6 spread options under 2 horizontal section dividers ("3-card spreads" / "6-card spreads"), reversal-% caption, GRAVITY + LEVITY deck swatches, LOCK HAND + DEL btns. Default = Situation, Action, Outcome (a 3-card spread). Selecting a 6-card spread (Celtic Cross Waite-Smith or Escape Velocity) swaps `.my-sea-cross[data-spread-shape]` from `three-card` to `six-card` — revealing the crown / lay / cross cells that the default 3-card variants hide.
Naming correction (user-locked): the spread itself is a "three-card spread" not a "three-card cross" — "cross" stays scoped to the Celtic Cross variants (6-card spreads). CSS class `.my-sea-cross` carries grid-container semantics regardless of which spread shape is active; the spread-vs-cross distinction lives at the spread-name layer only.
- **View** (gameboard/views.py): `my_sea` adds `default_spread = "situation-action-outcome"` + `reversals_pct = 25` context keys.
- **Template** (my_sea.html): renders all 7 cross cells (crown/leave/core+cover+cross/loom/lay) unconditionally + adds `data-spread-shape="three-card"` to `.my-sea-cross`. Form col DRY-reuses gameroom `_sea_overlay.html`'s `.sea-form-col` shape — `.sea-form-main` w. `.sea-field` (SPREAD label + reversal hint + custom combobox) + `.sea-stacks` (GRAVITY + LEVITY swatches) + `.sea-form-actions` (LOCK HAND + DEL). 6 options + 2 dividers in the combobox `<ul>`; dividers are `role="presentation"` so `combobox.js` skips them naturally. Inline IIFE listens for the hidden `<input id="id_sea_spread">`'s `change` event + sets `.my-sea-cross`'s `data-spread-shape` based on whether the value is in `['waite-smith', 'escape-velocity']`. No new combobox.js wiring — the existing module's `change`-bubbling contract feeds straight in.
- **SCSS** (_gameboard.scss):
- `.my-sea-cross[data-spread-shape="three-card"]` — single-row `"leave core loom"` grid + `display: none` on crown/lay/cross.
- `.my-sea-cross[data-spread-shape="six-card"]` — inherits the gameroom `.sea-cross`'s 3×3 grid + reveals all cells.
- `.sea-select-divider` — section header style mirrors `.kit-bag-label`'s small-uppercase-underlined-letter-spaced --quaUser/0.75 treatment but HORIZONTAL (kit-bag uses `writing-mode: vertical-rl`; dropdown menus are flat). `pointer-events: none` belt-and-braces against accidental click/hover.
- `.my-sea-form-col` — width-constrains the form col so the picker's cross + form sit side-by-side.
**Iter-2 contract updated** (cells in DOM, hidden via CSS for 3-card default):
- FT `test_picker_does_not_render_forsaken_positions` → renamed to `test_picker_hides_six_card_only_positions_by_default` — asserts the 3 cells are in the DOM but `is_displayed() == False` so iter-3's spread switch can reveal them via CSS without re-rendering.
- IT `test_picker_does_not_render_forsaken_positions` → renamed to `test_picker_renders_six_card_only_positions_for_spread_switch` — assertContains the classes (server now renders them unconditionally).
**Tests**:
- 4 FTs in new `MySeaSpreadFormTest`: combobox renders 6 options + 2 dividers w. correct labels, default is Situation/Action/Outcome (hidden input value + visible current-label span + cross's data-spread-shape), picking Celtic Cross flips data-spread-shape to six-card + reveals crown/lay/cross, form col carries DECKS swatches + LOCK HAND + DEL + reversal-% caption. Combobox `<li>` options are inside `aria-expanded='false'` listbox → use `get_attribute("textContent")` not `.text` (which returns "" for Selenium-hidden elements).
- 7 ITs in new `MySeaSpreadFormTemplateTest`: default_spread + reversals_pct context keys, all 6 options + both labels render, 2 dividers render w. expected text, default option carries aria-selected="true", cross's initial data-spread-shape="three-card", form col DECKS + buttons + reversal hint render.
Tests: 32/32 FT green across test_bill_my_sign + test_game_my_sea; 1048/1048 IT/UT green in 52s.
Card-draw mechanics (clicking a deck swatch deposits a card into the next empty slot; LOCK HAND commits the draw) defer to iter 4 — this iter ships the spread-selection + layout-shape switch UI; the buttons are stubs (LOCK HAND starts disabled, DEL is a placeholder).
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 17:23:25 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Section dividers inside the SPREAD combobox — labels "3-card spreads"
|
|
|
|
|
|
// / "6-card spreads" separating the option groups. Styled to echo the
|
|
|
|
|
|
// `.kit-bag-label` treatment (small uppercase underlined letter-spaced
|
|
|
|
|
|
// --quaUser) but horizontal rather than vertical (kit-bag uses writing-
|
|
|
|
|
|
// mode: vertical-rl; this is a flat dropdown).
|
|
|
|
|
|
.sea-select-list .sea-select-divider {
|
|
|
|
|
|
font-size: 0.55rem;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
text-decoration: underline;
|
|
|
|
|
|
letter-spacing: 0.12em;
|
|
|
|
|
|
color: rgba(var(--quaUser), 0.75);
|
|
|
|
|
|
padding: 0.4rem 0.6rem 0.2rem;
|
|
|
|
|
|
pointer-events: none; // not selectable; combobox.js skips it
|
|
|
|
|
|
// (no role=option), but belt-and-braces
|
|
|
|
|
|
// against accidental hover/click styles.
|
|
|
|
|
|
list-style: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Form col on my-sea — same DRY treatment as the gameroom sea-overlay
|
|
|
|
|
|
// `.sea-form-col` (handled in _card-deck.scss) but sits next to the
|
|
|
|
|
|
// picker's cross on a `--duoUser` page. Just constrain the width so it
|
|
|
|
|
|
// doesn't fight the cross for horizontal space.
|
|
|
|
|
|
.my-sea-form-col {
|
|
|
|
|
|
flex: 0 0 16rem;
|
|
|
|
|
|
max-width: 16rem;
|
My Sea iter-4a follow-up batch: modal port + draw polish + label positioning + Major Arcana fix — TDD
User-driven bug-squash + UX-polish cycle on top of iter 4a (ca2a62f). All 14 fixes ship behind the same iter-4a banner since they close the substage's UX gaps without expanding scope to iter 4b's persistence layer.
SeaDeal modal port — extracted apps/gameboard/_partials/_sea_stage.html shared by gameroom + my-sea; aliased .my-sea-picker w. id=id_sea_overlay so SeaDeal.init() finds it; FLIP click → SeaDeal.openStage delegation instead of bare _fillSlot. Fixes the user-reported 'thumbnail disappears' bug — slot was landing at opacity 0 (.--filled w.o .--visible) because SeaDeal's _hideStage (which adds --visible on modal dismiss) was never running. 3 new FTs cover the modal flow.
Spread lock + DEL reshuffle — _lockSpread/_unlockSpread toggle .sea-select--locked class on the combobox; first deposit locks, _resetHand unlocks. _reshuffleDeck Fisher-Yates over combined piles + re-rolls 25% reversal axis on DEL so successive DELs don't re-deal the same hand. Verified Claudezilla: 3 DEL cycles produced distinct lay cards (150 → 114 → 155).
Cover/cross empty slots — subtle dotted outline (transparent bg + 0.25 alpha border) w. --duoUser mask reveal on hover/touch. Per the user spec; rule lives in _card-deck.scss (shared between gameroom + my-sea). Plus matching label-opacity (0.25 idle → 0.6 hover) via CSS :hover ancestor propagation.
DOS spec — Solution moved from cover → crown per user correction. DRAW_ORDER ['loom', 'cross', 'crown']; POSITION_LABELS {loom: Desire, cross: Obstacle, crown: Solution}; SCSS hide list flipped from [leave, crown, lay] → [leave, cover, lay]; FT/IT assertions updated.
SAO → DOS soft-reload bug — Firefox autofill on hidden input restored the previous-session DOS value, tripping combobox.js's change-event guard. Fix: autocomplete=off + force-sync hidden.value from server-rendered aria-selected option in init. Captured as feedback_firefox_autofill_hidden_inputs (generalizable trap).
.sea-pos-label outside .sea-card-slot — moved label to be a sibling of the slot in the cell, so SeaDeal innerHTML clobber on draw doesn't erase it. Per-position absolute positioning touching slot borders: crown/cover above (translate -50%, 0.1rem, scaleY 1.2); lay/cross below (translate -50%, -0.1rem, scaleY 1.2); leave left, CCW (writing-mode vertical-rl + rotate 180deg + scaleX 1.2); loom right, CW (writing-mode vertical-rl + scaleX 1.2). scaleX for rotated labels (not scaleY) — perpendicular to text-flow is the visible-width direction after rotation. .my-sea-cross gap bumped to 1.75rem for label clearance.
Escape Velocity label swaps — POSITION_LABELS for escape-velocity: {crown: Crown, leave: Lay, cover: Cover, cross: Cross, loom: Loom, lay: Leave}. Replaces the Waite-Smith Behind/Beneath/Before per user spec.
SPREAD dropdown portal — .my-sea-form-col .sea-form-main { overflow: visible } + .sea-select-list { z-index: 1000 } so the dropdown extends past the form-main scroll area + sits above the picker stacking ints. Gameroom .sea-form-main still scrolls (only my-sea opts out).
Major Arcana polarity-split rendering — added 9 missing _card_dict keys to my-sea's _my_sea_deck_data to match gameroom epic.views.sea_deck's contract: levity_emanation, gravity_emanation, levity_reversal, gravity_reversal, italic_word, keywords_upright, keywords_reversed, energies, operations. Without these StageCard.populateCard falls through to plain name_title for trumps 19-21 + cards 48-49. Iter 4b cleanup candidate: extract apps.epic.utils.card_dict() to DRY the now-identical helpers.
Tests deferred — user explicitly belayed FT runs during the bug-fix substage. Iter 4b will re-establish a green sweep before its commit lands.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:02:27 -04:00
|
|
|
|
|
2026-05-20 11:58:41 -04:00
|
|
|
|
// Portrait — split the form into two columns. LEFT carries the
|
|
|
|
|
|
// SPREAD field (label + reversal hint + combobox) above the action
|
|
|
|
|
|
// btns (AUTO DRAW / GATE VIEW + DEL); RIGHT carries the DECKS
|
|
|
|
|
|
// section (label + GRAVITY/LEVITY stacks) spanning both rows.
|
|
|
|
|
|
// Without this rearrange, on a phone-portrait viewport the stacked
|
|
|
|
|
|
// form col runs off the bottom of the viewport — DECKS lives above
|
|
|
|
|
|
// the action btns + everything below the fold (user-spec
|
|
|
|
|
|
// 2026-05-21). `.sea-form-main` uses `display: contents` so its
|
|
|
|
|
|
// `.sea-field` + `.sea-stacks` children act as direct grid items of
|
|
|
|
|
|
// `.my-sea-form-col` despite the intermediate wrapper in the DOM.
|
|
|
|
|
|
@media (orientation: portrait) {
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
max-width: none;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
|
|
|
grid-template-areas:
|
|
|
|
|
|
"field stacks"
|
|
|
|
|
|
"actions stacks";
|
|
|
|
|
|
column-gap: 1rem;
|
|
|
|
|
|
row-gap: 0.5rem;
|
|
|
|
|
|
align-items: start;
|
|
|
|
|
|
|
|
|
|
|
|
.sea-form-main { display: contents; }
|
|
|
|
|
|
.sea-field { grid-area: field; margin-bottom: 0; }
|
|
|
|
|
|
.sea-stacks { grid-area: stacks; margin: 0; justify-content: center; }
|
|
|
|
|
|
.sea-form-actions { grid-area: actions; align-self: end; padding-top: 0; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
My Sea iter-4a follow-up batch: modal port + draw polish + label positioning + Major Arcana fix — TDD
User-driven bug-squash + UX-polish cycle on top of iter 4a (ca2a62f). All 14 fixes ship behind the same iter-4a banner since they close the substage's UX gaps without expanding scope to iter 4b's persistence layer.
SeaDeal modal port — extracted apps/gameboard/_partials/_sea_stage.html shared by gameroom + my-sea; aliased .my-sea-picker w. id=id_sea_overlay so SeaDeal.init() finds it; FLIP click → SeaDeal.openStage delegation instead of bare _fillSlot. Fixes the user-reported 'thumbnail disappears' bug — slot was landing at opacity 0 (.--filled w.o .--visible) because SeaDeal's _hideStage (which adds --visible on modal dismiss) was never running. 3 new FTs cover the modal flow.
Spread lock + DEL reshuffle — _lockSpread/_unlockSpread toggle .sea-select--locked class on the combobox; first deposit locks, _resetHand unlocks. _reshuffleDeck Fisher-Yates over combined piles + re-rolls 25% reversal axis on DEL so successive DELs don't re-deal the same hand. Verified Claudezilla: 3 DEL cycles produced distinct lay cards (150 → 114 → 155).
Cover/cross empty slots — subtle dotted outline (transparent bg + 0.25 alpha border) w. --duoUser mask reveal on hover/touch. Per the user spec; rule lives in _card-deck.scss (shared between gameroom + my-sea). Plus matching label-opacity (0.25 idle → 0.6 hover) via CSS :hover ancestor propagation.
DOS spec — Solution moved from cover → crown per user correction. DRAW_ORDER ['loom', 'cross', 'crown']; POSITION_LABELS {loom: Desire, cross: Obstacle, crown: Solution}; SCSS hide list flipped from [leave, crown, lay] → [leave, cover, lay]; FT/IT assertions updated.
SAO → DOS soft-reload bug — Firefox autofill on hidden input restored the previous-session DOS value, tripping combobox.js's change-event guard. Fix: autocomplete=off + force-sync hidden.value from server-rendered aria-selected option in init. Captured as feedback_firefox_autofill_hidden_inputs (generalizable trap).
.sea-pos-label outside .sea-card-slot — moved label to be a sibling of the slot in the cell, so SeaDeal innerHTML clobber on draw doesn't erase it. Per-position absolute positioning touching slot borders: crown/cover above (translate -50%, 0.1rem, scaleY 1.2); lay/cross below (translate -50%, -0.1rem, scaleY 1.2); leave left, CCW (writing-mode vertical-rl + rotate 180deg + scaleX 1.2); loom right, CW (writing-mode vertical-rl + scaleX 1.2). scaleX for rotated labels (not scaleY) — perpendicular to text-flow is the visible-width direction after rotation. .my-sea-cross gap bumped to 1.75rem for label clearance.
Escape Velocity label swaps — POSITION_LABELS for escape-velocity: {crown: Crown, leave: Lay, cover: Cover, cross: Cross, loom: Loom, lay: Leave}. Replaces the Waite-Smith Behind/Beneath/Before per user spec.
SPREAD dropdown portal — .my-sea-form-col .sea-form-main { overflow: visible } + .sea-select-list { z-index: 1000 } so the dropdown extends past the form-main scroll area + sits above the picker stacking ints. Gameroom .sea-form-main still scrolls (only my-sea opts out).
Major Arcana polarity-split rendering — added 9 missing _card_dict keys to my-sea's _my_sea_deck_data to match gameroom epic.views.sea_deck's contract: levity_emanation, gravity_emanation, levity_reversal, gravity_reversal, italic_word, keywords_upright, keywords_reversed, energies, operations. Without these StageCard.populateCard falls through to plain name_title for trumps 19-21 + cards 48-49. Iter 4b cleanup candidate: extract apps.epic.utils.card_dict() to DRY the now-identical helpers.
Tests deferred — user explicitly belayed FT runs during the bug-fix substage. Iter 4b will re-establish a green sweep before its commit lands.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:02:27 -04:00
|
|
|
|
// Portal the SPREAD dropdown out of `.sea-form-main`'s overflow
|
|
|
|
|
|
// clip — by default the gameroom's `.sea-form-main { overflow-y:
|
|
|
|
|
|
// auto }` (from _card-deck.scss:1424) keeps the modal contents
|
|
|
|
|
|
// scrollable, but for my-sea's much shorter form the dropdown gets
|
|
|
|
|
|
// clipped instead of overlaying the LOCK HAND / DEL btns below.
|
|
|
|
|
|
// Setting overflow visible here lets the absolute-positioned
|
|
|
|
|
|
// `.sea-select-list` extend past the form area + sit "above
|
|
|
|
|
|
// everything else" via its existing z-index: 100.
|
|
|
|
|
|
.sea-form-main {
|
|
|
|
|
|
overflow: visible;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Bump the dropdown z-index well above the picker's stacking ints
|
|
|
|
|
|
// (cover z:3, cross z:4, modal stage z:9999 only opens on draw
|
|
|
|
|
|
// anyway). 1000 sits above any in-page layer the user might be
|
|
|
|
|
|
// interacting w. when they open the SPREAD picker.
|
|
|
|
|
|
.sea-select-list {
|
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
|
}
|
2026-05-19 16:06:14 -04:00
|
|
|
|
}
|
My Sea client-side card draw + DEL + LOCK HAND visual lock — Sprint 5 iter 4a of My Sea roadmap — TDD
Two-step deposit flow lifted from gameroom sea.js's `_fillSlot`: click a polarity stack → FLIP btn appears on the active stack → click FLIP → top card pops from that polarity's pile and deposits into `DRAW_ORDER[currentSpread][_filled]`. Per-spread hand-size completion (3 for any three-card spread, 6 for Celtic Cross variants) flips LOCK HAND from disabled to enabled. DEL fully resets — every filled slot reverts to `.sea-card-slot--empty` w. its `.sea-pos-label` re-rendered, piles re-clone from the immutable server payload, LOCK HAND re-disables. Switching spreads mid-draw triggers the same reset (position-subset + draw-order both change). LOCK HAND click visually locks the picker (`.my-sea-picker--locked` + `.btn-disabled` on stacks/DEL/itself) — server persistence defers to iter 4b.
**Card source**: new `_my_sea_deck_data(user)` helper in `apps/gameboard/views.py` mirrors the gameroom `epic.views.sea_deck` JSON contract — same `_card_dict` shape (id/name/arcana/suit/number/corner_rank/suit_icon/name_group/name_title/qualifiers/reversed). Differences from the room version per spec lock:
- No `room` context; excludes only the **current user's significator** (no other seated gamers).
- Backup-deck fallthrough: `user.equipped_deck or DeckVariant.filter(slug='earthman').first()` — mirrors `personal_sig_cards`, keeps the no-deck-equipped path working.
- Reversal probability hardcoded at 0.25 per the iter 3 spec lock. (Future per-user config will share a helper w. the gameroom's `stack_reversal_probability`.)
Deck data flows in via `{{ sea_deck_data|json_script:"id_my_sea_deck" }}` — Django's built-in script-tag JSON embedder. JS reads `id_my_sea_deck`'s textContent on init + maintains `_levityPile` / `_gravityPile` working copies that shift one card per deposit. DEL re-clones from the immutable initial payload rather than re-fetching (server is stateless wrt this client-side dealing — same shuffle survives DEL).
`.my-sea-picker--locked` SCSS: `.sea-deck-stack.btn-disabled` gets `pointer-events: none; opacity: 0.5` per [[feedback_btn_disabled_pointer_events]] convention. Hand state freezes; only iter 4b's LOCK HAND POST can mutate the persisted state from there.
**FTs** (9 in new `MySeaCardDrawTest`, using a `_draw_one(picker, polarity)` helper that clicks the stack + waits for the FLIP btn to surface + clicks FLIP):
- deck JSON embedded w. two polarity halves, disjoint card ids;
- user significator excluded from both halves;
- first LEVITY draw lands in SAO's first slot (`.sea-pos-lay`) w. `.sea-card-slot--filled.sea-card-slot--levity` + corner_rank inside;
- second draw (GRAVITY) lands in SAO's second slot (`.sea-pos-cover`) w. polarity reflected;
- 3 draws complete the SAO hand → LOCK HAND `disabled` attribute drops;
- DEL resets every filled slot, LOCK HAND re-disables;
- LOCK HAND click adds `.my-sea-picker--locked` + `.btn-disabled` on the stacks;
- switching to MBS mid-draw wipes the in-progress hand.
**ITs** (6 in new `MySeaDeckDataViewTest`):
- context `sea_deck_data` has `levity` + `gravity` keys, both lists;
- user significator absent from both halves;
- halves are disjoint sets of card ids;
- card dicts carry `id` / `corner_rank` / `suit_icon` / `reversed` (bool); shape matches gameroom contract;
- template embeds via `<script id="id_my_sea_deck" type="application/json">`;
- no-equipped-deck users get the Earthman backup pile (not empty).
Tests: 41/41 FT green across test_bill_my_sign + test_game_my_sea; 1055/1055 IT/UT green in 53s.
**Deferred to iter 4b** (server persistence):
- `MySeaDraw` model (FK to user, spread name, JSON field for hand layout, created_at);
- LOCK HAND POST endpoint → commits the hand to the DB;
- 1/24h FREE DRAW quota check + the eventual FREE DRAW → DRAW SEA btn-label swap;
- Sig stage card full populate (name/qualifier/keywords/FYI/SPIN/FLIP) — currently corner rank + suit icon only.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 20:02:20 -04:00
|
|
|
|
|
|
|
|
|
|
// LOCK HAND post-commit visual-lock: dim everything that mutates the
|
|
|
|
|
|
// hand. `.btn-disabled` is the project's existing soft-disabled
|
|
|
|
|
|
// treatment per [[feedback_btn_disabled_pointer_events]] — pointer-
|
|
|
|
|
|
// events:none + opacity reduction. The deck stacks aren't buttons
|
|
|
|
|
|
// themselves so we apply the class manually + the rule below ensures
|
|
|
|
|
|
// they stop responding to clicks.
|
|
|
|
|
|
.my-sea-picker--locked {
|
|
|
|
|
|
.sea-deck-stack.btn-disabled {
|
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
|
cursor: default;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
My Sea iter-4a follow-up batch: modal port + draw polish + label positioning + Major Arcana fix — TDD
User-driven bug-squash + UX-polish cycle on top of iter 4a (ca2a62f). All 14 fixes ship behind the same iter-4a banner since they close the substage's UX gaps without expanding scope to iter 4b's persistence layer.
SeaDeal modal port — extracted apps/gameboard/_partials/_sea_stage.html shared by gameroom + my-sea; aliased .my-sea-picker w. id=id_sea_overlay so SeaDeal.init() finds it; FLIP click → SeaDeal.openStage delegation instead of bare _fillSlot. Fixes the user-reported 'thumbnail disappears' bug — slot was landing at opacity 0 (.--filled w.o .--visible) because SeaDeal's _hideStage (which adds --visible on modal dismiss) was never running. 3 new FTs cover the modal flow.
Spread lock + DEL reshuffle — _lockSpread/_unlockSpread toggle .sea-select--locked class on the combobox; first deposit locks, _resetHand unlocks. _reshuffleDeck Fisher-Yates over combined piles + re-rolls 25% reversal axis on DEL so successive DELs don't re-deal the same hand. Verified Claudezilla: 3 DEL cycles produced distinct lay cards (150 → 114 → 155).
Cover/cross empty slots — subtle dotted outline (transparent bg + 0.25 alpha border) w. --duoUser mask reveal on hover/touch. Per the user spec; rule lives in _card-deck.scss (shared between gameroom + my-sea). Plus matching label-opacity (0.25 idle → 0.6 hover) via CSS :hover ancestor propagation.
DOS spec — Solution moved from cover → crown per user correction. DRAW_ORDER ['loom', 'cross', 'crown']; POSITION_LABELS {loom: Desire, cross: Obstacle, crown: Solution}; SCSS hide list flipped from [leave, crown, lay] → [leave, cover, lay]; FT/IT assertions updated.
SAO → DOS soft-reload bug — Firefox autofill on hidden input restored the previous-session DOS value, tripping combobox.js's change-event guard. Fix: autocomplete=off + force-sync hidden.value from server-rendered aria-selected option in init. Captured as feedback_firefox_autofill_hidden_inputs (generalizable trap).
.sea-pos-label outside .sea-card-slot — moved label to be a sibling of the slot in the cell, so SeaDeal innerHTML clobber on draw doesn't erase it. Per-position absolute positioning touching slot borders: crown/cover above (translate -50%, 0.1rem, scaleY 1.2); lay/cross below (translate -50%, -0.1rem, scaleY 1.2); leave left, CCW (writing-mode vertical-rl + rotate 180deg + scaleX 1.2); loom right, CW (writing-mode vertical-rl + scaleX 1.2). scaleX for rotated labels (not scaleY) — perpendicular to text-flow is the visible-width direction after rotation. .my-sea-cross gap bumped to 1.75rem for label clearance.
Escape Velocity label swaps — POSITION_LABELS for escape-velocity: {crown: Crown, leave: Lay, cover: Cover, cross: Cross, loom: Loom, lay: Leave}. Replaces the Waite-Smith Behind/Beneath/Before per user spec.
SPREAD dropdown portal — .my-sea-form-col .sea-form-main { overflow: visible } + .sea-select-list { z-index: 1000 } so the dropdown extends past the form-main scroll area + sits above the picker stacking ints. Gameroom .sea-form-main still scrolls (only my-sea opts out).
Major Arcana polarity-split rendering — added 9 missing _card_dict keys to my-sea's _my_sea_deck_data to match gameroom epic.views.sea_deck's contract: levity_emanation, gravity_emanation, levity_reversal, gravity_reversal, italic_word, keywords_upright, keywords_reversed, energies, operations. Without these StageCard.populateCard falls through to plain name_title for trumps 19-21 + cards 48-49. Iter 4b cleanup candidate: extract apps.epic.utils.card_dict() to DRY the now-identical helpers.
Tests deferred — user explicitly belayed FT runs during the bug-fix substage. Iter 4b will re-establish a green sweep before its commit lands.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:02:27 -04:00
|
|
|
|
|
|
|
|
|
|
// SPREAD combobox lock — applied after the first deposit so the user
|
|
|
|
|
|
// can't switch spread mid-draw + scramble the in-progress hand's
|
|
|
|
|
|
// position-to-card mapping. DEL releases the lock by removing this
|
|
|
|
|
|
// class. Same `pointer-events: none` treatment as `.btn-disabled` per
|
|
|
|
|
|
// [[feedback_btn_disabled_pointer_events]].
|
|
|
|
|
|
.sea-select.sea-select--locked {
|
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
|
cursor: default;
|
|
|
|
|
|
}
|
My Sea iter 4b: MySeaDraw persistence + LOCK HAND POST + DEL guard + Brief banner; rewrite obsolete spread-switch FT; fix bud-panel CI race on gatekeeper FT — Sprint 5 iter 4b of My Sea roadmap — TDD
Iter 4b lands server persistence of the iter-4a client-side hand. New MySeaDraw model (FK user, spread, hand JSONField in draw order, sig snapshot, created_at) w. 1/24h quota window; new endpoints /gameboard/my-sea/lock (POST, 409 on quota-active, 400 on partial hand) + /gameboard/my-sea/delete (POST, idempotent). LOCK HAND now collects the in-progress hand from DOM, POSTs, and on success un-hides a Brief banner inline (no page reload — preserves iter-4a FT picker refs). DEL post-LOCK opens #id_my_sea_del_portal w. uniform 'Are you sure?' copy; CONFIRM POSTs delete + reloads to landing. Brief banner carries the next-free-draw timestamp + a NVM dismiss. Saved-draw render bypasses the sign-gate via _resolve_sig (sig snapshot on the draw is used even if user.significator was cleared later) + bypasses the landing phase (the saved hand IS what the user came to see). Per-position slot rendering extracted to _my_sea_slot.html. DRY follow-up: card_dict() extracted to apps.epic.utils — gameroom sea_deck + my-sea _my_sea_deck_data now share one source of truth (prevents drift like the iter-4a-follow-up Major Arcana fix from recurring).
Pipeline #316 fixes bundled: (a) functional_tests.test_game_my_sea.MySeaCardDrawTest.test_switching_spread_resets_in_progress_hand was obsoleted by the iter-4a follow-up's spread-lock-after-first-draw — the test premise (mid-draw spread switching resets hand) no longer matches behavior (switching is blocked outright). Rewrote as test_first_draw_locks_spread_combobox, which pins .sea-select--locked after first draw + verifies DEL releases it. (b) functional_tests.test_game_room_gatekeeper.GatekeeperTest.test_second_gamer_drops_token_into_open_slot failed in CI on ElementNotInteractableException when clicking #id_bud_panel .btn.btn-confirm — the bud panel's scaleX(0)→scaleX(1) 0.2s CSS transition wasn't settled by click-time, so Selenium read scroll-into-view against a near-zero-width target. Added a wait_for on getBoundingClientRect().width > 100 so the click waits for the animation to finish. Local passes consistently; CI was 1+ frame slower than the implicit 'find element' wait.
Tests: 1085 IT/UT green in 55s; 35 my_sea FTs green in 5m; new ITs in MySeaDrawModelTest (8), MySeaLockHandViewTest (7), MySeaDeleteDrawViewTest (5), MySeaViewWithSavedDrawTest (9); new FTs in MySeaLockHandTest (5).
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 23:54:00 -04:00
|
|
|
|
|
|
|
|
|
|
// ── Iter 4b: Brief banner + DEL guard portal ─────────────────────────────────
|
My Sea iter 4b polish: Brief banner uses standard portaled `.note-banner` (Gaussian glass atop h2); next-free-draw datetime in dedicated `<time>` slot (not "Invalid Date"); DEL guard reuses shared `#id_guard_portal` from base.html — TDD
UX refactor on top of iter 4b (b76d3c5) per user direction:
(1) Brief banner — replaced custom `.my-sea-brief` markup + SCSS w. a call to `Brief.showBanner` from note.js. Now matches the my-notes / my-sign default-deck-warning Briefs exactly: standard `.note-banner` portaled atop the h2 w. Gaussian-glass backdrop-filter blur. Tagged `.my-sea-locked-banner` for FT disambiguation only — no visual override.
(2) Brief timestamp — fix for "Invalid Date" rendering in note.js's `<time class="note-banner__timestamp">` slot. Previously passed `created_at: ''` to `Brief.showBanner` → `new Date('')` returns Invalid Date → `toLocaleDateString` renders "Invalid Date". Now passes the next-free-draw ISO timestamp as `created_at` (server emits via `|date:'c'`). After Brief.showBanner returns, the `_showFreeDrawLockedBrief` JS overwrites the rendered text w. the more detailed `D, M j @ g:i A` format ("Wed, May 20 @ 11:57 PM") — leaves the ISO `datetime=` attribute intact for accessibility. The `line_text` no longer carries the timestamp inline (it's redundant w. the dedicated slot).
(3) DEL guard portal — replaced custom `#id_my_sea_del_portal` fullscreen modal + `.my-sea-del-portal` SCSS w. a call to `window.showGuard` from base.html, targeting the shared `#id_guard_portal`. Same Gaussian-glass tooltip the room gear-menu DEL flow uses: no backdrop, positioned above the anchor button, standard `.btn-confirm OK` + `.btn-cancel NVM` pair. Bundled a non-breaking `options.yesLabel` extension to `show()` in base.html for future destructive flows that need a custom YES label (defaults to 'OK', resets on dismiss/confirm) — my-sea doesn't use it per user direction (the `.btn-confirm` class implies "OK"; destructive intent belongs on the trigger button, which is `.btn-danger DEL`).
Tests: 30 iter-4b ITs (model + lock + delete + saved-draw view branches) + 5 iter-4b FTs all green; IT/FT assertions updated to target the shared portal markup (`#id_guard_portal.active`, `.guard-yes`, `.guard-no`, `.note-banner.my-sea-locked-banner`).
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 00:12:52 -04:00
|
|
|
|
// Both reuse shared chrome: the Brief is `.note-banner` from note.js
|
|
|
|
|
|
// (portaled atop h2 w. Gaussian glass); the DEL guard is `#id_guard_portal`
|
|
|
|
|
|
// from base.html (the same one the room gear-menu DEL uses, positioned
|
|
|
|
|
|
// above the anchor button w. Gaussian glass + no backdrop). The picker IIFE
|
|
|
|
|
|
// invokes it via `window.showGuard(delBtn, "Are you sure?", confirmFn,
|
|
|
|
|
|
// null, {yesLabel: "DEL"})`. No my-sea-specific SCSS needed.
|