/* ============ THE BID ENGINE — conversational quiz (/bid) ============
   Spec: CaliforniaJanitor_Conversion_Brief §3. One question at a time,
   answers build a visible bid card, contact captured LAST. Route A shows a
   preliminary band; Route B never shows a dollar figure. All hour math
   carries a 1.15 safety factor and a hard $1,000 floor. */

/* ---- Funnel analytics ----
   Fires to GA4 (when GA_MEASUREMENT_ID is set in shell.html) and always to
   our own first-party /api/track endpoint so the funnel is measurable
   before/without GA4. */
function getSid() {
  try {
    let sid = localStorage.getItem('lamj-sid');
    if (!sid) {
      sid = Math.random().toString(36).slice(2) + Date.now().toString(36);
      localStorage.setItem('lamj-sid', sid);
    }
    return sid;
  } catch (e) { return 'anon'; }
}
function track(event, params = {}) {
  // Mirror every funnel event to Vercel Web Analytics as a custom event.
  try {
    if (window.va && event !== 'page_view') window.va('event', { name: event, data: params });
  } catch (e) {}
  try {
    if (window.gtag && window.GA_MEASUREMENT_ID) window.gtag('event', event, params);
  } catch (e) {}
  try {
    const payload = JSON.stringify({
      event, params,
      path: window.location.pathname,
      sid: getSid()
    });
    if (navigator.sendBeacon) {
      navigator.sendBeacon('/api/track', new Blob([payload], { type: 'application/json' }));
    } else {
      fetch('/api/track', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: payload, keepalive: true });
    }
  } catch (e) {}
}
window.track = track;

/* ---- Quiz definition (§3.2 exact flow) ---- */
const BID_Q1 = [
  { v: 'office', label: 'Office', flag: false },
  { v: 'retail', label: 'Retail / showroom', flag: false },
  { v: 'medical', label: 'Medical / dental', flag: true },
  { v: 'school', label: 'School / daycare', flag: true },
  { v: 'gym', label: 'Gym / fitness', flag: true },
  { v: 'industrial', label: 'Warehouse / industrial', flag: true },
  { v: 'hoa', label: 'Apartment / HOA common areas', flag: true },
  { v: 'restaurant', label: 'Restaurant / hospitality', flag: true },
  { v: 'other', label: 'Something else', flag: true },
];
const BID_Q2 = [
  { v: 'under2500', label: 'Under 2,500 sq ft', mid: 1750, flag: false },
  { v: '2500-5000', label: '2,500–5,000 sq ft', mid: 3750, flag: false },
  { v: '5000-8000', label: '5,000–8,000 sq ft', mid: 6500, flag: false },
  { v: '8000-15000', label: '8,000–15,000 sq ft', mid: null, flag: true },
  { v: '15000plus', label: '15,000+ sq ft', mid: null, flag: true },
  { v: 'not_sure', label: 'Not sure', mid: null, flag: true },
];
const BID_Q3 = [
  { v: '1x', label: '1x per week', visits: 1, flag: false },
  { v: '2x', label: '2x per week', visits: 2, flag: false },
  { v: '3x', label: '3x per week', visits: 3, flag: false },
  { v: '5x', label: '5x per week', visits: 5, flag: false },
  { v: 'porter', label: 'Daytime porter', visits: null, flag: true },
  { v: 'not_sure', label: 'Not sure yet', visits: null, flag: true },
];
const BID_Q4 = [
  { v: 'medical_waste', label: 'Exam rooms / medical waste' },
  { v: 'food_service', label: 'Food service area' },
  { v: 'gym_floors', label: 'Gym floors & showers' },
  { v: 'high_expectations', label: 'Tenants with high expectations' },
  { v: 'machine_floors', label: 'Floors needing machine work (strip & wax, polish)' },
  { v: 'post_construction', label: 'Post-construction mess' },
];

