diff --git a/public/announce.js b/public/announce.js
index 5c158bd..bffca22 100644
--- a/public/announce.js
+++ b/public/announce.js
@@ -1,202 +1,208 @@
-/* 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 false;
- if (EV.expiresUTC) { var exp = Date.parse(EV.expiresUTC); if (exp && Date.now() > exp) return false; }
- var KEY = 'rmc-announce-' + EV.id;
- try { if (localStorage.getItem(KEY) === 'done') return false; } 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 = '๐ ';
- 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);
-
- var st = EV.startUTC ? Date.parse(EV.startUTC) : 0;
- if (st) {
- var en = st + (Number(EV.durationMin) || 60) * 60000;
- var f = function (ms) { return new Date(ms).toISOString().replace(/[-:]|\.\d{3}/g, ''); };
- var title = EV.eyebrow || 'Team RM Circle ยท Weekly Huddle';
- var details = (EV.meetUrl ? 'Join: ' + EV.meetUrl + '\n' : '') + String(EV.times || '').replace(/\|/g, ' ');
- var gcal = 'https://calendar.google.com/calendar/render?action=TEMPLATE&text=' + encodeURIComponent(title) +
- '&dates=' + f(st) + '/' + f(en) + '&details=' + encodeURIComponent(details) + '&location=' + encodeURIComponent(EV.meetUrl || '');
- // On a phone a calendar.google.com link is claimed by the Google Calendar app, which opens its own
- // home screen and throws the event details away. Phones get the calendar file instead: iOS, Android
- // and the Google Calendar app all import it straight into the default calendar.
- var phone = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent || '');
- var row = document.createElement('div');
- row.style.cssText = 'display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px;';
- var btnCss = 'display:block;text-align:center;text-decoration:none;background:rgba(212,175,55,.08);border:1px solid rgba(212,175,55,.35);' +
- 'color:#f3c34a;font-size:13px;font-weight:700;padding:10px 8px;border-radius:10px;';
- var g = document.createElement('a');
- g.textContent = '๐
Add to Calendar'; g.style.cssText = btnCss;
- if (phone) g.href = '/announce.ics?open=1';
- else { g.href = gcal; g.target = '_blank'; g.rel = 'noopener'; }
- g.addEventListener('click', done);
- var a = document.createElement('a');
- a.style.cssText = btnCss;
- if (phone) { a.href = gcal; a.target = '_blank'; a.rel = 'noopener'; a.textContent = 'Google Calendar'; }
- else { a.href = '/announce.ics'; a.setAttribute('download', 'rm-circle-huddle.ics'); a.textContent = 'Apple / Outlook'; }
- a.addEventListener('click', done);
- row.appendChild(g); row.appendChild(a); foot.appendChild(row);
- }
-
- 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);
- return true;
- }
-
- /* Promo overlay: one flyer, one button, once per browser SESSION (sessionStorage), only on the
- pages listed in promo.pages (path segment after the first slash: 'my', 'app', or 'all'). */
- function startPromo(P) {
- if (!P || !P.enabled || !P.id || !P.img) return;
- if (P.expiresUTC) { var exp = Date.parse(P.expiresUTC); if (exp && Date.now() > exp) return; }
- var seg = (location.pathname.split('/')[1] || 'home').toLowerCase();
- var pages = String(P.pages || 'all').split(',').map(function (x) { return x.trim().toLowerCase(); }).filter(Boolean);
- if (pages.indexOf('all') < 0 && pages.indexOf(seg) < 0) return;
- var KEY = 'rmc-promo-' + P.id;
- try { if (sessionStorage.getItem(KEY) === 'seen') return; } catch (e) {}
- var back;
- function mark() { try { sessionStorage.setItem(KEY, 'seen'); } catch (e) {} }
- function close() { if (!back) return; mark(); back.style.opacity = '0'; setTimeout(function () { if (back && back.parentNode) back.parentNode.removeChild(back); back = null; }, 260); document.removeEventListener('keydown', onKey); }
- function onKey(e) { if (e.key === 'Escape') close(); }
- function build() {
- back = document.createElement('div');
- back.setAttribute('role', 'dialog'); back.setAttribute('aria-label', P.eyebrow || 'Upgrade');
- back.style.cssText = 'position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;justify-content:center;padding:16px;background:rgba(3,7,14,.84);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) close(); });
- var card = document.createElement('div');
- card.style.cssText = 'position:relative;width:100%;max-width:420px;max-height:96vh;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);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', close);
- var img = document.createElement('img');
- img.src = P.img; img.alt = P.eyebrow || 'Upgrade'; img.style.cssText = 'display:block;width:100%;height:auto;border-radius:20px 20px 0 0;';
- var foot = document.createElement('div'); foot.style.cssText = 'padding:14px 18px 18px;';
- var cta = document.createElement('a');
- cta.href = P.ctaUrl || '/my'; cta.textContent = P.ctaText || 'See my next step \u2192';
- cta.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);';
- cta.addEventListener('click', function (e) {
- // already on the target page: close and scroll to the plan card instead of reloading
- var here = (P.ctaUrl || '/my').split('#')[0].replace(/\/$/, '') === location.pathname.replace(/\/$/, '');
- if (here) { e.preventDefault(); close(); var t = document.querySelector('.ns-card, #nextStep, #plan'); if (t) setTimeout(function () { t.scrollIntoView({ behavior: 'smooth', block: 'center' }); }, 300); }
- else mark();
- });
- var later = document.createElement('button');
- later.textContent = 'Not now'; later.style.cssText = 'display:block;margin:10px auto 0;background:none;border:none;color:#7d8ea0;font-size:13px;cursor:pointer;text-decoration:underline;';
- later.addEventListener('click', close);
- foot.appendChild(cta); foot.appendChild(later);
- card.appendChild(x); 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, 900);
- }
-
- function fetchAndGo() {
- fetch('/api/announce', { headers: { 'Accept': 'application/json' } })
- .then(function (r) { return r.ok ? r.json() : null; })
- .then(function (EV) { if (!EV) return; var shown = start(EV); if (!shown) startPromo(EV.promo); })
- .catch(function () {});
- }
- if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', fetchAndGo);
- else fetchAndGo();
-})();
+/* 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) {
+ // A cancellation has no flyer and no meet link, so neither is required any more: the
+ // same card doubles as a plain notice (Marty, 2026-09-24).
+ if (!EV || !EV.enabled || !EV.id) return false;
+ if (EV.expiresUTC) { var exp = Date.parse(EV.expiresUTC); if (exp && Date.now() > exp) return false; }
+ var KEY = 'rmc-announce-' + EV.id;
+ try { if (localStorage.getItem(KEY) === 'done') return false; } 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 = EV.img ? document.createElement('img') : null;
+ if (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 = '๐ ';
+ 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 = EV.meetUrl ? document.createElement('a') : null;
+ if (join) {
+ 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);
+ }
+
+ var st = (EV.meetUrl && EV.startUTC) ? Date.parse(EV.startUTC) : 0;
+ if (st) {
+ var en = st + (Number(EV.durationMin) || 60) * 60000;
+ var f = function (ms) { return new Date(ms).toISOString().replace(/[-:]|\.\d{3}/g, ''); };
+ var title = EV.eyebrow || 'Team RM Circle ยท Weekly Huddle';
+ var details = (EV.meetUrl ? 'Join: ' + EV.meetUrl + '\n' : '') + String(EV.times || '').replace(/\|/g, ' ');
+ var gcal = 'https://calendar.google.com/calendar/render?action=TEMPLATE&text=' + encodeURIComponent(title) +
+ '&dates=' + f(st) + '/' + f(en) + '&details=' + encodeURIComponent(details) + '&location=' + encodeURIComponent(EV.meetUrl || '');
+ // On a phone a calendar.google.com link is claimed by the Google Calendar app, which opens its own
+ // home screen and throws the event details away. Phones get the calendar file instead: iOS, Android
+ // and the Google Calendar app all import it straight into the default calendar.
+ var phone = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent || '');
+ var row = document.createElement('div');
+ row.style.cssText = 'display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px;';
+ var btnCss = 'display:block;text-align:center;text-decoration:none;background:rgba(212,175,55,.08);border:1px solid rgba(212,175,55,.35);' +
+ 'color:#f3c34a;font-size:13px;font-weight:700;padding:10px 8px;border-radius:10px;';
+ var g = document.createElement('a');
+ g.textContent = '๐
Add to Calendar'; g.style.cssText = btnCss;
+ if (phone) g.href = '/announce.ics?open=1';
+ else { g.href = gcal; g.target = '_blank'; g.rel = 'noopener'; }
+ g.addEventListener('click', done);
+ var a = document.createElement('a');
+ a.style.cssText = btnCss;
+ if (phone) { a.href = gcal; a.target = '_blank'; a.rel = 'noopener'; a.textContent = 'Google Calendar'; }
+ else { a.href = '/announce.ics'; a.setAttribute('download', 'rm-circle-huddle.ics'); a.textContent = 'Apple / Outlook'; }
+ a.addEventListener('click', done);
+ row.appendChild(g); row.appendChild(a); foot.appendChild(row);
+ }
+
+ 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); if (img) 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);
+ return true;
+ }
+
+ /* Promo overlay: one flyer, one button, once per browser SESSION (sessionStorage), only on the
+ pages listed in promo.pages (path segment after the first slash: 'my', 'app', or 'all'). */
+ function startPromo(P) {
+ if (!P || !P.enabled || !P.id || !P.img) return;
+ if (P.expiresUTC) { var exp = Date.parse(P.expiresUTC); if (exp && Date.now() > exp) return; }
+ var seg = (location.pathname.split('/')[1] || 'home').toLowerCase();
+ var pages = String(P.pages || 'all').split(',').map(function (x) { return x.trim().toLowerCase(); }).filter(Boolean);
+ if (pages.indexOf('all') < 0 && pages.indexOf(seg) < 0) return;
+ var KEY = 'rmc-promo-' + P.id;
+ try { if (sessionStorage.getItem(KEY) === 'seen') return; } catch (e) {}
+ var back;
+ function mark() { try { sessionStorage.setItem(KEY, 'seen'); } catch (e) {} }
+ function close() { if (!back) return; mark(); back.style.opacity = '0'; setTimeout(function () { if (back && back.parentNode) back.parentNode.removeChild(back); back = null; }, 260); document.removeEventListener('keydown', onKey); }
+ function onKey(e) { if (e.key === 'Escape') close(); }
+ function build() {
+ back = document.createElement('div');
+ back.setAttribute('role', 'dialog'); back.setAttribute('aria-label', P.eyebrow || 'Upgrade');
+ back.style.cssText = 'position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;justify-content:center;padding:16px;background:rgba(3,7,14,.84);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) close(); });
+ var card = document.createElement('div');
+ card.style.cssText = 'position:relative;width:100%;max-width:420px;max-height:96vh;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);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', close);
+ var img = document.createElement('img');
+ img.src = P.img; img.alt = P.eyebrow || 'Upgrade'; img.style.cssText = 'display:block;width:100%;height:auto;border-radius:20px 20px 0 0;';
+ var foot = document.createElement('div'); foot.style.cssText = 'padding:14px 18px 18px;';
+ var cta = document.createElement('a');
+ cta.href = P.ctaUrl || '/my'; cta.textContent = P.ctaText || 'See my next step \u2192';
+ cta.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);';
+ cta.addEventListener('click', function (e) {
+ // already on the target page: close and scroll to the plan card instead of reloading
+ var here = (P.ctaUrl || '/my').split('#')[0].replace(/\/$/, '') === location.pathname.replace(/\/$/, '');
+ if (here) { e.preventDefault(); close(); var t = document.querySelector('.ns-card, #nextStep, #plan'); if (t) setTimeout(function () { t.scrollIntoView({ behavior: 'smooth', block: 'center' }); }, 300); }
+ else mark();
+ });
+ var later = document.createElement('button');
+ later.textContent = 'Not now'; later.style.cssText = 'display:block;margin:10px auto 0;background:none;border:none;color:#7d8ea0;font-size:13px;cursor:pointer;text-decoration:underline;';
+ later.addEventListener('click', close);
+ foot.appendChild(cta); foot.appendChild(later);
+ card.appendChild(x); if (img) 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, 900);
+ }
+
+ function fetchAndGo() {
+ fetch('/api/announce', { headers: { 'Accept': 'application/json' } })
+ .then(function (r) { return r.ok ? r.json() : null; })
+ .then(function (EV) { if (!EV) return; var shown = start(EV); if (!shown) startPromo(EV.promo); })
+ .catch(function () {});
+ }
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', fetchAndGo);
+ else fetchAndGo();
+})();