/* Sections — composes the full long-scroll site. */

function NavBar({ onRequestAudit, auditInView }) {
  const [scrolled, setScrolled] = React.useState(false);
  React.useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 24);
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  const handleAuditClick = (e) => {
    if (auditInView) { e.preventDefault(); onRequestAudit(); }
  };
  return (
    <nav className={"nav-root" + (scrolled ? " is-scrolled" : "")}>
      <a href="#top" className="nav-mark bare">
        <img src="assets/logo-mark-purple.png" alt="" />
        <span>MarTech LA</span>
      </a>
      <div className="nav-links">
        <a href="#system" className="bare">The system</a>
        <a href="#produce" className="bare">What it produces</a>
        <a href="#engage" className="bare">How we engage</a>
        <a href="#audit" className="nav-cta" onClick={handleAuditClick}>
          {auditInView ? 'Get my audit →' : 'Request audit →'}
        </a>
      </div>
    </nav>
  );
}

function Hero() {
  return (
    <section className="hero" id="top" data-screen-label="01 Hero">
      <div className="shell">
        <div className="hero-eyebrow-row">
          <span className="dot"></span>
          <span className="eyebrow">MarTech LA — Data + Paid · Built for ServiceTitan operators</span>
        </div>
        <h1>
          Your business runs on ServiceTitan.<br/>
          Your growth should <span className="accent">too.</span>
        </h1>
        <p className="hero-sub">
          We connect the data inside your ServiceTitan to the ad platforms spending on it — Meta, Yelp, Google. Segments score themselves overnight. Campaigns fire when the numbers say so. Attribution closes back to invoiced revenue.
        </p>
        <div className="hero-actions">
          <a href="#audit" className="btn btn-primary">Request a free audit <span className="btn-arrow">→</span></a>
          <a href="#system" className="btn btn-ghost">See the system <span className="btn-arrow">↓</span></a>
        </div>
        <HeroDemo />
      </div>
    </section>
  );
}

function NetworkSection() {
  const canvasRef = React.useRef(null);
  const animRef = React.useRef(null);
  const hovRef = React.useRef('st');
  const posRef = React.useRef([]);
  const sectionRef = React.useRef(null);
  const enteredRef = React.useRef(false);
  const frameRef = React.useRef(0);
  const imgRef = React.useRef({});
  const [hovLabel, setHovLabel] = React.useState({ id:'st', name:'ServiceTitan', sub:'Field ops · source of truth', color:'#4a7fca' });

  const platforms = [
    { id:'st',   name:'ServiceTitan', sub:'Field ops · source of truth',  color:'#4a7fca', icon:'🔧' },
    { id:'sf',   name:'Snowflake',    sub:'Data warehouse',                color:'#29B5E8', icon:'❄' },
    { id:'ghl',  name:'GoHighLevel',  sub:'CRM + automation',              color:'#fb923c', icon:'📊' },
    { id:'meta', name:'Meta',         sub:'Paid social + CAPI',            color:'#3b82f6', icon:'📱' },
    { id:'yelp', name:'Yelp',         sub:'Local search ads',              color:'#ef4444', icon:'⭐' },
    { id:'goog', name:'Google',       sub:'Search + LSA bidding',          color:'#22c55e', icon:'🔍' },
  ];

  React.useEffect(() => {
    const obs = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) { enteredRef.current = true; frameRef.current = 0; }
    }, { threshold: 0.1 });
    if (sectionRef.current) obs.observe(sectionRef.current);
    return () => obs.disconnect();
  }, []);

  // Preload platform logos
  React.useEffect(() => {
    const logoMap = {
      st:   'assets/logo-st.png',
      sf:   'assets/logo-sf.png',
      ghl:  'assets/logo-ghl.png',
      meta: 'assets/logo-meta.png',
      yelp: 'assets/logo-yelp.png',
      goog: 'assets/logo-goog.png',
    };
    Object.entries(logoMap).forEach(([id, src]) => {
      const img = new Image();
      img.onload = () => { imgRef.current[id] = img; };
      img.src = src;
    });
  }, []);

  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    const PR = Math.min(window.devicePixelRatio || 1, 2);

    let W = 0, H = 0, CX = 0, CY = 0, R_ORBIT = 0, NODE_R = 0;

    const setup = () => {
      W = canvas.parentElement.clientWidth;
      const mobile = W < 560;
      // On mobile give far more vertical room so sub-labels don't clip
      H = mobile
        ? Math.max(460, Math.min(620, W * 1.28))
        : Math.max(360, Math.min(540, W * 0.70));
      canvas.width = Math.round(W * PR);
      canvas.height = Math.round(H * PR);
      canvas.style.width = W + 'px';
      canvas.style.height = H + 'px';
      ctx.setTransform(PR, 0, 0, PR, 0, 0);
      CX = W / 2; CY = H / 2;
      // Tighten orbit on mobile so nodes have clearance from edges
      R_ORBIT = mobile
        ? Math.min(W * 0.28, H * 0.32, 148)
        : Math.min(W * 0.31, H * 0.40, 172);
      NODE_R = mobile
        ? Math.min(W * 0.072, H * 0.09, 38)
        : Math.min(W * 0.075, H * 0.11, 44);
    };
    setup();

    const N = platforms.length;
    const nodes = platforms.map((p, i) => ({
      ...p,
      baseAngle: (i / N) * Math.PI * 2 - Math.PI / 2,
      driftPhase: i * 1.09 + 0.3,
      driftAmpX: 8 + (i % 3) * 4,
      driftAmpY: 6 + (i % 2) * 5,
      driftSX: 0.006 + i * 0.0007,
      driftSY: 0.005 + i * 0.0005,
      pulseT: i / N,
      pulseSpeed: 0.0045 + i * 0.0006,
    }));

    const rings = [0, 1, 2].map(i => ({ phase: i / 3 }));

    const draw = () => {
      const f = frameRef.current++;
      const entered = enteredRef.current;
      const rawEntry = entered ? Math.min(1, f / 72) : 0;
      const ef = rawEntry < 0.5 ? 2*rawEntry*rawEntry : -1+(4-2*rawEntry)*rawEntry;

      ctx.clearRect(0, 0, W, H);

      // Background radial glow — extend to cover canvas corners so gradient fades fully
      const bgR = Math.max(R_ORBIT * 1.7, Math.sqrt(CX * CX + CY * CY) * 1.1);
      const bg = ctx.createRadialGradient(CX, CY, 0, CX, CY, bgR);
      bg.addColorStop(0, 'rgba(76,47,201,0.13)');
      bg.addColorStop(0.5, 'rgba(76,47,201,0.05)');
      bg.addColorStop(1, 'rgba(76,47,201,0)');
      ctx.fillStyle = bg;
      ctx.fillRect(0, 0, W, H);

      // Compute positions
      const positions = nodes.map((n, i) => {
        const bx = CX + Math.cos(n.baseAngle) * R_ORBIT;
        const by = CY + Math.sin(n.baseAngle) * R_ORBIT;
        const dx = Math.sin(f * n.driftSX + n.driftPhase) * n.driftAmpX;
        const dy = Math.cos(f * n.driftSY + n.driftPhase + 1.4) * n.driftAmpY;
        const raw = Math.max(0, Math.min(1, ef * 1.5 - i * 0.07));
        const ne = raw < 0.5 ? 2*raw*raw : -1+(4-2*raw)*raw;
        return { x: CX + (bx + dx - CX) * ne, y: CY + (by + dy - CY) * ne, alpha: ne };
      });
      posRef.current = positions;

      // Connection lines + data packets
      nodes.forEach((n, i) => {
        const pos = positions[i];
        if (pos.alpha < 0.02) return;
        const isHov = hovRef.current === n.id;

        ctx.save();
        ctx.globalAlpha = (isHov ? 0.65 : 0.18) * pos.alpha;
        if (isHov) { ctx.shadowColor = n.color; ctx.shadowBlur = 10; }
        ctx.strokeStyle = isHov ? n.color : 'rgba(167,139,250,0.85)';
        ctx.lineWidth = isHov ? 1.6 : 0.75;
        if (isHov) ctx.setLineDash([6, 4]);
        ctx.beginPath(); ctx.moveTo(CX, CY); ctx.lineTo(pos.x, pos.y); ctx.stroke();
        ctx.setLineDash([]); ctx.restore();

        // Moving data packet
        n.pulseT = (n.pulseT + n.pulseSpeed) % 1;
        const pt = n.pulseT;
        const px = CX + (pos.x - CX) * pt;
        const py = CY + (pos.y - CY) * pt;
        ctx.save();
        ctx.globalAlpha = Math.sin(pt * Math.PI) * 0.88 * pos.alpha;
        ctx.fillStyle = n.color;
        ctx.shadowColor = n.color; ctx.shadowBlur = 9;
        ctx.beginPath(); ctx.arc(px, py, 2.6, 0, Math.PI * 2); ctx.fill();
        ctx.restore();
      });

      // Pulse rings
      rings.forEach(ring => {
        ring.phase = (ring.phase + 0.0065) % 1;
        const rr = NODE_R * 1.15 + ring.phase * R_ORBIT * 0.38;
        ctx.save();
        ctx.globalAlpha = (1 - ring.phase) * 0.20 * ef;
        ctx.strokeStyle = 'rgba(167,139,250,1)';
        ctx.lineWidth = 0.75;
        ctx.beginPath(); ctx.arc(CX, CY, rr, 0, Math.PI * 2); ctx.stroke();
        ctx.restore();
      });

      // Center brain glow
      const brainR = NODE_R * 1.5;
      const cg = ctx.createRadialGradient(CX, CY, 0, CX, CY, brainR * 2.6);
      cg.addColorStop(0, 'rgba(76,47,201,0.32)'); cg.addColorStop(0.5, 'rgba(76,47,201,0.10)'); cg.addColorStop(1, 'rgba(76,47,201,0)');
      ctx.save(); ctx.fillStyle = cg; ctx.beginPath(); ctx.arc(CX, CY, brainR * 2.6, 0, Math.PI * 2); ctx.fill(); ctx.restore();

      // Brain circle
      ctx.save();
      ctx.fillStyle = 'rgba(14,10,42,0.88)';
      ctx.strokeStyle = 'rgba(167,139,250,0.58)'; ctx.lineWidth = 2;
      ctx.shadowColor = 'rgba(167,139,250,0.35)'; ctx.shadowBlur = 22;
      ctx.beginPath(); ctx.arc(CX, CY, brainR, 0, Math.PI * 2); ctx.fill(); ctx.stroke();
      ctx.restore();

      // Brain text
      const f1 = Math.max(11, Math.round(brainR * 0.41));
      const f2 = Math.max(8, Math.round(brainR * 0.27));
      ctx.save();
      ctx.font = `800 ${f1}px -apple-system,BlinkMacSystemFont,sans-serif`;
      ctx.fillStyle = 'rgba(167,139,250,0.96)'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
      ctx.fillText('natit', CX, CY - f2 * 0.45);
      ctx.font = `600 ${f2}px -apple-system,BlinkMacSystemFont,sans-serif`;
      ctx.fillStyle = 'rgba(167,139,250,0.42)';
      ctx.fillText('AI BRAIN', CX, CY + f1 * 0.52);
      ctx.restore();

      // Platform nodes
      nodes.forEach((n, i) => {
        const pos = positions[i];
        if (pos.alpha < 0.02) return;
        const isHov = hovRef.current === n.id;

        if (isHov) {
          const ng = ctx.createRadialGradient(pos.x, pos.y, 0, pos.x, pos.y, NODE_R * 2.2);
          ng.addColorStop(0, n.color + '28'); ng.addColorStop(1, n.color + '00');
          ctx.save(); ctx.globalAlpha = pos.alpha; ctx.fillStyle = ng;
          ctx.beginPath(); ctx.arc(pos.x, pos.y, NODE_R * 2.2, 0, Math.PI * 2); ctx.fill(); ctx.restore();
        }

        ctx.save();
        ctx.globalAlpha = pos.alpha;
        ctx.fillStyle = isHov ? n.color + '28' : n.color + '16';
        ctx.strokeStyle = n.color; ctx.lineWidth = isHov ? 2.2 : 1.6;
        ctx.shadowColor = n.color; ctx.shadowBlur = isHov ? 22 : 8;
        ctx.beginPath(); ctx.arc(pos.x, pos.y, NODE_R, 0, Math.PI * 2); ctx.fill(); ctx.stroke();
        ctx.restore();

        const nameFs = Math.max(7, Math.round(NODE_R * 0.30));
        const logoImg = imgRef.current[n.id];
        // Keep the outer node circle the same; shrink logo to ~54% of node radius
        // so there's visible padding between the logo and the ring
        const logoClipR = NODE_R * 0.54;
        const logoS = logoClipR * 2;
        ctx.save();
        ctx.globalAlpha = pos.alpha;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        if (logoImg && logoImg.complete && logoImg.naturalWidth > 0) {
          // Clip to circle and draw logo
          ctx.save();
          ctx.beginPath();
          ctx.arc(pos.x, pos.y - nameFs * 0.35, logoClipR, 0, Math.PI * 2);
          ctx.clip();
          ctx.drawImage(logoImg, pos.x - logoS / 2, pos.y - logoS / 2 - nameFs * 0.35, logoS, logoS);
          ctx.restore();
        } else {
          // Fallback: first letter
          const iconFs = Math.max(12, Math.round(NODE_R * 0.44));
          ctx.font = `800 ${iconFs}px -apple-system,BlinkMacSystemFont,sans-serif`;
          ctx.fillStyle = n.color;
          ctx.fillText(n.name[0], pos.x, pos.y - nameFs * 0.5);
        }
        // Company short name below
        ctx.font = `600 ${nameFs}px -apple-system,BlinkMacSystemFont,sans-serif`;
        ctx.fillStyle = isHov ? 'rgba(26,24,20,0.94)' : 'rgba(26,24,20,0.65)';
        ctx.fillText(n.name.split(' ')[0], pos.x, pos.y + NODE_R * 0.52);
        ctx.restore();

        if (pos.alpha > 0.4) {
          const ldx = pos.x - CX, ldy = pos.y - CY;
          const ll = Math.sqrt(ldx*ldx + ldy*ldy);
          const nx = ll > 0 ? ldx/ll : 1, ny = ll > 0 ? ldy/ll : 0;
          const rawLx = pos.x + nx * (NODE_R + 8);
          const ly = pos.y + ny * (NODE_R + 8);
          const subFs = Math.max(6, Math.round(NODE_R * 0.26));
          // Clamp label anchor to canvas edges with per-side margins
          const PAD = 4;
          let anchor, lx;
          if (Math.abs(nx) < 0.28) {
            anchor = 'center'; lx = rawLx;
          } else if (nx > 0) {
            anchor = 'left'; lx = Math.min(rawLx, W - PAD);
          } else {
            anchor = 'right'; lx = Math.max(rawLx, PAD);
          }
          ctx.save(); ctx.globalAlpha = (isHov ? 0.88 : 0.42) * pos.alpha;
          ctx.font = `500 ${subFs}px -apple-system,BlinkMacSystemFont,sans-serif`;
          ctx.fillStyle = isHov ? n.color : 'rgba(26,24,20,0.55)';
          ctx.textAlign = anchor; ctx.textBaseline = 'middle';
          ctx.fillText(n.sub, lx, ly);
          ctx.restore();
        }
      });

      animRef.current = requestAnimationFrame(draw);
    };

    animRef.current = requestAnimationFrame(draw);

    const NATIT = { id:'__natit__', name:'Natit', sub:'AI brain · reads your data, runs your growth', color:'#a78bfa' };
    const onMouseMove = (e) => {
      const rect = canvas.getBoundingClientRect();
      const mx = (e.clientX - rect.left) * (W / rect.width);
      const my = (e.clientY - rect.top) * (H / rect.height);
      let found = null;
      // Center brain hover
      const cdx = mx - CX, cdy = my - CY;
      if (Math.sqrt(cdx*cdx + cdy*cdy) < NODE_R * 1.7) {
        found = '__natit__';
      } else {
        posRef.current.forEach((pos, i) => {
          const dx = mx - pos.x, dy = my - pos.y;
          if (Math.sqrt(dx*dx + dy*dy) < NODE_R + 10) found = platforms[i].id;
        });
      }
      if (found !== hovRef.current) {
        hovRef.current = found;
        if (found === '__natit__') setHovLabel(NATIT);
        else setHovLabel(found ? platforms.find(p => p.id === found) : platforms.find(p => p.id === 'st'));
      }
    };
    const onMouseLeave = () => {
      hovRef.current = 'st';
      setHovLabel(platforms.find(p => p.id === 'st'));
    };
    const onResize = () => { cancelAnimationFrame(animRef.current); setup(); animRef.current = requestAnimationFrame(draw); };

    canvas.addEventListener('mousemove', onMouseMove);
    canvas.addEventListener('mouseleave', onMouseLeave);
    window.addEventListener('resize', onResize);
    return () => {
      cancelAnimationFrame(animRef.current);
      canvas.removeEventListener('mousemove', onMouseMove);
      canvas.removeEventListener('mouseleave', onMouseLeave);
      window.removeEventListener('resize', onResize);
    };
  }, []);

  return (
    <section className="ntw-section" ref={sectionRef}>
      <div className="shell">
        <div className="ntw-label">
          <span className="eyebrow">Built on the stack you already run</span>
        </div>
        <div className="ntw-canvas-wrap">
          <canvas ref={canvasRef} className="ntw-canvas" />
          {hovLabel && (
            <div className="ntw-hov-pill" style={{borderColor: hovLabel.color + '55'}}>
              <span className="ntw-hov-dot" style={{background: hovLabel.color}}></span>
              <span className="ntw-hov-name">{hovLabel.name}</span>
              <span className="ntw-hov-sub">{hovLabel.sub}</span>
            </div>
          )}
        </div>
      </div>
    </section>
  );
}

