diff --git a/src/apps/epic/static/apps/epic/role-select.js b/src/apps/epic/static/apps/epic/role-select.js index 4910855..cacc56b 100644 --- a/src/apps/epic/static/apps/epic/role-select.js +++ b/src/apps/epic/static/apps/epic/role-select.js @@ -154,7 +154,7 @@ var RoleSelect = (function () { var back = document.createElement("div"); back.className = "card-back"; - back.textContent = "?"; + back.textContent = "ROLE"; var front = document.createElement("div"); front.className = "card-front"; @@ -208,6 +208,10 @@ var RoleSelect = (function () { function handleAllRolesFilled() { var wrap = document.getElementById('id_pick_sigs_wrap'); if (wrap) wrap.style.display = ''; + var stack = document.querySelector('.card-stack'); + if (stack) stack.remove(); + var trayWrap = document.getElementById('id_tray_wrap'); + if (trayWrap) trayWrap.classList.remove('role-select-phase'); } function handleSigSelectStarted() { diff --git a/src/apps/epic/static/apps/epic/room.js b/src/apps/epic/static/apps/epic/room.js index 2bbb665..ca8c511 100644 --- a/src/apps/epic/static/apps/epic/room.js +++ b/src/apps/epic/static/apps/epic/room.js @@ -7,7 +7,9 @@ if (!scene || !container) return; var w = container.clientWidth, h = container.clientHeight; if (!w || !h) return; - scene.style.transform = 'scale(' + Math.min(w / SCENE_W, h / SCENE_H) + ')'; + var scale = Math.min(w / SCENE_W, h / SCENE_H); + scene.style.transform = 'scale(' + scale + ')'; + document.documentElement.style.setProperty('--table-scale', scale); } if (document.readyState === 'loading') { diff --git a/src/apps/epic/tests/integrated/test_views.py b/src/apps/epic/tests/integrated/test_views.py index 923a096..824966e 100644 --- a/src/apps/epic/tests/integrated/test_views.py +++ b/src/apps/epic/tests/integrated/test_views.py @@ -762,6 +762,7 @@ class RoomViewAllRolesFilledTest(TestCase): response = self.client.get(reverse("epic:room", kwargs={"room_id": self.room.id})) parsed = self.lxml.fromstring(response.content) [_] = parsed.cssselect("#id_pick_sigs_btn") + self.assertEqual(parsed.cssselect(".card-stack"), []) def test_pick_sigs_btn_hidden_during_role_select(self): # Clear one role — still mid-pick, wrap must be hidden diff --git a/src/apps/epic/views.py b/src/apps/epic/views.py index 4626170..775baaf 100644 --- a/src/apps/epic/views.py +++ b/src/apps/epic/views.py @@ -189,6 +189,8 @@ def _role_select_context(room, user): starter_roles = list( room.table_seats.exclude(role__isnull=True).values_list("role", flat=True) ) + if len(starter_roles) == 6: + card_stack_state = None _action_order = {r: i for i, r in enumerate(["PC", "NC", "EC", "SC", "AC", "BC"])} assigned_seats = ( sorted( diff --git a/src/functional_tests/test_room_role_select.py b/src/functional_tests/test_room_role_select.py index e5d0b5d..8414910 100644 --- a/src/functional_tests/test_room_role_select.py +++ b/src/functional_tests/test_room_role_select.py @@ -846,4 +846,53 @@ class RoleSelectChannelsTest(ChannelsFunctionalTest): "Tray should be closed after turn advances" )) + # ------------------------------------------------------------------ # + # Test 7 — PICK SIGS appears + card stack removed on last role # + # ------------------------------------------------------------------ # + + def test_pick_sigs_appears_and_card_stack_removed_on_last_role(self): + """When the sixth and final role is confirmed, the all_roles_filled + WS event makes the PICK SIGS button visible and removes the card + stack from the DOM entirely.""" + emails = [ + "founder@test.io", "amigo@test.io", "bud@test.io", + "pal@test.io", "dude@test.io", "bro@test.io", + ] + founder, _ = User.objects.get_or_create(email="founder@test.io") + room = Room.objects.create(name="Last Role Test", owner=founder) + _fill_room_via_orm(room, emails) + room.table_status = Room.ROLE_SELECT + room.save() + # Pre-assign 5 roles (slots 2–6); founder (slot 1) is the final picker. + pre_assigned = {2: "NC", 3: "EC", 4: "SC", 5: "AC", 6: "BC"} + for slot in room.gate_slots.order_by("slot_number"): + TableSeat.objects.create( + room=room, + gamer=slot.gamer, + slot_number=slot.slot_number, + role=pre_assigned.get(slot.slot_number), + ) + + room_url = f"{self.live_server_url}/gameboard/room/{room.id}/gate/" + self.create_pre_authenticated_session("founder@test.io") + self.browser.get(room_url) + self.wait_for(lambda: self.browser.find_element( + By.CSS_SELECTOR, ".card-stack[data-state='eligible']" + )) + + # Founder picks the last remaining role (PC — the only card in the fan). + self.browser.find_element(By.CSS_SELECTOR, ".card-stack").click() + self.wait_for(lambda: self.browser.find_element(By.ID, "id_role_select")) + self.browser.find_element(By.CSS_SELECTOR, "#id_role_select .card").click() + self.confirm_guard() + + # PICK SIGS wrap must become visible via the all_roles_filled WS event. + self.wait_for(lambda: self.assertFalse( + self.browser.find_element(By.ID, "id_pick_sigs_wrap").get_attribute("style"), + )) + # Card stack must be removed from the DOM entirely. + self.wait_for(lambda: self.assertEqual( + len(self.browser.find_elements(By.CSS_SELECTOR, ".card-stack")), 0, + )) + diff --git a/src/static_src/scss/_room.scss b/src/static_src/scss/_room.scss index 26ba316..abe26b4 100644 --- a/src/static_src/scss/_room.scss +++ b/src/static_src/scss/_room.scss @@ -624,12 +624,25 @@ html:has(.gate-backdrop) .position-strip .gate-slot button { pointer-events: aut background: rgba(var(--terUser), 1); cursor: default; transition: box-shadow 0.2s ease; + position: relative; + + &::before { + content: "ROLE"; + font-size: 0.6rem; + letter-spacing: 0.14em; + color: rgba(var(--quiUser), 1); + } + + .fa-ban { + position: absolute; + font-size: 1.4rem; + } &[data-state="eligible"] { cursor: pointer; - border-color: rgba(var(--terUser), 1); + border: 2px solid rgba(var(--quiUser), 1); box-shadow: - 0 0 0.6rem rgba(var(--ninUser), 0.6), + 0 0 0.6rem rgba(var(--ninUser), 1), 0 0 1.6rem rgba(var(--secUser), 0.25); } @@ -640,10 +653,11 @@ html:has(.gate-backdrop) .position-strip .gate-slot button { pointer-events: aut } // ─── Card dimensions ─────────────────────────────────────────────────────── -// Role cards are landscape format — wider than tall — and the largest card type. -// Sig cards (half this size) will be layered on top during SIG_SELECT. -$card-w: 160px; -$card-h: 110px; +// Base size matches the card-stack footprint; --table-scale (set by scaleTable() +// in room.js) stretches both the grid and individual cards to stay in sync with +// the scene transform. Fallback of 1 keeps the fan functional if JS hasn't run. +$card-w: 90px; +$card-h: 60px; // ─── Role select modal ───────────────────────────────────────────────────── @@ -662,27 +676,17 @@ $card-h: 110px; #id_role_select { // Always a 3×2 grid — 6 landscape cards in a row would overflow any viewport. display: grid; - grid-template-columns: repeat(3, $card-w); + grid-template-columns: repeat(3, calc(#{$card-w} * var(--table-scale, 1))); gap: 1rem; pointer-events: none; - // Narrow portrait: scale cards down so the 3-col grid still fits - @media (max-width: 600px) { - grid-template-columns: repeat(3, 110px); - gap: 0.75rem; - - .card { - width: 110px; - height: 75px; - } - } } // ─── Card component ──────────────────────────────────────────────────────── .card { - width: $card-w; - height: $card-h; + width: calc(#{$card-w} * var(--table-scale, 1)); + height: calc(#{$card-h} * var(--table-scale, 1)); border-radius: 6px; cursor: pointer; pointer-events: auto; @@ -707,7 +711,8 @@ $card-h: 110px; .card-back { transform: rotateY(0deg); - font-size: 1.5rem; + font-size: calc(0.66rem * var(--table-scale, 1)); + letter-spacing: 0.14em; color: rgba(var(--quiUser), 1); background: rgba(var(--terUser), 1); border: 2px solid rgba(var(--quiUser), 1); @@ -719,7 +724,7 @@ $card-h: 110px; text-align: center; .card-role-name { - font-size: 0.75rem; + font-size: calc(0.66rem * var(--table-scale, 1)); color: rgba(var(--quaUser), 1); text-transform: uppercase; letter-spacing: 0.05em; diff --git a/src/templates/apps/gameboard/room.html b/src/templates/apps/gameboard/room.html index 4adbe5d..f8e4794 100644 --- a/src/templates/apps/gameboard/room.html +++ b/src/templates/apps/gameboard/room.html @@ -95,7 +95,7 @@ {% include "apps/gameboard/_partials/_gatekeeper.html" %} {% endif %} {% if room.table_status %} -