Sea Select: rebuild as a felt + Gaussian spread modal, unify w. my_sea — TDD

Hollow out the gameroom DRAW SEA dark modal into the two surfaces my_sea
uses: a --duoUser felt (.sea-page--room) filling the hex pane where the
Celtic Cross deals, + a Gaussian spread modal (#id_sea_spread_modal: the
.sea-select combobox w. the two Celtic Cross 6-card opts ONLY, AUTO DRAW/DEL,
a mini preview, + a corner #id_sea_cancel NVM). Opened by DRAW SEA
(html.sea-open); the room gear's NVM (room-menu-sea) returns to the hex;
#id_text_btn + #id_sky_btn go inert while the felt is open.

- persist: epic:sea_save / sea_delete upsert the seat's Character.celtic_cross
  (none of my_sea's MySeaDraw quota/Brief machinery); room ctx adds
  saved_by_position + saved_sea_spread + sea_default_spread + hand_complete so
  a reload re-renders the filled cross. celtic_cross field already existed (no
  migration)
- mini spread preview (_sea_spread_preview.html) in BOTH the gameroom + my_sea
  modals — shape only, NEVER dealt to: SeaDeal scopes its slot queries to
  .sea-cross:not(.sea-cross--preview)
- always TWO deck stacks (Gravity + Levity) in the room Sea Select — the gamer
  draws from either populated half (sea_deck split), even a CARTE monodeck;
  unlike my_sea / Sig Select's polarization collapse
- glow coordination: the sky-saved glow is muted in the sky/sea phases
  (html.sky-open / sea-open / sea-entered); sea glow color --priYl -> --priId
  (distinct from sky's --priTk); the sea glow-machine fires at hand-COMPLETION
  (mirrors Sky Select), not during drawing
- guard copy "Auto deal cards?" -> "Auto Draw cards?" (match the AUTO DRAW btn)
- fix: drop the stale `html.sea-open #id_aperture_fill { opacity:1 }` — it
  painted the opaque z-90 fill over the z-5 felt so the spread flashed then
  vanished (same trap as the CAST SKY felt); removed the dead .sea-backdrop /
  .sea-overlay / .sea-modal-* SCSS
- tests: epic PickSeaPersistTest (7) + PickSeaUnifiedFeltTest (6) ITs; SeaDeal
  preview-scoping + BurgerSpec sky-glow-mute Jasmine specs; my_sea sig-card
  ITs scoped to .my-sea-cross (the preview adds a 2nd .sea-sig-card)

[[feedback-felt-aperture-fill-covers-felt]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-06-07 21:42:24 -04:00
parent 4322e1fc17
commit c037e876e2
21 changed files with 1100 additions and 293 deletions

View File

@@ -661,6 +661,25 @@ def _role_select_context(room, user, seat_param=None):
if sky_confirmed:
# Fall back to seat.significator for Characters created before the sync was added
ctx["my_tray_sig"] = confirmed_char.significator or _canonical_seat.significator
# DRAW SEA persistence — pre-render the saved Celtic-Cross hand from
# the seat's Character.celtic_cross so a reload lands the filled
# cross + the right spread (mirrors my-sea's saved_by_position, but
# Character-backed). Empty dict / "" when no sea drawn yet. The
# `_saved_by_position` helper lives in gameboard.views; deferred
# import dodges the module-load cycle (gameboard.views imports epic).
from apps.gameboard.views import _saved_by_position
_cc = confirmed_char.celtic_cross or {}
_sea_hand = _cc.get("hand") or []
ctx["saved_by_position"] = _saved_by_position(_sea_hand)
ctx["saved_sea_spread"] = _cc.get("spread") or ""
# DRAW SEA offers ONLY the two 6-card Celtic Cross spreads (user-spec
# 2026-06-07); default to the saved one, else the polarity-matched CC.
ctx["sea_default_spread"] = (
ctx["saved_sea_spread"]
or ("waite-smith" if user_polarity == "levity" else "escape-velocity")
)
# 6-card Celtic Cross is complete at 6 placed cards.
ctx["hand_complete"] = len(_sea_hand) >= 6
return ctx
@@ -1776,6 +1795,68 @@ def sky_delete(request, room_id):
return JsonResponse({'deleted': True})
def _confirmed_character_for(seat):
"""The seat's active (sky-confirmed, un-retired) Character, or None. DRAW
SEA only follows a confirmed sky, so the Celtic-Cross hand attaches here."""
if seat is None:
return None
return Character.objects.filter(
seat=seat, confirmed_at__isnull=False, retired_at__isnull=True,
).first()
@login_required
def sea_save(request, room_id):
"""Upsert the seat's Celtic-Cross spread hand onto its confirmed
Character.celtic_cross. The gameroom analogue of my_sea_lock, but
Character-backed — NONE of the solo MySeaDraw quota / FREE-PAID-DRAW /
Brief machinery (that's my-sea-only). Fires on each card placement
(manual FLIP or AUTO DRAW) so a navigate-away mid-draw still persists.
POST body (JSON): {"spread": "<slug>", "hand": [{position, card_id,
reversed, polarity}, ...]} — the FULL current hand (partial OK mid-draw).
Returns {ok} 200 · 400 malformed · 403 not own seat / sky not confirmed.
"""
if request.method != 'POST':
return HttpResponse(status=405)
room = Room.objects.get(id=room_id)
seat = _canonical_user_seat(room, request.user)
if seat is None:
return HttpResponse(status=403)
try:
body = json.loads(request.body or b'{}')
except json.JSONDecodeError:
return HttpResponse(status=400)
spread = body.get('spread')
hand = body.get('hand')
if not spread or not isinstance(hand, list) or not hand:
return JsonResponse({'error': 'spread_and_hand_required'}, status=400)
char = _confirmed_character_for(seat)
if char is None:
return HttpResponse(status=403)
char.celtic_cross = {'spread': spread, 'hand': hand}
char.save(update_fields=['celtic_cross'])
return JsonResponse({'ok': True})
@login_required
def sea_delete(request, room_id):
"""Clear the seat's Celtic-Cross hand (celtic_cross=None). The DRAW SEA DEL
targets this; the Character + its confirmed sky stay intact (only the sea
spread is dropped, so the gamer can re-draw)."""
if request.method != 'POST':
return HttpResponse(status=405)
room = Room.objects.get(id=room_id)
seat = _canonical_user_seat(room, request.user)
if seat is None:
return HttpResponseForbidden()
char = _confirmed_character_for(seat)
if char is not None and char.celtic_cross is not None:
char.celtic_cross = None
char.save(update_fields=['celtic_cross'])
return JsonResponse({'deleted': True})
@login_required
def sea_deck(request, room_id):
"""Shuffled deck lists (levity + gravity halves) for DRAW SEA draw.