// femme-app.jsx — Daniéle laser landing, Femme-style layout
// All sections in one file (kept ≤500 lines).

const F_PALETTES = {
  // Brand palette sampled from danielelaserandaesthetics.com — bronze #826409, gold #E0AA3E,
  // warm sand #E7E3E0, olive-grey #8A8574 (logo field).
  daniele: { label: 'Daniéle — Bronze & Sand', swatch: ['#E7E3E0', '#36322D', '#826409'],
    vars: { '--bg': '#E7E3E0', '--bg-2': '#8A8574', '--ink': '#36322D', '--ink-soft': '#5d564e',
      '--accent': '#826409', '--accent-light': '#E0AA3E', '--btn-brown': '#826409',
      '--line': 'rgba(54,50,45,0.14)', '--line-strong': 'rgba(54,50,45,0.28)',
      '--paper': '#FFFFFF', '--shadow': '0 30px 60px -30px rgba(54,50,45,0.22)' } },
  cream: { label: 'Cream & Cocoa', swatch: ['#F5EFE6', '#3D2817', '#8B6B4F'],
    vars: { '--bg': '#F5EFE6', '--bg-2': '#ECE3D4', '--ink': '#3D2817', '--ink-soft': '#6B5440',
      '--accent': '#8B6B4F', '--line': 'rgba(61,40,23,0.14)', '--line-strong': 'rgba(61,40,23,0.28)',
      '--paper': '#FBF7EF', '--shadow': '0 30px 60px -30px rgba(61,40,23,0.25)' } },
  sand: { label: 'Sand & Olive', swatch: ['#F0EAE0', '#3F4429', '#7A7256'],
    vars: { '--bg': '#F0EAE0', '--bg-2': '#E3DBCC', '--ink': '#3F4429', '--ink-soft': '#6B6849',
      '--accent': '#7A7256', '--line': 'rgba(63,68,41,0.14)', '--line-strong': 'rgba(63,68,41,0.28)',
      '--paper': '#F7F2E9', '--shadow': '0 30px 60px -30px rgba(63,68,41,0.22)' } },
  blush: { label: 'Blush & Plum', swatch: ['#F4E8E5', '#3A1F2B', '#9A6B73'],
    vars: { '--bg': '#F4E8E5', '--bg-2': '#EAD8D2', '--ink': '#3A1F2B', '--ink-soft': '#6E5158',
      '--accent': '#9A6B73', '--line': 'rgba(58,31,43,0.14)', '--line-strong': 'rgba(58,31,43,0.26)',
      '--paper': '#FAF1EE', '--shadow': '0 30px 60px -30px rgba(58,31,43,0.22)' } },
  ivory: { label: 'Ivory & Ink', swatch: ['#F0EFEB', '#1B1B1A', '#5C5C58'],
    vars: { '--bg': '#F0EFEB', '--bg-2': '#E1DFD9', '--ink': '#1B1B1A', '--ink-soft': '#56564F',
      '--accent': '#5C5C58', '--line': 'rgba(27,27,26,0.12)', '--line-strong': 'rgba(27,27,26,0.25)',
      '--paper': '#F8F7F3', '--shadow': '0 30px 60px -30px rgba(27,27,26,0.2)' } }
};

const F_FONTS = {
  // Headings keep the template's Cormorant; body uses Montserrat from the client's
  // own site (danielelaserandaesthetics.com).
  daniele: { label: 'Cormorant + Montserrat',
    display: "'Cormorant Garamond', Georgia, serif", body: "'Montserrat', ui-sans-serif, system-ui, sans-serif" },
  cormorant: { label: 'Cormorant + Manrope',
    display: "'Cormorant Garamond', Georgia, serif", body: "'Manrope', ui-sans-serif, system-ui, sans-serif" },
  bodoni: { label: 'Bodoni + Outfit',
    display: "'Bodoni Moda', Georgia, serif", body: "'Outfit', ui-sans-serif, system-ui, sans-serif" },
  italiana: { label: 'Italiana + Jakarta',
    display: "'Italiana', Georgia, serif", body: "'Plus Jakarta Sans', ui-sans-serif, system-ui, sans-serif" }
};

const F_TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "palette": "daniele",
  "fonts": "daniele"
} /*EDITMODE-END*/;

// ─────── Gift card bar (mirrors the top bar on danielelaserandaesthetics.com) ───────
// Gift cards are sold through the client's Boulevard booking system, same link as their site.
const F_GIFT_CARD_URL = 'https://blvd.me/aniele-laser-and-aesthetics/gift-cards';

function FGiftBar() {
  return (
    <div className="giftbar">
      <span className="giftbar-text">Gift yourself or anyone with a luxury experience.</span>
      <a
        href={F_GIFT_CARD_URL}
        className="giftbar-cta"
        target="_blank"
        rel="noopener noreferrer"
      >
        Get your gift card here
      </a>
    </div>);

}

