Files
python-tdd/src/static_src/scss/_button-pad.scss

573 lines
18 KiB
SCSS
Raw Normal View History

.btn {
display: inline-flex;
align-items: center;
justify-content: center;
text-align: center;
width: 2rem;
height: 2rem;
text-shadow: 0 0 0.25rem rgba(0, 0, 0, 0.5);
border: 0.15rem solid rgba(var(--priUser), 1);
border-radius: 50%;
font-weight: 700;
font-size: 0.63rem;
text-transform: uppercase;
margin: 0.25rem;
flex-shrink: 0;
&:hover,
&:active {
cursor: pointer;
}
&:active {
font-size: 0.61rem;
border: 0.18rem solid rgba(var(--priUser), 1);
}
// BIG btn
&.btn-primary {
width: 4rem;
height: 4rem;
font-size: 0.875rem;
border-width: 0.21rem;
color: rgba(var(--quaUser), 1);
border-color: rgba(var(--quaUser), 1);
background-color: rgba(var(--quiUser), 1);
box-shadow:
0.1rem 0.1rem 0.12rem rgba(var(--quiUser), 0.25),
0.12rem 0.12rem 0.25rem rgba(0, 0, 0, 0.25),
0.25rem 0.25rem 0.25rem rgba(var(--quiUser), 0.12)
;
&:hover {
text-shadow:
0.2rem 0.2rem 0.2rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--quaUser), 1)
;
box-shadow:
0.24rem 0.24rem 0.5rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--quaUser), 0.22)
;
}
&:active {
border-width: 0.25rem;
text-shadow:
-0.2rem -0.2rem 0.25rem rgba(0, 0, 0, 0.25),
0 0 0.24rem rgba(var(--quaUser), 1)
;
box-shadow:
-0.2rem -0.2rem 0.24rem rgba(var(--quiUser), 0.25),
-0.2rem -0.2rem 0.24rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--quaUser), 0.22)
;
}
@media (orientation: landscape) and (max-width: 1100px),
(orientation: portrait) and (max-height: 800px) {
width: 2.75rem !important;
height: 2.75rem !important;
font-size: 0.625rem !important;
border-width: 0.125rem !important;
}
}
// BYE btn
&.btn-abandon {
color: rgba(var(--priBl), 1);
border-color: rgba(var(--priBl), 1);
background-color: rgba(var(--terBl), 1);
box-shadow:
0.1rem 0.1rem 0.12rem rgba(var(--terBl), 0.25),
0.12rem 0.12rem 0.25rem rgba(0, 0, 0, 0.25),
0.25rem 0.25rem 0.25rem rgba(var(--terBl), 0.12)
;
&:hover {
text-shadow:
0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.25),
0 0 1rem rgba(var(--priBl), 1)
;
box-shadow:
0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priBl), 0.12)
;
}
&:active {
border: 0.18rem solid rgba(var(--priBl), 1);
text-shadow:
-0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 0.25),
0 0 0.12rem rgba(var(--priBl), 1)
;
box-shadow:
-0.1rem -0.1rem 0.12rem rgba(var(--terBl), 0.25),
-0.1rem -0.1rem 0.12rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priBl), 0.12)
;
}
}
// DEL btn
&.btn-cancel {
color: rgba(var(--priOr), 1);
border-color: rgba(var(--priOr), 1);
background-color: rgba(var(--terOr), 1);
box-shadow:
0.1rem 0.1rem 0.12rem rgba(var(--terOr), 0.25),
0.12rem 0.12rem 0.25rem rgba(0, 0, 0, 0.25),
0.25rem 0.25rem 0.25rem rgba(var(--terOr), 0.12)
;
&:hover {
text-shadow:
0.2rem 0.2rem 0.2rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priOr), 1)
;
box-shadow:
0.24rem 0.24rem 0.5rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priOr), 0.12)
;
}
&:active {
text-shadow:
-0.1rem -0.1rem 0.5rem rgba(0, 0, 0, 0.25),
0 0 0.12rem rgba(var(--priOr), 1)
;
box-shadow:
-0.1rem -0.1rem 0.12rem rgba(var(--terOr), 0.25),
-0.1rem -0.1rem 0.12rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priOr), 0.12)
;
}
}
// FYI btn
&.btn-info {
color: rgba(var(--priYl), 1);
border-color: rgba(var(--priYl), 1);
background-color: rgba(var(--terYl), 1);
box-shadow:
0.1rem 0.1rem 0.12rem rgba(var(--terYl), 0.25),
0.12rem 0.12rem 0.25rem rgba(0, 0, 0, 0.25),
0.25rem 0.25rem 0.25rem rgba(var(--terYl), 0.12)
;
&:hover {
text-shadow:
0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.25),
0 0 1rem rgba(var(--priYl), 1)
;
box-shadow:
0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priYl), 0.12)
;
}
&:active {
border: 0.18rem solid rgba(var(--priYl), 1);
text-shadow:
-0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 0.25),
0 0 0.12rem rgba(var(--priYl), 1)
;
box-shadow:
-0.1rem -0.1rem 0.12rem rgba(var(--terYl), 0.25),
-0.1rem -0.1rem 0.12rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priYl), 0.12)
;
}
}
// OK btn
&.btn-confirm {
color: rgba(var(--priGn), 1);
border-color: rgba(var(--priGn), 1);
background-color: rgba(var(--terGn), 1);
box-shadow:
0.1rem 0.1rem 0.12rem rgba(var(--terGn), 0.25),
0.12rem 0.12rem 0.25rem rgba(0, 0, 0, 0.25),
0.25rem 0.25rem 0.25rem rgba(var(--terGn), 0.12)
;
&:hover {
text-shadow:
0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.25),
0 0 1rem rgba(var(--priGn), 1)
;
box-shadow:
0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priGn), 0.12)
;
}
&:active {
border: 0.18rem solid rgba(var(--priGn), 1);
text-shadow:
-0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 0.25),
0 0 0.12rem rgba(var(--priGn), 1)
;
box-shadow:
-0.1rem -0.1rem 0.12rem rgba(var(--terGn), 0.25),
-0.1rem -0.1rem 0.12rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priGn), 0.12)
;
}
}
// DEL btn
&.btn-danger {
color: rgba(var(--priRd), 1);
background-color: rgba(var(--terRd), 1);
border-color: rgba(var(--priRd), 1);
box-shadow:
0.1rem 0.1rem 0.12rem rgba(var(--terRd), 0.25),
0.12rem 0.12rem 0.25rem rgba(0, 0, 0, 0.25),
0.25rem 0.25rem 0.25rem rgba(var(--terRd), 0.12)
;
&:hover {
text-shadow:
0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.25),
0 0 1rem rgba(var(--priRd), 1)
;
box-shadow:
0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priRd), 0.12)
;
}
&:active {
text-shadow:
-0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 0.25),
0 0 0.12rem rgba(var(--priRd), 1)
;
box-shadow:
-0.1rem -0.1rem 0.12rem rgba(var(--terRd), 0.25),
-0.1rem -0.1rem 0.12rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priRd), 0.12)
;
}
&.stop-player {
width: 4rem;
height: 4rem;
font-size: 0.9rem;
border: 0.2rem solid rgba(var(--priRd), 1);
box-shadow:
0.1rem 0.1rem 0.25rem rgba(var(--terRd), 0.5),
0.25rem 0.25rem 1rem rgba(0, 0, 0, 0.5),
0.5rem 0.5rem 0.5rem rgba(var(--terRd), 0.25)
;
&:hover {
text-shadow:
0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.5),
0 0 1rem rgba(var(--priRd), 1)
;
box-shadow:
0.25rem 0.25rem 1rem rgba(0, 0, 0, 0.5),
0 0 1rem rgba(var(--priRd), 0.25)
;
}
&:active {
font-size: 0.88rem;
border: 0.25rem solid rgba(var(--priRd), 1);
text-shadow:
-0.1rem -0.1rem 0.5rem rgba(0, 0, 0, 0.5),
0 0 0.25rem rgba(var(--priRd), 1)
;
box-shadow:
-0.1rem -0.1rem 0.25rem rgba(var(--terRd), 0.5),
-0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 0.5),
0 0 1rem rgba(var(--priRd), 0.25)
;
}
}
}
@media (orientation: landscape) and (min-width: 1800px) {
width: 2.4rem; // 2rem × 1.2
height: 2.4rem;
font-size: 0.75rem; // 0.63rem × 1.2
}
// PRV btn
&.btn-nav-left {
color: rgba(var(--priFs), 1);
border-color: rgba(var(--priFs), 1);
background-color: rgba(var(--terFs), 1);
box-shadow:
0.1rem 0.1rem 0.12rem rgba(var(--terFs), 0.25),
0.12rem 0.12rem 0.25rem rgba(0, 0, 0, 0.25),
0.25rem 0.25rem 0.25rem rgba(var(--terFs), 0.12)
;
&:hover {
text-shadow:
0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.25),
0 0 1rem rgba(var(--priFs), 1)
;
box-shadow:
0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priFs), 0.12)
;
}
&:active {
border: 0.18rem solid rgba(var(--priFs), 1);
text-shadow:
-0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 0.25),
0 0 0.12rem rgba(var(--priFs), 1)
;
box-shadow:
-0.1rem -0.1rem 0.12rem rgba(var(--terFs), 0.25),
-0.1rem -0.1rem 0.12rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priFs), 0.12)
;
}
}
// NXT btn
&.btn-nav-right {
color: rgba(var(--priLm), 1);
border-color: rgba(var(--priLm), 1);
background-color: rgba(var(--terLm), 1);
box-shadow:
0.1rem 0.1rem 0.12rem rgba(var(--terLm), 0.25),
0.12rem 0.12rem 0.25rem rgba(0, 0, 0, 0.25),
0.25rem 0.25rem 0.25rem rgba(var(--terLm), 0.12)
;
&:hover {
text-shadow:
0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.25),
0 0 1rem rgba(var(--priLm), 1)
;
box-shadow:
0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priLm), 0.12)
;
}
&:active {
border: 0.18rem solid rgba(var(--priLm), 1);
text-shadow:
-0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 0.25),
0 0 0.12rem rgba(var(--priLm), 1)
;
box-shadow:
-0.1rem -0.1rem 0.12rem rgba(var(--terLm), 0.25),
-0.1rem -0.1rem 0.12rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priLm), 0.12)
;
}
}
// DON btn
&.btn-equip {
color: rgba(var(--priTk), 1);
border-color: rgba(var(--priTk), 1);
background-color: rgba(var(--terTk), 1);
box-shadow:
0.1rem 0.1rem 0.12rem rgba(var(--terTk), 0.25),
0.12rem 0.12rem 0.25rem rgba(0, 0, 0, 0.25),
0.25rem 0.25rem 0.25rem rgba(var(--terTk), 0.12)
;
&:hover {
text-shadow:
0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.25),
0 0 1rem rgba(var(--priTk), 1)
;
box-shadow:
0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priTk), 0.12)
;
}
&:active {
border: 0.18rem solid rgba(var(--priTk), 1);
text-shadow:
-0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 0.25),
0 0 0.12rem rgba(var(--priTk), 1)
;
box-shadow:
-0.1rem -0.1rem 0.12rem rgba(var(--terTk), 0.25),
-0.1rem -0.1rem 0.12rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priTk), 0.12)
;
}
}
// DOFF btn
&.btn-unequip {
color: rgba(var(--priId), 1);
border-color: rgba(var(--priId), 1);
background-color: rgba(var(--terId), 1);
box-shadow:
0.1rem 0.1rem 0.12rem rgba(var(--terId), 0.25),
0.12rem 0.12rem 0.25rem rgba(0, 0, 0, 0.25),
0.25rem 0.25rem 0.25rem rgba(var(--terId), 0.12)
;
&:hover {
text-shadow:
0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.25),
0 0 1rem rgba(var(--priId), 1)
;
box-shadow:
0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priId), 0.12)
;
}
&:active {
border: 0.18rem solid rgba(var(--priId), 1);
text-shadow:
-0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 0.25),
0 0 0.12rem rgba(var(--priId), 1)
;
box-shadow:
-0.1rem -0.1rem 0.12rem rgba(var(--terId), 0.25),
-0.1rem -0.1rem 0.12rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priId), 0.12)
;
}
}
// FLIP btn
&.btn-reveal {
color: rgba(var(--priMe), 1);
border-color: rgba(var(--priMe), 1);
background-color: rgba(var(--terMe), 1);
box-shadow:
0.1rem 0.1rem 0.12rem rgba(var(--terMe), 0.25),
0.12rem 0.12rem 0.25rem rgba(0, 0, 0, 0.25),
0.25rem 0.25rem 0.25rem rgba(var(--terMe), 0.12)
;
&:hover {
text-shadow:
0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.25),
0 0 1rem rgba(var(--priMe), 1)
;
box-shadow:
0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priMe), 0.12)
;
}
&:active {
border: 0.18rem solid rgba(var(--priMe), 1);
text-shadow:
-0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 0.25),
0 0 0.12rem rgba(var(--priMe), 1)
;
box-shadow:
-0.1rem -0.1rem 0.12rem rgba(var(--terMe), 0.25),
-0.1rem -0.1rem 0.12rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priMe), 0.12)
;
}
}
// SPIN btn
&.btn-reverse {
color: rgba(var(--priCy), 1);
border-color: rgba(var(--priCy), 1);
background-color: rgba(var(--terCy), 1);
box-shadow:
0.1rem 0.1rem 0.12rem rgba(var(--terCy), 0.25),
0.12rem 0.12rem 0.25rem rgba(0, 0, 0, 0.25),
0.25rem 0.25rem 0.25rem rgba(var(--terCy), 0.12)
;
&:hover {
text-shadow:
0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.25),
0 0 1rem rgba(var(--priCy), 1)
;
box-shadow:
0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priCy), 0.12)
;
}
&:active {
border: 0.18rem solid rgba(var(--priCy), 1);
text-shadow:
-0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 0.25),
0 0 0.12rem rgba(var(--priCy), 1)
;
box-shadow:
-0.1rem -0.1rem 0.12rem rgba(var(--terCy), 0.25),
-0.1rem -0.1rem 0.12rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priCy), 0.12)
;
}
}
// Dead last — wins over all color modifiers by source order.
&.btn-disabled {
cursor: default !important;
pointer-events: none;
font-size: 1.2rem;
padding-bottom: 0.1rem;
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 `&times;` 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
color: transparent !important; // hide native text
background-color: rgba(var(--priUser), 1) !important;
border-color: rgba(var(--secUser), 0.25) !important;
box-shadow:
0.1rem 0.1rem 0.12rem rgba(var(--priUser), 0.5),
0.12rem 0.12rem 0.25rem rgba(0, 0, 0, 0.25),
0.25rem 0.25rem 0.25rem rgba(var(--secUser), 0.12)
;
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 `&times;` 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
position: relative;
// Universal × overlay — any `.btn-disabled` button reads as ×
// regardless of its native inner text/icons (DEL → ×, FLIP → ×,
// LOCK HAND → ×, etc.). Templates that already render `&times;`
// explicitly (e.g. don/doff toggle pairs) just have their inner
// × hidden by `visibility: hidden` on children; the pseudo's
// glyph is the only one visible — no double-× regression. User
// spec 2026-05-20.
> * { visibility: hidden; }
&::before {
content: "\00d7";
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
color: rgba(var(--secUser), 0.5);
font-size: 1.5rem;
font-weight: bold;
}
&:hover {
text-shadow:
0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.25),
0 0 1rem rgba(var(--priUser), 1)
;
box-shadow:
0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--priUser), 0.12)
;
}
&:active {
text-shadow:
-0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 0.25),
0 0 0.12rem rgba(var(--priUser), 1)
;
box-shadow:
-0.1rem -0.1rem 0.12rem rgba(var(--priUser), 0.75),
-0.1rem -0.1rem 0.12rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--secUser), 0.12)
;
}
}
}