// components.jsx — shared building blocks: Placeholder, icons, Nav, Hero, sections.

const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ── Stripe-pattern placeholder (used everywhere photos would go) ────────
// kind controls the pattern + base hue offset; label is the monospace
// caption rendered at the bottom-left.
function Placeholder({ kind = 'colour', label = '', ratio = '4/5', accent = 0, dense = false }) {
  // The pattern is a 4-stop diagonal hatch tinted by --color-primary;
  // accent index lets us subtly differ adjacent placeholders so they
  // don't read as identical tiles.
  const angle = [-22, 18, -8, 32][accent % 4];
  const tint  = ['var(--c-primary)', 'var(--c-primaryDark)', 'var(--c-accent)', 'var(--c-primary)'][accent % 4];
  return (
    <div className="ph" style={{ aspectRatio: ratio }}>
      <div className="ph-pattern" style={{
        '--ph-tint': tint, '--ph-angle': `${angle}deg`,
        backgroundImage: `repeating-linear-gradient(var(--ph-angle),
          color-mix(in srgb, var(--ph-tint) 18%, transparent) 0 1px,
          transparent 1px 7px),
          linear-gradient(135deg,
          color-mix(in srgb, var(--ph-tint) 22%, var(--c-surfaceAlt)) 0%,
          color-mix(in srgb, var(--ph-tint) 8%, var(--c-surfaceAlt)) 100%)`,
      }} />
      <div className="ph-meta">
        <span className="ph-kind">[ {kind} ]</span>
        {label && <span className="ph-label">{label}</span>}
      </div>
    </div>
  );
}

// ── Real image with Placeholder fallback ───────────────────────────────
// Drop-in replacement for Placeholder when a real URL is available.
// fit: 'cover' crops to the box (default). 'contain' letterboxes instead —
// use it for composed images (before/after collages) where a crop would cut
// half the comparison away.
function SiteImage({ src, ratio = '4/5', kind = 'colour', label = '', accent = 0, alt = '', fit = 'cover' }) {
  const [err, setErr] = React.useState(false);
  if (!src || err) return <Placeholder ratio={ratio} kind={kind} label={label} accent={accent}/>;
  return (
    <div className="ph" style={{ aspectRatio: ratio }}>
      <img src={src} alt={alt || label}
           style={{ width: '100%', height: '100%', objectFit: fit, display: 'block' }}
           onError={() => setErr(true)}/>
    </div>
  );
}