// ─────── Nav ───────
function FNav() {
  React.useEffect(() => {
    const onScroll = () => document.querySelector('.nav')?.classList.toggle('scrolled', window.scrollY > 24);
    window.addEventListener('scroll', onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener('scroll', onScroll);
  }, []);
  const [menuOpen, setMenuOpen] = React.useState(false);
  React.useEffect(() => {
    document.body.style.overflow = menuOpen ? 'hidden' : '';
    return () => { document.body.style.overflow = ''; };
  }, [menuOpen]);
  const links = [
    { href: 'index.html', label: 'Home' },
    { href: 'about-us.html', label: 'About Us' },
    { href: 'laser-hair-removal.html', label: 'Laser Hair Removal' },
    { href: 'aesthetic-treatments.html', label: 'Aesthetic Treatments' },
    { href: 'payment-plans.html', label: 'Payment Plans' },
    { href: 'contact-us.html', label: 'Contact Us' },
    { href: F_GIFT_CARD_URL, label: 'Gift Cards', external: true },
  ];
  const ext = (l) => (l.external ? { target: '_blank', rel: 'noopener' } : {});
  // Highlight the page we're on. Cloudflare serves both /about-us and /about-us.html,
  // and home is '/', so compare on a normalized slug (filename without .html; '' → index).
  const slugOf = (href) => ((href.split(/[?#]/)[0].split('/').pop() || 'index').replace(/\.html$/, '') || 'index');
  const currentSlug = slugOf(window.location.pathname);
  const isActive = (l) => !l.external && slugOf(l.href) === currentSlug;
  return (
    <>
    <nav className="nav">
      <a href="index.html" className="logo" aria-label="Daniéle Laser + Aesthetics — home">
        <img src="images/daniele/footer-logo-new.png" alt="Daniéle Laser + Aesthetics" />
      </a>
      <ul className="nav-menu">
        {links.map((l) => (
          <li key={l.href} className="nav-item"><a href={l.href} {...ext(l)} className={isActive(l) ? 'active' : undefined} aria-current={isActive(l) ? 'page' : undefined}>{l.label}</a></li>
        ))}
      </ul>
      <div className="nav-right">
        <a href="#book" className="nav-cta" onClick={(e) => { e.preventDefault(); openBooking(); }}>Book &amp; Glow</a>
        <button
          type="button"
          className={`nav-burger ${menuOpen ? 'on' : ''}`}
          aria-label="Menu"
          aria-expanded={menuOpen}
          onClick={() => setMenuOpen(o => !o)}
        >
          <span></span><span></span>
        </button>
      </div>
      <div className={`nav-drawer ${menuOpen ? 'on' : ''}`} onClick={() => setMenuOpen(false)}>
        <div className="nav-drawer-panel" onClick={(e) => e.stopPropagation()}>
          <button type="button" className="nav-drawer-close" aria-label="Close menu" onClick={() => setMenuOpen(false)}>×</button>
          <ul>
            {links.map((l) => (
              <li key={l.href}><a href={l.href} {...ext(l)} className={isActive(l) ? 'active' : undefined} aria-current={isActive(l) ? 'page' : undefined} onClick={() => setMenuOpen(false)}>{l.label}</a></li>
            ))}
          </ul>
          <a href="#book" className="btn nav-drawer-cta" onClick={(e) => { e.preventDefault(); setMenuOpen(false); openBooking(); }}>Book &amp; Glow</a>
        </div>
      </div>
    </nav>
    <FBookingHost />
    <FCherryFloating />
    </>);

}

// ─────── Rotating circle button (Femme signature) ───────
function RotateButton({ children, label = 'Book · Today · Now · ', href = '#contact' }) {
  const ref = useMagnet(0.15);
  return (
    <a href={href} className="btn-rotate">
      <span ref={ref} className="ring">
        <IconArrowUpRight size={20} sw={1.5} />
        <svg className="rotating-text" viewBox="0 0 60 60">
          <defs>
            <path id="rt-circle" d="M 30,30 m -22,0 a 22,22 0 1,1 44,0 a 22,22 0 1,1 -44,0" />
          </defs>
          <text>
            <textPath href="#rt-circle">{label.repeat(3)}</textPath>
          </text>
        </svg>
      </span>
      {children}
    </a>);

}

// ─────── HERO (Femme-style with marquee top, welcome bottom-left, pill grid bottom-right) ───────
function FHero() {
  return (
    <section className="hero">
      <div className="hero-marquee" aria-hidden="true">
        <div className="hero-marquee-track">
          {Array.from({ length: 8 }).map((_, i) =>
          <span key={i} className="hero-marquee-item">
              {['Laser hair removal', 'Smoother skin'][i % 2]} <span className="dot">✦</span>{" "}
            </span>
          )}
        </div>
      </div>
      <div className="hero-bg">
        <HeroSlider />
      </div>
      <div className="hero-bottom">
        <div className="hero-welcome">
          <span className="hero-welcome-eyebrow">— Welcome to Daniéle:</span>
          <TXT tag="p" className="hero-welcome-body" k="home.hero.body" />
        </div>
        <div className="hero-pills">
          <a href="#faq" className="hero-pill">Expert advice</a>
          <a href="#results" className="hero-pill">Gallery</a>
          {F_SHOW_PRICING && <a href="#calculator" className="hero-pill">Pricing</a>}
          <a href="#testimonials" className="hero-pill">Reviews</a>
          <a href="#services" className="hero-pill">Services</a>
          <a href="#contact" className="hero-pill">Support</a>
        </div>
      </div>
    </section>);

}

// ─────── Hero auto-cross-fade slider ───────
function HeroSlider() {
  const slides = [
    'images/daniele/aesthetic-banner.png',
    'images/daniele/hero-banner.jpg',
  ];
  const [active, setActive] = React.useState(0);
  React.useEffect(() => {
    if (slides.length < 2) return;
    const id = setInterval(() => setActive(a => (a + 1) % slides.length), 8000);
    return () => clearInterval(id);
  }, []);
  return (
    <div className="hero-slider">
      {slides.map((src, i) => (
        <img
          key={src}
          src={src}
          alt=""
          className={"hero-slide" + (i === active ? " on" : "")}
          aria-hidden={i === active ? "false" : "true"}
        />
      ))}
    </div>
  );
}

// ─────── Statement (centered sticky heading + chaotic floating cards) ───────
function FStatement() {
  // Wording comes from content-text.js (keys home.promise.N.*); the layout numbers —
  // position, parallax, entry angle — stay here since they are design, not copy.
  const benefits = [
  { n: '01', k: 'home.promise.1', icon: IconSparkle,
    pos: { top: '3%', left: '0%' }, parallax: 0.06, rotate: -5, fromX: -40, fromY: 80 },
  { n: '02', k: 'home.promise.2', icon: IconLeaf,
    pos: { top: '12%', right: '10%' }, parallax: -0.08, rotate: 6, fromX: 40, fromY: 80 },
  { n: '03', k: 'home.promise.3', icon: IconCompass,
    pos: { top: '36%', left: '12%' }, parallax: 0.09, rotate: 4, fromX: -30, fromY: 100 },
  { n: '04', k: 'home.promise.4', icon: IconDrop,
    pos: { top: '50%', right: '0%' }, parallax: -0.07, rotate: -3, fromX: 50, fromY: 90 },
  { n: '05', k: 'home.promise.5', icon: IconClock,
    pos: { top: '72%', left: '1%' }, parallax: 0.05, rotate: -4, fromX: -20, fromY: 110 },
  { n: '06', k: 'home.promise.6', icon: IconShield,
    pos: { top: '84%', right: '11%' }, parallax: -0.06, rotate: 5, fromX: 30, fromY: 80 }];

  return (
    <section id="statement" className="statement">
      <div className="statement-cards-fall">
        {benefits.map((b, i) => <BenefitCard key={b.n} {...b} index={i} />)}
      </div>
      <div className="statement-middle">
        <div className="statement-sticky">
          <span className="eyebrow">The Daniéle promise</span>
          <h2 className="h-display">
            Laser care, quietly <span className="ny">calibrated</span> to your unique skin and the rhythm of life you'd rather be living.
          </h2>
          <div className="statement-thumbs">
            <div className="statement-thumb-fan">
              <div className="statement-thumb"><img src="images/daniele/laser-img.png" alt="" /></div>
              <div className="statement-thumb"><img src="images/daniele/bikini.jpg" alt="" /></div>
              <div className="statement-thumb"><img src="images/daniele/laser-treatment.jpg" alt="" /></div>
            </div>
            <span className="statement-thumb-count">
              <TXT tag="strong" k="home.promise.count" /> <TXT k="home.promise.countLabel" />
            </span>
          </div>
        </div>
      </div>
    </section>);

}

function BenefitCard({ n, k, icon: Icon, index, parallax, rotate, fromX, fromY, pos }) {
  const [inRef, inView] = useInView({ threshold: 0.15 });
  const parRef = useParallax(parallax);
  const setRef = (el) => {inRef.current = el;parRef.current = el;};
  return (
    <div ref={setRef} className="benefit-wrap" style={pos}>
      <article
        className={`benefit ${inView ? 'in' : ''}`}
        style={{
          '--enter-rot': `${rotate}deg`,
          '--enter-x': `${fromX}px`,
          '--enter-y': `${fromY}px`
        }}>
        
        <div className="benefit-inner">
          <span className="benefit-icon" aria-hidden="true"><Icon size={20} sw={1.4} /></span>
          <TXT tag="h3" className="benefit-t h-display" k={`${k}.title`} />
          <TXT tag="p" className="benefit-body" k={`${k}.body`} />
          <TXT className="benefit-meta" k={`${k}.meta`} />
        </div>
      </article>
    </div>);

}

// ─────── Tabbed services ───────
// Names, blurbs and "from" prices live in content-text.js (keys home.services.N.*).
const F_SERVICES = [
{ n: '01', k: 'home.services.1', img: 'images/daniele/services-bg-1.png' },
{ n: '02', k: 'home.services.2', img: 'images/daniele/laser-img.png' },
{ n: '03', k: 'home.services.3', img: 'images/daniele/bikini.jpg' },
{ n: '04', k: 'home.services.4', img: 'images/daniele/hero-banner.jpg' }];


function FServices() {
  const [tab, setTab] = React.useState(0);
  return (
    <section id="services" className="services">
      <div className="container">
        <div className="services-head">
          <div>
            <span className="eyebrow">Laser services</span>
            <h2 className="h-display">
              Four <span className="i">considered</span> ways to begin a course.
            </h2>
          </div>
          <a href="contact-us.html" className="btn" onClick={(e) => { e.preventDefault(); openBooking(); }}>Book a service</a>
        </div>
        <div className="services-row">
          <div className="services-tabs">
            {F_SERVICES.map((s, i) =>
            <div key={i} className={`tab ${tab === i ? 'on' : ''}`} onClick={() => setTab(i)}>
                <div className="tab-n">{s.n}</div>
                <div className="tab-main">
                  <TXT tag="div" className="tab-title h-display" k={`${s.k}.title`} />
                  <TXT tag="div" className="tab-body" k={`${s.k}.body`} />
                </div>
                <div className="tab-arrow"><IconArrowUpRight size={16} sw={1.5} /></div>
                <div className="tab-img">
                  <img src={s.img} alt={`${TX(`${s.k}.title`)} — laser hair removal at Daniéle`} loading="lazy" />
                  {F_SHOW_CARD_PRICES && <span className="price-tag"><small>from</small>$<TXT k={`${s.k}.price`} /></span>}
                </div>
              </div>
            )}
          </div>
          <div className="services-img">
            {F_SERVICES.map((s, i) =>
            <div key={i} className={`services-img-card ${tab === i ? 'on' : ''}`}>
                <img src={s.img} alt={`${TX(`${s.k}.title`)} — laser hair removal at Daniéle`} />
                {F_SHOW_CARD_PRICES && <span className="price-tag"><small>from</small>$<TXT k={`${s.k}.price`} /></span>}
              </div>
            )}
          </div>
        </div>
      </div>
    </section>);

}

// ─────── Word marquee (Femme signature) ───────
function FWordMarquee({ tone }) {
  const words = ['Quietly', 'Smooth', 'Confident', 'Refined', 'Magnetic', 'Effortless'];
  const dup = [...words, ...words];
  return (
    <div className={`word-marquee${tone ? ` word-marquee--${tone}` : ''}`}>
      <div className="word-marquee-track">
        {dup.map((w, i) =>
        <span key={i} className="word-marquee-item">{w}</span>
        )}
      </div>
    </div>);

}

// ─────── Service cards ───────
// ─────── Build your course (calculator) ───────
// Each package covers up to F_MAX_PER_TIER areas from a single tier at a flat price.
// Selecting areas across tiers stacks one package per tier.
// Everything below has a working default baked in. When the studio edits pricing,
// areas or locations at /admin, the saved values arrive as `window.__SITE__` (loaded
// from /api/content?format=js before this file runs) and override the defaults.
// If that request fails, the defaults stand and the site keeps working.
const F_SITE = (typeof window !== 'undefined' && window.__SITE__) || {};
const F_OVERRIDE = (key, fallback) => {
  const v = F_SITE[key];
  return Array.isArray(v) && v.length ? v : fallback;
};

// Visibility switches saved at /admin. Missing means "show it", so the site behaves
// exactly as before until the studio turns something off — and turning it back on
// restores the prices untouched, because only the switch is stored, never the numbers.
const F_FLAG = (key) => {
  const flags = F_SITE.flags;
  return flags && typeof flags[key] === 'boolean' ? flags[key] : true;
};
const F_SHOW_PRICING = F_FLAG('showPricing');
const F_SHOW_CARD_PRICES = F_FLAG('showCardPrices');

// Anything the studio has typed at /admin wins over both wordings below.
const F_SAVED_TEXT = (key) => {
  const text = F_SITE.text;
  const v = text ? text[key] : null;
  return typeof v === 'string' && v.trim() !== '' ? v : null;
};
// Same sentences, minus the figures — used only while prices are hidden.
const F_NO_PRICE_TEXT = {
  'home.warranty.body': 'Every package at Daniéle comes with our unlimited lifetime warranty. Once your course is complete you stay on our books for good — any touch-up you ever want is always half price. No expiry date, no fine print.',
  'home.faq.6.a': 'Every package carries our unlimited lifetime warranty. Your course never expires, and any touch-up you want afterwards — months or years later — is always 50% off.',
  'aesthetic.step4.body': "Enjoy smooth, hair-free skin for life. Every package carries our unlimited lifetime warranty — need a touch-up months or even years later? It's always 50% off. Your results are our commitment.",
};

// Editable copy. TX(key) returns the studio's saved wording or the original; <TXT>
// renders it, turning {braces} into italics and **asterisks** into bold, and tags the
// node with data-tx so the on-page editor at ?edit=1 knows what it is editing.
const TX = (key) => {
  if (!F_SHOW_PRICING && !F_SAVED_TEXT(key) && F_NO_PRICE_TEXT[key]) return F_NO_PRICE_TEXT[key];
  return window.__TX__ ? window.__TX__(key) : '';
};

function TXT({ k, tag: Tag = 'span', ...rest }) {
  const parts = String(TX(k)).split(/(\{[^}]*\}|\*\*[^*]+\*\*)/);
  return (
    <Tag data-tx={k} {...rest}>
      {parts.map((p, i) => {
        if (p.startsWith('{') && p.endsWith('}')) return <span key={i} className="i">{p.slice(1, -1)}</span>;
        if (p.startsWith('**') && p.endsWith('**')) return <strong key={i}>{p.slice(2, -2)}</strong>;
        return p;
      })}
    </Tag>);
}

const F_TIERS = F_OVERRIDE('tiers', [
  { id: 'small',  label: 'Small',   price: 299 },
  { id: 'medium', label: 'Medium',  price: 499 },
  { id: 'large',  label: 'Large',   price: 599 },
  { id: 'xlarge', label: 'X-Large', price: 699 },
]);
const F_MAX_PER_TIER = 6;
// Mix + match: any areas from any category, priced by how many you pick (client, 2026-08-08).
const F_MIX = F_OVERRIDE('mix', [
  { count: 2, price: 799 },
  { count: 3, price: 899 },
  { count: 4, price: 999 },
]);
const F_MIX_MIN = F_MIX[0].count;
const F_MIX_MAX = F_MIX[F_MIX.length - 1].count;
const F_AREAS = F_OVERRIDE('areas', [
  { id: 'areolas',      name: 'Areolas',        tier: 'small' },
  { id: 'butt-strip',   name: 'Buttocks strip', tier: 'small' },
  { id: 'chin',         name: 'Chin',           tier: 'small' },
  { id: 'ears',         name: 'Ears',           tier: 'small' },
  { id: 'eyebrows',     name: 'Eyebrows',       tier: 'small' },
  { id: 'feet',         name: 'Feet',           tier: 'small' },
  { id: 'hairline',     name: 'Hairline',       tier: 'small' },
  { id: 'hands',        name: 'Hands',          tier: 'small' },
  { id: 'happy-trail',  name: 'Happy trail',    tier: 'small' },
  { id: 'jawline',      name: 'Jawline',        tier: 'small' },
  { id: 'nose',         name: 'Nose',           tier: 'small' },
  { id: 'sideburns',    name: 'Sideburns',      tier: 'small' },
  { id: 'upper-lip',    name: 'Upper lip',      tier: 'small' },

  { id: 'face',         name: 'Face',           tier: 'medium' },
  { id: 'underarms',    name: 'Underarms',      tier: 'medium' },
  { id: 'inner-thighs', name: 'Inner thighs',   tier: 'medium' },

  { id: 'buttocks',     name: 'Buttocks',       tier: 'large' },
  { id: 'head',         name: 'Head',           tier: 'large' },
  { id: 'lower-legs',   name: 'Lower legs',     tier: 'large' },
  { id: 'upper-legs',   name: 'Upper legs',     tier: 'large' },
  { id: 'brazilian',    name: 'Brazilian',      tier: 'large' },
  { id: 'shoulders',    name: 'Shoulders',      tier: 'large' },
  { id: 'lower-arms',   name: 'Lower arms',     tier: 'large' },
  { id: 'upper-arms',   name: 'Upper arms',     tier: 'large' },
  { id: 'abs',          name: 'Abs',            tier: 'large' },
  { id: 'half-back',    name: 'Half back',      tier: 'large' },
  { id: 'lower-back',   name: 'Lower back',     tier: 'large' },
  { id: 'upper-back',   name: 'Upper back',     tier: 'large' },
  { id: 'half-abdomen', name: 'Half abdomen',   tier: 'large' },

  { id: 'chest',        name: 'Chest',          tier: 'xlarge' },
  { id: 'full-legs',    name: 'Full legs',      tier: 'xlarge' },
  { id: 'full-back',    name: 'Full back',      tier: 'xlarge' },
  { id: 'full-abdomen', name: 'Full abdomen',   tier: 'xlarge' },
  { id: 'full-arms',    name: 'Full arms',      tier: 'xlarge' },
]);
// Locations, phone, hours and map embed all taken from danielelaserandaesthetics.com/contact-us
const F_LOCATIONS = F_OVERRIDE('locations', [
  { id: 'friendswood', name: 'Friendswood',
    address: '1769 S. Friendswood Dr. Suite 113, Friendswood, TX 77546',
    email: 'corporate@danielefriendswood.com',
    phone: '(281) 612-2073',
    hours: 'Mon–Fri 10–7 · Sat 10–5 · Sun closed',
    mapUrl: 'https://maps.app.goo.gl/AWyeh7J67v6qRwMY8' },
  // Second studio in Cypress, TX — open since Aug 12. `opening` renders a badge everywhere the location is listed.
  { id: 'cypress', name: 'Cypress',
    address: '20049 House Hahl Rd. Suite 113, Cypress, TX 77433',
    email: 'corporate@danielecypress.com',
    phone: '(713) 496-0403',
    hours: 'Wed–Fri 10–7 · Sat 10–5 · Sun closed',
    opening: 'Now open',
    mapUrl: 'https://www.google.com/maps/search/?api=1&query=20049+House+Hahl+Rd+Suite+113+Cypress+TX+77433' },
]);
// Each studio answers its own line. Friendswood is also the number the site shows when it
// speaks for the company rather than for one location.
const F_PHONE = F_LOCATIONS[0].phone;
// tel: takes digits only, and every studio number is US.
const telHref = (phone) => 'tel:+1' + String(phone).replace(/\D/g, '');
const F_INSTAGRAM = 'https://www.instagram.com/danielelaserandaesthetics/';
const F_FACEBOOK = 'https://www.facebook.com/profile.php?id=61561791801373';
const F_MAP_EMBED = 'https://www.google.com/maps/embed?pb=!1m14!1m8!1m3!1d3472.3198881230796!2d-95.1879451!3d29.5070337!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x86409ad58cd67a63%3A0xe976b01a950a7fcd!2s1769%20S%20Friendswood%20Dr%2C%20Friendswood%2C%20TX%2077546%2C%20USA!5e0!3m2!1sen!2sin!4v1719391444880!5m2!1sen!2sin';

// ─────── Form delivery ───────
// Real server-side email via Web3Forms (free, works on a static site — no backend).
// Each studio has its OWN access key so the request lands in that location's inbox,
// routed by the customer's location choice. To activate: go to https://web3forms.com,
// enter corporate@danielefriendswood.com to get one key and corporate@danielecypress.com
// to get another, then paste them below. Until the keys are set the forms fall back to a
// mailto so nothing is broken.
const F_WEB3_KEYS = {
  friendswood: 'ca93e4ae-5742-4a15-8e77-e483bccefb68',
  cypress: 'ede1f728-b79b-487c-891f-ced35e1fb893',
};

async function deliverBooking({ locationId, name, email, phone, subject, message }) {
  const loc = F_LOCATIONS.find(l => l.id === locationId) || F_LOCATIONS[0];
  const key = F_WEB3_KEYS[loc.id];
  if (key && !key.startsWith('YOUR_')) {
    const res = await fetch('https://api.web3forms.com/submit', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
      body: JSON.stringify({
        access_key: key,
        subject,
        from_name: 'Daniéle Website',
        replyto: email || '',
        name: name || '',
        email: email || '',
        phone: phone || '',
        message,
      }),
    });
    const data = await res.json().catch(() => ({}));
    if (!res.ok || !data.success) throw new Error(data.message || 'Submission failed');
    return 'sent';
  }
  // Fallback until the Web3Forms keys are in place — opens the user's mail client.
  window.location.href = `mailto:${loc.email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(message)}`;
  return 'mailto';
}

// ─────── Cherry payment plans (financing widget) ───────
// Vendor snippet from provider.withcherry.com/script-generator, transplanted as-is: the
// loader stub queues _hw() calls until widget.js arrives, then Cherry renders into the
// mount points it is given (#hero / #calculator / #howitworks / #faq, or
// #floatingEstimator). Cherry only accepts ONE init per page load, so the payment-plans
// page takes the full section set and every other page takes the floating estimator.
// One Cherry practice per studio, values taken verbatim from the snippets the studio
// generated at provider.withcherry.com. `active` gates whether we ever point a visitor at
// it. Cypress was off while the studio was still closed — Cherry answered "isn't accepting
// applications from this provider" (checked 2026-07-23 from a US IP) — and was switched on
// when the studio opened on 2026-08-12. If Cherry still refuses to draw the widget, the
// payment-plans page falls back to its own "coming soon" panel on its own; see
// CHERRY_RENDER_GRACE in pages.jsx.
const F_CHERRY_ACCOUNTS = [
  { id: 'friendswood', slug: 'daniele-laser-aesthetics', name: 'daniéle laser + aesthetics - friendswood',
    label: 'Friendswood', primaryColor: '#806d49', active: true, opening: null },
  { id: 'cypress', slug: 'danielelasercypress', name: 'daniéle laser + aesthetics cypress',
    label: 'Cypress', primaryColor: '#806e35', active: true, opening: null },
];
const F_CHERRY_ACTIVE = F_CHERRY_ACCOUNTS.filter(a => a.active);

// ?studio=<id> picks that practice (even an inactive one, so its "coming soon" panel can
// show); otherwise the first active one. A switch means a page reload, because Cherry only
// accepts one _hw('init') per page load.
function cherryAccount() {
  const want = new URLSearchParams(window.location.search).get('studio');
  return F_CHERRY_ACCOUNTS.find(a => a.id === want)
    || F_CHERRY_ACTIVE[0] || F_CHERRY_ACCOUNTS[0] || null;
}

const F_CHERRY_STYLES = {
  fontFamily: 'Montserrat',
  headerFontFamily: 'Montserrat',
};
const F_CHERRY_FLOATING = {
  position: 'bottom-right',
  offset: { x: '0px', y: '0px' },
  zIndex: 9999,
  ctaFontFamily: 'Montserrat',
  bodyFontFamily: 'Montserrat',
  ctaColor: '#806e35',
  ctaTextColor: '#FFFFFF',
};

function loadCherry(components, extraStyles) {
  const account = cherryAccount();
  if (!account || !account.active || window.__danieleCherryInit) return;
  window.__danieleCherryInit = true;
  (function (w, d, s, o, f, js, fjs) {
    w[o] = w[o] || function () {(w[o].q = w[o].q || []).push(arguments);};
    (js = d.createElement(s)), (fjs = d.getElementsByTagName(s)[0]);
    js.id = o;
    js.src = f;
    js.async = 1;
    fjs.parentNode.insertBefore(js, fjs);
  })(window, document, 'script', '_hw', 'https://files.withcherry.com/widgets/widget.js');
  window._hw('init', {
    debug: false,
    variables: {
      slug: account.slug,
      name: account.name,
      images: [43],
      customLogo: '',
      defaultPurchaseAmount: 750,
      customImage: '',
      imageCategory: 'medspa',
      language: 'en',
    },
    styles: {
      ...F_CHERRY_STYLES,
      primaryColor: account.primaryColor,
      secondaryColor: account.primaryColor + '10',
      ...(extraStyles || {}),
    },
  }, components);
}

// Floating "see if you prequalify" estimator, on every page except the payment-plans
// page (which renders the full widget and would otherwise fight over the single init).
// Cherry opens its estimator card expanded, so it is held back until after the booking
// popup has had its moment (F_AUTO_BOOK_MS) — two panels at once would fight each other,
// and the third-party script stays out of the critical first paint.
const F_CHERRY_FLOATING_DELAY = F_AUTO_BOOK_MS + 3000;

function FCherryFloating() {
  const isPaymentsPage = (document.getElementById('root')?.dataset.page || 'home') === 'payments';
  const account = cherryAccount();
  const off = isPaymentsPage || !account;
  React.useEffect(() => {
    if (off) return;
    const styles = { floatingEstimator: { ...F_CHERRY_FLOATING, ctaColor: account.primaryColor } };
    const t = setTimeout(() => loadCherry(['floatingEstimator'], styles), F_CHERRY_FLOATING_DELAY);
    return () => clearTimeout(t);
  }, [off]);
  if (off) return null;
  return <div id="floatingEstimator"></div>;
}

function FBookingModal({ onClose, quote }) {
  const [sent, setSent] = React.useState(false);
  const [error, setError] = React.useState(false);
  const [location, setLocation] = React.useState(F_LOCATIONS[0].id);
  const [service, setService] = React.useState('consult');
  const [form, setForm] = React.useState({ name: '', email: '', phone: '', date: '', notes: '' });
  const items = quote ? quote.items : [];
  const lines = quote ? quote.lines : [];
  const total = quote ? quote.price : 0;
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('keydown', onKey);
    document.body.style.overflow = 'hidden';
    return () => { document.removeEventListener('keydown', onKey); document.body.style.overflow = ''; };
  }, []);
  const set = (k) => (e) => setForm(f => ({ ...f, [k]: e.target.value }));

  // Route the booking to the chosen location's own inbox via Web3Forms (mailto fallback).
  const submit = async (e) => {
    e.preventDefault();
    const loc = F_LOCATIONS.find(l => l.id === location);
    const areaLines = lines.map(l => `  • ${l.name} — ${l.tierLabel} package (6 sessions), $${l.price}`).join('\n');
    const body = [
      `${quote ? 'New course reservation' : 'New booking request'} — ${loc.name}`,
      '',
      `Name:  ${form.name}`,
      `Email: ${form.email}`,
      `Phone: ${form.phone || '—'}`,
      `Preferred date: ${form.date || '—'}`,
      '',
      quote ? 'Selected areas:' : 'Requested:',
      quote
        ? (areaLines || '  —')
        : `  • ${service === 'consult' ? 'Free consultation' : service}`,
      '',
      quote
        ? (quote.kind === 'mix'
            ? `Pricing: Mix + match, any ${items.length} areas — $${total} (saves $${quote.saving})`
            : `Estimated total: $${total}`)
        : 'No course selected yet — happy to advise at the consultation.',
      '',
      form.notes ? `Notes: ${form.notes}` : '',
    ].join('\n');
    const subject = quote ? `Course reservation — ${loc.name}` : `Booking request — ${loc.name}`;
    try {
      await deliverBooking({ locationId: loc.id, name: form.name, email: form.email, phone: form.phone, subject, message: body });
      setSent(true);
    } catch (err) {
      setError(true);
    }
  };

  const loc = F_LOCATIONS.find(l => l.id === location);
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal modal--split" role="dialog" aria-modal="true" onClick={(e) => e.stopPropagation()}>
        <button type="button" className="modal-close" onClick={onClose} aria-label="Close">×</button>
        <div className="modal-media" aria-hidden="true"><img src="images/daniele/bikini.jpg" alt="" /></div>
        <div className="modal-inner">
        {sent ? (
          <div className="modal-done">
            <span className="eyebrow">Reservation sent</span>
            <h3 className="modal-h">On its way to <span className="i">{loc.name}.</span></h3>
            <p className="modal-done-p">Your request has been addressed to our {loc.name} studio ({loc.email}). A Daniéle practitioner will confirm your consultation and course, usually within the hour.</p>
            <button type="button" className="btn" onClick={onClose}>Close</button>
          </div>
        ) : (
          <>
            <span className="eyebrow">{quote ? 'Reserve your course' : 'Book your visit'}</span>
            <h3 className="modal-h">Book my <span className="i">{quote ? 'course.' : 'visit.'}</span></h3>
            {quote ? (
              <div className="modal-summary">
                <div className="modal-summary-row"><span>Areas</span><span>{items.map(a => a.name).join(', ') || '—'}</span></div>
                <div className="modal-summary-row"><span>{quote.kind === 'mix' ? 'Mix + match' : 'Packages'}</span><span>{quote.label}</span></div>
                <div className="modal-summary-row modal-summary-total"><span>Estimated total</span><span>${total.toLocaleString()}</span></div>
              </div>
            ) : (
              <label className="modal-field modal-service">
                <span>What would you like to book?</span>
                <select value={service} onChange={(e) => setService(e.target.value)}>
                  <option value="consult">A free consultation</option>
                  {F_SERVICES.map(s => <option key={s.t} value={s.t}>{s.t}</option>)}
                </select>
              </label>
            )}
            <form className="modal-form" onSubmit={submit}>
              <label className="modal-field"><span>Full name</span><input type="text" required placeholder="Your name" value={form.name} onChange={set('name')} /></label>
              <label className="modal-field"><span>Email</span><input type="email" required placeholder="your@email.com" value={form.email} onChange={set('email')} /></label>
              <label className="modal-field"><span>Phone</span><input type="tel" placeholder="(___) ___-____" value={form.phone} onChange={set('phone')} /></label>
              <label className="modal-field">
                <span>Location</span>
                <select required value={location} onChange={(e) => setLocation(e.target.value)}>
                  {F_LOCATIONS.map(l => <option key={l.id} value={l.id}>{l.name}{l.opening ? ` (${l.opening})` : ''}</option>)}
                </select>
              </label>
              <label className="modal-field"><span>Preferred date</span><input type="date" className="date-input" min={new Date().toISOString().split('T')[0]} value={form.date} onChange={set('date')} /></label>
              <label className="modal-field modal-field--full"><span>Anything we should know?</span><textarea rows="2" placeholder="Optional — sensitivities, questions, timing" value={form.notes} onChange={set('notes')}></textarea></label>
              <button type="submit" className="btn modal-submit">Send to {loc.name}</button>
              {error && <p className="modal-error" role="alert">Something went wrong sending your request. Please call us at {F_PHONE} or try again.</p>}
            </form>
          </>
        )}
        </div>
      </div>
    </div>
  );
}

