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>
This commit is contained in:
@@ -297,10 +297,19 @@ body.page-gameboard {
|
||||
// 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)
|
||||
// DOS: loom (1) cross (2) cover (3) — sig-anchored cluster + loom
|
||||
// DOS: loom (1) cross (2) crown (3) — loom right · cross overlay · crown above
|
||||
// CC variants: all 6 positions (Waite-Smith / Escape Velocity differ in DRAW ORDER only,
|
||||
// not in position visibility).
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
.my-sea-cross[data-spread="past-present-future"] {
|
||||
.sea-pos-crown,
|
||||
.sea-pos-cross,
|
||||
@@ -321,28 +330,106 @@ body.page-gameboard {
|
||||
|
||||
.my-sea-cross[data-spread="desire-obstacle-solution"] {
|
||||
.sea-pos-leave,
|
||||
.sea-pos-crown,
|
||||
.sea-pos-cover,
|
||||
.sea-pos-lay { display: none; }
|
||||
}
|
||||
|
||||
// Celtic Cross variants (waite-smith / escape-velocity) — all positions
|
||||
// visible by default. No `display: none` overrides needed.
|
||||
|
||||
// Position-name caption inside each empty `.sea-card-slot--empty` —
|
||||
// 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.
|
||||
// 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
|
||||
.sea-pos-label {
|
||||
font-size: 0.65rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
opacity: 0.6;
|
||||
transform: scaleY(1.2);
|
||||
color: rgba(var(--terUser), 1);
|
||||
opacity: 1;
|
||||
color: rgba(var(--seciUser), 1);
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
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);
|
||||
}
|
||||
|
||||
// Section dividers inside the SPREAD combobox — labels "3-card spreads"
|
||||
@@ -370,6 +457,26 @@ body.page-gameboard {
|
||||
.my-sea-form-col {
|
||||
flex: 0 0 16rem;
|
||||
max-width: 16rem;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// LOCK HAND post-commit visual-lock: dim everything that mutates the
|
||||
@@ -385,3 +492,14 @@ body.page-gameboard {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user