/* Netlify HUD — the badge and owner toolbar Netlify injects on published sites. Built inside a srcdoc iframe from the data-* attributes on the injected tag. */ (() => { "use strict"; const script = document.currentScript || document.querySelector("script[data-nf-variant]"); if (!script) return; const ds = script.dataset; const warn = (msg) => console.warn("[netlify-hud] " + msg + "; not rendering"); const raw = ds.nfVariant; if (!raw) return; const variant = raw === "public" || raw === "owner-private" ? raw : null; if (!variant) return warn("unknown variant: " + raw); const APP_HOSTS = ["app.netlify.com", "app.netlifystg.com"]; const APP_HOST = APP_HOSTS.indexOf(ds.appHost || "") !== -1 ? ds.appHost : ""; const SITE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test( ds.netlifySiteId || "" ) ? ds.netlifySiteId : ""; if (!APP_HOST) return warn("missing or invalid data-app-host"); if (variant === "owner-private" && !SITE_ID) return warn("missing or invalid data-netlify-site-id"); // On the free tier the badge is part of the plan: no "Hide this badge", and a // stored dismissal is not honoured. Proxy stamps the lowercase tier as // data-account-tier; only the literal "free" counts. The owner HUD ignores it. const HIDEABLE = variant !== "public" || ds.accountTier !== "free"; // Underscore, though the acquisition standard's per-feature table spells this // netlify-badge. Its formatting rule allows underscores with a reason, and the reason // is series continuity: this is the highest-volume owned-loop surface, and renaming it // splits every existing report at the cutover. // Notion: "Marketing Channel Classification - Review", 3cd1da88359a80aabee7f7ddbc8c490a. const UTM_SOURCE = "netlify_badge"; // Baked at deploy from BRANCH (see netlify.toml): the canary branch, or blank on production/local. // Reported as hud_variant. utm_campaign is the program label under the acquisition // standard and stays `loops` on every deploy, canary included. let HUD_VARIANT = "main"; if (HUD_VARIANT === "main" || !/^[a-z0-9-]+$/.test(HUD_VARIANT)) HUD_VARIANT = ""; if ( document.getElementById("nl-hud-frame") || document.getElementById("nl-badge-frame") ) return; /* ————— Dismissal, remembered per site and per device ————— */ const STORE_KEY = "nl-hud:" + variant + ":v1"; // A visitor's "Hide this badge" lapses after a week; the owner's hide is kept // until storage is cleared. const HIDE_TTL_MS = variant === "public" ? 7 * 24 * 60 * 60 * 1000 : 0; const writeState = (value) => { try { if (value) localStorage.setItem(STORE_KEY, value); else localStorage.removeItem(STORE_KEY); } catch (e) { } }; // Where the hide lapses, a stored dismissal counts only with an expiry ahead // of now by at most one TTL: a bare `hidden` from before the expiry existed, // a lapsed one, and one written under a skewed clock are all dropped. const hiddenIsLive = (expires) => { if (!HIDE_TTL_MS) return true; if (!expires) return false; const left = Number(expires) - Date.now(); return left > 0 && left <= HIDE_TTL_MS; }; // Stored as `mini`, `hidden` (permanent) or `hidden:` (lapses then). const readState = () => { let raw; try { raw = localStorage.getItem(STORE_KEY) || ""; } catch (e) { return ""; } if (raw === "mini") return raw; const m = /^hidden(?::(\d+))?$/.exec(raw); if (!m) return ""; if (HIDEABLE && hiddenIsLive(m[1])) return "hidden"; writeState(""); return ""; }; const hideValue = () => HIDE_TTL_MS ? "hidden:" + (Date.now() + HIDE_TTL_MS) : "hidden"; const saved = readState(); if (saved === "hidden") return; const startMinimized = variant === "owner-private" && saved === "mini"; const overview = (query) => `https://${APP_HOST}/projects/${SITE_ID}/overview?${query}`; const hudLink = (query, action) => overview(query + "&from_hud=private" + (HUD_VARIANT ? "," + HUD_VARIANT : "") + "&hud_action=" + action); const promptLink = (id) => hudLink("promptTemplate=" + encodeURIComponent(id), "ar_task"); /* ————— Shared bits ————— */ const LOGO = ( s ) => ``; const AI_SVG = (s) => ``; const SHARE_SVG = ``; const TOOLS_SVG = ``; const LOCK_SVG = ``; const LAUNCH_SVG = ``; const CHEVRON_SVG = (dir) => { const paths = dir === "left" ? "M12 4.5 8.5 8 12 11.5M7.5 4.5 4 8 7.5 11.5" : "M4 4.5 7.5 8 4 11.5M8.5 4.5 12 8 8.5 11.5"; return ``; }; const CLOSE_SVG = ``; const ARROW_UP_SVG = ``; const FONT = `-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif`; const PAD = { t: 8, r: 18, b: 18, l: 8 }; const INSET = PAD.l + PAD.r; const VW = "var(--nl-vw,100vw)"; const VH = "var(--nl-vh,100vh)"; const NARROW = 600; const rootClass = (extra) => { const c = []; if (window.innerWidth < NARROW) c.push("nl-narrow"); if (extra) c.push(extra); return c.length ? ` class="${c.join(" ")}"` : ""; }; const BASE_CSS = ` :root{--nl-vw:${window.innerWidth}px;--nl-vh:${window.innerHeight}px} *{box-sizing:border-box} html,body{margin:0;padding:0;background:transparent;overflow:hidden} body{display:flex;align-items:flex-end;justify-content:flex-end;height:100vh; padding:${PAD.t}px ${PAD.r}px ${PAD.b}px ${PAD.l}px; font-family:${FONT};font-size:14px;line-height:1.5;color:#f4f9f9;-webkit-font-smoothing:antialiased} .nl-wrap{display:flex;flex-direction:column;align-items:flex-end;gap:10px;width:max-content;max-width:calc(${VW} - ${INSET}px)} button{font-family:inherit;cursor:pointer} :focus-visible{outline:2px solid #32e6e2;outline-offset:2px;border-radius:6px} .nl-icon-btn{position:relative;flex:none;width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;border-radius:8px;border:1px solid transparent;background:transparent;color:#d5e3e6;transition:background .15s ease,color .15s ease} .nl-icon-btn:hover{background:rgba(255,255,255,0.08);color:#f4f9f9} .nl-tap::after{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%); width:max(100%,44px);height:max(100%,44px)} @keyframes nl-rise{from{opacity:0;transform:translateY(10px) scale(.97)}to{opacity:1;transform:none}} @media (prefers-reduced-motion:reduce){*{animation-duration:.01ms!important;transition:none!important}} `; const MEASURE_JS = ` function nlSend(){ var wrap=document.querySelector('.nl-wrap'),boxes=[]; for(var i=0;i

Build your own site with Netlify

Describe what you want, and an AI agent designs, builds and deploys it in minutes.

${SITE_ID ? `` : ""} ${HUD_VARIANT ? `` : ""}
or
Drop a site you already have ${HIDEABLE ? `` : ""}
`; } /* ————— Owner-private HUD ————— */ function ownerPrivateDoc() { const task = (title, desc, templateId) => `
${title}
${desc}
${AI_SVG(12)} Run
`; return `
${TOOLS_SVG} Pre-launch tools
${task( "SEO / GEO audit", "Check that Google and AI assistants can find and read your site.", "d7bd9353-a6a3-442d-86f0-9b15488f68c3" )} ${task( "Performance check", "See how fast your pages load, and what is slowing them down.", "f8ef80a5-be9b-494d-9fac-a4ac5d5689a4" )} ${task( "Security audit", "Look for common weak spots, like out-of-date code or open settings.", "5f50f746-2ba1-4a3d-8a47-1a515f4420eb" )}
${LOGO( 18 )}${LOCK_SVG}
This project is private - only your team and people you invite can view it. Learn more