// ─────── Global booking popup ───────
// Any CTA on any page calls openBooking(quote?) to open the shared booking form. A single
// FBookingHost (mounted once inside FNav, which every page renders) listens for the event,
// so every "Book a consultation" button opens the popup form instead of navigating away.
// quote is optional — the pricing builder passes its selection; everyone else passes none.
function openBooking(quote) {
  window.dispatchEvent(new CustomEvent('daniele:book', { detail: { quote: quote || null } }));
}

// Instagram-ad traffic lands cold, so the booking form invites itself once per visit:
// after F_AUTO_BOOK_MS of reading, or as soon as the visitor scrolls past the first
// screen — whichever comes first. Marked in sessionStorage so it never nags twice, and
// skipped entirely if the visitor already opened the form themselves.
const F_AUTO_BOOK_MS = 7000;
const F_AUTO_BOOK_KEY = 'daniele:autobook';

// sessionStorage throws in some locked-down/embedded browsers — never let that break the page.
function autoBookSeen() {
  try { return sessionStorage.getItem(F_AUTO_BOOK_KEY) === '1'; } catch (e) { return true; }
}
function markAutoBookSeen() {
  try { sessionStorage.setItem(F_AUTO_BOOK_KEY, '1'); } catch (e) {}
}

function FBookingHost() {
  const [state, setState] = React.useState({ open: false, quote: null });
  React.useEffect(() => {
    const onOpen = (e) => {
      markAutoBookSeen();
      setState({ open: true, quote: (e.detail && e.detail.quote) || null });
    };
    window.addEventListener('daniele:book', onOpen);
    return () => window.removeEventListener('daniele:book', onOpen);
  }, []);

  React.useEffect(() => {
    if (autoBookSeen()) return;
    // Not on the payment-plans page: someone checking financing is mid-task, and Cherry's
    // own estimator already owns that screen.
    if ((document.getElementById('root')?.dataset.page || 'home') === 'payments') return;
    let timer = 0;
    const fire = () => {
      cleanup();
      if (autoBookSeen()) return;
      openBooking();
    };
    const onScroll = () => { if (window.scrollY > window.innerHeight * 0.6) fire(); };
    const cleanup = () => {
      clearTimeout(timer);
      window.removeEventListener('scroll', onScroll);
    };
    timer = setTimeout(fire, F_AUTO_BOOK_MS);
    window.addEventListener('scroll', onScroll, { passive: true });
    return cleanup;
  }, []);
  if (!state.open) return null;
  return <FBookingModal quote={state.quote} onClose={() => setState({ open: false, quote: null })} />;
}

