import "@fontsource-variable/inter"; import "@fontsource/jetbrains-mono/400.css"; import "@fontsource/jetbrains-mono/500.css"; import "@fontsource/jetbrains-mono/600.css"; import "./styles.css"; console.log("%cCreated by Sarthak", "font-size:14px;font-weight:700;color:#F2A83B;"); (function(){ var reduced = matchMedia("(prefers-reduced-motion: reduce)").matches; var finePointer = matchMedia("(pointer: fine)").matches; var fmt = function(n){ return n.toLocaleString("en-US"); }; /* ---------- scrollbar materialises only while scrolling ---------- */ (function(){ var to = null; addEventListener("scroll", function(){ document.documentElement.classList.add("scrolling"); clearTimeout(to); to = setTimeout(function(){ document.documentElement.classList.remove("scrolling"); }, 900); }, { passive: true, capture: true }); })(); /* ---------- mobile scroll progress bar ---------- */ (function(){ var bar = document.getElementById("mprogBar"); if(!bar) return; var raf = null; function upd(){ raf = null; var doc = document.scrollingElement || document.documentElement; var max = Math.max(1, doc.scrollHeight - innerHeight); bar.style.transform = "scaleX(" + Math.min(1, window.scrollY / max).toFixed(4) + ")"; } addEventListener("scroll", function(){ if(raf == null) raf = requestAnimationFrame(upd); }, { passive: true }); upd(); })(); /* ---------- footer: hail alerts by ZIP ---------- */ (function(){ var form = document.getElementById("zipForm"); if(!form) return; var zin = document.getElementById("zipInput"); form.addEventListener("submit", function(e){ e.preventDefault(); var v = zin.value.trim(); if(!/^\d{5}$/.test(v)){ zin.focus(); zin.select(); return; } form.innerHTML = "
⚑ WATCHING " + v + "" + "First alert lands after the next radar-verified swath. No spam, only storms.
"; }); })(); /* ---------- scroll reveals: 150ms, 60ms stagger ---------- */ var revealEls = [].slice.call(document.querySelectorAll(".reveal")); if(!reduced && "IntersectionObserver" in window){ var groups = new Map(); var io = new IntersectionObserver(function(entries){ entries.forEach(function(e){ if(!e.isIntersecting && e.boundingClientRect.top >= 0) return; io.unobserve(e.target); var parent = e.target.parentElement; var idx = groups.get(parent) || 0; groups.set(parent, idx + 1); setTimeout(function(){ e.target.classList.add("in"); }, idx * 60); setTimeout(function(){ groups.set(parent, Math.max(0,(groups.get(parent)||1)-1)); }, 400); }); }, {threshold:.15}); revealEls.forEach(function(el){ io.observe(el); }); } else { revealEls.forEach(function(el){ el.classList.add("in"); }); } /* ---------- count-ups: one-shot, comma-formatted ---------- */ var counters = [].slice.call(document.querySelectorAll("[data-count]")); function runCounter(el){ var target = +el.dataset.count; var suffix = el.dataset.suffix || "", prefix = el.dataset.prefix || ""; var dec = +(el.dataset.decimals || 0); function print(v){ el.textContent = prefix + (dec ? v.toFixed(dec) : fmt(Math.round(v))) + suffix; } if(reduced){ print(target); return; } var t0 = null, dur = 2400; function step(ts){ if(!t0) t0 = ts; var p = Math.min((ts - t0)/dur, 1); p = 1 - Math.pow(1-p, 3); print(target * p); if(p < 1) requestAnimationFrame(step); } requestAnimationFrame(step); } if("IntersectionObserver" in window){ var cio = new IntersectionObserver(function(entries){ entries.forEach(function(e){ if(e.isIntersecting){ cio.unobserve(e.target); runCounter(e.target); } }); }, {threshold:.6}); counters.forEach(function(el){ cio.observe(el); }); } else counters.forEach(runCounter); /* ---------- hero contour-line shader (survey isolines, cyan) ---------- */ (function(){ var cv = document.getElementById("heroFx"); if(!cv) return; var gl = cv.getContext("webgl", {alpha:true, antialias:false}); if(!gl){ cv.remove(); return; } var vsrc = "attribute vec2 p;void main(){gl_Position=vec4(p,0.,1.);}"; var fsrc = "precision mediump float;" + "uniform vec2 u_res;uniform float u_t;uniform vec2 u_m;" + "void main(){" + " vec2 uv=gl_FragCoord.xy/u_res;" + " vec2 p=uv*vec2(u_res.x/u_res.y,1.);" + " p+=(u_m-.5)*.10;" + " float v=sin(p.x*4.2+u_t*.25)+sin(p.y*5.1-u_t*.2)+sin((p.x+p.y)*3.1+u_t*.12)+sin(length(p-vec2(1.2,.2))*6.-u_t*.3);" + " float f=fract(v*1.5);" + " float line=smoothstep(.90,.975,f)*(1.-smoothstep(.975,1.,f));" + " float mask=smoothstep(1.3,.1,distance(uv,vec2(.78,.82)));" + " gl_FragColor=vec4(.27,.69,.79,line*mask*.085);" + "}"; function sh(t,s){var o=gl.createShader(t);gl.shaderSource(o,s);gl.compileShader(o);return o;} var pr=gl.createProgram(); gl.attachShader(pr,sh(gl.VERTEX_SHADER,vsrc)); gl.attachShader(pr,sh(gl.FRAGMENT_SHADER,fsrc)); gl.linkProgram(pr);gl.useProgram(pr); var buf=gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,buf); gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,-1,3,-1,-1,3]),gl.STATIC_DRAW); var loc=gl.getAttribLocation(pr,"p"); gl.enableVertexAttribArray(loc);gl.vertexAttribPointer(loc,2,gl.FLOAT,false,0,0); var uRes=gl.getUniformLocation(pr,"u_res"),uT=gl.getUniformLocation(pr,"u_t"),uM=gl.getUniformLocation(pr,"u_m"); var hero=document.querySelector(".hero"),mx=.5,my=.5; if(finePointer) hero.addEventListener("pointermove",function(e){ var r=hero.getBoundingClientRect(); mx=(e.clientX-r.left)/r.width; my=(e.clientY-r.top)/r.height; }); var dpr=Math.min(devicePixelRatio||1,1.5); function rs(){var r=hero.getBoundingClientRect();cv.width=r.width*dpr;cv.height=r.height*dpr;gl.viewport(0,0,cv.width,cv.height);} rs();addEventListener("resize",rs); gl.enable(gl.BLEND);gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA); function paint(t){ gl.uniform2f(uRes,cv.width,cv.height); gl.uniform1f(uT,t); gl.uniform2f(uM,mx,1-my); gl.drawArrays(gl.TRIANGLES,0,3); } paint(0); /* warm the compile at load */ if(reduced){ paint(8); return; } var vis=true,t0=performance.now(); if("IntersectionObserver" in window) new IntersectionObserver(function(en){vis=en[0].isIntersecting;}).observe(hero); (function frame(now){ if(vis) paint((now-t0)/1000); requestAnimationFrame(frame); })(t0); })(); /* ---------- cursor-following hero glow ---------- */ (function(){ var g=document.getElementById("heroGlow"); if(!g) return; if(!finePointer || reduced){ g.remove(); return; } var hero=document.querySelector(".hero"); hero.addEventListener("pointermove",function(e){ var r=hero.getBoundingClientRect(); g.style.opacity="1"; g.style.transform="translate("+(e.clientX-r.left-260)+"px,"+(e.clientY-r.top-260)+"px)"; }); hero.addEventListener("pointerleave",function(){ g.style.opacity="0"; }); })(); /* ---------- card tilt physics (spring, with glare tracking) ---------- */ if(finePointer && !reduced){ [].slice.call(document.querySelectorAll(".ind,.risk-card,.plan,.founder-card,.feat,.oc")).forEach(function(el){ var rx=0,ry=0,tx=0,ty=0,vx=0,vy=0,raf=null,over=false; function frame(){ vx+=(tx-rx)*.16; vy+=(ty-ry)*.16; vx*=.72; vy*=.72; rx+=vx; ry+=vy; if(!over && Math.abs(rx)<.02 && Math.abs(ry)<.02 && Math.abs(vx)<.02 && Math.abs(vy)<.02){ el.style.transform=""; raf=null; return; } el.style.transform="perspective(900px) rotateX("+rx.toFixed(2)+"deg) rotateY("+ry.toFixed(2)+"deg) translateY(-2px)"; raf=requestAnimationFrame(frame); } el.addEventListener("pointerenter",function(){ over=true; el.classList.add("tilting"); if(!raf) raf=requestAnimationFrame(frame); }); el.addEventListener("pointermove",function(e){ var r=el.getBoundingClientRect(); var px=(e.clientX-r.left)/r.width, py=(e.clientY-r.top)/r.height; ty=(px-.5)*5; tx=(.5-py)*5; el.style.setProperty("--gx",(px*100)+"%"); el.style.setProperty("--gy",(py*100)+"%"); }); el.addEventListener("pointerleave",function(){ over=false; tx=0; ty=0; }); }); } /* ---------- mobile nav ---------- */ (function(){ var mb = document.getElementById("menuBtn"); if(!mb) return; mb.addEventListener("click", function(){ var open = document.body.classList.toggle("nav-open"); mb.setAttribute("aria-expanded", open ? "true" : "false"); }); [].slice.call(document.querySelectorAll(".nav-links a")).forEach(function(a){ a.addEventListener("click", function(){ document.body.classList.remove("nav-open"); mb.setAttribute("aria-expanded", "false"); }); }); })(); /* The 3D scene (three.js + gsap, ~540KB) loads after first paint; everything that needs the model lives inside initEstimator. */ function initEstimator(createScene){ /* ---------- 3D blueprint model: source of truth for the estimator ---------- */ var modelWrap = document.getElementById("modelWrap"); var roof3d = createScene(modelWrap, { reduced: reduced }); roof3d.stage(1); /* no scan phase on this page: model arrives fully inked */ roof3d.onDragStart(function(){ var hint = document.getElementById("dragHint"); if(hint) hint.style.opacity = "0"; }); (function(){ var slot = document.getElementById("estSlot"); function size(){ var r = slot.getBoundingClientRect(); if(r.width > 2 && r.height > 2) roof3d.resize(r.width, r.height); } if("ResizeObserver" in window) new ResizeObserver(size).observe(slot); else addEventListener("resize", size); size(); })(); /* ---------- estimator: quantities derived from the 3D model ---------- rates below are pulled from a real Certainteed Landmark AR tear-off/install invoice (roofing material + labor line items on a 12/12, 64.89 sq job): shingles $203.19/sq, synthetic underlayment ~$44.53/sq (10 sq/roll @ $445.30), ridge/hip cap ~$4.00/lf, accessories (drip edge, vents, flashing, nails, starter, sealant) ~$53/sq, plus a steep-pitch labor surcharge that kicks in at 8/12 and climbs with pitch — the old flat/guessed numbers didn't scale with job size or pitch at all. */ function makeEstimate(o){ var CANON_W = {gable:42, hip:46, storm:58, estate:42, mustang:48}; var CANON_P = {gable:6, hip:6, storm:5, estate:6, mustang:12}; o.wf = CANON_W[o.style] || o.wf; o.pn = CANON_P[o.style] || o.pn; var mm = roof3d.measure({style:o.style, wf:o.wf, pn:o.pn}); var layers = o.layers || 1; var rate = o.shingleRate || 203; /* hips and complex multi-facet roofs honestly carry more cut waste than plain gables */ var waste = (o.style === "hip" || o.style === "mustang") ? 1.12 : 1.10; var squares = Math.ceil(mm.roofSqFt * waste / 100); var shingles = squares * rate; var rolls = Math.ceil(squares / 10), under = rolls * 445; var ridge = Math.round(mm.ridgeLf * 4.0); var miscRate = o.miscRate || 53; var misc = Math.round(squares * miscRate); var tear = squares * 78 * layers; var labor = Math.round(o.crew * o.days * 760); var gutters = o.gutters ? Math.round(o.gutters * 12) : 0; var extras = o.extras || []; var extrasSum = extras.reduce(function(a,x){ return a + x[1]; }, 0); /* real steep-fee tiering: 8-9/12, 10-11/12, 12/12+ */ var steepRate = o.pn >= 12 ? 26 : o.pn >= 10 ? 20 : o.pn >= 8 ? 14.29 : 0; var steep = steepRate ? Math.round(squares * steepRate) : 0; var total = shingles + under + ridge + misc + tear + labor + gutters + extrasSum + steep; var lines = [ ["SCAN   " + o.scanLabel, "OK " + o.scanTime + "", null], ["MEASURE  roof " + fmt(mm.roofSqFt) + " sq ft · " + o.pn + "/12 · " + mm.facets + " facets", "OK", "roof"], ["" + (o.shingleLabel || "Architectural shingles") + " · " + squares + " sq", "$" + fmt(shingles), "roof"], ["Synthetic underlayment · " + rolls + " rolls", "$" + fmt(under), "roof"], ["Ridge" + (o.style === "hip" || o.style === "mustang" ? " + hip" : "") + " cap · " + mm.ridgeLf + " lf", "$" + fmt(ridge), "ridge"], ["Drip edge, vents, fasteners", "$" + fmt(misc), "eave"], ["Tear-off + disposal · " + layers + " layer" + (layers > 1 ? "s" : ""), "$" + fmt(tear), "roof"] ]; if(steep) lines.push(["Steep-pitch labor · " + o.pn + "/12 · " + squares + " sq", "$" + fmt(steep), "roof"]); if(o.gutters) lines.push(["Gutters · " + o.gutters + " lf aluminum", "$" + fmt(gutters), "eave"]); extras.forEach(function(x){ lines.push(["" + x[0] + "", "$" + fmt(x[1]), null]); }); lines.push(["Labor · crew of " + o.crew + " · " + o.days + " day" + (o.days > 1 ? "s" : ""), "$" + fmt(labor), null]); if(o.damage){ var sev = o.damage > 38 ? "HIGH" : o.damage > 26 ? "MODERATE" : "LOW"; lines.splice(2, 0, ["DAMAGE  " + o.damage + " impacts · facet F2 · severity " + sev, "FLAG", "damage"]); lines.splice(3, 0, ["Digital Twin evidence pack · " + (o.damage * 4 + 38) + " photos", "included", "damage"]); } return { roof:{ style:o.style, wf:o.wf, pn:o.pn }, lines:lines, total:"$" + fmt(total), time:o.time }; } var jobs = [ makeEstimate({style:"gable", wf:42, pn:6, scanLabel:"aerial imagery + street view", scanTime:"0.4s", layers:2, crew:5, days:2, gutters:110, extras:[["Chimney reflash + cricket",680],["Skylight curb + reflash · 2 units",940]], time:"3.1 seconds."}), makeEstimate({style:"hip", wf:46, pn:8, scanLabel:"aerial imagery + street view", scanTime:"0.5s", shingleRate:203, crew:5, days:1, time:"3.4 seconds."}), makeEstimate({style:"storm", wf:58, pn:5, scanLabel:"hail impact detection", scanTime:"0.6s", shingleRate:255, shingleLabel:"Class-4 impact shingles", crew:6, days:2, damage:41, gutters:148, extras:[["Decking replacement · 12 sheets",1560],["Drip edge + valley shield · code items",1100],["Permits + inspection",850]], time:"2.8 seconds. Claim-ready."}) ]; var body = document.getElementById("estBody"); var timer = []; function clearTimers(){ timer.forEach(clearTimeout); timer = []; } function renderJob(i, animate){ renderEstimate(jobs[i], animate); } function renderEstimate(j, animate){ clearTimers(); roof3d.set(j.roof); document.getElementById("estTime").textContent = j.time; body.innerHTML = ""; var frag = document.createDocumentFragment(); j.lines.forEach(function(l){ var d = document.createElement("div"); d.className = "est-line"; d.innerHTML = ""+l[0]+""+l[1]+""; if(l[2]){ d.setAttribute("data-hl", l[2]); d.addEventListener("mouseenter", function(){ roof3d.highlight(l[2]); }); d.addEventListener("mouseleave", function(){ roof3d.highlight(null); }); } frag.appendChild(d); }); var tot = document.createElement("div"); tot.className = "est-total"; tot.innerHTML = "ESTIMATE TOTAL"+j.total+""; frag.appendChild(tot); body.appendChild(frag); var rows = [].slice.call(body.querySelectorAll(".est-line")); if(!animate || reduced){ rows.forEach(function(r){ r.classList.add("on"); }); tot.classList.add("on"); return; } var roofBox = document.querySelector(".est-roof"); roofBox.classList.remove("scanning"); void roofBox.offsetWidth; roofBox.classList.add("scanning"); timer.push(setTimeout(function(){ roofBox.classList.remove("scanning"); }, 1300)); rows.forEach(function(r, idx){ timer.push(setTimeout(function(){ r.classList.add("on"); }, 220 + idx * 260)); }); timer.push(setTimeout(function(){ tot.classList.add("on"); }, 220 + rows.length * 260 + 180)); } /* typed address: deterministic scripted estimate */ function hashStr(s){ var h = 2166136261; for(var i = 0; i < s.length; i++){ h ^= s.charCodeAt(i); h = Math.imul(h, 16777619); } return h >>> 0; } function rng(seed){ return function(){ seed = Math.imul(seed ^ (seed >>> 15), 2246822519); seed = Math.imul(seed ^ (seed >>> 13), 3266489917); return ((seed ^= seed >>> 16) >>> 0) / 4294967296; }; } function jobFromAddress(addr){ var clean = addr.replace(/[<>&"]/g, "").replace(/\s+/g, " ").trim(); var r = rng(hashStr(clean.toLowerCase())); var pitch = 4 + Math.floor(r() * 7); var hipStyle = r() < .45; var damaged = r() < .3; var impacts = damaged ? 18 + Math.floor(r() * 36) : 0; var widthFt = 30 + Math.round(r() * 30); var layers = r() < .3 ? 2 : 1; var crew = 4 + Math.floor(r() * 3); var days = 1 + Math.round(r() * 3) / 2; var short = clean.length > 30 ? clean.slice(0, 28) + "..." : clean; return makeEstimate({ style: hipStyle ? "hip" : "gable", wf: widthFt, pn: pitch, scanLabel: short, scanTime: "0.5s", layers: layers, crew: crew, days: days, damage: impacts, misc: 600 + Math.round(r() * 350), time: (2.4 + r() * 1.2).toFixed(1) + " seconds." + (damaged ? " Claim-ready." : "") }); } /* the addresses we have a client-supplied aerial survey for get their own modelled house */ var BENT_OAK = "5705 Bent Oak Place, Dallas, TX 75248"; function estateJob(){ return makeEstimate({ style: "estate", wf: 42, pn: 6, scanLabel: BENT_OAK, scanTime: "0.6s", layers: 1, crew: 6, days: 3, gutters: 165, extras: [["Chimney reflash + cricket", 680], ["Pool deck coping reseal", 420]], time: "3.3 seconds." }); } var MUSTANG = "12552 Mustang Circle, Forney, TX 75126"; function mustangJob(){ return makeEstimate({ style: "mustang", wf: 48, pn: 12, scanLabel: MUSTANG, scanTime: "0.7s", layers: 1, crew: 7, days: 3, gutters: 240, miscRate: 58, extras: [["Ice & water shield · valleys + eaves", 1850], ["Steep-pitch safety rigging", 620]], time: "3.6 seconds." }); } var chips = [].slice.call(document.querySelectorAll(".est-chips .chip")); /* the two addresses with a client-supplied aerial survey get their modelled house; anything else falls back to the deterministic hashed estimate */ function jobForAddress(addr){ if(addr === MUSTANG) return mustangJob(); if(addr === BENT_OAK) return estateJob(); return jobFromAddress(addr); } /* custom address dropdown: native