/* ===========================================================================
   Home v2 — full-page hero, trust section with logo letterbox, pain carousel.
   ---------------------------------------------------------------------------
   Voice rules in force: UK English, no em dashes, no banned vocabulary.
   =========================================================================== */

const { useState: useStateH, useEffect: useEffectH, useRef: useRefH } = React;

/* ===========================================================================
   Hero v2 — full viewport, slogan-led, product-mock peek on the right
   =========================================================================== */
function HeroV2({ bg = '#FFFFFF', accent = '#2563EB' }) {
  return (
    <section
      id="top"
      className="relative min-h-[100svh] flex items-center pt-28 lg:pt-32 pb-16 lg:pb-20 overflow-hidden bg-slate-900"
      aria-labelledby="hero-h1"
    >
      {/* Background Image with Dark Overlay */}
      <div className="absolute inset-0 z-0">
        <img
          src="assets/built-for-sector2.webp"
          alt="A group photograph of a mixed housing workforce: frontline operatives, housing officers and back-office staff together."
          className="w-full h-full object-cover object-center"
        />
        <div className="absolute inset-0 bg-slate-900/70" />
      </div>

      <div className="relative z-10 max-w-container mx-auto px-5 sm:px-8 w-full">
        <div className="grid lg:grid-cols-12 gap-10 lg:gap-16 items-center">
          {/* Left — slogan + supporting copy */}
          <div className="lg:col-span-7">
            <div className="reveal mb-7">
              <span className="inline-flex items-center gap-2.5 rounded-full bg-white/10 ring-1 ring-inset ring-white/30 backdrop-blur-sm px-5 py-2.5 text-[15px] sm:text-[16px] font-semibold tracking-[0.04em] uppercase text-white">
                <span className="relative flex h-2 w-2">
                  <span className="absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75 animate-ping" />
                  <span className="relative inline-flex rounded-full h-2 w-2 bg-blue-400" />
                </span>
                Built exclusively for UK social housing
              </span>
            </div>
            <h1
              id="hero-h1"
              className="reveal text-balance"
              style={{
                fontSize: 'clamp(36px, 8vw, 76px)',
                lineHeight: 1.02,
                letterSpacing: '-0.025em',
                fontWeight: 800,
                color: '#ffffff',
                transitionDelay: '40ms',
              }}
            >
              Hear from every colleague.<br className="hidden sm:inline" />{' '}
              Including the ones not at the office.
            </h1>
            <p className="mt-7 body-lg text-slate-200 max-w-[640px] reveal" style={{ transitionDelay: '120ms' }}>
              Colleague Pulse is the employee engagement platform built for <strong className="font-semibold text-white">UK social housing</strong>. Automated pulse surveys, one-off and exit surveys, four-channel targeted messaging, AI Critical Concern, board-ready Word packs, HR integration and housing-only benchmarks, for a workforce that does not always sit at a desk.
            </p>
            <div className="mt-9 flex flex-col sm:flex-row gap-3 sm:gap-4 reveal" style={{ transitionDelay: '180ms' }}>
              <BtnPrimary href="Book a demo.html" className="w-full sm:w-auto" accent>Let's Talk</BtnPrimary>
              <BtnSecondary href="Features.html" icon={<IconArrowRight size={16} />} className="border-white text-slate-900 hover:bg-slate-100 w-full sm:w-auto">
                See the features
              </BtnSecondary>
            </div>
            <p className="mt-7 text-[14px] text-slate-300 flex items-center gap-2 reveal" style={{ transitionDelay: '240ms' }}>
              <IconLock size={14} className="text-slate-400" />
              UK-built. EU-hosted.
            </p>
          </div>

          {/* Right — product mock peek */}
          <div className="lg:col-span-5 reveal" style={{ transitionDelay: '160ms' }}>
            <HeroVisual />
          </div>
        </div>
      </div>
    </section>
  );
}

/* A stack of three slightly-rotated product mocks — quiet, polished, hints
   at the breadth of the platform without being a giant screenshot. */
