A notice the whole hall shows: hall-announce on the host, served by every game's site, shown at the top of each page
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
294b1d5460
commit
96ec6861d5
@@ -113,6 +113,9 @@
|
||||
|
||||
footer { margin-top: 64px; padding: 28px 0 48px; border-top: 1px solid var(--line); color: var(--bone-faint); font-size: 0.9rem; }
|
||||
footer p { margin: 0 0 8px; }
|
||||
/* The hall-wide notice: its own colors, the same in every game of the hall. */
|
||||
.hall-announcement { position: sticky; top: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; gap: .8rem; padding: .45rem 2.4rem .45rem 1rem; background: #f2c45a; color: #1d1a12; font: 500 .95rem/1.35 system-ui, -apple-system, 'Segoe UI', sans-serif; text-align: center; box-shadow: 0 1px 4px #0005; }
|
||||
.hall-announcement button { position: absolute; right: .5rem; background: none; border: 0; color: inherit; font-size: 1.3rem; line-height: 1; padding: .2rem .5rem; cursor: pointer; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -214,5 +217,50 @@
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
// The hall-wide notice every game of Kestrel's Hall shows (hall-announce on the
|
||||
// host): checked on load, every few minutes, and when the page is looked at
|
||||
// again; a reader may dismiss one, and the next still shows.
|
||||
(() => {
|
||||
const KEY = 'hall:dismissed';
|
||||
let bar = null;
|
||||
let current = null;
|
||||
const read = () => { try { return localStorage.getItem(KEY); } catch { return null; } };
|
||||
function when(at) {
|
||||
const t = at ? Date.parse(at) : NaN;
|
||||
if (!Number.isFinite(t)) return '';
|
||||
const minutes = Math.round((t - Date.now()) / 60000);
|
||||
if (minutes > 90) return ` (${new Date(t).toLocaleString([], { weekday: 'short', hour: 'numeric', minute: '2-digit' })}, your time)`;
|
||||
if (minutes > 1) return ` (in ${minutes} minutes, at ${new Date(t).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })})`;
|
||||
return minutes >= -15 ? ' (now)' : '';
|
||||
}
|
||||
function show() {
|
||||
const a = current;
|
||||
const live = a && a.id !== read() && !(a.until && Date.parse(a.until) < Date.now());
|
||||
if (!live) { bar?.remove(); bar = null; return; }
|
||||
if (!bar) {
|
||||
bar = document.createElement('div');
|
||||
bar.className = 'hall-announcement';
|
||||
bar.setAttribute('role', 'status');
|
||||
bar.innerHTML = '<span></span><button type="button" aria-label="Dismiss this announcement">×</button>';
|
||||
bar.querySelector('button').onclick = () => { try { localStorage.setItem(KEY, current.id); } catch {} show(); };
|
||||
document.body.prepend(bar);
|
||||
}
|
||||
bar.querySelector('span').textContent = a.text + when(a.at);
|
||||
}
|
||||
async function check() {
|
||||
try {
|
||||
const res = await fetch('/hall/announcement.json', { cache: 'no-store' });
|
||||
const a = res.ok ? await res.json() : null;
|
||||
current = a && typeof a.text === 'string' && a.text && typeof a.id === 'string' ? a : null;
|
||||
} catch {}
|
||||
show();
|
||||
}
|
||||
check();
|
||||
setInterval(check, 5 * 60000);
|
||||
setInterval(show, 30000);
|
||||
document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') check(); });
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user