diff --git a/src/static_src/scss/_card-deck.scss b/src/static_src/scss/_card-deck.scss
index 85fd280..e6f5857 100644
--- a/src/static_src/scss/_card-deck.scss
+++ b/src/static_src/scss/_card-deck.scss
@@ -1696,15 +1696,35 @@ $sea-card-h: 6.5rem;
.sea-pos-cover .sea-card-slot--visible { opacity: 0.3; animation: sea-cover-appear 2s ease; }
.sea-pos-cross .sea-card-slot--visible { opacity: 0.15; animation: sea-cross-appear 2s ease; }
-// Hover: reveal fully (snappy)
-.sea-pos-cover .sea-card-slot--visible:hover,
-.sea-pos-cross .sea-card-slot--visible:hover { opacity: 1; transition: opacity 0.15s ease; }
+// ── Spread-card hover-glow + active-persist (user-spec 2026-05-30) ──────────
+// Mirror the deck-stack's hover / `--active` glow onto EACH spread card: the
+// customary --ninUser selection halo appears on HOVER + PERSISTS on the active
+// (`.sea-card-slot--focused`) two-tap selection sea.js sets. Builds on the
+// cover/cross opacity-reveal rules above.
+//
+// `!important`: the filled-card drop-shadow ladder (lines ~498-542) climbs to
+// 0-4-0, so on a hovered/focused rotated card it out-specifies a plain
+// `.sea-card-slot--focused` box-shadow (0-1-0) — exactly why the PRIOR focused
+// glow silently never rendered (verified 2026-05-30). The halo is symmetric so
+// it's rotation-invariant; it replaces the tiny directional drop (the
+// established focused intent). No colour change — box-shadow only.
+$_sea-card-glow: 0 0 0.5rem 0.5rem rgba(var(--ninUser), 0.3), 0 0 0.4rem rgba(0, 0, 0, 0.85);
-// Focused (first tap): persist at opacity 1 + selection glow until modal opens
+// Hover: Cover/Cross bump opacity to full (they overlay the semi-transparent
+// sig); every position then gains the halo.
+.sea-pos-cover .sea-card-slot--visible:hover,
+.sea-pos-cross .sea-card-slot--visible:hover { opacity: 1; }
+.my-sea-cross .sea-card-slot--visible:hover {
+ box-shadow: $_sea-card-glow !important;
+ transition: opacity 0.15s ease, box-shadow 0.15s ease;
+}
+
+// Active/persist (first tap): hold full opacity + the halo until the second tap
+// opens the stage (sea.js two-tap pattern, all positions).
.sea-card-slot--focused {
opacity: 1 !important;
transition: opacity 0.15s ease, box-shadow 0.15s ease;
- box-shadow: 0 0 0.5rem 0.25rem rgba(var(--ninUser), 0.35), 0 0 0.4rem rgba(0, 0, 0, 0.85);
+ box-shadow: $_sea-card-glow !important;
}
// Cover + Cross — absolutely overlaid on the Sig card in .sea-pos-core
@@ -2000,7 +2020,27 @@ $sea-card-h: 6.5rem;
margin: 0 auto;
transform: translateY(-50%);
z-index: 5;
+ // FLIP reveal — UNIFIED across the owner picker (my_sea) + the read-only
+ // spectator (my_sea_visit), user-spec 2026-05-30. Hidden + non-interactive
+ // by default; HOVER / focus fades it in ephemerally; a CLICK PERSISTS it via
+ // the JS-set `.sea-deck-stack--active` class (the same class the face-glow
+ // rules below persist on). Was split: the owner toggled `display` on click
+ // only (no hover); the spectator faded on hover only (no persist).
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 0.3s ease;
}
+.sea-deck-stack:hover .sea-stack-ok,
+.sea-deck-stack:focus-within .sea-stack-ok,
+.sea-deck-stack--active .sea-stack-ok { opacity: 1; }
+// Interactivity is gated on the PERSIST state (`--active`), NOT hover. Hover is
+// a purely VISUAL preview — same as the spectator's disabled FLIP, whose "hover
+// effect" is just the reveal. Keeping the FLIP click-through on hover preserves
+// the owner's two-step deal: click the stack (→ `--active`) THEN click the FLIP.
+// Were it clickable on hover, a single stack-click would land on the centred
+// FLIP itself + deal early. The spectator's FLIP stays `.btn-disabled` (read-
+// only) so it never becomes interactive even while persisted.
+.sea-deck-stack--active .sea-stack-ok:not(.btn-disabled) { pointer-events: auto; }
.sea-deck-stack { gap: 0; } // remove gap so name slides under the face
@@ -2021,8 +2061,13 @@ $sea-card-h: 6.5rem;
// Deck backs — face-down pile colour identifies polarity
$_sea-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);
-$_glow-levity: 0 0 0.8rem 0.15rem rgba(var(--ninUser), 0.6);
-$_glow-gravity: 0 0 0.8rem 0.15rem rgba(var(--quaUser), 0.6);
+// Levity + gravity stack glows mirror the spread-card halo's tuned geometry
+// ($_sea-card-glow, user-tuned 2026-05-30: 0.5rem blur / 0.5rem spread / 0.3
+// alpha) so the deck glows read identically to the card halo — each keeps its
+// own polarity colour (--ninUser / --quaUser). Duplicated literally (not
+// shared) so each stays independently tunable. (single keeps its own tone.)
+$_glow-levity: 0 0 0.5rem 0.5rem rgba(var(--ninUser), 0.3);
+$_glow-gravity: 0 0 0.5rem 0.5rem rgba(var(--ninUser), 0.3);
.sea-deck-stack--levity .sea-stack-face {
background: rgba(var(--terUser), 0.88);
diff --git a/src/static_src/scss/_gameboard.scss b/src/static_src/scss/_gameboard.scss
index e89ddfe..e26d483 100644
--- a/src/static_src/scss/_gameboard.scss
+++ b/src/static_src/scss/_gameboard.scss
@@ -808,20 +808,11 @@ body.page-gameboard {
transform-origin: center;
}
- // Read-only FLIP (disabled ×) — hidden until its stack is hovered/focused,
- // then eases in: same reveal shape as the shared `flip-btn-base` mixin
- // (opacity 0→1 over 0.3s ease), inlined here because _gameboard.scss is
- // imported before _card-deck.scss (where the mixin lives). Stays non-
- // interactive — `.btn-disabled` keeps pointer-events:none. The base
- // `.sea-stack-ok` positioning is untouched (we only add the fade).
- .sea-stack-ok {
- opacity: 0;
- transition: opacity 0.3s ease;
- }
- .sea-deck-stack:hover .sea-stack-ok,
- .sea-deck-stack:focus-within .sea-stack-ok {
- opacity: 1;
- }
+ // FLIP reveal (hover-fade + click-persist) is now the SHARED `.sea-stack-ok`
+ // behaviour in _card-deck.scss — unified with the owner picker per user-spec
+ // 2026-05-30, so the visitor's prior hover-only override is gone. The
+ // spectator's click-persist (`.sea-deck-stack--active`) is wired in
+ // my_sea_visit.html (the read-only FLIP stays disabled throughout).
}
// ── Iter 4b: Brief banner + DEL guard portal ─────────────────────────────────
diff --git a/src/templates/apps/gameboard/my_sea.html b/src/templates/apps/gameboard/my_sea.html
index 8cdc045..5fa9424 100644
--- a/src/templates/apps/gameboard/my_sea.html
+++ b/src/templates/apps/gameboard/my_sea.html
@@ -357,10 +357,12 @@
return DRAW_ORDER[hidden.value] || [];
}
+ // FLIP visibility is now CSS-driven (opacity on hover/focus +
+ // the `.sea-deck-stack--active` persist class — unified w. the
+ // spectator page, user-spec 2026-05-30). `_showOk`/`_hideOk` just
+ // toggle `--active`; no more inline `display` juggling.
function _hideOk() {
if (_activeStack) {
- var ok = _activeStack.querySelector('.sea-stack-ok');
- if (ok) ok.style.display = 'none';
_activeStack.classList.remove('sea-deck-stack--active');
_activeStack = null;
}
@@ -369,8 +371,6 @@
_hideOk();
_activeStack = stack;
stack.classList.add('sea-deck-stack--active');
- var ok = stack.querySelector('.sea-stack-ok');
- if (ok) ok.style.display = '';
}
function _fillSlot(positionName, card, isLevity) {
@@ -529,7 +529,6 @@
});
var ok = stack.querySelector('.sea-stack-ok');
if (ok) {
- ok.style.display = 'none';
ok.addEventListener('click', function (e) {
e.stopPropagation();
// Hand complete → FLIP is disabled. No draw.
diff --git a/src/templates/apps/gameboard/my_sea_visit.html b/src/templates/apps/gameboard/my_sea_visit.html
index 7799689..594110d 100644
--- a/src/templates/apps/gameboard/my_sea_visit.html
+++ b/src/templates/apps/gameboard/my_sea_visit.html
@@ -137,6 +137,34 @@
}
}());
+ {# Deck-stack persist — the spectator's FLIP is disabled (read-only), but #}
+ {# clicking a stack PERSISTS its glow + the revealed × via the shared #}
+ {# `.sea-deck-stack--active` class (same persist the owner picker uses, #}
+ {# user-spec 2026-05-30). Hover-reveal + the fade are the shared CSS; this #}
+ {# only adds the click-to-lock. Clicking another stack / elsewhere clears. #}
+
{# Async witness — a WS to `mysea_` pushes the owner's hand as each #}
{# card lands; SeaDeal.register fills the cross slot (+ seeds it clickable) #}
{# live, no refresh. A DEL (empty hand) re-empties the cleared slots. #}