// =========================================================================
// Alice Murrell, shared site chrome & behaviours
// Exposes Nav, Footer, Cursor, Icon, PROJECTS, useSiteEffects on window.
// Each <script type="text/babel"> has its own scope, so we export at the end.
// =========================================================================

// ---- Icon set (inline SVG; matches Lucide thin-stroke vocabulary) --------
const ICON_PATHS = {
  'arrow-up-right': '<line x1="7" y1="17" x2="17" y2="7"/><polyline points="7 7 17 7 17 17"/>',
  'arrow-right': '<line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/>',
  'arrow-down': '<line x1="12" y1="5" x2="12" y2="19"/><polyline points="5 12 12 19 19 12"/>',
  'arrow-left': '<line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/>',
  'mail': '<rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/>',
  'map-pin': '<path d="M20 10c0 4.4-8 12-8 12s-8-7.6-8-12a8 8 0 0 1 16 0Z"/><circle cx="12" cy="10" r="3"/>',
  'menu': '<line x1="4" y1="8" x2="20" y2="8"/><line x1="4" y1="16" x2="20" y2="16"/>',
  'x': '<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>',
  'check': '<polyline points="20 6 9 17 4 12"/>',
  'check-circle': '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>',
  'linkedin': '<path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-4 0v7h-4v-7a6 6 0 0 1 6-6z"/><rect x="2" y="9" width="4" height="12"/><circle cx="4" cy="4" r="2"/>',
  'instagram': '<rect x="2" y="2" width="20" height="20" rx="5"/><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"/><line x1="17.5" y1="6.5" x2="17.51" y2="6.5"/>',
  'star': '<path d="m12 3 2.6 5.9 6.4.6-4.8 4.3 1.4 6.3L12 17l-5.6 3.4 1.4-6.3L3 9.8l6.4-.6Z"/>'
};
function Icon({ name, size = 20, style = {}, ...rest }) {
  return (
    <svg className="icon" width={size} height={size} viewBox="0 0 24 24" fill="none"
    stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"
    style={style} dangerouslySetInnerHTML={{ __html: ICON_PATHS[name] || '' }} {...rest} />);

}

// ---- Project data --------------------------------------------------------
const PROJECTS = [
{ slug: 'the-pig', href: 'pig-hotels.html', n: '01', title: 'The Pig Hotels', sectors: ['Hospitality'], year: 2025, blurb: 'Brand and digital direction across a growing collection of restaurants-with-rooms rooted in the British countryside.', role: 'Brand & digital direction', tone: 'var(--ink-700)', fg: 'var(--sand-100)' },
{ slug: 'london-stone', href: 'london-stone.html', n: '02', title: 'London Stone', sectors: ['E-commerce', 'Retail'], year: 2026, blurb: 'E-commerce redesign for a premium landscaping brand, building for scale and an ongoing CRO partnership.', role: 'E-commerce redesign', tone: 'var(--ink-600)', fg: 'var(--sand-100)' },
{ slug: 'agent-clarks', href: 'clarks.html', n: '03', title: 'Clarks', sectors: ['E-commerce', 'Retail'], year: 2022, blurb: 'Global e-commerce replatform for a heritage British shoemaker - redesigning the end-to-end digital experience.', role: 'Global e-commerce replatform', tone: 'var(--sand-400)', fg: 'var(--ink-900)' },
{ slug: 'siro-hotels', href: 'siro.html', n: '04', title: 'SIRO Hotels', sectors: ['Hospitality', 'Luxury'], year: 2023, blurb: '0 → 1 App for a luxury wellness destination built around fitness, nutrition, sleep, recovery and mindfulness.', role: '0 → 1 App', tone: 'var(--ink-800)', fg: 'var(--sand-100)' },
{ slug: 'noble-panacea', href: 'noble-panacea.html', n: '05', title: 'Noble Panacea', sectors: ['E-commerce', 'Luxury'], year: 2023, blurb: 'Full e-commerce redesign for a high-end skincare brand - growing the customer base and building a scalable component system.', role: 'E-commerce redesign', tone: 'var(--sand-300)', fg: 'var(--ink-900)' },
{ slug: 'agent-provocateur', href: 'agent-provocateur.html', n: '06', title: 'Agent Provocateur', sectors: ['E-commerce', 'Luxury'], year: 2024, blurb: 'Retained design and CRO partner for a luxury lingerie brand, spanning full creative output and an ongoing testing programme.', role: 'Design and CRO Retainer', tone: 'var(--ink-900)', fg: 'var(--sand-100)', hidden: true },
{ slug: 'grenson', href: 'grenson.html', n: '07', title: 'Grenson', sectors: ['Luxury', 'Retail', 'E-commerce'], year: 2022, blurb: 'Heritage storefront and product storytelling for a Northampton shoemaker since 1866.', role: 'Brand & e-commerce', tone: 'var(--sand-200)', fg: 'var(--ink-900)' },
{ slug: 'one-and-only', href: 'one-and-only.html', n: '08', title: 'One & Only Resorts', sectors: ['Hospitality', 'Luxury'], year: 2021, blurb: 'EDM redesign and monthly digital campaigns across 12 ultra-luxury resorts, delivered within a global CRM strategy.', role: 'EDM & campaign design', tone: 'var(--green-400)', fg: 'var(--ink-900)' }];


