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;");
/* ---------- CTA sample-address estimate (standalone so it always runs) ---------- */
(function(){
function init(){
var form = document.getElementById("ctaForm");
var sel = document.getElementById("ctaAddr");
var btn = document.getElementById("ctaEstimateBtn");
var panel = document.getElementById("ctaEstimate");
if(!sel || !panel) return;
var money = function(n){ return "$" + Number(n).toLocaleString("en-US"); };
var set = function(id, val){ var el = document.getElementById(id); if(el) el.textContent = val; };
function render(scroll){
var opt = sel.options[sel.selectedIndex];
if(!opt || opt.dataset.placeholder){ panel.hidden = true; return; }
set("estAddr", opt.value);
set("estArea", Number(opt.dataset.area).toLocaleString("en-US") + " sq ft");
set("estSquares", opt.dataset.squares + " sq");
set("estPitch", opt.dataset.pitch);
set("estMaterial", opt.dataset.material);
set("estPrice", money(opt.dataset.low) + " – " + money(opt.dataset.high));
panel.hidden = false;
if(scroll) panel.scrollIntoView({behavior:"smooth", block:"nearest"});
}
function go(e){ if(e) e.preventDefault(); render(true); }
if(form) form.addEventListener("submit", go);
if(btn) btn.addEventListener("click", go);
sel.addEventListener("change", function(){ if(!panel.hidden) render(false); });
}
if(document.readyState === "loading") document.addEventListener("DOMContentLoaded", init);
else init();
})();
(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