diff --git a/static/js/core/shell.js b/static/js/core/shell.js index 1bc5c06..b5298b4 100644 --- a/static/js/core/shell.js +++ b/static/js/core/shell.js @@ -9,14 +9,22 @@ */ import { BASE_URL, getConfig } from "./config.js"; +import { ensureDefaultPolicy } from "./trusted-types.js"; import { initResponsive } from "./responsive.js"; import { initTheme } from "./theme-engine.js"; import { initAuth } from "../system/auth-integration.js"; import { checkAccess, renderAccessWall } from "../system/access-guard.js"; -import { filterAppsByRole, getManifestSync } from "../system/manifest.js"; +import { + fetchManifest, + filterAppsByRole, + getManifestSync, +} from "../system/manifest.js"; import { initDropdowns } from "../ui/dropdowns.js"; import { SHELL_CONFIG_DEFAULTS } from "./shell-config.js"; +// Must run before any DOM injection sink is used (innerHTML, script.src, etc.) +ensureDefaultPolicy(); + window.__componentsJS = true; let _injected = false; @@ -202,12 +210,20 @@ function safeIconClass(value) { } function renderAppsGrid(shellRoot, apps) { - const grids = shellRoot.querySelectorAll("[data-app-menu-grid]"); + // Backward compatibility: + // - New markup uses: [data-app-menu-grid="desktop"|"mobile"] + // - Older injected markup used: [data-apps-grid] and [data-apps-grid-mobile] + const grids = shellRoot.querySelectorAll( + "[data-app-menu-grid], [data-apps-grid], [data-apps-grid-mobile]", + ); if (!grids.length) return; const entries = Array.isArray(apps) ? apps : []; grids.forEach((grid) => { - const isDesktop = grid.getAttribute("data-app-menu-grid") === "desktop"; + const gridMode = grid.getAttribute("data-app-menu-grid"); + const isDesktop = + gridMode === "desktop" || + (!gridMode && grid.hasAttribute("data-apps-grid")); const padding = isDesktop ? "p-3" : "p-2.5"; const fragment = document.createDocumentFragment(); @@ -243,11 +259,17 @@ function renderAppsGrid(shellRoot, apps) { }); } -function updateAppsGridForRole(shellRoot, role) { - const fallback = getManifestSync(); - const visibleApps = filterAppsByRole(fallback.apps, role); - renderAppsGrid(shellRoot, visibleApps); - return Promise.resolve(visibleApps); +async function updateAppsGridForRole(shellRoot, role) { + // Render cached/fallback immediately (fast paint). + const cached = getManifestSync(); + const cachedVisible = filterAppsByRole(cached.apps, role); + renderAppsGrid(shellRoot, cachedVisible); + + // Then refresh from the live manifest source of truth. + const fresh = await fetchManifest(role); + const freshVisible = filterAppsByRole(fresh.apps, role); + renderAppsGrid(shellRoot, freshVisible); + return freshVisible; } // --- Mobile panel logic --- @@ -311,7 +333,14 @@ function syncMobileAuth(shellRoot, authStatus) { if (authedAvatar) { authedAvatar.classList.remove("hidden"); const img = authedAvatar.querySelector("img"); - if (img) img.src = user.avatar_url || ""; + if (img) { + const avatarUrl = user.avatar_url; + if (avatarUrl) { + img.src = avatarUrl; + } else { + img.removeAttribute("src"); + } + } } if (nameEl) nameEl.textContent = user.name || "User"; if (emailEl) emailEl.textContent = user.email || ""; @@ -382,8 +411,6 @@ function hydrate(shellRoot) { applyChromeVisibility(shellRoot, config); - const cachedManifest = getManifestSync(); - renderAppsGrid(shellRoot, filterAppsByRole(cachedManifest.apps, "guest")); updateAppsGridForRole(shellRoot, "guest"); initResponsive(); diff --git a/static/js/core/trusted-types.js b/static/js/core/trusted-types.js new file mode 100644 index 0000000..5feea9c --- /dev/null +++ b/static/js/core/trusted-types.js @@ -0,0 +1,52 @@ +/** + * Trusted Types Bootstrap — Shared across main site and subdomains. + * + * Creates the 'default' Trusted Types policy so that raw string + * assignments to innerHTML, script.src, etc. are automatically + * converted to TrustedHTML / TrustedScriptURL by the browser. + * + * Must run BEFORE any code that touches DOM injection sinks. + */ +export function ensureDefaultPolicy() { + if (window.trustedTypes && window.trustedTypes.createPolicy) { + try { + window.__defaultPolicy = window.trustedTypes.createPolicy("default", { + createScriptURL: function (s) { + return s; + }, + createHTML: function (s) { + return s; + }, + }); + } catch { + /* policy already exists — safe to ignore */ + } + } + + // Patch appendChild/insertBefore so dynamically-created iframes + // (e.g. Cloudflare challenge, giscus) also get a default policy. + function installFramePolicy(methodName) { + const original = Element.prototype[methodName]; + Element.prototype[methodName] = function (...args) { + const result = original.apply(this, args); + const node = args[0]; + if (node && node.tagName === "IFRAME") { + try { + const win = node.contentWindow; + if (win && win.trustedTypes && !win.trustedTypes.defaultPolicy) { + win.trustedTypes.createPolicy("default", { + createHTML: (s) => s, + createScript: (s) => s, + createScriptURL: (s) => s, + }); + } + } catch { + /* cross-origin — expected */ + } + } + return result; + }; + } + installFramePolicy("appendChild"); + installFramePolicy("insertBefore"); +} diff --git a/static/js/shell.min.js b/static/js/shell.min.js index ced9e91..3ed0453 100644 --- a/static/js/shell.min.js +++ b/static/js/shell.min.js @@ -1,4 +1,4 @@ -(()=>{function P(){if(window.__siteConfig)return window.__siteConfig;let e=document.getElementById("site-config");if(e)try{return window.__siteConfig=JSON.parse(e.textContent),window.__siteConfig}catch(t){console.error("[SiteConfig] Failed to parse JSON config",t)}return window.__siteConfig=window.SiteNavConfig||{},window.__siteConfig}var S="https://dhanur.me",H=(()=>{let e=window.location.hostname;if(e==="localhost"||e==="127.0.0.1")return"";let t=e.split(".");return t.length>=2?"."+t.slice(-2).join("."):""})();var fe={MOBILE:0,SM:640,MD:768,LG:1024,XL:1280,XXL:1536},d={isDesktop:!1,isMobile:!1,mediaQueries:{},listeners:new Set},O=!1;function _(){let e=d.isDesktop;d.isDesktop=d.mediaQueries.hoverCapable?.matches&&d.mediaQueries.finePointer?.matches,d.isMobile=!d.isDesktop,e!==d.isDesktop&&he()}function he(){d.listeners.forEach(e=>{try{e({isDesktop:d.isDesktop,isMobile:d.isMobile,isLargeScreen:me(),isTouchDevice:pe(),prefersReducedMotion:ge()})}catch(t){console.error(t)}})}function U(){O||typeof window.matchMedia=="function"&&(O=!0,d.mediaQueries.hoverCapable=window.matchMedia("(hover: hover)"),d.mediaQueries.finePointer=window.matchMedia("(pointer: fine)"),d.mediaQueries.largeScreen=window.matchMedia(`(min-width: ${fe.LG}px)`),d.mediaQueries.touchDevice=window.matchMedia("(pointer: coarse)"),d.mediaQueries.reducedMotion=window.matchMedia("(prefers-reduced-motion: reduce)"),_(),Object.values(d.mediaQueries).forEach(e=>{e.addListener?e.addListener(_):e.addEventListener&&e.addEventListener("change",_)}))}var me=()=>d.mediaQueries.largeScreen?.matches||!1,pe=()=>d.mediaQueries.touchDevice?.matches||!1,ge=()=>d.mediaQueries.reducedMotion?.matches||!1;function G(e){let t=`${e}=`,n=document.cookie?document.cookie.split(";"):[];for(let o of n){let a=o.trim();if(a.startsWith(t))return decodeURIComponent(a.slice(t.length))}return""}function j(e,t,n={}){let{maxAgeSeconds:o,domain:a,path:i="/",sameSite:r="Lax"}=n,l=typeof o=="number"?`; Max-Age=${o}`:"",s=a?`; Domain=${a}`:"";document.cookie=`${e}=${encodeURIComponent(t)}; Path=${i}; SameSite=${r}; Secure${l}${s}`}var ye={dark:"dark",light:"light"},ve=["auto","light","dark"],z={dark:"#010409",light:"#f6f8fa"},we=240,v="auto",g=window.matchMedia?window.matchMedia("(prefers-color-scheme: dark)"):null,A=null;function L(e,t="auto"){let n=String(e||"").trim().toLowerCase();return ve.includes(n)?n:n.includes("dark")?"dark":n.includes("light")?"light":t}function be(){let e=window.__getThemeCookie?window.__getThemeCookie():G("theme")||null;return L(e,"auto")}function Se(e){let t=L(e,"auto");if(window.__setThemeCookie){window.__setThemeCookie(t);return}j("theme",t,{maxAgeSeconds:31536e3,domain:H||void 0,path:"/",sameSite:"Lax",secure:window.location.protocol==="https:"})}function M(e){let t=L(e,"auto");return window.__resolveColorset?window.__resolveColorset(t):t==="auto"?g&&g.matches?"dark":"light":t}function T(e){let t=ye[e]||e;document.documentElement.setAttribute("data-theme",t),document.documentElement.style.backgroundColor=z[e]||z.dark,e==="dark"?(document.documentElement.classList.add("dark"),document.documentElement.classList.remove("light"),document.documentElement.style.colorScheme="dark"):(document.documentElement.classList.add("light"),document.documentElement.classList.remove("dark"),document.documentElement.style.colorScheme="light");function n(o,a){document.querySelectorAll(o).forEach(i=>{i.classList.remove("hidden","invisible"),i.style.opacity=a?"1":"0",i.style.visibility=a?"visible":"hidden",i.style.pointerEvents=a?"":"none"})}n(".logo-dark",e==="dark"),n(".logo-light",e==="light"),n(".hero-dark",e==="dark"),n(".hero-light",e==="light")}function F(){let e=document.documentElement;e.classList.add("is-theme-switching"),A&&window.clearTimeout(A),A=window.setTimeout(()=>{e.classList.remove("is-theme-switching"),A=null},we)}function D(){document.querySelectorAll(".theme-switcher").forEach(e=>{e.querySelectorAll("[data-theme-mode]").forEach(t=>{let o=t.getAttribute("data-theme-mode")===v;t.style.background=o?"color-mix(in oklab, var(--color-base-content) 20%, transparent)":"",t.style.boxShadow=o?"0 1px 3px rgba(0,0,0,0.12), inset 0 0 0 1px color-mix(in oklab, var(--color-base-content) 8%, transparent)":"",t.style.opacity=o?"1":"0.55"})})}function Ae(e){v=e,Se(e),F();let t=M(e);T(t),D(),document.dispatchEvent(new CustomEvent("themeChanged",{detail:t}))}function Q(e=document){v=L(be(),"auto");let t=M(v);if(T(t),D(),document.dispatchEvent(new CustomEvent("themeChanged",{detail:t})),e.querySelectorAll(".theme-switcher [data-theme-mode]").forEach(n=>{n.addEventListener("click",o=>{o.preventDefault(),o.stopPropagation();let a=n.getAttribute("data-theme-mode");a&&a!==v&&Ae(a)})}),g){let n=()=>{if(v==="auto"){F();let o=M("auto");T(o),D(),document.dispatchEvent(new CustomEvent("themeChanged",{detail:o}))}};g.addEventListener?g.addEventListener("change",n):g.addListener&&g.addListener(n)}}function W({src:e,selector:t,async:n=!0,defer:o=!1,crossOrigin:a,loadedAttribute:i,onLoad:r,onError:l}){if(!e&&!t)return null;let s=t?document.querySelector(t):document.querySelector(`script[src="${e}"]`);if(s)return typeof r=="function"&&((i?s.getAttribute(i)==="1":!1)||s.readyState==="complete"?r():s.addEventListener("load",r,{once:!0})),typeof l=="function"&&s.addEventListener("error",l,{once:!0}),s;let c=document.createElement("script"),u=window.__defaultPolicy;return c.src=u?u.createScriptURL(e):e,c.async=n,c.defer=o,a&&(c.crossOrigin=a),c.addEventListener("load",()=>{i&&c.setAttribute(i,"1"),typeof r=="function"&&r()},{once:!0}),typeof l=="function"&&c.addEventListener("error",l,{once:!0}),document.head.appendChild(c),c}function C(){return!window.AUTH||typeof window.AUTH!="object"?null:window.AUTH}function Le(e){let t=!1;return(...n)=>{t||(t=!0,e(...n))}}function p(...e){return e.find(Boolean)||null}function b(...e){e.forEach(t=>t?.classList.remove("hidden"))}function k(...e){e.forEach(t=>t?.classList.add("hidden"))}function w(e,t){e&&(e.textContent=t)}function I(e,t){if(e){if(t){e.src=t;return}e.removeAttribute("src")}}function J(e,t,n,o,a){e.querySelectorAll(t).forEach(i=>{i.addEventListener(n,o,a)})}function ke(e){let t=Le(e);if(C()){t();return}document.addEventListener("authReady",t,{once:!0}),W({src:"https://auth.dhanur.me/auth-client.js",selector:'script[src*="auth-client.js"]',defer:!0,onLoad:()=>{let n=C();n&&typeof n.onReady=="function"?n.onReady(()=>t()):t()},onError:()=>{console.warn("[Auth] Could not load auth-client.js")}}),window.setTimeout(()=>{C()&&t()},2e3)}function Ce(e){if(!e)return"";try{let t=new Date(e);return`resets ${["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][t.getUTCMonth()]} ${t.getUTCDate()}`}catch{return""}}function K(e,t){if(!t)return;let n=t.unlimited||t.balance===-1,o=n?"\u221E":String(t.balance),a=n?"":Ce(t.periodEnd),i=e.querySelector('[data-auth="credits-row"]');if(i){i.classList.remove("hidden");let l=i.querySelector('[data-auth="credits-balance"]'),s=i.querySelector('[data-auth="credits-reset"]');l&&(l.textContent=o),s&&(s.textContent=a)}let r=e.querySelector('[data-auth="sidebar-credits-row"]');if(r){r.classList.remove("hidden");let l=r.querySelector('[data-auth="sidebar-credits-balance"]'),s=r.querySelector('[data-auth="sidebar-credits-reset"]');l&&(l.textContent=o),s&&(s.textContent=a)}}function qe(e){e.querySelector('[data-auth="credits-row"]')?.classList.add("hidden"),e.querySelector('[data-auth="sidebar-credits-row"]')?.classList.add("hidden")}function X(e=document,t=null){e.__authIntegrationBound||(e.__authIntegrationBound=!0,ke(()=>{let n=C();if(!n)return;let o=e.querySelector(".navbar"),a=e.querySelector("[data-sidebar-root]")||e.querySelector("#navigation-drawer"),i=e.querySelector("[data-sidebar-account]"),r={navGuestAvatar:p(e.querySelector('[data-auth="nav-guest-avatar"]'),o?.querySelector('[data-dropdown="account"] .bg-base-300')),navAuthedAvatar:p(e.querySelector('[data-auth="nav-authed-avatar"]'),o?.querySelector('[data-dropdown="account"] .ring-primary')),navAvatarImg:p(e.querySelector('[data-auth="nav-authed-avatar"] img'),o?.querySelector('[data-dropdown="account"] .ring-primary img')),navAuthedHeader:p(e.querySelector('[data-auth="nav-authed-header"]'),o?.querySelector('.dropdown-panel [data-auth="nav-name"]')?.closest(".border-b")),navGuestHeader:p(e.querySelector('[data-auth="nav-guest-header"]'),o?.querySelector(".dropdown-panel .fa-user")?.closest(".border-b")),navAuthedHeaderImg:e.querySelector('[data-auth="nav-authed-header-avatar"]'),navName:p(e.querySelector('[data-auth="nav-name"]'),o?.querySelector('[data-auth="name"]')),navEmail:p(e.querySelector('[data-auth="nav-email"]'),o?.querySelector('[data-auth="email"]')),navRole:p(e.querySelector('[data-auth="nav-role"]'),o?.querySelector('[data-auth="role"]')),navLoginItem:o?.querySelector('[data-auth="login-item"]')||null,navAccountItem:o?.querySelector('[data-auth="account-item"]')||null,navLogoutItem:o?.querySelector('[data-auth="logout-item"]')||null,sidebarGuestAvatar:p(e.querySelector('[data-auth="sidebar-guest-avatar"]'),i?.querySelector(".bg-base-300")),sidebarAuthedAvatar:p(e.querySelector('[data-auth="sidebar-authed-avatar"]'),i?.querySelector(".ring-primary")),sidebarAvatarImg:p(e.querySelector('[data-auth="sidebar-authed-avatar"] img'),i?.querySelector(".ring-primary img")),sidebarName:p(e.querySelector('[data-auth="sidebar-name"]'),i?.querySelector(".font-semibold")),sidebarEmail:p(e.querySelector('[data-auth="sidebar-email"]'),i?.querySelector(".text-xs.opacity-60")),sidebarLoginBtn:a?.querySelector('[data-auth="sidebar-login-btn"]')||null,sidebarLogoutBtn:a?.querySelector('[data-auth="sidebar-logout-btn"]')||null,sidebarAccountBtn:a?.querySelector('[data-auth="sidebar-account-btn"]')||null};function l(s){if(!s)return;let c=s.authenticated,u=s.user,h=u?.avatar_url||"",f=u?.name||"User";if(c&&u){if(k(r.navGuestAvatar,r.navGuestHeader,r.navLoginItem),b(r.navAuthedAvatar,r.navAuthedHeader,r.navAccountItem,r.navLogoutItem),I(r.navAvatarImg,h),I(r.navAuthedHeaderImg,h),w(r.navName,f),w(r.navEmail,u.email||""),r.navRole){let m=s.role||"user";r.navRole.textContent=m.toUpperCase(),r.navRole.className=m==="admin"?"badge badge-sm badge-error":"badge badge-sm badge-success",b(r.navRole)}k(r.sidebarGuestAvatar,r.sidebarLoginBtn),b(r.sidebarAuthedAvatar,r.sidebarLogoutBtn,r.sidebarAccountBtn),I(r.sidebarAvatarImg,h),w(r.sidebarName,f),w(r.sidebarEmail,u.email||""),K(e,s.credits||null)}else b(r.navGuestAvatar,r.navGuestHeader,r.navLoginItem),k(r.navAuthedAvatar,r.navAuthedHeader,r.navRole,r.navAccountItem,r.navLogoutItem),b(r.sidebarGuestAvatar,r.sidebarLoginBtn),k(r.sidebarAuthedAvatar,r.sidebarLogoutBtn,r.sidebarAccountBtn),w(r.sidebarName,"Guest"),w(r.sidebarEmail,"Not signed in"),qe(e);typeof t=="function"&&t(s)}typeof n.onReady=="function"?n.onReady(s=>l(s?.status||s||n.status||null)):n.status&&l(n.status),document.addEventListener("authChanged",s=>l(s.detail)),document.addEventListener("creditsChanged",s=>K(e,s.detail)),J(e,'[data-auth="login-btn"], [data-auth="sidebar-login-btn"]',"click",s=>{s.preventDefault(),typeof n.login=="function"&&n.login()}),J(e,'[data-auth="logout-btn"], [data-auth="sidebar-logout-btn"]',"click",s=>{if(s.preventDefault(),typeof n.logout=="function"){let c=n.logout();c&&typeof c.then=="function"?c.then(()=>window.location.reload()).catch(u=>{console.warn("[Auth] Logout failed:",u),window.location.reload()}):window.location.reload()}})}))}var Ee=` +(()=>{function H(){if(window.__siteConfig)return window.__siteConfig;let e=document.getElementById("site-config");if(e)try{return window.__siteConfig=JSON.parse(e.textContent),window.__siteConfig}catch(t){console.error("[SiteConfig] Failed to parse JSON config",t)}return window.__siteConfig=window.SiteNavConfig||{},window.__siteConfig}var A="https://dhanur.me",O=(()=>{let e=window.location.hostname;if(e==="localhost"||e==="127.0.0.1")return"";let t=e.split(".");return t.length>=2?"."+t.slice(-2).join("."):""})();var ye={MOBILE:0,SM:640,MD:768,LG:1024,XL:1280,XXL:1536},f={isDesktop:!1,isMobile:!1,mediaQueries:{},listeners:new Set},G=!1;function M(){let e=f.isDesktop;f.isDesktop=f.mediaQueries.hoverCapable?.matches&&f.mediaQueries.finePointer?.matches,f.isMobile=!f.isDesktop,e!==f.isDesktop&&ve()}function ve(){f.listeners.forEach(e=>{try{e({isDesktop:f.isDesktop,isMobile:f.isMobile,isLargeScreen:we(),isTouchDevice:be(),prefersReducedMotion:Se()})}catch(t){console.error(t)}})}function j(){G||typeof window.matchMedia=="function"&&(G=!0,f.mediaQueries.hoverCapable=window.matchMedia("(hover: hover)"),f.mediaQueries.finePointer=window.matchMedia("(pointer: fine)"),f.mediaQueries.largeScreen=window.matchMedia(`(min-width: ${ye.LG}px)`),f.mediaQueries.touchDevice=window.matchMedia("(pointer: coarse)"),f.mediaQueries.reducedMotion=window.matchMedia("(prefers-reduced-motion: reduce)"),M(),Object.values(f.mediaQueries).forEach(e=>{e.addListener?e.addListener(M):e.addEventListener&&e.addEventListener("change",M)}))}var we=()=>f.mediaQueries.largeScreen?.matches||!1,be=()=>f.mediaQueries.touchDevice?.matches||!1,Se=()=>f.mediaQueries.reducedMotion?.matches||!1;function z(e){let t=`${e}=`,n=document.cookie?document.cookie.split(";"):[];for(let a of n){let o=a.trim();if(o.startsWith(t))return decodeURIComponent(o.slice(t.length))}return""}function F(e,t,n={}){let{maxAgeSeconds:a,domain:o,path:r="/",sameSite:i="Lax"}=n,c=typeof a=="number"?`; Max-Age=${a}`:"",s=o?`; Domain=${o}`:"";document.cookie=`${e}=${encodeURIComponent(t)}; Path=${r}; SameSite=${i}; Secure${c}${s}`}var Ae={dark:"dark",light:"light"},Le=["auto","light","dark"],Q={dark:"#010409",light:"#f6f8fa"},Ce=240,w="auto",y=window.matchMedia?window.matchMedia("(prefers-color-scheme: dark)"):null,L=null;function C(e,t="auto"){let n=String(e||"").trim().toLowerCase();return Le.includes(n)?n:n.includes("dark")?"dark":n.includes("light")?"light":t}function ke(){let e=window.__getThemeCookie?window.__getThemeCookie():z("theme")||null;return C(e,"auto")}function qe(e){let t=C(e,"auto");if(window.__setThemeCookie){window.__setThemeCookie(t);return}F("theme",t,{maxAgeSeconds:31536e3,domain:O||void 0,path:"/",sameSite:"Lax",secure:window.location.protocol==="https:"})}function T(e){let t=C(e,"auto");return window.__resolveColorset?window.__resolveColorset(t):t==="auto"?y&&y.matches?"dark":"light":t}function D(e){let t=Ae[e]||e;document.documentElement.setAttribute("data-theme",t),document.documentElement.style.backgroundColor=Q[e]||Q.dark,e==="dark"?(document.documentElement.classList.add("dark"),document.documentElement.classList.remove("light"),document.documentElement.style.colorScheme="dark"):(document.documentElement.classList.add("light"),document.documentElement.classList.remove("dark"),document.documentElement.style.colorScheme="light");function n(a,o){document.querySelectorAll(a).forEach(r=>{r.classList.remove("hidden","invisible"),r.style.opacity=o?"1":"0",r.style.visibility=o?"visible":"hidden",r.style.pointerEvents=o?"":"none"})}n(".logo-dark",e==="dark"),n(".logo-light",e==="light"),n(".hero-dark",e==="dark"),n(".hero-light",e==="light")}function W(){let e=document.documentElement;e.classList.add("is-theme-switching"),L&&window.clearTimeout(L),L=window.setTimeout(()=>{e.classList.remove("is-theme-switching"),L=null},Ce)}function I(){document.querySelectorAll(".theme-switcher").forEach(e=>{e.querySelectorAll("[data-theme-mode]").forEach(t=>{let a=t.getAttribute("data-theme-mode")===w;t.style.background=a?"color-mix(in oklab, var(--color-base-content) 20%, transparent)":"",t.style.boxShadow=a?"0 1px 3px rgba(0,0,0,0.12), inset 0 0 0 1px color-mix(in oklab, var(--color-base-content) 8%, transparent)":"",t.style.opacity=a?"1":"0.55"})})}function Ee(e){w=e,qe(e),W();let t=T(e);D(t),I(),document.dispatchEvent(new CustomEvent("themeChanged",{detail:t}))}function J(e=document){w=C(ke(),"auto");let t=T(w);if(D(t),I(),document.dispatchEvent(new CustomEvent("themeChanged",{detail:t})),e.querySelectorAll(".theme-switcher [data-theme-mode]").forEach(n=>{n.addEventListener("click",a=>{a.preventDefault(),a.stopPropagation();let o=n.getAttribute("data-theme-mode");o&&o!==w&&Ee(o)})}),y){let n=()=>{if(w==="auto"){W();let a=T("auto");D(a),I(),document.dispatchEvent(new CustomEvent("themeChanged",{detail:a}))}};y.addEventListener?y.addEventListener("change",n):y.addListener&&y.addListener(n)}}function K({src:e,selector:t,async:n=!0,defer:a=!1,crossOrigin:o,loadedAttribute:r,onLoad:i,onError:c}){if(!e&&!t)return null;let s=t?document.querySelector(t):document.querySelector(`script[src="${e}"]`);if(s)return typeof i=="function"&&((r?s.getAttribute(r)==="1":!1)||s.readyState==="complete"?i():s.addEventListener("load",i,{once:!0})),typeof c=="function"&&s.addEventListener("error",c,{once:!0}),s;let l=document.createElement("script"),u=window.__defaultPolicy;return l.src=u?u.createScriptURL(e):e,l.async=n,l.defer=a,o&&(l.crossOrigin=o),l.addEventListener("load",()=>{r&&l.setAttribute(r,"1"),typeof i=="function"&&i()},{once:!0}),typeof c=="function"&&l.addEventListener("error",c,{once:!0}),document.head.appendChild(l),l}function q(){return!window.AUTH||typeof window.AUTH!="object"?null:window.AUTH}function xe(e){let t=!1;return(...n)=>{t||(t=!0,e(...n))}}function p(...e){return e.find(Boolean)||null}function S(...e){e.forEach(t=>t?.classList.remove("hidden"))}function k(...e){e.forEach(t=>t?.classList.add("hidden"))}function b(e,t){e&&(e.textContent=t)}function R(e,t){if(e){if(t){e.src=t;return}e.removeAttribute("src")}}function V(e,t,n,a,o){e.querySelectorAll(t).forEach(r=>{r.addEventListener(n,a,o)})}function _e(e){let t=xe(e);if(q()){t();return}document.addEventListener("authReady",t,{once:!0}),K({src:"https://auth.dhanur.me/auth-client.js",selector:'script[src*="auth-client.js"]',defer:!0,onLoad:()=>{let n=q();n&&typeof n.onReady=="function"?n.onReady(()=>t()):t()},onError:()=>{console.warn("[Auth] Could not load auth-client.js")}}),window.setTimeout(()=>{q()&&t()},2e3)}function Me(e){if(!e)return"";try{let t=new Date(e);return`resets ${["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][t.getUTCMonth()]} ${t.getUTCDate()}`}catch{return""}}function X(e,t){if(!t)return;let n=t.unlimited||t.balance===-1,a=n?"\u221E":String(t.balance),o=n?"":Me(t.periodEnd),r=e.querySelector('[data-auth="credits-row"]');if(r){r.classList.remove("hidden");let c=r.querySelector('[data-auth="credits-balance"]'),s=r.querySelector('[data-auth="credits-reset"]');c&&(c.textContent=a),s&&(s.textContent=o)}let i=e.querySelector('[data-auth="sidebar-credits-row"]');if(i){i.classList.remove("hidden");let c=i.querySelector('[data-auth="sidebar-credits-balance"]'),s=i.querySelector('[data-auth="sidebar-credits-reset"]');c&&(c.textContent=a),s&&(s.textContent=o)}}function Te(e){e.querySelector('[data-auth="credits-row"]')?.classList.add("hidden"),e.querySelector('[data-auth="sidebar-credits-row"]')?.classList.add("hidden")}function Y(e=document,t=null){e.__authIntegrationBound||(e.__authIntegrationBound=!0,_e(()=>{let n=q();if(!n)return;let a=e.querySelector(".navbar"),o=e.querySelector("[data-sidebar-root]")||e.querySelector("#navigation-drawer"),r=e.querySelector("[data-sidebar-account]"),i={navGuestAvatar:p(e.querySelector('[data-auth="nav-guest-avatar"]'),a?.querySelector('[data-dropdown="account"] .bg-base-300')),navAuthedAvatar:p(e.querySelector('[data-auth="nav-authed-avatar"]'),a?.querySelector('[data-dropdown="account"] .ring-primary')),navAvatarImg:p(e.querySelector('[data-auth="nav-authed-avatar"] img'),a?.querySelector('[data-dropdown="account"] .ring-primary img')),navAuthedHeader:p(e.querySelector('[data-auth="nav-authed-header"]'),a?.querySelector('.dropdown-panel [data-auth="nav-name"]')?.closest(".border-b")),navGuestHeader:p(e.querySelector('[data-auth="nav-guest-header"]'),a?.querySelector(".dropdown-panel .fa-user")?.closest(".border-b")),navAuthedHeaderImg:e.querySelector('[data-auth="nav-authed-header-avatar"]'),navName:p(e.querySelector('[data-auth="nav-name"]'),a?.querySelector('[data-auth="name"]')),navEmail:p(e.querySelector('[data-auth="nav-email"]'),a?.querySelector('[data-auth="email"]')),navRole:p(e.querySelector('[data-auth="nav-role"]'),a?.querySelector('[data-auth="role"]')),navLoginItem:a?.querySelector('[data-auth="login-item"]')||null,navAccountItem:a?.querySelector('[data-auth="account-item"]')||null,navLogoutItem:a?.querySelector('[data-auth="logout-item"]')||null,sidebarGuestAvatar:p(e.querySelector('[data-auth="sidebar-guest-avatar"]'),r?.querySelector(".bg-base-300")),sidebarAuthedAvatar:p(e.querySelector('[data-auth="sidebar-authed-avatar"]'),r?.querySelector(".ring-primary")),sidebarAvatarImg:p(e.querySelector('[data-auth="sidebar-authed-avatar"] img'),r?.querySelector(".ring-primary img")),sidebarName:p(e.querySelector('[data-auth="sidebar-name"]'),r?.querySelector(".font-semibold")),sidebarEmail:p(e.querySelector('[data-auth="sidebar-email"]'),r?.querySelector(".text-xs.opacity-60")),sidebarLoginBtn:o?.querySelector('[data-auth="sidebar-login-btn"]')||null,sidebarLogoutBtn:o?.querySelector('[data-auth="sidebar-logout-btn"]')||null,sidebarAccountBtn:o?.querySelector('[data-auth="sidebar-account-btn"]')||null};function c(s){if(!s)return;let l=s.authenticated,u=s.user,h=u?.avatar_url||"",m=u?.name||"User";if(l&&u){if(k(i.navGuestAvatar,i.navGuestHeader,i.navLoginItem),S(i.navAuthedAvatar,i.navAuthedHeader,i.navAccountItem,i.navLogoutItem),R(i.navAvatarImg,h),R(i.navAuthedHeaderImg,h),b(i.navName,m),b(i.navEmail,u.email||""),i.navRole){let d=s.role||"user";i.navRole.textContent=d.toUpperCase(),i.navRole.className=d==="admin"?"badge badge-sm badge-error":"badge badge-sm badge-success",S(i.navRole)}k(i.sidebarGuestAvatar,i.sidebarLoginBtn),S(i.sidebarAuthedAvatar,i.sidebarLogoutBtn,i.sidebarAccountBtn),R(i.sidebarAvatarImg,h),b(i.sidebarName,m),b(i.sidebarEmail,u.email||""),X(e,s.credits||null)}else S(i.navGuestAvatar,i.navGuestHeader,i.navLoginItem),k(i.navAuthedAvatar,i.navAuthedHeader,i.navRole,i.navAccountItem,i.navLogoutItem),S(i.sidebarGuestAvatar,i.sidebarLoginBtn),k(i.sidebarAuthedAvatar,i.sidebarLogoutBtn,i.sidebarAccountBtn),b(i.sidebarName,"Guest"),b(i.sidebarEmail,"Not signed in"),Te(e);typeof t=="function"&&t(s)}typeof n.onReady=="function"?n.onReady(s=>c(s?.status||s||n.status||null)):n.status&&c(n.status),document.addEventListener("authChanged",s=>c(s.detail)),document.addEventListener("creditsChanged",s=>X(e,s.detail)),V(e,'[data-auth="login-btn"], [data-auth="sidebar-login-btn"]',"click",s=>{s.preventDefault(),typeof n.login=="function"&&n.login()}),V(e,'[data-auth="logout-btn"], [data-auth="sidebar-logout-btn"]',"click",s=>{if(s.preventDefault(),typeof n.logout=="function"){let l=n.logout();l&&typeof l.then=="function"?l.then(()=>window.location.reload()).catch(u=>{console.warn("[Auth] Logout failed:",u),window.location.reload()}):window.location.reload()}})}))}var De=` .access-wall { display: flex; flex-direction: column; @@ -22,14 +22,14 @@ font-size: 0.9rem; opacity: 0.7; } -`;function xe(){if(document.getElementById("access-wall-styles"))return;let e=document.createElement("style");e.id="access-wall-styles",e.textContent=Ee,document.head.appendChild(e)}function V(e={},t=null){let n=e.requireAdmin===!0;if(!(e.requireAuth===!0||n))return{allowed:!0};let a=t?.authenticated===!0,i=t?.role||"guest";return a?n&&i!=="admin"?{allowed:!1,reason:"admin_required"}:{allowed:!0}:{allowed:!1,reason:"auth_required"}}function Y(e,t="auth_required",n="This app"){if(!e)return;xe();let o=t==="admin_required",a=o?"fa-solid fa-shield-halved":"fa-solid fa-lock",i=o?"Admin access required":"Sign in to continue",r=o?`${n} is restricted to administrator sessions.`:`${n} requires a dhanur.me account to continue.`,l=o?"Verify Admin":"Sign In",s=o?"upgrade-btn":"login-btn";e.innerHTML=` +`;function Ie(){if(document.getElementById("access-wall-styles"))return;let e=document.createElement("style");e.id="access-wall-styles",e.textContent=De,document.head.appendChild(e)}function Z(e={},t=null){let n=e.requireAdmin===!0;if(!(e.requireAuth===!0||n))return{allowed:!0};let o=t?.authenticated===!0,r=t?.role||"guest";return o?n&&r!=="admin"?{allowed:!1,reason:"admin_required"}:{allowed:!0}:{allowed:!1,reason:"auth_required"}}function ee(e,t="auth_required",n="This app"){if(!e)return;Ie();let a=t==="admin_required",o=a?"fa-solid fa-shield-halved":"fa-solid fa-lock",r=a?"Admin access required":"Sign in to continue",i=a?`${n} is restricted to administrator sessions.`:`${n} requires a dhanur.me account to continue.`,c=a?"Verify Admin":"Sign In",s=a?"upgrade-btn":"login-btn";e.innerHTML=`
${r}
+ +${i}