110 lines
5.4 KiB
HTML
110 lines
5.4 KiB
HTML
|
|
{% extends "core/base.html" %}
|
||
|
|
{% load static %}
|
||
|
|
{% load lyric_extras %}
|
||
|
|
|
||
|
|
{% block title_text %}Game Gate{% endblock title_text %}
|
||
|
|
{% block header_text %}<span>Game</span><span>Gate</span>{% endblock header_text %}
|
||
|
|
|
||
|
|
{% block content %}
|
||
|
|
{# Room renewal gate-view (sprint 2026-05-31) — the 3rd-person mirror of #}
|
||
|
|
{# `my_sea_gate.html`. Reachable mid-game: unlike the gatekeeper (which #}
|
||
|
|
{# redirects to the table once table_status is set), GATE VIEW routes here #}
|
||
|
|
{# at any time so a seated gamer can check their token TIME REMAINING or #}
|
||
|
|
{# RENEW. Reuses the room gatekeeper's `.gate-overlay` / `.gate-modal` #}
|
||
|
|
{# chrome (hand-rolled, not `{% include _gatekeeper %}` — the inner content #}
|
||
|
|
{# differs: one seat circle + countdown + RENEW, no rails/PICK ROLES). The #}
|
||
|
|
{# gear-menu NVM returns to the table hex (passed `nvm_url`), not /gameboard.#}
|
||
|
|
<div class="room-page room-gate-page" data-room-id="{{ room.id }}">
|
||
|
|
<div id="id_gate_wrapper" class="room-gate-wrapper">
|
||
|
|
<div class="gate-backdrop"></div>
|
||
|
|
<div class="gate-overlay room-gate-overlay">
|
||
|
|
<div class="gate-modal room-gate-modal" role="dialog" aria-label="Renewal Gate">
|
||
|
|
|
||
|
|
<div class="gate-title-panel">
|
||
|
|
<header class="gate-header">
|
||
|
|
<h1>{{ room.name }}</h1>
|
||
|
|
<div class="gate-status-wrap">
|
||
|
|
<span class="gate-status-text">{% if not user_filled_slot %}No Seat{% elif cost_current %}Token Current{% elif in_renewal_grace %}Renewal Due{% else %}Grace Expired{% endif %}</span>
|
||
|
|
</div>
|
||
|
|
</header>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="gate-top-row">
|
||
|
|
<div class="gate-main-panel">
|
||
|
|
{% if user_filled_slot %}
|
||
|
|
{# The viewer's own seat + position circle — held while #}
|
||
|
|
{# they hold the slot (through the renewal-grace window).#}
|
||
|
|
<div class="room-gate-seat table-seat seated" data-slot="{{ user_filled_slot.slot_number }}">
|
||
|
|
<i class="fa-solid fa-chair"></i>
|
||
|
|
<span class="seat-position-label">{{ slot_role_label }}</span>
|
||
|
|
<i class="position-status-icon fa-solid fa-circle-check"></i>
|
||
|
|
</div>
|
||
|
|
{# Live time-remaining — ticks to cost_current_until while #}
|
||
|
|
{# current, then to grace_expires_at once in renewal grace. #}
|
||
|
|
<p id="id_room_gate_remaining"
|
||
|
|
class="room-gate-remaining"
|
||
|
|
data-cost-until="{{ cost_current_until|date:'c' }}"
|
||
|
|
data-grace-until="{{ grace_expires_at|date:'c' }}"></p>
|
||
|
|
{% else %}
|
||
|
|
<p class="room-gate-remaining">You hold no seat in this room.</p>
|
||
|
|
{% endif %}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="gate-roles-panel">
|
||
|
|
{% if user_filled_slot %}
|
||
|
|
<form method="POST" action="{% url 'epic:renew_token' room.id %}" style="display:contents">
|
||
|
|
{% csrf_token %}
|
||
|
|
<button type="submit"
|
||
|
|
id="id_room_renew_btn"
|
||
|
|
class="launch-game-btn btn btn-primary">RENEW</button>
|
||
|
|
</form>
|
||
|
|
{% endif %}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{# NVM nav-backs one step to the table hex (not out to /gameboard/). #}
|
||
|
|
{% url 'epic:room' room.id as nvm_url %}
|
||
|
|
{% include "apps/gameboard/_partials/_room_gear.html" with nvm_url=nvm_url %}
|
||
|
|
{% include "apps/gameboard/_partials/_burger.html" %}
|
||
|
|
</div>
|
||
|
|
{% endblock content %}
|
||
|
|
|
||
|
|
{% block scripts %}
|
||
|
|
<script src="{% static 'apps/dashboard/note.js' %}"></script>
|
||
|
|
<script src="{% static 'apps/epic/burger-btn.js' %}"></script>
|
||
|
|
<script>
|
||
|
|
{# Time-remaining ticker — mirrors the status-dots IIFE pattern in #}
|
||
|
|
{# _gatekeeper.html. Counts down to cost_current_until while the token #}
|
||
|
|
{# cost is current, then to grace_expires_at once in renewal grace. #}
|
||
|
|
(function () {
|
||
|
|
var el = document.getElementById('id_room_gate_remaining');
|
||
|
|
if (!el) return;
|
||
|
|
var costUntil = el.dataset.costUntil ? new Date(el.dataset.costUntil) : null;
|
||
|
|
var graceUntil = el.dataset.graceUntil ? new Date(el.dataset.graceUntil) : null;
|
||
|
|
function fmt(ms) {
|
||
|
|
if (ms <= 0) return '0h';
|
||
|
|
var d = Math.floor(ms / 86400000);
|
||
|
|
var h = Math.floor((ms % 86400000) / 3600000);
|
||
|
|
return (d > 0 ? d + 'd ' : '') + h + 'h';
|
||
|
|
}
|
||
|
|
function tick() {
|
||
|
|
var now = new Date();
|
||
|
|
if (costUntil && now < costUntil) {
|
||
|
|
el.textContent = 'Token current — ' + fmt(costUntil - now) + ' remaining';
|
||
|
|
} else if (graceUntil && now < graceUntil) {
|
||
|
|
el.textContent = 'Renewal due — ' + fmt(graceUntil - now) + ' to renew';
|
||
|
|
} else {
|
||
|
|
el.textContent = 'Renewal grace expired';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
tick();
|
||
|
|
clearInterval(window._roomGateTick);
|
||
|
|
window._roomGateTick = setInterval(tick, 60000);
|
||
|
|
}());
|
||
|
|
</script>
|
||
|
|
{% endblock scripts %}
|