/* Segment-aware proof lines (§4.3) */
const PROOF_BY_TYPE = {
  medical: 'We clean medical and clinical spaces to protocol — discreet, checklist-driven, after-hours.',
  school: 'We serve multiple LA charter school campuses, CDC-aligned.',
  industrial: 'From food-processing floors to fabrication shops, our crews run site-specific safety protocols.',
  office: '100s of LA businesses have trusted our crews for 10+ years.',
  retail: '100s of LA businesses have trusted our crews for 10+ years.',
  gym: 'Locker rooms, showers, and equipment floors — disinfection-first, on a schedule that never touches your open hours.',
  hoa: 'Lobbies, mail rooms, gyms, and corridors your residents actually notice — cared for like a front desk would.',
  restaurant: 'Front-of-house polish and back-of-house degrease — crews who know a health inspection is always coming.',
  other: '100s of LA businesses have trusted our crews for 10+ years.',
};

/* ---- The napkin engine (Zoe, Jul 2026) ----
   One estimating mechanic for the whole site (quiz, pricing slider, emails).
   Never shown on screen — only its results are.
     hours/visit = sqft / 3,000 sq ft/hr × 1.15 safety × space-density
     monthly $   = hours × visits/wk × 4.33 × rate × 1.10 uplift
     range       = ±10% around that, rounded to $50, floor $1,000
   Rate is $45/hr for medical work, $35/hr otherwise. */
const SIZE_MID = { under2500: 1750, '2500-5000': 3750, '5000-8000': 6500, '8000-15000': 11500, '15000plus': 17500, not_sure: null };
const DENSITY = { office: 1.0, retail: 1.0, medical: 1.35, school: 1.2, gym: 1.25, industrial: 1.0, hoa: 1.0, restaurant: 1.3, other: 1.1 };
const UPLIFT = 1.10;
const r50 = (n) => Math.round(n / 50) * 50;

function napkinEstimate({ sqft, spaceType = 'office', visits = 5, specials = [], porter = false }) {
  const rate = (spaceType === 'medical' || specials.includes('medical_waste')) ? 45 : 35;
  let hours;
  if (porter) {
    hours = 4 * 5 * 4.33;                     // day porter: ~4 hrs/day, Mon–Fri
  } else {
    if (!sqft) return null;
    const density = DENSITY[spaceType] || 1.0;
    hours = (sqft / 3000) * 1.15 * density * visits * 4.33;
  }
  const est = hours * rate * UPLIFT;
  const low = Math.max(1000, r50(est * 0.9));
  const high = Math.max(low, r50(est * 1.1));
  return { hours: Math.round(hours), rate, low, high };
}

function computeBid(ans) {
  const q1 = BID_Q1.find(o => o.v === ans.q1) || {};
  const q2 = BID_Q2.find(o => o.v === ans.q2) || {};
  const q3 = BID_Q3.find(o => o.v === ans.q3) || {};
  const flagged = !!(q1.flag || q2.flag || q3.flag || (ans.q4 && ans.q4.length > 0));
  const route = flagged ? 'B' : 'A';

  const specials = ans.q4 || [];
  // Strip & wax and post-construction are project work — estimated at the
  // walkthrough, excluded from the recurring number.
  const projectWork = specials.includes('machine_floors') || specials.includes('post_construction');

  const sqft = SIZE_MID[ans.q2] ?? null;
  const porter = ans.q3 === 'porter';
  // "Not sure yet" on frequency → suggest one from size, same as the slider.
  const visits = q3.visits || (sqft ? (sqft <= 2500 ? 2 : sqft <= 5000 ? 3 : 5) : null);

  // Sizeless (and not porter) → we genuinely can't math it; walkthrough first.
  if (!sqft && !porter) return { route, kind: 'walkthrough', projectWork };

  const est = napkinEstimate({ sqft, spaceType: ans.q1 || 'office', visits, specials, porter });
  if (!est) return { route, kind: 'walkthrough', projectWork };

  // Unflagged office/retail that fits the flat package stays the flagship.
  if (!flagged && !porter && est.hours <= 28) {
    return { route, kind: 'package', hours: est.hours, low: 1000, high: 1000, projectWork };
  }

  // 15,000+ sq ft: the range is a floor — actual size may be well past the midpoint.
  const minOnly = ans.q2 === '15000plus';
  return { route, kind: 'range', hours: est.hours, low: est.low, high: est.high, minOnly, porter, projectWork };
}

/* LA County ZIPs run 90001–91899, OC 92602–92899. Coarse but correct at the
   edges we care about; out-of-range still gets a "let's talk" path. */
function zipInArea(zip) {
  const n = parseInt(zip, 10);
  if (isNaN(n)) return false;
  return (n >= 90001 && n <= 91899) || (n >= 92602 && n <= 92899);
}