// ── Stylist avatar — initials when no photo available ───────────────────
function StylistAvatar({ member, ratio = '3/4' }) {
  const [err, setErr] = React.useState(false);
  const initials = member.name.split(' ').map(w => w[0]).join('').slice(0, 2).toUpperCase();
  const tints = ['var(--c-primary)', 'var(--c-primaryDark)', 'var(--c-accent)', 'var(--c-primary)'];
  const tint = tints[member.accent % 4];
  if (member.photoUrl && !err) {
    return (
      <div className="ph" style={{ aspectRatio: ratio }}>
        <img src={member.photoUrl} alt={member.name}
             style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
             onError={() => setErr(true)}/>
      </div>
    );
  }
  return (
    <div className="ph stylist-avatar" style={{ aspectRatio: ratio,
      background: `color-mix(in srgb, ${tint} 15%, var(--c-surfaceAlt))`,
      display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
      <span style={{ fontSize: 'clamp(2rem,8cqw,4rem)', fontFamily: 'var(--font-heading)',
                     fontWeight: 'var(--font-heading-weight)',
                     color: `color-mix(in srgb, ${tint} 70%, var(--c-text))`,
                     letterSpacing: '-0.02em', lineHeight: 1 }}>{initials}</span>
    </div>
  );
}

// ── Tiny inline icons (no library — keeps the bundle clean) ─────────────
const Icon = {
  arrow:    (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><path d="M5 12h14M13 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round"/></svg>,
  star:     (p) => <svg viewBox="0 0 24 24" fill="currentColor" {...p}><path d="M12 2l3 6.9 7.5.6-5.7 4.9 1.8 7.3L12 17.8 5.4 21.7l1.8-7.3L1.5 9.5 9 8.9z"/></svg>,
  phone:    (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" {...p}><path d="M5 4.5C5 3.7 5.7 3 6.5 3h2c.6 0 1.1.4 1.3.9l1.1 3.2c.2.5 0 1.1-.4 1.4l-1.5 1.1c1 2.5 3 4.5 5.5 5.5l1.1-1.5c.3-.4.9-.6 1.4-.4l3.2 1.1c.5.2.9.7.9 1.3v2c0 .8-.7 1.5-1.5 1.5C10.3 19 5 13.7 5 4.5z"/></svg>,
  whatsapp: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" {...p}><path d="M3.5 20.5l1.5-4.7A8.5 8.5 0 1112 20.5a8.5 8.5 0 01-4.3-1.2l-4.2 1.2z"/><path d="M8 10c.4 1.7 1.5 3.3 3 4.5 1.5 1.2 3.3 1.8 5 1.5l1-2-2.5-1-1 1.5c-1.5-.5-2.5-1.5-3-3l1.5-1-1-2.5-2 1z" strokeLinejoin="round"/></svg>,
  pin:      (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><path d="M12 22s7-6 7-12a7 7 0 10-14 0c0 6 7 12 7 12z"/><circle cx="12" cy="10" r="2.5"/></svg>,
  clock:    (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2" strokeLinecap="round"/></svg>,
  ig:       (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" {...p}><rect x="3" y="3" width="18" height="18" rx="5"/><circle cx="12" cy="12" r="4"/><circle cx="17.3" cy="6.7" r=".9" fill="currentColor"/></svg>,
  fb:       (p) => <svg viewBox="0 0 24 24" fill="currentColor" {...p}><path d="M13 22v-8h3l.5-4H13V7.5c0-1.1.3-1.9 2-1.9h2V2.1A28 28 0 0014.4 2C11.7 2 10 3.7 10 6.9V10H7v4h3v8z"/></svg>,
  check:    (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" {...p}><path d="M4 12l5 5L20 6" strokeLinecap="round" strokeLinejoin="round"/></svg>,
  x:        (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" {...p}><path d="M6 6l12 12M18 6L6 18" strokeLinecap="round"/></svg>,
  chevL:    (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" {...p}><path d="M14 6l-6 6 6 6" strokeLinecap="round" strokeLinejoin="round"/></svg>,
  chevR:    (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" {...p}><path d="M10 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round"/></svg>,
  scissors: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><circle cx="6" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M20 4L8.5 15.5M20 20L8.5 8.5"/></svg>,
  sun:      (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" {...p}><circle cx="12" cy="12" r="4.2"/><path d="M12 2.5v2.5M12 19v2.5M4.2 4.2l1.8 1.8M18 18l1.8 1.8M2.5 12H5M19 12h2.5M4.2 19.8L6 18M18 6l1.8-1.8" strokeLinecap="round"/></svg>,
  moon:     (p) => <svg viewBox="0 0 24 24" fill="currentColor" {...p}><path d="M20 14.5A8.5 8.5 0 019.5 4a7 7 0 100 16 8.5 8.5 0 0010.5-5.5z"/></svg>,
  diploma:  (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><circle cx="12" cy="9" r="5"/><path d="M9.5 13.5L8 21l4-2 4 2-1.5-7.5" strokeLinecap="round" strokeLinejoin="round"/></svg>,
  globe:    (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c2.5 2.6 2.5 15.4 0 18M12 3c-2.5 2.6-2.5 15.4 0 18"/></svg>,
  chevD:    (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" {...p}><path d="M6 9l6 6 6-6" strokeLinecap="round" strokeLinejoin="round"/></svg>,
};

// ── Language picker ─────────────────────────────────────────────────────
// Most visitors never open this: detectVisitorLang() in app.jsx already
// matched them against navigator.languages on first paint. It exists for
// the ones it guessed wrong, so the job is to be findable, not prominent.
// The list is written in each language's own name because that is what
// someone looking for their language actually scans for; the two-letter
// code rides along as a badge for people who scan for that instead.
function LangPicker({ lang, setLang }) {
  const langs = window.SITE.LANGS;
  const [open, setOpen] = useState(false);
  const wrapRef = useRef(null);
  const current = langs.find(l => l.code === lang) || langs[0];

  useEffect(() => {
    if (!open) return;
    const onDown = (e) => {
      if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
    };
    const onKey = (e) => { if (e.key === 'Escape') setOpen(false); };
    document.addEventListener('pointerdown', onDown);
    window.addEventListener('keydown', onKey);
    return () => {
      document.removeEventListener('pointerdown', onDown);
      window.removeEventListener('keydown', onKey);
    };
  }, [open]);

  return (
    <div className="lang" ref={wrapRef}>
      <button className={`lang-btn ${open ? 'is-open' : ''}`}
              onClick={() => setOpen(o => !o)}
              aria-haspopup="listbox" aria-expanded={open}
              aria-label={`${current.native}. Change language`}
              title={current.native}>
        <Icon.globe width={15} height={15}/>
        <span className="lang-btn-code">{current.label}</span>
        <Icon.chevD width={11} height={11} className="lang-btn-caret"/>
      </button>

      {open && (
        <ul className="lang-menu" role="listbox" aria-label="Language">
          {langs.map(l => (
            <li key={l.code}>
              <button type="button" role="option" aria-selected={l.code === lang}
                      lang={l.code}
                      className={`lang-opt ${l.code === lang ? 'is-active' : ''}`}
                      onClick={() => { setLang(l.code); setOpen(false); }}>
                <span className="lang-opt-native">{l.native}</span>
                {l.code === lang
                  ? <Icon.check width={13} height={13}/>
                  : <span className="lang-opt-code">{l.label}</span>}
              </button>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

// ── Stars ───────────────────────────────────────────────────────────────
function Stars({ value = 5, size = 14 }) {
  return (
    <span className="stars" style={{ fontSize: size }}>
      {Array.from({ length: 5 }).map((_, i) =>
        <Icon.star key={i} width={size} height={size} style={{
          color: i < value ? 'var(--c-gold)' : 'var(--c-border)',
          marginRight: 1,
        }}/>
      )}
    </span>
  );
}

// ── Translation helper ──────────────────────────────────────────────────
const t = (obj, lang) => {
  if (obj == null) return '';
  if (typeof obj === 'string') return obj;
  return obj[lang] ?? obj.en ?? '';
};

// ── Portfolio embed mode helpers ────────────────────────────────────────
// When window.SITE.PORTFOLIO.enabled is true, the site is being shown
// inside the studio's portfolio. Real CTAs (booking, WhatsApp, phone,
// email) should not fire — interceptCta() wraps any handler with an
// alert + early return.
function usePortfolio() {
  const p = (window.SITE && window.SITE.PORTFOLIO) || { enabled: false };
  return p;
}

// Wrap an onClick (or any event handler) so that, in portfolio mode, it
// prevents the default action and shows an alert pointing to the live
// site. Pass the handler you would normally use; in normal mode it is
// returned unchanged.
function interceptCta(originalHandler, lang = 'en') {
  const p = (window.SITE && window.SITE.PORTFOLIO) || { enabled: false };
  if (!p.enabled) return originalHandler;
  return (e) => {
    if (e && e.preventDefault) e.preventDefault();
    if (e && e.stopPropagation) e.stopPropagation();
    const msg = t(p.ctaBlocked, lang);
    const live = p.liveUrl && p.liveUrl !== '{{LIVE_URL}}' ? `\n\n${p.liveUrl}` : '';
    // eslint-disable-next-line no-alert
    window.alert(`${msg}${live}`);
  };
}

// ── Portfolio mode banner ───────────────────────────────────────────────
// Fixed bar at the top of the page that announces this is a demo and
// links out to the real site. Rendered only when PORTFOLIO.enabled.
function PortfolioBanner({ lang }) {
  const p = usePortfolio();
  if (!p.enabled) return null;
  const hasLive = p.liveUrl && p.liveUrl !== '{{LIVE_URL}}';
  const hasStudio = p.studioUrl && p.studioUrl !== '{{STUDIO_URL}}';
  return (
    <div className="portfolio-banner" role="note">
      <span className="portfolio-banner-tag">DEMO</span>
      <span className="portfolio-banner-msg">
        {t(p.banner, lang)}{' '}
        {hasLive ? (
          <a href={p.liveUrl} target="_blank" rel="noreferrer" className="portfolio-banner-link">
            {p.liveUrl.replace(/^https?:\/\//, '')}
          </a>
        ) : <em>(liveUrl not set)</em>}
      </span>
      {hasStudio && (
        <a href={p.studioUrl} target="_blank" rel="noreferrer" className="portfolio-banner-studio">
          {p.studioName || 'Studio'} →
        </a>
      )}
    </div>
  );
}

// ── Sticky nav ──────────────────────────────────────────────────────────
function Nav({ lang, setLang, theme, setTheme, onBook, onNav, view, business }) {
  const [scrolled, setScrolled] = useState(false);
  const [activeSec, setActiveSec] = useState('hero');

  useEffect(() => {
    const onScroll = () => {
      setScrolled(window.scrollY > 16);
      const sections = ['hero', 'services', 'gallery', 'about', 'reviews', 'book', 'visit'];
      let current = 'hero';
      for (const id of sections) {
        const el = document.getElementById(id);
        if (el && el.getBoundingClientRect().top < window.innerHeight * 0.4) current = id;
      }
      setActiveSec(current);
    };
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, [view]);

  const C = window.SITE.COPY.nav;
  const items = [
    ['services', t(C.services, lang)],
    ['gallery',  t(C.gallery,  lang)],
    ['about',    t(C.team,     lang)],
    ['reviews',  t(C.reviews,  lang)],
    // "Contact" points at the booking section (id="book"), which carries
    // the phone / WhatsApp / address details.
    ['book',     t(C.contact,  lang)],
    ['visit',    t(C.visit,    lang)],
  ];
  // Logo sits centered, between the two halves of the menu links.
  const mid = Math.ceil(items.length / 2);
  const renderLinks = (group) => group.map(([id, label]) => (
    <li key={id}>
      <a href={`#${id}`} className={activeSec === id ? 'is-active' : ''}
         onClick={(e) => { e.preventDefault(); onNav('home', id); }}>{label}</a>
    </li>
  ));

  return (
    <nav className={`nav ${scrolled || view !== 'home' ? 'nav--scrolled' : ''}`}>
      {view === 'home'
        ? <ul className="nav-links nav-links--left">{renderLinks(items.slice(0, mid))}</ul>
        : <div className="nav-links nav-links--left" />}

      <a className="nav-brand" href="#hero" onClick={(e) => { e.preventDefault(); onNav('home', 'hero'); }}>
        <HairCoLogo size="nav"/>
      </a>

      {view === 'home'
        ? <ul className="nav-links nav-links--right">{renderLinks(items.slice(mid))}</ul>
        : <div className="nav-links nav-links--right" />}

      <div className="nav-actions">
        <button className="nav-theme"
                onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
                aria-label={theme === 'dark'
                  ? t({ en: 'Switch to light theme', pt: 'Mudar para tema claro',
                        es: 'Cambiar al tema claro', fr: 'Passer au thème clair' }, lang)
                  : t({ en: 'Switch to dark theme', pt: 'Mudar para tema escuro',
                        es: 'Cambiar al tema oscuro', fr: 'Passer au thème sombre' }, lang)}
                title={theme === 'dark'
                  ? t({ en: 'Light theme', pt: 'Tema claro', es: 'Tema claro', fr: 'Thème clair' }, lang)
                  : t({ en: 'Dark theme', pt: 'Tema escuro', es: 'Tema oscuro', fr: 'Thème sombre' }, lang)}>
          {theme === 'dark'
            ? <Icon.sun width={17} height={17}/>
            : <Icon.moon width={16} height={16}/>}
        </button>
        <LangPicker lang={lang} setLang={setLang}/>
        <button className="btn btn-primary nav-cta" onClick={onBook}>
          {t(C.book, lang)} <Icon.arrow width={14} height={14}/>
        </button>
      </div>
    </nav>
  );
}

// ── Hero ────────────────────────────────────────────────────────────────
function Hero({ lang, category, onBook, onScrollTo, palette }) {
  const C = window.SITE.COPY.hero;
  const cat = window.SITE.CATEGORIES[category];
  const B = window.SITE.BUSINESS;
  return (
    <header id="hero" className="hero" data-reveal-group>
      <div className="hero-left">
        <div className="hero-eyebrow" data-reveal="up">{t(C.eyebrow, lang)}</div>
        <h1 className="hero-title" data-reveal="up">{(() => {
          // Split on the FIRST comma only; everything after becomes the italic line.
          // Re-joining on ',' (as we did before) re-inserts any later commas the
          // regex stripped out — producing "Galway,." in the rendered title.
          const full = t(cat.tagline, lang).replace(/\.$/, '');
          const ci = full.indexOf(',');
          const head = ci >= 0 ? full.slice(0, ci) : full;
          const tail = ci >= 0 ? full.slice(ci + 1).trim() : '';
          return (<>
            <span className="hero-title-line">{head}{tail && ','}</span>
            {tail && <span className="hero-title-line hero-title-italic">{tail}.</span>}
          </>);
        })()}</h1>
        <p className="hero-lede" data-reveal="up">{t(cat.about, lang)}</p>

        <div className="hero-meta" data-reveal="up">
          <div className="hero-rating">
            <Stars value={5} size={15}/>
            <strong>{B.totalScore}</strong>
            <span className="hero-rating-cap">· {B.reviewsCount} {t(C.rating, lang)}</span>
          </div>
          <div className="hero-divider"/>
          <div className="hero-walkin">
            <span className="hero-walkin-dot"/>{t(C.walkin, lang)}
          </div>
        </div>

        <div className="hero-actions" data-reveal="up">
          <button className="btn btn-primary btn-lg" onClick={onBook}>
            {t(C.primaryCta, lang)} <Icon.arrow width={16} height={16}/>
          </button>
          <button className="btn btn-ghost btn-lg" onClick={() => onScrollTo('gallery')}>
            {t(C.secondary, lang)}
          </button>
        </div>
      </div>

      <div className="hero-right" data-reveal="blur">
        {B.heroVideo ? (
          <div className="ph hero-video-wrap" style={{ aspectRatio: '3/4' }}>
            <video className="hero-video" autoPlay muted loop playsInline
                    preload="metadata" poster={B.coverImage}
                    aria-label={`${B.name}, ${B.address || ''}`}>
              {B.heroVideoWebm && <source src={B.heroVideoWebm} type="video/webm"/>}
              <source src={B.heroVideo} type="video/mp4"/>
            </video>
          </div>
        ) : (
          <SiteImage src={B.coverImage} ratio="3/4" kind="hero"
                     label={`${B.name} · ${B.area || ''}`}
                     alt={`${B.name}, ${B.address || ''}`}
                     accent={1}/>
        )}
        <div className="hero-badge">
          <div className="hero-badge-num">{B.established
            ? new Date().getFullYear() - B.established
            : `${B.totalScore}★`}</div>
          <div className="hero-badge-lbl">{B.established
            ? (lang === 'en' ? `years in ${B.area}` : `anos em ${B.area}`)
            : (lang === 'en' ? `${B.reviewsCount} Google reviews` : `${B.reviewsCount} avaliações Google`)}</div>
        </div>
      </div>
    </header>
  );
}

// ── About me — portrait + Andreia's own words + the headline numbers ────
function About({ lang, onProfile }) {
  const C = window.SITE.COPY.about;
  const me = window.SITE.TEAM[0];
  const B = window.SITE.BUSINESS;
  const stats = [
    [C.meStat1Big, C.meStat1Lbl],
    [C.meStat2Big, C.meStat2Lbl],
    [C.meStat3Big, C.meStat3Lbl],
  ].filter(([big]) => big);
  return (
    <section id="about" className="about">
      <div className="about-inner">
        <figure className="about-photo" data-reveal="blur">
          <SiteImage src={me.photoUrl} ratio="613/792" kind="portrait"
                     label={me.name} alt={`${me.name}, ${t(me.role, lang)}`} accent={2}/>
          <figcaption className="about-photo-tag">
            <span className="about-photo-name">{me.name}</span>
            <span className="about-photo-role">{t(me.role, lang)}</span>
          </figcaption>
        </figure>

        <div className="about-copy">
          <div className="sec-eyebrow" data-reveal="up">{t(C.eyebrow, lang)}</div>
          <h2 className="about-title" data-reveal="up">{t(C.title, lang)}</h2>
          {[C.line1, C.line2, C.line3].filter(Boolean).map((line, i) => (
            <p key={i} className="about-line" data-reveal="up">{t(line, lang)}</p>
          ))}
          {C.line4 && (
            <p className="about-line about-line--mark" data-reveal="up">{t(C.line4, lang)}</p>
          )}

          {me.credentials && me.credentials.length > 0 && (
            <div className="about-creds" data-reveal="up">
              <div className="about-creds-lbl">{t(C.credentialsLbl, lang)}</div>
              <ul className="about-creds-list">
                {me.credentials.map((c, i) => (
                  <li key={i}>
                    <Icon.diploma width={15} height={15}/>
                    <span>{t(c, lang)}</span>
                  </li>
                ))}
              </ul>
            </div>
          )}

          <div className="about-actions" data-reveal="up">
            {onProfile && (
              <button className="btn btn-outline btn-sm" onClick={() => onProfile(me.id)}>
                {t(window.SITE.COPY.team.profile, lang)} <Icon.arrow width={12} height={12}/>
              </button>
            )}
            {B.instagram && (
              <a className="about-ig" href={B.instagram} target="_blank" rel="noreferrer"
                 onClick={interceptCta(undefined, lang)}>
                <Icon.ig width={16} height={16}/> {t(C.follow, lang)}
              </a>
            )}
          </div>
          <div className="about-stats" data-reveal-group>
            {stats.map(([big, lbl], i) => (
              <div key={i} className="about-stat" data-reveal="scale">
                <div className="about-stat-num">{t(big, lang)}</div>
                <div className="about-stat-lbl">{t(lbl, lang)}</div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
}

// ── Walk-in banner (module — togglable) ─────────────────────────────────
function WalkInBanner({ lang }) {
  return (
    <div className="walkin-banner" data-reveal="fade">
      <span className="walkin-dot"/>
      <span>{t(window.SITE.COPY.walkInBanner.line, lang)}</span>
    </div>
  );
}

// ── Services ────────────────────────────────────────────────────────────
function Services({ lang, category, onBook, onViewAll, anchored = true }) {
  const C = window.SITE.COPY.services;
  const cat = window.SITE.CATEGORIES[category];
  const [active, setActive] = useState(cat.services[0].id);
  // Reset active when category changes
  useEffect(() => { setActive(cat.services[0].id); }, [category]);

  const activeCat = cat.services.find(s => s.id === active) || cat.services[0];

  return (
    <section id={anchored ? 'services' : undefined} className="services">
      <SectionHead eyebrow={t(C.eyebrow, lang)} title={t(C.title, lang)} sub={t(C.sub, lang)}/>

      <div className="services-tabs" role="tablist" data-reveal="up">
        {cat.services.map(s => (
          <button key={s.id} className={`services-tab ${active === s.id ? 'is-active' : ''}`}
                  onClick={() => setActive(s.id)} role="tab" aria-selected={active === s.id}>
            <span>{t(s.name, lang)}</span>
            <span className="services-tab-count">{s.items.length}</span>
          </button>
        ))}
      </div>

      {activeCat.blurb && (
        <div className="services-blurb" key={activeCat.id} data-reveal="up">
          <p>{t(activeCat.blurb, lang)}</p>
        </div>
      )}

      <ul className="services-list" data-reveal-group>
        {activeCat.items.map((it, i) => (
          <li key={i} className="services-row" data-reveal="left">
            <div className="services-row-name">{t(it.name, lang)}</div>
            <div className="services-row-dot"/>
            <div className="services-row-dur">{it.duration} {t(C.min, lang)}</div>
            <div className="services-row-price">{t(it.price, lang)}</div>
            <button className="services-row-cta"
                    onClick={() => onBook({ service: it, category: activeCat })}>
              {t(C.bookThis, lang)} <Icon.arrow width={12} height={12}/>
            </button>
            {(it.desc || it.note) && (
              <div className="services-row-meta">
                {it.desc && <span className="services-row-desc">{t(it.desc, lang)}</span>}
                {it.note && <span className="services-row-note">{t(it.note, lang)}</span>}
              </div>
            )}
          </li>
        ))}
      </ul>

      {onViewAll && (
        <button className="btn btn-ghost services-viewall" onClick={onViewAll}>
          {t(C.viewAll, lang)} <Icon.arrow width={14} height={14}/>
        </button>
      )}
    </section>
  );
}

// ── Section heading reused everywhere ───────────────────────────────────
// Centered by default so every section header lines up consistently.
function SectionHead({ eyebrow, title, sub, align = 'center' }) {
  return (
    <div className={`sec-head sec-head--${align}`} data-reveal-group>
      <div className="sec-eyebrow" data-reveal="up">{eyebrow}</div>
      <h2 className="sec-title" data-reveal="up">{title}</h2>
      {sub && <p className="sec-sub" data-reveal="up">{sub}</p>}
    </div>
  );
}

// ── Gallery + Lightbox ──────────────────────────────────────────────────
function Gallery({ lang }) {
  const C = window.SITE.COPY.gallery;
  const items = window.SITE.GALLERY_ITEMS;
  const [lightbox, setLightbox] = useState(null); // index

  useEffect(() => {
    if (lightbox == null) return;
    const onKey = (e) => {
      if (e.key === 'Escape') setLightbox(null);
      if (e.key === 'ArrowLeft')  setLightbox((i) => (i + items.length - 1) % items.length);
      if (e.key === 'ArrowRight') setLightbox((i) => (i + 1) % items.length);
    };
    window.addEventListener('keydown', onKey);
    document.body.style.overflow = 'hidden';
    return () => {
      window.removeEventListener('keydown', onKey);
      document.body.style.overflow = '';
    };
  }, [lightbox, items.length]);

  return (
    <section id="gallery" className="gallery">
      <SectionHead eyebrow={t(C.eyebrow, lang)} title={t(C.title, lang)} sub={t(C.sub, lang)}/>
      <div className={`gallery-grid ${items.length <= 3 ? 'gallery-grid--few' : ''}`} data-reveal-group>
        {items.map((it, i) => {
          const label = t(it.label, lang);
          return (
          <button key={it.id} className="gallery-cell" data-reveal="scale"
                  onClick={() => setLightbox(i)} aria-label={label}>
            <SiteImage src={it.imageUrl} ratio={it.tileRatio || it.ratio} fit={it.tileRatio ? 'contain' : 'cover'}
                       kind={it.kind} label={label} accent={i % 4} alt={label}/>
            {it.beforeAfter && (
              <span className="gallery-cell-ba">
                {t(C.beforeLbl, lang)} <span className="gallery-cell-ba-sep">/</span> {t(C.afterLbl, lang)}
              </span>
            )}
            <span className="gallery-cell-caption">{label}</span>
            <span className="gallery-cell-zoom">
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><circle cx="11" cy="11" r="7"/><path d="M16 16l5 5" strokeLinecap="round"/><path d="M11 8v6M8 11h6" strokeLinecap="round"/></svg>
            </span>
          </button>
          );
        })}
      </div>
      <div className="gallery-foot">
        <a className="btn btn-ghost" href={window.SITE.BUSINESS.instagram} target="_blank" rel="noreferrer"
           onClick={interceptCta(undefined, lang)}>
          <Icon.ig width={16} height={16}/> {t(C.follow, lang)}
        </a>
      </div>

      {lightbox != null && (
        <div className="lightbox" onClick={() => setLightbox(null)}>
          <button className="lightbox-x" aria-label="Close"><Icon.x width={22} height={22}/></button>
          <button className="lightbox-nav lightbox-nav--prev"
                  onClick={(e) => { e.stopPropagation(); setLightbox((i) => (i + items.length - 1) % items.length); }}
                  aria-label="Previous"><Icon.chevL width={26} height={26}/></button>
          <button className="lightbox-nav lightbox-nav--next"
                  onClick={(e) => { e.stopPropagation(); setLightbox((i) => (i + 1) % items.length); }}
                  aria-label="Next"><Icon.chevR width={26} height={26}/></button>
          <div className="lightbox-content" onClick={(e) => e.stopPropagation()}>
            <SiteImage src={items[lightbox].imageUrl} ratio={items[lightbox].ratio}
                       kind={items[lightbox].kind} label={t(items[lightbox].label, lang)}
                       accent={lightbox % 4} alt={t(items[lightbox].label, lang)}/>
            <div className="lightbox-caption">
              <span>{t(items[lightbox].label, lang)}</span>
              <span className="lightbox-counter">{lightbox + 1} / {items.length}</span>
            </div>
          </div>
        </div>
      )}
    </section>
  );
}

// ── Team grid with hover/expand cards ───────────────────────────────────
function Team({ lang, category, onBook, onProfile, bookingProvider }) {
  const C = window.SITE.COPY.team;
  return (
    <section id="team" className="team">
      <SectionHead eyebrow={t(C.eyebrow, lang)} title={t(C.title, lang)} sub={t(C.sub, lang)}/>
      <div className="team-grid" data-reveal-group>
        {window.SITE.TEAM.map((m, i) => (
          <TeamCard key={m.id} member={m} lang={lang} category={category}
                    onBook={() => onBook({ stylist: m })}
                    onProfile={() => onProfile(m.id)}/>
        ))}
      </div>
    </section>
  );
}

function TeamCard({ member, lang, category, onBook, onProfile }) {
  const C = window.SITE.COPY.team;
  // The whole card is a link to the stylist profile. Hover just lifts the
  // card and highlights it — no dark overlay, no hidden state. Specialties
  // and full bio live on the profile page.
  return (
    <article className="team-card" data-reveal="up"
             role="link" tabIndex={0}
             onClick={onProfile}
             onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onProfile(); } }}>
      <div className="team-card-photo">
        <StylistAvatar member={member} ratio="3/4"/>
      </div>
      <div className="team-card-meta">
        <div className="team-card-name">{member.name}</div>
        <div className="team-card-role">{t(member.role, lang)}</div>
        <div className="team-card-foot">
          {member.years
            ? <span>{member.years} {t(C.years, lang)}</span>
            : <span/>}
          <span className="team-card-ig">{member.instagram}</span>
        </div>
      </div>
    </article>
  );
}

// ── Reviews carousel ────────────────────────────────────────────────────
function Reviews({ lang }) {
  const C = window.SITE.COPY.reviews;
  const reviews = window.SITE.REVIEWS;
  const [idx, setIdx] = useState(0);
  const trackRef = useRef(null);
  const [dragX, setDragX] = useState(0);
  const dragStartRef = useRef(null);

  const go = (delta) => setIdx((i) => (i + delta + reviews.length) % reviews.length);

  // Auto-advance
  useEffect(() => {
    const id = setInterval(() => setIdx((i) => (i + 1) % reviews.length), 7000);
    return () => clearInterval(id);
  }, [reviews.length]);

  // Pointer-drag swipe
  const onPointerDown = (e) => { dragStartRef.current = e.clientX; };
  const onPointerMove = (e) => {
    if (dragStartRef.current == null) return;
    setDragX(e.clientX - dragStartRef.current);
  };
  const onPointerUp = () => {
    if (dragStartRef.current == null) return;
    if (dragX > 60)  go(-1);
    if (dragX < -60) go(1);
    dragStartRef.current = null;
    setDragX(0);
  };

  return (
    <section id="reviews" className="reviews">
      <SectionHead eyebrow={t(C.eyebrow, lang)} title={t(C.title, lang)}/>

      <div className="reviews-stats" data-reveal-group>
        {(() => { const A = window.SITE.COPY.about; return [[A.stat1Big, A.stat1Lbl], [A.stat2Big, A.stat2Lbl], [A.stat3Big, A.stat3Lbl]]; })().map(([b, l], i) => (
          <div key={i} className="reviews-stat" data-reveal="scale">
            <div className="reviews-stat-num">{t(b, lang)}</div>
            <div className="reviews-stat-lbl">{t(l, lang)}</div>
          </div>
        ))}
      </div>

      <div className="reviews-carousel" data-reveal="up"
           onPointerDown={onPointerDown}
           onPointerMove={onPointerMove}
           onPointerUp={onPointerUp}
           onPointerCancel={onPointerUp}>
        <div className="reviews-track" ref={trackRef}
             style={{ transform: `translateX(calc(${-idx * 100}% + ${dragX}px))` }}>
          {reviews.map((r, i) => (
            <article key={i} className="review-card">
              <Stars value={r.rating} size={18}/>
              <blockquote className="review-text">
                <span className="review-quote-mark">“</span>
                {t(r.text, lang)}
              </blockquote>
              <footer className="review-foot">
                <div>
                  <div className="review-author">{r.author}</div>
                  <div className="review-meta">{t(r.service, lang)} · {r.days}{t(C.daysAgo, lang)}</div>
                </div>
                <div className="review-source">Google</div>
              </footer>
            </article>
          ))}
        </div>

        <div className="reviews-controls">
          <button className="reviews-nav" onClick={() => go(-1)} aria-label="Previous"><Icon.chevL width={18} height={18}/></button>
          <div className="reviews-dots">
            {reviews.map((_, i) => (
              <button key={i} className={`reviews-dot ${i === idx ? 'is-active' : ''}`}
                      onClick={() => setIdx(i)} aria-label={`Review ${i + 1}`}/>
            ))}
          </div>
          <button className="reviews-nav" onClick={() => go(1)} aria-label="Next"><Icon.chevR width={18} height={18}/></button>
        </div>
      </div>
      <div className="reviews-foot">
        <a href="#" className="link-arrow" onClick={(e) => e.preventDefault()}>
          {t(C.seeAllTpl, lang).replace('{n}', window.SITE.BUSINESS.reviewsCount)} <Icon.arrow width={14} height={14}/>
        </a>
      </div>
    </section>
  );
}

// ── Brand logo ──────────────────────────────────────────────────────────
// Renders the AB Aesthetics brand mark from assets/logo.webp. The logo is
// multi-colour (navy AB monogram + wordmark on a soft blue badge) so it is
// rendered as a regular <img>, not as a palette-tinted CSS mask. Kept under
// the legacy name HairCoLogo so existing call sites work without churn.
function HairCoLogo({ size = 'nav' }) {
  const name = (window.SITE && window.SITE.BUSINESS && window.SITE.BUSINESS.name) || 'Salon';
  return (
    <img
      className={`hc-logo hc-logo--${size}`}
      src="assets/logo.webp"
      alt={name}
      draggable={false}
    />
  );
}

window.Components = {
  Placeholder, SiteImage, StylistAvatar, HairCoLogo, Icon, Stars, LangPicker, Nav, Hero, About, WalkInBanner,
  Services, SectionHead, Gallery, Team, TeamCard, Reviews, t,
  PortfolioBanner, usePortfolio, interceptCta,
};