const F_TIER_PRICE = F_TIERS.reduce((m, t) => { m[t.id] = t; return m; }, {});

// Correct model (confirmed by the client + their own "$199 for a package of 6" graphic):
// each AREA is its own package of 6 sessions, priced by that area's size tier. Buying
// several areas normally sums those package prices — UNLESS mix + match (a flat price by
// area count, any category) is cheaper, in which case the studio charges that. We quote
// the lower of the two so we never overcharge.
function quoteFor(selected) {
  const items = F_AREAS.filter(a => selected.includes(a.id));
  if (!items.length) return null;

  const lines = items.map(a => ({ ...a, price: F_TIER_PRICE[a.tier].price, tierLabel: F_TIER_PRICE[a.tier].label }));
  const areaSum = lines.reduce((sum, l) => sum + l.price, 0);

  const mix = F_MIX.find(m => m.count === items.length) || null;

  if (mix && mix.price < areaSum) {
    return {
      kind: 'mix', price: mix.price, items, lines,
      saving: areaSum - mix.price,
      label: `Mix + match · any ${items.length} areas`,
    };
  }
  return {
    kind: 'areas', price: areaSum, items, lines, saving: 0,
    label: items.length === 1 ? `${lines[0].tierLabel} package` : `${items.length} packages`,
  };
}

// ══════════════════════════════════════════════════════════════════════════
//  PRICING — interactive builder. Each area is a 6-session package priced by
//  size; mix + match discounts by count; booking routes to the location email.
// ══════════════════════════════════════════════════════════════════════════