function GapSection() {
  return (
    <section className="section-pad" id="gap" data-screen-label="02 The gap">
      <div className="shell">
        <div className="section-head">
          <span className="num">— 01 / The gap</span>
          <h2>Industry standard is doing this <span className="accent">manually</span>. We built the loop that runs without you.</h2>
          <p className="lede">Most home-service companies sit on years of operational data and the budget to act on it. The pieces are there. What isn't — yet — is the connection between them. That's the whole gap.</p>
        </div>

        <div className="gap-grid">
          <div className="gap-col gap-col-industry">
            <h3>Industry standard</h3>
            <ul className="gap-list">
              <li><span className="marker">a</span><span><strong>ServiceTitan reports live in ServiceTitan.</strong> The data is rich, but it never makes it into the targeting that drives the spend.</span></li>
              <li><span className="marker">b</span><span><strong>Ad budgets run on intuition.</strong> Meta, Yelp, and Google get the same audience the agency built last year — refreshed by hand, on a cadence nobody trusts.</span></li>
              <li><span className="marker">c</span><span><strong>Attribution stops at the click.</strong> "Leads" go into a CRM. Whether they invoiced is a separate spreadsheet, two weeks later.</span></li>
              <li><span className="marker">d</span><span><strong>Enrichment is a project, not a pipeline.</strong> Property and wealth data exists — it's commercially available — but stitching it onto live segments is too complex for most teams to set up and maintain.</span></li>
            </ul>
          </div>
          <div className="gap-col gap-col-us">
            <h3>How we run it</h3>
            <ul className="gap-list">
              <li><span className="marker">i</span><span><strong>The data flows.</strong> ServiceTitan → warehouse → enriched → scored → activated, every night, without anyone in the loop.</span></li>
              <li><span className="marker">ii</span><span><strong>Agentic scoring with checks.</strong> A network of agents reads the data, scores every customer, and flags edge cases for human review — fast, visible, never a black box.</span></li>
              <li><span className="marker">iii</span><span><strong>Campaigns trigger themselves.</strong> When a segment hits threshold, audiences upload to Meta, Yelp budgets shift, Google LSA bids adjust. Automatic, with a paper trail.</span></li>
              <li><span className="marker">iv</span><span><strong>Closed loop, end to end.</strong> Ad impression → booked job → invoiced revenue, in one view. Real CPL, real ROAS, by division.</span></li>
            </ul>
          </div>
        </div>

        <p className="gap-pullquote">
          The gap isn't the data. It's the wiring. <em>Most teams haven't built the connection between their operational data and the ad platforms spending on it</em> — because doing it well takes a system, not a tool.
        </p>
      </div>
    </section>
  );
}

