// Site chrome — Header, Footer, page hero, floating CTAs, chat bubble, quote modal.
// Used by every page.

// === Web3Forms submission ===
// Each lead form posts to Web3Forms with its own access key (public/frontend-safe).
// Submissions are delivered to the inbox tied to each key in the Web3Forms dashboard.
const WEB3FORMS = {
  quote:   '2567e442-f1c2-410f-b193-6964b14fc1fc', // Services/Destinations pop-up
  contact: 'a81167b0-fb1f-4619-8066-e5339a24b1bd', // Contact — customer enquiry
  partner: '9fbd34e4-135e-4337-86f8-fbd452652ec1', // Services — partner registration
};
async function submitWeb3Form(accessKey, fields) {
  const res = await fetch('https://api.web3forms.com/submit', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
    body: JSON.stringify({ access_key: accessKey, ...fields }),
  });
  const json = await res.json().catch(() => ({}));
  if (!res.ok || !json.success) throw new Error(json.message || 'Submission failed');
  return json;
}
window.WEB3FORMS = WEB3FORMS;
window.submitWeb3Form = submitWeb3Form;

// === hCaptcha (for forms with captcha enabled in the Web3Forms dashboard) ===
// Web3Forms' shared sitekey. The widget is rendered explicitly (not via auto-detect)
// so it works for forms inside modals that mount after the API script loads.
const HCAPTCHA_SITEKEY = '50b2fe65-b00b-4b9e-ad62-3ba471098be2';
function useHCaptcha(ref, active = true) {
  const widgetId = React.useRef(null);
  React.useEffect(() => {
    if (!active) return;
    let cancelled = false;
    let tries = 0;
    const render = () => {
      if (cancelled) return;
      const el = ref.current;
      if (window.hcaptcha && el && widgetId.current === null) {
        try { widgetId.current = window.hcaptcha.render(el, { sitekey: HCAPTCHA_SITEKEY }); } catch (e) {}
        return;
      }
      if (tries++ < 100) setTimeout(render, 100); // wait for the async api.js (up to ~10s)
    };
    render();
    return () => {
      cancelled = true;
      if (window.hcaptcha && widgetId.current !== null) {
        try { window.hcaptcha.remove(widgetId.current); } catch (e) {}
        widgetId.current = null;
      }
    };
  }, [active]);
  const getToken = () => {
    if (window.hcaptcha && widgetId.current !== null) {
      try { return window.hcaptcha.getResponse(widgetId.current) || ''; } catch (e) { return ''; }
    }
    return '';
  };
  const reset = () => {
    if (window.hcaptcha && widgetId.current !== null) {
      try { window.hcaptcha.reset(widgetId.current); } catch (e) {}
    }
  };
  return { getToken, reset };
}
window.useHCaptcha = useHCaptcha;

const NAV_LINKS = [
  { href: 'index.html',        label: 'Home' },
  { href: 'about.html',        label: 'About' },
  { href: 'services.html',     label: 'Services' },
  { href: 'destinations.html', label: 'Destinations' },
  { href: 'blog.html',         label: 'Blog' },
  { href: 'contact.html',      label: 'Contact' },
];

// === Sticky header (transparent over hero, solid white on scroll) ===
function Header({ active, overHero = true, onPlan }) {
  const bp = useBP();
  const mob = bp === 'mobile';
  const [scrolled, setScrolled] = React.useState(false);
  const [menuOpen, setMenuOpen] = React.useState(false);
  React.useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 60);
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, []);
  // Lock body scroll while the mobile menu is open
  React.useEffect(() => {
    if (menuOpen) {
      const prev = document.body.style.overflow;
      document.body.style.overflow = 'hidden';
      return () => { document.body.style.overflow = prev; };
    }
  }, [menuOpen]);
  // Close the menu if we grow past mobile
  React.useEffect(() => { if (!mob) setMenuOpen(false); }, [mob]);

  const transparent = overHero && !scrolled && !menuOpen;
  const ease = 'cubic-bezier(.2,.7,.2,1)';
  const hPad = mob ? (scrolled ? '10px 18px' : '14px 18px')
             : bp === 'tablet' ? (scrolled ? '8px 32px' : '18px 32px')
             : (scrolled ? '8px 56px' : '22px 56px');
  return (
    <React.Fragment>
    <header style={{
      position: 'fixed', top: 0, left: 0, right: 0, zIndex: 30,
      background: transparent ? 'transparent' : 'rgba(255,255,255,0.96)',
      backdropFilter: transparent ? 'none' : 'saturate(160%) blur(10px)',
      borderBottom: transparent ? '1px solid transparent' : `1px solid ${TNF.lineSoft}`,
      boxShadow: scrolled && !menuOpen ? '0 8px 24px rgba(11,38,48,0.06)' : 'none',
      transition: `background .35s ${ease}, box-shadow .35s ${ease}, border-color .35s ${ease}, padding .35s ${ease}`,
      padding: hPad,
      willChange: 'padding, background',
    }}>
      <div style={{
        display: 'flex', alignItems: 'center', maxWidth: 1440, margin: '0 auto',
        transition: `transform .35s ${ease}`,
        transform: !mob && scrolled ? 'scale(0.94)' : 'scale(1)',
        transformOrigin: 'left center',
        position: 'relative', zIndex: 2,
      }}>
        <div style={{ transition: `transform .35s ${ease}`, transformOrigin: 'left center' }}>
          <Logo inverted={transparent} size={mob ? 30 : (transparent ? 36 : 32)}/>
        </div>

        {!mob && (
          <nav style={{
            display: 'flex', gap: scrolled ? 22 : 28, marginLeft: scrolled ? 44 : 56,
            fontSize: 14, fontWeight: 500,
            transition: `gap .35s ${ease}, margin-left .35s ${ease}`,
          }}>
            {NAV_LINKS.map(l => {
              const isActive = active === l.label;
              return (
                <a key={l.href} href={l.href} style={{
                  color: transparent ? '#fff' : (isActive ? TNF.blueDk : TNF.ink),
                  textDecoration: 'none',
                  opacity: transparent ? 0.92 : 1,
                  borderBottom: isActive ? `2px solid ${TNF.green}` : '2px solid transparent',
                  paddingBottom: 4,
                  transition: `color .25s ${ease}, opacity .25s ${ease}`,
                }}>{l.label}</a>
              );
            })}
          </nav>
        )}

        <div style={{ marginLeft: 'auto' }}/>

        {mob && (
          <button
            onClick={() => setMenuOpen(o => !o)}
            aria-label={menuOpen ? 'Close menu' : 'Open menu'}
            aria-expanded={menuOpen}
            style={{
              width: 44, height: 44, borderRadius: 12, border: 'none',
              background: transparent ? 'rgba(255,255,255,0.14)' : TNF.cream,
              color: transparent ? '#fff' : TNF.blueDk,
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              cursor: 'pointer', flex: '0 0 auto',
            }}
          >
            {menuOpen ? <Icon.x width={24} height={24}/> : <Icon.menu width={24} height={24}/>}
          </button>
        )}
      </div>
    </header>

      {/* Mobile slide-down menu — kept OUTSIDE <header> on purpose: the header's
          backdrop-filter makes it a containing block for fixed descendants, which
          would trap this overlay inside the 72px bar instead of the viewport. */}
      {mob && (
        <div style={{
          position: 'fixed', left: 0, right: 0, top: 0, bottom: 0,
          zIndex: 29,
          pointerEvents: menuOpen ? 'auto' : 'none',
        }}>
          {/* scrim */}
          <div onClick={() => setMenuOpen(false)} style={{
            position: 'absolute', inset: 0, background: 'rgba(7,42,53,0.45)',
            backdropFilter: 'blur(2px)',
            opacity: menuOpen ? 1 : 0, transition: 'opacity .3s ease',
          }}/>
          {/* panel */}
          <nav style={{
            position: 'absolute', top: 0, left: 0, right: 0,
            background: '#fff', padding: '84px 20px 24px',
            boxShadow: '0 16px 40px rgba(11,38,48,0.16)',
            borderBottomLeftRadius: 24, borderBottomRightRadius: 24,
            transform: menuOpen ? 'translateY(0)' : 'translateY(-110%)',
            transition: `transform .38s ${ease}`,
            display: 'flex', flexDirection: 'column', gap: 4,
          }}>
            {NAV_LINKS.map(l => {
              const isActive = active === l.label;
              return (
                <a key={l.href} href={l.href} onClick={() => setMenuOpen(false)} style={{
                  display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                  minHeight: 52, padding: '0 12px', borderRadius: 12,
                  textDecoration: 'none', fontSize: 18, fontWeight: 500,
                  color: isActive ? TNF.blueDk : TNF.ink,
                  background: isActive ? `${TNF.green}1a` : 'transparent',
                }}>
                  {l.label}
                  {isActive && <span style={{ width: 8, height: 8, borderRadius: '50%', background: TNF.green }}/>}
                </a>
              );
            })}
            <button onClick={() => { setMenuOpen(false); window.scrollToQuickEnquiry && window.scrollToQuickEnquiry(); }}
              style={{ ...btn(TNF.blueDk), width: '100%', justifyContent: 'center', minHeight: 52, marginTop: 12 }}>
              Plan with us <Icon.arrow width={16} height={16}/>
            </button>
          </nav>
        </div>
      )}
    </React.Fragment>
  );
}
window.Header = Header;

