e3f6b00f95
The Weekly Huddle overlay now reads its content from /api/announce (config- driven) instead of a baked-in block, and Admin -> Settings -> Event pop-up exposes a toggle plus editable fields (eyebrow, flyer, join URL, date, times, auto-hide, event id). Defaults are seeded into config on boot so the panel reflects real state. Removed the include from join.html so it never interrupts the join flow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
124 lines
6.3 KiB
JavaScript
124 lines
6.3 KiB
JavaScript
/* announce.js — dismissible event pop-up overlay (RM Circle).
|
||
Content is admin-controlled: fetched from /api/announce (edit it under
|
||
Admin → Settings → Event pop-up; no deploy needed). Self-gating: shows once
|
||
per browser until dismissed, and never after `expiresUTC`. */
|
||
(function () {
|
||
function parseTimes(s) {
|
||
return String(s || '').split(/\r?\n+/).map(function (l) { return l.trim(); }).filter(Boolean)
|
||
.map(function (l) { var i = l.indexOf('|'); return i < 0 ? [l, ''] : [l.slice(0, i).trim(), l.slice(i + 1).trim()]; });
|
||
}
|
||
|
||
function start(EV) {
|
||
if (!EV || !EV.enabled || !EV.id || !EV.img) return;
|
||
if (EV.expiresUTC) { var exp = Date.parse(EV.expiresUTC); if (exp && Date.now() > exp) return; }
|
||
var KEY = 'rmc-announce-' + EV.id;
|
||
try { if (localStorage.getItem(KEY) === 'done') return; } catch (e) {}
|
||
EV.timesArr = parseTimes(EV.times);
|
||
|
||
var back, card;
|
||
function done() { try { localStorage.setItem(KEY, 'done'); } catch (e) {} }
|
||
function close() {
|
||
if (!back) return;
|
||
back.style.opacity = '0';
|
||
setTimeout(function () { if (back && back.parentNode) back.parentNode.removeChild(back); back = null; }, 260);
|
||
document.removeEventListener('keydown', onKey);
|
||
}
|
||
function dismiss() { done(); close(); }
|
||
function onKey(e) { if (e.key === 'Escape') dismiss(); }
|
||
|
||
function build() {
|
||
back = document.createElement('div');
|
||
back.setAttribute('role', 'dialog');
|
||
back.setAttribute('aria-label', (EV.eyebrow || 'Event') + ' invitation');
|
||
back.style.cssText = 'position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;justify-content:center;' +
|
||
'padding:20px;background:rgba(3,7,14,.82);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);' +
|
||
'opacity:0;transition:opacity .28s ease;overflow:auto;';
|
||
back.addEventListener('click', function (e) { if (e.target === back) dismiss(); });
|
||
|
||
card = document.createElement('div');
|
||
card.style.cssText = 'position:relative;width:100%;max-width:428px;max-height:94vh;overflow:auto;border-radius:20px;' +
|
||
'background:#0a1119;border:1px solid rgba(212,175,55,.55);box-shadow:0 24px 70px rgba(0,0,0,.7),0 0 0 1px rgba(212,175,55,.15);' +
|
||
'font-family:system-ui,Segoe UI,Arial,sans-serif;';
|
||
|
||
var x = document.createElement('button');
|
||
x.setAttribute('aria-label', 'Close');
|
||
x.innerHTML = '×';
|
||
x.style.cssText = 'position:absolute;top:10px;right:12px;z-index:2;width:38px;height:38px;border-radius:50%;border:none;cursor:pointer;' +
|
||
'background:rgba(6,10,16,.72);color:#fff;font-size:24px;line-height:1;font-weight:700;display:grid;place-items:center;box-shadow:0 2px 10px rgba(0,0,0,.5);';
|
||
x.addEventListener('click', dismiss);
|
||
|
||
var eyebrow = document.createElement('div');
|
||
eyebrow.textContent = EV.eyebrow || '';
|
||
eyebrow.style.cssText = 'text-align:center;letter-spacing:2px;text-transform:uppercase;font-size:11px;font-weight:700;color:#d4af37;padding:14px 44px 10px;';
|
||
|
||
var img = document.createElement('img');
|
||
img.src = EV.img;
|
||
img.alt = EV.eyebrow || 'Event flyer';
|
||
img.style.cssText = 'display:block;width:100%;height:auto;';
|
||
|
||
var foot = document.createElement('div');
|
||
foot.style.cssText = 'padding:16px 20px 20px;';
|
||
|
||
if (EV.dateLabel) {
|
||
var dateRow = document.createElement('div');
|
||
dateRow.innerHTML = '🗓 <b></b>';
|
||
dateRow.querySelector('b').textContent = EV.dateLabel;
|
||
dateRow.style.cssText = 'text-align:center;color:#eef2f7;font-size:15px;margin-bottom:12px;';
|
||
foot.appendChild(dateRow);
|
||
}
|
||
|
||
if (EV.timesArr.length) {
|
||
var grid = document.createElement('div');
|
||
grid.style.cssText = 'display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:16px;';
|
||
EV.timesArr.forEach(function (t) {
|
||
var cell = document.createElement('div');
|
||
cell.style.cssText = 'background:rgba(212,175,55,.08);border:1px solid rgba(212,175,55,.22);border-radius:10px;padding:8px 10px;';
|
||
var time = document.createElement('div');
|
||
time.style.cssText = 'font-size:15px;font-weight:800;color:#f3c34a;'; time.textContent = t[0];
|
||
var region = document.createElement('div');
|
||
region.style.cssText = 'font-size:11px;color:#9fb0c2;margin-top:1px;'; region.textContent = t[1] || ' ';
|
||
cell.appendChild(time); cell.appendChild(region); grid.appendChild(cell);
|
||
});
|
||
foot.appendChild(grid);
|
||
}
|
||
|
||
var join = document.createElement('a');
|
||
join.href = EV.meetUrl || '#'; join.target = '_blank'; join.rel = 'noopener';
|
||
join.textContent = 'Join Google Meet →';
|
||
join.style.cssText = 'display:block;text-align:center;text-decoration:none;background:linear-gradient(180deg,#e9c249,#d4af37);' +
|
||
'color:#0a1119;font-size:18px;font-weight:800;padding:15px;border-radius:12px;box-shadow:0 8px 22px rgba(212,175,55,.35);';
|
||
join.addEventListener('click', done);
|
||
foot.appendChild(join);
|
||
|
||
if (EV.meetUrl) {
|
||
var link = document.createElement('div');
|
||
link.textContent = EV.meetUrl.replace(/^https?:\/\//, '');
|
||
link.style.cssText = 'text-align:center;color:#9fb0c2;font-size:13px;margin-top:10px;font-family:ui-monospace,Menlo,Consolas,monospace;word-break:break-all;';
|
||
foot.appendChild(link);
|
||
}
|
||
|
||
var later = document.createElement('button');
|
||
later.textContent = 'Maybe later';
|
||
later.style.cssText = 'display:block;margin:12px auto 0;background:none;border:none;color:#7d8ea0;font-size:13px;cursor:pointer;text-decoration:underline;';
|
||
later.addEventListener('click', dismiss);
|
||
foot.appendChild(later);
|
||
|
||
card.appendChild(x); card.appendChild(eyebrow); card.appendChild(img); card.appendChild(foot);
|
||
back.appendChild(card); document.body.appendChild(back);
|
||
document.addEventListener('keydown', onKey);
|
||
requestAnimationFrame(function () { back.style.opacity = '1'; });
|
||
}
|
||
|
||
setTimeout(build, 1100);
|
||
}
|
||
|
||
function fetchAndGo() {
|
||
fetch('/api/announce', { headers: { 'Accept': 'application/json' } })
|
||
.then(function (r) { return r.ok ? r.json() : null; })
|
||
.then(function (EV) { if (EV) start(EV); })
|
||
.catch(function () {});
|
||
}
|
||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', fetchAndGo);
|
||
else fetchAndGo();
|
||
})();
|