1/* Netlify HUD â the badge and owner toolbar Netlify injects on published sites. 2 Built inside a srcdoc iframe from the data-* attributes on the injected tag. */ 3(() => { 4 "use strict"; 5 6 const script = 7 document.currentScript || document.querySelector("script[data-nf-variant]"); 8 if (!script) return; 9 10 const ds = script.dataset; 11 12 const warn = (msg) => 13 console.warn("[netlify-hud] " + msg + "; not rendering"); 14 15 const raw = ds.nfVariant; 16 if (!raw) return; 17 const variant = raw === "public" || raw === "owner-private" ? raw : null; 18 if (!variant) return warn("unknown variant: " + raw); 19 20 const APP_HOSTS = ["app.netlify.com", "app.netlifystg.com"]; 21 const APP_HOST = APP_HOSTS.indexOf(ds.appHost || "") !== -1 ? ds.appHost : ""; 22 const SITE_ID = 23 /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test( 24 ds.netlifySiteId || "" 25 ) 26 ? ds.netlifySiteId 27 : ""; 28 29 if (!APP_HOST) return warn("missing or invalid data-app-host"); 30 if (variant === "owner-private" && !SITE_ID) 31 return warn("missing or invalid data-netlify-site-id"); 32 33 // Underscore, though the acquisition standard's per-feature table spells this 34 // netlify-badge. Its formatting rule allows underscores with a reason, and the reason 35 // is series continuity: this is the highest-volume owned-loop surface, and renaming it 36 // splits every existing report at the cutover. 37 // Notion: "Marketing Channel Classification - Review", 3cd1da88359a80aabee7f7ddbc8c490a. 38 const UTM_SOURCE = "netlify_badge"; 39 40 // Baked at deploy from BRANCH (see netlify.toml): the canary branch, or blank on production/local. 41 // Reported as hud_variant. utm_campaign is the program label under the acquisition 42 // standard and stays `loops` on every deploy, canary included. 43 let HUD_VARIANT = "main"; 44 if (HUD_VARIANT === "main" || !/^[a-z0-9-]+$/.test(HUD_VARIANT)) HUD_VARIANT = ""; 45 46 if ( 47 document.getElementById("nl-hud-frame") || 48 document.getElementById("nl-badge-frame") 49 ) 50 return; 51 52 /* âââââ Dismissal, remembered per site and per device âââââ */ 53 const STORE_KEY = "nl-hud:" + variant + ":v1"; 54 const readState = () => { 55 try { 56 return localStorage.getItem(STORE_KEY) || ""; 57 } catch (e) { 58 return ""; 59 } 60 }; 61 const writeState = (value) => { 62 try { 63 if (value) localStorage.setItem(STORE_KEY, value); 64 else localStorage.removeItem(STORE_KEY); 65 } catch (e) { 66 } 67 }; 68 69 const saved = readState(); 70 if (saved === "hidden") return; 71 const startMinimized = variant === "owner-private" && saved === "mini"; 72 73 const overview = (query) => 74 `https://${APP_HOST}/projects/${SITE_ID}/overview?${query}`; 75 const hudLink = (query, action) => 76 overview(query + "&from_hud=private" + (HUD_VARIANT ? "," + HUD_VARIANT : "") + "&hud_action=" + action); 77 const promptLink = (id) => hudLink("promptTemplate=" + encodeURIComponent(id), "ar_task"); 78 79 /* âââââ Shared bits âââââ */ 80 const LOGO = ( 81 s 82 ) => `<svg width="${s}" height="${s}" viewBox="0 0 32 32" fill="none" aria-hidden="true"> 83 <path fill="#fff" d="M17.9922 18.3225H14.005L13.6746 17.9921V14.005L14.005 13.6746H17.9922L18.3226 14.005V17.9921L17.9922 18.3225Z"/> 84 <g fill="#05BDBA"> 85 <path d="M9.6958 24.3311H9.35705L7.6661 22.6401V22.3041L10.2511 19.7164L12.042 19.7191L12.2808 19.9552V21.7461L9.6958 24.3311Z"/> 86 <path d="M7.6661 9.69576V9.35701L9.35705 7.66606H9.6958L12.2808 10.2511V12.0392L12.042 12.2808H10.2511L7.6661 9.69576Z"/> 87 <path d="M10.0457 17.4369H0.205466L0 17.2314V14.7658L0.205466 14.5603H10.0457L10.2511 14.7658V17.2314L10.0457 17.4369Z"/> 88 <path d="M31.7945 17.4369H21.9544L21.7489 17.2314V14.7658L21.9544 14.5603H31.7945L32 14.7658V17.2314L31.7945 17.4369Z"/> 89 <path d="M14.5631 10.0456V2.66547L14.7686 2.46001H17.2343L17.4396 2.66547V10.0456L17.2343 10.2511H14.7686L14.5631 10.0456Z"/> 90 <path d="M14.5631 29.3316V21.9515L14.7686 21.746H17.2343L17.4396 21.9515V29.3316L17.2343 29.5371H14.7686L14.5631 29.3316Z"/> 91 </g></svg>`; 92 const AI_SVG = (s) => 93 `<svg width="${s}" height="${s}" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M5.68 9.925 4.607 8.85l-.013-.379 1.1-1.1.38.012 1.075 1.075.012.379-1.1 1.1zM9.96 9.925 8.886 8.85l-.013-.379 1.1-1.1.38.012 1.075 1.075.012.379-1.1 1.1z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M9.005 2.52v1.296h2.657a3.19 3.19 0 0 1 3.191 3.192v3.356a3.19 3.19 0 0 1-3.191 3.191H4.338a3.19 3.19 0 0 1-3.191-3.19V7.007a3.19 3.19 0 0 1 3.191-3.192h2.836V2.52l.229-.245h1.373zM4.338 5.316c-.934 0-1.691.758-1.691 1.692v3.356c0 .934.758 1.691 1.691 1.691h7.324c.933 0 1.69-.757 1.691-1.69V7.007c0-.934-.757-1.692-1.691-1.692z"/></svg>`; 94 const SHARE_SVG = `<svg width="15" height="15" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" clip-rule="evenodd" d="M7.5636 8C6.94548 8.31953 6.24382 8.5 5.5 8.5C4.75618 8.5 4.05452 8.31953 3.4364 8H1.5L0.5 9V13
94L1.5 14H9.5L10.5 13V9L9.5 8H7.5636ZM8.87868 9.5H7.90075C7.16414 9.82186 6.35125 10 5.5 10C4.64875 10 3.83586 9.82186 3.09925 9.5H2.12132L2 9.62132V12.3787L2.12132 12.5H8.87868L9 12.3787V9.62132L8.87868 9.5Z"/><path d="M13.9999 10.1213V11.8463L12.2043 11.8481L12.0039 12.0357V13.1606L12.2043 13.3481H13.7955L13.7975 13.3463H14.7024L15.4999 12.5V9.5L14.5001 8.5001L12.2043 8.49976L12.0039 8.68726V9.81225L12.2043 9.99976L13.8786 10L13.9999 10.1213Z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M5.5 7C6.88071 7 8 5.88071 8 4.5C8 3.11929 6.88071 2 5.5 2C4.11929 2 3 3.11929 3 4.5C3 5.88071 4.11929 7 5.5 7ZM5.5 5.5C6.05228 5.5 6.5 5.05228 6.5 4.5C6.5 3.94772 6.05228 3.5 5.5 3.5C4.94772 3.5 4.5 3.94772 4.5 4.5C4.5 5.05228 4.94772 5.5 5.5 5.5Z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M13.7 5.1C13.7 6.25937 12.7594 7.2 11.6 7.2C10.4406 7.2 9.5 6.25937 9.5 5.1C9.5 3.94062 10.4406 3 11.6 3C12.7594 3 13.7 3.94062 13.7 5.1ZM11.6 5.7C11.9309 5.7 12.2 5.43095 12.2 5.1C12.2 4.76905 11.9309 4.5 11.6 4.5C11.2691 4.5 11 4.76905 11 5.1C11 5.43095 11.2691 5.7 11.6 5.7Z"/></svg>`; 95 const TOOLS_SVG = `<svg width="15" height="15" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" clip-rule="evenodd" d="M7.98407 9.625L8.58935 11.0952C8.62792 11.1889 8.71922 11.25 8.82053 11.25H9.77587C9.88268 11.25 9.95527 11.1415 9.91454 11.0428L7.3828 4.90468C7.3442 4.81107 7.25294 4.75 7.15169 4.75H6.34295C6.24163 4.75 6.15033 4.81115 6.11177 4.90484L3.58524 11.0429C3.54461 11.1416 3.61719 11.25 3.72395 11.25H4.67399C4.7753 11.25 4.86659 11.1889 4.90516 11.0952L5.51045 9.625H7.98407ZM7.46943 8.375L6.74726 6.62092L6.02508 8.375H7.46943Z"/><path d="M10.75 11.25C10.6119 11.25 10.5 11.1381 10.5 11V5C10.5 4.86193 10.6119 4.75 10.75 4.75H11.5C11.6381 4.75 11.75 4.86193 11.75 5V11C11.75 11.1381 11.6381 11.25 11.5 11.25H10.75Z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M0 5.5C0 2.73858 2.23858 0.5 5 0.5H11C13.7614 0.5 16 2.73858 16 5.5V10.5C16 13.2614 13.7614 15.5 11 15.5H5C2.23858 15.5 0 13.2614 0 10.5V5.5ZM5 1.75H11C13.0711 1.75 14.75 3.42893 14.75 5.5V10.5C14.75 12.5711 13.0711 14.25 11 14.25H5C2.92893 14.25 1.25 12.5711 1.25 10.5V5.5C1.25 3.42893 2.92893 1.75 5 1.75Z"/></svg>`; 96 const LOCK_SVG = `<svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor" fill-rule="evenodd" aria-hidden="true"><path d="M8 1.5A3.5 3.5 0 0 0 4.5 5v2H4A1.5 1.5 0 0 0 2.5 8.5v4A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5v-4A1.5 1.5 0 0 0 12 7h-.5V5A3.5 3.5 0 0 0 8 1.5zm2 5.5V5a2 2 0 1 0-4 0v2h4z"/></svg>`; 97 const LAUNCH_SVG = `<svg width="15" height="15" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M11.072 6C10.8557 5.99995 10.6443 5.93576 10.4644 5.81554C10.2846 5.69533 10.1444 5.52449 10.0617 5.32463C9.97895 5.12477 9.95733 4.90486 9.99956 4.69271C10.0418 4.48056 10.146 4.2857 10.299 4.13276C10.4519 3.97982 10.6468 3.87568 10.859 3.8335C11.0711 3.79132 11.291 3.81299 11.4909 3.89578C11.6907 3.97857 11.8615 4.11876 11.9817 4.29862C12.1019 4.47848 12.166 4.68994 12.166 4.90625C12.1658 5.19633 12.0505 5.47447 11.8454 5.67956C11.6402 5.88464 11.3621 5.9999 11.072 6Z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M3.59279 4.5H6.77473L8.48034 2.74577L10.1619 1L14.4308 1.00686L15 1.59031V6.01763L11.5291 9.61732L11.78 12.4852L11.1608 13.7165L8.58695 15.2986L7.59043 14.7959L7.31674 11.6676L4.624 8.875H1.65598L1.06836 7.92609L2.42011 5.22418L3.59279 4.5ZM8.7673 11.037L8.96573 13.305L10.0122 12.6618L10.2486 12.1917L10.1476 11.037L8.7673 11.037ZM8.43037 10.6616L12.036 6.93056L13.5 5.41225V2.50537L10.7987 2.50103L10.0283 3.30089L5.69627 7.82603L8.43037 10.6616ZM5.27424 7.38834L5.35943 6H4.01862L3.57089 6.27649L3.02131 7.375H5.26138L5.27424 7.38834Z"/><path d="M2.05008 10.9935L3.79156 9.6874L4.81429 10.7954L3.17932 12.0216L2.95615 13.1374L4.07201 12.9143L5.28155 11.3016L6.31603 12.4222L5.10008 14.0435L4.64717 14.3289L2.14717 14.8289L1.26465 13.9464L1.76465 11.4464L2.05008 10.9935Z"/></svg>`; 98 99 const CHEVRON_SVG = (dir) => { 100 const paths = 101 dir === "left" 102 ? "M12 4.5 8.5 8 12 11.5M7.5 4.5 4 8 7.5 11.5" 103 : "M4 4.5 7.5 8 4 11.5M8.5 4.5 12 8 8.5 11.5"; 104 return `<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="${paths}"/></svg>`; 105 }; 106 107 const CLOSE_SVG = `<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" aria-hidden="true"><path d="M4.5 4.5 11.5 11.5M11.5 4.5 4.5 11.5"/></svg>`; 108 109 const ARROW_UP_SVG = `<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 12.75v-9.5M3.9 7.35 8 3.25l4.1 4.1"/></svg>`; 110 111 const FONT = `-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif`; 112 113 const PAD = { t: 8, r: 18, b: 18, l: 8 }; 114 const INSET = PAD.l + PAD.r; 115 116 const VW = "var(--nl-vw,100vw)"; 117 const VH = "var(--nl-vh,100vh)"; 118 119 const NARROW = 600; 120 121 const rootClass = (extra) => { 122 const c = []; 123 if (window.innerWidth < NARROW) c.push("nl-narrow"); 124 if (extra) c.push(extra); 125 return c.length ? ` class="${c.join(" ")}"` : ""; 126 }; 127 128 const BASE_CSS = ` 129 :root{--nl-vw:${window.innerWidth}px;--nl-vh:${window.innerHeight}px} 130 *{box-sizing:border-box} 131 html,body{margin:0;padding:0;background:transparent;overflow:hidden} 132 body{display:flex;align-items:flex-end;justify-content:flex-end;height:100vh; 133 padding:${PAD.t}px ${PAD.r}px ${PAD.b}px ${PAD.l}px; 134 font-family:${FONT};font-size:14px;line-height:1.5;color:#f4f9f9;-webkit-font-smoothing:antialiased} 135 .nl-wrap{display:flex;flex-direction:column;align-items:flex-end;
135gap:10px;width:max-content;max-width:calc(${VW} - ${INSET}px)} 136 button{font-family:inherit;cursor:pointer} 137 :focus-visible{outline:2px solid #32e6e2;outline-offset:2px;border-radius:6px} 138 .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} 139 .nl-icon-btn:hover{background:rgba(255,255,255,0.08);color:#f4f9f9} 140 .nl-tap::after{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%); 141 width:max(100%,44px);height:max(100%,44px)} 142 @keyframes nl-rise{from{opacity:0;transform:translateY(10px) scale(.97)}to{opacity:1;transform:none}} 143 @media (prefers-reduced-motion:reduce){*{animation-duration:.01ms!important;transition:none!important}} 144 `; 145 146 const MEASURE_JS = ` 147 function nlSend(){ 148 var wrap=document.querySelector('.nl-wrap'),boxes=[]; 149 for(var i=0;i<wrap.children.length;i++){ 150 var c=wrap.children[i]; 151 if(!c.offsetWidth||!c.offsetHeight)continue; 152 boxes.push({x:c.offsetLeft-wrap.offsetLeft+${PAD.l}, 153 y:c.offsetTop-wrap.offsetTop+${PAD.t}, 154 w:c.offsetWidth,h:c.offsetHeight}); 155 } 156 parent.postMessage({nlHud:'resize', 157 width:wrap.offsetWidth+${INSET}, 158 height:wrap.offsetHeight+${PAD.t + PAD.b}, 159 boxes:boxes},'*'); 160 } 161 function nlSendSoon(){requestAnimationFrame(nlSend);} 162 window.addEventListener('load',nlSend); 163 window.addEventListener('message',function(e){ 164 if(e.source!==parent||!e.data||e.data.nlHud!=='viewport')return; 165 var s=document.documentElement.style; 166 s.setProperty('--nl-vw',e.data.w+'px'); 167 s.setProperty('--nl-vh',e.data.h+'px'); 168 document.documentElement.classList.toggle('nl-narrow',e.data.w<${NARROW}); 169 nlSendSoon(); 170 }); 171 var nlCoarse=window.matchMedia('(pointer: coarse)').matches; 172 function nlSubmit(form){ 173 if(form.requestSubmit)return form.requestSubmit(); 174 if(form.checkValidity&&!form.checkValidity()) 175 return form.reportValidity&&form.reportValidity(); 176 form.submit(); 177 } 178 `; 179 180 /* âââââ Public badge (+ "build your own" popover) âââââ */ 181 function publicDoc() { 182 return `<!doctype html><html${rootClass()}><head><meta charset="utf-8"><meta name="color-scheme" content="dark"><style>${BASE_CSS} 183 .nl-card{position:relative;display:none;width:min(340px,calc(${VW} - ${INSET}px));max-height:calc(${VH} - 84px);overflow-y:auto;background:linear-gradient(180deg,#132832 0%,#0e1e25 100%);border:1px solid rgba(255,255,255,0.09);border-radius:18px;padding:18px;transform-origin:bottom right;animation:nl-rise .3s cubic-bezier(.34,1.4,.4,1) both} 184 .nl-card.open{display:block} 185 .nl-card-close{position:absolute;top:10px;right:10px} 186 .nl-card h2{margin:0;padding-right:30px;font-size:16px;font-weight:750;letter-spacing:-0.015em;line-height:1.35} 187 .nl-card h2 em{font-style:normal;background:linear-gradient(90deg,#32e6e2,#4d7cfe,#9a6bff);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent} 188 .nl-card p{margin:8px 0 0;font-size:12.5px;color:#d5e3e6} 189 .nl-prompt{position:relative;margin-top:14px;background:#10222b;border:1px solid rgba(255,255,255,0.09);border-radius:14px;padding:10px 12px;transition:border-color .15s ease,box-shadow .15s ease} 190 .nl-prompt:focus-within{border-color:#05bdba;box-shadow:0 0 0 3px rgba(5,189,186,0.18)} 191 .nl-prompt textarea{display:block;width:100%;height:38px;resize:none;border:0;outline:none;background:transparent;color:#f4f9f9;font:inherit;font-size:13px;line-height:19px;padding:0 28px 0 0} 192 .nl-prompt textarea::placeholder{color:#a9bdc2} 193 .nl-send{position:absolute;right:9px;bottom:9px;width:24px;height:24px;flex:none;display:inline-flex;align-items:center;justify-content:center;border:0;border-radius:50%;background:#05bdba;color:#02191b;transition:background .15s ease} 194 .nl-send:hover{background:#32e6e2} 195 @media (pointer: coarse){ 196 .nl-prompt textarea{font-size:16px;line-height:21px;height:42px} 197 } 198 .nl-or{display:flex;align-items:center;gap:10px;margin:12px 0;font-size:10.5px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;color:#a9bdc2} 199 .nl-or::before,.nl-or::after{content:"";flex:1;height:1px;background:rgba(255,255,255,0.09)} 200 .nl-drop{display:flex;align-items:center;justify-content:space-between;gap:6px;text-decoration:none;background:#1b3a45;border:1px solid rgba(255,255,255,0.09);border-radius:10px;padding:10px 12px;font-size:13px;font-weight:600;color:#cfe0e3;transition:background .15s ease,color .15s ease,border-color .15s ease} 201 .nl-drop:hover{background:#224753;color:#f4f9f9;border-color:rgba(50,230,226,0.3)} 202 .nl-drop .nl-arrow{transition:transform .18s cubic-bezier(.34,1.4,.4,1)} 203 .nl-drop:hover .nl-arrow{transform:translateX(3px)} 204 .nl-badge{display:flex;align-items:center;gap:9px;white-space:nowrap;border:1px solid rgba(255,255,255,0.09);background:linear-gradient(180deg,#142b34,#0e1e25);color:#f4f9f9;border-radius:999px;padding:9px 14px 9px 11px;font-size:13px;font-weight:600;letter-spacing:0.01em;transition:border-color .2s ease} 205 .nl-badge:hover{border-color:rgba(50,230,226,0.26)} 206 .nl-hide{display:block;width:100%;margin-top:10px;border:0;background:transparent;color:#a9bdc2;font-size:11.5px;font-weight:600;padding:4px;border-radius:8px;transition:color .15s ease} 207 .nl-hide:hover{color:#d5e3e6;text-decoration:underline} 208 .nl-badge-label{color:#d5e3e6;font-weight:500} 209 .nl-badge-brand{font-weight:750} 210 </style></head><body> 211 <div class="nl-wrap"> 212 <section class="nl-card" id="nl-card" aria-label="Build your own site on Netlify"> 213 <button type="button" class="nl-icon-btn nl-tap nl-card-close" id="nl-card-close" aria-label="Close">${CLOSE_SVG}</button> 214 <h2><em>Build your own site with Netlify</em></h2> 215 <p>Describe what you want, and an AI agent designs, builds and deploys it in minutes.</p> 216 <form class="nl-prompt" action="https://${APP_HOST}/start" method="get" target="_blank" rel="noopener"> 217 <textarea name="prompt" rows="2" required maxlength="300" autocomplete="off" 218 enterkeyhint="go" 219 placeholder="A shop for my handmade surfboards…" 220 aria-label="Describe the site you want to build"></textarea>
221 <button type="submit" class="nl-send nl-tap" aria-label="Build this site">${ARROW_UP_SVG}</button> 222 <input type="hidden" name="utm_source" value="${UTM_SOURCE}"> 223 <input type="hidden" name="utm_campaign" value="loops"> 224 <input type="hidden" name="utm_medium" value="owned"> 225 <input type="hidden" name="utm_content" value="prompt"> 226 ${SITE_ID ? `<input type="hidden" name="utm_id" value="${SITE_ID}">` : ""} 227 ${HUD_VARIANT ? `<input type="hidden" name="hud_variant" value="${HUD_VARIANT}">` : ""} 228 </form> 229 <div class="nl-or"><span>or</span></div> 230 <a class="nl-drop" href="https://${APP_HOST}/drop?utm_source=${UTM_SOURCE}&utm_campaign=loops&utm_medium=owned&utm_content=drop${SITE_ID ? "&utm_id=" + SITE_ID : ""}${HUD_VARIANT ? "&hud_variant=" + HUD_VARIANT : ""}" target="_blank" rel="noopener"> 231 <span>Drop a site you already have</span><span class="nl-arrow" aria-hidden="true">→</span> 232 </a> 233 <button type="button" class="nl-hide" id="nl-hide">Hide this badge</button> 234 </section> 235 <button type="button" class="nl-badge" id="nl-badge" aria-expanded="false" aria-controls="nl-card" 236 aria-label="Powered by Netlify â build your own site"> 237 ${LOGO(18)} 238 <span><span class="nl-badge-label">Powered by</span> <span class="nl-badge-brand">Netlify</span></span> 239 </button> 240 </div> 241 <script> 242 ${MEASURE_JS} 243 var badge=document.getElementById('nl-badge'),card=document.getElementById('nl-card'), 244 box=card.querySelector('textarea'); 245 function setOpen(open){ 246 card.classList.toggle('open',open); 247 badge.setAttribute('aria-expanded',open?'true':'false'); 248 nlSendSoon(); 249 if(open&&!nlCoarse)box.focus(); 250 } 251 box.addEventListener('keydown',function(e){ 252 if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();nlSubmit(box.form);} 253 }); 254 badge.addEventListener('click',function(){setOpen(!card.classList.contains('open'));}); 255 document.getElementById('nl-card-close').addEventListener('click',function(){setOpen(false);badge.focus();}); 256 document.addEventListener('keydown',function(e){ 257 if(e.key==='Escape'&&card.classList.contains('open')){setOpen(false);badge.focus();} 258 }); 259 document.getElementById('nl-hide').addEventListener('click',function(){ 260 parent.postMessage({nlHud:'state',value:'hidden'},'*'); 261 }); 262 <\/script> 263 </body></html>`; 264 } 265 266 /* âââââ Owner-private HUD âââââ */ 267 function ownerPrivateDoc() { 268 const task = (title, desc, templateId) => ` 269 <div class="nl-task"> 270 <div class="nl-task-meta"><div class="nl-task-title">${title}</div><div class="nl-task-desc">${desc}</div></div> 271 <a class="nl-task-run" href="${promptLink( 272 templateId 273 )}" target="_blank" rel="noopener">${AI_SVG(12)} Run</a> 274 </div>`; 275 276 return `<!doctype html><html${rootClass( 277 startMinimized ? "nl-mini-state" : "" 278 )}><head><meta charset="utf-8"><meta name="color-scheme" content="dark"><style>${BASE_CSS} 279 .nl-popover{display:none;width:min(320px,calc(${VW} - ${INSET}px));max-height:calc(${VH} - 90px);overflow-y:auto;background:linear-gradient(180deg,#132832 0%,#0e1e25 100%);border:1px solid rgba(255,255,255,0.09);border-radius:18px;transform-origin:bottom right;animation:nl-rise .28s cubic-bezier(.34,1.4,.4,1) both} 280 .nl-popover.open{display:block} 281 .nl-popover-head{display:flex;align-items:center;gap:9px;padding:13px 12px 5px 16px} 282 .nl-popover-icon{display:inline-flex;color:#32e6e2} 283 .nl-popover-title{flex:1;font-size:13.5px;font-weight:700;letter-spacing:-0.01em;color:#f4f9f9} 284 .nl-popover-body{padding:0 16px 12px} 285 .nl-task{display:flex;align-items:center;gap:10px;padding:11px 0} 286 .nl-task+.nl-task{border-top:1px solid rgba(255,255,255,0.06)} 287 .nl-task-meta{flex:1;min-width:0} 288 .nl-task-title{font-size:13.5px;font-weight:650;color:#f4f9f9} 289 .nl-task-desc{font-size:11.5px;color:#a9bdc2} 290 .nl-task-run{flex:none;display:inline-flex;align-items:center;gap:6px;text-decoration:none;border:1px solid rgba(50,230,226,0.35);background:transparent;color:#32e6e2;font-size:12px;font-weight:700;border-radius:999px;padding:6px 13px;transition:background .15s ease,transform .15s cubic-bezier(.34,1.4,.4,1)} 291 .nl-task-run:hover{background:rgba(5,189,186,0.12);transform:translateY(-1px)} 292 .nl-bar,.nl-mini{-webkit-user-select:none;user-select:none} 293 .nl-bar{display:flex;align-items:center;gap:2px;white-space:nowrap;border:1px solid rgba(255,255,255,0.09);background:linear-gradient(180deg,#142b34,#0e1e25);border-radius:999px;padding:7px 8px 7px 14px;color:#f4f9f9} 294 .nl-logo{flex:none;margin-right:8px;display:inline-flex} 295 .nl-status{display:inline-flex;align-items:center;gap:5px;border:0;background:transparent;font-family:inherit;font-size:12.5px;font-weight:700;letter-spacing:0.01em;color:#fbbf24;padding:6px 5px;border-radius:8px;transition:background .15s ease} 296 .nl-status:hover,.nl-status[aria-expanded='true']{background:rgba(251,191,36,0.12)} 297 .nl-tip{display:none;align-self:flex-start;width:min(300px,calc(${VW} - ${INSET}px));background:linear-gradient(180deg,#132832 0%,#0e1e25 100%);border:1px solid rgba(255,255,255,0.09);border-radius:14px;padding:12px 14px;font-size:12px;line-height:1.5;color:#d5e3e6;animation:nl-rise .2s cubic-bezier(.34,1.4,.4,1) both} 298 .nl-tip.open{display:block} 299 .nl-tip a{display:flex;width:fit-content;align-items:center;gap:4px;margin-top:8px;color:#32e6e2;text-decoration:none;font-weight:650} 300 .nl-tip a:hover{text-decoration:underline} 301 .nl-sep{width:1px;height:18px;background:rgba(255,255,255,0.09);margin:0 10px 0 12px;flex:none} 302 .nl-btn{display:inline-flex;align-items:center;gap:7px;text-decoration:none;border:0;background:transparent;color:#d5e3e6;font-family:inherit;font-size:13px;font-weight:600;padding:8px 12px;border-radius:999px;transition:background .15s ease,color .15s ease} 303 .nl-btn svg{color:#32e6e2} 304 .nl-btn:hover{background:rgba(255,255,255,0.07);color:#f4f9f9} 305 .nl-btn[aria-expanded='true']{background:rgba(255,255,255,0.1);color:#f4f9f9} 306 .nl-publish{display:inline-flex;align-items:center;gap:7px;text-decoration:none;margin-left:6px;background:#05bdba;color:#02191b;font-size:13px;font-weight:700;padding:8px 15px;border-radius:999px;transition:background .15s ease,transform .15s cubic-bezier(.34,1.4,.4,1)} 307 .nl-publish:hover{background:#32e6e2;transform:translateY(-1px)} 308 .nl-min{margin-left:6px} 309 310 /* âââ Minimized âââ */ 311 .nl-bar,.nl-mini{min-height:52px} 312 .nl-mini{display:none;align-items:center;gap:2px;border:1px solid rgba(255,255,255,0.09);background:linear-gradient(180deg,#142b34,#0e1e25);border-radius:999px;padding:5px 5px 5px 13px;
312cursor:pointer} 313 .nl-mini-state .nl-mini{display:flex} 314 .nl-mini-state .nl-bar,.nl-mini-state .nl-popover,.nl-mini-state .nl-tip{display:none} 315 .nl-mini-brand{display:inline-flex;align-items:center;gap:7px;padding-right:8px} 316 .nl-mini-lock{display:inline-flex;color:#fbbf24} 317 318 /* âââ Narrow layout âââ */ 319 .nl-narrow .nl-shrinks{position:absolute;width:1px;height:1px;overflow:hidden; 320 clip-path:inset(50%);white-space:nowrap} 321 .nl-narrow .nl-status{gap:0} 322 .nl-narrow .nl-bar{padding:6px 6px 6px 10px} 323 .nl-narrow .nl-logo{margin-right:6px} 324 .nl-narrow .nl-sep{margin:0 6px 0 8px} 325 .nl-narrow .nl-btn{padding:8px 9px} 326 .nl-narrow .nl-publish{margin-left:4px;padding:8px 11px} 327 </style></head><body> 328 <div class="nl-wrap"> 329 <section class="nl-popover" id="nl-pop" aria-label="Pre-launch tools"> 330 <div class="nl-popover-head"> 331 <span class="nl-popover-icon">${TOOLS_SVG}</span> 332 <span class="nl-popover-title">Pre-launch tools</span> 333 <button type="button" class="nl-icon-btn nl-tap" id="nl-pop-close" aria-label="Close">${CLOSE_SVG}</button> 334 </div> 335 <div class="nl-popover-body"> 336 ${task( 337 "SEO / GEO audit", 338 "Check that Google and AI assistants can find and read your site.", 339 "d7bd9353-a6a3-442d-86f0-9b15488f68c3" 340 )} 341 ${task( 342 "Performance check", 343 "See how fast your pages load, and what is slowing them down.", 344 "f8ef80a5-be9b-494d-9fac-a4ac5d5689a4" 345 )} 346 ${task( 347 "Security audit", 348 "Look for common weak spots, like out-of-date code or open settings.", 349 "5f50f746-2ba1-4a3d-8a47-1a515f4420eb" 350 )} 351 </div> 352 </section> 353 <div class="nl-mini" id="nl-mini"> 354 <span class="nl-mini-brand">${LOGO( 355 18 356 )}<span class="nl-mini-lock">${LOCK_SVG}</span></span> 357 <button type="button" class="nl-icon-btn nl-tap nl-mini-open" id="nl-mini-open" 358 aria-label="Show Netlify site tools">${CHEVRON_SVG( 359 "left" 360 )}</button> 361 <button type="button" class="nl-icon-btn nl-tap nl-mini-hide" id="nl-mini-hide" 362 aria-label="Hide Netlify tools on this site">${CLOSE_SVG}</button> 363 </div> 364 <section class="nl-tip" id="nl-tip" aria-label="Project visibility"> 365 This project is private - only your team and people you invite can view it. 366 <a href="https://docs.netlify.com/manage/security/secure-access-to-sites/project-visibility/" 367 target="_blank" rel="noopener">Learn more <span aria-hidden="true">↗</span></a> 368 </section> 369 <div class="nl-bar" role="toolbar" aria-label="Netlify site tools"> 370 <span class="nl-logo">${LOGO(20)}</span> 371 <button type="button" class="nl-status" id="nl-status" 372 aria-expanded="false" aria-controls="nl-tip">${LOCK_SVG}<span class="nl-shrinks">Private</span></button> 373 <span class="nl-sep" aria-hidden="true"></span> 374 <a class="nl-btn" href="${hudLink( 375 "modal=share", "share" 376 )}" target="_blank" rel="noopener">${SHARE_SVG} 377 <span class="nl-shrinks">Share</span></a> 378 <button type="button" class="nl-btn" id="nl-tools" aria-expanded="false" aria-controls="nl-pop">${TOOLS_SVG} 379 <span class="nl-shrinks">Pre-launch tools</span></button> 380 <a class="nl-publish" href="${hudLink( 381 "modal=go-live", "make_public" 382 )}" target="_blank" rel="noopener">${LAUNCH_SVG} Make public</a> 383 <button type="button" class="nl-icon-btn nl-tap nl-min" id="nl-min" 384 aria-label="Minimize Netlify tools">${CHEVRON_SVG( 385 "right" 386 )}</button> 387 </div> 388 </div> 389 <script> 390 ${MEASURE_JS} 391 var btn=document.getElementById('nl-tools'),pop=document.getElementById('nl-pop'); 392 function setOpen(open){ 393 pop.classList.toggle('open',open); 394 btn.setAttribute('aria-expanded',open?'true':'false'); 395 nlSendSoon(); 396 } 397 btn.addEventListener('click',function(){setOpen(!pop.classList.contains('open'));}); 398 document.getElementById('nl-pop-close').addEventListener('click',function(){setOpen(false);btn.focus();}); 399 400 var statusBtn=document.getElementById('nl-status'),tip=document.getElementById('nl-tip'),tipTimer=null; 401 function setTip(open){ 402 tip.classList.toggle('open',open); 403 statusBtn.setAttribute('aria-expanded',open?'true':'false'); 404 nlSendSoon(); 405 } 406 function openTip(){clearTimeout(tipTimer);setTip(true);} 407 function closeTipSoon(){clearTimeout(tipTimer);tipTimer=setTimeout(function(){setTip(false);},200);} 408 if(!nlCoarse){ 409 statusBtn.addEventListener('mouseenter',openTip); 410 statusBtn.addEventListener('mouseleave',closeTipSoon); 411 tip.addEventListener('mouseenter',openTip); 412 tip.addEventListener('mouseleave',closeTipSoon); 413 } 414 statusBtn.addEventListener('focus',function(){ 415 var keyboard; 416 try{keyboard=statusBtn.matches(':focus-visible');}catch(e){keyboard=!nlCoarse;} 417 if(keyboard)openTip(); 418 }); 419 statusBtn.addEventListener('blur',closeTipSoon); 420 statusBtn.addEventListener('click',function(){ 421 clearTimeout(tipTimer); 422 setTip(nlCoarse ? !tip.classList.contains('open') : true); 423 }); 424 425 var root=document.documentElement; 426 function setStage(value){ 427 root.classList.toggle('nl-mini-state',value==='mini'); 428 if(value==='mini'){setOpen(false);setTip(false);} 429 parent.postMessage({nlHud:'state',value:value},'*'); 430 nlSendSoon(); 431 } 432 document.getElementById('nl-min').addEventListener('click',function(){ 433 setStage('mini'); 434 document.getElementById('nl-mini-open').focus(); 435 }); 436 document.getElementById('nl-mini').addEventListener('click',function(e){ 437 if(e.target.closest('#nl-mini-hide'))return; 438 setStage(''); 439 document.getElementById('nl-min').focus(); 440 }); 441 document.getElementById('nl-mini-hide').addEventListener('click',function(){ 442 parent.postMessage({nlHud:'state',value:'hidden'},'*'); 443 }); 444 document.addEventListener('keydown',function(e){ 445 if(e.key!=='Escape')return; 446 if(tip.classList.contains('open')){setTip(false);statusBtn.focus();return;} 447 if(pop.classList.contains('open')){setOpen(false);btn.focus();} 448 }); 449 <\/script> 450 </body></html>`; 451 } 452 453 /* âââââ Mount âââââ */ 454 const frame = document.createElement("iframe"); 455 456 const base = 457 "position:fixed;bottom:0;right:0;border:0;margin:0;padding:0;background:transparent;color-scheme:dark;z-index:2147483645;"; 458 459 const CLOSED = 'path("M0 0Z")'; 460 461 if (variant === "public") { 462 frame.id = "nl-badge-frame"; 463 frame.setAttribute("title", "Powered by Netlify"); 464 frame.style.cssText = base + "width:216px;height:92px;clip-path:" + CLOSED; 465 frame.srcdoc = publicDoc(); 466 } else { 467 frame.id = "nl-hud-frame"; 468 frame.setAttribute("title", "Netlify"); 469 frame.style.cssText = base + "width:652px;height:94px;clip-path:" + CLOSED; 470 frame.srcdoc = ownerPrivateDoc(); 471 } 472 473 const BLEED = 5; 474 475 function clipFor(boxes, w, h, dx, dy) { 476 if (!Array.isArray(boxes) || !boxes.length) return CLOSED; 477 return ( 478 'path("' + 479 boxes 480 .map((o) => { 481 const x = o.x + dx, 482 y = o.y + dy; 483 const l = Math.max(0, x - BLEED), 484 r = Math.min(w, x + o.w + BLEED); 485 const t = Math.max(0, y - BLEED), 486 b = Math.min(h, y + o.h + BLEED); 487 return `M${l} ${t}H${r}V${b}H${l}Z`; 488 }) 489 .join("") + 490 '")' 491 ); 492 } 493 494 const onMessage = (e) => { 495 if (e.source !== frame.contentWindow || !e.data) return; 496 497 if (e.data.nlHud === "state") { 498 const value = 499 e.data.value === "mini" || e.data.value === "hidden" 500 ? e.data.value 501 : ""; 502 writeState(value); 503 if (value === "hidden") teardown(); 504 return; 505 } 506 507 if (e.data.nlHud !== "resize") return; 508 const wantW = Math.max(Number(e.data.width) || 0, 1); 509 const wantH = Math.max(Number(e.data.height) || 0, 1); 510 const w = Math.min( 511 wantW, 512 document.documentElement.clientWidth || window.innerWidth 513 ); 514 const h = Math.min(wantH, window.innerHeight); 515 frame.style.width = w + "px"; 516 frame.style.height = h + "px"; 517 frame.style.clipPath = clipFor(e.data.boxes, w, h, w - wantW, h - wantH); 518 if (!frame.dataset.nlReady) frame.dataset.nlReady = "1"; 519 }; 520 window.addEventListener("message", onMessage); 521 522 const sendViewport = () => { 523 if (frame.contentWindow) { 524 frame.contentWindow.postMessage( 525 { nlHud: "viewport", w: window.innerWidth, h: window.innerHeight }, 526 "*" 527 ); 528 } 529 }; 530 let queued = false;
531 const onViewportChange = () => { 532 if (queued) return; 533 queued = true; 534 requestAnimationFrame(() => { 535 queued = false; 536 sendViewport(); 537 }); 538 }; 539 frame.addEventListener("load", sendViewport); 540 window.addEventListener("resize", onViewportChange); 541 window.addEventListener("orientationchange", onViewportChange); 542 543 function teardown() { 544 window.removeEventListener("message", onMessage); 545 window.removeEventListener("resize", onViewportChange); 546 window.removeEventListener("orientationchange", onViewportChange); 547 frame.remove(); 548 } 549 550 const mount = () => document.body.appendChild(frame); 551 if (document.body) mount(); 552 else document.addEventListener("DOMContentLoaded", mount); 553})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.