function HeroVisual() {
  return (
    <div className="relative w-full max-w-[520px] mx-auto h-[320px] sm:h-[460px] lg:h-[520px]">
      {/* Back card — board pack */}
      <div
        className="absolute right-0 top-0 w-[78%] origin-top-right opacity-95"
        style={{ transform: 'rotate(3deg) translateY(8px)' }}
      >
        <div className="rounded-xl bg-white border border-slate-200 shadow-card p-4">
          <div className="text-[10px] uppercase tracking-wider font-semibold text-slate-500 mb-2">Board pack · Q1</div>
          <div className="grid grid-cols-3 gap-1.5 mb-3">
            {[['71%', 'Response'], ['+38', 'eNPS'], ['1', 'Open']].map(([v, l]) => (
              <div key={l} className="rounded bg-slate-50 px-2 py-1.5">
                <div className="text-[14px] font-bold text-slate-900 leading-none tabular-nums">{v}</div>
                <div className="text-[9px] text-slate-500 mt-0.5">{l}</div>
              </div>
            ))}
          </div>
          <div className="space-y-1.5">
            {[82, 74, 61, 78].map((pct, i) => (
              <div key={i} className="flex items-center gap-1.5">
                <span className="w-12 h-1.5 rounded-full bg-slate-200" />
                <div className="flex-1 h-2.5 bg-slate-100 rounded">
                  <div className={`h-full rounded ${pct < 65 ? 'bg-amber-500' : 'bg-emerald-500'}`} style={{ width: pct + '%' }} />
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>

      {/* Middle card — Critical Concern alert (the star of the show) */}
      <div
        className="absolute left-0 top-12 w-[80%] origin-top-left"
        style={{ transform: 'rotate(-2deg)' }}
      >
        <div className="rounded-xl bg-white border border-slate-200 shadow-cardlg p-5">
          <div className="rounded-lg border border-amber-200 bg-amber-50 p-4">
            <div className="flex items-start gap-3">
              <div className="inline-flex items-center justify-center h-9 w-9 rounded-full bg-amber-500 text-white shrink-0">
                <IconShieldAlert size={18} />
              </div>
              <div className="min-w-0 flex-1">
                <div className="text-[10px] uppercase tracking-wider font-bold text-amber-800">Critical concern · open</div>
                <div className="mt-0.5 text-[14px] font-semibold text-slate-900 leading-snug">Possible bullying reference</div>
                <div className="mt-0.5 text-[11px] text-slate-600">Q3 pulse · 9:14am · routed to Priya Shah</div>
              </div>
            </div>
          </div>
          <div className="mt-3 grid grid-cols-4 gap-1.5">
            {[
              { label: 'Email', Icon: IconMail, iconColor: 'text-slate-900', active: true },
              { label: 'SMS', Icon: IconSmartphone, iconColor: 'text-emerald-500', active: true },
              { label: 'Pers email', Icon: IconMail, iconColor: 'text-sky-500', active: true },
              { label: 'Pers SMS', Icon: IconSmartphone, iconColor: 'text-amber-500', active: false },
            ].map(({ label, Icon, iconColor, active }) => (
              <span key={label} className={`inline-flex items-center justify-center gap-1 text-[9px] font-semibold px-1 py-1 rounded ${active ? 'bg-blue-50 border border-blue-500 text-slate-900 ring-1 ring-blue-500/40' : 'bg-slate-50 border border-dashed border-slate-300 text-slate-400 opacity-60'}`}>
                <Icon size={10} className={active ? iconColor : 'text-slate-400'} />
                {label}
              </span>
            ))}
          </div>
        </div>
      </div>

      {/* Front card — small pulse question */}
      <div
        className="absolute right-3 bottom-0 w-[60%] origin-bottom-right"
        style={{ transform: 'rotate(2deg)' }}
      >
        <div className="rounded-xl bg-white border border-slate-200 shadow-cardlg p-4">
          <div className="flex items-center justify-between mb-2">
            <span className="text-[9px] uppercase tracking-wider font-semibold text-slate-500">Q4 of 10</span>
            <span className="text-[9px] font-medium text-slate-400">2 mins</span>
          </div>
          <div className="text-[12px] font-semibold text-slate-900 leading-snug mb-3">
            How likely are you to recommend us as a place to work?
          </div>
          <div className="grid grid-cols-10 gap-0.5">
            {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((n) => {
              const colors = {
                1: '#c0392b', 2: '#e74c3c',
                3: '#e67e22', 4: '#f39c12',
                5: '#f1c40f', 6: '#cddc39', 7: '#9acd32',
                8: '#7cb342', 9: '#4caf50', 10: '#2e7d32',
              };
              const isSelected = n === 8;
              return (
                <span
                  key={n}
                  className={`h-5 rounded text-[9px] font-semibold flex items-center justify-center text-white ${isSelected ? 'ring-2 ring-offset-1 ring-slate-900' : ''}`}
                  style={{ backgroundColor: colors[n] }}
                >
                  {n}
                </span>
              );
            })}
          </div>
        </div>
      </div>
    </div>
  );
}

/* ===========================================================================
   TrustSection v2 — short CX-Feedback statement + 3 numbers + logo letterbox
   =========================================================================== */
function TrustSectionV2() {
  const stats = [
    { num: '110', cap: 'UK social landlords already using our products.' },
    { num: '98%', cap: "Customer retention across the team's products, multi-year average." },
    { num: '2008', cap: 'Trading since. Glasgow-based, twelve people.' },
  ];
  return (
    <section className="max-md:min-h-[100svh] flex flex-col py-10 md:py-14 lg:py-16 bg-blue-50/60" aria-labelledby="trust-h">
      <div className="max-w-container mx-auto px-5 sm:px-8 w-full flex-1 min-h-0 flex flex-col">
        <div className="text-center max-w-prose-wide mx-auto reveal">
          <p className="eyebrow mb-3 md:mb-4">By the team behind CX-Feedback</p>
          <h2 id="trust-h" className="h2 text-balance">
            You probably know someone using us right now.
          </h2>
          <p className="mt-4 md:mt-5 text-[15px] md:text-[18px] leading-relaxed md:leading-[1.7] text-slate-600 max-w-[680px] mx-auto">
            Colleague Pulse is built by the team behind CX-Feedback, the tenant-engagement platform 110 UK social landlords already use.
          </p>
        </div>

        {/* Numbers */}
        <div className="mt-6 md:mt-14 lg:mt-16 grid grid-cols-3 gap-3 md:gap-12 lg:gap-10 max-w-[1080px] mx-auto w-full">
          {stats.map((s, i) => (
            <div key={s.num} className="text-center reveal" style={{ transitionDelay: `${i * 80}ms` }}>
              <div className="stat-num stat-num-compact">{s.num}</div>
              <p className="mt-2 md:mt-4 text-[12px] md:text-[15px] leading-snug md:leading-relaxed text-slate-700 max-w-[34ch] mx-auto">{s.cap}</p>
            </div>
          ))}
        </div>

        {/* Logo letterbox */}
        <CustomerLogoGrid />
      </div>
    </section>
  );
}

function CustomerLogoGrid() {
  const ROW_H = 56;
  const GAP = 8;
  const VISIBLE_ROWS = 6;
  const COL_LCM = 24;   // lcm(4, 6, 8): the three column counts this grid uses
  const MIN_COPY = 48;  // six rows at eight columns, so one copy always fills the window
  const windowH = ROW_H * VISIBLE_ROWS + GAP * (VISIBLE_ROWS - 1);

  // The shared logos folder is the single source of truth. /api/logos returns
  // every image in it, so there is no hardcoded list to keep in sync.
  const [logos, setLogos] = useStateH([]);
  useEffectH(() => {
    let active = true;
    fetch('api/logos')
      .then(r => (r.ok ? r.json() : []))
      .then(files => { if (active) setLogos(Array.isArray(files) ? files : []); })
      .catch(() => { if (active) setLogos([]); });
    return () => { active = false; };
  }, []);

  // The track scrolls up by exactly half its own height, so the second copy has
  // to land where the first one started. That needs each copy to be a whole
  // number of grid rows, i.e. its length has to divide by the column count, and
  // this grid is 4, 6 or 8 columns wide across its breakpoints, so it has to
  // divide by all three: a multiple of 24. It also has to be at least 48 tiles,
  // or a copy is shorter than the 376px window and the background shows through
  // before the wrap.
  //
  // The listing is whatever the shared container holds (97 today, and 97 is
  // prime, so no column count divides it) and nothing polices that. The
  // shortfall is made up by repeating logos from the front of the list, spliced
  // in halfway down rather than appended: that puts every repeat exactly
  // (target / 2) tiles from its original, which at eight columns is six rows,
  // one more than the window shows, so the two are never on screen together.
  // Repeats rather than truncation to a multiple of 24, because a customer not
  // finding their own logo on a trust wall is worse than a repeat nobody sees.
  const n = logos.length;
  let copy = logos;
  if (n) {
    const target = Math.max(MIN_COPY, Math.ceil(n / COL_LCM) * COL_LCM);
    const extras = [];
    for (let i = 0; extras.length < target - n; i++) extras.push(logos[i % n]);
    const mid = Math.floor(n / 2);
    copy = logos.slice(0, mid).concat(extras, logos.slice(mid));
  }
  const tiles = copy.concat(copy);

  return (
    <div className="w-full mt-6 md:mt-10 lg:mt-12 reveal">
      <style dangerouslySetInnerHTML={{
        __html: `
        @keyframes logo-scroll-up {
          0%   { transform: translateY(0); }
          100% { transform: translateY(-50%); }
        }
        .logo-ticker-track {
          animation: logo-scroll-up 45s linear infinite;
          will-change: transform;
        }
        /* A background image cannot carry loading="lazy", and the 97 logos are
           4.3 MB of PNG for tiles painted 38px tall, so the deferral has to come
           from somewhere. content-visibility skips an element's contents rather
           than its own background, which is why the image sits on a child. */
        .logo-ticker-tile {
          content-visibility: auto;
          contain-intrinsic-size: auto ${ROW_H}px;
        }
        /* Reproduces max-h-full max-w-full object-contain inside the tile's 38px
           content box. no-repeat and centre are not optional here: the CSS
           defaults are repeat and top left. */
        .logo-ticker-img {
          background-repeat: no-repeat;
          background-position: center center;
          background-size: contain;
        }
        .logo-fade-top {
          position: absolute; top: 0; left: 0; right: 0; height: 28px;
          background: linear-gradient(to bottom, #eff6ff, transparent);
          pointer-events: none; z-index: 1;
        }
        .logo-fade-bottom {
          position: absolute; bottom: 0; left: 0; right: 0; height: 36px;
          background: linear-gradient(to top, #eff6ff, transparent);
          pointer-events: none; z-index: 1;
        }
        /* .reveal and the page loader already honour this; the ticker did not. */
        @media (prefers-reduced-motion: reduce) {
          .logo-ticker-track { animation: none; }
        }
      `}} />
      <div className="text-center mb-6">
        <p className="text-[18px] uppercase tracking-widest text-slate-600 font-black">Trusted by Over 100 UK Social Landlords</p>
      </div>

      {/* The wall is decoration: the sentence above it carries the message, no
          tile is a link and none has a tooltip, and every logo appears twice, so
          it is hidden from assistive technology rather than announced 240 times.
          The customer names that used to sit in alt text are gone with it, which
          is the point: that text is what let a search engine match "colleague
          pulse" to a named landlord's logo. */}
      <div
        className="relative rounded-2xl border border-slate-200 shadow-lg bg-white overflow-hidden"
        style={{ height: windowH + 'px' }}
        aria-hidden="true"
      >
        <div className="logo-fade-top" />
        <div className="logo-fade-bottom" />
        {/* px-2 pb-2, not p-2. The 8px under the last row completes that row's
            64px pitch (56 + 8), which makes the track an exact multiple of the
            pitch, so translateY(-50%) lands on a row boundary. With p-2 the
            track was 8px too long and every wrap landed (32R + 4) mod 64 off the
            grid, which is 36px whenever R is odd, as it was at all three
            breakpoints. Dropping the padding entirely undershoots by 4px for the
            same reason. The side gutters stay on the grid because they do not
            affect the height and cannot move to the clip container: overflow
            clips at the padding box, so the track would scroll through them.
            The animation goes on only once there is something to scroll, or the
            cycle starts against a zero-height track and the first frame of real
            content appears already part-scrolled. */}
        <div
          className={(tiles.length ? 'logo-ticker-track ' : '') + 'grid grid-cols-4 sm:grid-cols-6 md:grid-cols-8 gap-2 px-2 pb-2 bg-blue-50/60'}
          style={{ gridAutoRows: ROW_H + 'px' }}
        >
          {tiles.map((file, i) => (
            <div key={file + i} className="logo-ticker-tile bg-white border border-slate-100 rounded-xl p-2 flex">
              <div
                className="logo-ticker-img flex-1 self-stretch"
                style={{ backgroundImage: `url("assets/logos/${encodeURIComponent(file)}")` }}
              />
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

/* ===========================================================================
   PainCarousel — HubSpot-style, 4 slides
   =========================================================================== */
function PainCarousel() {
  const slides = [
    {
      problemNumber: 'Problem 1',
      tagLabel: 'AI Critical Concern',
      tagTone: 'amber',
      headline: "Uncovering harassment only when the resignation lands.",
      body: "When bullying or mental health signals go unnoticed, you lose good people. Other tools wait for the annual survey or an HR complaint to surface what was sitting in the data for months.",
      solution: "Colleague Pulse's built-in AI reads every response for these risks and alerts you the day an issue is raised, so you can intervene before it becomes a resignation.",
      ctaText: 'See Critical Concern',
      ctaHref: 'Features.html#concern',
      mock: 'concern-problem',
      dwellMs: 9000,
    },
    {
      problemNumber: 'Problem 2',
      tagLabel: 'Live sentiment',
      tagTone: 'blue',
      headline: 'Resignations land before the annual survey does.',
      body: 'A year between data points is a year of people who quietly checked out. Annual-only platforms hand you a verdict, not a warning.',
      solution: 'Colleague Pulse runs short pulses on a cadence the platform keeps, so you get a live read on sentiment by team and by site before the conversation becomes a notice period.',
      ctaText: 'See the survey builder',
      ctaHref: 'Features.html#survey',
      mock: 'survey',
      dwellMs: 9000,
    },
    {
      problemNumber: 'Problem 3',
      tagLabel: 'Reporting',
      tagTone: 'violet',
      headline: 'Hours of work, done in minutes.',
      body: 'Building a board pack shouldn\'t mean exporting to CSV, rebuilding charts and writing the narrative from scratch.',
      solution: 'Colleague Pulse turns your latest survey into a finished, branded Word document with a plain-English AI summary, ready to share.',
      ctaText: 'See the board pack builder',
      ctaHref: 'Features.html#reporting',
      mock: 'reporting',
      dwellMs: 9000,
    },
    {
      problemNumber: 'Problem 4',
      tagLabel: 'Frontline reach',
      tagTone: 'blue',
      headline: 'Half your workforce never sees the all-staff email.',
      body: 'Repairs operatives in vans, housing officers on the patch, caretakers on the schemes. The intranet was built for the office.',
      solution: 'Colleague Pulse reaches them on the channel they actually check, every time.',
      ctaText: 'See targeted messaging',
      ctaHref: 'Features.html#messaging',
      image: 'assets/care2.webp',
      imageAlt: 'A frontline housing worker checking a message on their phone.',
      mock: 'messaging',
      dwellMs: 14500,
    },
  ];

  const [idx, setIdx] = useStateH(0);
  const [autoplay, setAutoplay] = useStateH(true);
  const [inView, setInView] = useStateH(false);
  const sectionRef = useRefH(null);
  const trackRef = useRefH(null);
  const total = slides.length;

  const go = (n) => setIdx(((n % total) + total) % total);
  const next = () => go(idx + 1);
  const prev = () => go(idx - 1);

  // Hold on slide 1 until the actual carousel track scrolls into view, then start advancing.
  useEffectH(() => {
    const el = trackRef.current;
    if (!el || typeof IntersectionObserver === 'undefined') {
      setInView(true);
      return;
    }
    const io = new IntersectionObserver((entries) => {
      for (const e of entries) {
        if (e.isIntersecting) {
          setInView(true);
          io.disconnect();
          break;
        }
      }
    }, { threshold: 0.2 });
    io.observe(el);
    return () => io.disconnect();
  }, []);

  // Autoplay — each slide has its own dwell so the longer mock animations
  // (e.g. messaging, 14s loop) finish before we advance.
  useEffectH(() => {
    if (!autoplay || !inView) return;
    const dwell = slides[idx]?.dwellMs ?? 9000;
    const id = setTimeout(() => setIdx((i) => (i + 1) % total), dwell);
    return () => clearTimeout(id);
  }, [autoplay, inView, idx, total]);

  // Pause on hover / focus within
  const pause = () => setAutoplay(false);
  const resume = () => setAutoplay(true);

  // Keyboard navigation
  const onKey = (e) => {
    if (e.key === 'ArrowRight') { e.preventDefault(); next(); }
    if (e.key === 'ArrowLeft') { e.preventDefault(); prev(); }
  };

  // Touch swipe (mobile)
  const touchStartX = useRefH(0);
  const touchDeltaX = useRefH(0);
  const onTouchStart = (e) => {
    touchStartX.current = e.touches[0].clientX;
    touchDeltaX.current = 0;
    pause();
  };
  const onTouchMove = (e) => {
    touchDeltaX.current = e.touches[0].clientX - touchStartX.current;
  };
  const onTouchEnd = () => {
    const dx = touchDeltaX.current;
    if (Math.abs(dx) > 40) { dx < 0 ? next() : prev(); }
    resume();
  };

  return (
    <section id="pains" ref={sectionRef} className="py-8 sm:py-16 md:py-24 lg:py-32 bg-white" aria-labelledby="carousel-h">
      <div className="max-w-container mx-auto px-5 sm:px-8">
        <div className="max-w-[820px] mx-auto text-center reveal mb-8 sm:mb-12 lg:mb-16">
          <p className="eyebrow mb-3 sm:mb-4">Four problems we exist to solve</p>
          <h2 id="carousel-h" className="h2 text-balance">
            The things that keep an HR Director up on a <strong>Sunday</strong>.
          </h2>
        </div>

        <div
          className="relative reveal"
          onMouseEnter={pause}
          onMouseLeave={resume}
          onFocus={pause}
          onBlur={resume}
          onKeyDown={onKey}
          role="region"
          aria-roledescription="carousel"
          aria-label="Problems we solve"
          tabIndex={0}
        >
          {/* Arrows — far left and far right (HubSpot style) */}
          <button
            type="button"
            onClick={prev}
            aria-label="Previous slide"
            className="absolute left-0 lg:-left-2 top-1/2 -translate-y-1/2 z-10 hidden md:inline-flex items-center justify-center h-12 w-12 rounded-full bg-white border border-slate-200 text-slate-700 hover:text-blue-600 hover:border-blue-300 shadow-card transition-[color,border-color,transform] duration-200 ease-out active:scale-90"
          >
            <IconArrowRight size={18} className="rotate-180" />
          </button>
          <button
            type="button"
            onClick={next}
            aria-label="Next slide"
            className="absolute right-0 lg:-right-2 top-1/2 -translate-y-1/2 z-10 hidden md:inline-flex items-center justify-center h-12 w-12 rounded-full bg-white border border-slate-200 text-slate-700 hover:text-blue-600 hover:border-blue-300 shadow-card transition-[color,border-color,transform] duration-200 ease-out active:scale-90"
          >
            <IconArrowRight size={18} />
          </button>

          {/* Mobile nav above the slide — dots + prev/next so the controls are visible without scrolling past the mock */}
          <div className="mb-6 flex md:hidden items-center justify-between gap-3">
            <button
              type="button"
              onClick={prev}
              aria-label="Previous slide"
              className="inline-flex items-center justify-center h-11 w-11 rounded-full bg-white border border-slate-200 text-slate-700 shadow-card shrink-0"
            >
              <IconArrowRight size={18} className="rotate-180" />
            </button>
            <div className="flex items-center justify-center gap-2.5">
              {slides.map((_, i) => (
                <button
                  key={i}
                  type="button"
                  onClick={() => go(i)}
                  aria-label={`Go to slide ${i + 1}`}
                  aria-current={i === idx}
                  className={`rounded-full active:scale-90 ${i === idx ? 'bg-blue-600 w-7 h-2' : 'bg-slate-300 w-2 h-2'}`}
                  style={{ transition: 'width 250ms cubic-bezier(0.16,1,0.3,1), height 250ms cubic-bezier(0.16,1,0.3,1), background-color 200ms ease-out, transform 140ms ease-out' }}
                />
              ))}
            </div>
            <button
              type="button"
              onClick={next}
              aria-label="Next slide"
              className="inline-flex items-center justify-center h-11 w-11 rounded-full bg-white border border-slate-200 text-slate-700 shadow-card shrink-0"
            >
              <IconArrowRight size={18} />
            </button>
          </div>

          {/* Track */}
          <div
            className="mx-auto max-w-[1080px] overflow-hidden rounded-2xl touch-pan-y"
            onTouchStart={onTouchStart}
            onTouchMove={onTouchMove}
            onTouchEnd={onTouchEnd}
          >
            <div
              ref={trackRef}
              className="flex transition-transform duration-[600ms] ease-[cubic-bezier(0.22,1,0.36,1)]"
              style={{ transform: `translateX(-${idx * 100}%)` }}
              aria-live="polite"
            >
              {slides.map((s, i) => (
                <div
                  key={i}
                  className="min-w-full px-2 sm:px-4"
                  role="group"
                  aria-roledescription="slide"
                  aria-label={`${i + 1} of ${total}: ${s.headline}`}
                  aria-hidden={i !== idx}
                >
                  <PainSlide s={s} active={i === idx && inView} />
                </div>
              ))}
            </div>
          </div>

          {/* Dots — desktop only; mobile has dots above the slide */}
          <div className="mt-10 hidden md:flex items-center justify-center gap-2.5">
            {slides.map((_, i) => (
              <button
                key={i}
                type="button"
                onClick={() => go(i)}
                aria-label={`Go to slide ${i + 1}`}
                aria-current={i === idx}
                className={`rounded-full active:scale-90 ${i === idx ? 'bg-blue-600 w-8 h-2.5' : 'bg-slate-300 hover:bg-slate-400 w-2.5 h-2.5'}`}
                style={{ transition: 'width 250ms cubic-bezier(0.16,1,0.3,1), height 250ms cubic-bezier(0.16,1,0.3,1), background-color 200ms ease-out, transform 140ms ease-out' }}
              />
            ))}
          </div>

        </div>

        <div className="mt-10 sm:mt-16 lg:mt-20 reveal">
          <a
            href="Features.html"
            className="group block rounded-2xl bg-slate-900 text-white p-6 sm:p-8 lg:p-10 shadow-card hover:shadow-cardlg hover:-translate-y-[2px] active:translate-y-0 active:scale-[0.995] transition-[box-shadow,transform] duration-300 ease-out"
          >
            <div className="grid lg:grid-cols-12 gap-6 lg:gap-10 items-center">
              <div className="lg:col-span-8">
                <p className="text-[13px] uppercase tracking-wider font-semibold text-blue-300 mb-3">
                  Every feature, in detail
                </p>
                <h3 className="text-[26px] sm:text-[32px] leading-tight font-bold tracking-tight">
                  The features HR Directors come back for.
                </h3>
                <p className="mt-4 text-[17px] leading-relaxed text-slate-300 max-w-[60ch]">
                  AI Critical Concern, surveys staff trust, customisable dashboards and board-ready packs, four-channel messaging, automated onboarding and exit journeys, HR integration and housing-only benchmarks.
                </p>
              </div>
              <div className="lg:col-span-4 lg:flex lg:justify-end">
                <span className="inline-flex items-center justify-center gap-2 bg-blue-600 group-hover:bg-blue-500 text-white font-semibold rounded-lg px-6 py-3.5 text-[17px] transition-colors">
                  See all features
                  <IconArrowRight size={18} className="transition-transform group-hover:translate-x-1" />
                </span>
              </div>
            </div>
          </a>
        </div>
      </div>
    </section>
  );
}

function PainSlide({ s, active }) {
  // Mobile: each mock has a different native height, so use a per-mock scale
  // to land them all at the same visible height (~240px).
  const mobileScale = s.image
    ? 240 / 380          // image is 380px tall
    : s.mock === 'concern-problem'
      ? 240 / 360        // ConcernProblemVisual fixed height 360
      : 240 / 440;       // ProductScreen wrapper is 440px
  const mobileInner = {
    transform: `scale(${mobileScale})`,
    transformOrigin: 'top left',
    width: `${100 / mobileScale}%`,
  };
  return (
    <div className="grid lg:grid-cols-12 gap-5 sm:gap-10 lg:gap-16 items-center px-4 py-5 sm:px-8 sm:py-6 rounded-2xl border border-slate-200 shadow-card lg:shadow-none lg:border-0 lg:rounded-none bg-white">
      <style>{`
        @media (max-width: 639px) {
          .pain-mock-outer { height: 240px; overflow: hidden; border-radius: 1rem; border: 1px solid #e2e8f0; background: #ffffff; }
          /* Hide the inner mock's own outline so only the outer frame shows — keeps borders consistent across slides.
             Need to walk a few levels because ProductScreen wraps FSFrame two layers deep. */
          .pain-mock-inner-m > div,
          .pain-mock-inner-m > div > div:first-child,
          .pain-mock-inner-m > div > div:first-child > div:first-child,
          .pain-mock-inner-m > div > div:first-child > div:first-child > div:first-child {
            border-color: transparent !important;
            box-shadow: none !important;
          }
          .pain-mock-inner-m > img { box-shadow: none !important; border-color: transparent !important; }
        }
        @media (min-width: 640px) {
          .pain-mock-inner-m { transform: none !important; width: 100% !important; }
        }
      `}</style>
      {/* Text — left */}
      <div className="lg:col-span-5">
        {s.problemNumber && (
          <p className="text-[11px] sm:text-[12px] uppercase tracking-[0.16em] font-semibold text-slate-500 mb-2 sm:mb-3">
            {s.problemNumber}
          </p>
        )}
        <h3 className="text-[20px] sm:text-[28px] lg:text-[40px] leading-[1.15] font-bold tracking-tight text-slate-900 text-balance">
          {s.headline}
        </h3>
        <p className="mt-2 sm:mt-5 text-[14px] sm:text-[17px] leading-relaxed text-slate-600 max-w-[52ch]">
          {s.body}
          {s.solution && (
            <> <strong className="font-semibold text-slate-900">{s.solution}</strong></>
          )}
        </p>
        <div className="mt-3 sm:mt-6 flex flex-wrap items-center gap-x-5 gap-y-3">
          <FSTag label={s.tagLabel} tone={s.tagTone} />
          <TextLinkCTA href={s.ctaHref}>{s.ctaText}</TextLinkCTA>
        </div>
      </div>
      {/* Mock — right */}
      <div className="lg:col-span-7 pain-mock-wrap pain-mock-outer">
        {s.image ? (
          <img
            src={s.image}
            alt={s.imageAlt || ''}
            className="w-full h-full sm:h-[380px] object-cover sm:rounded-2xl sm:shadow-cardlg sm:border sm:border-slate-200 pain-mock-img"
          />
        ) : (
          <div className="pain-mock-inner-m" style={mobileInner}>
            {s.mock === 'concern-problem' ? (
              <ConcernProblemVisual playing={active} />
            ) : (
              <ProductScreen kind={s.mock} playing={active} />
            )}
          </div>
        )}
      </div>
    </div>
  );
}

/* Editorial visual for the "AI Critical Concern" carousel slide. Not a product UI —
   a wall of anonymous pulse quotes with the buried bullying signal sitting
   quietly among them, plus the timing that makes the point. */
function ConcernProblemVisual({ playing }) {
  const [t, setT] = React.useState(0);
  const LOOP = 13;

  React.useEffect(() => {
    if (!playing) { setT(0); return; }
    const start = performance.now();
    let raf;
    const tick = (now) => {
      setT(((now - start) / 1000) % LOOP);
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [playing]);

  // Phases (seconds)
  const SCROLL_END = 4.8;   // scroll finishes here
  const PICK_IN = 5.0;   // target highlights
  const CHIP_IN = 5.8;   // AI chip slides in
  const HOLD_END = 11.5;  // start fade-out before loop
  const FADE_END = 12.6;

  // The feed
  const TARGET_IDX = 9;
  const cards = [
    { text: 'Manager is supportive. Really good week.' },
    { text: 'Liking the new shift pattern.' },
    { text: 'All fine. No issues.' },
    { text: 'Workload OK most weeks.' },
    { text: 'Happy enough. Pay could be better.' },
    { text: 'Team is great. No complaints.' },
    { text: 'Good induction. Useful sessions.' },
    { text: 'Comms have improved this quarter.' },
    { text: 'Nothing to flag this month.' },
    {
      text: 'My line manager shouts at me in front of the team and nothing has changed.',
      flagged: true,
      trigger: 'shouts at me in front of the team',
    },
    { text: 'Patch is busy but manageable.' },
    { text: 'Office is too cold on Mondays.' },
    { text: 'New starter pack was clear.' },
    { text: 'No concerns from me.' },
  ];

  const CARD_H = 54;       // px including gap
  const VIEWPORT_H = 240;  // px

  // Scroll: bring target to ~38% of viewport (slightly above centre).
  const targetCentreY = VIEWPORT_H * 0.42;
  const finalScrollY = TARGET_IDX * CARD_H + CARD_H / 2 - targetCentreY;

  const easeOut = (p) => 1 - Math.pow(1 - p, 3);
  let scrollY = 0;
  if (t <= SCROLL_END) {
    const p = Math.min(1, t / SCROLL_END);
    scrollY = finalScrollY * easeOut(p);
  } else {
    scrollY = finalScrollY;
  }

  const picked = t >= PICK_IN;
  const chip = t >= CHIP_IN;

  // Overall fade for graceful loop reset
  let containerOpacity = 1;
  if (t < 0.35) containerOpacity = t / 0.35;
  else if (t > HOLD_END) containerOpacity = Math.max(0, 1 - (t - HOLD_END) / (FADE_END - HOLD_END));

  return (
    <div
      className="relative rounded-2xl bg-gradient-to-br from-slate-50 to-white border border-slate-200 shadow-cardlg overflow-hidden"
      style={{ height: '360px', opacity: containerOpacity, transition: 'opacity 200ms ease-out' }}
    >
      {/* Apple-style window chrome */}
      <div className="flex items-center gap-1.5 px-4 py-3 border-b border-slate-100 bg-slate-50">
        <span className="h-2.5 w-2.5 rounded-full bg-slate-300" />
        <span className="h-2.5 w-2.5 rounded-full bg-slate-300" />
        <span className="h-2.5 w-2.5 rounded-full bg-slate-300" />
      </div>
      {/* Header */}
      <div className="px-6 pt-5 pb-3 flex items-center justify-between border-b border-slate-100">
        <div className="text-[11px] uppercase tracking-[0.14em] font-semibold text-slate-500">
          Pulse responses · streaming
        </div>
        <div className="inline-flex items-center gap-1.5 text-[11px] font-medium text-slate-400 tabular-nums">
          <span className="h-1.5 w-1.5 rounded-full bg-emerald-500 animate-pulse" />
          947 read
        </div>
      </div>

      {/* Scrolling viewport */}
      <div className="relative overflow-hidden" style={{ height: `${VIEWPORT_H}px` }}>
        {/* Top + bottom fade masks */}
        <div className="pointer-events-none absolute inset-x-0 top-0 h-14 bg-gradient-to-b from-white to-transparent z-10" />
        <div className="pointer-events-none absolute inset-x-0 bottom-0 h-14 bg-gradient-to-t from-white to-transparent z-10" />

        <div
          className="absolute inset-x-0 top-0 px-6 pt-3"
          style={{ transform: `translateY(${-scrollY}px)`, transition: 'none' }}
        >
          {cards.map((c, i) => {
            const isTarget = i === TARGET_IDX;
            const dim = picked && !isTarget;
            return (
              <div
                key={i}
                className="rounded-lg border bg-white px-3.5 py-2.5"
                style={{
                  marginBottom: `${CARD_H - 42}px`,
                  borderColor: isTarget && picked ? '#fcd34d' : '#e2e8f0',
                  background: isTarget && picked ? '#fffbeb' : '#ffffff',
                  opacity: dim ? 0.28 : 1,
                  transform: isTarget && picked ? 'scale(1.015)' : 'scale(1)',
                  transformOrigin: 'left center',
                  boxShadow: isTarget && picked ? '0 6px 18px -8px rgba(245, 158, 11, 0.35)' : 'none',
                  transition: 'opacity 350ms ease-out, border-color 350ms ease-out, background 350ms ease-out, transform 350ms ease-out, box-shadow 350ms ease-out',
                }}
              >
                <p className="text-[13px] leading-snug text-slate-700">
                  {isTarget ? (
                    <>
                      "My line manager{' '}
                      <span className="relative inline-block">
                        <span className="relative z-10">{c.trigger}</span>
                        <span
                          className="absolute left-0 bottom-0 h-[7px] bg-amber-300/80 rounded-sm -z-0"
                          style={{
                            width: picked ? '100%' : '0%',
                            transition: 'width 600ms cubic-bezier(0.22, 1, 0.36, 1) 200ms',
                          }}
                        />
                      </span>
                      {' '}and nothing has changed."
                    </>
                  ) : c.text}
                </p>
              </div>
            );
          })}
        </div>
      </div>

      {/* AI flag chip — slides in */}
      <div
        className="absolute left-6 right-6 bottom-5 flex items-center gap-2.5 rounded-xl bg-white border border-amber-200 shadow-cardlg px-4 py-3 z-20"
        style={{
          opacity: chip ? 1 : 0,
          transform: chip ? 'translateY(0)' : 'translateY(12px)',
          transition: 'opacity 380ms ease-out, transform 420ms cubic-bezier(0.22, 1, 0.36, 1)',
        }}
      >
        <span className="inline-flex items-center justify-center h-7 w-7 rounded-full bg-amber-500 text-white shrink-0">
          <IconShieldAlert size={14} />
        </span>
        <div className="min-w-0 flex-1">
          <div className="text-[10px] uppercase tracking-wider font-bold text-amber-700">
            Critical concern detected
          </div>
          <div className="text-[13px] font-semibold text-slate-900 truncate">
            Possible harassment · response #4127
          </div>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, {
  HeroV2, TrustSectionV2, CustomerLogoGrid, PainCarousel, PainSlide, HeroVisual, ConcernProblemVisual,
});