// ---- Custom cursor -------------------------------------------------------
function Cursor() {
  React.useEffect(() => {
    if (window.matchMedia('(hover: none), (pointer: coarse)').matches) return;
    const dot = document.querySelector('.cursor-dot');
    const ring = document.querySelector('.cursor-ring');
    const label = ring && ring.querySelector('.cursor-label');
    if (!dot || !ring) return;
    document.body.classList.add('has-cursor');

    let mx = window.innerWidth / 2,my = window.innerHeight / 2;
    let rx = mx,ry = my;
    let raf;
    const onMove = (e) => {
      mx = e.clientX;my = e.clientY;
      dot.style.transform = `translate(${mx}px, ${my}px)`;
      document.body.classList.remove('cursor-hidden');
    };
    const loop = () => {
      rx += (mx - rx) * 0.18;ry += (my - ry) * 0.18;
      ring.style.transform = `translate(${rx}px, ${ry}px)`;
      raf = requestAnimationFrame(loop);
    };
    const onOver = (e) => {
      const view = e.target.closest('[data-cursor="view"]');
      const hov = e.target.closest('a, button, [data-cursor], input, textarea, .magnetic');
      if (view) {
        document.body.classList.add('cursor-view');
        if (label) label.textContent = view.getAttribute('data-cursor-label') || 'View';
      } else {document.body.classList.remove('cursor-view');}
      document.body.classList.toggle('cursor-hover', !!hov && !view);
    };
    const onLeave = () => document.body.classList.add('cursor-hidden');
    document.addEventListener('pointermove', onMove);
    document.addEventListener('mouseover', onOver);
    document.addEventListener('mouseout', (e) => {if (!e.relatedTarget) document.body.classList.remove('cursor-hover', 'cursor-view');});
    document.documentElement.addEventListener('mouseleave', onLeave);
    loop();
    return () => {
      cancelAnimationFrame(raf);
      document.removeEventListener('pointermove', onMove);
      document.removeEventListener('mouseover', onOver);
      document.body.classList.remove('has-cursor');
    };
  }, []);
  return (
    <React.Fragment>
      <div className="cursor-dot" />
      <div className="cursor-ring"><span className="cursor-label">View</span></div>
    </React.Fragment>);

}

// ---- Reveal helper: add is-in, plus a timer-based safety net -------------
// Some preview/capture environments pause the CSS transition clock, leaving
// transitioned-in content stuck at opacity 0. Timers still run, so ~1.5s
// after an element is revealed we snap it to its final visible state with
// transition disabled (a no-op in real browsers where the fade already ran).
function revealEl(el) {
  if (el.__revealed) return;
  el.__revealed = true;
  el.classList.add('is-in');
  setTimeout(() => {
    el.style.transition = 'none';
    el.style.opacity = '1';
    el.style.transform = 'none';
  }, 1500);
}