// === Page hero (dark image hero used by interior pages) ===
function PageHero({ eyebrow, title, lede, photo, height = 460 }) {
  const bp = useBP();
  const mob = bp === 'mobile';
  const h = bpv(bp, Math.max(380, Math.round(height * 0.74)), Math.round(height * 0.9), height);
  return (
    <section style={{
      position: 'relative', height: h, color: '#fff', overflow: 'hidden',
      paddingTop: mob ? 64 : 80, // space for fixed header
    }}>
      <Img photo={photo} eager style={{ position: 'absolute', inset: 0, width: '100%', height: '100%' }}/>
      <div style={{
        position: 'absolute', inset: 0,
        background: 'linear-gradient(135deg, rgba(7,42,53,0.85) 0%, rgba(15,74,92,0.6) 50%, rgba(27,100,120,0.45) 100%)',
      }}/>
      <div style={{
        position: 'relative', maxWidth: 1440, margin: '0 auto',
        padding: bpv(bp, '40px 20px 0', '50px 36px 0', '60px 56px 0'), height: '100%',
        display: 'flex', flexDirection: 'column', justifyContent: 'center',
      }}>
        {eyebrow && (
          <div style={{
            fontSize: mob ? 11 : 12, letterSpacing: '0.3em', textTransform: 'uppercase',
            color: TNF.green, marginBottom: mob ? 14 : 20, display: 'flex', alignItems: 'center', gap: 14,
          }}>
            <span style={{ width: 32, height: 1, background: TNF.green }}/>
            {eyebrow}
          </div>
        )}
        <h1 style={{
          fontFamily: 'var(--tnf-display)', fontSize: 'clamp(34px, 7vw, 80px)', lineHeight: 1.04,
          fontWeight: 800, letterSpacing: '-0.025em', margin: 0,
          maxWidth: 900, textWrap: 'balance',
        }}>{title}</h1>
        {lede && (
          <p style={{
            fontSize: mob ? 16 : 18, lineHeight: 1.55, marginTop: mob ? 16 : 24, maxWidth: 600,
            opacity: 0.88, marginBottom: 0,
          }}>{lede}</p>
        )}
      </div>
    </section>
  );
}
window.PageHero = PageHero;