const money = (n) => '$' + n.toLocaleString('en-US');

/* ---- The bid card that visibly builds as answers stack up ---- */
function BidCard({ ans, step }) {
  const chips = [];
  const q1 = BID_Q1.find(o => o.v === ans.q1);
  const q2 = BID_Q2.find(o => o.v === ans.q2);
  const q3 = BID_Q3.find(o => o.v === ans.q3);
  if (q1) chips.push({ k: 'Space', v: q1.label });
  if (q2) chips.push({ k: 'Size', v: q2.label });
  if (q3) chips.push({ k: 'Frequency', v: q3.label });
  if (ans.q4 && ans.q4.length) chips.push({ k: 'Special', v: `${ans.q4.length} item${ans.q4.length > 1 ? 's' : ''}` });
  if (ans.q6) chips.push({ k: 'ZIP', v: ans.q6 });
  if (!chips.length) return null;
  return (
    <div className="bid-card" aria-label="Your bid so far">
      <div className="bid-card-title">Your price, so far</div>
      <div className="bid-card-chips">
        {chips.map((c, i) => (
          <span key={i} className="bid-chip"><em>{c.k}</em>{c.v}</span>
        ))}
      </div>
    </div>
  );
}

/* ---- Result screens (§3.4) ---- */
function BidResultA({ bid, ans, emailStatus }) {
  const proof = PROOF_BY_TYPE[ans.q1] || PROOF_BY_TYPE.other;
  const isPackage = true;
  return (
    <div className="bid-result" role="status">
      <div className="label-mono" style={{color: 'var(--success)'}}>GOOD NEWS — YOUR SPACE FITS THE ESSENTIAL CLEAN</div>
      <div className="bid-price">$1,000/month flat*</div>
      <div className="bid-price-meta">28 service hours &nbsp;·&nbsp; everything included</div>
      <p className="bid-result-body">
        We estimate your space needs ~{bid.hours} hours/month. The package includes 28 — supplies, backup coverage, and quarterly business reviews included.
      </p>
      <p className="bid-disclaimer">*Preliminary estimate — confirmed at your free 20-minute walkthrough, subject to scope.</p>
      <div className="bid-next-step">
        <strong>What happens next:</strong> a real person reaches out within business hours to schedule your free 20-minute walkthrough — in person or by video, whichever is easier for you. That locks in your final number.
      </div>
      <p className="bid-email-note">
        {emailStatus === 'sent'
          ? <>✓ Your bid card is on its way to <strong>{ans.email}</strong>.</>
          : <>Your estimate is saved under <strong>{ans.email}</strong>.</>}
        {' '}Need us sooner? Call <a href="tel:3232647800">(323) 264-7800</a>.
      </p>
      <div className="bid-proof">
        <p>{proof}</p>
      </div>
    </div>
  );
}

function BidResultRange({ bid, ans, emailStatus }) {
  const proof = PROOF_BY_TYPE[ans.q1] || PROOF_BY_TYPE.other;
  const isMedicalRate = ans.q1 === 'medical' || (ans.q4 || []).includes('medical_waste');
  const priceLabel = bid.low === bid.high ? `${money(bid.low)}/mo` : `${money(bid.low)}–${money(bid.high)}${bid.minOnly ? '+' : ''}/mo`;
  return (
    <div className="bid-result" role="status">
      <div className="label-mono" style={{color: 'var(--accent)'}}>YOUR PRELIMINARY ESTIMATE</div>
      <div className="bid-price">{priceLabel}*</div>
      <div className="bid-price-meta">
        ~{bid.hours} service hrs/month{bid.porter ? ' · day porter, ~4 hrs/day Mon–Fri' : ''}{isMedicalRate ? ' · clinical-grade protocols' : ''}
      </div>
      <p className="bid-result-body">
        Based on what you described{bid.minOnly ? ' — and since you’re past 15,000 sq ft, treat this as the starting point' : ''}. Supplies, backup coverage, and quarterly business reviews included.
        {bid.projectWork && <> Strip &amp; wax / post-construction work is project-priced separately at your walkthrough.</>}
      </p>
      <p className="bid-disclaimer">*Preliminary estimate — confirmed at your free 20-minute walkthrough, subject to scope.</p>
      <div className="bid-next-step">
        <strong>What happens next:</strong> a real person reaches out within business hours to schedule your free 20-minute walkthrough — in person or by video, whichever is easier. That locks in your final number.
      </div>
      <p className="bid-email-note">
        {emailStatus === 'sent'
          ? <>✓ Your estimate is on its way to <strong>{ans.email}</strong>.</>
          : <>Your estimate is saved under <strong>{ans.email}</strong>.</>}
        {' '}Need us sooner? Call <a href="tel:3232647800">(323) 264-7800</a>.
      </p>
      <div className="bid-proof">
        <p>{proof}</p>
      </div>
    </div>
  );
}