// ---- Site effects: smooth scroll, reveal, magnetic, nav behaviour --------
function useSiteEffects() {
  React.useEffect(() => {
    const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

    // Lenis smooth/momentum scroll
    let lenis, raf;
    if (window.Lenis && !reduce) {
      lenis = new window.Lenis({ duration: 1.1, easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), smoothWheel: true });
      const loop = (time) => {lenis.raf(time);raf = requestAnimationFrame(loop);};
      raf = requestAnimationFrame(loop);
      window.__lenis = lenis;
    }

    // Reveal on scroll, IntersectionObserver plus a viewport "sweep" fallback.
    // (Some embedded/iframe + smooth-scroll setups don't fire IO for elements
    // already on-screen at load, so we reveal in-view elements directly too.)
    const io = new IntersectionObserver((entries) => {
      entries.forEach((en) => {
        if (en.isIntersecting) {revealEl(en.target);io.unobserve(en.target);}
      });
    }, { threshold: 0.12, rootMargin: '0px 0px -8% 0px' });
    document.querySelectorAll('[data-reveal], .reveal-line').forEach((el) => io.observe(el));

    const sweep = () => {
      const vh = window.innerHeight || document.documentElement.clientHeight;
      document.querySelectorAll('[data-reveal]:not(.is-in), .reveal-line:not(.is-in)').forEach((el) => {
        const r = el.getBoundingClientRect();
        if (r.top < vh * 0.92 && r.bottom > 0) {revealEl(el);io.unobserve(el);}
      });
    };
    requestAnimationFrame(sweep);
    setTimeout(sweep, 180);

    // Magnetic elements
    const magnets = Array.from(document.querySelectorAll('.magnetic'));
    const magMove = (el) => (e) => {
      const r = el.getBoundingClientRect();
      const dx = e.clientX - (r.left + r.width / 2);
      const dy = e.clientY - (r.top + r.height / 2);
      el.style.transform = `translate(${dx * 0.28}px, ${dy * 0.28}px)`;
    };
    const magLeave = (el) => () => {el.style.transform = '';};
    const handlers = [];
    if (!reduce) magnets.forEach((el) => {
      const m = magMove(el),l = magLeave(el);
      el.addEventListener('pointermove', m);el.addEventListener('pointerleave', l);
      handlers.push([el, m, l]);
    });

    // Nav hide on scroll-down, show on scroll-up + scrolled style
    const nav = document.querySelector('.nav');
    let lastY = window.scrollY;
    const onScroll = () => {
      const y = window.scrollY;
      sweep();
      if (nav) {
        nav.classList.toggle('is-scrolled', y > 24);
        if (y > 220 && y > lastY + 4) nav.classList.add('is-hidden');else
        if (y < lastY - 4) nav.classList.remove('is-hidden');
      }
      lastY = y;
    };
    window.addEventListener('scroll', onScroll, { passive: true });

    return () => {
      if (raf) cancelAnimationFrame(raf);
      if (lenis) lenis.destroy();
      io.disconnect();
      handlers.forEach(([el, m, l]) => {el.removeEventListener('pointermove', m);el.removeEventListener('pointerleave', l);});
      window.removeEventListener('scroll', onScroll);
    };
  }, []);
}

// ---- Navigation ----------------------------------------------------------
function Nav({ active }) {
  const [open, setOpen] = React.useState(false);
  const links = [
  ['Work', 'work.html', 'work'],
  ['About', 'about.html', 'about'],
  ['Contact', 'contact.html', 'contact']];

  React.useEffect(() => {
    document.body.style.overflow = open ? 'hidden' : '';
    return () => { document.body.style.overflow = ''; };
  }, [open]);

  return (
    <React.Fragment>
      <header className="nav">
        <div className="nav-inner">
          <a className="nav-brand magnetic" href="index.html" aria-label="Alice Murrell, home">
            <img className="nav-logo nav-logo--ondark" src={(window.__resources && window.__resources.logoSealDark) || "assets/logo-seal-dark-bg.svg"} alt="Alice Designs" />
            <img className="nav-logo nav-logo--onlight" src={(window.__resources && window.__resources.logoSealLight) || "assets/logo-seal-light-bg.png"} alt="Alice Designs" />
          </a>
          <div className="nav-right">
            <ul className="nav-links">
              {links.map(([label, href, id]) =>
              <li key={id}><a className={active === id ? 'is-active' : ''} href={href}>{label}</a></li>
              )}
            </ul>
            <a className="nav-cta magnetic" href="contact.html">Say hello</a>
            <button className="nav-menu-btn" onClick={() => setOpen((o) => !o)} aria-label={open ? 'Close menu' : 'Open menu'}>
              <Icon name={open ? 'x' : 'menu'} size={20} /> {open ? 'Close' : 'Menu'}
            </button>
          </div>
        </div>
      </header>
      <nav className={`nav-mobile${open ? ' is-open' : ''}`} style={{ display: open ? 'flex' : 'none' }}>
        <a href="index.html">Home</a>
        {links.map(([label, href]) => <a key={href} href={href}>{label}</a>)}
      </nav>
    </React.Fragment>);

}

