Second slice of the Sea sub-btn rollout (phase 1 = .active wiring in 3ae85b9; phase 3 = --priYl glow handoff to come).
## Spread modal (#id_sea_spread_modal)
`templates/apps/gameboard/my_sea.html` — `.sea-form-col` (spread combobox + AUTO DRAW + DEL btns) now lives inside `<div id="id_sea_spread_modal" class="my-sea-spread-modal" hidden>`. Hidden by default; opens on #id_sea_btn click (per the inline `<script>` block). The deck stacks (`.sea-stacks` + `.sea-stacks-label`) are extracted to a new `.my-sea-stacks-wrap` sibling that stays visible on the page so the FLIP affordance remains usable while the modal is closed.
## Modal SCSS
`static_src/scss/_gameboard.scss`:
- `.my-sea-spread-modal` — position:fixed inset:0; z-index:320 (above all corner btns); pointer-events:none w. children opting back in. `&[hidden]` makes it explicit that the closed state stays display:none.
- `.my-sea-spread-modal__backdrop` — full-viewport semi-transparent w. backdrop-filter blur; pointer-events:auto so click-outside closes.
- `.my-sea-spread-modal__panel` — opaque card, border + shadow, max-width:90vw. `.sea-form-col` inside drops its fixed 16rem width + uses min-width.
- `.my-sea-stacks-wrap` — position:absolute bottom:4.5rem right:1rem (clear of bud/burger); z-index:5 (above .my-sea-cross, below modal).
## Modal JS
`templates/apps/gameboard/my_sea.html` — new inline `<script>` at the bottom owns:
- `#id_sea_btn` click → opens modal (guarded on `.active` so the burger-btn.js inactive-flash handler still runs for inactive sub-btns).
- Escape key → close.
- Backdrop click → close.
- `#id_guard_portal .guard-yes` click (delegated) → close. This is the key UX detail user-spec'd: AUTO DRAW + DEL both open a guard portal that positions itself against the visible action/del btn — closing the modal on the btn click itself would dump the portal at (0,0). Closing on guard OK instead means the portal positions correctly + the modal closes only when the action is confirmed. NVM (`.guard-no`) leaves the modal up so the user can retry.
Also `apps/epic/static/apps/epic/burger-btn.js` — delegated fan click now closes the burger fan when an ACTIVE sub-btn is clicked (was: no-op for active). The per-page sub-btn handler runs in target phase BEFORE the fan-bubble handler, so the action kicks off w. a clean visual.
Client-side `.active` sync: server renders sea_btn inactive on the landing page (show_picker=False there). The DRAW SEA → picker transition is client-side (no re-render), so the IIFE in my_sea.html now flips `seaBtn.classList.add('active')` at the same SEAT_ANIM_MS moment data-phase swaps to 'picker'. Sea_btn stays `.active` for the rest of the picker phase, INCLUDING after hand_complete — DEL + GATE VIEW both live inside the modal + the user needs them accessible (earlier iteration deactivated on hand_complete; reverted on user feedback).
## Mid-draw NVM → CONT DRAW (apps/gameboard/views.py + my_sea.html)
Earlier iteration's gear-menu NVM nav-backed to `/gameboard/my-sea/` mid-draw, but the server's `hand_non_empty` branch re-rendered the picker on the next GET — looping the user right back into the spread. New `?phase=landing` escape hatch:
- `views.py`: `force_landing = request.GET.get('phase') == 'landing'`; `show_picker = (hand_non_empty or (phase_param and show_paid_draw)) and not force_landing`. New context var `show_cont_draw = force_landing and active_draw and not active_draw.is_hand_complete`.
- `my_sea.html` landing: new `{% if show_cont_draw %} CONT DRAW {% elif show_paid_draw %} ... ` branch on the table-center action-btn. CONT DRAW is an `<a href="{% url 'my_sea' %}">` (plain navigation, no `?phase=landing` so the next GET falls through to the hand_non_empty picker branch).
- `my_sea.html` gear include: picker phase passes `nvm_url={% url 'my_sea' %}?phase=landing` so the gear NVM exits to the landing-with-CONT-DRAW state instead of looping back into the picker.
## Tests
`apps/gameboard/tests/integrated/test_views.py` — 4 ITs added/updated:
- `test_sea_btn_stays_active_when_hand_complete` (replaced the prior "returns to inactive" assertion — sea_btn now stays active per user spec).
- `test_gear_nvm_navs_to_my_sea_landing_on_picker_phase` (updated URL to include `?phase=landing`).
- `test_force_landing_renders_cont_draw_btn_mid_draw` (NEW) — verifies CONT DRAW renders + FREE DRAW absent.
- `test_force_landing_hides_cont_draw_when_no_active_draw` (NEW) — regression guard: sig'd user w. no draw still sees FREE DRAW.
- `test_force_landing_hides_cont_draw_when_hand_complete` (NEW) — CONT DRAW absent for completed hands.
`functional_tests/test_game_my_sea.py` — new module-level `_open_spread_modal(test)` helper + applied to 7 tests that touch in-modal selectors (combobox / action_btn / del):
- `MySeaSpreadFormTest.test_picking_spread_swaps_data_spread_and_position_visibility`
- `MySeaSpreadFormTest.test_per_spread_position_labels_render_and_update`
- `MySeaCardDrawTest.test_action_btn_transitions_to_gate_view_on_hand_complete` (also: close modal before manual FLIP draws, use innerText for hidden-element text checks)
- `MySeaCardDrawTest.test_auto_drawn_slots_can_reopen_stage_modal_on_click`
- `MySeaCardDrawTest.test_form_col_renders_decks_lock_hand_del_and_reversal_pct`
- `MySeaLockHandTest.test_del_click_opens_shared_guard_portal`
- `MySeaLockHandTest.test_del_confirm_clears_hand_and_returns_to_gate_view_landing`
JS .click() is used inside `_open_spread_modal` (not Selenium .click) because the fan sub-btns spend ~0.25s mid-animation stacked at the burger centre — Selenium would hit a click-intercept by whichever sub-btn is z-topmost during the transform.
## Verification
All 1370 IT+UT green (+5 new MySeaViewTest, +1 from earlier phase 1 carry-over). 7 updated FTs green when re-run together.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
1061 lines
42 KiB
SCSS
1061 lines
42 KiB
SCSS
// 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.
|
||
|
||
body.page-gameboard {
|
||
.container {
|
||
overflow: clip;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
body.page-gameboard .container {
|
||
overflow: visible;
|
||
}
|
||
}
|
||
|
||
@media (orientation: landscape) {
|
||
// 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;
|
||
}
|
||
}
|
||
|
||
#id_applet_game_kit {
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
#id_game_kit {
|
||
flex: 1;
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
justify-content: space-evenly;
|
||
overflow-x: visible;
|
||
scrollbar-width: none;
|
||
&::-webkit-scrollbar { display: none; }
|
||
|
||
.token { position: static; }
|
||
|
||
.token:hover .token-tooltip,
|
||
.token:hover .tt { display: none; } // JS portal handles show/hide
|
||
|
||
.token,
|
||
.kit-item { font-size: 1.5rem; }
|
||
|
||
.kit-item { opacity: 0.6; }
|
||
}
|
||
}
|
||
|
||
// Sprint A.4 — card-deck stack icon (.deck-stack-icon) replaces the
|
||
// fa-regular fa-id-badge wherever a deck appears in icon form: gameboard's
|
||
// .token.deck-variant, kit-bag dialog's .kit-bag-deck, future room.html pile
|
||
// + deck-bag. Lifted out of the #id_applet_game_kit nest so the base sizing
|
||
// + rest-state SCSS applies in any deck-icon context. 3 stacked card-back
|
||
// rects, 5° CW rest tilt; see [[project-card-deck-icon]] for the design rules.
|
||
// When the deck has card-images, the rect fills are overridden inline w. an
|
||
// SVG <pattern> referencing the deck's <deck-slug>-back.png; otherwise the
|
||
// placeholder `fill: rgba(--priUser, 1)` shows through.
|
||
.deck-stack-icon {
|
||
display: inline-block;
|
||
// 2026-05-25 PM user spec: 1.5× the prior fa-id-badge visual weight
|
||
// since the icon is no longer constrained to placeholder-icon dimensions
|
||
// (now a meaningful first-class deck visualization).
|
||
width: 2.25rem; // 1.5rem × 1.5
|
||
height: 3.6rem; // 1.5× while preserving 5:8 tarot card aspect
|
||
color: rgba(var(--terUser), 1); // stroke color via currentColor
|
||
overflow: visible; // fan-out exceeds the viewBox bounds
|
||
filter: drop-shadow(0.08rem 0.08rem 0.15rem rgba(0, 0, 0, 0.6));
|
||
|
||
.deck-stack-icon__stack {
|
||
transform: rotate(5deg);
|
||
transform-origin: 50% 50%;
|
||
transform-box: fill-box;
|
||
transition: transform 0.25s ease;
|
||
}
|
||
|
||
.deck-stack-icon__card {
|
||
fill: rgba(var(--priUser), 1);
|
||
stroke: currentColor;
|
||
stroke-width: 1;
|
||
transform-origin: 50% 50%;
|
||
transform-box: fill-box;
|
||
transition: transform 0.25s ease;
|
||
}
|
||
// Rest: tightly stacked w. tiny vertical offsets (suggests stack
|
||
// depth without separating the cards visually).
|
||
.deck-stack-icon__card--1 { transform: translateY(-0.4px); }
|
||
.deck-stack-icon__card--3 { transform: translateY( 0.4px); }
|
||
}
|
||
|
||
// Sprint A.4 — fan-out trigger is wrapper-only (NOT self-triggered on the
|
||
// SVG itself) so placeholder-mode icons inside .kit-bag-placeholder stay
|
||
// static + dim like the empty dice slot. Real-deck wrappers (.token.deck-
|
||
// variant on gameboard, .kit-bag-deck in kit-bag dialog, future room.html
|
||
// pile + deck-bag) drive the splay; cards 2 + 3 fan out from under card 1,
|
||
// card 1 stays put. Tooltip portal is wired to the same `.token:hover` /
|
||
// `.kit-bag-deck:hover` triggers via JS so splay + tooltip-appearance
|
||
// co-activate.
|
||
.token.deck-variant:hover .deck-stack-icon,
|
||
.token.deck-variant:active .deck-stack-icon,
|
||
.token.deck-variant:focus .deck-stack-icon,
|
||
.kit-bag-deck:hover .deck-stack-icon,
|
||
.kit-bag-deck:active .deck-stack-icon,
|
||
.kit-bag-deck:focus .deck-stack-icon {
|
||
.deck-stack-icon__card--2 { transform: translate(-5px, -2px) rotate(-12deg); }
|
||
.deck-stack-icon__card--3 { transform: translate( 5px, -2px) rotate( 12deg); }
|
||
}
|
||
|
||
// Sprint A.4 — placeholder-mode dim styling. When the icon is inside a
|
||
// .kit-bag-placeholder (no deck equipped) or game_kit applet's empty-state
|
||
// .kit-item (no decks unlocked), color + fill drop to `--quaUser` at 0.3
|
||
// alpha to match the existing empty-slot treatment (`.kit-bag-placeholder`
|
||
// at `_game-kit.scss:143`, `.kit-item { opacity: 0.6 }` here). No animation
|
||
// since the wrapper isn't in the splay-trigger list above.
|
||
.kit-bag-placeholder .deck-stack-icon,
|
||
.kit-item .deck-stack-icon {
|
||
color: rgba(var(--quaUser), 0.15);
|
||
.deck-stack-icon__card { fill: rgba(var(--quiUser), 0.15); }
|
||
}
|
||
|
||
#id_applet_new_game {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
#id_applet_my_games {
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.applet-list {
|
||
flex: 1;
|
||
padding-top: 0.25rem;
|
||
}
|
||
}
|
||
|
||
#id_tooltip_portal {
|
||
position: fixed;
|
||
z-index: 9999;
|
||
|
||
padding: 0.75rem 1.5rem;
|
||
|
||
@extend %tt-token-fields;
|
||
|
||
.tt-equip-btns {
|
||
position: absolute;
|
||
left: -1rem;
|
||
top: -1rem;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.25rem;
|
||
z-index: 1;
|
||
|
||
.btn { margin: 0; }
|
||
}
|
||
|
||
// 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; }
|
||
|
||
&.active { display: block; }
|
||
}
|
||
|
||
#id_mini_tooltip_portal {
|
||
position: fixed;
|
||
z-index: 9999;
|
||
// Polish-8 — bumped from font-size 0.8em → 0.95em + added padding to
|
||
// give the mini-portal a bit more presence per user-spec "a bit bigger
|
||
// both in dimensions and font-size". Visually closer to the main
|
||
// tooltip's text scale w/o approaching it — still clearly subordinate.
|
||
font-size: 0.95em;
|
||
font-style: italic;
|
||
padding: 0.35rem 0.75rem;
|
||
border-radius: 0.3rem;
|
||
width: fit-content;
|
||
white-space: nowrap;
|
||
text-align: right;
|
||
|
||
&.active { display: block; }
|
||
}
|
||
|
||
@media (max-height: 500px) {
|
||
body.page-gameboard {
|
||
.container {
|
||
.row {
|
||
padding: 0.25rem 0;
|
||
.col-lg-6 h2 {
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// ─── My Sea sign-gate ────────────────────────────────────────────────────────
|
||
// REMOVED 2026-05-22 — refactored to a Brief banner. The no-sig nudge now
|
||
// fires via `Brief.showBanner` from `_my_sea_sign_gate_brief.html`, which
|
||
// portals a `.note-banner.my-sea-sign-gate-brief` to the page h2 (gaussian-
|
||
// glass shell, FYI → /billboard/my-sign/, NVM dismisses). All the inline
|
||
// `.my-sea-sign-gate{,--applet,__line,__actions,__back,__fyi}` styling
|
||
// dropped — `.note-banner` rules in `_note.scss:11` cover positioning,
|
||
// shell, + button placement DRYly.
|
||
|
||
// ─── 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;
|
||
|
||
// FREE DRAW btn — centered in the hex, mirrors SCAN SIGN's 2-line
|
||
// font sizing so "FREE/DRAW" sits cleanly inside the 4rem circle.
|
||
#id_draw_sea_btn {
|
||
white-space: normal;
|
||
}
|
||
|
||
// 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;
|
||
font-size: 0.8rem;
|
||
font-weight: 600;
|
||
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 {
|
||
color: rgba(var(--terUser), 1);
|
||
filter: drop-shadow(0 0 4px rgba(var(--ninUser), 1));
|
||
}
|
||
}
|
||
|
||
// 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);
|
||
}
|
||
|
||
// 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);
|
||
}
|
||
|
||
// 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;
|
||
}
|
||
|
||
.my-sea-picker {
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 1rem;
|
||
|
||
// 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;
|
||
}
|
||
}
|
||
|
||
// .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)
|
||
// 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,
|
||
.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,
|
||
.sea-pos-cross { display: none; }
|
||
}
|
||
|
||
.my-sea-cross[data-spread="desire-obstacle-solution"] {
|
||
.sea-pos-leave,
|
||
.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 — 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: 1;
|
||
color: rgba(var(--seciUser), 1);
|
||
text-shadow: 0 0 0.25rem rgba(var(--priUser), 1);
|
||
text-align: center;
|
||
pointer-events: none;
|
||
white-space: nowrap;
|
||
position: absolute;
|
||
// z-index 0 (was 2) — labels sit BEHIND the slot so the slot's
|
||
// downward shadow can visually obscure the label's top edge per the
|
||
// .sea-stack-name "tuck under" treatment (user spec 2026-05-26).
|
||
z-index: 0;
|
||
}
|
||
|
||
// 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; }
|
||
|
||
// CROWN + COVER labels — ABOVE the slot per user spec 2026-05-26.
|
||
// Cross convention: crown is at the top + cover is the central card
|
||
// being covered, so their labels belong above. `bottom: 100%` anchors
|
||
// label's bottom edge to slot's top; `-0.4rem` translate-Y pushes it
|
||
// further away so the labels read clearly w. breathing room.
|
||
.sea-pos-crown > .sea-pos-label,
|
||
.sea-pos-cover > .sea-pos-label {
|
||
bottom: 100%;
|
||
left: 50%;
|
||
transform: translate(-50%, -0.4rem) scaleY(1.2);
|
||
}
|
||
|
||
// LAY + CROSS labels — BELOW the slot. `0.3rem` translate-Y pushes each
|
||
// label a bit further from its slot's bottom edge than the prior tuck-
|
||
// under (which crowded the label up against the slot's border). The
|
||
// filled-card shadow (added below to `.sea-card-slot--filled`, NOT the
|
||
// empty slot per user clarification 2026-05-26) extends 0.25rem downward
|
||
// via the `$_sea-shadow` chain — covers the label's top edge w/o the
|
||
// label having to physically overlap the card's border-box.
|
||
.sea-pos-lay > .sea-pos-label,
|
||
.sea-pos-cross > .sea-pos-label {
|
||
top: 100%;
|
||
left: 50%;
|
||
transform: translate(-50%, 0.3rem) scaleY(1.2);
|
||
}
|
||
|
||
// Breathing room around COVER + CROSS labels — bump CROWN cell UP by
|
||
// 0.5rem + LAY cell DOWN by 0.5rem so the COVER label (below the central
|
||
// sig card) + CROSS label (also in the central row) have vertical space
|
||
// w/o colliding into the crown's slot from above or the lay's slot from
|
||
// below. Translate (not margin) so the surrounding grid layout doesn't
|
||
// reflow — cells stay in their grid-areas but visually shift.
|
||
.my-sea-cross .sea-pos-crown { transform: translateY(-0.5rem); }
|
||
.my-sea-cross .sea-pos-lay { transform: translateY( 0.5rem); }
|
||
|
||
// Filled-card downward shadow — only on the my-sea Cross page (NOT the
|
||
// picker or other surfaces using `.sea-card-slot`), and only the FILLED
|
||
// variant (the dashed empty slot stays shadowless per user clarification
|
||
// 2026-05-26: "the slots themselves should not have box-shadows ... only
|
||
// the cards that replace them should"). Mirrors `.sea-stack-face`'s
|
||
// `$_sea-shadow` chain in `_card-deck.scss:1976` (the GRAVITY/LEVITY deck
|
||
// stacks): solid-black 1px×2px offset shadow + a softer 4px-down spread +
|
||
// a 2px×5px blurred falloff. The image-mode slot still keeps its filter-
|
||
// chain contour-stroke + 1px depth shadow from `_card-deck.scss:837` — the
|
||
// box-shadow layers cleanly over it for a richer depth read.
|
||
.my-sea-cross .sea-card-slot--filled {
|
||
box-shadow:
|
||
1px 2px 0 rgba(0, 0, 0, 0.7),
|
||
0 4px 0 rgba(0, 0, 0, 0.18),
|
||
2px 5px 5px rgba(0, 0, 0, 0.5);
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
// Rotated-card shadow corrections — `box-shadow` rotates w. the element's
|
||
// `transform`, so any rotated card's down-right shadow rotates to a wrong
|
||
// direction. Each rotation case needs its offsets pre-inverted so the
|
||
// post-rotation render still reads down-right.
|
||
//
|
||
// Derivation: CSS rotate(θ) CW maps offset (a, b) → screen (a·cos θ −
|
||
// b·sin θ, a·sin θ + b·cos θ). To get screen (1, 2) after rotation, solve
|
||
// for unrotated (a, b). Below shows the result for each rotation in play:
|
||
// • 180° (reversed, `.sea-card-slot--reversed` at _card-deck:1616):
|
||
// (1, 2) → (−1, −2). Flip all signs.
|
||
// • 90° (cross, `.sea-pos-cross .sea-card-slot` at _card-deck:1705):
|
||
// (1, 2) → (2, −1). Swap + negate y.
|
||
// • 270° (cross + reversed at _card-deck:1620): (1, 2) → (−2, 1).
|
||
//
|
||
// Specificity ladder: base filled (0,2,0) < reversed-only (0,3,0) = cross-
|
||
// only (0,3,0) < cross+reversed (0,4,0). For a card matching multiple, the
|
||
// most-specific rule wins; cross-only + reversed-only tie at (0,3,0) but
|
||
// only one matches per card (cross OR non-cross), so no source-order trap.
|
||
.my-sea-cross .sea-card-slot--filled.sea-card-slot--reversed {
|
||
box-shadow:
|
||
-1px -2px 0 rgba(0, 0, 0, 0.7),
|
||
0 -4px 0 rgba(0, 0, 0, 0.18),
|
||
-2px -5px 5px rgba(0, 0, 0, 0.5);
|
||
}
|
||
.my-sea-cross .sea-pos-cross .sea-card-slot--filled {
|
||
box-shadow:
|
||
2px -1px 0 rgba(0, 0, 0, 0.7),
|
||
4px 0 0 rgba(0, 0, 0, 0.18),
|
||
5px -2px 5px rgba(0, 0, 0, 0.5);
|
||
}
|
||
.my-sea-cross .sea-pos-cross .sea-card-slot--filled.sea-card-slot--reversed {
|
||
box-shadow:
|
||
-2px 1px 0 rgba(0, 0, 0, 0.7),
|
||
-4px 0 0 rgba(0, 0, 0, 0.18),
|
||
-5px 2px 5px rgba(0, 0, 0, 0.5);
|
||
}
|
||
|
||
// 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.5 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 full.
|
||
.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;
|
||
}
|
||
|
||
// 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;
|
||
// User spec 2026-05-26: parity w. CROWN/LAY's ~0.4rem breathing room.
|
||
// Was `0.1rem` overlap (tuck-under); now `-0.4rem` pulls the label
|
||
// further LEFT, AWAY from the slot's left edge so it reads cleanly w/o
|
||
// colliding into the slot's border-radius zone.
|
||
transform: translate(-0.3rem, -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;
|
||
// User spec 2026-05-26: same `0.4rem` AWAY distance as LEAVE (mirrored).
|
||
transform: translate(0.3rem, -50%) scaleX(1.2);
|
||
}
|
||
|
||
// 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;
|
||
|
||
// 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;
|
||
}
|
||
|
||
// 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
|
||
// the form col despite the intermediate wrapper in the DOM.
|
||
//
|
||
// Selector chains `.sea-form-col.my-sea-form-col` to win against
|
||
// `_card-deck.scss`'s base `.sea-form-col { display: flex; flex-
|
||
// direction: column }` (same 1-class specificity; card-deck loads
|
||
// AFTER gameboard in `core.scss`, so source order would otherwise
|
||
// overrule my-sea's grid). Chained 2-class selector pulls
|
||
// specificity ahead regardless of source order.
|
||
@media (orientation: portrait) {
|
||
&.sea-form-col {
|
||
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; }
|
||
}
|
||
}
|
||
|
||
// 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;
|
||
}
|
||
|
||
// Portrait — open the SPREAD dropdown UPWARD instead of downward.
|
||
// The portrait form col sits at the bottom of the viewport (below
|
||
// the cross spread) w. the navbar/footer pinned beneath it, so the
|
||
// default `top: 100%` dropdown extends BELOW the visible aperture
|
||
// + the user can't scroll to it. Flipping to `bottom: 100%` makes
|
||
// the list grow upward into the abundant green aperture above.
|
||
// Chained `&.sea-form-col` to beat card-deck's later-loaded base
|
||
// (per [[feedback-scss-import-order-specificity]]).
|
||
@media (orientation: portrait) {
|
||
&.sea-form-col .sea-select .sea-select-list {
|
||
top: auto;
|
||
bottom: 100%;
|
||
margin: 0 0 0.2rem;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 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;
|
||
}
|
||
}
|
||
|
||
// 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 spread modal (Phase 2 of the burger Sea sub-btn rollout) ──
|
||
//
|
||
// Holds the .sea-form-col chrome (spread combobox + AUTO DRAW + DEL).
|
||
// Hidden by default via the `hidden` attribute; JS removes the attr to
|
||
// open. Backdrop is click-to-dismiss, Escape also closes (handlers in
|
||
// the inline <script> at the bottom of my_sea.html).
|
||
.my-sea-spread-modal {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 320; // above burger (314), kit (318), bud (318), dialog (316)
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
pointer-events: none; // children re-enable
|
||
|
||
&[hidden] {
|
||
// `hidden` attr would already display:none — kept explicit so
|
||
// any later cascade still respects the closed state.
|
||
display: none;
|
||
}
|
||
|
||
&__backdrop {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: rgba(0, 0, 0, 0.55);
|
||
backdrop-filter: blur(0.25rem);
|
||
pointer-events: auto;
|
||
}
|
||
|
||
&__panel {
|
||
position: relative;
|
||
z-index: 1;
|
||
background: rgba(var(--priUser), 1);
|
||
border: 0.15rem solid rgba(var(--secUser), 1);
|
||
border-radius: 0.75rem;
|
||
box-shadow:
|
||
0 0 1rem rgba(var(--secUser), 0.5),
|
||
0.15rem 0.15rem 0.5rem rgba(0, 0, 0, 0.5);
|
||
padding: 1.25rem;
|
||
pointer-events: auto;
|
||
max-width: 90vw;
|
||
|
||
.sea-form-col {
|
||
// Already-styled .sea-form-col container handles internal layout.
|
||
// Drop the fixed width inside the modal — modal panel sizes to
|
||
// content + the form is the content.
|
||
width: auto;
|
||
min-width: 16rem;
|
||
}
|
||
}
|
||
}
|
||
|
||
// ── Relocated deck-stacks (Phase 2) ──────────────────────────────────
|
||
//
|
||
// .sea-stacks was a child of .sea-form-col; now lives on the page in
|
||
// .my-sea-stacks-wrap so it stays visible when the spread modal is
|
||
// closed. Pin to the bottom-right of the aperture (above the bud +
|
||
// burger btns, below the modal).
|
||
.my-sea-stacks-wrap {
|
||
position: absolute;
|
||
bottom: 4.5rem; // clear of the bud/burger pair at bottom
|
||
right: 1rem;
|
||
z-index: 5; // above .my-sea-cross, below modal (320)
|
||
pointer-events: auto;
|
||
}
|
||
|
||
// ── Iter 4b: Brief banner + DEL guard portal ─────────────────────────────────
|
||
// 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.
|
||
|
||
|
||
// ── My Sea applet (billboard-style gameboard applet) ─────────────────────────
|
||
// The applet at `_applet-my-sea.html` lists the active draw's slots in
|
||
// DRAW_ORDER — drawn cards filled + empty slots placeholder'd, each
|
||
// w. a label caption tucked tight against the slot's bottom edge.
|
||
// Horizontal-scroll mirrors the Palettes applet (`.palette` in
|
||
// `_palette-picker.scss:1`): row of fixed-size items + `overflow-x:
|
||
// auto`, so 6-card spreads scroll while 3-card spreads fit. Slots use
|
||
// the same `.sig-stage-card` layout language as the my_sign.html stage
|
||
// card (corner-tl + face w. name + corner-br) at applet scale —
|
||
// container queries on `.my-sea-scroll` lift `--slot-w` to fill the
|
||
// scroll's vertical aperture (minus label) so cards span the whole
|
||
// applet height per user spec 2026-05-22.
|
||
#id_applet_my_sea {
|
||
display: flex;
|
||
flex-direction: column;
|
||
// Anchor for #id_applet_sky_delete_btn's absolute centering.
|
||
position: relative;
|
||
background-color: rgba(var(--duoUser), 1) !important;
|
||
|
||
h2 {
|
||
flex-shrink: 0;
|
||
background-color: rgba(var(--priUser), 1);
|
||
box-shadow: rgba(0, 0, 0, 1) !important;
|
||
}
|
||
|
||
.my-sea-scroll {
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: stretch;
|
||
gap: 0.75rem;
|
||
padding: 0.25rem 0.5rem 0.5rem;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
scroll-snap-type: x mandatory;
|
||
-webkit-overflow-scrolling: touch;
|
||
container-type: size;
|
||
}
|
||
|
||
.my-sea-slot-wrap {
|
||
flex: 0 0 auto;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
scroll-snap-align: start;
|
||
height: 100%;
|
||
// No gap — the label sits directly against the slot's bottom
|
||
// border per user spec ("tighter [...] practically overlapping").
|
||
// Slight negative margin pulls the label baseline up into the
|
||
// slot's border line so the two visually merge.
|
||
}
|
||
|
||
// Slot shell — 5:8 card, sized to fill the wrap's height minus the
|
||
// label row. `--slot-w` resolves via container queries: 100cqi-cap
|
||
// when the scroll is wide-but-shallow, 100cqh*5/8 = 62.5cqh - tiny-
|
||
// label-reservation otherwise. The `- 1rem` carves out the label
|
||
// row + tight gap so the card doesn't overshoot the applet floor.
|
||
.my-sea-slot {
|
||
--slot-w: min(100cqi, calc((100cqh - 1rem) * 5 / 8));
|
||
width: var(--slot-w);
|
||
aspect-ratio: 5 / 8;
|
||
border-radius: 0.4rem;
|
||
border: 0.12rem solid rgba(var(--secUser), 0.6);
|
||
padding: 0.35rem;
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
flex: 0 0 auto;
|
||
|
||
.fan-card-corner--tl,
|
||
.fan-card-corner--br {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
line-height: 1.05;
|
||
gap: 0.05rem;
|
||
position: absolute;
|
||
|
||
.fan-corner-rank {
|
||
font-size: calc(var(--slot-w) * 0.16);
|
||
font-weight: 700;
|
||
}
|
||
i { font-size: calc(var(--slot-w) * 0.13); }
|
||
}
|
||
.fan-card-corner--tl { top: 0.25rem; left: 0.3rem; }
|
||
.fan-card-corner--br {
|
||
bottom: 0.25rem; right: 0.3rem;
|
||
transform: rotate(180deg);
|
||
}
|
||
|
||
// `gap: 0` so qualifier sits directly above the title at the
|
||
// title's own line-height (no flex gap between them); `.fan-card-
|
||
// arcana` carries its own margin-top to restore breathing room
|
||
// between title block and arcana label.
|
||
.fan-card-face {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 0;
|
||
text-align: center;
|
||
padding: 0 0.2rem;
|
||
}
|
||
|
||
// Qualifier + title share the same typography (per `_card-deck.scss`
|
||
// convention at lines 568-572 / 1821-1823) — both bold, same size,
|
||
// same wrap, same line-height. Color inherits from the slot's
|
||
// polarity-driven `color:` (set on `--gravity` / `--levity`).
|
||
.fan-card-qualifier,
|
||
.fan-card-name {
|
||
margin: 0;
|
||
font-size: calc(var(--slot-w) * 0.105);
|
||
font-weight: 700;
|
||
line-height: 1.15;
|
||
text-wrap: balance;
|
||
}
|
||
.fan-card-qualifier:empty { display: none; }
|
||
|
||
.fan-card-arcana {
|
||
margin: calc(var(--slot-w) * 0.05) 0 0;
|
||
font-size: calc(var(--slot-w) * 0.07);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
opacity: 0.6;
|
||
}
|
||
}
|
||
|
||
// Filled slot polarity — mirrors `.sea-card-slot--gravity` / `--levity`
|
||
// in `_card-deck.scss:1332-1341`. Gravity = priUser bg + quiUser text;
|
||
// levity = inverted (secUser bg + priUser text). `.fan-card-name`,
|
||
// `.fan-card-qualifier`, `.fan-card-corner` + `.fan-card-arcana` all
|
||
// pin `color: inherit` so they pick up the slot's polarity color
|
||
// uniformly — the global `.fan-card-face .fan-card-name { color:
|
||
// --terUser }` rule in `_card-deck.scss:376-383` loads AFTER gameboard
|
||
// (per `core.scss` import order) and otherwise wins at matching 0,2,0
|
||
// specificity, stranding the title at --terUser while the qualifier
|
||
// inherits the slot color. Explicit `inherit` here at 0,3,0 beats it.
|
||
.my-sea-slot--filled.my-sea-slot--gravity {
|
||
background: rgba(var(--priUser), 1);
|
||
color: rgba(var(--quiUser), 1);
|
||
border-color: rgba(var(--secUser), 0.6);
|
||
.fan-card-corner { color: inherit; }
|
||
.fan-card-qualifier { color: inherit; }
|
||
.fan-card-name { color: inherit; }
|
||
.fan-card-arcana { color: inherit; opacity: 0.6; }
|
||
}
|
||
.my-sea-slot--filled.my-sea-slot--levity {
|
||
background: rgba(var(--secUser), 1);
|
||
color: rgba(var(--priUser), 1);
|
||
border-color: rgba(var(--priUser), 1);
|
||
.fan-card-corner { color: inherit; }
|
||
.fan-card-qualifier { color: inherit; }
|
||
.fan-card-name { color: inherit; }
|
||
.fan-card-arcana { color: inherit; opacity: 0.7; }
|
||
}
|
||
.my-sea-slot--filled.my-sea-slot--reversed { transform: rotate(180deg); }
|
||
|
||
// Sprint A.7 — image-mode slot override. `_card-deck.scss` imports
|
||
// after `_gameboard.scss`, but the polarity rules above (`.my-sea-slot--
|
||
// filled.my-sea-slot--gravity` / `--levity`) are nested inside
|
||
// `#id_applet_my_sea` (specificity 1,2,0) and beat the top-level shared
|
||
// `.my-sea-slot.my-sea-slot--image` rule (0,2,0) on bg + border + color.
|
||
// Re-state the transparency here at matching nested specificity so the
|
||
// PNG card-back is unobstructed. Filter-chain / contour-stroke / depth
|
||
// shadow on `.sig-stage-card-img` still come from the shared rule (no
|
||
// collision — different selector target).
|
||
//
|
||
// `overflow: visible` is critical — the base `.my-sea-slot` rule above
|
||
// sets `overflow: hidden` at (1,1,0) which BEATS the shared image-mode
|
||
// rule's `overflow: visible` at (0,2,0) on the ID axis. Without re-
|
||
// stating here, the fourfold contour-stroke drop-shadows get clipped
|
||
// by the slot bounding box → reads as a uniform rectangular frame
|
||
// around the image, defeating the "stroke follows alpha contour"
|
||
// illusion. See [[feedback-scss-id-context-specificity-trap]].
|
||
.my-sea-slot--filled.my-sea-slot--image {
|
||
background: transparent;
|
||
border: 0;
|
||
padding: 0;
|
||
overflow: visible;
|
||
}
|
||
|
||
// Empty slot — matches the my_sea.html picker's empty `.sea-card-
|
||
// slot` style (`_card-deck.scss:1299-1303`): 0.15rem DASHED border in
|
||
// --terUser at full opacity, --duoUser fill. Same width + dash
|
||
// frequency as the picker so the applet reads as a true "miniature"
|
||
// of the picker rather than a cousin w. different dotting cadence.
|
||
// `!important` on the three border properties: the base `.my-sea-
|
||
// slot` border shorthand sits at the same (1,1,0) specificity, so
|
||
// belt-and-suspenders the override.
|
||
.my-sea-slot--empty {
|
||
background-color: rgba(var(--duoUser), 1);
|
||
border-style: dashed !important;
|
||
border-color: rgba(var(--terUser), 1) !important;
|
||
border-width: 0.15rem !important;
|
||
}
|
||
|
||
// Label — sibling of the slot inside the wrap, sits BELOW the slot.
|
||
// User spec 2026-05-26: parity w. my_sea.html's `.sea-pos-lay` label
|
||
// treatment — label tucked just below the slot w. the filled card's
|
||
// downward shadow (added below to `.my-sea-slot--filled`) obscuring
|
||
// the label's top edge. Empty dashed slots stay shadowless so the
|
||
// label sits cleanly below them.
|
||
.my-sea-slot-label {
|
||
position: relative;
|
||
// z-index 0 (was 2) — slot's shadow lives on top + casts over the
|
||
// label's top edge; the label is behind the slot in stacking order.
|
||
z-index: 0;
|
||
// Small gap (was `-0.05rem` flush margin) so the label doesn't
|
||
// glue to the slot's bottom border on the empty-slot case where
|
||
// there's no shadow to bridge the visual gap.
|
||
margin-top: 0.15rem;
|
||
padding: 0 0.2rem;
|
||
font-size: 0.65rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
color: rgba(var(--secUser), 0.85);
|
||
text-shadow: 0 0 0.25rem rgba(var(--priUser), 1);
|
||
text-align: center;
|
||
white-space: nowrap;
|
||
line-height: 1.1;
|
||
transform: scaleY(1.3);
|
||
transform-origin: top center;
|
||
}
|
||
|
||
// Filled-card downward shadow — applet parity w. my_sea.html's
|
||
// `.my-sea-cross .sea-card-slot--filled` rule. Empty dashed slots
|
||
// stay shadowless (per user clarification 2026-05-26: only the cards
|
||
// that replace the slots should carry a shadow).
|
||
.my-sea-slot--filled {
|
||
box-shadow:
|
||
1px 2px 0 rgba(0, 0, 0, 0.7),
|
||
0 4px 0 rgba(0, 0, 0, 0.18),
|
||
2px 5px 5px rgba(0, 0, 0, 0.5);
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
// Reversed-card shadow inversion — parity w. my_sea.html's
|
||
// `.sea-card-slot--reversed` override below. The applet's `.my-sea-
|
||
// slot--reversed` (line 843) also adds `rotate(180deg)` which flips the
|
||
// shadow up-left; invert ALL offsets so post-rotation it reads down-
|
||
// right. See [[feedback-css-transform-rotates-box-shadow]] (or similar
|
||
// future-note) for the gotcha.
|
||
.my-sea-slot--filled.my-sea-slot--reversed {
|
||
box-shadow:
|
||
-1px -2px 0 rgba(0, 0, 0, 0.7),
|
||
0 -4px 0 rgba(0, 0, 0, 0.18),
|
||
-2px -5px 5px rgba(0, 0, 0, 0.5);
|
||
}
|
||
// `.my-sea-slot-label--empty` intentionally has NO per-state recolor
|
||
// — the empty-state label keeps the same `--secUser` ink as the
|
||
// filled-slot label per user spec 2026-05-22 (pins position identity
|
||
// Cover/Cross/etc. across the row regardless of fill state).
|
||
// Previously dimmed to --terUser to echo the dashed border tone —
|
||
// but that broke title cohesion when most slots were empty.
|
||
|
||
// No-draws empty state — centred italic, mirrors the Brief / applet-
|
||
// list-entry--empty pattern in `_billboard.scss:29-38`.
|
||
.my-sea-empty {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-align: center;
|
||
font-style: italic;
|
||
opacity: 0.6;
|
||
margin: 0;
|
||
}
|
||
}
|