function BidResultWalkthrough({ ans, bid }) {
  const proof = PROOF_BY_TYPE[ans.q1] || PROOF_BY_TYPE.other;
  return (
    <div className="bid-result" role="status">
      <div className="label-mono" style={{color: 'var(--accent)'}}>ONE NUMBER WE CAN’T GUESS</div>
      <h2 className="bid-result-h">Your square footage decides the price.</h2>
      <p className="bid-result-body">
        You told us you’re not sure of the size — that’s the one input we won’t guess at. The walkthrough takes 20 minutes and pins it down exactly.
      </p>
      <div className="bid-next-step">
        <strong>What happens next:</strong> a real person reaches out within business hours to schedule your free walkthrough — in person, or a 10-minute video call with your phone camera on the space. Your estimate follows the same day.
      </div>
      <p className="bid-email-note">Saved under <strong>{ans.email}</strong>. Need us sooner? Call <a href="tel:3232647800">(323) 264-7800</a>.</p>
      <div className="bid-proof">
        <p>{proof}</p>
      </div>
    </div>
  );
}

function BidResultOutOfArea({ ans }) {
  return (
    <div className="bid-result" role="status">
      <div className="label-mono" style={{color: 'var(--warn)'}}>JUST OUTSIDE OUR USUAL ROUTES</div>
      <h2 className="bid-result-h">We may still cover you — let’s talk.</h2>
      <p className="bid-result-body">ZIP {ans.q6} is outside the LA + Orange County routes our crews run daily. Depending on the building and schedule, we can sometimes make it work — a real person will look at it and reply either way.</p>
      <div className="hero-cta-row" style={{justifyContent: 'center'}}>
        <a href="tel:3232647800" className="btn btn-primary btn-xl">Call (323) 264-7800</a>
        <a href="mailto:hello@californiajanitor.com" className="btn btn-secondary btn-xl">Email hello@</a>
      </div>
    </div>
  );
}