// ---- Footer --------------------------------------------------------------
function Footer({ hideCta = false }) {
  return (
    <footer className="footer">
      <div className="wrap section">
        {!hideCta && <>
          <a className="footer-cta magnetic" href="contact.html" data-reveal data-cursor="view" data-cursor-label="Say hi">
            Get in touch <span className="arrow">→</span>
          </a>
        </>}
        <div className="footer-meta" style={{ marginTop: '92px' }}>
          <div className="footer-col footer-brand">
            <img className="footer-logo footer-logo--ondark" src={(window.__resources && window.__resources.logoSealDark) || "assets/logo-seal-dark-bg.svg"} alt="Alice Designs" />
            <img className="footer-logo footer-logo--onlight" src={(window.__resources && window.__resources.logoSealLight) || "assets/logo-seal-light-bg.png"} alt="Alice Designs" />
          </div>
          <div className="footer-col">
            <div className="social-row">
              <a href="https://www.linkedin.com" target="_blank" rel="noopener" aria-label="LinkedIn"><img src="assets/icon-linkedin.svg" alt="LinkedIn" style={{ width: 36, height: 36, display: 'block' }} /></a>
              <a href="https://www.instagram.com/alicemmurrell" target="_blank" rel="noopener" aria-label="Instagram"><img src="assets/icon-instagram.svg" alt="Instagram" style={{ width: 36, height: 36, display: 'block' }} /></a>
            </div>
          </div>
        </div>
        <div style={{ display: 'flex', justifyContent: 'space-between', flexWrap: 'wrap', gap: 12, marginTop: 56 }}>
          <span className="mono">© 2026 Alice Murrell</span>
        </div>
      </div>
    </footer>);

}

// ---- Featured work carousel (shared by home + about) --------------------
function FeaturedCarousel({ eyebrow = 'Selected work', body, featImages = {}, featPositions = {}, idPrefix = 'feat' }) {
  const railRef = React.useRef(null);
  const scrollByCard = (dir) => {
    const rail = railRef.current;
    if (!rail) return;
    const card = rail.querySelector('.feat-slide');
    const step = card ? card.getBoundingClientRect().width : rail.clientWidth * 0.33;
    rail.scrollBy({ left: dir * step, behavior: 'smooth' });
  };
  return (
    <React.Fragment>
      <div className="wrap feat-head">
        <div className="ad-eyebrow-row feat-eyebrow" data-reveal style={{ maxWidth: 900 }}>
          <span className="ad-eyebrow">{eyebrow}</span>
          <div className="feat-body-cta">
            {body && <p className="ad-eyebrow-body">{body}</p>}
            <a className="btn btn-primary magnetic feat-viewall" href="work.html">View all work</a>
          </div>
        </div>
        <div className="feat-nav" data-reveal>
          <button type="button" className="feat-arrow magnetic" aria-label="Previous" onClick={() => scrollByCard(-1)}>
            <Icon name="arrow-left" size={18} />
          </button>
          <button type="button" className="feat-arrow magnetic" aria-label="Next" onClick={() => scrollByCard(1)}>
            <Icon name="arrow-right" size={18} />
          </button>
        </div>
      </div>
      <div className="feat-rail" ref={railRef}>
        {PROJECTS.filter((p) => !p.hidden).map((p, i) =>
          <a key={p.slug} className="feat-slide work-tile" href={p.href}
            data-reveal data-cursor="view" data-cursor-label="View" style={{ '--reveal-delay': `${Math.min(i, 4) * 70}ms` }}>
            <div className="frame">
              {featImages[p.slug]
                ? <img src={featImages[p.slug]} alt={p.title} style={{ width: '100%', aspectRatio: '3/4', objectFit: 'cover', objectPosition: featPositions[p.slug] || 'center', display: 'block' }} />
                : <image-slot id={`${idPrefix}-${p.slug}`} shape="rect" style={{ aspectRatio: '3 / 4' }} placeholder={`${p.title}, drop image`}></image-slot>
              }
            </div>
            <div className="feat-cap">
              <span className="feat-ix">{p.year}</span>
              <h3 className="feat-title">{p.title}</h3>
              <p className="feat-blurb">{p.blurb}</p>
            </div>
          </a>
        )}
      </div>
    </React.Fragment>);
}

Object.assign(window, { Icon, Cursor, Nav, Footer, PROJECTS, useSiteEffects, revealEl, FeaturedCarousel });