let _lastMintedToken = ""; function showNotice(msg, isError) { const banner = document.getElementById("status-banner"); if (!banner) return; banner.textContent = msg; banner.style.display = "block"; banner.style.background = isError ? "var(--danger-bg)" : "var(--success-bg)"; banner.style.color = isError ? "var(--danger-text)" : "var(--success-text)"; banner.style.border = isError ? "1px solid var(--danger-border)" : "1px solid var(--success-border)"; setTimeout(() => { banner.style.display = "none"; }, 5000); } globalThis.showNotice = showNotice; function openDelegateDrawer() { const drawer = document.getElementById("delegateDrawer"); if (!drawer) return; drawer.style.display = "block"; drawer.scrollIntoView({ behavior: "smooth" }); const eventCreateState = document.getElementById("eventCreateState"); if (eventCreateState) eventCreateState.style.display = "block"; const eventHandoffState = document.getElementById("eventHandoffState"); if (eventHandoffState) eventHandoffState.style.display = "none"; const eventForm = document.getElementById("eventForm"); if (eventForm) eventForm.reset(); switchDelegateTab("tabDirectPass"); } globalThis.openDelegateDrawer = openDelegateDrawer; function closeDelegateDrawer() { const drawer = document.getElementById("delegateDrawer"); if (drawer) drawer.style.display = "none"; } globalThis.closeDelegateDrawer = closeDelegateDrawer; function switchDelegateTab(tabId) { const directTab = document.getElementById("tabDirectPass"); const workshopTab = document.getElementById("tabWorkshopPass"); if (directTab) directTab.style.display = "none"; if (workshopTab) workshopTab.style.display = "none"; const btnDirect = document.getElementById("tabBtnDirectPass"); const btnWorkshop = document.getElementById("tabBtnWorkshopPass"); if (btnDirect) { btnDirect.classList.remove("active"); btnDirect.setAttribute("aria-selected", "false"); } if (btnWorkshop) { btnWorkshop.classList.remove("active"); btnWorkshop.setAttribute("aria-selected", "false"); } const activeTab = document.getElementById(tabId); if (activeTab) activeTab.style.display = "block"; if (tabId === "tabDirectPass" && btnDirect) { btnDirect.classList.add("active"); btnDirect.setAttribute("aria-selected", "true"); } else if (btnWorkshop) { btnWorkshop.classList.add("active"); btnWorkshop.setAttribute("aria-selected", "true"); } } globalThis.switchDelegateTab = switchDelegateTab; function selectSeats(btn, count) { document.querySelectorAll(".seat-pill").forEach((b) => b.classList.remove("active") ); btn.classList.add("active"); const input = document.getElementById("eventMaxSeats"); if (input) input.value = count; } globalThis.selectSeats = selectSeats; function selectEventLifespan(btn, hours) { document.querySelectorAll(".event-lifespan-pill").forEach((b) => b.classList.remove("active") ); btn.classList.add("active"); const input = document.getElementById("eventLifespanHours"); if (input) input.value = hours; } globalThis.selectEventLifespan = selectEventLifespan; function selectLifespan(btn, hours) { document.querySelectorAll(".lifespan-pill").forEach((b) => b.classList.remove("active") ); btn.classList.add("active"); const input = document.getElementById("delegateHours"); if (input) input.value = hours; } globalThis.selectLifespan = selectLifespan; function selectMode(btn, mode) { document.querySelectorAll(".mode-pill").forEach((b) => b.classList.remove("active") ); btn.classList.add("active"); const input = document.getElementById("delegateMode"); if (input) input.value = mode; const accordion = document.getElementById("customScopesSection"); if (accordion && mode === "custom") { accordion.open = true; } } globalThis.selectMode = selectMode; function handleTargetAppChange(appName) { if (appName) { const scopeVal = "app:" + appName; document.querySelectorAll(".scope-checkbox").forEach((cb) => { if (cb.value === scopeVal) cb.checked = true; }); } } globalThis.handleTargetAppChange = handleTargetAppChange; async function handleDelegateSession(e) { e.preventDefault(); const labelInput = document.getElementById("delegateLabel"); const label = labelInput ? labelInput.value.trim() : "Delegated Session"; const hoursInput = document.getElementById("delegateHours"); const lifespanHours = parseInt(hoursInput ? hoursInput.value : "1", 10) || 1; const modeInput = document.getElementById("delegateMode"); let mode = modeInput ? modeInput.value : "read_only"; const targetAppInput = document.getElementById("delegateTargetApp"); const targetApp = targetAppInput ? targetAppInput.value : ""; const customScopes = []; if (targetApp) { customScopes.push("app:" + targetApp); } if (mode === "custom" || targetApp) { document.querySelectorAll(".scope-checkbox:checked").forEach((cb) => { if (!customScopes.includes(cb.value)) { customScopes.push(cb.value); } }); if (targetApp && mode !== "custom" && mode !== "admin") { mode = "custom"; } } const btn = document.getElementById("submitDelegateBtn"); if (btn) { btn.disabled = true; btn.textContent = "Creating Session..."; } try { const res = await fetch("/api/sessions/delegate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ label, lifespanHours, mode, customScopes }), }); const data = await res.json(); if (res.ok) { _lastMintedToken = data.token; const origin = globalThis.location ? globalThis.location.origin : ""; const magicElem = document.getElementById("handoffMagicLinkText"); if (magicElem) { magicElem.textContent = origin + "/pass?token=" + data.token; } const cliElem = document.getElementById("handoffCliText"); if (cliElem) { cliElem.textContent = 'export AUTH_YES_TOKEN="' + data.token + '"'; } const curlElem = document.getElementById("handoffCurlText"); if (curlElem) { curlElem.textContent = '-H "Authorization: Bearer ' + data.token + '"'; } const handoffModal = document.getElementById("handoffModal"); if (handoffModal) handoffModal.style.display = "block"; showNotice("Delegated session created for: " + data.label, false); } else { showNotice(data.error || "Failed to delegate session", true); } } catch (_err) { showNotice("Network error delegating session", true); } finally { if (btn) { btn.disabled = false; btn.textContent = "Create Session"; } } } globalThis.handleDelegateSession = handleDelegateSession; async function handleCreateEvent(e) { e.preventDefault(); const nameInput = document.getElementById("eventName"); const name = nameInput ? nameInput.value.trim() : ""; const appIdInput = document.getElementById("eventAppId"); const appId = appIdInput ? appIdInput.value || null : null; const roleInput = document.getElementById("eventRole"); const role = roleInput ? roleInput.value || "viewer" : "viewer"; const maxSeatsInput = document.getElementById("eventMaxSeats"); const maxSeats = parseInt(maxSeatsInput ? maxSeatsInput.value : "0", 10) || 0; const lifespanHoursInput = document.getElementById("eventLifespanHours"); const lifespanHours = parseInt(lifespanHoursInput ? lifespanHoursInput.value : "3", 10) || 3; const slugInput = document.getElementById("eventSlug"); const slug = slugInput ? slugInput.value.trim() || undefined : undefined; const pinInput = document.getElementById("eventPinCode"); const pinCode = pinInput ? pinInput.value.trim() || undefined : undefined; const btn = document.getElementById("submitEventBtn"); if (btn) { btn.disabled = true; btn.textContent = "Creating Event..."; } try { const res = await fetch("/api/events", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, appId, role, maxSeats, lifespanHours, slug, pinCode, }), }); const data = await res.json(); if (res.ok && data.success) { const ev = data.event; const origin = globalThis.location ? globalThis.location.origin : ""; const titleElem = document.getElementById("createdEventTitle"); if (titleElem) titleElem.textContent = ev.name + " Live!"; const pinElem = document.getElementById("eventHandoffPin"); if (pinElem) pinElem.textContent = ev.pin_code; const linkElem = document.getElementById("eventHandoffLink"); if (linkElem) linkElem.textContent = origin + "/e/" + ev.slug; const cliElem = document.getElementById("eventHandoffCli"); if (cliElem) { cliElem.textContent = "curl -sSL " + origin + "/join/" + ev.slug + "?format=env | source /dev/stdin"; } const createState = document.getElementById("eventCreateState"); if (createState) createState.style.display = "none"; const handoffState = document.getElementById("eventHandoffState"); if (handoffState) handoffState.style.display = "block"; showNotice("Workshop pass created: " + ev.name, false); } else { showNotice(data.error || "Failed to create event pass", true); } } catch (_err) { showNotice("Network error creating event pass", true); } finally { if (btn) { btn.disabled = false; btn.textContent = "ποΈ Create Event"; } } } globalThis.handleCreateEvent = handleCreateEvent; function copyEventHandoff(type) { let text = ""; if (type === "pin") { const el = document.getElementById("eventHandoffPin"); if (el) text = el.textContent; } if (type === "link") { const el = document.getElementById("eventHandoffLink"); if (el) text = el.textContent; } if (type === "cli") { const el = document.getElementById("eventHandoffCli"); if (el) text = el.textContent; } if (navigator.clipboard) { navigator.clipboard.writeText(text); showNotice("Copied to clipboard: " + text, false); } } globalThis.copyEventHandoff = copyEventHandoff; function closeEventHandoffModal() { closeDelegateDrawer(); if (globalThis.location) globalThis.location.reload(); } globalThis.closeEventHandoffModal = closeEventHandoffModal; function copyHandoff(type) { let text = ""; if (type === "cli") { const el = document.getElementById("handoffCliText"); if (el) text = el.textContent; } if (type === "curl") { const el = document.getElementById("handoffCurlText"); if (el) text = el.textContent; } if (type === "magic") { const el = document.getElementById("handoffMagicLinkText"); if (el) text = el.textContent; } if (navigator.clipboard) { navigator.clipboard.writeText(text); showNotice("Copied to clipboard: " + text, false); } } globalThis.copyHandoff = copyHandoff; function closeHandoffModal() { const modal = document.getElementById("handoffModal"); if (modal) modal.style.display = "none"; if (globalThis.location) globalThis.location.reload(); } globalThis.closeHandoffModal = closeHandoffModal; async function extendSession(sessionId, hours) { try { const res = await fetch("/api/sessions/" + sessionId + "/extend", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ extendHours: hours }), }); if (res.ok) { showNotice("Session extended by " + hours + " hour(s)!", false); setTimeout(() => { if (globalThis.location) globalThis.location.reload(); }, 600); } else { const data = await res.json(); showNotice(data.error || "Failed to extend session", true); } } catch (_err) { showNotice("Network error extending session", true); } } globalThis.extendSession = extendSession; function openEditScopesModal(sessionId, label, scopesJson) { let scopes = []; try { scopes = typeof scopesJson === "string" ? JSON.parse(scopesJson) : scopesJson; if (typeof scopes === "string") scopes = JSON.parse(scopes); } catch (_e) { scopes = []; } const idInput = document.getElementById("editScopesSessionId"); if (idInput) idInput.value = sessionId; const labelEl = document.getElementById("editScopesLabel"); if (labelEl) labelEl.textContent = label; document.querySelectorAll(".edit-scope-chk").forEach((cb) => { cb.checked = Array.isArray(scopes) && scopes.includes(cb.value); }); const modal = document.getElementById("editScopesModal"); if (modal) modal.style.display = "flex"; } globalThis.openEditScopesModal = openEditScopesModal; function closeEditScopesModal() { const modal = document.getElementById("editScopesModal"); if (modal) modal.style.display = "none"; } globalThis.closeEditScopesModal = closeEditScopesModal; async function saveUpdatedScopes() { const idInput = document.getElementById("editScopesSessionId"); const sessionId = idInput ? idInput.value : ""; const customScopes = []; document.querySelectorAll(".edit-scope-chk:checked").forEach((cb) => { customScopes.push(cb.value); }); try { const res = await fetch("/api/sessions/" + sessionId + "/scopes", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ customScopes }), }); if (res.ok) { showNotice("Session scopes updated!", false); setTimeout(() => { if (globalThis.location) globalThis.location.reload(); }, 500); } else { const data = await res.json(); showNotice(data.error || "Failed to update scopes", true); } } catch (_err) { showNotice("Network error updating scopes", true); } } globalThis.saveUpdatedScopes = saveUpdatedScopes; function copyText(text) { if (navigator.clipboard) { navigator.clipboard.writeText(text); showNotice("Copied to clipboard!", false); } } globalThis.copyText = copyText; async function rotatePin(eventId) { try { const res = await fetch("/api/events/" + eventId + "/rotate-pin", { method: "POST", }); const data = await res.json(); if (res.ok && data.success) { const pinElem = document.getElementById("pin-" + eventId); if (pinElem) pinElem.textContent = data.pinCode; const compactPinElem = document.getElementById( "compact-pin-" + eventId, ); if (compactPinElem) compactPinElem.textContent = data.pinCode; if (data.slug) { const origin = globalThis.location ? globalThis.location.origin : ""; const linkUrl = origin + "/e/" + data.slug; const cliCmd = "curl -sSL " + origin + "/join/" + data.slug + "?format=env | source /dev/stdin"; const linkCodeElem = document.getElementById("link-code-" + eventId); if (linkCodeElem) linkCodeElem.textContent = "/e/" + data.slug; const linkBtnElem = document.getElementById("link-btn-" + eventId); if (linkBtnElem) linkBtnElem.onclick = () => copyText(linkUrl); const cliCodeElem = document.getElementById("cli-code-" + eventId); if (cliCodeElem) cliCodeElem.textContent = cliCmd; const cliBtnElem = document.getElementById("cli-btn-" + eventId); if (cliBtnElem) { cliBtnElem.onclick = (e) => { if (e && e.stopPropagation) e.stopPropagation(); copyText(cliCmd); }; } const compactLinkElem = document.getElementById( "compact-link-" + eventId, ); if (compactLinkElem) { compactLinkElem.onclick = () => copyText(linkUrl); } const compactCliElem = document.getElementById( "compact-cli-" + eventId, ); if (compactCliElem) { compactCliElem.onclick = () => copyText(cliCmd); } } showNotice("Event credentials rotated successfully", false); } else { showNotice(data.error || "Failed to rotate credentials", true); } } catch (_err) { showNotice("Network error rotating credentials", true); } } globalThis.rotatePin = rotatePin; async function expandSeats(eventId, count) { try { const res = await fetch("/api/events/" + eventId + "/expand", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ addSeats: count }), }); const data = await res.json(); if (res.ok && data.success) { showNotice("Expanded capacity by " + count + " seats", false); setTimeout(() => { if (globalThis.location) globalThis.location.reload(); }, 600); } else { showNotice(data.error || "Failed to expand seats", true); } } catch (_err) { showNotice("Network error expanding seats", true); } } globalThis.expandSeats = expandSeats; function closeAttendeesDrawer() { const drawer = document.getElementById("attendeesDrawer"); if (drawer) { drawer.classList.remove("open"); const panel = drawer.querySelector(".drawer-panel"); if (panel) panel.classList.remove("open"); setTimeout(() => { drawer.style.display = "none"; }, 250); } } globalThis.closeAttendeesDrawer = closeAttendeesDrawer; function formatNaturalExpiry(expiresAt) { const expDate = new Date(expiresAt); const now = new Date(); const diffMs = expDate.getTime() - now.getTime(); const timeStr = expDate.toLocaleTimeString([], { hour: "numeric", minute: "2-digit", }); const fullISO = expDate.toISOString(); let dateStr = ""; const isToday = expDate.getDate() === now.getDate() && expDate.getMonth() === now.getMonth() && expDate.getFullYear() === now.getFullYear(); const tomorrow = new Date(now); tomorrow.setDate(tomorrow.getDate() + 1); const isTomorrow = expDate.getDate() === tomorrow.getDate() && expDate.getMonth() === tomorrow.getMonth() && expDate.getFullYear() === tomorrow.getFullYear(); if (isToday) { dateStr = "Today"; } else if (isTomorrow) { dateStr = "Tomorrow"; } else if (expDate.getFullYear() === now.getFullYear()) { dateStr = expDate.toLocaleDateString([], { month: "short", day: "numeric", }); } else { dateStr = expDate.toLocaleDateString([], { month: "short", day: "numeric", year: "numeric", }); } let badge = ""; if (diffMs <= 0) { badge = 'Expired'; } else { const totalMins = Math.floor(diffMs / 60000); const hours = totalMins / 60; const days = hours / 24; const months = days / 30; const years = days / 365; let timeRemainingStr = ""; let badgeStyle = "background:rgba(34,197,94,0.15);color:#16a34a;padding:2px 6px;border-radius:4px;font-size:0.75rem;font-weight:600;"; if (hours < 1) { timeRemainingStr = totalMins + "m left"; badgeStyle = "background:rgba(245,158,11,0.15);color:#d97706;padding:2px 6px;border-radius:4px;font-size:0.75rem;font-weight:600;"; } else if (hours < 24) { const h = Math.floor(hours); const m = totalMins % 60; timeRemainingStr = h + "h " + m + "m left"; badgeStyle = "background:rgba(245,158,11,0.15);color:#d97706;padding:2px 6px;border-radius:4px;font-size:0.75rem;font-weight:600;"; } else if (days <= 60) { timeRemainingStr = Math.floor(days) + "d left"; } else if (months <= 12) { timeRemainingStr = months.toFixed(1) + " mos left"; } else { timeRemainingStr = years.toFixed(1) + " yrs left"; } badge = '' + timeRemainingStr + ""; } return dateStr + " · " + timeStr + " · " + badge; } globalThis.formatNaturalExpiry = formatNaturalExpiry; function formatNaturalJoinTime(createdAt, isActive) { const createdDate = new Date(createdAt); const now = new Date(); const diffMs = now.getTime() - createdDate.getTime(); const diffMins = Math.floor(diffMs / 60000); const timeStr = createdDate.toLocaleTimeString([], { hour: "numeric", minute: "2-digit", }); const isToday = createdDate.getDate() === now.getDate() && expDateMatches(createdDate, now); const dateStr = isToday ? "Today" : createdDate.toLocaleDateString([], { month: "short", day: "numeric", }); let durationStr = ""; if (diffMins < 60) { durationStr = diffMins + "m"; } else { const h = Math.floor(diffMins / 60); const m = diffMins % 60; durationStr = h + "h " + m + "m"; } const badgeStyle = isActive ? "background:rgba(34,197,94,0.15);color:#16a34a;padding:2px 6px;border-radius:4px;font-size:0.75rem;font-weight:600;" : "background:rgba(234,179,8,0.15);color:#ca8a04;padding:2px 6px;border-radius:4px;font-size:0.75rem;font-weight:600;"; const badgeText = isActive ? "Active " + durationStr : "Paused"; const activeBadge = '' + badgeText + ""; return "Joined " + dateStr + " · " + timeStr + " · " + activeBadge; } function expDateMatches(a, b) { return a.getMonth() === b.getMonth() && a.getFullYear() === b.getFullYear(); } globalThis.formatNaturalJoinTime = formatNaturalJoinTime; function updateAllCountdowns() { const pills = document.querySelectorAll( ".countdown-pill, #guestDrawerCountdown", ); pills.forEach((pill) => { const expiresAtStr = pill.getAttribute("data-expires-at"); if (!expiresAtStr) return; const now = new Date(); const expDate = new Date(expiresAtStr); const diffMs = expDate.getTime() - now.getTime(); if (diffMs <= 0) { pill.textContent = "β³ Expired"; } else { const totalMins = Math.floor(diffMs / 60000); const hours = Math.floor(totalMins / 60); const mins = totalMins % 60; pill.textContent = "β³ " + hours + "h " + mins + "m left"; } }); } globalThis.updateAllCountdowns = updateAllCountdowns; async function openAttendeesDrawer(eventId, eventName) { const titleEl = document.getElementById("guestDrawerTitle"); if (titleEl) titleEl.textContent = eventName + " Guests"; const contentEl = document.getElementById("attendeesDrawerContent"); if (contentEl) { contentEl.innerHTML = '