/* ---- The quiz itself ---- */
function BidQuiz({ embedded = false, source = 'bid' }) {
  const [stepIdx, setStepIdx] = useState(0);
  const [ans, setAns] = useState({ q1: null, q2: null, q3: null, q4: [], q5: null, q6: '', name: '', company: '', phone: '', email: '', sms: true });
  const [done, setDone] = useState(false);
  const [submitting, setSubmitting] = useState(false);
  const [emailStatus, setEmailStatus] = useState('');
  const startedRef = useRef(false);
  const panelRef = useRef(null);

  const STEPS = ['q1', 'q2', 'q3', 'q4', 'q6', 'q7'];
  const step = STEPS[stepIdx];
  const progress = Math.round(((done ? STEPS.length : stepIdx) / STEPS.length) * 100);

  const markStart = () => {
    if (!startedRef.current) {
      startedRef.current = true;
      track('quiz_start', { source });
    }
  };

  const next = () => {
    setStepIdx(i => Math.min(i + 1, STEPS.length - 1));
    if (panelRef.current) panelRef.current.focus();
  };
  const back = () => setStepIdx(i => Math.max(i - 1, 0));
  // Advance to the question AFTER `k` — absolute, not relative, so a fast
  // double-tap on an answer can never skip a question.
  const advanceFrom = (k) => {
    setStepIdx(i => Math.min(Math.max(i, STEPS.indexOf(k) + 1), STEPS.length - 1));
    if (panelRef.current) panelRef.current.focus();
  };

  const pick = (k, v, auto = true) => {
    markStart();
    setAns(a => ({ ...a, [k]: v }));
    if (auto) setTimeout(() => advanceFrom(k), 220);   // brief beat so the selection is seen
  };
  const toggleQ4 = (v) => {
    markStart();
    setAns(a => ({ ...a, q4: a.q4.includes(v) ? a.q4.filter(x => x !== v) : [...a.q4, v] }));
  };

  const bid = useMemo(() => computeBid(ans), [ans.q1, ans.q2, ans.q3, ans.q4]);
  const inArea = zipInArea(ans.q6);

  const isEmailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test((ans.email || '').trim());
  const isPhoneValid = (ans.phone || '').replace(/\D/g, '').length >= 10;
  const canSubmit = (ans.name || '').trim().length >= 2 && isEmailValid && isPhoneValid;

  const submit = async (e) => {
    if (e) e.preventDefault();
    if (!canSubmit || submitting) return;
    setSubmitting(true);
    const route = inArea ? bid.route : 'OOA';
    track('quiz_complete', { route, source });
    track('quiz_route', { route, kind: bid.kind || null, source });
    try {
      const res = await fetch('/api/bid', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          answers: ans,
          route,
          estimate: inArea && (bid.kind === 'package' || bid.kind === 'range')
            ? { hours: bid.hours, low: bid.low, high: bid.high, kind: bid.kind, minOnly: !!bid.minOnly, porter: !!bid.porter, projectWork: !!bid.projectWork }
            : null,
          source,
          sid: getSid()
        })
      });
      const json = await res.json().catch(() => ({}));
      if (json && json.leadEmail && json.leadEmail.ok) setEmailStatus('sent');
    } catch (err) {}
    setSubmitting(false);
    setDone(true);
    if (panelRef.current) panelRef.current.focus();
  };

  /* ---- render ---- */
  if (done) {
    return (
      <div className={`bid-shell ${embedded ? 'bid-embedded' : ''}`}>
        <div className="bid-progress" aria-hidden="true"><span style={{width: '100%'}}/></div>
        <div className="bid-panel" ref={panelRef} tabIndex={-1}>
          {!inArea ? <BidResultOutOfArea ans={ans}/>
            : bid.kind === 'package' ? <BidResultA bid={bid} ans={ans} emailStatus={emailStatus}/>
            : bid.kind === 'range' ? <BidResultRange bid={bid} ans={ans} emailStatus={emailStatus}/>
            : <BidResultWalkthrough ans={ans} bid={bid}/>}
        </div>
      </div>
    );
  }

  const OptionGrid = ({ options, k }) => (
    <div className="bid-options" role="radiogroup">
      {options.map(o => (
        <button key={o.v} type="button"
          className={`bid-option ${ans[k] === o.v ? 'on' : ''}`}
          onClick={() => pick(k, o.v)}
          role="radio" aria-checked={ans[k] === o.v}>
          {o.label}
        </button>
      ))}
    </div>
  );

  return (
    <div className={`bid-shell ${embedded ? 'bid-embedded' : ''}`}>
      <div className="bid-progress" aria-label={`Question ${stepIdx + 1} of ${STEPS.length}`}>
        <span style={{width: progress + '%'}}/>
      </div>

      <div className="bid-panel" ref={panelRef} tabIndex={-1}>
        <div className="bid-step-count label-mono">QUESTION {stepIdx + 1} OF {STEPS.length} · UNDER 1 MIN</div>

        {step === 'q1' && (<>
          <h2 className="bid-q">What kind of space are we cleaning?</h2>
          <OptionGrid options={BID_Q1} k="q1"/>
        </>)}

        {step === 'q2' && (<>
          <h2 className="bid-q">Roughly how big is the cleanable space?</h2>
          <p className="bid-helper">Not sure? A typical office suite is 150–250 sq ft per employee.</p>
          <OptionGrid options={BID_Q2} k="q2"/>
        </>)}

        {step === 'q3' && (<>
          <h2 className="bid-q">How often should we come?</h2>
          <OptionGrid options={BID_Q3} k="q3"/>
        </>)}

        {step === 'q4' && (<>
          <h2 className="bid-q">Anything special about the space?</h2>
          <p className="bid-helper">Check all that apply — or none.</p>
          <div className="bid-options" role="group">
            {BID_Q4.map(o => (
              <button key={o.v} type="button"
                className={`bid-option bid-option-multi ${ans.q4.includes(o.v) ? 'on' : ''}`}
                onClick={() => toggleQ4(o.v)}
                aria-pressed={ans.q4.includes(o.v)}>
                <span className="bid-option-box" aria-hidden="true">{ans.q4.includes(o.v) ? '✓' : ''}</span>
                {o.label}
              </button>
            ))}
            <button type="button"
              className={`bid-option ${ans.q4.length === 0 && ans._q4None ? 'on' : ''}`}
              onClick={() => { markStart(); setAns(a => ({ ...a, q4: [], _q4None: true })); setTimeout(() => advanceFrom('q4'), 220); }}>
              None of these
            </button>
          </div>
          {ans.q4.length > 0 && (
            <div className="bid-continue-row">
              <button type="button" className="btn btn-accent btn-lg" onClick={() => advanceFrom('q4')}>Continue →</button>
            </div>
          )}
        </>)}

        {step === 'q6' && (<>
          <h2 className="bid-q">What’s the ZIP code of the space?</h2>
          <div className="bid-zip-row">
            <input
              type="text" inputMode="numeric" maxLength={5} autoFocus
              className="q-input q-input-lg bid-zip"
              placeholder="90040"
              value={ans.q6}
              onChange={e => { markStart(); setAns(a => ({ ...a, q6: e.target.value.replace(/\D/g, '').slice(0, 5) })); }}
              onKeyDown={e => { if (e.key === 'Enter' && ans.q6.length === 5) advanceFrom('q6'); }}
              aria-label="ZIP code"
            />
            <button type="button" className="btn btn-accent btn-lg" disabled={ans.q6.length !== 5} onClick={() => advanceFrom('q6')}>Continue →</button>
          </div>
          {ans.q6.length === 5 && !zipInArea(ans.q6) && (
            <p className="bid-helper" style={{marginTop: 16}}>That’s outside our usual LA + OC routes — finish up and we’ll see if we can make it work.</p>
          )}
        </>)}

        {step === 'q7' && (<>
          <h2 className="bid-q">Where should we send your pricing?</h2>
          <form className="bid-contact" onSubmit={submit}>
            <div className="quote-row-2">
              <div className="field">
                <label htmlFor="bid-name" className="q-label">Name <span className="req">*</span></label>
                <input id="bid-name" className="q-input q-input-lg" type="text" autoComplete="name" required
                  value={ans.name} onChange={e => setAns(a => ({ ...a, name: e.target.value }))}/>
              </div>
              <div className="field">
                <label htmlFor="bid-company" className="q-label">Company <span className="q-optional">optional</span></label>
                <input id="bid-company" className="q-input q-input-lg" type="text" autoComplete="organization"
                  value={ans.company} onChange={e => setAns(a => ({ ...a, company: e.target.value }))}/>
              </div>
              <div className="field">
                <label htmlFor="bid-phone" className="q-label">Mobile <span className="req">*</span></label>
                <input id="bid-phone" className="q-input q-input-lg" type="tel" autoComplete="tel" required
                  placeholder="(323) 555-0100"
                  value={ans.phone} onChange={e => setAns(a => ({ ...a, phone: e.target.value }))}/>
              </div>
              <div className="field">
                <label htmlFor="bid-email" className="q-label">Email <span className="req">*</span></label>
                <input id="bid-email" className="q-input q-input-lg" type="email" autoComplete="email" required
                  placeholder="you@company.com"
                  value={ans.email} onChange={e => setAns(a => ({ ...a, email: e.target.value }))}/>
              </div>
            </div>
            <label className="bid-sms">
              <input type="checkbox" checked={ans.sms} onChange={e => setAns(a => ({ ...a, sms: e.target.checked }))}/>
              <span>Text me my estimate + reminders</span>
            </label>
            <button type="submit" className="btn btn-accent btn-xl" disabled={!canSubmit || submitting} style={{width: '100%'}}>
              {submitting ? 'Building your price…' : 'Show me my price →'}
            </button>
            <p className="bid-disclaimer" style={{textAlign: 'center'}}>Preliminary estimate, subject to scope — confirmed at your free walkthrough. No spam, ever.</p>
          </form>
        </>)}

        {stepIdx > 0 && step !== 'q7' && (
          <button type="button" className="bid-back" onClick={back}>← Back</button>
        )}
        {step === 'q7' && (
          <button type="button" className="bid-back" onClick={back}>← Back</button>
        )}
      </div>

      <BidCard ans={ans} step={stepIdx}/>
    </div>
  );
}

