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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user