// === Footer ===
function Footer() {
  const bp = useBP();
  const mob = bp === 'mobile';
  return (
    <footer style={{ background: TNF.blueXdk, color: 'rgba(255,255,255,0.7)', padding: bpv(bp, '52px 20px 28px', '64px 36px 30px', '80px 56px 32px') }}>
      <div style={{ maxWidth: 1440, margin: '0 auto' }}>
        <div style={{ display: 'grid', gridTemplateColumns: bpv(bp, '1fr', '1fr 1fr', '1.4fr 1fr 1fr 1fr'), gap: bpv(bp, 36, 40, 60), paddingBottom: mob ? 40 : 60, borderBottom: '1px solid rgba(255,255,255,0.12)' }}>
          <div>
            <Logo inverted size={36}/>
            <p style={{ marginTop: 20, fontSize: 14, lineHeight: 1.6, maxWidth: 360 }}>
              Built on family, passion, and deep industry expertise. We make every trip seamless, memorable, and personal.
            </p>
            <div style={{ display: 'flex', gap: 10, marginTop: 20 }}>
              {[
                { ico: Icon.whatsapp, href: 'https://wa.me/916238983511', label: 'WhatsApp' },
                { ico: Icon.instagram, href: 'https://www.instagram.com/tripsnflys?igsh=MW9nY2RocXN1M2h4ZQ==', label: 'Instagram' },
                { ico: Icon.facebook, href: 'https://www.facebook.com/share/1HoLacn1uk/?mibextid=wwXIfr', label: 'Facebook' },
              ].map((s, i) => (
                <a key={i} href={s.href} aria-label={s.label} target="_blank" rel="noopener noreferrer" style={{
                  width: 44, height: 44, borderRadius: 10,
                  background: 'rgba(255,255,255,0.08)',
                  border: '1px solid rgba(255,255,255,0.12)',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  color: '#fff', textDecoration: 'none',
                }}>
                  <s.ico width={16} height={16}/>
                </a>
              ))}
            </div>
          </div>
          <FooterCol title="Explore" items={[
            { label: 'Home',         href: 'index.html' },
            { label: 'About us',     href: 'about.html' },
            { label: 'Services',     href: 'services.html' },
            { label: 'Destinations', href: 'destinations.html' },
            { label: 'Blog',         href: 'blog.html' },
            { label: 'Contact',      href: 'contact.html' },
          ]}/>
          <FooterCol title="Services" items={SERVICES.map(s => ({ label: s.label, href: 'services.html' }))}/>
          <div>
            <div style={{ color: '#fff', fontSize: 12, fontWeight: 700, marginBottom: 18, letterSpacing: '0.16em', textTransform: 'uppercase' }}>Visit us</div>
            <div style={{ fontSize: 14, lineHeight: 1.7 }}>
              First Floor Vattuthottam Complex<br/>
              Ernakulam Road, Piravom<br/>
              Ernakulam 686664, Kerala
            </div>
            <div style={{ marginTop: 16, fontSize: 14, color: '#fff', lineHeight: 1.8 }}>
              <a href="tel:+916238983511" style={{ display: 'inline-flex', alignItems: 'center', minHeight: mob ? 44 : 'auto', textDecoration: 'none', color: 'inherit' }}>+91 6238 983 511</a><br/>
              <a href="mailto:info@tripsnflys.com" style={{ display: 'inline-flex', alignItems: 'center', minHeight: mob ? 44 : 'auto', textDecoration: 'none', color: 'inherit' }}>info@tripsnflys.com</a>
            </div>
          </div>
        </div>
        <div style={{ paddingTop: 24, display: 'flex', alignItems: 'center', justifyContent: 'space-between', fontSize: 12, flexDirection: mob ? 'column' : 'row', gap: mob ? 14 : 0, textAlign: mob ? 'center' : 'left' }}>
          <div>© 2026 Trips N Flys Travel Solutions — All rights reserved.</div>
        </div>
      </div>
    </footer>
  );
}
window.Footer = Footer;
function FooterCol({ title, items }) {
  const mob = useBP() === 'mobile';
  return (
    <div>
      <div style={{ color: '#fff', fontSize: 12, fontWeight: 700, marginBottom: mob ? 10 : 18, letterSpacing: '0.16em', textTransform: 'uppercase' }}>{title}</div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: mob ? 2 : 10, fontSize: 14 }}>
        {items.map(i => <a key={i.label} href={i.href} style={{ color: 'inherit', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', minHeight: mob ? 44 : 'auto', width: 'fit-content' }}>{i.label}</a>)}
      </div>
    </div>
  );
}

// === Floating CTAs (chat / call) ===
function FloatingCtas() {
  return (
    <div style={{
      position: 'fixed', bottom: 24, right: 24, display: 'flex',
      flexDirection: 'column', gap: 12, zIndex: 25,
    }}>
      <a href="tel:+916238983511" title="Call us" style={floatBtn(TNF.blueDk, '#fff')}>
        <Icon.phone width={22} height={22}/>
      </a>
    </div>
  );
}
window.FloatingCtas = FloatingCtas;
function floatBtn(bg, fg) {
  return {
    width: 52, height: 52, borderRadius: '50%', border: 'none',
    background: bg, color: fg, display: 'flex', alignItems: 'center',
    justifyContent: 'center', cursor: 'pointer',
    boxShadow: '0 8px 24px rgba(0,0,0,0.18)', textDecoration: 'none',
  };
}

// === Live chat bubble ===
function ChatBubble({ open, onClose, accent = TNF.blueDk }) {
  const [msgs, setMsgs] = React.useState([
    { who: 'them', text: 'Hi! I’m Anjali from Trips N Flys ✈️ How can I help plan your trip?' },
  ]);
  const [v, setV] = React.useState('');
  const send = () => {
    const text = InputGuard.text(v, 500).trim();
    if (!text) return;
    setMsgs(m => [...m, { who: 'me', text }]);
    setV('');
    setTimeout(() => setMsgs(m => [...m, { who: 'them', text: 'Lovely — let me check a few options and get back to you within an hour. Could you share your travel dates?' }]), 900);
  };
  if (!open) return null;
  return (
    <div style={{
      position: 'fixed', bottom: 100, right: 24, width: 340, maxHeight: 480,
      background: '#fff', borderRadius: 16, boxShadow: '0 16px 48px rgba(0,0,0,0.22)',
      display: 'flex', flexDirection: 'column', overflow: 'hidden', zIndex: 30,
    }}>
      <div style={{ background: accent, color: '#fff', padding: '14px 16px', display: 'flex', alignItems: 'center', gap: 10 }}>
        <Avatar name="Anjali Menon" size={36}/>
        <div style={{ flex: 1 }}>
          <div style={{ fontWeight: 600, fontSize: 14 }}>Anjali Menon</div>
          <div style={{ fontSize: 11, opacity: 0.8 }}>Travel consultant · Replies in ~10 min</div>
        </div>
        <button onClick={onClose} style={{ background: 'transparent', border: 'none', color: '#fff', cursor: 'pointer' }}>
          <Icon.x width={18} height={18}/>
        </button>
      </div>
      <div style={{ flex: 1, padding: 12, overflow: 'auto', background: TNF.cream, display: 'flex', flexDirection: 'column', gap: 8 }}>
        {msgs.map((m, i) => (
          <div key={i} style={{
            alignSelf: m.who === 'me' ? 'flex-end' : 'flex-start',
            background: m.who === 'me' ? accent : '#fff',
            color: m.who === 'me' ? '#fff' : TNF.ink,
            padding: '8px 12px', borderRadius: 14, fontSize: 13, maxWidth: '80%',
            boxShadow: m.who === 'me' ? 'none' : '0 1px 2px rgba(0,0,0,0.06)',
          }}>{m.text}</div>
        ))}
      </div>
      <div style={{ display: 'flex', borderTop: `1px solid ${TNF.line}` }}>
        <input value={v} onChange={e => setV(e.target.value)} onKeyDown={e => e.key === 'Enter' && send()}
          placeholder="Type a message..." maxLength={500}
          style={{ flex: 1, border: 'none', padding: '12px 14px', fontSize: 13, outline: 'none', background: 'transparent', fontFamily: 'inherit' }}/>
        <button onClick={send} style={{ background: 'transparent', border: 'none', color: accent, padding: '0 16px', cursor: 'pointer' }}>
          <Icon.send width={18} height={18}/>
        </button>
      </div>
    </div>
  );
}
window.ChatBubble = ChatBubble;

// === Quote modal (multi-step) ===
function QuoteModal({ open, onClose, accent = TNF.blueDk, initialDestination = '', initial = {} }) {
  const bp = useBP();
  const mob = bp === 'mobile';
  const [step, setStep] = React.useState(0);
  const [errors, setErrors] = React.useState({});
  const [nudge, setNudge] = React.useState(0);
  const [sending, setSending] = React.useState(false);
  const [data, setData] = React.useState({
    type: '', destination: '', from: '', to: '', adults: 2, kids: 0,
    budget: '', name: '', email: '', countryCode: 'IN', phone: '',
  });
  const [notice, setNotice] = React.useState('');
  // Abuse deterrents: honeypot + min fill time + max 4 submits / minute.
  const guard = useFormGuard('quote', { maxPerWindow: 4, windowMs: 60000, minFillMs: 1200 });
  React.useEffect(() => {
    if (open) {
      guard.reset(); setNotice(''); setSending(false);
      // Prefill from the launching context — a destination card, or the home quick-enquiry form.
      setData(d => ({
        ...d,
        destination: initialDestination || d.destination,
        name: initial.name || d.name,
        email: initial.email || d.email,
        countryCode: initial.countryCode || d.countryCode,
        phone: initial.phone || d.phone,
      }));
    } else {
      setTimeout(() => { setStep(0); setErrors({}); setData(d => ({ ...d, name: '', email: '', countryCode: 'IN', phone: '' })); }, 300);
    }
  }, [open, initialDestination]);
  // Updating a field clears any pending error on it. Sanitize by field type.
  const update = (k, v) => {
    let cv = v;
    if (k === 'name') cv = InputGuard.line(v, 80);
    else if (k === 'email') cv = InputGuard.email(v);
    else if (k === 'phone') cv = InputGuard.phone(v);
    else if (k === 'countryCode') cv = InputGuard.oneOf(v, COUNTRY_CODES.map(c => c.c), 'IN');
    else if (k === 'destination') cv = InputGuard.oneOf(v, [...(window.DESTINATIONS || []).map(d => d.name), 'Multiple / not sure yet'], '');
    else if (k === 'type') cv = InputGuard.oneOf(v, ['leisure', 'honeymoon', 'family', 'corporate'], '');
    else if (k === 'budget') cv = InputGuard.oneOf(v, ['Under 50k', '50k – 1L', '1L – 2L', '2L – 5L', '5L+'], '');
    else if (k === 'adults') cv = InputGuard.int(v, 1, 30, 1);
    else if (k === 'kids') cv = InputGuard.int(v, 0, 20, 0);
    else if (k === 'from' || k === 'to') cv = /^\d{0,4}(-\d{0,2}){0,2}$/.test(String(v)) ? v : '';
    setData(d => ({ ...d, [k]: cv }));
    setErrors(e => { if (!e[k]) return e; const n = { ...e }; delete n[k]; return n; });
  };
  // Which required fields on the current step are still empty/invalid (strict checks).
  const missingOn = (s) => {
    const m = {};
    if (s === 0 && !data.type) m.type = true;
    if (s === 1) {
      if (!data.destination) m.destination = true;
      if (!data.from) m.from = true;
    }
    if (s === 3) {
      if (!isFilledName(data.name)) m.name = true;
      if (!isEmail(data.email)) m.email = true;
      if (!isPhone(data.phone)) m.phone = true;
    }
    return m;
  };
  if (!open) return null;
  const next = async () => {
    const m = missingOn(step);
    if (Object.keys(m).length) { setErrors(m); setNudge(n => n + 1); return; }
    // Final step = the actual submission → apply abuse deterrents, then send.
    if (step === 3) {
      const g = guard.evaluate();
      if (!g.ok) {
        if (g.bot) { setErrors({}); setStep(4); return; }   // honeypot → soak silently
        if (g.reason === 'too-fast') { setNotice('That was quick — take a moment, then send.'); setNudge(n => n + 1); return; }
        if (g.reason === 'rate') { setNotice(`Too many requests. Please wait ${Math.ceil((g.retryInMs || 0) / 1000)}s.`); setNudge(n => n + 1); return; }
        return;
      }
      setErrors({}); setNotice(''); setSending(true);
      const dial = (COUNTRY_CODES.find(c => c.c === data.countryCode) || {}).code || '';
      try {
        await submitWeb3Form(WEB3FORMS.quote, {
          subject: `New quote request — ${data.name || 'Website visitor'}`,
          from_name: 'Trips N Flys Website',
          form_name: 'Services/Destinations Quote',
          trip_type: data.type,
          destination: data.destination,
          departure: data.from,
          return_date: data.to || '—',
          adults: data.adults,
          children: data.kids,
          budget_per_person: data.budget || '—',
          name: data.name,
          email: data.email,
          phone: `${dial} ${data.phone}`.trim(),
        });
      } catch (err) {
        setSending(false);
        setNotice('Could not send right now. Please try again, or call/WhatsApp us.');
        setNudge(n => n + 1);
        return;
      }
      guard.accept();
      setSending(false);
      setErrors({}); setNotice('');
      setStep(4);
      return;
    }
    setErrors({}); setNotice('');
    setStep(s => Math.min(s + 1, 4));
  };
  const back = () => { setErrors({}); setNotice(''); setStep(s => Math.max(s - 1, 0)); };
  const hasErr = Object.keys(errors).length > 0;

  return (
    <div style={{
      position: 'fixed', inset: 0, background: 'rgba(11, 38, 48, 0.6)',
      display: 'flex', alignItems: mob ? 'flex-end' : 'center', justifyContent: 'center', zIndex: 40,
      backdropFilter: 'blur(4px)', padding: mob ? 0 : 16,
    }} onClick={onClose}>
      <div style={{
        width: 580, maxWidth: mob ? '100%' : '92%', background: '#fff',
        borderRadius: mob ? '20px 20px 0 0' : 20, maxHeight: mob ? '92vh' : '90vh',
        boxShadow: '0 24px 64px rgba(0,0,0,0.3)', overflow: 'hidden',
        display: 'flex', flexDirection: 'column',
      }} onClick={e => e.stopPropagation()}>
        <div style={{ padding: mob ? '16px 20px' : '20px 28px', borderBottom: `1px solid ${TNF.line}`, display: 'flex', alignItems: 'center', flex: '0 0 auto' }}>
          <div style={{ fontWeight: 600, fontSize: 16, color: TNF.ink }}>Plan your trip</div>
          <div style={{ marginLeft: 'auto', display: 'flex', gap: 6 }}>
            {[0,1,2,3].map(k => (
              <div key={k} style={{
                width: k <= step ? 28 : 8, height: 6, borderRadius: 3,
                background: k <= step ? accent : TNF.line, transition: 'all .25s',
              }}/>
            ))}
          </div>
          <button onClick={onClose} style={{ background: 'transparent', border: 'none', cursor: 'pointer', marginLeft: 16, color: TNF.inkSoft, width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            <Icon.x width={20} height={20}/>
          </button>
        </div>
        <div style={{ padding: mob ? 20 : 28, minHeight: mob ? 0 : 320, overflowY: 'auto', flex: 1 }}>
          <HoneypotField value={guard.hp} onChange={guard.setHp}/>
          {step === 0 && (
            <div>
              <h3 style={qH}>What kind of trip are you dreaming of?</h3>
              <div className={errors.type ? 'tnf-shake' : ''} key={'type-' + nudge} style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginTop: 20 }}>
                {[
                  { v: 'leisure',   l: 'Leisure / Holiday', i: Icon.globe },
                  { v: 'honeymoon', l: 'Honeymoon',         i: Icon.star },
                  { v: 'family',    l: 'Family vacation',   i: Icon.user },
                  { v: 'corporate', l: 'Corporate travel',  i: Icon.briefcase },
                ].map(o => (
                  <button key={o.v} onClick={() => { update('type', o.v); setErrors({}); setStep(s => Math.min(s + 1, 4)); }}
                    style={qChoice(data.type === o.v, accent)}>
                    <o.i width={22} height={22}/>
                    <span>{o.l}</span>
                  </button>
                ))}
              </div>
            </div>
          )}
          {step === 1 && (
            <div>
              <h3 style={qH}>Where would you like to go?</h3>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 14, marginTop: 20 }}>
                <Field label="Destination" required error={errors.destination} key={'dest-' + nudge}>
                  <div style={{ position: 'relative' }}>
                    <select value={data.destination} onChange={e => update('destination', e.target.value)}
                      style={{ ...qInp, appearance: 'none', WebkitAppearance: 'none', MozAppearance: 'none', paddingRight: 38, cursor: 'pointer', color: data.destination ? TNF.ink : TNF.inkSoft, borderColor: errors.destination ? QERR : TNF.line }}>
                      <option value="" disabled>Select a destination…</option>
                      {(window.DESTINATIONS || []).map(d => (
                        <option key={d.id} value={d.name}>{d.name}</option>
                      ))}
                      <option value="Multiple / not sure yet">Multiple / not sure yet</option>
                    </select>
                    <div style={{ position: 'absolute', right: 14, top: '50%', transform: 'translateY(-50%) rotate(90deg)', pointerEvents: 'none', color: TNF.inkSoft, display: 'flex' }}>
                      <Icon.chevR width={16} height={16}/>
                    </div>
                  </div>
                </Field>
                <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
                  <Field label="Departure" required error={errors.from} key={'from-' + nudge}><input type="date" value={data.from} onChange={e => update('from', e.target.value)} style={{ ...qInp, borderColor: errors.from ? QERR : TNF.line }}/></Field>
                  <Field label="Return"><input type="date" value={data.to} onChange={e => update('to', e.target.value)} style={qInp}/></Field>
                </div>
              </div>
            </div>
          )}
          {step === 2 && (
            <div>
              <h3 style={qH}>Who's traveling, and what's the budget?</h3>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 14, marginTop: 20 }}>
                <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
                  <Field label="Adults"><Stepper value={data.adults} onChange={v => update('adults', v)} min={1}/></Field>
                  <Field label="Children"><Stepper value={data.kids} onChange={v => update('kids', v)} min={0}/></Field>
                </div>
                <Field label="Approximate budget per person (₹)">
                  <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
                    {['Under 50k', '50k – 1L', '1L – 2L', '2L – 5L', '5L+'].map(b => (
                      <button key={b} onClick={() => update('budget', b)}
                        style={qChip(data.budget === b, accent)}>{b}</button>
                    ))}
                  </div>
                </Field>
              </div>
            </div>
          )}
          {step === 3 && (
            <div>
              <h3 style={qH}>How can we reach you?</h3>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 14, marginTop: 20 }}>
                <Field label="Full name" required error={errors.name} key={'name-' + nudge}><input value={data.name} onChange={e => update('name', e.target.value)} placeholder="Your name" maxLength={80} autoComplete="name" style={{ ...qInp, borderColor: errors.name ? QERR : TNF.line }}/></Field>
                <Field label="Email" required error={errors.email} key={'email-' + nudge}><input type="email" value={data.email} onChange={e => update('email', e.target.value)} placeholder="you@email.com" maxLength={254} autoComplete="email" style={{ ...qInp, borderColor: errors.email ? QERR : TNF.line }}/></Field>
                <Field label="Phone" required error={errors.phone} key={'phone-' + nudge}>
                  <div style={{ display: 'flex', gap: 8 }}>
                    <CountryCodePicker value={data.countryCode} onChange={v => update('countryCode', v)} triggerStyle={qInp} fixed/>
                    <input value={data.phone} onChange={e => update('phone', e.target.value)} placeholder="Phone number" inputMode="tel" autoComplete="tel" maxLength={24} style={{ ...qInp, flex: 1, borderColor: errors.phone ? QERR : TNF.line }}/>
                  </div>
                </Field>
              </div>
            </div>
          )}
          {step === 4 && (
            <div style={{ textAlign: 'center', padding: '30px 0' }}>
              <div style={{ width: 64, height: 64, borderRadius: '50%', background: TNF.green, color: '#fff', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', marginBottom: 20 }}>
                <Icon.check width={32} height={32}/>
              </div>
              <h3 style={{ ...qH, textAlign: 'center' }}>We’re on it, {data.name.split(' ')[0] || 'traveler'}!</h3>
              <p style={{ color: TNF.inkSoft, fontSize: 14, lineHeight: 1.6, marginTop: 12, maxWidth: 380, marginLeft: 'auto', marginRight: 'auto' }}>
                A travel consultant will reach out within the next 24 business hours.
              </p>
            </div>
          )}
        </div>
        {step < 4 && (
          <div style={{ padding: mob ? '14px 20px' : '16px 28px', borderTop: `1px solid ${TNF.line}`, display: 'flex', alignItems: 'center', gap: 12, flex: '0 0 auto' }}>
            {step > 0 && <button onClick={back} style={qBtnGhost}>Back</button>}
            <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 14 }}>
              {(notice || hasErr) && (
                <span style={{ color: QERR, fontSize: 13, fontWeight: 500, display: 'flex', alignItems: 'center', gap: 6, maxWidth: 240, lineHeight: 1.3 }}>
                  <Icon.alert width={15} height={15}/>
                  {notice || 'Please fill the required fields'}
                </span>
              )}
              <button onClick={next} disabled={sending} style={{ ...btn(accent), opacity: sending ? 0.6 : 1, cursor: sending ? 'not-allowed' : 'pointer' }}>
                {step === 3 ? (sending ? 'Sending…' : 'Send request') : 'Continue'}
              </button>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}
window.QuoteModal = QuoteModal;

const qH = { fontFamily: 'var(--tnf-display)', fontSize: 24, color: TNF.ink, margin: 0, fontWeight: 500, letterSpacing: '-0.01em' };
const QERR = '#D14343';
const qInp = {
  width: '100%', padding: '12px 14px', border: `1px solid ${TNF.line}`,
  borderRadius: 10, fontSize: 14, fontFamily: 'inherit', outline: 'none',
  background: TNF.cream, color: TNF.ink, boxSizing: 'border-box',
};

// === Country code picker (shared by the contact form + the quote pop-up) ===
// Lives here (loaded on every page) so both the Contact page and QuoteModal can use it.
const COUNTRY_CODES = [
  { c: 'IN', f: '🇮🇳', code: '+91',  n: 'India' },
  { c: 'AE', f: '🇦🇪', code: '+971', n: 'United Arab Emirates' },
  { c: 'OM', f: '🇴🇲', code: '+968', n: 'Oman' },
  { c: 'SA', f: '🇸🇦', code: '+966', n: 'Saudi Arabia' },
  { c: 'QA', f: '🇶🇦', code: '+974', n: 'Qatar' },
  { c: 'KW', f: '🇰🇼', code: '+965', n: 'Kuwait' },
  { c: 'BH', f: '🇧🇭', code: '+973', n: 'Bahrain' },
  { c: 'US', f: '🇺🇸', code: '+1',   n: 'United States' },
  { c: 'GB', f: '🇬🇧', code: '+44',  n: 'United Kingdom' },
  { c: 'CA', f: '🇨🇦', code: '+1',   n: 'Canada' },
  { c: 'AU', f: '🇦🇺', code: '+61',  n: 'Australia' },
  { c: 'SG', f: '🇸🇬', code: '+65',  n: 'Singapore' },
  { c: 'MY', f: '🇲🇾', code: '+60',  n: 'Malaysia' },
  { c: 'LK', f: '🇱🇰', code: '+94',  n: 'Sri Lanka' },
  { c: 'BD', f: '🇧🇩', code: '+880', n: 'Bangladesh' },
  { c: 'NP', f: '🇳🇵', code: '+977', n: 'Nepal' },
  { c: 'MV', f: '🇲🇻', code: '+960', n: 'Maldives' },
  { c: 'DE', f: '🇩🇪', code: '+49',  n: 'Germany' },
  { c: 'FR', f: '🇫🇷', code: '+33',  n: 'France' },
  { c: 'IT', f: '🇮🇹', code: '+39',  n: 'Italy' },
  { c: 'ES', f: '🇪🇸', code: '+34',  n: 'Spain' },
  { c: 'CH', f: '🇨🇭', code: '+41',  n: 'Switzerland' },
  { c: 'TR', f: '🇹🇷', code: '+90',  n: 'Turkey' },
  { c: 'EG', f: '🇪🇬', code: '+20',  n: 'Egypt' },
  { c: 'ZA', f: '🇿🇦', code: '+27',  n: 'South Africa' },
  { c: 'TH', f: '🇹🇭', code: '+66',  n: 'Thailand' },
  { c: 'JP', f: '🇯🇵', code: '+81',  n: 'Japan' },
  { c: 'KR', f: '🇰🇷', code: '+82',  n: 'South Korea' },
  { c: 'ID', f: '🇮🇩', code: '+62',  n: 'Indonesia' },
  { c: 'PH', f: '🇵🇭', code: '+63',  n: 'Philippines' },
  { c: 'VN', f: '🇻🇳', code: '+84',  n: 'Vietnam' },
  { c: 'CN', f: '🇨🇳', code: '+86',  n: 'China' },
  { c: 'HK', f: '🇭🇰', code: '+852', n: 'Hong Kong' },
  { c: 'PK', f: '🇵🇰', code: '+92',  n: 'Pakistan' },
  { c: 'NZ', f: '🇳🇿', code: '+64',  n: 'New Zealand' },
  { c: 'IE', f: '🇮🇪', code: '+353', n: 'Ireland' },
  { c: 'NL', f: '🇳🇱', code: '+31',  n: 'Netherlands' },
  { c: 'BE', f: '🇧🇪', code: '+32',  n: 'Belgium' },
  { c: 'AT', f: '🇦🇹', code: '+43',  n: 'Austria' },
  { c: 'PT', f: '🇵🇹', code: '+351', n: 'Portugal' },
  { c: 'GR', f: '🇬🇷', code: '+30',  n: 'Greece' },
  { c: 'SE', f: '🇸🇪', code: '+46',  n: 'Sweden' },
  { c: 'NO', f: '🇳🇴', code: '+47',  n: 'Norway' },
  { c: 'DK', f: '🇩🇰', code: '+45',  n: 'Denmark' },
  { c: 'FI', f: '🇫🇮', code: '+358', n: 'Finland' },
  { c: 'PL', f: '🇵🇱', code: '+48',  n: 'Poland' },
  { c: 'CZ', f: '🇨🇿', code: '+420', n: 'Czechia' },
  { c: 'RU', f: '🇷🇺', code: '+7',   n: 'Russia' },
  { c: 'JO', f: '🇯🇴', code: '+962', n: 'Jordan' },
  { c: 'LB', f: '🇱🇧', code: '+961', n: 'Lebanon' },
  { c: 'IL', f: '🇮🇱', code: '+972', n: 'Israel' },
  { c: 'KE', f: '🇰🇪', code: '+254', n: 'Kenya' },
  { c: 'TZ', f: '🇹🇿', code: '+255', n: 'Tanzania' },
  { c: 'NG', f: '🇳🇬', code: '+234', n: 'Nigeria' },
  { c: 'MA', f: '🇲🇦', code: '+212', n: 'Morocco' },
  { c: 'MU', f: '🇲🇺', code: '+230', n: 'Mauritius' },
  { c: 'BR', f: '🇧🇷', code: '+55',  n: 'Brazil' },
  { c: 'MX', f: '🇲🇽', code: '+52',  n: 'Mexico' },
  { c: 'AR', f: '🇦🇷', code: '+54',  n: 'Argentina' },
  { c: 'BT', f: '🇧🇹', code: '+975', n: 'Bhutan' },
];

// Flag image — uses flagcdn.com for cross-platform rendering (Windows/Linux don't ship flag emoji)
function Flag({ code, size = 22 }) {
  const lc = code.toLowerCase();
  // Prefer a bundled resource when present (offline/standalone build); else use flagcdn.
  const bundled = (typeof window !== 'undefined' && window.__resources && window.__resources['flag_' + lc]) || null;
  const src = bundled || `https://flagcdn.com/w80/${lc}.png`;
  const extra = bundled ? {} : { srcSet: `https://flagcdn.com/w160/${lc}.png 2x` };
  return (
    <img
      src={src}
      {...extra}
      alt={code}
      width={size}
      height={Math.round(size * 0.75)}
      style={{
        display: 'block', flex: '0 0 auto', borderRadius: 3,
        boxShadow: '0 0 0 1px rgba(11,38,48,0.08)', objectFit: 'cover',
      }}
      loading="lazy"
    />
  );
}

// `triggerStyle` lets each form match its own inputs (contact = inpC / white, pop-up = qInp / cream).
// `fixed` renders the dropdown viewport-anchored so it isn't clipped by the modal's scroll/overflow box.
function CountryCodePicker({ value, onChange, triggerStyle, fixed = false }) {
  const [open, setOpen] = React.useState(false);
  const [q, setQ]       = React.useState('');
  const [coords, setCoords] = React.useState(null);
  const ref    = React.useRef(null);
  const btnRef = React.useRef(null);

  const place = React.useCallback(() => {
    const el = btnRef.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    const vw = window.innerWidth, vh = window.innerHeight;
    const width = Math.min(320, vw - 16);
    const left  = Math.max(8, Math.min(r.left, vw - width - 8));
    const estH  = 360;                                  // search box + list, worst case
    const up    = (vh - r.bottom) < estH && r.top > (vh - r.bottom);
    setCoords(up
      ? { left, width, bottom: vh - r.top + 6 }
      : { left, width, top: r.bottom + 6 });
  }, []);

  React.useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    const onEsc = (e) => { if (e.key === 'Escape') setOpen(false); };
    document.addEventListener('mousedown', onDoc);
    document.addEventListener('keydown', onEsc);
    if (fixed) {
      window.addEventListener('scroll', place, true);  // capture: catch scrolls in any container (e.g. modal body)
      window.addEventListener('resize', place);
    }
    return () => {
      document.removeEventListener('mousedown', onDoc);
      document.removeEventListener('keydown', onEsc);
      if (fixed) {
        window.removeEventListener('scroll', place, true);
        window.removeEventListener('resize', place);
      }
    };
  }, [open, fixed, place]);

  const selected = COUNTRY_CODES.find(c => c.c === value) || COUNTRY_CODES[0];
  const filtered = COUNTRY_CODES.filter(c => {
    if (!q) return true;
    const needle = q.toLowerCase();
    return c.n.toLowerCase().includes(needle) || c.code.includes(q) || c.c.toLowerCase().includes(needle);
  });
  const trig = triggerStyle || qInp;
  const panelBase = {
    background: '#fff', borderRadius: 14, boxShadow: '0 20px 48px rgba(11,38,48,0.2)',
    border: `1px solid ${TNF.lineSoft}`, zIndex: 50, overflow: 'hidden', maxWidth: '90vw',
    display: 'flex', flexDirection: 'column',
  };
  const panelStyle = (fixed && coords)
    ? { ...panelBase, position: 'fixed', left: coords.left, width: coords.width,
        ...(coords.top != null ? { top: coords.top } : { bottom: coords.bottom }) }
    : { ...panelBase, position: 'absolute', top: 'calc(100% + 6px)', left: 0, width: 320 };

  return (
    <div ref={ref} style={{ position: 'relative', flex: '0 0 auto' }}>
      <button
        ref={btnRef}
        type="button"
        onClick={() => { const willOpen = !open; if (willOpen && fixed) place(); setOpen(willOpen); }}
        aria-haspopup="listbox"
        aria-expanded={open}
        style={{
          ...trig, display: 'flex', alignItems: 'center', gap: 8,
          cursor: 'pointer', minWidth: 116, justifyContent: 'space-between',
        }}
      >
        <span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <Flag code={selected.c} size={22}/>
          <span style={{ fontWeight: 600, fontSize: 14, color: TNF.ink }}>{selected.code}</span>
        </span>
        <Icon.chevR
          width={14} height={14}
          style={{
            color: TNF.inkSoft,
            transform: open ? 'rotate(-90deg)' : 'rotate(90deg)',
            transition: 'transform .2s',
          }}
        />
      </button>

      {open && (
        <div role="listbox" style={panelStyle}>
          <div style={{ padding: 12, borderBottom: `1px solid ${TNF.lineSoft}` }}>
            <div style={{
              display: 'flex', alignItems: 'center', gap: 8,
              background: TNF.cream, borderRadius: 8, padding: '8px 12px',
              border: `1px solid ${TNF.line}`,
            }}>
              <Icon.search width={14} height={14} style={{ color: TNF.inkSoft }}/>
              <input
                autoFocus
                value={q}
                onChange={e => setQ(InputGuard.search(e.target.value).slice(0, 32))}
                maxLength={32}
                placeholder="Search country or code…"
                style={{
                  flex: 1, border: 'none', outline: 'none', background: 'transparent',
                  fontSize: 13, fontFamily: 'inherit', color: TNF.ink,
                }}
              />
            </div>
          </div>
          <div style={{ overflowY: 'auto', maxHeight: 300 }}>
            {filtered.length === 0 && (
              <div style={{ padding: 20, textAlign: 'center', color: TNF.inkSoft, fontSize: 13 }}>
                No match.
              </div>
            )}
            {filtered.map(c => {
              const isSel = c.c === value;
              return (
                <button
                  key={c.c}
                  type="button"
                  onClick={() => { onChange(c.c); setOpen(false); setQ(''); }}
                  style={{
                    width: '100%', display: 'flex', alignItems: 'center', gap: 12,
                    padding: '10px 14px', border: 'none',
                    background: isSel ? `${TNF.green}1a` : 'transparent',
                    cursor: 'pointer', fontFamily: 'inherit', fontSize: 14, color: TNF.ink,
                    textAlign: 'left', borderLeft: `3px solid ${isSel ? TNF.green : 'transparent'}`,
                  }}
                  onMouseEnter={e => { if (!isSel) e.currentTarget.style.background = TNF.cream; }}
                  onMouseLeave={e => { if (!isSel) e.currentTarget.style.background = 'transparent'; }}
                >
                  <Flag code={c.c} size={24}/>
                  <span style={{ flex: 1, color: TNF.ink }}>{c.n}</span>
                  <span style={{ fontWeight: 600, color: TNF.inkSoft, fontSize: 13 }}>{c.code}</span>
                  {isSel && <Icon.check width={14} height={14} style={{ color: TNF.greenDk }}/>}
                </button>
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
}
// Expose for other page scripts (e.g. the Contact page) — consts don't cross Babel-eval'd files.
window.COUNTRY_CODES = COUNTRY_CODES;
window.CountryCodePicker = CountryCodePicker;
window.Flag = Flag;

const qBtnGhost = {
  background: 'transparent', border: 'none', color: TNF.inkSoft,
  padding: '10px 16px', cursor: 'pointer', fontSize: 14, fontFamily: 'inherit',
};
function qChoice(active, accent) {
  return {
    display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 10,
    padding: '18px 16px', borderRadius: 14,
    border: `1.5px solid ${active ? accent : TNF.line}`,
    background: active ? `${accent}10` : '#fff',
    color: active ? accent : TNF.ink, cursor: 'pointer',
    fontFamily: 'inherit', fontWeight: 500, fontSize: 14,
    textAlign: 'left', transition: 'all .15s',
  };
}
function qChip(active, accent) {
  return {
    padding: '9px 16px', borderRadius: 999, minHeight: 40,
    display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
    border: `1.5px solid ${active ? accent : TNF.line}`,
    background: active ? accent : '#fff', color: active ? '#fff' : TNF.ink,
    cursor: 'pointer', fontFamily: 'inherit', fontSize: 13, fontWeight: 500,
  };
}
function Field({ label, children, required, error }) {
  return (
    <label className={error ? 'tnf-shake' : ''} style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      <span style={{ fontSize: 12, fontWeight: 600, color: error ? QERR : TNF.inkSoft, letterSpacing: '0.04em', textTransform: 'uppercase' }}>
        {label}{required && <span style={{ color: error ? QERR : TNF.blueDk, marginLeft: 3 }}>*</span>}
      </span>
      {children}
      {error && <span style={{ fontSize: 12, color: QERR, fontWeight: 500, textTransform: 'none', letterSpacing: 0 }}>This field is required</span>}
    </label>
  );
}
function Stepper({ value, onChange, min = 0 }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', border: `1px solid ${TNF.line}`, borderRadius: 10, padding: 4, background: TNF.cream, width: 'fit-content' }}>
      <button onClick={() => onChange(Math.max(min, value - 1))} style={stepBtn}>−</button>
      <span style={{ width: 40, textAlign: 'center', fontWeight: 600, color: TNF.ink }}>{value}</span>
      <button onClick={() => onChange(value + 1)} style={stepBtn}>+</button>
    </div>
  );
}
const stepBtn = {
  width: 28, height: 28, borderRadius: 6, border: 'none',
  background: 'transparent', cursor: 'pointer', fontSize: 18, color: TNF.blueDk,
};

// === Testimonials carousel ===
function TestimonialCarousel({ accent = TNF.blueDk, dark = false }) {
  const [i, setI] = React.useState(0);
  const t = TESTIMONIALS[i];
  React.useEffect(() => {
    const id = setInterval(() => setI(x => (x + 1) % TESTIMONIALS.length), 7000);
    return () => clearInterval(id);
  }, []);
  const fg = dark ? '#fff' : TNF.ink;
  const sub = dark ? 'rgba(255,255,255,0.7)' : TNF.inkSoft;
  return (
    <div style={{ position: 'relative' }}>
      <div style={{ display: 'flex', gap: 4, color: TNF.green, marginBottom: 24 }}>
        {Array.from({ length: t.rating }).map((_, k) => <Icon.star key={k} width={18} height={18}/>)}
      </div>
      <p style={{
        fontFamily: 'var(--tnf-display)', fontSize: 28, lineHeight: 1.35,
        color: fg, fontWeight: 400, letterSpacing: '-0.01em', margin: 0,
        textWrap: 'pretty', minHeight: 168,
      }}>“{t.quote}”</p>
      <div style={{ marginTop: 32, display: 'flex', alignItems: 'center', gap: 16 }}>
        <Avatar name={t.name} size={44}/>
        <div>
          <div style={{ color: fg, fontWeight: 600, fontSize: 15 }}>{t.name}</div>
          <div style={{ color: sub, fontSize: 13 }}>{t.role}</div>
        </div>
        <div style={{ marginLeft: 'auto', display: 'flex', gap: 6 }}>
          {TESTIMONIALS.map((_, k) => (
            <button key={k} onClick={() => setI(k)} style={{
              width: k === i ? 28 : 8, height: 8, borderRadius: 4,
              background: k === i ? accent : (dark ? 'rgba(255,255,255,0.3)' : TNF.line),
              border: 'none', cursor: 'pointer', transition: 'all .25s',
            }}/>
          ))}
        </div>
      </div>
    </div>
  );
}
window.TestimonialCarousel = TestimonialCarousel;

// === SiteShell — wraps every page, manages chat + quote state ===
function scrollToQuickEnquiry() {
  const el = document.getElementById('quick-enquiry');
  if (el) {
    const top = el.getBoundingClientRect().top + window.scrollY - 70;
    window.scrollTo({ top, behavior: 'smooth' });
  } else if (window.__openQuote) {
    // Interior pages have no inline enquiry form — open the quote modal instead.
    window.__openQuote();
  } else {
    window.location.href = 'contact.html';
  }
}
window.scrollToQuickEnquiry = scrollToQuickEnquiry;

function SiteShell({ active, overHero = true, children }) {
  const [quoteOpen, setQuoteOpen] = React.useState(false);
  const [quoteDest, setQuoteDest] = React.useState('');
  const [quoteInitial, setQuoteInitial] = React.useState({});
  // expose globally so child sections can trigger them.
  // Accepts a destination string, or an object { destination, name, email, phone } to prefill.
  React.useEffect(() => {
    window.__openQuote = (arg) => {
      if (arg && typeof arg === 'object') {
        setQuoteDest(arg.destination || '');
        setQuoteInitial(arg);
      } else {
        setQuoteDest(typeof arg === 'string' ? arg : '');
        setQuoteInitial({});
      }
      setQuoteOpen(true);
    };
  }, []);
  return (
    <div style={{ background: TNF.cream }}>
      <Header active={active} overHero={overHero} onPlan={scrollToQuickEnquiry}/>
      {children}
      <Footer/>
      <QuoteModal open={quoteOpen} initialDestination={quoteDest} initial={quoteInitial} onClose={() => setQuoteOpen(false)}/>
    </div>
  );
}
window.SiteShell = SiteShell;

// === Section heading helpers ===
function SectionEyebrow({ children, color = TNF.turqDk }) {
  return (
    <div style={{ fontSize: 12, letterSpacing: '0.3em', textTransform: 'uppercase', color, marginBottom: 16, fontWeight: 600 }}>
      {children}
    </div>
  );
}
window.SectionEyebrow = SectionEyebrow;

function H2({ children, style = {} }) {
  const bp = useBP();
  const base = style.fontSize || 56;
  const size = bp === 'mobile' ? Math.max(28, Math.round(base * 0.56))
             : bp === 'tablet' ? Math.round(base * 0.82)
             : base;
  return (
    <h2 style={{
      fontFamily: 'var(--tnf-display)', fontSize: 56, lineHeight: 1.05,
      fontWeight: 400, letterSpacing: '-0.025em', color: TNF.blueDk, margin: 0,
      ...style,
      fontSize: size,
    }}>{children}</h2>
  );
}
window.H2 = H2;