/* ---- /bid page ---- */
function BidPage() {
  useDocumentHead({
    title: 'Custom Pricing in Under a Minute — Commercial Cleaning | LA Maintenance & Janitorial',
    description: 'Answer 6 quick questions and get preliminary commercial cleaning pricing in under a minute — or a free walkthrough for bigger spaces. Public rates: $35/hr standard, $45/hr specialty, $1,000/mo flat package. LA & Orange County.',
    canonical: 'https://californiajanitor.com/bid'
  });
  return (
    <div className="bid-page">
      <section className="page-header no-border" style={{paddingBottom: 0}}>
        <div className="container" style={{textAlign: 'center'}}>
          <div className="label-mono" style={{marginBottom: 16}}>CUSTOM PRICING · UNDER 1 MIN</div>
          <h1 style={{marginBottom: 12}}>Watch your price build itself.</h1>
          <p className="lead" style={{maxWidth: 640, marginInline: 'auto'}}>Six quick questions and you’ll see a real preliminary number for your space — then a real person confirms it at your free 20-minute walkthrough.</p>
        </div>
      </section>
      <section style={{paddingTop: 32}}>
        <div className="container">
          <BidQuiz/>
        </div>
      </section>
      <NumbersStrip/>
    </div>
  );
}

/* ---- Paid landing pages (/lp/*) — quiz-first, zero nav ---- */
const LP_CONTENT = {
  'essential-clean': {
    kicker: 'THE ESSENTIAL CLEAN · LA + ORANGE COUNTY',
    h1: <>$1,000/mo flat — see if your space fits.</>,
    sub: '28 service hours a month, supplies, backup coverage, and quarterly business reviews included. One price, no decoding. Get your price below.',
  },
  'commercial-cleaning': {
    kicker: 'COMMERCIAL CLEANING · LA + ORANGE COUNTY',
    h1: <>A janitorial bid that shows its math.</>,
    sub: 'Public rates — $35/hr standard, $45/hr specialty — and an under-a-minute quiz that either gives you a real preliminary number or sets up the 20-minute walkthrough that will.',
  },
};

