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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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-19 15:15:37 -04:00
|
|
|
|
}
|
2026-05-19 16:06:14 -04:00
|
|
|
|
|
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
|
|
|
|
// .my-sea-cross renders all 6 surrounding positions (crown/lay/cross
|
|
|
|
|
|
// PLUS leave/loom/cover) so the SPREAD dropdown can toggle 3-card vs
|
|
|
|
|
|
// 6-card layout via a single data-attribute swap (no DOM mutation).
|
|
|
|
|
|
// Default `three-card` hides the 3 forsaken positions via `display:
|
|
|
|
|
|
// none`; `six-card` (Celtic Cross variants) renders all 7 cells.
|
|
|
|
|
|
// Cover always shows (it's part of both shapes — overlaid on sig).
|
|
|
|
|
|
.my-sea-cross[data-spread-shape="three-card"] {
|
|
|
|
|
|
grid-template-areas:
|
|
|
|
|
|
"leave core loom";
|
2026-05-19 16:06:14 -04:00
|
|
|
|
grid-template-rows: auto;
|
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,
|
|
|
|
|
|
.sea-pos-lay,
|
|
|
|
|
|
.sea-pos-cross { display: none; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.my-sea-cross[data-spread-shape="six-card"] {
|
|
|
|
|
|
// Inherits `.sea-cross`'s 3×3 template from _card-deck.scss line 1189-
|
|
|
|
|
|
// 1200; nothing to override layout-wise. All cells visible.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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;
|
2026-05-19 16:06:14 -04:00
|
|
|
|
}
|