function FPricingBuilder() {
  const [selected, setSelected] = React.useState(['underarms']);
  const [activeTier, setActiveTier] = React.useState('medium');

  const toggle = (id) => setSelected(s =>
    s.includes(id) ? s.filter(x => x !== id) : [...s, id]);

  const countInTier = (tierId) => F_AREAS.filter(a => a.tier === tierId && selected.includes(a.id)).length;
  const quote = quoteFor(selected);
  const items = quote ? quote.items : [];
  const total = quote ? quote.price : 0;
  const animatedTotal = useSmoothNumber(total, 450);
  const canBook = items.length > 0;

  return (
    <section id="calculator" className="calc">
      <div className="container">
        <div className="calc-head">
          <div>
            <span className="eyebrow">Build your course</span>
            <h2 className="h-display">
              Build your <span className="i">package.</span>
            </h2>
          </div>
          <p className="calc-blurb">
            Every area is a package of six sessions, priced by size. Pick the areas you'd like — we always quote you the lowest price your selection qualifies for, mix + match included.
          </p>
        </div>

        <div className="pcards-grid calc-tiercards">
          {F_TIERS.map(t => {
            const cnt = countInTier(t.id);
            return (
              <button key={t.id} type="button"
                className={`pcard ${activeTier === t.id ? 'active' : ''}`}
                onClick={() => setActiveTier(t.id)}>
                <div className="pcard-head-l">
                  <span className="pcard-size">{t.label}</span>
                  <span className="pcard-sessions">6 sessions · one area</span>
                </div>
                <span className="pcard-price">${t.price}</span>
                {cnt > 0 && <span className="pcard-count">{cnt}</span>}
              </button>
            );
          })}
        </div>

        <div className="calc-panel">
          <div className="calc-section-head">
            <div className="calc-section-l">— {F_TIERS.find(t => t.id === activeTier).label} areas</div>
            <span className="calc-section-note">
              ${F_TIERS.find(t => t.id === activeTier).price} each · six sessions per area
            </span>
          </div>

          <div className="calc-pills">
            {F_AREAS.filter(a => a.tier === activeTier).map(a => {
              const on = selected.includes(a.id);
              return (
                <button key={a.id} type="button"
                  className={`calc-pill ${on ? 'on' : ''}`}
                  onClick={() => toggle(a.id)} aria-pressed={on}>
                  <span className="calc-pill-name">{a.name}</span>
                </button>
              );
            })}
          </div>

          <div className="calc-footer">
            <div className="calc-breakdown">
              {items.length === 0 ? (
                <p className="calc-empty">
                  We only sell packages, never single sessions — the price you see is the price you pay, confirmed at your free consultation. Pick an area to begin.
                </p>
              ) : (
                <>
                  <div className="calc-breakdown-head">
                    <span className="calc-breakdown-l">Your selection</span>
                    <button type="button" className="calc-clear calc-clear--sm" onClick={() => setSelected([])}>Clear all</button>
                  </div>
                  <ul className="calc-items">
                    {quote.lines.map(l => (
                      <li key={l.id}>
                        <span>{l.name}</span>
                        <span className="calc-item-meta">
                          {l.tierLabel} · 6 sessions · ${l.price}
                          <button type="button" className="calc-item-del" aria-label={`Remove ${l.name}`}
                            onClick={() => toggle(l.id)}>
                            <IconTrash size={15} />
                          </button>
                        </span>
                      </li>
                    ))}
                  </ul>
                </>
              )}
            </div>

            <div className="calc-checkout">
              <div className="calc-total">
                <div>
                  <span className="calc-total-l">Your price</span>
                  <span className="calc-total-sub">
                    {quote ? (quote.kind === 'mix' ? quote.label : `${items.length} ${items.length === 1 ? 'area' : 'areas'} · ${quote.label}`) : 'Nothing selected yet'}
                  </span>
                </div>
                <div className="calc-total-n">${animatedTotal.toLocaleString()}</div>
              </div>
              {quote && quote.saving > 0 && (
                <p className="calc-saving">Mix + match saves you ${quote.saving.toLocaleString()} versus booking these areas separately.</p>
              )}
              <button type="button" className={`btn calc-cta ${canBook ? '' : 'disabled'}`}
                onClick={() => canBook && openBooking(quote)}>
                {canBook ? 'Book my package' : 'Pick an area to begin'}
              </button>
              {/* Bridge to the Cherry page — the widget's amount is entered there by the
                  visitor, so we hand over the number they just built rather than a guess. */}
              <a className="calc-pay" href="payment-plans.html">
                Prefer to pay monthly? See payment plans <IconArrowUpRight size={12} sw={1.6} />
              </a>
            </div>
          </div>

          <div className="pcards-mix">
            <span className="mixnote-l">Mix + match, any areas</span>
            {F_MIX.map(m => (
              <div key={m.count} className="pcards-mix-tier">
                <span>{m.count} areas</span><strong>${m.price}</strong>
              </div>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
}

function FCalculator() {
  // Hidden from /admin: the whole pricing block drops out of every page that mounts it.
  if (!F_SHOW_PRICING) return null;
  return <FPricingBuilder />;
}

// ─────── Unlimited lifetime warranty ───────
// Client-confirmed offer (Jul 2026): every package carries an unlimited lifetime
// warranty — after the course, any touch-up is half price, forever, from $35 a session.
const F_WARRANTY_FACTS = ['home.warranty.1', 'home.warranty.2', 'home.warranty.3']
  .filter((key) => F_SHOW_PRICING || key !== 'home.warranty.2');

function FWarranty() {
  return (
    <section id="warranty" className="warranty">
      <div className="warranty-card">
        <div className="warranty-l">
          <span className="eyebrow">Unlimited lifetime warranty</span>
          <h2 className="warranty-h h-display">
            Your results, <span className="i">covered for life.</span>
          </h2>
          <TXT tag="p" className="warranty-body" k="home.warranty.body" />
          <button type="button" className="btn warranty-cta" onClick={() => openBooking()}>
            Book a free consultation
          </button>
        </div>
        <ul className="warranty-facts">
          {F_WARRANTY_FACTS.map((key) => (
            <li key={key} className="warranty-fact">
              <TXT className="warranty-fact-k" k={`${key}.k`} />
              <TXT className="warranty-fact-v" k={`${key}.v`} />
            </li>
          ))}
        </ul>
      </div>
    </section>
  );
}

// ─────── Why us with stats ───────
function FWhy() {
  const videoRef = React.useRef(null);
  // Subtle background-photo parallax on scroll (sets --parallax on the section).
  const whyRef = useParallax(0.06);

  // React can render <video muted> without the property being set in time, and an
  // unmuted autoplay is blocked outright. Force it, then start playback ourselves.
  React.useEffect(() => {
    const v = videoRef.current;
    if (!v) return;
    v.muted = true;
    v.defaultMuted = true;
    const p = v.play();
    if (p && typeof p.catch === 'function') p.catch(() => {});
  }, []);

  return (
    <section id="why" className="why" ref={whyRef}>
      <div className="container">
        <div className="why-head">
          <span className="eyebrow">Smooth. Confident. You.</span>
          <h2 className="why-h h-display">
            Why choose <span className="i">Daniéle?</span>
          </h2>
        </div>

        <div className="why-bottom">
          <div className="why-mission">
            <TXT tag="p" className="why-lead" k="home.why.lead" />
            <TXT tag="p" k="home.why.body" />
            <a href="contact-us.html" className="btn" onClick={(e) => { e.preventDefault(); openBooking(); }}>Book a free consultation</a>
          </div>
          <div className="why-images">
            <div className="img img--video">
              {/* Client's own Instagram clip — a real client walking through her appointment.
                  Autoplays muted (the only way browsers allow autoplay); controls stay so the
                  voiceover, which is the actual review, can be turned on. */}
              <video
                ref={videoRef}
                src="images/daniele/client-review.mp4"
                poster="images/daniele/client-review-poster.jpg"
                controls
                autoPlay
                loop
                muted
                playsInline
                preload="metadata"
                aria-label="A client's walkthrough of her laser hair removal appointment at Daniéle"
              ></video>
              <span className="img-caption">Hair removal at Daniéle</span>
            </div>
            <div className="img img--still">
              <img src="images/daniele/reception.jpg" alt="Inside a Daniéle studio — reception and product wall" loading="lazy" />
              <span className="img-caption">Inside our studio</span>
            </div>
          </div>
        </div>

        {/* Real figures supplied by the client (2026-07-17): 2 years open, 20,000+ treatments,
            thousands of happy clients, 2 studio locations. */}
        <div className="why-stats">
          <CountStatF target={Number(TX('home.why.stat1n')) || 0} suffix="+" k="home.why.stat1" icon={IconSparkle} />
          <CountStatF target={Number(TX('home.why.stat2n')) || 0} suffix="+" k="home.why.stat2" icon={IconStar} />
          <CountStatF target={Number(TX('home.why.stat3n')) || 0} suffix="" k="home.why.stat3" icon={IconPin} />
          <CountStatF target={Number(TX('home.why.stat4n')) || 0} suffix="" k="home.why.stat4" icon={IconClock} />
        </div>
      </div>
    </section>);

}

function CountStatF({ target, suffix, k, icon: Icon }) {
  const [ref, inView] = useInView({ threshold: 0.3 });
  const v = useCountUp(target, { trigger: inView, duration: 1600 });
  return (
    <div className="why-stat" ref={ref}>
      <div className="why-stat-body">
        <div className="why-stat-n">{v.toLocaleString()}{suffix}</div>
        <TXT tag="div" className="why-stat-l" k={k} />
      </div>
    </div>);

}

// ─────── Video promo banner ───────
function FVideoBanner() {
  return (
    <section className="video-banner">
      <div className="video-card">
        <img src="images/daniele/faq-room.jpg" alt="Inside a Daniéle treatment room" />
        <div className="video-card-content">
          <div className="video-card-top">
            <span className="eyebrow">A studio note</span>
          </div>
          <div className="video-card-bottom">
            <h3 className="video-card-h">
              The first consultation is always <span style={{ fontStyle: 'italic' }}>free.</span>
            </h3>
            <TXT tag="p" className="video-card-sub" k="home.consult.sub" />
            <a href="contact-us.html" className="btn video-card-cta" onClick={(e) => { e.preventDefault(); openBooking(); }}>Book a free consultation</a>
          </div>
        </div>
      </div>
    </section>);
}

// ─────── Journal ───────
function FJournal() {
  // Post copy lives in content-text.js (keys home.journal.N.*).
  const posts = [
  { k: 'home.journal.1', img: 'images/daniele/rejuvenation.jpg' },
  { k: 'home.journal.2', img: 'images/daniele/about-img-1.png' }];

  return (
    <section id="journal" className="journal">
      <div className="container">
        <div className="journal-head">
          <div>
            <span className="eyebrow">From the journal</span>
            <h2 className="h-display">
              Unlock the secrets to <span className="i">timeless</span> smooth skin.
            </h2>
          </div>
          <a href="#" className="btn">Read all journal</a>
        </div>
        <div className="journal-grid">
          {posts.map((p, i) =>
          <a className="journal-card" key={i} href="#">
              <div className="journal-img"><img src={p.img} alt={TX(`${p.k}.title`)} /></div>
              <div className="journal-body">
                <div>
                  <span className="journal-cat"><IconAsterisk size={9} sw={1.4} /> <TXT k={`${p.k}.cat`} /></span>
                  <TXT tag="h3" className="journal-title" style={{ marginTop: 12 }} k={`${p.k}.title`} />
                </div>
                <div className="journal-meta">
                  <span>By <TXT tag="em" style={{ fontStyle: 'italic' }} k={`${p.k}.author`} /></span>
                  <TXT k={`${p.k}.year`} />
                </div>
              </div>
            </a>
          )}
        </div>
      </div>
    </section>);

}

// ─────── Testimonials slider (3-up) ───────
function FTestimonials() {
  // Real reviews from the studio's own booking system. Client and technician names are
  // withheld — each is credited only by the service performed and the month. Text is
  // verbatim aside from removing staff first names at the studio's request.
  const tests = [
  { q: "My technician was super sweet and knowledgeable! This was my first time doing laser hair removal. She made me feel comfortable the whole time. 10/10 would recommend. (:",
    service: "Full Body Laser Hair Removal", date: "March 2026" },
  { q: "My technician was very welcoming and made me feel at ease. She provided helpful tips for after care too.",
    service: "Full Legs Laser Hair Removal", date: "January 2026" },
  { q: "Best customer service and amazing attitude and grace. My technician is just wonderful and amazing.",
    service: "Lower Legs Laser Hair Removal", date: "November 2025" },
  { q: "The facility was beautiful and my technician was amazing.",
    service: "Advanced Facial Tightening", date: "September 2025" },
  { q: "My technician was excellent. Looking forward towards my next visit.",
    service: "Brazilian Laser Hair Removal", date: "August 2025" },
  { q: "Technician was very helpful.",
    service: "Brazilian Laser Hair Removal", date: "July 2025" },
  { q: "My technician was amazing.",
    service: "Full Body Laser Hair Removal", date: "May 2025" }];

  const trackRef = React.useRef(null);
  const [page, setPage] = React.useState(0);
  const [pageCount, setPageCount] = React.useState(1);

  const recompute = React.useCallback(() => {
    const el = trackRef.current;
    if (!el) return;
    const card = el.querySelector('.test-card');
    if (!card) return;
    const styles = getComputedStyle(el);
    const gap = parseFloat(styles.columnGap || styles.gap || 0);
    const padL = parseFloat(styles.paddingLeft) || 0;
    const padR = parseFloat(styles.paddingRight) || 0;
    const cardW = card.getBoundingClientRect().width;
    const step = cardW + gap;
    const visibleArea = Math.max(0, el.clientWidth - padL - padR);
    const visible = Math.max(1, Math.round(visibleArea / step));
    const pages = Math.max(1, tests.length - visible + 1);
    setPageCount(pages);
    const current = Math.min(pages - 1, Math.round(el.scrollLeft / step));
    setPage(current);
  }, [tests.length]);

  React.useEffect(() => {
    recompute();
    const el = trackRef.current;
    if (!el) return;
    const onScroll = () => recompute();
    el.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', recompute);
    return () => {
      el.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', recompute);
    };
  }, [recompute]);

  const goBy = (dir) => {
    const el = trackRef.current;
    if (!el) return;
    const card = el.querySelector('.test-card');
    if (!card) return;
    const gap = parseFloat(getComputedStyle(el).columnGap || 0);
    const step = card.getBoundingClientRect().width + gap;
    el.scrollBy({ left: dir * step, behavior: 'smooth' });
  };
  const goTo = (p) => {
    const el = trackRef.current;
    if (!el) return;
    const card = el.querySelector('.test-card');
    if (!card) return;
    const gap = parseFloat(getComputedStyle(el).columnGap || 0);
    const step = card.getBoundingClientRect().width + gap;
    el.scrollTo({ left: p * step, behavior: 'smooth' });
  };

  return (
    <section id="testimonials" className="testimonials">
      <div className="container">
        <div className="test-head">
          <div className="test-head-left">
            <span className="eyebrow">4.8★ from 170+ Google reviews</span>
            <h2 className="h-display">What our <span className="i">clients</span> say.</h2>
          </div>
          <div className="test-head-right">
            <div className="test-counter">
              <span className="test-counter-now">{String(page + 1).padStart(2, '0')}</span>
              <span className="test-counter-sep">/</span>
              <span className="test-counter-total">{String(pageCount).padStart(2, '0')}</span>
            </div>
            <div className="test-nav">
              <button className="test-arrow" aria-label="Previous" onClick={() => goBy(-1)} disabled={page === 0}>
                <IconArrowUpRight size={18} sw={1.4} style={{ transform: 'rotate(-135deg)' }} />
              </button>
              <button className="test-arrow" aria-label="Next" onClick={() => goBy(1)} disabled={page >= pageCount - 1}>
                <IconArrowUpRight size={18} sw={1.4} style={{ transform: 'rotate(45deg)' }} />
              </button>
            </div>
          </div>
        </div>

        <div className="test-track" ref={trackRef}>
          {tests.map((t, k) =>
          <article key={k} className="test-card">
              <div className="test-cardtop">
                <span className="test-mark" aria-hidden="true">"</span>
                <div className="test-stars" aria-label="5 out of 5 stars">
                  {[0, 1, 2, 3, 4].map((s) => <IconStar key={s} size={17} sw={0} style={{ fill: 'currentColor' }} />)}
                </div>
              </div>
              <p className="test-q">{t.q}</p>
              <div className="test-foot">
                <div className="test-avatar">
                  <img src="images/daniele/footer-logo-new.png" alt="" loading="lazy" />
                </div>
                <div className="test-who">
                  <div className="test-name">{t.service}</div>
                  <div className="test-role">{t.date}</div>
                </div>
              </div>
            </article>
          )}
        </div>

        <div className="test-dots" role="tablist">
          {Array.from({ length: pageCount }).map((_, k) =>
          <button key={k} className={`test-dot ${k === page ? 'on' : ''}`}
          onClick={() => goTo(k)} aria-label={`Go to slide ${k + 1}`} />
          )}
        </div>
      </div>
    </section>);

}

// ─────── Social / Follow us ───────
function FSocial() {
  // The studio's own Instagram content — candid treatment moments mixed with posts.
  const tiles = [
  { src: 'images/daniele/ig-back-laser.jpg', shape: 'rounded' },
  { src: 'images/daniele/laser-treatment.jpg', shape: 'arch' },
  { src: 'images/daniele/ig-room.jpg', shape: 'rounded' },
  { src: 'images/daniele/ig-leg-pro.jpg', shape: 'arch' },
  { src: 'images/daniele/ig-lounge.jpg', shape: 'rounded' }];

  return (
    <section className="social">
      <div className="social-top">
        <div className="social-top-inner">
          <span className="social-eyebrow">— Follow us</span>
          <ul className="social-links">
            <li><a href={F_INSTAGRAM} target="_blank" rel="noopener" aria-label="Daniéle on Instagram">Instagram</a></li>
            <li><a href={F_FACEBOOK} target="_blank" rel="noopener" aria-label="Daniéle on Facebook">Facebook</a></li>
          </ul>
        </div>
      </div>
      <div className="social-strip">
        <div className="social-tiles">
          {tiles.map((t, i) =>
          <a key={i} className={`social-tile social-tile--${t.shape}`} href={F_INSTAGRAM} target="_blank" rel="noopener" aria-label="View Daniéle on Instagram">
              <img src={t.src} alt="" loading="lazy" />
            </a>
          )}
        </div>
      </div>
      <div className="social-bottom">
        {/* Client's own studio: reception wordmark left, their wall quote right.
            Both are cropped low so their lettering sits below our headline, not under it. */}
        <div className="social-bottom-bg" aria-hidden="true">
          {/* Each half clips its own photo: the reception shot is rotated to level its
              wall sign, and without a clipping box that rotation spills across the seam. */}
          <div className="social-bottom-half">
            <img src="images/daniele/studio-reception.jpg" alt="Reception area at a Daniéle Laser + Aesthetics studio in Texas" loading="lazy" />
          </div>
          <div className="social-bottom-half">
            <img src="images/daniele/studio-wall.jpg" alt="Product and treatment wall inside a Daniéle Laser + Aesthetics studio" loading="lazy" />
          </div>
        </div>
        <div className="social-bottom-inner">
          {/* No headline of our own here: both photos are the studio's own lettered walls,
              and anything we set over them competes rather than reads. */}
          <div className="social-foot">
            <p className="social-blurb">
              Book a free consultation and begin your course. Fewer hairs every session — no razors, no waxing, no ingrowns.
            </p>
            {/* Both studios answer their own line, so both numbers stand here — each
                under its studio name, otherwise two bare numbers are a guessing game. */}
            <div className="social-phones">
              {F_LOCATIONS.map((l) => (
                <React.Fragment key={l.id}>
                  <span className="social-phone-item">
                    <span className="social-phone-loc">{l.name}</span>
                    <a className="social-phone" href={telHref(l.phone)}>{l.phone}</a>
                  </span>
                  <span className="social-phone-sep" aria-hidden="true"></span>
                </React.Fragment>
              ))}
              <a className="social-phone" href="contact-us.html" onClick={(e) => { e.preventDefault(); openBooking(); }}>Book a consultation</a>
            </div>
          </div>
        </div>
      </div>
    </section>);

}

// ─────── Before / After comparison slider ───────
function FBeforeAfter() {
  const [pos, setPos] = React.useState(50);
  const ref = React.useRef(null);
  const dragRef = React.useRef(false);

  const setFromX = (clientX) => {
    const r = ref.current.getBoundingClientRect();
    const next = Math.max(0, Math.min(100, ((clientX - r.left) / r.width) * 100));
    setPos(next);
  };
  const onDown = (e) => {
    dragRef.current = true;
    setFromX(e.touches ? e.touches[0].clientX : e.clientX);
    e.preventDefault();
  };
  const onMove = (e) => {
    if (!dragRef.current) return;
    setFromX(e.touches ? e.touches[0].clientX : e.clientX);
  };
  const onUp = () => { dragRef.current = false; };

  React.useEffect(() => {
    window.addEventListener('mousemove', onMove);
    window.addEventListener('touchmove', onMove, { passive: false });
    window.addEventListener('mouseup', onUp);
    window.addEventListener('touchend', onUp);
    return () => {
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('touchmove', onMove);
      window.removeEventListener('mouseup', onUp);
      window.removeEventListener('touchend', onUp);
    };
  }, []);

  return (
    <section id="results" className="results">
      <div className="container">
        <div className="results-head">
          <div>
            <span className="eyebrow">Real client results</span>
            <h2 className="h-display">
              Smoother skin, <span className="i">session by session.</span>
            </h2>
          </div>
          <p className="results-blurb">
            Drag the divider to compare. These photos show a typical underarm course at week 0 and week 24 — six sessions on a five-week rhythm.
          </p>
        </div>

        <div className="ba" ref={ref} onMouseDown={onDown} onTouchStart={onDown}>
          <img className="ba-img ba-img--before"
            src="images/o/ba-before.jpg" alt="Before treatment" />
          {/* Before label sits on the base layer, under the after overlay, so the
              after image covers (clips) it once the divider slides past it. */}
          <span className="ba-label ba-label--before" style={{ opacity: pos > 20 ? 1 : 0 }}>Before · Week 0</span>
          <div className="ba-after-wrap" style={{ clipPath: `inset(0 0 0 ${pos}%)` }}>
            <img className="ba-img ba-img--after"
              src="images/o/ba-after.jpg" alt="After 6 sessions" />
            {/* After label lives inside the clipped layer, so it's cut at the divider
                and never crosses onto the before side. */}
            <span className="ba-label ba-label--after" style={{ opacity: pos < 80 ? 1 : 0 }}>After · Week 24</span>
          </div>
          <div className="ba-divider" style={{ left: `${pos}%` }}>
            <button className="ba-handle" aria-label="Drag to compare">
              <IconArrowUpRight size={14} sw={1.5} style={{transform:'rotate(-135deg)'}} />
              <IconArrowUpRight size={14} sw={1.5} style={{transform:'rotate(45deg)'}} />
            </button>
          </div>
        </div>

        <div className="results-stats">
          <div className="results-stat">
            <span className="results-stat-n">20,000<sup>+</sup></span>
            <span className="results-stat-l">Treatments performed</span>
          </div>
          <div className="results-stat">
            <span className="results-stat-n">6</span>
            <span className="results-stat-l">Sessions in a full course</span>
          </div>
          <div className="results-stat">
            <span className="results-stat-n">4–6<sup>w</sup></span>
            <span className="results-stat-l">Between visits</span>
          </div>
          <div className="results-stat">
            <span className="results-stat-n">2</span>
            <span className="results-stat-l">Texas studios</span>
          </div>
        </div>
      </div>
    </section>
  );
}

// ─────── FAQ (accordion) ───────
function FFAQ() {
  // Questions and answers live in content-text.js (keys home.faq.N.q / .a). An entry
  // whose question has been emptied is dropped, so the studio can retire one.
  const faqs = [1, 2, 3, 4, 5, 6, 7]
    .map((n) => `home.faq.${n}`)
    .filter((k) => TX(`${k}.q`));
  const [open, setOpen] = React.useState(-1);
  return (
    <section id="faq" className="faq">
      <div className="container">
        <div className="faq-head">
          <div>
            <span className="eyebrow">Frequently asked</span>
            <h2 className="h-display">
              The <span className="i">quiet answers</span> to the questions clients ask most.
            </h2>
          </div>
          <a href="contact-us.html" className="btn">Ask us anything</a>
        </div>
        <div className="faq-grid">
        <ul className="faq-list">
          {faqs.map((k, i) => (
            <li key={k} className={`faq-item ${open === i ? 'on' : ''}`}>
              <button className="faq-q" aria-expanded={open === i} onClick={() => setOpen(open === i ? -1 : i)}>
                <span className="faq-n">{String(i + 1).padStart(2, '0')}</span>
                <TXT className="faq-qt" k={`${k}.q`} />
                <span className="faq-toggle" aria-hidden="true">
                  <IconArrowUpRight size={14} sw={1.6} />
                </span>
              </button>
              <div className="faq-a-wrap">
                <div className="faq-a-inner">
                  <TXT tag="p" className="faq-a" k={`${k}.a`} />
                </div>
              </div>
            </li>
          ))}
        </ul>
        <aside className="faq-media">
          <img src="images/daniele/treatment-room.jpg" alt="A Daniéle treatment room with the Candela GentleMax Pro laser" loading="lazy" />
        </aside>
        </div>
      </div>
    </section>
  );
}

// ─────── Booking form (before footer) ───────
const F_SERVICE_LABELS = { face: 'Face Laser', body: 'Body Smooth', bikini: 'Bikini & Brazilian', concierge: 'Concierge Full-Body', unsure: "I'd like to be advised" };

function FCTA() {
  const [form, setForm] = React.useState({ name: '', email: '', phone: '', service: '', location: F_LOCATIONS[0].id, date: '', notes: '' });
  const [sent, setSent] = React.useState(false);
  const [error, setError] = React.useState(false);
  const upd = (k) => (e) => setForm({ ...form, [k]: e.target.value });
  // Route to the chosen studio's own inbox — same location-based routing as the booking popup.
  const onSubmit = async (e) => {
    e.preventDefault();
    const loc = F_LOCATIONS.find(l => l.id === form.location) || F_LOCATIONS[0];
    const body = [
      `New consultation request — ${loc.name}`, '',
      `Name:  ${form.name}`,
      `Email: ${form.email}`,
      `Phone: ${form.phone || '—'}`,
      `Service: ${F_SERVICE_LABELS[form.service] || 'To be advised'}`,
      `Preferred date: ${form.date || '—'}`,
      form.notes ? `\nNotes: ${form.notes}` : '',
    ].join('\n');
    const subject = `Consultation request — ${loc.name}`;
    setError(false);
    try {
      await deliverBooking({ locationId: loc.id, name: form.name, email: form.email, phone: form.phone, subject, message: body });
      setSent(true);
    } catch (err) {
      setError(true);
    }
  };
  return (
    <section className="cta" id="book">
      <div className="cta-inner">
        <div className="cta-map">
          <iframe
            src={F_MAP_EMBED}
            title="Daniéle Laser + Aesthetics — Friendswood location on Google Maps"
            loading="lazy"
            referrerPolicy="no-referrer-when-downgrade"
            allowFullScreen
          ></iframe>
          <div className="cta-map-panel">
            {F_LOCATIONS.map(l => (
              <div key={l.id} className="cta-map-loc">
                <span className="cta-map-loc-n">
                  {l.name}
                  {l.opening && <em className="cta-map-soon">{l.opening}</em>}
                </span>
                <a className="cta-map-addr" href={l.mapUrl} target="_blank" rel="noopener noreferrer">
                  {l.address}
                </a>
                <span className="cta-map-hours-v">{l.hours}</span>
              </div>
            ))}
          </div>
        </div>
        <form className="cta-form" onSubmit={onSubmit}>
          <div className="cta-form-head">
            <span className="eyebrow">Book a consultation</span>
            <h2 className="h-display">
              Reserve your <span className="i">first visit.</span>
            </h2>
            <p className="cta-form-blurb">
              Tell us a little about yourself and the area you'd like to treat. We'll be in touch within two hours to suggest a quiet slot.
            </p>
          </div>

          <div className="cta-form-grid">
            <label className="field">
              <span>Full name</span>
              <input type="text" value={form.name} onChange={upd('name')} placeholder="Inès Laurent" required />
            </label>
            <label className="field">
              <span>Email</span>
              <input type="email" value={form.email} onChange={upd('email')} placeholder="ines@example.com" required />
            </label>
            <label className="field">
              <span>Phone</span>
              <input type="tel" value={form.phone} onChange={upd('phone')} placeholder="(281) 000-0000" />
            </label>
            <label className="field">
              <span>Service</span>
              <div className="field-select">
                <select value={form.service} onChange={upd('service')} required>
                  <option value="">Choose a service…</option>
                  <option value="face">Face Laser</option>
                  <option value="body">Body Smooth</option>
                  <option value="bikini">Bikini & Brazilian</option>
                  <option value="concierge">Concierge Full-Body</option>
                  <option value="unsure">I'd like to be advised</option>
                </select>
                <IconArrowUpRight size={14} sw={1.5} style={{ transform: 'rotate(135deg)' }} />
              </div>
            </label>
            <label className="field field--full">
              <span>Studio</span>
              <div className="field-select">
                <select value={form.location} onChange={upd('location')} required>
                  {F_LOCATIONS.map(l => <option key={l.id} value={l.id}>{l.name}{l.opening ? ` (${l.opening})` : ''}</option>)}
                </select>
                <IconArrowUpRight size={14} sw={1.5} style={{ transform: 'rotate(135deg)' }} />
              </div>
            </label>
            <label className="field field--full">
              <span>Preferred date</span>
              <input type="date" className="date-input" value={form.date} min={new Date().toISOString().split('T')[0]} onChange={upd('date')} />
            </label>
            <label className="field field--full">
              <span>A note for your practitioner <em>(optional)</em></span>
              <textarea rows={3} value={form.notes} onChange={upd('notes')} placeholder="Anything you'd like us to know in advance — skin sensitivities, previous treatments, time-of-day preference." />
            </label>
          </div>

          <div className="cta-form-foot">
            <label className="cta-form-consent">
              <input type="checkbox" defaultChecked />
              <span>I agree to receive a reply by email or phone.</span>
            </label>
            <button type="submit" className="btn cta-form-submit">Send &amp; book</button>
          </div>
          {sent && (
            <p className="cta-form-sent" role="status">
              Thank you — your request is on its way to our {F_LOCATIONS.find(l => l.id === form.location)?.name} studio. We'll reply within two hours.
            </p>
          )}
          {error && (
            <p className="cta-form-sent cta-form-error" role="alert">
              Something went wrong sending your request. Please call us at {F_PHONE} or try again.
            </p>
          )}
        </form>
      </div>
    </section>);

}

// ─────── Footer ───────
function FFooter() {
  const [email, setEmail] = React.useState('');
  const onSubmit = (e) => {e.preventDefault();setEmail('');};
  const year = new Date().getFullYear();
  return (
    <footer id="contact">
      <div className="footer-cta">
        <div className="container">
          <div className="footer-cta-row">
            <div>
              <span className="footer-eyebrow">— Let's begin</span>
              <h2 className="footer-h h-display">
                Ready for a quieter, smoother <span className="i">you?</span>
              </h2>
            </div>
            <a href="contact-us.html" className="btn footer-cta-btn" onClick={(e) => { e.preventDefault(); openBooking(); }}>Book a consultation</a>
          </div>
        </div>
      </div>

      <div className="container">
        <div className="footer-mid">
          <div className="footer-col footer-col--brand">
            <span className="footer-logo">
              <img src="images/daniele/footer-logo-new.png" alt="Daniéle Laser + Aesthetics" />
            </span>
            <p className="footer-brand-blurb">
              Luxury laser hair removal on the newest Candela GentleMax Pro. Licensed specialists, two Texas studios, over 20,000 treatments performed.
            </p>
            <div className="footer-phones">
              {F_LOCATIONS.map((l) => (
                <span className="footer-phone-item" key={l.id}>
                  <span className="footer-phone-loc">{l.name}</span>
                  <a className="footer-phone" href={telHref(l.phone)}>{l.phone}</a>
                </span>
              ))}
            </div>
            <div className="footer-social">
              <a href={F_INSTAGRAM} target="_blank" rel="noopener noreferrer">Instagram</a>
              <a href={F_FACEBOOK} target="_blank" rel="noopener noreferrer">Facebook</a>
            </div>
          </div>

          <div className="footer-col">
            <h5>Friendswood</h5>
            <div className="footer-loc-item">
              <span className="footer-loc-addr">1769 S. Friendswood Dr. Suite 113<br />Friendswood, TX 77546</span>
              <span className="footer-loc-addr">Mon–Fri 10–7 · Sat 10–5 · Sun closed</span>
              <a className="footer-loc-mail" href={telHref(F_LOCATIONS[0].phone)}>{F_LOCATIONS[0].phone}</a>
              <a className="footer-loc-mail" href="mailto:corporate@danielefriendswood.com">corporate@danielefriendswood.com</a>
              <a className="footer-loc-link" href={F_LOCATIONS[0].mapUrl} target="_blank" rel="noopener noreferrer">
                Get directions <IconArrowUpRight size={11} sw={1.6} />
              </a>
            </div>
          </div>

          <div className="footer-col">
            <h5>Cypress <span className="footer-loc-soon">Now open</span></h5>
            <div className="footer-loc-item">
              <span className="footer-loc-addr">20049 House Hahl Rd. Suite 113<br />Cypress, TX 77433</span>
              <span className="footer-loc-addr">Wed–Fri 10–7 · Sat 10–5 · Sun closed</span>
              <a className="footer-loc-mail" href={telHref(F_LOCATIONS[1].phone)}>{F_LOCATIONS[1].phone}</a>
              <a className="footer-loc-mail" href="mailto:corporate@danielecypress.com">corporate@danielecypress.com</a>
              <a className="footer-loc-link" href={F_LOCATIONS[1].mapUrl} target="_blank" rel="noopener noreferrer">
                Get directions <IconArrowUpRight size={11} sw={1.6} />
              </a>
            </div>
          </div>

          <div className="footer-col">
            <h5>Sitemap</h5>
            <ul>
              <li><a href="index.html">Home</a></li>
              <li><a href="about-us.html">About Us</a></li>
              <li><a href="laser-hair-removal.html">Laser Hair Removal</a></li>
              <li><a href="aesthetic-treatments.html">Aesthetic Treatments</a></li>
              <li><a href="payment-plans.html">Payment Plans</a></li>
              <li><a href="contact-us.html">Contact Us</a></li>
            </ul>
            <a className="footer-loc-link" href={F_GIFT_CARD_URL} target="_blank" rel="noopener noreferrer">
              Gift cards <IconArrowUpRight size={11} sw={1.6} />
            </a>
          </div>
        </div>

        <div className="footer-bottom">
          <span>© {year} daniéle laser + aesthetics · All rights reserved.</span>
          <span className="footer-legal">
            <a href="privacy-policy.html">Privacy</a>
            <a href="cancellation-refund-policy.html">Cancellation &amp; refunds</a>
            <a href="customer-care-policy.html">Customer care</a>
          </span>
          <span className="footer-made">Friendswood &amp; Cypress, Texas</span>
        </div>
      </div>
    </footer>);

}

// ─────── Immersive cinematic interlude (signature moment) ───────
function FImmersive() {
  const secRef = React.useRef(null);
  const mediaRef = React.useRef(null);
  const [entered, setEntered] = React.useState(false);

  React.useEffect(() => {
    const sec = secRef.current;
    const media = mediaRef.current;
    if (!sec || !media) return;
    let raf;
    let done = false;
    const apply = () => {
      const r = sec.getBoundingClientRect();
      const vh = window.innerHeight;
      const prog = (vh - r.top) / (vh + r.height); // 0..1 as it crosses the viewport
      const y = (Math.min(1, Math.max(0, prog)) - 0.5) * 34; // -17% … +17% drift
      media.style.transform = "translate3d(0, " + y + "%, 0)";
      if (!done && r.top < vh * 0.8) { done = true; setEntered(true); }
    };
    const onScroll = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(apply); };
    window.addEventListener("scroll", onScroll, { passive: true });
    apply();

    return () => {
      window.removeEventListener("scroll", onScroll);
      cancelAnimationFrame(raf);
    };
  }, []);

  return (
    <section ref={secRef} className={"immersive" + (entered ? " in" : "")} aria-label="Considered light">
      <div ref={mediaRef} className="immersive-media">
        <img src="images/daniele/laser-img.png" alt="Laser hair removal on the Candela GentleMax Pro at Daniéle Laser + Aesthetics" />
      </div>
      <div className="immersive-scrim" aria-hidden="true"></div>
      <div className="immersive-content">
        <span className="immersive-eyebrow"><span>— The Daniéle hour</span></span>
        <h2 className="immersive-h">
          <span className="immersive-line"><span>Where precision</span></span>
          <span className="immersive-line"><span>becomes <em className="i">calm.</em></span></span>
        </h2>
        <p className="immersive-caption">An unhurried hour, considered light, and skin that quietly remembers the difference.</p>
        {F_SHOW_PRICING
          ? <a href="#calculator" className="immersive-cta">Build your course</a>
          : <a href="contact-us.html" className="immersive-cta"
              onClick={(e) => { e.preventDefault(); openBooking(); }}>Book a consultation</a>}
      </div>
    </section>
  );
}

function useScrollReveal() {
  React.useEffect(() => {
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    const sel = ".services-head, .calc-head, .why-head, .faq-head, .reviews-head, .results-head, .cta-form-head, .section-head" +
      // Sub-page content blocks (laser / aesthetic / about / contact) — these
      // classes only exist on the standalone pages, so the home App is unaffected.
      ", .laser-intro-l, .laser-intro-media, .aesthetic-card, .zone-row, .zones-mix, .zones-cta" +
      ", .exp-step, .about-intro-r, .about-intro-media, .about-feats, .contact-panel, .legal-inner";
    const els = Array.prototype.slice.call(document.querySelectorAll(sel));
    document.documentElement.classList.add("reveal-on");
    els.forEach(function (el) { el.classList.add("rv"); });
    const io = new IntersectionObserver(function (entries) {
      entries.forEach(function (e) {
        if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); }
      });
    }, { threshold: 0.18, rootMargin: "0px 0px -8% 0px" });
    els.forEach(function (el) { io.observe(el); });

    // Scroll/resize fallback: IntersectionObserver is throttled in some contexts
    // (backgrounded tabs, occasional iOS bfcache restores). Since every element
    // here starts at opacity:0, a missed IO would leave content invisible — the
    // geometry check on scroll guarantees it always reveals.
    const revealIfVisible = function () {
      const vh = window.innerHeight || document.documentElement.clientHeight;
      els.forEach(function (el) {
        if (el.classList.contains("in")) return;
        const r = el.getBoundingClientRect();
        if (r.top < vh * 0.92 && r.bottom > 0) { el.classList.add("in"); io.unobserve(el); }
      });
    };
    window.addEventListener("scroll", revealIfVisible, { passive: true });
    window.addEventListener("resize", revealIfVisible);
    revealIfVisible();

    return () => {
      io.disconnect();
      window.removeEventListener("scroll", revealIfVisible);
      window.removeEventListener("resize", revealIfVisible);
    };
  }, []);
}

// Shared page chrome for the standalone sub-pages (pages.jsx): applies the default
// palette/font CSS vars and wires the same scroll-reveal the home App uses. The home
// page keeps its own tweakable version inside FApp; sub-pages just use the defaults.
function usePageChrome() {
  useScrollReveal();
  React.useEffect(() => {
    const root = document.documentElement;
    const p = F_PALETTES[F_TWEAK_DEFAULTS.palette] || F_PALETTES.cream;
    Object.entries(p.vars).forEach(([k, v]) => root.style.setProperty(k, v));
    const f = F_FONTS[F_TWEAK_DEFAULTS.fonts] || F_FONTS.cormorant;
    root.style.setProperty('--font-display', f.display);
    root.style.setProperty('--font-body', f.body);
  }, []);
}

// ─────── App root ───────
function FApp() {
  const [t, setTweak] = useTweaks(F_TWEAK_DEFAULTS);
  useScrollReveal();
  React.useEffect(() => {
    const root = document.documentElement;
    const p = F_PALETTES[t.palette] || F_PALETTES.cream;
    Object.entries(p.vars).forEach(([k, v]) => root.style.setProperty(k, v));
    const f = F_FONTS[t.fonts] || F_FONTS.cormorant;
    root.style.setProperty('--font-display', f.display);
    root.style.setProperty('--font-body', f.body);
  }, [t.palette, t.fonts]);

  return (
    <>
      <ScrollProgress />
      <FGiftBar />
      <FNav />
      <FHero />
      <FStatement />
      <FServices />
      <FWordMarquee />
      <FImmersive />
      <FCalculator />
      <FWarranty />
      <FWhy />
      <FBeforeAfter />
      <FWordMarquee tone="bronze" />
      <FFAQ />
      <FVideoBanner />
      <FTestimonials />
      <FSocial />
      <FWordMarquee tone="bronze" />
      <FCTA />
      <FFooter />

      <TweaksPanel title="Tweaks">
        <TweakSection label="Color palette" />
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
          {Object.entries(F_PALETTES).map(([k, p]) =>
          <button key={k} onClick={() => setTweak('palette', k)}
          style={{ display: 'flex', flexDirection: 'column', gap: 6, padding: 8,
            border: '1px solid ' + (t.palette === k ? 'rgba(41,38,27,0.7)' : 'rgba(0,0,0,0.1)'),
            borderRadius: 8, background: t.palette === k ? 'rgba(0,0,0,0.04)' : 'transparent',
            cursor: 'pointer', textAlign: 'left', font: 'inherit', color: 'inherit' }}>
              <div style={{ display: 'flex', gap: 3, height: 22 }}>
                {p.swatch.map((c, i) =>
              <div key={i} style={{ flex: 1, background: c, borderRadius: 3, border: '0.5px solid rgba(0,0,0,0.08)' }}></div>
              )}
              </div>
              <span style={{ fontSize: 10, color: 'rgba(41,38,27,0.7)' }}>{p.label}</span>
            </button>
          )}
        </div>
        <TweakSection label="Type pair" />
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
          {Object.entries(F_FONTS).map(([k, f]) =>
          <button key={k} onClick={() => setTweak('fonts', k)}
          style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', padding: '10px 12px',
            borderRadius: 8, border: '1px solid ' + (t.fonts === k ? 'rgba(41,38,27,0.7)' : 'rgba(0,0,0,0.1)'),
            background: t.fonts === k ? 'rgba(0,0,0,0.04)' : 'transparent', cursor: 'pointer',
            textAlign: 'left', font: 'inherit', color: 'inherit', width: '100%' }}>
              <span style={{ fontFamily: f.display, fontSize: 24, lineHeight: 1, fontStyle: 'italic' }}>Smooth.</span>
              <span style={{ fontFamily: f.body, fontSize: 10, color: 'rgba(41,38,27,0.6)', marginTop: 4, letterSpacing: '0.04em' }}>{f.label}</span>
            </button>
          )}
        </div>
      </TweaksPanel>
    </>);

}

// Multi-page site: each HTML sets #root[data-page]. The home page ('home' or no
// attribute) renders the full FApp here; the sub-pages are rendered by pages.jsx,
// which no-ops on 'home'. Both scripts load on every page, so this guard keeps them
// from fighting over #root.
(function () {
  const rootEl = document.getElementById('root');
  const page = (rootEl && rootEl.dataset.page) || 'home';
  if (page === 'home') {
    ReactDOM.createRoot(rootEl).render(<FApp />);
    // Arriving with a hash (e.g. from a sub-page footer link -> index.html#services):
    // the target section only exists after the app renders, so the browser's native
    // hash scroll misses it. Retry until the anchor mounts, then scroll to it.
    if (window.location.hash && window.location.hash.length > 1) {
      const id = decodeURIComponent(window.location.hash.slice(1));
      let tries = 0;
      const tryScroll = () => {
        const el = document.getElementById(id);
        if (el) { el.scrollIntoView({ behavior: 'smooth', block: 'start' }); }
        else if (tries++ < 40) { setTimeout(tryScroll, 100); }
      };
      setTimeout(tryScroll, 150);
    }
  }
})();