function LandingPage({ slug }) {
  const lp = LP_CONTENT[slug];
  useDocumentHead({
    title: 'Custom Pricing — Commercial Cleaning | LA Maintenance & Janitorial',
    description: 'Get preliminary commercial cleaning pricing in under a minute.',
    canonical: `https://californiajanitor.com/lp/${slug}`
  });
  useEffect(() => {
    // Paid LPs stay out of the index.
    let m = document.head.querySelector('meta[name="robots"]');
    if (!m) { m = document.createElement('meta'); m.setAttribute('name', 'robots'); document.head.appendChild(m); }
    m.setAttribute('content', 'noindex,nofollow');
    return () => { m.setAttribute('content', 'index,follow'); };
  }, []);
  if (!lp) return <NotFound/>;
  return (
    <div className="lp-page">
      <header className="lp-header">
        <BrandMark size={16}/>
        <a href="tel:3232647800" className="lp-phone">(323) 264-7800</a>
      </header>
      <section className="lp-hero">
        <div className="container" style={{textAlign: 'center'}}>
          <div className="label-mono" style={{marginBottom: 16}}>{lp.kicker}</div>
          <h1 style={{marginBottom: 12}}>{lp.h1}</h1>
          <p className="lead" style={{maxWidth: 620, marginInline: 'auto'}}>{lp.sub}</p>
        </div>
      </section>
      <section style={{paddingTop: 8}}>
        <div className="container">
          <BidQuiz embedded source={`lp-${slug}`}/>
        </div>
      </section>
      <NumbersStrip/>
      <footer className="lp-footer">
        <p>LA Maintenance &amp; Janitorial Services · Commerce, CA · <a href="tel:3232647800">(323) 264-7800</a> · <a href="mailto:hello@californiajanitor.com">hello@californiajanitor.com</a></p>
        <p className="bid-disclaimer">Every figure shown is a preliminary estimate, subject to scope — confirmed at your free walkthrough.</p>
      </footer>
    </div>
  );
}

Object.assign(window, { BidQuiz, BidPage, LandingPage, track, computeBid, napkinEstimate, zipInArea });
