2026-03-14 00:34:07 -04:00
|
|
|
<div
|
|
|
|
|
id="id_gate_wrapper"
|
Django Channels role-select sprint: turn_changed, roles_revealed, role_select_start consumer handlers; WS URL changed from room_slug to room_id UUID; TableSeat model - room, gamer, slot_number, role, role_revealed, seat_position fields; Room.table_status field with ROLE_SELECT, SIG_SELECT, IN_GAME choices; migration 0006_table_status_and_table_seat; pick_roles and select_role views; _role_select_context helper; _notify_turn_changed, _notify_roles_revealed, _notify_role_select_start notifiers; all gate-mutation views now call _notify_gate_update; ChannelsFunctionalTest base class with serve_static, screenshot, dump helpers; SQLite TEST NAME set to file path for ChannelsLiveServerTestCase; InMemoryChannelLayer added to test CHANNEL_LAYERS settings; FT 5 and FT 6 now passing - active seat arc and turn advance via WS, no page refresh; room.js, gatekeeper.js, role-select.js added to apps/epic/static; applets.js, game-kit.js, dashboard.js, wallet.js relocated to app-scoped static dirs; room.html: hex table, table-seat arcs, card-stack, inventory panel, role-card hand, WS scripts; _room.scss: room-shell flex layout, .table-hex polygon clip-path, .table-seat and .seat-card-arc, .card-stack eligible/ineligible states, .card flip animation, .inv-role-card stacked hand, .role-select-backdrop; gear btn and room menu always position: fixed; 375 tests, 0 skipped
2026-03-17 00:24:23 -04:00
|
|
|
data-gate-status-url="{% url 'epic:gate_status' room.id %}"
|
2026-03-14 00:34:07 -04:00
|
|
|
>
|
2026-03-30 18:31:05 -04:00
|
|
|
<div class="gate-backdrop"></div>
|
2026-03-14 00:34:07 -04:00
|
|
|
<div class="gate-overlay">
|
|
|
|
|
<div class="gate-modal" role="dialog" aria-label="Gatekeeper">
|
2026-03-14 02:03:44 -04:00
|
|
|
|
2026-04-05 19:10:02 -04:00
|
|
|
<div class="gate-title-panel">
|
|
|
|
|
<header class="gate-header">
|
|
|
|
|
<h1>{{ room.name }}</h1>
|
|
|
|
|
<div class="gate-status-wrap">
|
|
|
|
|
<span class="gate-status-text">{{ room.get_gate_status_display }}</span>
|
|
|
|
|
<span class="status-dots" aria-hidden="true">
|
|
|
|
|
<span></span><span></span><span></span><span></span>
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</header>
|
|
|
|
|
</div>
|
2026-03-13 00:31:17 -04:00
|
|
|
|
2026-04-05 19:10:02 -04:00
|
|
|
<div class="gate-top-row">
|
|
|
|
|
<div class="gate-main-panel">
|
|
|
|
|
<div class="token-slot{% if can_drop %} active{% elif user_reserved_slot %} pending{% elif user_filled_slot or carte_active %} claimed{% elif token_depleted %} depleted{% else %} locked{% endif %}">
|
|
|
|
|
{% if can_drop %}
|
|
|
|
|
<form method="POST" action="{% url 'epic:drop_token' room.id %}" style="display:contents">
|
|
|
|
|
{% csrf_token %}
|
|
|
|
|
<button type="submit" class="token-rails" aria-label="Insert token to play">
|
|
|
|
|
<span class="rail"></span>
|
|
|
|
|
<span class="rail"></span>
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
{% else %}
|
|
|
|
|
<div class="token-rails">
|
|
|
|
|
<span class="rail"></span>
|
|
|
|
|
<span class="rail"></span>
|
|
|
|
|
</div>
|
|
|
|
|
{% endif %}
|
|
|
|
|
<div class="token-panel">
|
|
|
|
|
<div class="token-denomination">1</div>
|
|
|
|
|
<span class="token-insert-label">INSERT TOKEN TO PLAY</span>
|
|
|
|
|
<span class="token-return-label">PUSH TO RETURN</span>
|
|
|
|
|
</div>
|
|
|
|
|
{% if user_can_reject %}
|
|
|
|
|
<form method="POST" action="{% url 'epic:return_token' room.id %}" style="display:contents">
|
|
|
|
|
{% csrf_token %}
|
|
|
|
|
<button type="submit" class="token-return-btn" aria-label="Push to return"></button>
|
|
|
|
|
</form>
|
|
|
|
|
{% endif %}
|
2026-03-14 02:03:44 -04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-04-05 19:10:02 -04:00
|
|
|
<div class="gate-roles-panel">
|
|
|
|
|
{% if room.gate_status == 'OPEN' %}
|
|
|
|
|
<form method="POST" action="{% url 'epic:pick_roles' room.id %}" style="display:contents">
|
|
|
|
|
{% csrf_token %}
|
|
|
|
|
<button type="submit" class="launch-game-btn btn btn-primary btn-xl">PICK ROLES</button>
|
|
|
|
|
</form>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-03-14 02:03:44 -04:00
|
|
|
|
2026-03-14 00:34:07 -04:00
|
|
|
{% if request.user == room.owner %}
|
2026-04-05 19:10:02 -04:00
|
|
|
<div class="gate-invite-panel">
|
2026-03-14 22:00:16 -04:00
|
|
|
<h3>Invite Friend</h3>
|
|
|
|
|
<form method="POST" action="{% url 'epic:invite_gamer' room.id %}" style="display:flex; gap:0.5rem; align-items:center;">
|
|
|
|
|
{% csrf_token %}
|
|
|
|
|
<input type="email" name="invitee_email" id="id_invite_email" class="form-control form-control-lg" placeholder="friend@example.com" style="flex:1; min-width:0;" hx-preserve>
|
|
|
|
|
<button type="submit" id="id_invite_btn" class="btn btn-confirm">OK</button>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
2026-03-14 00:34:07 -04:00
|
|
|
{% endif %}
|
2026-03-14 02:03:44 -04:00
|
|
|
|
2026-03-14 00:34:07 -04:00
|
|
|
</div>
|
2026-03-13 00:31:17 -04:00
|
|
|
</div>
|
2026-03-14 00:34:07 -04:00
|
|
|
</div>
|
2026-03-14 02:03:44 -04:00
|
|
|
<script>
|
|
|
|
|
(function () {
|
|
|
|
|
clearInterval(window._gateDots);
|
|
|
|
|
var wrap = document.querySelector('.status-dots');
|
|
|
|
|
if (!wrap) return;
|
|
|
|
|
var dots = wrap.querySelectorAll('span');
|
|
|
|
|
var n = 0;
|
|
|
|
|
window._gateDots = setInterval(function () {
|
|
|
|
|
if (!document.contains(wrap)) { clearInterval(window._gateDots); return; }
|
|
|
|
|
dots.forEach(function (d, i) { d.textContent = i < n ? '.' : ''; });
|
|
|
|
|
n = (n + 1) % 5;
|
|
|
|
|
}, 400);
|
|
|
|
|
}());
|
|
|
|
|
</script>
|