function SystemSection() {
  return (
    <section className="section-pad" id="system" style={{background:'var(--site-cream-2)'}} data-screen-label="03 The system">
      <div className="shell">
        <div className="section-head">
          <span className="num">— 02 / The system</span>
          <h2>An agentic stack that runs your growth between meetings.</h2>
          <p className="lede">The system is a sequence of agents — each with one job, all auditable. They read your data, score it, build segments, and fire campaigns. You see every step, every reason, every record. Click any node to see what flows through it.</p>
        </div>
        <SystemDiagram/>
        <div className="learning-note">
          <p>The system improves with every cycle. After each campaign run, it updates its scoring based on what actually converted: which segments booked, which zip codes performed, which creative drove invoiced revenue. New engagements start in supervised mode — every action is reviewed before it fires. Over 60 to 90 days, as the system builds a track record on your specific business, more of the loop runs on its own. The guardrails stay. The manual review gates open.</p>
        </div>
      </div>
    </section>
  );
}

function ProduceSection() {
  const more = [
    { num:'— 04', title:'Daily executive brief', desc:'Revenue from paid, attributed to invoice, by division. One view, every morning. No log-ins required.' },
    { num:'— 05', title:'Geographic performance map', desc:'Which zip codes are converting. Which are dead zones. Where to shift budget. Rebuilt every night from live data.' },
    { num:'— 06', title:'Enriched CRM records', desc:'Up to 85% of your customer records appended with home value, equity, system age, and permit history. Nightly.' },
    { num:'— 07', title:'Predictive service pipeline', desc:'Customers likely to need service in the next 90 days, scored against property age and prior job patterns. Auto-refreshed.' },
    { num:'— 08', title:'Cross-division opportunity map', desc:'Pool customers ready for roofing. HVAC customers ready for garage doors. Identified and queued for outreach automatically.' },
    { num:'— 09', title:'Budget reallocation log', desc:'Every time the system shifts spend, it logs what moved, why, and what the outcome was. Fully auditable, no black box.' },
  ];

  return (
    <section className="section-pad" id="produce" data-screen-label="04 What it produces">
      <div className="shell">
        <div className="section-head">
          <span className="num">— 03 / What it produces</span>
          <h2>What you get on the <span className="accent">other side</span> of the system.</h2>
          <p className="lede">Not slides. Not dashboards you will never log into. Real outputs your team can act on — and ad platforms that act on themselves. Every night, without anyone in the loop.</p>
        </div>

        <div className="artifacts">
          <div className="artifact">
            <div className="artifact-side">
              <div className="num">— output 01</div>
              <h3>A scored segment, refreshed nightly.</h3>
              <p>Every customer in your ServiceTitan, joined to property and wealth signals, scored for replacement likelihood and lifetime value. Up to 85% match rate on address-based enrichment. Tagged. Versioned. Reviewable per record.</p>
            </div>
            <SegmentArtifact/>
          </div>

          <div className="artifact">
            <div className="artifact-side">
              <div className="num">— output 02</div>
              <h3>A campaign that fires itself, across every channel.</h3>
              <p>When a segment crosses threshold, the system activates everywhere at once. Audiences upload to Meta. Re-engagement texts and emails go out through your existing GHL. Yelp shifts budget. Google LSA reweights. Every step logged. Every guardrail checked. Nothing happens by surprise.</p>
            </div>
            <CampaignLogArtifact/>
          </div>

          <div className="artifact">
            <div className="artifact-side">
              <div className="num">— output 03</div>
              <h3>Attribution that holds — to invoiced revenue.</h3>
              <p>Booked job triggers a CAPI Lead. Invoiced job triggers a CAPI Purchase with the actual revenue value. Meta, Yelp, and Google reconcile back to ServiceTitan in one view — by division, by zip, by week.</p>
            </div>
            <AttributionArtifact/>
          </div>
        </div>

        <div className="produce-more">
          <div className="produce-more-label">And then six more things, every day</div>
          <div className="produce-more-grid">
            {more.map((m, i) => (
              <div className="produce-more-card" key={i}>
                <div className="produce-more-num">{m.num}</div>
                <div className="produce-more-title">{m.title}</div>
                <p className="produce-more-desc">{m.desc}</p>
              </div>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
}

function EngageSection() {
  return (
    <section className="section-pad" id="engage" style={{background:'var(--site-cream-2)'}} data-screen-label="05 How we engage">
      <div className="shell">
        <div className="section-head">
          <span className="num">— 04 / How we engage</span>
          <h2>Done-for-you, end to end. We build it, run it, and stand behind the numbers.</h2>
          <p className="lede">This is not software you buy and figure out. We come in, connect everything, and run the system. Your team keeps doing what it does. Ours adds the intelligence layer underneath.</p>
        </div>
        <div className="engage-grid">
          <div className="engage-col">
            <h3>Build</h3>
            <p>We stand up the pipeline: ServiceTitan to warehouse, enrichment, scoring, and ad platform connections. Two to four weeks to first segment in production. No migrations. No rip-and-replace. Your existing stack stays exactly as it is.</p>
          </div>
          <div className="engage-col">
            <h3>Run</h3>
            <p>Segments refresh nightly. Campaigns trigger across Meta, Yelp, Google, SMS, and email when the data says so. We monitor every guardrail, intervene where the system asks for it, and ship updates weekly. You get a brief in the morning. The work runs overnight.</p>
          </div>
          <div className="engage-col">
            <h3>Measure</h3>
            <p>One number per division: revenue from paid, attributed to invoice. Not clicks. Not leads. The actual dollars, traced back through ServiceTitan. Sent weekly. Reconciled to the dollar so there is no ambiguity about what is working and what is not.</p>
          </div>
        </div>
      </div>
    </section>
  );
}

function ICPSection() {
  const items = [
    <>You run <em>ServiceTitan</em> as your field management platform — and you've been running it long enough that the data is real.</>,
    <>You're between <em>$20M and $125M</em> in annual revenue, with multiple service divisions under one roof.</>,
    <>You already spend on <em>Meta, Yelp, and Google</em> — and you don't fully trust what your reports say it produced.</>,
    <>You have a <em>marketing leader</em> (or you are one) who reads numbers and asks where they came from.</>,
    <>You'd rather have a <em>system that runs</em> than a dashboard that shows.</>,
  ];
  return (
    <section className="section-pad" id="who" data-screen-label="06 Who this is for">
      <div className="shell">
        <div className="section-head">
          <span className="num">— 05 / Who this is for</span>
          <h2>Not for everyone. By design.</h2>
          <p className="lede">If three or more of these are true, the audit will show you something useful within a week. If they're not — we'll say so, and point you somewhere better.</p>
        </div>
        <ol className="icp-list">
          {items.map((t, i) => (
            <li key={i}>
              <span className="idx">{String(i + 1).padStart(2, "0")} —</span>
              <span className="text">{t}</span>
            </li>
          ))}
        </ol>
        <p className="icp-foot">If you're under $5M or run a different field platform, this isn't the right fit yet. We'd rather tell you up front.</p>
      </div>
    </section>
  );
}

/* ---------- Audit dashboard — chart helpers ---------- */

function useAuditChart(canvasRef, getConfig) {
  React.useEffect(() => {
    if (!canvasRef.current || !window.Chart) return;
    const chart = new window.Chart(canvasRef.current, getConfig());
    return () => chart.destroy();
  }, []);
}

const CD = {
  green:  '#4ade80',
  red:    '#f87171',
  yellow: '#fbbf24',
  purple: '#a78bfa',
  textDim:  'rgba(255,255,255,0.46)',
  textMid:  'rgba(255,255,255,0.68)',
  textHi:   'rgba(255,255,255,0.90)',
  grid:     'rgba(255,255,255,0.06)',
  tip: { backgroundColor:'rgba(8,4,24,0.94)', bodyColor:'rgba(255,255,255,0.78)', titleColor:'#fff', padding:10, cornerRadius:8 },
};

function RevenueTrendChart() {
  const ref = React.useRef(null);
  useAuditChart(ref, () => ({
    type: 'line',
    data: {
      labels: ['Jun','Jul','Aug','Sep','Oct','Nov','Dec','Jan','Feb','Mar','Apr','May'],
      datasets: [
        {
          label: 'Actual revenue',
          data: [3.82,4.01,3.74,4.18,4.44,4.21,4.63,4.31,4.88,4.52,4.31,4.94],
          borderColor: CD.purple, backgroundColor: 'rgba(167,139,250,0.10)',
          fill: true, tension: 0.42, pointRadius: 3.5, pointBackgroundColor: CD.purple, borderWidth: 2,
        },
        {
          label: 'With system (projection)',
          data: [null,null,null,null,null,4.62,4.89,5.18,5.48,5.62,5.81,6.14],
          borderColor: CD.green, borderDash: [6,4], backgroundColor: 'rgba(74,222,128,0.07)',
          fill: true, tension: 0.42, pointRadius: 3.5, pointBackgroundColor: CD.green, borderWidth: 2,
        }
      ]
    },
    options: {
      responsive: true, maintainAspectRatio: false, animation: { duration: 900, easing: 'easeInOutQuart' },
      plugins: {
        legend: { labels: { color: CD.textMid, boxWidth: 12, padding: 14, font: { size: 11 } } },
        tooltip: { ...CD.tip, callbacks: { label: ctx => '  $' + ctx.raw + 'M' } }
      },
      scales: {
        x: { ticks: { color: CD.textDim, font: { size: 10 } }, grid: { color: CD.grid }, border: { color:'transparent' } },
        y: { ticks: { color: CD.textDim, font: { size: 10 }, callback: v => '$' + v + 'M' }, grid: { color: CD.grid }, border: { color:'transparent' }, min: 3.2, max: 6.8 }
      }
    }
  }));
  return (
    <div className="adash-panel adash-panel-full">
      <div className="adash-panel-label">Monthly booked revenue · 12 months actual + system projection</div>
      <div style={{height:210,position:'relative',marginTop:14}}><canvas ref={ref}></canvas></div>
      <div className="adash-panel-foot">
        <span className="adash-leg-dot" style={{background:CD.purple}}></span>Actual
        <span className="adash-leg-dot" style={{background:CD.green,marginLeft:14}}></span>Projection assumes system live month 6
        <span className="adash-tag-green" style={{marginLeft:'auto'}}>+$1.2M annual upside modeled at current booking rate</span>
      </div>
    </div>
  );
}

function ChannelCplChart() {
  const ref = React.useRef(null);
  useAuditChart(ref, () => ({
    type: 'bar',
    data: {
      labels: ['Meta · Retargeting','Yelp · Roofing','Google LSA','Yelp · Garage Doors','Yelp · Pools'],
      datasets: [
        {
          label: 'CPL (cost per booked job)',
          data: [38,42,61,89,112],
          backgroundColor: ['#4ade80','#4ade80','#fbbf24','#f87171','#f87171'],
          borderRadius: 5, barThickness: 18,
        },
        {
          label: '$55 target',
          data: [55,55,55,55,55], type: 'line',
          borderColor: 'rgba(255,255,255,0.28)', borderDash: [5,4], pointRadius: 0, borderWidth: 1.5,
        }
      ]
    },
    options: {
      indexAxis: 'y', responsive: true, maintainAspectRatio: false, animation: { duration: 900 },
      plugins: {
        legend: { labels: { color: CD.textMid, boxWidth: 12, padding: 10, font: { size: 11 } } },
        tooltip: { ...CD.tip, callbacks: { label: ctx => '  $' + ctx.raw + ' per job' } }
      },
      scales: {
        x: { ticks: { color: CD.textDim, font: { size: 10 }, callback: v => '$' + v }, grid: { color: CD.grid }, border: { color:'transparent' }, max: 140 },
        y: { ticks: { color: CD.textMid, font: { size: 11 } }, grid: { display: false }, border: { color:'transparent' } }
      }
    }
  }));
  return (
    <div className="adash-panel">
      <div className="adash-panel-label">Cost per booked job by channel · last 90 days vs $55 target</div>
      <div style={{height:230,position:'relative',marginTop:14}}><canvas ref={ref}></canvas></div>
      <div className="adash-panel-foot">
        <span className="adash-tag-red">2 channels above benchmark</span>
        <span className="adash-tag-green" style={{marginLeft:8}}>2 channels efficient</span>
      </div>
    </div>
  );
}

function AttributionDonut() {
  const ref = React.useRef(null);
  useAuditChart(ref, () => ({
    type: 'doughnut',
    data: {
      labels: ['Attributed (68%)','Untraced (32%)'],
      datasets: [{
        data: [68,32],
        backgroundColor: ['rgba(74,222,128,0.85)','rgba(248,113,113,0.85)'],
        borderColor: ['rgba(74,222,128,0.2)','rgba(248,113,113,0.2)'],
        borderWidth: 2, hoverOffset: 6,
      }]
    },
    options: {
      responsive: true, maintainAspectRatio: false, cutout: '72%', animation: { duration: 900 },
      plugins: {
        legend: { display: false },
        tooltip: { ...CD.tip, callbacks: { label: ctx => '  ' + ctx.raw + '% of revenue' } }
      }
    }
  }));
  return (
    <div className="adash-panel">
      <div className="adash-panel-label">Revenue attribution coverage · $887k/mo total booked</div>
      <div className="adash-donut-layout">
        <div style={{position:'relative',flexShrink:0}}>
          <div style={{height:164,width:164}}><canvas ref={ref}></canvas></div>
          <div className="adash-donut-center">
            <div className="adash-donut-big">$284k</div>
            <div className="adash-donut-sm">untraced<br/>per month</div>
          </div>
        </div>
        <div className="adash-attr-channels">
          {[
            {ch:'Meta',      pct:72, c:CD.green},
            {ch:'Yelp',      pct:64, c:CD.green},
            {ch:'Google',    pct:51, c:CD.yellow},
            {ch:'GHL / SMS', pct:31, c:CD.red},
            {ch:'Direct',    pct:18, c:CD.red},
          ].map(r => (
            <div className="adash-attr-row" key={r.ch}>
              <span className="adash-attr-ch">{r.ch}</span>
              <div className="adash-attr-track">
                <div style={{width:r.pct+'%',height:'100%',background:r.c,borderRadius:3}}></div>
              </div>
              <span className="adash-attr-pct" style={{color:r.c}}>{r.pct}%</span>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function DivisionChart() {
  const ref = React.useRef(null);
  useAuditChart(ref, () => ({
    type: 'bar',
    data: {
      labels: ['Roofing','Pools','Garage Doors','HVAC','Electrical'],
      datasets: [
        { label:'Ad spend / mo', data:[12400,7800,9200,6100,3900], backgroundColor:'rgba(167,139,250,0.65)', borderRadius:4, barThickness:18, yAxisID:'y' },
        { label:'Revenue attributed', data:[187000,94000,142000,61000,29000], backgroundColor:'rgba(74,222,128,0.65)', borderRadius:4, barThickness:18, yAxisID:'y2' }
      ]
    },
    options: {
      responsive: true, maintainAspectRatio: false, animation: { duration: 900 },
      layout: { padding: { right: 14 } },
      plugins: {
        legend: { labels: { color: CD.textMid, boxWidth: 12, padding: 12, font: { size: 11 } } },
        tooltip: { ...CD.tip, callbacks: { label: ctx => '  $' + ctx.raw.toLocaleString() } }
      },
      scales: {
        x: { ticks: { color: CD.textMid, font: { size: 11 }, maxRotation: 0 }, grid: { color: CD.grid }, border: { color:'transparent' } },
        y:  { ticks: { color:'rgba(167,139,250,0.8)', font:{size:10}, callback: v=>'$'+(v/1000).toFixed(0)+'k' }, grid:{color:CD.grid}, border:{color:'transparent'}, position:'left' },
        y2: { ticks: { color:'rgba(74,222,128,0.8)',  font:{size:10}, callback: v=>'$'+(v/1000).toFixed(0)+'k' }, grid:{display:false}, border:{color:'transparent'}, position:'right' }
      }
    }
  }));
  return (
    <div className="adash-panel adash-panel-full">
      <div className="adash-panel-label">Ad spend vs attributed revenue by division · 90 days · left axis = spend · right = revenue</div>
      <div style={{height:220,position:'relative',marginTop:14}}><canvas ref={ref}></canvas></div>
    </div>
  );
}

function GeoMap({ rows, active, setActive }) {
  // Pre-compute tooltip data for the active row so it can be rendered last (on top of all markers).
  const activeRow = active ? rows.find(r => r.city === active) : null;
  let tip = null;
  if (activeRow) {
    const r = activeRow;
    const good = r.status === 'good';
    const jobs = parseInt(r.jobs) || 0;
    const radius = good ? Math.max(13, Math.min(27, 11 + jobs * 1.1)) : 11;
    tip = {
      r, good, strokeCol: good ? '#4ade80' : '#f87171',
      tipX: Math.min(r.x + radius + 4, 200),
      tipY: Math.max(r.y - 26, 4),
    };
  }
  return (
    <svg viewBox="0 0 310 230" className="adash-geomap" role="img" aria-label="Geographic revenue map"
      style={{touchAction:'manipulation'}}>
      <line x1="0" y1="70" x2="310" y2="70" stroke="rgba(255,255,255,0.04)" strokeWidth="0.5"/>
      <line x1="0" y1="140" x2="310" y2="140" stroke="rgba(255,255,255,0.04)" strokeWidth="0.5"/>
      <line x1="78" y1="0" x2="78" y2="190" stroke="rgba(255,255,255,0.04)" strokeWidth="0.5"/>
      <line x1="155" y1="0" x2="155" y2="190" stroke="rgba(255,255,255,0.04)" strokeWidth="0.5"/>
      <line x1="232" y1="0" x2="232" y2="190" stroke="rgba(255,255,255,0.04)" strokeWidth="0.5"/>
      <path d="M12,190 Q20,150 30,110 Q40,76 50,46 Q56,30 62,16"
        stroke="rgba(167,139,250,0.08)" strokeWidth="10" fill="none" strokeLinecap="round"/>
      <path d="M12,190 Q20,150 30,110 Q40,76 50,46 Q56,30 62,16"
        stroke="rgba(167,139,250,0.28)" strokeWidth="1.8" fill="none" strokeLinecap="round" strokeDasharray="5,4"/>
      <text transform="rotate(-62,24,112)" x="24" y="112" fontSize="7.5" fill="rgba(167,139,250,0.30)" letterSpacing="1.8">PACIFIC OCEAN</text>

      {/* First pass: all city markers — NO tooltips here so they can't paint over any tooltip */}
      {rows.map(r => {
        const good = r.status === 'good';
        const jobs = parseInt(r.jobs) || 0;
        const radius = good ? Math.max(13, Math.min(27, 11 + jobs * 1.1)) : 11;
        const fillColor = good ? 'rgba(74,222,128,0.22)' : 'rgba(248,113,113,0.18)';
        const strokeCol = good ? '#4ade80' : '#f87171';
        const isActive = active === r.city;
        const cityWords = r.city.split(' ');
        const labelY = r.y + radius + 11;
        return (
          <g key={r.city} style={{cursor:'pointer'}}
            onClick={() => setActive(prev => prev === r.city ? null : r.city)}>
            {isActive && <circle cx={r.x} cy={r.y} r={radius} fill="none" stroke={strokeCol} strokeWidth="1.5" className="geo-pulse-ring"/>}
            <circle cx={r.x} cy={r.y} r={radius + (isActive ? 8 : 4)}
              fill={isActive ? (good ? 'rgba(74,222,128,0.10)' : 'rgba(248,113,113,0.10)') : (good ? 'rgba(74,222,128,0.06)' : 'rgba(248,113,113,0.06)')}
              style={{transition:'r 150ms'}}/>
            <circle cx={r.x} cy={r.y} r={radius} fill={fillColor} stroke={strokeCol}
              strokeWidth={isActive ? 2.2 : 1.5} style={{transition:'stroke-width 150ms'}}/>
            <text x={r.x} y={r.y + 1.5} textAnchor="middle" dominantBaseline="middle"
              fontSize="8.5" fontWeight="700" fill={strokeCol} style={{pointerEvents:'none'}}>{jobs || '0'}</text>
            {cityWords.map((word, wi) => (
              <text key={wi} x={r.x} y={labelY + wi * 10}
                textAnchor="middle" fontSize={wi === 0 ? '7.5' : '6.5'}
                fontWeight={wi === 0 ? '600' : '400'}
                fill={wi === 0 ? 'rgba(255,255,255,0.58)' : 'rgba(255,255,255,0.36)'}
                style={{pointerEvents:'none'}}>
                {word}
              </text>
            ))}
          </g>
        );
      })}

      {/* Second pass: active tooltip rendered after all markers so it always sits on top */}
      {tip && (
        <g className="adash-geo-tooltip" style={{pointerEvents:'none'}}>
          <rect x={tip.tipX} y={tip.tipY} width={104} height={44} rx={4}
            fill="rgba(8,4,24,0.94)" stroke="rgba(167,139,250,0.32)" strokeWidth="1"/>
          <text x={tip.tipX + 8} y={tip.tipY + 13} fontSize="8.5" fontWeight="700" fill="#fff">{tip.r.city}</text>
          <text x={tip.tipX + 8} y={tip.tipY + 24} fontSize="7.5" fill="rgba(255,255,255,0.58)">{tip.r.spend} spend</text>
          <text x={tip.tipX + 8} y={tip.tipY + 35} fontSize="7.5" fill={tip.strokeCol}>
            {tip.r.jobs === '0' ? 'No jobs booked' : tip.r.jobs + ' jobs · ' + tip.r.cpl + ' CPL'}
          </text>
        </g>
      )}

      <circle cx="16" cy="210" r="5" fill="rgba(74,222,128,0.20)" stroke="#4ade80" strokeWidth="1.5"/>
      <text x="25" y="214" fontSize="7.5" fill="rgba(255,255,255,0.38)">Converting · tap for details</text>
      <circle cx="16" cy="222" r="5" fill="rgba(248,113,113,0.15)" stroke="#f87171" strokeWidth="1.5"/>
      <text x="25" y="226" fontSize="7.5" fill="rgba(255,255,255,0.38)">Dead zone · budget burning</text>
    </svg>
  );
}

function AuditSection({ onRequestAudit }) {
  const [geoActive, setGeoActive] = React.useState('Santa Monica');
  const [showSignal, setShowSignal] = React.useState(false);
  const [showFindings, setShowFindings] = React.useState(false);
  const [showOpps, setShowOpps] = React.useState(false);

  const kpis = [
    { val:'$4.3M',  label:'Monthly booked revenue',          color:CD.green  },
    { val:'42,847', label:'Total CRM records',                color:'rgba(255,255,255,0.88)' },
    { val:'$284k',  label:'Revenue with no paid source',      color:CD.red    },
    { val:'$6,200', label:'Monthly spend to dead zones',      color:CD.red    },
    { val:'5,243',  label:'Reactivation candidates found',    color:CD.yellow },
    { val:'3.2x',   label:'Avg ROAS across live channels',    color:CD.green  },
  ];

  const grades = [
    {
      grade:'B+', cls:'grade-b', label:'Database Quality',
      detail:'89% phone match · 4.2% duplicate rate',
      bench:'Industry standard: 95%+ match rate · under 2% duplicates',
    },
    {
      grade:'D', cls:'grade-d', label:'Attribution Coverage',
      detail:'68% traced · $284k/mo invisible',
      bench:'Benchmark: 85%+ traced for operators your size. You are 2x the normal gap.',
    },
    {
      grade:'C', cls:'grade-c', label:'Paid Media Efficiency',
      detail:'3.2x ROAS · 2 channels above CPL target',
      bench:'Home services avg: 3.5x ROAS · $45 to $75 CPL. You have 2 outliers pulling the average down.',
    },
    {
      grade:'D', cls:'grade-d', label:'Customer Reactivation',
      detail:'5,243 dormant · zero active campaigns',
      bench:'Industry avg: 15 to 20% of dormant base contacted per campaign cycle. You are at 0%.',
    },
    {
      grade:'C', cls:'grade-c', label:'Geographic Targeting',
      detail:'4 dead zones · $6,200/mo wasted',
      bench:'Best practice: under 5% of budget in non-converting zones. You are at 28%.',
    },
    {
      grade:'F', cls:'grade-f', label:'Enrichment Depth',
      detail:'No property or wealth layer on any record',
      bench:'Top operators: 3 to 4 data layers per record. Property age alone improves targeting 40%.',
    },
  ];

  const opps = [
    {
      color:'green', badge:'Revenue unlock',
      headline:'5,243 reactivation candidates ready to activate',
      detail:'Customers with no inbound signal in 18 or more months. Scored against property age, prior job type, and neighborhood. Sitting idle in your database today with no active campaign targeting them.',
      value:'$320k/mo', valLabel:'estimated reachable revenue',
      spendToActivate:'$9,200/mo to activate ($300/day Meta + $200/mo SMS via GHL)',
      buildBullets:[
        'ST segment scored and synced in under 24 hrs',
        'Meta custom audience matched and activated',
        'GHL 3-touch SMS and email re-engagement live in 72 hrs',
        'Scores recalculate nightly against live ST data',
      ],
      action:'Natit builds this segment tonight, syncs to Meta and GHL within 24 hours. Re-engagement sequence fires in 72 hours. Score recalculates every night against live ST data.',
    },
    {
      color:'red', badge:'Waste elimination',
      headline:'$6,200 per month burning in 4 zero-conversion zones',
      detail:'4 west LA neighborhoods account for 28% of current ad spend. Zero booked jobs in the last 90 days across Yelp, Meta, and Google. Every dollar compounds the loss with no path to recovery.',
      value:'$74,400/yr', valLabel:'annualized waste to eliminate immediately',
      spendToActivate:'No additional spend. Budget redistribution only.',
      buildBullets:[
        'Exclusion audiences uploaded to Meta, Yelp, and Google tonight',
        'Budget reallocated to converting zones at $221 avg CPL',
        'No manual campaign edits required by your team',
        'Geographic suppression rules maintained automatically going forward',
      ],
      action:'Suppression audiences built and uploaded to all 3 platforms tonight. Budget reallocated to the 3 zones producing the most jobs at $221 average CPL. No manual campaign edits required.',
    },
    {
      color:'yellow', badge:'Attribution fix',
      headline:'$284k per month in revenue you cannot optimize',
      detail:'32% of all booked revenue has no traceable paid source. Real bookings. Real revenue. You just cannot connect it to a campaign, so the spend that drove it is invisible to every optimization decision.',
      value:'$284k/mo', valLabel:'in revenue you are currently flying blind on',
      spendToActivate:'One-time setup only. No ongoing ad spend increase.',
      buildBullets:[
        'CAPI Purchase events wired to every ServiceTitan invoice',
        'First clean attribution cycle closes within 14 days',
        'Every booking forward traced to an impression and channel',
        'GHL and ST revenue data reconciled in a single view',
      ],
      action:'CAPI Purchase events wired to every ServiceTitan invoice. First clean attribution cycle closes within 14 days of launch. Every booking forward is traceable to an impression.',
    },
  ];

  const findings = [
    {
      num:'01', status:'bad',
      problem:'Yelp Pools CPL is $112 vs $55 target',
      cost:'103% above efficient cost · $3,400/mo excess',
      bench:'Industry avg CPL for home services paid ads: $45 to $75 per booked job',
      action:'Suppression built, budget shifted to Yelp Roofing and Meta. CPL projected to normalize within 30 days.',
    },
    {
      num:'02', status:'bad',
      problem:'4 west LA zones with $6,200/mo spend and zero conversions',
      cost:'$74,400/yr in pure waste with no recovery path',
      bench:'Best practice: under 5% of budget in non-converting zones. You are running at 28%.',
      action:'Exclusion audiences uploaded to Meta, Yelp, and Google tonight. Redirected to converting zones.',
    },
    {
      num:'03', status:'bad',
      problem:'32% of revenue has no paid attribution',
      cost:'$284k/mo invisible to every optimization you make',
      bench:'Operators with proper CAPI setup: 85 to 95% attribution coverage. You are at 68%.',
      action:'CAPI mapping closes this. Every invoice tied to an impression within 14 days.',
    },
    {
      num:'04', status:'warn',
      problem:'5,243 customers with no active campaign targeting them',
      cost:'$320k/mo in reachable revenue sitting completely idle',
      bench:'Industry avg: 15 to 20% of dormant base contacted per month. You are at 0%.',
      action:'Segment scored and synced to Meta and GHL. Re-engagement running in 72 hours.',
    },
    {
      num:'05', status:'warn',
      problem:'42,847 records with no enrichment layer',
      cost:'~85% are enrichable. Every unenriched score is 2D: no property signal, no wealth signal.',
      bench:'Top home service operators: 3 to 4 enrichment layers per record. Property age alone improves segment targeting 40%.',
      action:'Enrichment layer joins property data to your CRM nightly. We match against address, typically hitting 82 to 87% of records. Unmatched records remain in pool but score lower.',
    },
  ];

  // West LA cities for geo table + SVG map
  const geoRows = [
    { city:'Santa Monica',      div:'Roofing',      spend:'$3,100', jobs:'14', cpl:'$221', status:'good', x:70,  y:126 },
    { city:'Brentwood',         div:'Pools',        spend:'$1,800', jobs:'9',  cpl:'$200', status:'good', x:108, y:74  },
    { city:'Beverly Hills',     div:'Garage Doors', spend:'$2,400', jobs:'11', cpl:'$218', status:'good', x:170, y:78  },
    { city:'Pacific Palisades', div:'Roofing',      spend:'$1,900', jobs:'0',  cpl:'none', status:'bad',  x:42,  y:48  },
    { city:'Culver City',       div:'Pools',        spend:'$1,100', jobs:'0',  cpl:'none', status:'bad',  x:168, y:148 },
  ];

  // upside projections if performance improves to benchmark
  const upside = [
    { label:'If booking rate improves from 27% to 42% avg', gain:'+$380k/mo', note:'Industry avg for home services CSR booking is 42%; top operators hit 70%' },
    { label:'If attribution closes and CPL normalizes on 2 channels', gain:'+$74k/mo', note:'Based on current spend redirected at $55 CPL target vs current $112 and $89 outliers' },
  ];

  return (
    <section className="audit-section" id="audit" data-screen-label="07 The audit">
      <div className="shell">
        <div className="audit-card">
          <div className="audit-card-inner">
            <span className="eyebrow">— 06 / The audit · Free · we do the work</span>
            <h2 style={{marginTop:12}}>Before the call, you will already know what is in your database <span style={{color:'var(--site-purple)',fontStyle:'italic',fontWeight:600}}>and what it is worth.</span></h2>
            <p className="lede" style={{marginTop:12,marginBottom:32}}>Our AI connects directly to your ServiceTitan, Meta, Yelp, and Google. It maps every gap, scores every opportunity, and puts a dollar figure on what your data is already telling you. No spreadsheet to upload. No form to fill out.</p>

            <div className="adash-wrap">

              {/* Dashboard header */}
              <div className="adash-head">
                <div className="adash-head-left">
                  <div className="adash-head-title">Sample pipeline read — MTLA Home Services <span style={{fontSize:10,opacity:0.5,fontWeight:400}}>(fictional sample company)</span></div>
                  <div className="adash-head-meta">8 division operator · West LA · 42,847 records · May 2026</div>
                  <div className="adash-connections">
                    {['ServiceTitan','Meta','Yelp','Google','Snowflake'].map(s => (
                      <span className="adash-conn" key={s}>{s} ✓</span>
                    ))}
                  </div>
                </div>
                <div className="adash-head-right">
                  <span className="adash-sample-badge">Sample data</span>
                  <div className="adash-refresh">Refreshed · 4 min ago</div>
                </div>
              </div>

              {/* Hero opportunity banner */}
              <div className="adash-hero-opp">
                <div className="adash-hero-opp-val">$4.2M</div>
                <div className="adash-hero-opp-right">
                  <div className="adash-hero-opp-label">Estimated annual revenue sitting idle in your existing customer database today</div>
                  <div className="adash-hero-opp-sub">5,243 scored reactivation candidates · $350k/mo modeled at current avg ticket · proven in the analysis below</div>
                  <div className="adash-hero-opp-chips">
                    <span className="adash-proof-item green">$350k/mo reactivation</span>
                    <span className="adash-proof-sep">+</span>
                    <span className="adash-proof-item red">$74.4k/yr waste eliminated</span>
                    <span className="adash-proof-sep">+</span>
                    <span className="adash-proof-item yellow">$284k/mo attribution upside</span>
                  </div>
                  <div className="adash-roi-chips">
                    <span className="adash-roi-label">Paid channel upside:</span>
                    <span className="adash-roi-chip">$39k/mo current spend + system active = 4.8x ROAS modeled</span>
                    <span className="adash-roi-chip">$187k/mo attributed · $2.24M/yr from paid alone</span>
                  </div>
                </div>
              </div>

              {/* KPI row */}
              <div className="adash-kpis">
                {kpis.map((k,i) => (
                  <div className="adash-kpi" key={i}>
                    <div className="adash-kpi-val" style={{color:k.color}}>{k.val}</div>
                    <div className="adash-kpi-label">{k.label}</div>
                  </div>
                ))}
              </div>

              {/* Scorecard grades */}
              <div className="adash-scorecard-label">System audit scores · graded against home services industry benchmarks</div>
              <div className="adash-scorecard">
                {grades.map((g,i) => (
                  <div className="adash-score-card" key={i}>
                    <div className={"adash-grade " + g.cls}>{g.grade}</div>
                    <div className="adash-score-info">
                      <div className="adash-score-name">{g.label}</div>
                      <div className="adash-score-detail">{g.detail}</div>
                      <div className="adash-score-bench">{g.bench}</div>
                    </div>
                  </div>
                ))}
              </div>

              {/* Upside projections row */}
              <div className="adash-panel adash-panel-full" style={{background:'rgba(167,139,250,0.06)',borderColor:'rgba(167,139,250,0.16)'}}>
                <div className="adash-panel-label" style={{color:'rgba(167,139,250,0.7)'}}>Performance upside · what improving to benchmark looks like</div>
                <div className="adash-upside-grid">
                  {upside.map((u,i) => (
                    <div key={i} style={{background:'rgba(167,139,250,0.08)',borderRadius:8,padding:'12px 14px',border:'1px solid rgba(167,139,250,0.15)'}}>
                      <div style={{fontSize:24,fontWeight:800,color:'#a78bfa',letterSpacing:'-0.02em',marginBottom:4}}>{u.gain}</div>
                      <div style={{fontSize:12,fontWeight:600,color:'rgba(255,255,255,0.80)',marginBottom:5}}>{u.label}</div>
                      <div style={{fontFamily:'var(--font-sans-small)',fontSize:10,color:'rgba(255,255,255,0.40)',lineHeight:1.4}}>{u.note}</div>
                    </div>
                  ))}
                </div>
              </div>

              {/* Revenue trend chart */}
              <RevenueTrendChart/>

              {/* CPL + Attribution row */}
              <div className="adash-2col">
                <ChannelCplChart/>
                <AttributionDonut/>
              </div>

              {/* Division chart */}
              <DivisionChart/>

              {/* Geographic map + table */}
              <div className="adash-panel adash-panel-full">
                <div className="adash-panel-label">Geographic performance · West LA · 90 days · 5 zones analyzed · tap a city to explore</div>
                <div className="adash-geo-layout">
                  <GeoMap rows={geoRows} active={geoActive} setActive={setGeoActive}/>
                  <div className="adash-geo-table-wrap">
                    <div className="adash-geo-table">
                      <div className="adash-geo-head">
                        <span>Area</span><span>Division</span>
                        <span className="adash-geo-r">Spend</span>
                        <span className="adash-geo-r">Jobs</span>
                        <span className="adash-geo-r">CPL</span>
                        <span className="adash-geo-r">Status</span>
                      </div>
                      {geoRows.map(z => (
                        <div className={"adash-geo-row " + z.status + (geoActive === z.city ? ' geo-row-active' : '')}
                          key={z.city} style={{cursor:'pointer'}}
                          onClick={() => setGeoActive(prev => prev === z.city ? null : z.city)}>
                          <span className="adash-geo-zip" style={{fontSize:11}}>{z.city}</span>
                          <span>{z.div}</span>
                          <span className="adash-geo-r">{z.spend}</span>
                          <span className={"adash-geo-r " + (z.jobs === '0' ? 'bad' : 'good')}>{z.jobs === '0' ? 'none' : z.jobs}</span>
                          <span className={"adash-geo-r " + (z.cpl === 'none' ? 'bad' : 'good')}>{z.cpl}</span>
                          <span className="adash-geo-r">
                            <span className={"adash-geo-badge " + z.status}>{z.status === 'good' ? 'Converting' : 'Dead zone'}</span>
                          </span>
                        </div>
                      ))}
                    </div>
                    <div style={{marginTop:12,fontFamily:'var(--font-sans-small)',fontSize:10,color:'rgba(255,255,255,0.36)',lineHeight:1.5}}>
                      Pacific Palisades and Culver City account for $3,000/mo in spend with zero jobs booked across all channels in 90 days. Industry best practice: under 5% of budget in non-converting zones.
                    </div>
                  </div>
                </div>
              </div>

              {/* Market signal / weather insights panel */}
              <div className="adash-panel adash-panel-full" style={{background:'rgba(18,14,40,0.60)', borderColor:'rgba(167,139,250,0.10)'}}>
                <div className="adash-panel-label" style={{color:'rgba(167,139,250,0.60)',display:'flex',alignItems:'center',gap:12}}>
                  Market signal analysis · West LA · May 2026 · 4 patterns detected
                  <button className={"adash-toggle-btn" + (showSignal ? ' is-open' : '')} onClick={() => setShowSignal(s => !s)}>
                    {showSignal ? <>↑ Collapse</> : <><span className="t-arrow">↓</span> View 4 market patterns</>}
                  </button>
                </div>
                {showSignal && (
                  <ul className="adash-weather-list">
                    <li className="adash-weather-item">
                      <span className="adash-weather-icon">☀</span>
                      <span>Coastal zones (Santa Monica, Brentwood, Malibu): HVAC replacement demand runs ~40% lower than inland LA due to mild coastal climate. Pools and garage doors are the primary spring and summer opportunity window. Current budget mix does not reflect this.</span>
                    </li>
                    <li className="adash-weather-item">
                      <span className="adash-weather-icon">⚑</span>
                      <span>Pacific Palisades: $1,900/mo spend with zero booked jobs in 90 days. High household income neighborhood likely has existing maintenance contracts. Recommendation: shift to conquest-free acquisition in Beverly Hills and Brentwood where conversion rates are proven.</span>
                    </li>
                    <li className="adash-weather-item">
                      <span className="adash-weather-icon">◎</span>
                      <span>8 zip codes within your licensed service area have zero active ad impressions in the last 90 days. All are within driving distance of your current team. Zero conquest competition means first-mover advantage on acquisition costs right now.</span>
                    </li>
                    <li className="adash-weather-item">
                      <span className="adash-weather-icon">▲</span>
                      <span>June through September is peak HVAC season in LA County. Your current segments are not preloaded for the demand window opening in 4 weeks. Roofing and pool retargeting should be built and in warmup now to hit efficient CPL when volume spikes.</span>
                    </li>
                  </ul>
                )}
              </div>

              {/* Opportunity cards */}
              <div className="adash-section-label">
                <span className="adash-sdot green"></span>Identified opportunities · 3 found · estimated $4.2M in reachable revenue
                <button className={"adash-toggle-btn" + (showOpps ? ' is-open' : '')} onClick={() => setShowOpps(s => !s)}>
                  {showOpps ? <>↑ Collapse</> : <><span className="t-arrow">↓</span> Expand opportunities</>}
                </button>
              </div>
              <div className="adash-opps" style={showOpps ? {} : {display:'none'}}>
                {opps.map((o,i) => (
                  <div className={"adash-opp adash-opp-" + o.color} key={i}>
                    <div className={"adash-opp-badge adash-opp-badge-" + o.color}>{o.badge}</div>
                    <div className="adash-opp-headline">{o.headline}</div>
                    <div className="adash-opp-detail">{o.detail}</div>
                    <div className={"adash-opp-value adash-opp-value-" + o.color}>
                      <span className="adash-opp-num">{o.value}</span>
                      <span className="adash-opp-val-sub">{o.valLabel}</span>
                    </div>
                    <div className="adash-opp-spend">
                      <strong>Cost to activate:</strong> {o.spendToActivate}
                    </div>
                    <ul className="adash-opp-build">
                      {o.buildBullets.map((b,bi) => <li key={bi}>{b}</li>)}
                    </ul>
                    <div className="adash-opp-action">
                      <div className="adash-opp-action-lbl">Natit action</div>
                      <div className="adash-opp-action-txt">{o.action}</div>
                    </div>
                  </div>
                ))}
              </div>

              {/* Findings + AI actions */}
              <div className="adash-section-label" style={{marginTop:0}}>
                <span className="adash-sdot red"></span>Surfaced findings · 5 issues · AI action + benchmark for each
                <button className={"adash-toggle-btn" + (showFindings ? ' is-open' : '')} onClick={() => setShowFindings(s => !s)}>
                  {showFindings ? <>↑ Collapse</> : <><span className="t-arrow">↓</span> See all 5 findings</>}
                </button>
              </div>
              <div className="adash-findings" style={showFindings ? {} : {display:'none'}}>
                <div className="adash-findings-head">
                  <span></span><span>Finding</span>
                  <span>Current cost</span><span>What Natit does</span>
                </div>
                {findings.map((f,i) => (
                  <div className={"adash-finding-row adash-finding-" + f.status} key={i}>
                    <div className={"adash-finding-num adash-fn-" + f.status}>{f.num}</div>
                    <div>
                      <div className="adash-finding-problem">{f.problem}</div>
                      <div className="adash-finding-bench">{f.bench}</div>
                    </div>
                    <div className={"adash-finding-cost adash-fc-" + f.status}>{f.cost}</div>
                    <div className="adash-finding-action">
                      <span className="adash-action-chip">→</span>{f.action}
                    </div>
                  </div>
                ))}
              </div>

              {/* Closing */}
              <div className="adash-closing">
                <div className="adash-closing-lbl">What this means</div>
                <p className="adash-closing-body">Every finding above exists in your operation right now. They compound daily. The geography keeps burning. The attribution gap keeps growing. The reactivation audience keeps aging. None of it closes on its own.</p>
                <p className="adash-closing-body" style={{marginTop:10}}>The system closes all five in the first 72 hours. After that, it holds them closed. Automatically, every night, while your team keeps doing what it does. That is the only way this moves at the speed the data demands.</p>
                <div className="adash-closing-stats">
                  {[
                    {val:'72 hrs',      label:'to first live segment'},
                    {val:'14 days',     label:'to clean attribution'},
                    {val:'Every night', label:'scores refresh automatically'},
                    {val:'5 issues',    label:'closed before first invoice'},
                  ].map((s,i) => (
                    <div className="adash-cs" key={i}>
                      <div className="adash-cs-val">{s.val}</div>
                      <div className="adash-cs-label">{s.label}</div>
                    </div>
                  ))}
                </div>
              </div>

            </div>{/* end adash-wrap */}

            <div className="audit-actions">
              <button onClick={onRequestAudit} className="btn-audit">Request the free audit <span className="btn-arrow">→</span></button>
              <span className="meta">No card. Brief back within 48 hours.</span>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

function DeskSection({ onRequestAudit }) {
  return (
    <section className="desk" id="desk" data-screen-label="08 From the desk">
      <div className="shell">
        <div className="prose">
          <div className="desk-eyebrow"><span className="eyebrow">From the desk</span></div>
          <div className="desk-body">
            <p>Over the last several years I have built growth systems for home service companies operating at scale. The work started with demand generation and attribution, moved into AI and automation, and eventually landed on the specific problem this site is about: how do you take a company that runs ServiceTitan, already spending real money on paid ads, and connect those two things in a way that closes to invoiced revenue?</p>
            <p>The system we built answers that. One operator. Eight service divisions. 58,000 customer records enriched with property and wealth signals. Cost per lead down more than 30% after suppressing the geography that was not converting. Attribution closed from ad impression to booked job to invoice, across Meta and Yelp, in a single view. That work, the pipeline, the scoring, the activation logic, is what Natit runs. No platform was doing it before we built it. If you run ServiceTitan and you are spending real money on paid, the audit will show you what is already in your database. After that, we can talk.</p>
          </div>
          <div className="desk-sig">
            <div>
              <span className="name">Nick Morales</span>
              <span className="role">Managing Director · MarTech LA</span>
            </div>
          </div>
          <div className="desk-cta">
            <button onClick={onRequestAudit} className="btn btn-primary">Request the free audit <span className="btn-arrow">→</span></button>
          </div>
        </div>
      </div>
    </section>
  );
}

function Footer() {
  return (
    <footer className="foot">
      <div className="shell">
        <div className="foot-row">
          <div className="left">
            <strong>MarTech LA</strong> — data + paid for home-service operators · © 2026
          </div>
          <div className="right">
            <a href="#audit" className="bare">Request audit</a>
          </div>
        </div>
      </div>
    </footer>
  );
}

function AuditPopup({ onClose }) {
  const revenues     = ['Under $5M','$5M to $20M','$20M to $50M','$50M to $125M','$125M+'];
  const tradeCounts  = ['1 to 2 trades','3 to 4 trades','5 or more trades'];
  const adSpends     = ['Under $5k/mo','$5k to $15k/mo','$15k to $40k/mo','$40k+/mo'];
  const challengeOpts = [
    'Attribution & tracking','Targeting existing customers',
    'Channel ROI clarity','Cross-selling between trades',
    'Geographic waste','CRM data enrichment',
  ];
  const sourceOpts = [
    { key:'referral',  label:'Referral',       icon:'👋', color:'#fb923c' },
    { key:'google',    label:'Google Search',   icon:'🔍', color:'#4a7fca' },
    { key:'ai',        label:'AI assistant',    icon:'🤖', color:'#22c55e' },
    { key:'linkedin',  label:'LinkedIn',        icon:'💼', color:'#0ea5e9' },
    { key:'instagram', label:'Instagram / Ads', icon:'📸', color:'#c026d3' },
    { key:'email',     label:'Email',           icon:'✉️', color:'#f59e0b' },
    { key:'other',     label:'Other',           icon:'↗',  color:'#94a3b8' },
  ];

  // Capture attribution on mount — UTM params + referrer
  const [attribution] = React.useState(() => {
    try {
      const p = new URLSearchParams(window.location.search);
      return {
        utm_source:   p.get('utm_source')   || '',
        utm_medium:   p.get('utm_medium')   || '',
        utm_campaign: p.get('utm_campaign') || '',
        utm_term:     p.get('utm_term')     || '',
        utm_content:  p.get('utm_content')  || '',
        referrer:     document.referrer     || 'direct',
        landing_url:  window.location.href,
      };
    } catch { return {}; }
  });

  const [form, setForm] = React.useState({
    company:'', name:'', email:'', phone:'',
    revenue:'', usesST:'', divisions:'', adSpend:'',
    challenges:[], source:'', referredBy:'', otherSource:'',
  });
  const [stepIdx, setStepIdx]   = React.useState(0);
  const [animKey, setAnimKey]   = React.useState(0);
  const [submitted, setSubmitted] = React.useState(false);
  const [submitting, setSubmitting] = React.useState(false);
  const [submitError, setSubmitError] = React.useState(null);

  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
  const toggleChallenge = (c) => setForm(f => ({
    ...f,
    challenges: f.challenges.includes(c) ? f.challenges.filter(x => x !== c) : [...f.challenges, c],
  }));

  // Steps ordered for sales-process flow: qualify and demonstrate knowledge first, collect contact after they're engaged
  const STEP_DEFS = [
    { id:'st',         q:"Do you run ServiceTitan?",             sub:"Our entire system is built on ST data. Scoring, enrichment, activation — all of it reads from ST directly. It's the only platform we've built for.",
      type:'pills-single',key:'usesST',
      opts:[{v:'yes',label:'Yes, we run ServiceTitan'},{v:'no',label:'Different platform'}] },
    { id:'revenue',    q:"What's your annual revenue?",          sub:"Helps us size the opportunity in your database before we run the analysis.",
      type:'pills-single',key:'revenue',
      opts:revenues.map(v=>({v,label:v})), condition: f => f.usesST === 'yes' },
    { id:'trades',     q:"How many service divisions are you running?",  sub:"Multi-trade operators get more from the system — cross-division scoring is one of the biggest revenue levers we pull.",
      type:'pills-single',key:'divisions',
      opts:tradeCounts.map(v=>({v,label:v})), condition: f => f.usesST === 'yes' },
    { id:'spend',      q:"Monthly paid ad spend across all channels?",   sub:"Meta, Yelp, Google, LSA — total. This tells us how much CPL improvement is worth in dollar terms before we even open your accounts.",
      type:'pills-single',key:'adSpend',
      opts:adSpends.map(v=>({v,label:v})), condition: f => f.usesST === 'yes' },
    { id:'challenges', q:"Where's the biggest gap right now?",   sub:"Pick everything that applies — we'll look at all of it in the audit.",
      type:'pills-multi', key:'challenges',
      opts:challengeOpts.map(v=>({v,label:v})) },
    { id:'company',    q:"What's your company called?",          sub:"We'll put this on the brief.",
      type:'text',        key:'company',   placeholder:"Acme Home Services" },
    { id:'contact',    q:"And who are we talking to?",           sub:"Name and a good number for the follow-up call.",
      type:'contact' },
    { id:'email',      q:"Best email to send the brief to?",     sub:"Back within 48 hours. No spam, no drip sequence — just the brief.",
      type:'email',       key:'email',     placeholder:"you@yourcompany.com" },
    { id:'source',     q:"How did you find us?",                 sub:"Helps us know which channels are working.",
      type:'source' },
  ];

  const visibleSteps = STEP_DEFS.filter(s => !s.condition || s.condition(form));
  const totalSteps   = visibleSteps.length;
  const safeIdx      = Math.min(stepIdx, totalSteps - 1);
  const cs           = visibleSteps[safeIdx];

  // Clamp index when conditional steps appear/disappear
  React.useEffect(() => {
    const vs = STEP_DEFS.filter(s => !s.condition || s.condition(form));
    setStepIdx(i => Math.min(i, Math.max(0, vs.length - 1)));
  }, [form.usesST]);

  const canContinue = () => {
    if (!cs) return false;
    if (cs.id === 'company')  return form.company.trim().length > 0;
    if (cs.id === 'contact')  return form.name.trim().length > 0;
    if (cs.id === 'email')    return /\S+@\S+/.test(form.email);
    if (cs.id === 'st')       return !!form.usesST;
    return true; // optional steps — always skippable
  };
  const canSubmit = form.company.trim() && form.name.trim() && /\S+@\S+/.test(form.email);

  const goNext = () => {
    if (safeIdx < totalSteps - 1) { setStepIdx(safeIdx + 1); setAnimKey(k => k + 1); }
    else if (canSubmit) handleSubmit();
  };

  const handleSubmit = async () => {
    if (!canSubmit || submitting) return;
    setSubmitting(true);
    setSubmitError(null);
    try {
      await fetch('/api/submit', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ...form, attribution }),
      });
      setSubmitted(true);
    } catch (e) {
      // Still mark submitted — don't gate the UX on network errors
      setSubmitted(true);
    } finally {
      setSubmitting(false);
    }
  };
  const goBack = () => {
    if (safeIdx > 0) { setStepIdx(safeIdx - 1); setAnimKey(k => k + 1); }
  };

  const handleOverlay = (e) => { if (e.target === e.currentTarget) onClose(); };
  const isLastStep = safeIdx === totalSteps - 1;
  const progress   = ((safeIdx + 1) / totalSteps) * 100;

  const renderStepContent = () => {
    if (!cs) return null;

    if (cs.type === 'text' || cs.type === 'email') {
      const acMap = { company: 'organization', email: 'email' };
      return (
        <input className="tf-input-large"
          type={cs.type === 'email' ? 'email' : 'text'}
          placeholder={cs.placeholder}
          value={form[cs.key] || ''}
          autoComplete={acMap[cs.key] || 'off'}
          onChange={e => set(cs.key, e.target.value)}
          onKeyDown={e => { if (e.key === 'Enter' && canContinue()) goNext(); }}
          autoFocus />
      );
    }

    if (cs.type === 'contact') {
      return (
        <div className="tf-contact-row">
          <input className="tf-input-large" type="text" placeholder="First Last"
            value={form.name} onChange={e => set('name', e.target.value)}
            autoComplete="name"
            onKeyDown={e => { if (e.key === 'Enter' && form.name) goNext(); }} autoFocus />
          <input className="tf-input-large" type="tel" placeholder="(555) 555-5555"
            value={form.phone} onChange={e => set('phone', e.target.value)}
            autoComplete="tel"
            onKeyDown={e => { if (e.key === 'Enter' && form.name) goNext(); }} />
        </div>
      );
    }

    if (cs.type === 'pills-single') {
      const handleSingleSelect = (v) => {
        set(cs.key, v);
        // Auto-advance after a short delay so the selection registers visually
        // Skip auto-advance for ST=no so they can read the not-fit message
        if (!(cs.id === 'st' && v === 'no')) {
          setTimeout(() => goNext(), 280);
        }
      };
      return (
        <>
          <div className="tf-pills">
            {cs.opts.map(opt => (
              <button type="button" key={opt.v}
                className={"tf-pill" + (form[cs.key] === opt.v ? ' selected' : '')}
                onClick={() => handleSingleSelect(opt.v)}>
                {opt.label}
              </button>
            ))}
          </div>
          {cs.id === 'st' && form.usesST === 'no' && (
            <div className="tf-not-fit">Our system reads directly from ServiceTitan data. We are not the right fit for other platforms right now — but that may change. Happy to stay in touch.</div>
          )}
        </>
      );
    }

    if (cs.type === 'pills-multi') {
      return (
        <div className="tf-pills">
          {cs.opts.map(opt => (
            <button type="button" key={opt.v}
              className={"tf-pill" + (form.challenges.includes(opt.v) ? ' selected' : '')}
              onClick={() => toggleChallenge(opt.v)}>
              {form.challenges.includes(opt.v) && <span className="tf-pill-check">✓ </span>}
              {opt.label}
            </button>
          ))}
        </div>
      );
    }

    if (cs.type === 'source') {
      return (
        <>
          <div className="tf-pills">
            {sourceOpts.map(s => (
              <button type="button" key={s.key}
                className={"tf-pill" + (form.source === s.key ? ' selected' : '')}
                style={form.source === s.key ? {borderColor:s.color,background:s.color+'18',color:s.color} : {}}
                onClick={() => set('source', form.source === s.key ? '' : s.key)}>
                <span className="tf-pill-icon">{s.icon}</span>{s.label}
              </button>
            ))}
          </div>
          {form.source === 'referral' && (
            <div className="tf-referral-field">
              <input className="tf-input-large" type="text" placeholder="Who referred you?"
                value={form.referredBy} onChange={e => set('referredBy', e.target.value)} autoFocus />
            </div>
          )}
          {form.source === 'other' && (
            <div className="tf-referral-field">
              <input className="tf-input-large" type="text" placeholder="Where did you come across us?"
                value={form.otherSource} onChange={e => set('otherSource', e.target.value)} autoFocus />
            </div>
          )}
        </>
      );
    }

    return null;
  };

  return (
    <div className="adash-popup-overlay" onClick={handleOverlay}>
      <div className="adash-popup adash-popup-light tf-popup">
        <button className="adash-popup-close adash-popup-close-light" onClick={onClose} aria-label="Close">×</button>

        {submitted ? (
          <div className="adash-form-success">
            <div className="adash-form-success-check" style={{background:'rgba(76,47,201,0.10)',borderColor:'rgba(76,47,201,0.28)',color:'var(--site-purple)'}}>✓</div>
            <div className="adash-form-success-title" style={{color:'var(--site-ink)'}}>Audit request received.</div>
            <div className="adash-form-success-sub" style={{color:'rgba(26,24,20,0.55)'}}>We connect your ServiceTitan and ad accounts ourselves and run the full analysis. Brief back within 48 hours. No card, no commitment, no template.</div>
            <button className="adash-form-submit" style={{width:'auto',padding:'12px 32px',marginTop:28}} onClick={onClose}>Close</button>
          </div>
        ) : (
          <div className="tf-wrap">
            <div className="tf-progress-bar"><div className="tf-progress-fill" style={{width:progress+'%'}}></div></div>
            <div className="tf-step-count">{safeIdx + 1} of {totalSteps}</div>
            <div className="tf-step-body" key={animKey}>
              <div className="tf-question">{cs.q}</div>
              {cs.sub && <div className="tf-sub">{cs.sub}</div>}
              <div className="tf-content">{renderStepContent()}</div>
            </div>
            <div className="tf-nav">
              {safeIdx > 0 && (
                <button type="button" className="tf-btn-back" onClick={goBack}>← Back</button>
              )}
              <button type="button"
                className={"tf-btn-next" + ((isLastStep ? (!canSubmit || submitting) : !canContinue()) ? ' disabled' : '')}
                disabled={isLastStep ? (!canSubmit || submitting) : !canContinue()}
                onClick={goNext}>
                {isLastStep ? (submitting ? 'Sending...' : 'Request the audit →') : 'Continue →'}
              </button>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

function App() {
  const [auditOpen, setAuditOpen] = React.useState(false);
  const [auditInView, setAuditInView] = React.useState(false);
  const openAudit = () => setAuditOpen(true);

  React.useEffect(() => {
    const el = document.getElementById('audit');
    if (!el) return;
    const obs = new IntersectionObserver(([e]) => {
      setAuditInView(e.isIntersecting || e.boundingClientRect.top < window.innerHeight * 0.5);
    }, { threshold: 0, rootMargin: '0px 0px -10% 0px' });
    obs.observe(el);
    return () => obs.disconnect();
  }, []);

  return (
    <React.Fragment>
      <NavBar onRequestAudit={openAudit} auditInView={auditInView}/>
      <Hero/>
      <NetworkSection/>
      <GapSection/>
      <SystemSection/>
      <ProduceSection/>
      <EngageSection/>
      <ICPSection/>
      <AuditSection onRequestAudit={openAudit}/>
      <DeskSection onRequestAudit={openAudit}/>
      <Footer/>
      {auditOpen && <AuditPopup onClose={() => setAuditOpen(false)}/>}
    </React.Fragment>
  );
}

window.App = App;
