My Sea DRAW SEA landing — Sprint 5 iter 1 of My Sea roadmap — TDD
DRAW SEA landing UX on /gameboard/my-sea/ for users past the [[sprint-my-sea-sign-gate-may19]] gate. DRY table hex (reused from the room shell + my-sign Sprint 4a iter 3) w. 6 chair seats labeled 1C-6C (placeholder for friend-invite per the My Sea roadmap "Six chairs retained even in solo" anchor) + central DRAW SEA `.btn-primary` mirroring SCAN SIGN on /billboard/my-sign/. Click swaps `.my-sea-page[data-phase]` from `landing` to `picker`; the picker UX itself (three-card cross w. cover/leave/loom + form col / spread dropdown / decks / LOCK HAND / DEL) lands in iters 2 + 3. The 'C' suffix on the chair labels = "Chair" (user-locked); no role semantics (this is a solo draw, not a 6-player role assignment). `.table-seat` CSS class + `data-slot` attribute preserved so the room's existing `[data-slot="N"]` positioning rules (`_room.scss` L583-588) carry over for free — no SCSS fork; just a new `.seat-label` span inside each seat. The 'Default deck warning' Brief banner from /billboard/my-sign/ fires verbatim when `user.equipped_deck` is None (the user is headed for a draw against the Earthman [Shabby Cardstock] backup unless they equip one first). Tagged `.my-sea-intro-banner` so FTs disambiguate from other Briefs. Same FYI (→ /gameboard/) / NVM (dismiss) action grammar. Bundled: BACK→NVM label swap (user-edited mid-sprint) in the existing sign-gate. CSS class `.my-sea-sign-gate__back` retained — the swap was label-only — so existing FTs targeting the class still pass; docstrings + comments updated for accuracy. Files: - `apps/gameboard/views.py` — `my_sea` view adds 2 context keys: `no_equipped_deck` (bool) + `show_backup_intro_banner` (= user_has_sig AND no_equipped_deck). The sig-gate path still wins precedence. - `templates/apps/gameboard/my_sea.html` — `.my-sea-page[data-phase="landing"]`; new `.my-sea-landing` block w. room-shell hex + `#id_draw_sea_btn` + 6 `.table-seat[data-slot="N"]` w. `<span class="seat-label">NC</span>`; new `.my-sea-picker` placeholder (`display:none` til DRAW SEA click); inline `<script>` for the click→data-phase swap + scaleTable re-fire on next tick (mirrors my-sign's iter-3 RAF dispatch); copies the my-sign Brief banner script block verbatim w. `.my-sea-intro-banner` post-render tag. - `static_src/scss/_gameboard.scss` — new `.my-sea-page` + `.my-sea-landing` + `.my-sea-picker` rule blocks mirroring `.my-sign-page` / `.my-sign-landing` from `_card-deck.scss`. `.seat-label` styled in `--terUser` to match the chair iconography. - `apps/gameboard/tests/integrated/test_views.py` — `+7 ITs` in new `MySeaDrawSeaLandingViewTest` pinning the context keys + presence/absence of `#id_draw_sea_btn` + 6 `data-slot=N` w. `NC` labels + Sprint 4b gate's precedence over the new landing. - `functional_tests/test_game_my_sea.py` — `+5 FTs` in new `MySeaDrawSeaLandingTest` for the visible UX (hex + DRAW SEA, 6 seats labeled 1C-6C, click→picker phase swap, Brief banner on no-deck, no banner when deck equipped). Uses new `_assign_sig` helper from [[sprint-sig-page-helper-may19c]] for the user-w-sig precondition. Tests: 25/25 FTs green across test_bill_my_sign + test_game_my_sea in 219s; 1036/1036 IT/UT green in 50s (+7 from baseline). Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -512,3 +512,61 @@ class MySeaViewTest(TestCase):
|
|||||||
response = self.client.get(reverse("my_sea"))
|
response = self.client.get(reverse("my_sea"))
|
||||||
self.assertIn("page-gameboard", response.content.decode())
|
self.assertIn("page-gameboard", response.content.decode())
|
||||||
self.assertIn("page-my-sea", response.content.decode())
|
self.assertIn("page-my-sea", response.content.decode())
|
||||||
|
|
||||||
|
|
||||||
|
class MySeaDrawSeaLandingViewTest(TestCase):
|
||||||
|
"""Sprint 5 iter 1 — view context for the DRAW SEA landing UX. Pins
|
||||||
|
`no_equipped_deck` + `show_backup_intro_banner` context keys + the
|
||||||
|
presence of the new landing template elements when user passes the
|
||||||
|
Sprint 4b sign-gate."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
from apps.epic.models import personal_sig_cards
|
||||||
|
self.user = User.objects.create(email="draw@test.io")
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
# Assign a sig so the view's landing branch (not the gate) renders.
|
||||||
|
self.user.significator = personal_sig_cards(self.user)[0]
|
||||||
|
self.user.save(update_fields=["significator"])
|
||||||
|
|
||||||
|
def test_context_no_equipped_deck_false_when_user_has_deck(self):
|
||||||
|
# post_save auto-equips Earthman; `no_equipped_deck` should be False.
|
||||||
|
response = self.client.get(reverse("my_sea"))
|
||||||
|
self.assertFalse(response.context["no_equipped_deck"])
|
||||||
|
|
||||||
|
def test_context_no_equipped_deck_true_when_user_cleared_deck(self):
|
||||||
|
self.user.equipped_deck = None
|
||||||
|
self.user.save(update_fields=["equipped_deck"])
|
||||||
|
response = self.client.get(reverse("my_sea"))
|
||||||
|
self.assertTrue(response.context["no_equipped_deck"])
|
||||||
|
|
||||||
|
def test_context_show_backup_intro_banner_when_no_deck_and_has_sig(self):
|
||||||
|
# Brief banner fires when user has a sig AND no deck — they're on the
|
||||||
|
# landing UX (gate passed) but headed for the backup-deck draw path.
|
||||||
|
self.user.equipped_deck = None
|
||||||
|
self.user.save(update_fields=["equipped_deck"])
|
||||||
|
response = self.client.get(reverse("my_sea"))
|
||||||
|
self.assertTrue(response.context["show_backup_intro_banner"])
|
||||||
|
|
||||||
|
def test_context_show_backup_intro_banner_false_when_deck_equipped(self):
|
||||||
|
response = self.client.get(reverse("my_sea"))
|
||||||
|
self.assertFalse(response.context["show_backup_intro_banner"])
|
||||||
|
|
||||||
|
def test_landing_renders_draw_sea_btn_when_sig_set(self):
|
||||||
|
response = self.client.get(reverse("my_sea"))
|
||||||
|
self.assertContains(response, 'id="id_draw_sea_btn"')
|
||||||
|
|
||||||
|
def test_landing_renders_six_chair_seats_with_C_suffix(self):
|
||||||
|
response = self.client.get(reverse("my_sea"))
|
||||||
|
html = response.content.decode()
|
||||||
|
for n in range(1, 7):
|
||||||
|
with self.subTest(slot=n):
|
||||||
|
self.assertIn(f'data-slot="{n}"', html)
|
||||||
|
self.assertIn(f"{n}C", html)
|
||||||
|
|
||||||
|
def test_landing_not_rendered_when_user_has_no_sig(self):
|
||||||
|
# Sprint 4b gate still wins precedence — DRAW SEA must not render
|
||||||
|
# when significator is None.
|
||||||
|
self.user.significator = None
|
||||||
|
self.user.save(update_fields=["significator"])
|
||||||
|
response = self.client.get(reverse("my_sea"))
|
||||||
|
self.assertNotContains(response, 'id="id_draw_sea_btn"')
|
||||||
|
|||||||
@@ -170,16 +170,23 @@ def toggle_game_kit_sections(request):
|
|||||||
def my_sea(request):
|
def my_sea(request):
|
||||||
"""Shell view for the My Sea standalone page.
|
"""Shell view for the My Sea standalone page.
|
||||||
|
|
||||||
Sprint 3 scaffolding + Sprint 4b sign-gate. The gate fires when the
|
Branches three ways:
|
||||||
user has no saved significator — a Look!-formatted Brief-style line
|
|
||||||
nudges them to /billboard/my-sign/ (FYI) or back to /gameboard/
|
1. No sig → Look!-formatted gate w. FYI/NVM (Sprint 4b).
|
||||||
(BACK) before the draw UX can be reached. With a sig set, the draw
|
2. Sig + equipped deck → DRAW SEA landing (Sprint 5 iter 1) — hex w.
|
||||||
shell renders normally (gatekeeper / sig-select / sea-select land
|
6 chair seats labeled 1C-6C + central DRAW SEA btn. Click swaps
|
||||||
in Sprints 5-9).
|
data-phase to picker (the picker UX itself lands in iter 2).
|
||||||
|
3. Sig + no equipped deck → same landing PLUS a 'Default deck warning'
|
||||||
|
Brief banner identical to the one on /billboard/my-sign/ (the user
|
||||||
|
is headed for a draw against the Earthman [Shabby Cardstock]
|
||||||
|
backup deck unless they equip one first).
|
||||||
"""
|
"""
|
||||||
user_has_sig = request.user.significator_id is not None
|
user_has_sig = request.user.significator_id is not None
|
||||||
|
no_equipped_deck = request.user.equipped_deck_id is None
|
||||||
return render(request, "apps/gameboard/my_sea.html", {
|
return render(request, "apps/gameboard/my_sea.html", {
|
||||||
"user_has_sig": user_has_sig,
|
"user_has_sig": user_has_sig,
|
||||||
|
"no_equipped_deck": no_equipped_deck,
|
||||||
|
"show_backup_intro_banner": user_has_sig and no_equipped_deck,
|
||||||
"page_class": "page-gameboard page-my-sea",
|
"page_class": "page-gameboard page-my-sea",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Sprint 4b of [[project-my-sea-roadmap]]. The /gameboard/my-sea/ page is
|
Sprint 4b of [[project-my-sea-roadmap]]. The /gameboard/my-sea/ page is
|
||||||
gated behind sig selection — when `user.significator` is None, render a
|
gated behind sig selection — when `user.significator` is None, render a
|
||||||
Look!-formatted Brief-style line w. FYI (→ /billboard/my-sign/) + BACK
|
Look!-formatted Brief-style line w. FYI (→ /billboard/my-sign/) + NVM
|
||||||
(→ /gameboard/) instead of the draw UX. The My Sea applet on /gameboard/
|
(→ /gameboard/) instead of the draw UX. The My Sea applet on /gameboard/
|
||||||
mirrors the gate hint in its empty-state slot.
|
mirrors the gate hint in its empty-state slot.
|
||||||
"""
|
"""
|
||||||
@@ -36,7 +36,7 @@ def _seed_gameboard_applets():
|
|||||||
class MySeaSignGateTest(FunctionalTest):
|
class MySeaSignGateTest(FunctionalTest):
|
||||||
"""Sign-gate UX on the standalone /gameboard/my-sea/ page + the
|
"""Sign-gate UX on the standalone /gameboard/my-sea/ page + the
|
||||||
/gameboard/ My Sea applet. User without a saved sig sees a Look!-
|
/gameboard/ My Sea applet. User without a saved sig sees a Look!-
|
||||||
formatted nudge w. FYI to the picker + BACK to the gameboard."""
|
formatted nudge w. FYI to the picker + NVM to the gameboard."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
@@ -55,7 +55,7 @@ class MySeaSignGateTest(FunctionalTest):
|
|||||||
|
|
||||||
def test_no_sig_renders_lookline_gate_on_standalone_page(self):
|
def test_no_sig_renders_lookline_gate_on_standalone_page(self):
|
||||||
"""User without significator → /gameboard/my-sea/ shows the Look!-
|
"""User without significator → /gameboard/my-sea/ shows the Look!-
|
||||||
formatted Brief-style line w. the gate copy + FYI + BACK buttons."""
|
formatted Brief-style line w. the gate copy + FYI + NVM buttons."""
|
||||||
self.create_pre_authenticated_session(self.email)
|
self.create_pre_authenticated_session(self.email)
|
||||||
self.browser.get(self.live_server_url + "/gameboard/my-sea/")
|
self.browser.get(self.live_server_url + "/gameboard/my-sea/")
|
||||||
gate = self.wait_for(
|
gate = self.wait_for(
|
||||||
@@ -67,11 +67,12 @@ class MySeaSignGateTest(FunctionalTest):
|
|||||||
self.assertIn("Look!", text)
|
self.assertIn("Look!", text)
|
||||||
self.assertIn("pick your sign", text.lower())
|
self.assertIn("pick your sign", text.lower())
|
||||||
self.assertIn("drawing the Sea", text)
|
self.assertIn("drawing the Sea", text)
|
||||||
# FYI + BACK action buttons
|
# FYI + NVM action buttons (class .my-sea-sign-gate__back retained
|
||||||
|
# post-relabel; the BACK→NVM swap was label-only).
|
||||||
fyi = gate.find_element(By.CSS_SELECTOR, ".my-sea-sign-gate__fyi")
|
fyi = gate.find_element(By.CSS_SELECTOR, ".my-sea-sign-gate__fyi")
|
||||||
self.assertTrue(fyi.is_displayed())
|
self.assertTrue(fyi.is_displayed())
|
||||||
back = gate.find_element(By.CSS_SELECTOR, ".my-sea-sign-gate__back")
|
nvm = gate.find_element(By.CSS_SELECTOR, ".my-sea-sign-gate__back")
|
||||||
self.assertTrue(back.is_displayed())
|
self.assertTrue(nvm.is_displayed())
|
||||||
|
|
||||||
# ── Test 2 ───────────────────────────────────────────────────────────────
|
# ── Test 2 ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -93,18 +94,19 @@ class MySeaSignGateTest(FunctionalTest):
|
|||||||
# ── Test 3 ───────────────────────────────────────────────────────────────
|
# ── Test 3 ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def test_gate_back_links_to_gameboard(self):
|
def test_gate_back_links_to_gameboard(self):
|
||||||
"""BACK button is an `<a href>` pointing at /gameboard/."""
|
"""NVM button is an `<a href>` pointing at /gameboard/. CSS class
|
||||||
|
`.my-sea-sign-gate__back` retained post BACK→NVM label swap."""
|
||||||
self.create_pre_authenticated_session(self.email)
|
self.create_pre_authenticated_session(self.email)
|
||||||
self.browser.get(self.live_server_url + "/gameboard/my-sea/")
|
self.browser.get(self.live_server_url + "/gameboard/my-sea/")
|
||||||
back = self.wait_for(
|
nvm = self.wait_for(
|
||||||
lambda: self.browser.find_element(
|
lambda: self.browser.find_element(
|
||||||
By.CSS_SELECTOR, ".my-sea-sign-gate__back"
|
By.CSS_SELECTOR, ".my-sea-sign-gate__back"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
href = back.get_attribute("href") or ""
|
href = nvm.get_attribute("href") or ""
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
href.endswith("/gameboard/"),
|
href.endswith("/gameboard/"),
|
||||||
f"BACK should link to /gameboard/, got {href!r}",
|
f"NVM should link to /gameboard/, got {href!r}",
|
||||||
)
|
)
|
||||||
|
|
||||||
# ── Test 4 ───────────────────────────────────────────────────────────────
|
# ── Test 4 ───────────────────────────────────────────────────────────────
|
||||||
@@ -159,3 +161,134 @@ class MySeaSignGateTest(FunctionalTest):
|
|||||||
)),
|
)),
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MySeaDrawSeaLandingTest(FunctionalTest):
|
||||||
|
"""Sprint 5 iter 1 — DRAW SEA landing on /gameboard/my-sea/ for a
|
||||||
|
user w. a saved sig (past the [[sprint-my-sea-sign-gate-may19]] gate).
|
||||||
|
|
||||||
|
Landing renders a DRY table hex (parameterized from the room) w. 6
|
||||||
|
chair seats labeled 1C-6C (placeholders for the eventual friend-
|
||||||
|
invite feature per [[project-my-sea-roadmap]] architectural anchor
|
||||||
|
"Six chairs retained even in solo") + a central DRAW SEA `.btn-
|
||||||
|
primary` mirroring SCAN SIGN on /billboard/my-sign/. The same Brief
|
||||||
|
"Default deck warning" copy from my-sign fires when the user has no
|
||||||
|
equipped deck.
|
||||||
|
|
||||||
|
Iter 1 scope: landing render + DRAW SEA click swaps `data-phase` to
|
||||||
|
`picker` (picker UX itself lands in iter 2). Form-col (spread
|
||||||
|
dropdown / decks / LOCK HAND / DEL) lands in iter 3."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
_seed_earthman_sig_pile()
|
||||||
|
_seed_gameboard_applets()
|
||||||
|
self.email = "draw@test.io"
|
||||||
|
self.gamer = User.objects.create(email=self.email)
|
||||||
|
# Assign a sig so the page passes the Sprint 4b gate + lands on
|
||||||
|
# the new DRAW SEA UX rather than the Look!-line gate.
|
||||||
|
self.target_card = _assign_sig(self.gamer)
|
||||||
|
|
||||||
|
# ── Test 1 ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def test_landing_renders_hex_with_draw_sea_btn(self):
|
||||||
|
"""User w. sig → /gameboard/my-sea/ shows the DRY table hex (re-
|
||||||
|
used from my-sign / the room shell) w. a central DRAW SEA btn."""
|
||||||
|
self.create_pre_authenticated_session(self.email)
|
||||||
|
self.browser.get(self.live_server_url + "/gameboard/my-sea/")
|
||||||
|
# data-phase=landing on the page wrapper
|
||||||
|
page = self.wait_for(
|
||||||
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".my-sea-page[data-phase='landing']")
|
||||||
|
)
|
||||||
|
# Hex shell present
|
||||||
|
page.find_element(By.CSS_SELECTOR, ".room-shell .table-hex")
|
||||||
|
# DRAW SEA btn in hex center
|
||||||
|
btn = page.find_element(By.CSS_SELECTOR, "#id_draw_sea_btn")
|
||||||
|
self.assertTrue(btn.is_displayed())
|
||||||
|
self.assertIn("DRAW", btn.text.upper())
|
||||||
|
self.assertIn("SEA", btn.text.upper())
|
||||||
|
self.assertIn("btn-primary", btn.get_attribute("class"))
|
||||||
|
|
||||||
|
# ── Test 2 ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def test_landing_renders_six_chair_seats_labeled_1C_to_6C(self):
|
||||||
|
"""All 6 chair positions render w. labels 1C-6C (placeholder for
|
||||||
|
friend-invite). CSS class `.table-seat` is preserved so the SCSS
|
||||||
|
positioning rules (data-slot=N) carry over from the room shell."""
|
||||||
|
self.create_pre_authenticated_session(self.email)
|
||||||
|
self.browser.get(self.live_server_url + "/gameboard/my-sea/")
|
||||||
|
seats = self.wait_for(
|
||||||
|
lambda: self._six_seats()
|
||||||
|
)
|
||||||
|
self.assertEqual(len(seats), 6)
|
||||||
|
labels = [
|
||||||
|
"".join(s.text.upper().split()) for s in seats
|
||||||
|
]
|
||||||
|
for n in range(1, 7):
|
||||||
|
with self.subTest(slot=n):
|
||||||
|
self.assertIn(f"{n}C", labels[n - 1])
|
||||||
|
|
||||||
|
def _six_seats(self):
|
||||||
|
seats = self.browser.find_elements(
|
||||||
|
By.CSS_SELECTOR, ".my-sea-page[data-phase='landing'] .table-seat"
|
||||||
|
)
|
||||||
|
if len(seats) != 6:
|
||||||
|
raise AssertionError(f"expected 6 seats, got {len(seats)}")
|
||||||
|
return seats
|
||||||
|
|
||||||
|
# ── Test 3 ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def test_draw_sea_click_transitions_to_picker_phase(self):
|
||||||
|
"""Click DRAW SEA → page wrapper's data-phase swaps to 'picker';
|
||||||
|
landing hides. Picker content itself lands in iter 2 — this test
|
||||||
|
pins only the phase-swap contract iter 1 must establish."""
|
||||||
|
self.create_pre_authenticated_session(self.email)
|
||||||
|
self.browser.get(self.live_server_url + "/gameboard/my-sea/")
|
||||||
|
btn = self.wait_for(
|
||||||
|
lambda: self.browser.find_element(By.CSS_SELECTOR, "#id_draw_sea_btn")
|
||||||
|
)
|
||||||
|
btn.click()
|
||||||
|
self.wait_for(
|
||||||
|
lambda: self.browser.find_element(
|
||||||
|
By.CSS_SELECTOR, ".my-sea-page[data-phase='picker']"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# Landing block hidden after swap.
|
||||||
|
landing = self.browser.find_element(By.CSS_SELECTOR, ".my-sea-landing")
|
||||||
|
self.assertFalse(landing.is_displayed())
|
||||||
|
|
||||||
|
# ── Test 4 ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def test_brief_banner_renders_when_no_deck_equipped(self):
|
||||||
|
"""No equipped deck → the same 'Default deck warning' Brief
|
||||||
|
banner from my-sign fires (lifted verbatim). Tagged w. a my-sea-
|
||||||
|
specific class so FTs can disambiguate from any other Briefs."""
|
||||||
|
self.gamer.equipped_deck = None
|
||||||
|
self.gamer.save(update_fields=["equipped_deck"])
|
||||||
|
self.create_pre_authenticated_session(self.email)
|
||||||
|
self.browser.get(self.live_server_url + "/gameboard/my-sea/")
|
||||||
|
banner = self.wait_for(
|
||||||
|
lambda: self.browser.find_element(
|
||||||
|
By.CSS_SELECTOR, ".my-sea-intro-banner"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertIn("Default deck warning", banner.text)
|
||||||
|
self.assertIn("no deck is equipped", banner.text)
|
||||||
|
self.assertIn("Shabby Cardstock", banner.text)
|
||||||
|
|
||||||
|
# ── Test 5 ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def test_no_brief_banner_when_deck_equipped(self):
|
||||||
|
"""User w. an equipped deck → no Default-deck-warning Brief on
|
||||||
|
landing. Auto-equip via the User post_save signal handles this
|
||||||
|
for fresh users; assertion guards against accidental render of
|
||||||
|
the banner when the condition shouldn't fire."""
|
||||||
|
self.create_pre_authenticated_session(self.email)
|
||||||
|
self.browser.get(self.live_server_url + "/gameboard/my-sea/")
|
||||||
|
self.wait_for(
|
||||||
|
lambda: self.browser.find_element(By.CSS_SELECTOR, "#id_draw_sea_btn")
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
len(self.browser.find_elements(By.CSS_SELECTOR, ".my-sea-intro-banner")),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ body.page-gameboard {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Applet variant — denser layout, omits BACK (the user is already on
|
// Applet variant — denser layout, omits NVM (the user is already on
|
||||||
// the gameboard). Smaller line + just the FYI action surviving.
|
// the gameboard). Smaller line + just the FYI action surviving.
|
||||||
&.my-sea-sign-gate--applet {
|
&.my-sea-sign-gate--applet {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
@@ -205,3 +205,54 @@ body.page-gameboard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── 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;
|
||||||
|
|
||||||
|
// DRAW SEA btn — centered in the hex, mirrors SCAN SIGN's 2-line
|
||||||
|
// font sizing so "DRAW/SEA" sits cleanly inside the 4rem circle.
|
||||||
|
#id_draw_sea_btn {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.1;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chair-seat labels (1C-6C) sit beside the chair icon. The room's
|
||||||
|
// .table-seat positioning by data-slot (1-6) already places them
|
||||||
|
// around the hex — this just sizes the label readably + colors it
|
||||||
|
// to match the gate's --terUser accent.
|
||||||
|
.table-seat .seat-label {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(var(--terUser), 1);
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-sea-picker {
|
||||||
|
// Iter-1 placeholder — iter 2 will populate w. the three-card cross
|
||||||
|
// (sig in center + cover/leave/loom) on a --duoUser background.
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
{% block header_text %}<span>Game</span><span>Sea</span>{% endblock header_text %}
|
{% block header_text %}<span>Game</span><span>Sea</span>{% endblock header_text %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="my-sea-page">
|
<div class="my-sea-page" data-phase="landing">
|
||||||
{% if not user_has_sig %}
|
{% if not user_has_sig %}
|
||||||
{# Sprint 4b sign-gate. The draw UX is gated behind a saved #}
|
{# Sprint 4b sign-gate. The draw UX is gated behind a saved #}
|
||||||
{# significator — render a Look!-formatted Brief-style line w. #}
|
{# significator — render a Look!-formatted Brief-style line w. #}
|
||||||
{# FYI (→ /billboard/my-sign/) + BACK (→ /gameboard/) until the #}
|
{# FYI (→ /billboard/my-sign/) + NVM (→ /gameboard/) until the #}
|
||||||
{# user picks a sign. Inline (not portaled like .note-banner) #}
|
{# user picks a sign. Inline (not portaled like .note-banner) #}
|
||||||
{# because the gate IS the page content, not a transient nudge. #}
|
{# because the gate IS the page content, not a transient nudge. #}
|
||||||
<div class="my-sea-sign-gate">
|
<div class="my-sea-sign-gate">
|
||||||
@@ -18,15 +18,95 @@
|
|||||||
</p>
|
</p>
|
||||||
<div class="my-sea-sign-gate__actions">
|
<div class="my-sea-sign-gate__actions">
|
||||||
<a class="btn btn-cancel my-sea-sign-gate__back"
|
<a class="btn btn-cancel my-sea-sign-gate__back"
|
||||||
href="{% url 'gameboard' %}">BACK</a>
|
href="{% url 'gameboard' %}">NVM</a>
|
||||||
<a class="btn btn-info my-sea-sign-gate__fyi"
|
<a class="btn btn-info my-sea-sign-gate__fyi"
|
||||||
href="{% url 'billboard:my_sign' %}">FYI</a>
|
href="{% url 'billboard:my_sign' %}">FYI</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{# Sprint 3 shell — gatekeeper / sig-select / sea-select phases #}
|
{# Sprint 5 iter 1 — DRAW SEA landing UX. DRY table hex from #}
|
||||||
{# will land here in later sprints of the My Sea roadmap. #}
|
{# the room shell (.room-shell > .room-table > … > .table-hex) #}
|
||||||
<p class="my-sea-page__empty">No draws yet—the depths remain unfathomable.</p>
|
{# w. 6 chair seats labeled 1C-6C as placeholders for the #}
|
||||||
|
{# friend-invite feature per the My Sea roadmap architectural #}
|
||||||
|
{# anchor "Six chairs retained even in solo". DRAW SEA btn #}
|
||||||
|
{# mirrors SCAN SIGN on /billboard/my-sign/. #}
|
||||||
|
<div class="my-sea-landing">
|
||||||
|
<div class="room-shell">
|
||||||
|
<div id="id_game_table" class="room-table">
|
||||||
|
<div class="room-table-scene">
|
||||||
|
<div class="table-hex-border">
|
||||||
|
<div class="table-hex">
|
||||||
|
<div class="table-center">
|
||||||
|
<button id="id_draw_sea_btn" type="button" class="btn btn-primary">DRAW<br>SEA</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% for n in "123456" %}
|
||||||
|
<div class="table-seat" data-slot="{{ n }}">
|
||||||
|
<i class="fa-solid fa-chair"></i>
|
||||||
|
<span class="seat-label">{{ n }}C</span>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Picker phase placeholder — iter 2 wires up the three-card #}
|
||||||
|
{# cross layout (sig in center + cover/leave/loom) w. the #}
|
||||||
|
{# --duoUser bg + form col (spread dropdown / decks / LOCK #}
|
||||||
|
{# HAND / DEL). For iter 1 it's just a phase-swap target. #}
|
||||||
|
<div class="my-sea-picker" style="display:none">
|
||||||
|
<p class="my-sea-picker__placeholder">DRAW SEA picker — wiring lands in Sprint 5 iter 2.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="{% static 'apps/epic/room.js' %}"></script>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
var page = document.querySelector('.my-sea-page');
|
||||||
|
if (!page) return;
|
||||||
|
var landing = page.querySelector('.my-sea-landing');
|
||||||
|
var picker = page.querySelector('.my-sea-picker');
|
||||||
|
var drawBtn = document.getElementById('id_draw_sea_btn');
|
||||||
|
if (drawBtn) {
|
||||||
|
drawBtn.addEventListener('click', function () {
|
||||||
|
page.setAttribute('data-phase', 'picker');
|
||||||
|
if (landing) landing.style.display = 'none';
|
||||||
|
if (picker) picker.style.display = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Mirror my-sign's scaleTable() init timing fix — the
|
||||||
|
// .my-sea-page hasn't flushed its flex sizing on
|
||||||
|
// DOMContentLoaded, so the hex stays unscaled until we
|
||||||
|
// dispatch a resize once layout settles.
|
||||||
|
window.requestAnimationFrame(function () {
|
||||||
|
window.dispatchEvent(new Event('resize'));
|
||||||
|
});
|
||||||
|
}());
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{# Brief 'Default deck warning' banner — lifted verbatim from #}
|
||||||
|
{# /billboard/my-sign/'s no-equipped-deck path. Same copy, #}
|
||||||
|
{# same FYI (→ /gameboard/) + NVM (dismiss + proceed) actions.#}
|
||||||
|
{# Tagged w. .my-sea-intro-banner so FTs disambiguate from #}
|
||||||
|
{# any other Briefs on the page. #}
|
||||||
|
<script src="{% static 'apps/dashboard/note.js' %}"></script>
|
||||||
|
{% if show_backup_intro_banner %}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
if (!window.Brief || !Brief.showBanner) return;
|
||||||
|
Brief.showBanner({
|
||||||
|
title: 'Default deck warning',
|
||||||
|
line_text: 'Look!—no deck is equipped. Navigate to the Game Kit to equip one (FYI) or (NVM) proceed with the Earthman [Shabby Cardstock] deck.',
|
||||||
|
post_url: '{% url "gameboard" %}',
|
||||||
|
created_at: '',
|
||||||
|
kind: 'NUDGE',
|
||||||
|
});
|
||||||
|
var banner = document.querySelector('.note-banner');
|
||||||
|
if (banner) banner.classList.add('my-sea-intro-banner');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
Reference in New Issue
Block a user