/* What's New modal + "New" badge mechanism. * * Two related UI surfaces that share localStorage as their source of truth: * * 1. Auto-show modal on a "feature version" bump. FEATURE_VERSION is an * opaque string controlled here (not CARGO_PKG_VERSION). Edit it when * you want to fire the modal — typically at the end of a sprint when * the changelog has something worth surfacing. Skipping a bump is * fine: nothing happens if FEATURE_VERSION matches the last-seen * version in localStorage. * * 2. "New" badge on individual links/buttons. Mark any element with * data-new-until="YYYY-MM-DD" — JS adds a `is-new` class while today * is before the date. CSS renders the dot. Once the date passes the * class is removed at page load and the dot disappears. * * Both deliberately avoid server cooperation: no API to call, no template * plumbing, no version-detection brittleness. The whole feature lives in * this file plus the CSS for `.is-new`. */ (function () { 'use strict'; // ── What's New modal ────────────────────────────────────────────── /// Bump this when /changelog has shipped something users should see. /// Setting it to a NEW value triggers the modal once per user. var FEATURE_VERSION = 'v0.8-synckit-per-key'; /// Summary shown in the modal body. Keep to 1–3 sentences; the link /// to /changelog is the canonical full list. var FEATURE_HEADLINE = 'SyncKit per-key storage'; var FEATURE_BODY = "SyncKit apps now bill per developer-defined key rather than per app, " + "with mini-gauges in the dashboard and per-key warning emails. " + "Existing SyncKit clients keep working — the SDK signature is updated " + "for new integrations."; var STORAGE_KEY = 'mnw_seen_feature_version'; function safeGet(key) { try { return localStorage.getItem(key); } catch (e) { return null; } } function safeSet(key, value) { try { localStorage.setItem(key, value); } catch (e) { /* ignore */ } } /// Show the modal regardless of seen state. Used by the "What's new" /// link in the footer. function showWhatsNewModal() { var existing = document.getElementById('whats-new-modal'); if (existing) { existing.remove(); return; } var overlay = document.createElement('div'); overlay.id = 'whats-new-modal'; overlay.className = 'modal-overlay'; overlay.style.display = 'flex'; overlay.onclick = function (e) { if (e.target === overlay) { overlay.remove(); safeSet(STORAGE_KEY, FEATURE_VERSION); } }; var content = document.createElement('div'); content.className = 'modal-content'; content.style.maxWidth = '480px'; content.style.padding = '2rem'; var header = document.createElement('div'); header.className = 'modal-header'; header.style.marginBottom = '1rem'; var h2 = document.createElement('h2'); h2.textContent = "What's new: " + FEATURE_HEADLINE; header.appendChild(h2); var closeBtn = document.createElement('button'); closeBtn.type = 'button'; closeBtn.className = 'modal-close'; closeBtn.setAttribute('aria-label', 'Dismiss'); closeBtn.innerHTML = '×'; closeBtn.onclick = function () { overlay.remove(); safeSet(STORAGE_KEY, FEATURE_VERSION); }; header.appendChild(closeBtn); var body = document.createElement('p'); body.textContent = FEATURE_BODY; var link = document.createElement('a'); link.href = '/changelog'; link.textContent = 'Full changelog →'; link.className = 'section-link'; link.style.display = 'inline-block'; link.style.marginTop = '0.75rem'; content.appendChild(header); content.appendChild(body); content.appendChild(link); overlay.appendChild(content); document.body.appendChild(overlay); } /// Auto-show on first visit after a feature-version bump. Records the /// seen version in localStorage so dismissal sticks across reloads. function maybeAutoShowWhatsNew() { var seen = safeGet(STORAGE_KEY); if (seen === FEATURE_VERSION) return; // First-ever visit also seeds the storage — no modal flash for // genuinely new users (they're already getting onboarded). if (seen === null) { safeSet(STORAGE_KEY, FEATURE_VERSION); return; } showWhatsNewModal(); } // Expose for footer link click + onboarding flows. window.showWhatsNewModal = showWhatsNewModal; // ── "New" badge ──────────────────────────────────────────────────── /// Walk every `[data-new-until]` element. If today < that date, mark /// it `.is-new` so the CSS dot renders. Otherwise strip the attribute /// so it doesn't get re-evaluated on later page loads. function applyNewBadges() { var today = new Date(); today.setHours(0, 0, 0, 0); var nodes = document.querySelectorAll('[data-new-until]'); for (var i = 0; i < nodes.length; i++) { var el = nodes[i]; var until = new Date(el.getAttribute('data-new-until')); if (isNaN(until.getTime())) { el.removeAttribute('data-new-until'); continue; } if (today <= until) { el.classList.add('is-new'); } else { el.removeAttribute('data-new-until'); el.classList.remove('is-new'); } } } // ── Init ────────────────────────────────────────────────────────── if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function () { applyNewBadges(); maybeAutoShowWhatsNew(); }); } else { applyNewBadges(); maybeAutoShowWhatsNew(); } })();