/* ── Page sections ─────────────────────────────────────────────────────── */

/* ── Nav ─────────────────────────────────────────────────────────── */
// Shared contact endpoints. CALENDAR_URL → Google Appointment Scheduling
// booking page. WHATSAPP_URL → Steve Coulson's WhatsApp.
const CALENDAR_URL = 'https://calendar.app.google/ZHdXrPLPowkHMDsP6';
const WHATSAPP_URL = 'https://wa.me/447742613897?text=Hi%20Reeve%2C%20I%27d%20like%20to%20book%20a%20job.';
const JOB_FORM_URL = 'https://reeve-helpdesk.vercel.app'; // Book a Job → helpdesk intake form
const JOB_WIDGET_SRC = 'https://reeve-helpdesk.vercel.app/embed'; // Book a Job → in-page chat overlay

// Open the Book a Job chat overlay instead of navigating away. Any "Book a
// Job" anchor keeps its JOB_FORM_URL href as a graceful fallback, but this
// preventDefaults the click and asks the <JobWidget/> to reveal itself.
function openJobWidget(e) {
  if (e) e.preventDefault();
  window.dispatchEvent(new CustomEvent('reeve:open-job'));
}

function Nav() {
  const [scrolled, setScrolled] = React.useState(false);
  const [showCta, setShowCta] = React.useState(false);
  React.useEffect(() => {
    const onScroll = () => {
      setScrolled(window.scrollY > 12);
      // Reveal the nav CTAs only once the visitor reaches the "Supporting
      // property teams…" (Trust) strip; hidden over the hero so the hero's
      // own Book a Demo / Book a Job buttons lead there.
      const trust = document.querySelector('.trust');
      if (trust) {
        const trustTop = trust.getBoundingClientRect().top + window.scrollY;
        setShowCta(window.scrollY + window.innerHeight * 0.5 >= trustTop);
      }
    };
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    onScroll();
    return () => {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onScroll);
    };
  }, []);
  return (
    <nav className={'nav' + (scrolled ? ' scrolled' : '')}>
      <div className="wrap nav-inner">
        <div className="nav-mid">
          <a href="#top" aria-label="Reeve home" className="nav-brand">
            <img
              src="assets/img/brand/reeve-lockup-light.svg"
              alt="Reeve"
              style={{ height: 30, width: 'auto', display: 'block' }}
            />
          </a>
        </div>
        <div className="nav-links">
          <a href="#product">Product</a>
          <a href="#brand">For property teams</a>
          <a href="#model">Model</a>
          <a href="#services">Services</a>
          <a href="#vs">Why Reeve</a>
        </div>
        <div className={'nav-cta' + (showCta ? ' is-visible' : '')}>
          <a
            href={CALENDAR_URL}
            target="_blank"
            rel="noopener noreferrer"
            className="btn btn-primary"
          >
            Book a demo
            <span className="arrow">→</span>
          </a>
          <a
            href={JOB_FORM_URL}
            target="_blank"
            rel="noopener noreferrer"
            className="btn btn-outline"
            onClick={openJobWidget}
          >
            Book a job
            <span className="arrow">→</span>
          </a>
        </div>
      </div>
    </nav>
  );
}

/* ── Hero ─────────────────────────────────────────────────────────── */
function Hero({ headline, sub }) {
  return (
    <>
      <section className="hero">
        <div className="wrap">
          <div className="hero-stack">
            <div className="hero-top">
              <div className="hero-header">
                <h1 className="h-display fade-up d2" dangerouslySetInnerHTML={{ __html: headline }} />
                <p className="lede fade-up d3">{sub}</p>
                <div className="hero-ctas fade-up d4">
                  <a
                    href={CALENDAR_URL}
                    target="_blank"
                    rel="noopener noreferrer"
                    className="btn btn-primary"
                  >
                    Book a demo <span className="arrow">→</span>
                  </a>
                  <a
                    href={JOB_FORM_URL}
                    target="_blank"
                    rel="noopener noreferrer"
                    className="btn btn-outline"
                    onClick={openJobWidget}
                  >
                    Book a job <span className="arrow">→</span>
                  </a>
                </div>

                <div className="hero-stat fade-up d4">
                  <div className="hero-stat-pair">
                    <div className="hero-stat-cell">
                      <div className="hero-stat-big">1.8<sub>d</sub></div>
                      <div className="hero-stat-cell-label">Reeve average</div>
                    </div>
                    <div className="hero-stat-vs">vs</div>
                    <div className="hero-stat-cell hero-stat-cell-muted">
                      <div className="hero-stat-big">9.0<sub>d</sub></div>
                      <div className="hero-stat-cell-label">Industry Average</div>
                    </div>
                  </div>
                  <div className="hero-stat-foot">
                    A <strong>7.2-day Resolution Gap</strong> where the chase lives. <span className="hero-stat-foot-src">Source: Reeve platform data.</span>
                  </div>
                </div>
              </div>

              <div className="hero-photo fade-up d3">
                <img
                  src="assets/img/hero.jpg"
                  alt="A Reeve operative cleaning an office air-handling vent"
                />
              </div>
            </div>
          </div>
        </div>
      </section>
    </>
  );
}

/* ── Platform showcase ─ live FM platform mockup, its own band ───────── */
function PlatformShowcase() {
  return (
    <section className="platform-showcase">
      <div className="wrap">
        <div className="platform-frame fade-up">
          <StairwellHero />
        </div>
      </div>
    </section>
  );
}

/* ── Compliance showcase ─ auto-playing photo carousel of the compliance flow.
   Mirrors the stairwell demo: each slide dwells on a timer then advances, and
   the ← → arrows step manually (which restarts the dwell for the new slide).
   A "Generate an indicative compliance checklist" CTA (→ CALENDAR_URL, same as
   the site's other booking links) sits directly beneath. */
const COMPLIANCE_SLIDES = [
  { src: 'assets/img/compliance/1.png', label: 'Compliance Overview' },
  { src: 'assets/img/compliance/2.png', label: 'Certificate status at a glance' },
  { src: 'assets/img/compliance/3.png', label: 'Reviewing an assessment' },
  { src: 'assets/img/compliance/4.png', label: 'Remedial findings' },
  { src: 'assets/img/compliance/5.png', label: 'Scheduled resolution' },
];
const COMPLIANCE_DWELL = 4500; // ms per slide

function ComplianceShowcase() {
  const [i, setI] = React.useState(0);
  // Once the user drives with the arrows, auto-play stops for good (manual-only)
  // so a demo isn't skipped to the next slide mid-explanation.
  const [manual, setManual] = React.useState(false);
  const n = COMPLIANCE_SLIDES.length;

  // Auto-advance until the user takes manual control; `manual` short-circuits the
  // timer so once an arrow is pressed the slides only change manually.
  React.useEffect(() => {
    if (manual) return;
    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    const t = setTimeout(() => setI((k) => (k + 1) % n), COMPLIANCE_DWELL);
    return () => clearTimeout(t);
  }, [i, n, manual]);

  const go = (dir) => {
    setManual(true);
    setI((k) => (k + dir + n) % n);
  };

  return (
    <section className="platform-showcase cx-showcase">
      <div className="wrap">
        <div className="platform-frame fade-up">
          <div className="cx-stage">
            {COMPLIANCE_SLIDES.map((s, idx) => (
              <img
                key={idx}
                src={s.src}
                alt={`Reeve compliance — ${s.label}`}
                className={'cx-slide' + (idx === i ? ' is-active' : '')}
                loading="lazy"
                draggable="false"
                aria-hidden={idx === i ? undefined : true}
              />
            ))}
            <div className="cx-controls" role="group" aria-label="Step through the compliance views">
              <button type="button" className="cx-ctrl" onClick={() => go(-1)} aria-label="Previous view">
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M15 6l-6 6 6 6"/></svg>
              </button>
              <span className="cx-count">{i + 1}/{n} · {COMPLIANCE_SLIDES[i].label}</span>
              <button type="button" className="cx-ctrl" onClick={() => go(1)} aria-label="Next view">
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M9 6l6 6-6 6"/></svg>
              </button>
            </div>
          </div>
        </div>
        <div className="cx-cta-row">
          <a href={CALENDAR_URL} target="_blank" rel="noopener noreferrer" className="cx-cta-link">
            Generate an indicative compliance checklist <span className="cx-cta-arrow" aria-hidden="true">→</span>
          </a>
        </div>
      </div>
      <style>{`
        .cx-showcase .cx-stage {
          position: relative;
          width: 100%;
          aspect-ratio: 1750 / 1000;
          border-radius: 16px;
          overflow: hidden;
          background: #0e0f0e;
          border: 1px solid var(--rule);
          box-shadow: 0 30px 60px -30px rgba(0, 0, 0, 0.35);
        }
        .cx-showcase .cx-slide {
          position: absolute;
          inset: 0;
          width: 100%;
          height: 100%;
          object-fit: cover;
          opacity: 0;
          transition: opacity 600ms ease;
          pointer-events: none;
          user-select: none;
        }
        .cx-showcase .cx-slide.is-active { opacity: 1; }
        .cx-showcase .cx-controls {
          position: absolute; left: 50%; bottom: 16px; transform: translateX(-50%);
          z-index: 20; display: flex; align-items: center; gap: 4px;
          padding: 5px 7px; border-radius: 999px;
          background: rgba(255, 255, 255, 0.9);
          border: 1px solid var(--rule);
          box-shadow: 0 8px 22px -10px rgb(0 0 0 / 0.3);
          backdrop-filter: blur(8px);
        }
        .cx-showcase .cx-ctrl {
          width: 32px; height: 32px; display: grid; place-items: center;
          border-radius: 50%; border: none; background: transparent;
          color: var(--ink-2); cursor: pointer; transition: background 140ms ease, color 140ms ease;
        }
        .cx-showcase .cx-ctrl:hover { background: #ececea; color: var(--ink); }
        .cx-showcase .cx-ctrl:focus-visible { outline: 2px solid var(--accent, #1f8a5b); outline-offset: 2px; }
        .cx-showcase .cx-count {
          font-size: 12.5px; font-weight: 600; color: var(--ink-2);
          min-width: 40px; text-align: center; letter-spacing: -0.01em; white-space: nowrap;
        }
        .cx-showcase .cx-cta-row { display: flex; justify-content: flex-end; margin-top: clamp(14px, 1.8vw, 22px); }
        .cx-showcase .cx-cta-link {
          color: var(--accent); font-family: var(--display);
          font-size: clamp(16px, 1.6vw, 19px); font-weight: 600; letter-spacing: -0.01em;
          text-decoration: underline; text-underline-offset: 5px; text-decoration-thickness: 1.5px;
          white-space: nowrap; transition: opacity 140ms ease;
        }
        .cx-showcase .cx-cta-link:hover { opacity: 0.72; }
        @media (max-width: 560px) {
          .cx-showcase .cx-cta-row { justify-content: center; }
          .cx-showcase .cx-cta-link { white-space: normal; text-align: center; }
        }
      `}</style>
    </section>
  );
}

/* ── Dispatch log ─ scrolling status ticker, sits under the hero ── */
function DispatchLog() {
  const items = [
    { time: '08:14', ev: 'P1 lift fault',         loc: 'Lyle St SE1',        s: 'dispatched · 6 min' },
    { time: '08:08', ev: 'HVAC filter clean',     loc: 'Tower 9 EC2',        s: 'on site' },
    { time: '07:52', ev: 'Leak',                  loc: 'Greycote SW1',       s: 'resolved', ok: true },
    { time: '07:41', ev: 'CCTV system repair',    loc: 'Lambert WC1',        s: 'triage' },
    { time: '07:20', ev: 'Fire panel adjustment', loc: 'Westhall E1',        s: 'resolved', ok: true },
    { time: '07:02', ev: 'Deep clean required',   loc: 'Thirty-Three EC1',   s: 'dispatched · 12 min' },
    { time: '06:48', ev: 'Pest control service',  loc: 'Hyperion W1',        s: 'resolved', ok: true },
    { time: '06:31', ev: 'Shower descale',        loc: 'Curtain Road EC2',   s: 'on site' },
  ];
  // double for marquee seamless loop
  const loop = [...items, ...items];
  return (
    <div className="dispatch-log">
      <div className="wrap">
        <div className="dispatch-track">
          {loop.map((it, i) => (
            <span className="item" key={i}>
              <span className="time">{it.time}</span>
              <span className="ev">{it.ev}</span>
              <span>{it.loc}</span>
              <span className="sep">→</span>
              <span className={it.ok ? 'ok' : ''}>{it.s}</span>
            </span>
          ))}
        </div>
      </div>
    </div>
  );
}

/* ── Certifications strip ─ sits directly under the dispatch ticker ── */
function Certifications() {
  return (
    <section className="certs">
      <div className="wrap certs-row">
        <img
          src="assets/img/iso27001.png"
          alt="ISO 27001 certified"
          className="cert-img"
        />
        <svg
          width="72"
          height="72"
          viewBox="0 0 100 100"
          xmlns="http://www.w3.org/2000/svg"
          className="cert-bcorp"
          aria-label="Certified B Corporation"
        >
          <text x="50" y="14" textAnchor="middle" fontFamily="Inter Tight, ui-sans-serif, system-ui" fontSize="9" fontWeight="600" letterSpacing="0.5" fill="currentColor">
            CERTIFIED
          </text>
          <circle cx="50" cy="52" r="20" fill="none" stroke="currentColor" strokeWidth="2.6" />
          <text x="50" y="60" textAnchor="middle" fontFamily="Inter Tight, ui-sans-serif, system-ui" fontSize="26" fontWeight="700" fill="currentColor">
            B
          </text>
          <line x1="32" y1="80" x2="68" y2="80" stroke="currentColor" strokeWidth="1.4" />
          <text x="50" y="92" textAnchor="middle" fontFamily="Inter Tight, ui-sans-serif, system-ui" fontSize="9" fontWeight="600" letterSpacing="0.3" fill="currentColor">
            CORPORATION
          </text>
        </svg>
        <img
          src="assets/img/accreditations/iwfm.png"
          alt="IWFM corporate member"
          className="cert-img cert-img-sm"
        />
      </div>
    </section>
  );
}

/* ── Three ways to start ─ buyer-entry wedges ────────────────────────── */
function ThreeWaysToStart() {
  const ways = [
    {
      title: 'Need a job handled?',
      body: 'Send Reeve one job your team would otherwise have to chase. We triage it, route it and report the resolution path.',
      cta: 'Book a job',
      href: JOB_FORM_URL,
      onClick: openJobWidget
    },
    {
      title: 'Reviewing your FM workflow?',
      body: 'Walk through how Reeve would sit alongside or replace your current helpdesk, CAFM, supplier coordination and reporting flow.',
      cta: 'Book a platform walkthrough',
      href: CALENDAR_URL
    },
    {
      title: 'Unsure what checks apply?',
      body: 'Generate an indicative, non-binding checklist of common FM and compliance-related checks to review with a competent person.',
      cta: 'Generate an indicative compliance checklist',
      href: CALENDAR_URL
    }
  ];

  return (
    <section className="section-pad" id="start">
      <div className="wrap">
        <div className="section-split">
          <div>
            <h2 className="h-section">Three ways to start.</h2>
          </div>
        </div>
        <div className="three-ways-grid">
          {ways.map((w, i) => (
            <a key={w.title} href={w.href} target="_blank" rel="noopener noreferrer" className="three-way-card" onClick={w.onClick}>
              <span className="step-n">0{i + 1}</span>
              <h3>{w.title}</h3>
              <p>{w.body}</p>
              <span className="text-link">{w.cta} <span aria-hidden="true">→</span></span>
            </a>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ── Trust strip ─────────────────────────────────────────────────────── */
function Trust() {
  // Real Kitt client logos — used here as illustrative occupier brands
  // that already sit in Kitt-operated buildings.
  const clients = [
    { src: 'assets/img/logos/clients/client_flightstory.png',     alt: 'Flightstory' },
    { src: 'assets/img/logos/clients/client_eurostar.svg',        alt: 'Eurostar' },
    { src: 'assets/img/logos/clients/client_amazon_property.png', alt: 'Amazon Property' },
    { src: 'assets/img/logos/clients/kitt_client_oatly.svg',      alt: 'Oatly' },
    { src: 'assets/img/logos/clients/kitt_client_flo.svg',        alt: 'Flo' },
  ];
  return (
    <section className="trust">
      <div className="wrap trust-row">
        {/* TODO: confirm logo/proof permissions before public launch (see open questions). */}
        <span className="trust-label">Supporting property teams and buildings connected to</span>
        <div className="trust-logos">
          {clients.map((c, i) => (
            <img key={i} className="trust-logo" src={c.src} alt={c.alt} />
          ))}
        </div>
      </div>
    </section>
  );
}

/* ── Problem ─────────────────────────────────────────────────────── */
function Problem() {
  const pains = [
    {
      n: '01',
      t: 'Too many routes, no single standard.',
      d: 'Different suppliers, different formats, different evidence. Every job is its own conversation.'
    },
    {
      n: '02',
      t: 'Suppliers are visible, not controlled.',
      d: 'You can see the directory. You can\'t see SLA breaches, recurring faults or what each supplier actually costs.'
    },
    {
      n: '03',
      t: 'Reporting and resolution arrive too late.',
      d: 'Static monthly reports describe last month. The building\'s already paid the price.'
    },
  ];
  return (
    <section className="section-pad" id="problem">
      <div className="wrap problem-grid">
        <div>
          <span className="eyebrow">The problem</span>
          <h2 className="h-section" style={{ marginTop: 20 }}>
            Ticketing systems log work. They don't run buildings.
          </h2>
        </div>
        <div>
          <p className="lede" style={{ marginBottom: 8 }}>
            Most property teams can already record a job. The hard part is everything after:
            triage, chasing suppliers, access, evidence, approvals, repeat faults, client updates —
            and knowing whether the building is actually getting better, faster.
          </p>
          <div className="pains">
            {pains.map(p => (
              <div className="pain" key={p.n}>
                <span className="pain-num">{p.n}</span>
                <div>
                  <div className="pain-title">{p.t}</div>
                  <div className="pain-desc">{p.d}</div>
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
}

/* ── Routes ─────────────────────────────────────────────────────── */
function Routes() {
  return (
    <section className="routes-block" id="model">
      <div className="wrap">
        <div className="section-split">
          <div>
            <h2 className="h-section">
              A flexible delivery model.
            </h2>
          </div>
          <p className="section-split-lede">
            For each job, your property team picks the right route; your suppliers, Reeve operatives or our vetted specialists. Reeve keeps the workflow, evidence and reporting consistent across all three, so your portfolio gets a consistent service.
          </p>
        </div>

        <div className="route-grid route-grid-anchored">
          {/* LEFT: core route — full height */}
          <article className="route premium route-core">
            <h3 className="route-title">Use Reeve operatives.</h3>
            <p className="route-desc">Use Reeve's team of operatives (in-house and vetted supply chain), ready for dispatch across a range of trades. The fastest, most reliable mode of delivery, tracked from dispatch to resolution. Proof that Reeve can make the work happen, not just log it.</p>
            <ul className="route-list">
              <li>Reeve-employed operatives, embedded alongside your team</li>
              <li>A vetted specialist supply chain for everything else, managed end to end</li>
              <li>Live 'en-route' tracking</li>
              <li>No call-out fee model, ensuring speedy and economic delivery</li>
            </ul>

            {/* Footer: overlapping operative avatars with hover tooltips */}
            <div className="route-operatives">
              {[
                { name: 'Jimmy P.',   skill: 'L2 Electrician',  tenure: '3 years, 4 months', img: 'assets/img/operatives/jimmy.jpg' },
                { name: 'Roy Kendall',     skill: 'Lift Engineer',   tenure: '2 years, 1 month',  img: 'assets/img/operatives/roy.jpg' },
                { name: 'Gerry W.',   skill: 'HVAC Specialist', tenure: '4 years, 7 months', img: 'assets/img/operatives/gerry.jpg' },
                { name: 'Karolis B.', skill: 'Delivery driver', tenure: '1 year, 2 months',  img: 'assets/img/operatives/karolis.jpg' },
                { name: 'Trish',      skill: 'Triaging agent',  ai: true,                    img: 'assets/img/operatives/trish.svg' },
              ].map((op, i) => (
                <span
                  key={op.name}
                  className={'op-avatar' + (op.ai ? ' op-avatar-ai' : '')}
                  style={{ zIndex: 10 - i }}
                  tabIndex="0"
                  aria-label={
                    op.ai
                      ? `${op.name}, ${op.skill}, in-built AI agent on Reeve platform`
                      : `${op.name}, ${op.skill}, with Reeve for ${op.tenure}`
                  }
                >
                  <span className="op-photo">
                    <img src={op.img} alt="" />
                  </span>
                  <span className="op-tooltip" role="tooltip">
                    <span className="op-tooltip-name">
                      {op.name}
                      {op.ai && (
                        <span className="op-tooltip-ai" aria-label="AI agent">
                          <svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
                            <path d="M10 3.2c.1-.27.49-.27.6 0l1.3 3.45a3 3 0 0 0 1.75 1.75l3.45 1.3c.27.1.27.49 0 .6l-3.45 1.3a3 3 0 0 0-1.75 1.75l-1.3 3.45a.32.32 0 0 1-.6 0l-1.3-3.45a3 3 0 0 0-1.75-1.75l-3.45-1.3a.32.32 0 0 1 0-.6l3.45-1.3a3 3 0 0 0 1.75-1.75l1.3-3.45Z" />
                          </svg>
                          AI
                        </span>
                      )}
                    </span>
                    <span className="op-tooltip-skill">{op.skill}</span>
                    <span className="op-tooltip-tenure">
                      {op.ai ? 'In-built AI agent on Reeve platform' : `With Reeve for ${op.tenure}`}
                    </span>
                  </span>
                </span>
              ))}
            </div>
          </article>

          {/* RIGHT: outline add-on */}
          <div className="route-addons">
            <article className="route route-outline">
              <h3 className="route-title">Use your suppliers.</h3>
              <p className="route-desc">Keep the contractors you already trust, and run them on the same Reeve platform, process and technology as everything else. Same workflow, same evidence, same reporting.</p>
              <ul className="route-list">
                <li>Onboarded onto the same platform, triage and routing</li>
                <li>Live tracking and evidence capture on every job</li>
                <li>Quotes, approvals and SLAs managed in one place</li>
                <li>Full visibility, so nothing slips back to your team to chase</li>
              </ul>
            </article>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ── Services in scope ─ exact port of the home-page ServiceExplorer ── */
const SERVICES_DATA = [
  {
    id: 'cleaning',
    title: 'Cleaning & Housekeeping',
    cat: 'Soft FM',
    img: 'assets/img/cleaning.jpg',
    lede: 'Nightly, lunchtime and housekeeping services, delivered by a range of in-house cleaning operatives. Customisable task lists, nightly audits, and specialist services, such as sanitary, waste collection and deep cleaning.',
  },
  {
    id: 'compliance',
    title: 'Compliance, Health & Safety',
    cat: 'Regulatory',
    img: 'assets/img/compliance-v2.jpg',
    lede: "Statutory compliance including Fire Risk Assessment, Water Risk Assessment, L8 legionella testing, emergency lighting drain-down, fire extinguisher servicing and fire door inspections. We automatically detect and triage remedial works, subject to your team's approval.",
  },
  {
    id: 'reactive',
    title: 'Reactive Maintenance',
    cat: 'Hard FM',
    img: 'assets/img/maintenance.jpg',
    lede: 'In-house electrical, plumbing, carpentry and handyman specialists. Bookable for one-off jobs, as well as more permanent day rates, our maintenance team embed into your property team as your own.',
  },
  {
    id: 'hvac',
    title: 'HVAC Maintenance',
    cat: 'Hard FM',
    img: 'assets/img/hvac-v2.jpg',
    lede: 'Annual servicing, inspections, filter swaps and monthly checks, for all heating, ventilation and cooling systems.',
  },
  {
    id: 'internet',
    title: 'Connectivity',
    cat: 'Tech',
    img: 'assets/img/tech.jpg',
    lede: 'Full fibre, microwave and 5G solutions, primary and backup lines, managed firewall and Wi-Fi, 24/7 ISP NOC monitoring with auto-ticketed fault triage.',
  },
  {
    id: 'security',
    title: 'Security & CCTV',
    cat: 'Tech',
    img: 'assets/img/security-delivery.jpg',
    lede: 'Motion-triggered CCTV on access/egress, out-of-hours monitoring, keyholding, guarding dispatch and police liaison on suspicious activation.',
  },
  {
    id: 'plants',
    title: 'Plants & Foliage',
    cat: 'Soft FM',
    img: 'assets/img/plants.jpg',
    lede: 'A range of foliage offerings, including floor standing, potted plants and bespoke/curated foliage design. Comprehensive plant servicing that focuses on maintaining plant health and aesthetics.',
  },
  {
    id: 'fb',
    title: 'Tea, Coffee & Milk',
    cat: 'F&B',
    img: 'assets/img/coffee-beans.jpg',
    lede: 'Artisan beans, three teas, dairy and alternative milk offerings. All delivered directly into your fridge, pantry and coffee machine. We track stock and detect top ups, rapidly restocking your supplies without you moving a finger.',
  },
  {
    id: 'coffee',
    title: 'Coffee Machines',
    cat: 'F&B',
    img: 'assets/img/coffee-machine.jpg',
    lede: "Commercial-grade coffee machines, with full management and servicing included. We're able to dispatch engineers quickly, provide replacement machines and work on bespoke machine cleaning programmes.",
  },
  {
    id: 'snacks',
    title: 'Snacks, Fruit & Pantry',
    cat: 'F&B',
    img: 'assets/img/fb-snacks-basket.jpg',
    lede: 'A 500+ product range delivered next-day and put away into your fridge or pantry. Fresh fruit sourced from local markets, all transported on a fully electric fleet.',
  },
];

function servicesImgPosition(id) {
  if (id === 'fb') return 'center 70%';
  if (id === 'coffee') return 'center 65%';
  return 'center';
}

function ServicesCard({ s }) {
  return (
    <article
      className="services-card"
      style={{
        scrollSnapAlign: 'start',
        display: 'flex',
        flexDirection: 'column',
        background: '#ffffff',
        border: '1px solid var(--rule)',
        borderRadius: 16,
        overflow: 'hidden',
        boxShadow: '0 2px 6px rgb(0 0 0 / 0.04)',
      }}
    >
      <div
        style={{
          height: 200,
          backgroundImage: `url(${s.img})`,
          backgroundSize: 'cover',
          backgroundPosition: servicesImgPosition(s.id),
          position: 'relative',
        }}
      >
        <div
          style={{
            position: 'absolute',
            top: 14,
            left: 14,
            background: '#ffffff',
            color: 'var(--ink)',
            padding: '5px 11px',
            borderRadius: 999,
            fontSize: 11,
            fontWeight: 600,
            letterSpacing: '0.04em',
            textTransform: 'uppercase',
            boxShadow: '0 2px 6px rgb(0 0 0 / 0.12)',
          }}
        >
          {s.cat}
        </div>
      </div>
      <div style={{ padding: 24, display: 'flex', flexDirection: 'column', flex: 1 }}>
        <h3
          style={{
            fontFamily: 'var(--display)',
            fontSize: 22,
            fontWeight: 600,
            letterSpacing: '-0.02em',
            lineHeight: 1.15,
            marginBottom: 12,
            color: 'var(--ink)',
          }}
        >
          {s.title}
        </h3>
        <p style={{ fontSize: 14, color: 'var(--ink-2)', lineHeight: 1.55, margin: 0 }}>{s.lede}</p>
      </div>
    </article>
  );
}

function Services() {
  const scrollerRef = React.useRef(null);
  const [canPrev, setCanPrev] = React.useState(false);
  const [canNext, setCanNext] = React.useState(true);

  const updateButtons = () => {
    const el = scrollerRef.current;
    if (!el) return;
    setCanPrev(el.scrollLeft > 4);
    setCanNext(el.scrollLeft + el.clientWidth < el.scrollWidth - 4);
  };

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

  const scrollBy = (dir) => {
    const el = scrollerRef.current;
    if (!el) return;
    const step = Math.round(el.clientWidth * 0.85);
    el.scrollBy({ left: dir * step, behavior: 'smooth' });
  };

  return (
    <section
      data-sect
      id="services"
      style={{ borderTop: '1px solid var(--rule)', padding: 'clamp(72px, 9vw, 112px) 0' }}
    >
      <div className="wrap">
        <div
          style={{
            display: 'flex',
            flexWrap: 'wrap',
            alignItems: 'end',
            justifyContent: 'space-between',
            gap: 24,
            marginBottom: 24,
          }}
        >
          <div style={{ maxWidth: 760 }}>
            <h2 className="h-section">
              Full FM scope, from cleaning to compliance.
            </h2>
            <p className="lede" style={{ marginTop: 14 }}>
              Reeve is not just reactive maintenance. It keeps the broader FM workload moving across the portfolio.
            </p>
          </div>
          <div style={{ display: 'flex', gap: 8 }}>
            <button
              type="button"
              aria-label="Previous services"
              onClick={() => scrollBy(-1)}
              disabled={!canPrev}
              style={{
                width: 44,
                height: 44,
                borderRadius: '50%',
                border: '1px solid var(--rule)',
                background: '#f4f4f4',
                color: canPrev ? 'var(--ink)' : 'var(--ink-3)',
                cursor: canPrev ? 'pointer' : 'default',
                opacity: canPrev ? 1 : 0.5,
                fontSize: 18,
                fontFamily: 'var(--display)',
              }}
            >
              ←
            </button>
            <button
              type="button"
              aria-label="Next services"
              onClick={() => scrollBy(1)}
              disabled={!canNext}
              style={{
                width: 44,
                height: 44,
                borderRadius: '50%',
                border: '1px solid var(--rule)',
                background: '#f4f4f4',
                color: canNext ? 'var(--ink)' : 'var(--ink-3)',
                cursor: canNext ? 'pointer' : 'default',
                opacity: canNext ? 1 : 0.5,
                fontSize: 18,
                fontFamily: 'var(--display)',
              }}
            >
              →
            </button>
          </div>
        </div>

        <div style={{ position: 'relative', marginTop: 16 }}>
          <style>{`
            .gantry-services-scroller { scrollbar-width: none; -ms-overflow-style: none; }
            .gantry-services-scroller::-webkit-scrollbar { display: none; }
          `}</style>
          <div
            ref={scrollerRef}
            className="gantry-services-scroller"
            style={{
              display: 'flex',
              gap: 20,
              overflowX: 'auto',
              scrollSnapType: 'x mandatory',
              padding: '8px 4px 32px',
              WebkitOverflowScrolling: 'touch',
            }}
          >
            {SERVICES_DATA.map((s) => (
              <ServicesCard key={s.id} s={s} />
            ))}
          </div>
        </div>
      </div>
    </section>
  );
}

/* ── Modules ─ carousel format, cards LEFT, heading RIGHT ─────────── */
function Modules() {
  return (
    <section className="section-pad" id="product">
      <div className="wrap">
        <div className="section-split">
          <div>
            <h2 className="h-section">Everything property teams usually have to chase, in one flow.</h2>
          </div>
          <p className="section-split-lede">
            From the first helpdesk message to resolution path reporting, Reeve keeps each step visible, evidenced and moving.
          </p>
        </div>

        <div className="modules-grid">
          {/* 1 — Live tracking */}
              <article className="m-card">
                <div className="m-card-visual m-vis-tracking">
                  <div className="m-tracking-phone">
                    <div className="m-tracking-head">
                      <span className="m-tracking-mark" />
                      <div>
                        <div className="m-tracking-brand">Marlow &amp; Finch</div>
                        <div className="m-tracking-sub">123 Lyle Street</div>
                      </div>
                      <span className="m-tracking-live"><span className="live-dot" />Live</span>
                    </div>
                    <div className="m-tracking-map">
                      <svg viewBox="0 0 200 100" preserveAspectRatio="xMidYMid slice">
                        <defs>
                          {/* Route hugs the streets: east along bottom road,
                              north up a side street, east along mid road,
                              then north up to the destination on top road.
                              Q-rounded corners at each junction. */}
                          <path
                            id="m-route-path"
                            d="M 20 89 L 86 88 Q 90 88 93 84 L 95 58 Q 96 54 100 53 L 152 51 Q 156 51 157 47 L 158 25"
                          />
                        </defs>

                        {/* Building blocks — gentle texture between streets */}
                        <g fill="oklch(94% 0.008 80)" opacity="0.7">
                          <rect x="42" y="27" width="44" height="22" rx="1" />
                          <rect x="102" y="27" width="48" height="22" rx="1" />
                          <rect x="40" y="60" width="50" height="22" rx="1" />
                          <rect x="106" y="58" width="46" height="22" rx="1" />
                          <rect x="165" y="32" width="38" height="16" rx="1" />
                          <rect x="-5" y="60" width="32" height="22" rx="1" />
                          <rect x="-5" y="27" width="32" height="22" rx="1" />
                        </g>

                        {/* Small park / square */}
                        <rect
                          x="165"
                          y="58"
                          width="40"
                          height="22"
                          rx="2"
                          fill="oklch(92% 0.04 145)"
                          opacity="0.75"
                        />

                        {/* Streets — irregular London-ish layout */}
                        <g stroke="oklch(86% 0.005 80)" strokeWidth="4" fill="none" strokeLinecap="round" strokeLinejoin="round">
                          {/* horizontal arteries with subtle curves */}
                          <path d="M -5 22 Q 60 18 110 25 Q 160 32 210 28" />
                          <path d="M -5 54 Q 80 58 150 51 L 210 49" />
                          <path d="M -5 89 Q 70 92 150 85 L 210 80" />
                          {/* vertical roads — slight tilt, not parallel */}
                          <path d="M 32 -5 L 28 110" />
                          <path d="M 95 -5 L 98 110" />
                          <path d="M 158 -5 L 155 110" />
                          {/* diagonal cross street (Charing Cross Rd-style) */}
                          <path d="M 178 -5 L 128 56" strokeWidth="3" opacity="0.6" />
                          {/* small mews / side street */}
                          <path d="M 60 22 L 64 54" strokeWidth="2.5" opacity="0.55" />
                        </g>

                        {/* Faint base route */}
                        <use
                          href="#m-route-path"
                          stroke="color-mix(in oklch, var(--accent) 28%, transparent)"
                          strokeWidth="3"
                          fill="none"
                          strokeLinecap="round"
                          strokeLinejoin="round"
                        />

                        {/* Animated bright progress line — draws in as the
                            operative travels */}
                        <use
                          href="#m-route-path"
                          stroke="var(--accent)"
                          strokeWidth="3"
                          fill="none"
                          strokeLinecap="round"
                          strokeLinejoin="round"
                          pathLength="100"
                          strokeDasharray="100"
                          strokeDashoffset="100"
                        >
                          <animate
                            attributeName="stroke-dashoffset"
                            from="100"
                            to="0"
                            dur="7s"
                            repeatCount="indefinite"
                          />
                        </use>

                        {/* Origin marker — operative's starting point */}
                        <g transform="translate(20 89)">
                          <circle r="2.6" fill="var(--accent)" opacity="0.4" />
                        </g>

                        {/* Destination pin (the building) */}
                        <g transform="translate(158 25)">
                          <circle r="5" fill="oklch(0.32 0.06 250)" />
                          <circle r="1.5" fill="var(--paper)" />
                        </g>

                        {/* Moving operative — pulses & travels */}
                        <g>
                          <circle r="3" fill="var(--accent)" opacity="0.45">
                            <animate attributeName="r" values="3;11;3" dur="1.8s" repeatCount="indefinite" />
                            <animate attributeName="opacity" values="0.55;0;0.55" dur="1.8s" repeatCount="indefinite" />
                          </circle>
                          <circle r="3.5" fill="var(--accent)" stroke="var(--paper)" strokeWidth="1.4" />
                          <animateMotion dur="7s" repeatCount="indefinite" rotate="auto">
                            <mpath href="#m-route-path" />
                          </animateMotion>
                        </g>
                      </svg>
                    </div>
                    <div className="m-tracking-eta">
                      <div>
                        <div className="m-tracking-eta-label">Technician en route</div>
                        <div className="m-tracking-tech">Tariq M. · Van 04</div>
                      </div>
                      <div className="m-tracking-eta-num">6<small>min</small></div>
                    </div>
                  </div>
                </div>
                <div className="m-card-body">
                  <span className="m-card-eyebrow">Live tracking</span>
                  <h3 className="m-card-title">Operative en route, with an ETA the occupier can see.</h3>
                  <p className="m-card-desc">
                    Real-time dispatch, operative identity and certifications, photo evidence
                    attached on arrival.
                  </p>
                </div>
              </article>

              {/* 2 — Workflow */}
              <article className="m-card">
                <div className="m-card-visual m-vis-tint">
                  <div className="m-audit">
                    <div className="m-audit-row"><span className="m-audit-time">08:14</span><span className="audit-state s-open">Logged</span><span className="m-audit-meta">P1 · Lyle St SE1</span></div>
                    <div className="m-audit-row"><span className="m-audit-time">08:17</span><span className="audit-state s-disp">Dispatched</span><span className="m-audit-meta">R03 · ETA 6 min</span></div>
                    <div className="m-audit-row"><span className="m-audit-time">08:24</span><span className="audit-state s-site">On site</span><span className="m-audit-meta">Photo · ID badge</span></div>
                    <div className="m-audit-row"><span className="m-audit-time">09:06</span><span className="audit-state s-ok">Resolved</span><span className="m-audit-meta">Report · 4 photos</span></div>
                  </div>
                </div>
                <div className="m-card-body">
                  <span className="m-card-eyebrow">Workflow</span>
                  <h3 className="m-card-title">Service requests with full audit trail.</h3>
                  <p className="m-card-desc">Triage, dispatch, on-site, resolved, complete visibility and evidence at every step.</p>
                </div>
              </article>

              {/* 3 — Automated quoting engine */}
              <article className="m-card">
                <div className="m-card-visual m-vis-tint">
                  <div className="m-quote">
                    <div className="m-quote-head">
                      <span className="m-quote-title">Quote · Boardroom light fitting</span>
                      <span className="m-quote-time">3 quotes</span>
                    </div>
                    <div className="m-quote-rows">
                      <div className="m-quote-row m-quote-row-best">
                        <span className="m-quote-name">Reeve</span>
                        <span className="m-quote-meta">L2 Electrician · 4h</span>
                        <span className="m-quote-price">£180</span>
                        <span className="m-quote-tag">Recommended</span>
                      </div>
                      <div className="m-quote-row">
                        <span className="m-quote-name">ThermaServ Ltd</span>
                        <span className="m-quote-meta">L1 Electrician · 1 day</span>
                        <span className="m-quote-price">£210</span>
                      </div>
                      <div className="m-quote-row">
                        <span className="m-quote-name">FrontGuard Maint</span>
                        <span className="m-quote-meta">Maintenance · 2 days</span>
                        <span className="m-quote-price">£245</span>
                      </div>
                      <div className="m-quote-row m-quote-row-pending">
                        <span className="m-quote-name">LightFix Co</span>
                        <span className="m-quote-meta">
                          Electrician ·
                          <span className="live-dot m-quote-dot" /> Pending…
                        </span>
                        <span className="m-quote-price">—</span>
                      </div>
                    </div>
                  </div>
                </div>
                <div className="m-card-body">
                  <span className="m-card-eyebrow">Quoting</span>
                  <h3 className="m-card-title">Automated quoting engine.</h3>
                  <p className="m-card-desc">Automatically get quotes from your suppliers and the Reeve network.</p>
                </div>
              </article>

              {/* 4 — Evidence (report mockup) */}
              <article className="m-card">
                <div className="m-card-visual m-vis-report">
                  <div className="m-report">
                    <div className="m-report-head">
                      <span className="m-report-title">Light out on 6th floor</span>
                      <span className="m-report-status">Closed</span>
                    </div>
                    <div className="m-report-body">
                      <div className="m-report-section">
                        <span className="m-report-label">Completed</span>
                        <div className="m-report-task">
                          <span className="m-report-check" aria-hidden="true">
                            <svg width="12" height="12" viewBox="0 0 14 14" fill="none">
                              <path d="M3 7.4 5.7 10 11 4.4" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
                            </svg>
                          </span>
                          <span>Investigate flickering light on 6th floor behind main tea point.</span>
                        </div>
                      </div>
                      <div className="m-report-bottom">
                        <img
                          src="assets/img/maintenance.jpg"
                          alt="Replaced light fitting"
                          className="m-report-photo"
                        />
                        <div className="m-report-section">
                          <span className="m-report-label">Visitor notes</span>
                          <p className="m-report-note">
                            G9 lamp had blown. New lamp purchased; fitting reinstalled.
                          </p>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
                <div className="m-card-body">
                  <span className="m-card-eyebrow">Evidence</span>
                  <h3 className="m-card-title">Photo &amp; report on every job.</h3>
                  <p className="m-card-desc">Technicians attach photos and findings on arrival and at close, with AI-generated customer-friendly visit reports.</p>
                </div>
              </article>

              {/* 5 — Performance intelligence (moved up) */}
              <article className="m-card">
                <div className="m-card-visual m-vis-summary">
                  <div className="m-summary">
                    <header className="m-summary-head">
                      <span className="m-summary-avatar">A</span>
                      <span className="m-summary-name">Ada</span>
                      <span className="m-summary-ai">
                        <svg width="9" height="9" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
                          <path d="M10 3.2c.1-.27.49-.27.6 0l1.3 3.45a3 3 0 0 0 1.75 1.75l3.45 1.3c.27.1.27.49 0 .6l-3.45 1.3a3 3 0 0 0-1.75 1.75l-1.3 3.45a.32.32 0 0 1-.6 0l-1.3-3.45a3 3 0 0 0-1.75-1.75l-3.45-1.3a.32.32 0 0 1 0-.6l3.45-1.3a3 3 0 0 0 1.75-1.75l1.3-3.45Z" />
                        </svg>
                        AI
                      </span>
                      <span className="m-summary-time">2h ago</span>
                    </header>
                    <div className="m-summary-title">Building summary</div>
                    <div className="m-summary-subtitle">Marlow &amp; Finch — week 24</div>
                    <div className="m-summary-rows">
                      <div className="m-summary-row">
                        <span>Avg. resolution</span>
                        <span className="m-summary-pill m-summary-pill-green">1.8d</span>
                      </div>
                      <div className="m-summary-row">
                        <span>SLA hit rate</span>
                        <span className="m-summary-pill m-summary-pill-amber">96%</span>
                      </div>
                      <div className="m-summary-row">
                        <span>Recurring faults</span>
                        <span className="m-summary-pill m-summary-pill-blue">−42%</span>
                      </div>
                    </div>
                    <div className="m-summary-footer">
                      Top operating site this week. Above portfolio average across all routes.
                    </div>
                  </div>
                </div>
                <div className="m-card-body">
                  <span className="m-card-eyebrow">Performance intelligence</span>
                  <h3 className="m-card-title">A live, per-building operating picture.</h3>
                  <p className="m-card-desc">Resolution time, recurring faults, supplier performance, occupier sentiment, by building, by client, by service line.</p>
                </div>
              </article>

              {/* 6 — Compliance */}
              <article className="m-card">
                <div className="m-card-visual m-vis-tint">
                  <div className="m-iso">
                    <div className="m-iso-head">
                      <div className="m-iso-label">
                        <span className="m-iso-badge">ISO 45001</span>
                        readiness
                      </div>
                      <div className="m-iso-pct">87%</div>
                    </div>
                    <div className="m-iso-bar" aria-hidden="true">
                      <div className="m-iso-fill" style={{ width: '87%' }} />
                    </div>
                    <div className="m-iso-list">
                      <div className="m-iso-row">
                        <span className="m-iso-check" aria-hidden="true">
                          <svg width="9" height="9" viewBox="0 0 14 14" fill="none">
                            <path d="M3 7.4 5.7 10 11 4.4" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" />
                          </svg>
                        </span>
                        <span className="m-iso-row-label">Asset register</span>
                        <span className="m-iso-row-meta">142 logged</span>
                      </div>
                      <div className="m-iso-row">
                        <span className="m-iso-check" aria-hidden="true">
                          <svg width="9" height="9" viewBox="0 0 14 14" fill="none">
                            <path d="M3 7.4 5.7 10 11 4.4" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" />
                          </svg>
                        </span>
                        <span className="m-iso-row-label">Risk assessments</span>
                        <span className="m-iso-row-meta">38 current</span>
                      </div>
                      <div className="m-iso-row">
                        <span className="m-iso-check" aria-hidden="true">
                          <svg width="9" height="9" viewBox="0 0 14 14" fill="none">
                            <path d="M3 7.4 5.7 10 11 4.4" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" />
                          </svg>
                        </span>
                        <span className="m-iso-row-label">Audit trail</span>
                        <span className="m-iso-row-meta">4,712 events</span>
                      </div>
                      <div className="m-iso-row m-iso-row-pending">
                        <span className="m-iso-check m-iso-check-pending" aria-hidden="true">
                          <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                            <circle cx="12" cy="12" r="9" />
                            <polyline points="12 7 12 12 15.5 13.5" />
                          </svg>
                        </span>
                        <span className="m-iso-row-label">Incident reports</span>
                        <span className="m-iso-row-meta">3 in review</span>
                      </div>
                    </div>
                  </div>
                </div>
                <div className="m-card-body">
                  <span className="m-card-eyebrow">Compliance</span>
                  <h3 className="m-card-title">Audit trails on every asset.</h3>
                  <p className="m-card-desc">Every work order carried out on your asset has a full audit trail so you can keep track of compliance. The fastest way to ISO 45001.</p>
                </div>
              </article>

              {/* 7 — Visitor management & access */}
              <article className="m-card">
                <div className="m-card-visual m-vis-guest">
                  <div className="m-guest">
                    <div className="m-guest-head">
                      <span className="m-guest-back" aria-hidden="true">
                        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                          <path d="M15 18l-6-6 6-6" />
                        </svg>
                      </span>
                      <span className="m-guest-title">Register a guest</span>
                    </div>
                    <div className="m-guest-body">
                      <div className="m-guest-field">
                        <label>Guest name</label>
                        <div className="m-guest-input">Maya Chen</div>
                      </div>
                      <div className="m-guest-field">
                        <label>Date</label>
                        <div className="m-guest-input m-guest-input-select">
                          <span>Thu 18 Jun · 9:30</span>
                          <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                            <path d="M6 9l6 6 6-6" />
                          </svg>
                        </div>
                      </div>
                      <div className="m-guest-coffee">
                        <span className="m-guest-coffee-icon" aria-hidden="true">
                          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
                            <path d="M4 8h12v6a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4V8z" />
                            <path d="M16 10h2a2 2 0 0 1 0 4h-2" />
                            <path d="M8 3v2M11 3v2M14 3v2" />
                          </svg>
                        </span>
                        <span className="m-guest-coffee-label">Coffee on arrival</span>
                        <span className="m-guest-toggle m-guest-toggle-on" aria-hidden="true">
                          <span className="m-guest-toggle-handle" />
                        </span>
                      </div>
                      <button type="button" className="m-guest-submit">
                        Continue <span className="arrow">→</span>
                      </button>
                    </div>
                  </div>
                </div>
                <div className="m-card-body">
                  <span className="m-card-eyebrow">Estate ops</span>
                  <h3 className="m-card-title">Visitor management &amp; access.</h3>
                  <p className="m-card-desc">Visitor pre-registration, door access provisioning and end-user management, allowing your occupiers direct access to the platform.</p>
                </div>
              </article>

              {/* 8 — Integrations */}
              <article className="m-card">
                <div className="m-card-visual m-vis-tint">
                  <div className="m-integrations">
                    <div className="m-int-row">
                      <span className="m-int-icon m-int-icon-whatsapp" aria-hidden="true">
                        <svg width="13" height="13" viewBox="0 0 32 32" fill="currentColor">
                          <path d="M16 0a16 16 0 0 0-13.7 24.2L0 32l8.1-2.1A16 16 0 1 0 16 0Zm9.4 22.4c-.4 1.1-2.3 2.1-3.2 2.2-.8.1-1.9.1-3-.2-.7-.2-1.6-.5-2.8-1-4.8-2.1-8-7-8.2-7.3-.2-.3-2-2.6-2-5 0-2.4 1.2-3.5 1.7-4 .4-.5.9-.6 1.2-.6h.9c.3 0 .7 0 1 .8.4.9 1.3 3 1.4 3.2.1.2.2.5 0 .8-.1.3-.2.4-.4.7-.2.2-.4.5-.6.7-.2.2-.4.4-.2.8.3.4 1.1 1.8 2.4 2.9 1.6 1.4 3 1.9 3.4 2.1.4.2.7.1.9-.1.3-.3.9-1.1 1.2-1.4.2-.3.5-.3.8-.2.3.1 2.1 1 2.5 1.2.4.2.6.3.7.5.1.2.1 1.1-.3 2.1Z" />
                        </svg>
                      </span>
                      <span className="m-int-label">WhatsApp</span>
                    </div>
                    <div className="m-int-row">
                      <span className="m-int-icon m-int-icon-email" aria-hidden="true">
                        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                          <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
                          <polyline points="22,6 12,13 2,6" />
                        </svg>
                      </span>
                      <span className="m-int-label">Email</span>
                    </div>
                    <div className="m-int-row">
                      <span className="m-int-icon m-int-icon-zendesk">
                        <img src="assets/img/zendesk.png" alt="" />
                      </span>
                      <span className="m-int-label">Zendesk</span>
                    </div>
                  </div>
                </div>
                <div className="m-card-body">
                  <span className="m-card-eyebrow">Integrations</span>
                  <h3 className="m-card-title">Living in where you already work.</h3>
                  <p className="m-card-desc">Raise requests and action work via WhatsApp, email or Zendesk. The experience is seamless for teams and occupiers.</p>
                </div>
              </article>
        </div>
      </div>
    </section>
  );
}

/* ── Desktop workstation mockup ─ lights-out flow with supplier step ─
   Phased animation, same arc as the phone GIF on the home page but on a
   desktop layout, with a new "supplier selected · cost" step inserted
   between AI triage and dispatch. ─ */
function DesktopWorkstation() {
  const [phase, setPhase] = React.useState(0);
  const [typed, setTyped] = React.useState('');
  const FULL_TEXT = 'Stairwell lights are out';

  // Sequential journey beneath the AI triage chips — matches the
  // hero job thread we used to show on the London map.
  const STEPS = [
    { key: 'bulb',  title: 'G9 LED bulb · matched', sub: 'from asset register',   ai: true },
    { key: 'parts', title: 'Parts ordered',         sub: 'auto-attached to job' },
    { key: 'quote', title: 'Quote approved',        sub: '£96 · auto-approved' },
    { key: 'roy',   title: 'Roy Kendall allocated',      sub: 'L2 Electrician',         ai: true },
  ];

  // Phases:
  //   0 → idle (waiting to start)
  //   1 → typing the WhatsApp message body
  //   2 → AI triage chips reveal
  //   3..6 → STEPS[0..3] reveal one-by-one
  //   7 → dispatch confirmation reveals
  //   8 → hold, then reset
  React.useEffect(() => {
    const timers = [];
    if (phase === 0) {
      timers.push(setTimeout(() => setPhase(1), 900));
    } else if (phase === 1) {
      let i = 0;
      const tick = () => {
        i += 1;
        setTyped(FULL_TEXT.slice(0, i));
        if (i < FULL_TEXT.length) {
          timers.push(setTimeout(tick, 38 + Math.random() * 28));
        } else {
          timers.push(setTimeout(() => setPhase(2), 750));
        }
      };
      tick();
    } else if (phase >= 2 && phase <= 6) {
      timers.push(setTimeout(() => setPhase(phase + 1), 1050));
    } else if (phase === 7) {
      timers.push(setTimeout(() => {
        setTyped('');
        setPhase(0);
      }, 5500));
    }
    return () => timers.forEach(clearTimeout);
  }, [phase]);

  const AiSparkle = ({ size = 9 }) => (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
      <path d="M10 3.2c.1-.27.49-.27.6 0l1.3 3.45a3 3 0 0 0 1.75 1.75l3.45 1.3c.27.1.27.49 0 .6l-3.45 1.3a3 3 0 0 0-1.75 1.75l-1.3 3.45a.32.32 0 0 1-.6 0l-1.3-3.45a3 3 0 0 0-1.75-1.75l-3.45-1.3a.32.32 0 0 1 0-.6l3.45-1.3a3 3 0 0 0 1.75-1.75l1.3-3.45Z" />
    </svg>
  );

  return (
    <div className="desk-mockup">
      {/* Browser chrome */}
      <div className="desk-chrome">
        <div className="desk-chrome-dots">
          <span /><span /><span />
        </div>
        <div className="desk-chrome-title" />
      </div>

      <div className="desk-body">
        {/* Ticket header bar */}
        <div className="desk-ticket-head">
          <div>
            <div className="desk-ticket-title">Stairwell light issue</div>
          </div>
          <span
            className={
              'desk-status-pill ' + (
                phase < 2 ? 'desk-status-new' :
                phase < 7 ? 'desk-status-triaged' :
                'desk-status-dispatched'
              )
            }
          >
            {phase < 2 ? 'New' : phase < 7 ? 'Triaged' : 'Dispatched'}
          </span>
        </div>

        {/* Incoming WhatsApp message from the building occupier */}
        <div className="desk-field">
          <div className="desk-msg">
            <div className="desk-msg-head">
              <span className="desk-msg-channel" aria-hidden="true">
                <svg width="11" height="11" viewBox="0 0 32 32" fill="currentColor">
                  <path d="M16 0a16 16 0 0 0-13.7 24.2L0 32l8.1-2.1A16 16 0 1 0 16 0Zm9.4 22.4c-.4 1.1-2.3 2.1-3.2 2.2-.8.1-1.9.1-3-.2-.7-.2-1.6-.5-2.8-1-4.8-2.1-8-7-8.2-7.3-.2-.3-2-2.6-2-5 0-2.4 1.2-3.5 1.7-4 .4-.5.9-.6 1.2-.6h.9c.3 0 .7 0 1 .8.4.9 1.3 3 1.4 3.2.1.2.2.5 0 .8-.1.3-.2.4-.4.7-.2.2-.4.5-.6.7-.2.2-.4.4-.2.8.3.4 1.1 1.8 2.4 2.9 1.6 1.4 3 1.9 3.4 2.1.4.2.7.1.9-.1.3-.3.9-1.1 1.2-1.4.2-.3.5-.3.8-.2.3.1 2.1 1 2.5 1.2.4.2.6.3.7.5.1.2.1 1.1-.3 2.1Z" />
                </svg>
                WhatsApp
              </span>
              <span className="desk-msg-from">4th floor occupier</span>
              <span className="desk-msg-time desk-mono">14:23</span>
            </div>
            <div className="desk-msg-body">
              <span className="desk-msg-avatar" aria-hidden="true">4F</span>
              <div className="desk-msg-bubble">
                <span className="desk-msg-text">
                  {phase >= 1 ? (phase === 1 ? typed : FULL_TEXT) : ''}
                  {(phase === 0 || phase === 1) && <span className="desk-cursor" />}
                </span>
              </div>
            </div>
          </div>
        </div>

        {/* AI triage chips */}
        <div className={'desk-field desk-phase' + (phase >= 2 ? ' desk-phase-on' : '')}>
          <div className="desk-triage">
              <div className="desk-triage-head">
                <span className="m-summary-ai">
                  <AiSparkle />
                  AI triage
                </span>
                <span className="desk-mono">~35 min est.</span>
              </div>
              <div className="desk-triage-chips">
                <span className="desk-chip"><span className="desk-chip-dot" /> Electrical</span>
                <span className="desk-chip"><span className="desk-chip-dot" /> P2 priority</span>
                <span className="desk-chip"><span className="desk-chip-dot" /> Stairwell · Floor 4</span>
              </div>
            </div>
        </div>

        {/* Sequential step timeline — bulb / parts / quote / Roy allocated */}
        <div className="desk-steps">
          {STEPS.map((step, i) => (
            <div
              key={step.key}
              className={
                'desk-step desk-phase' +
                (phase >= 3 + i ? ' desk-phase-on' : '') +
                (step.ai ? ' desk-step-ai' : '')
              }
            >
              {step.ai ? (
                <span className="desk-step-icon desk-step-icon-ai" aria-hidden="true">
                  <span className="desk-step-icon-dot" />
                </span>
              ) : (
                <span className="desk-step-icon desk-step-icon-check" aria-hidden="true">
                  <svg width="10" height="10" viewBox="0 0 14 14" fill="none">
                    <path
                      d="M3 7.4 5.7 10 11 4.4"
                      stroke="currentColor"
                      strokeWidth="2"
                      strokeLinecap="round"
                      strokeLinejoin="round"
                    />
                  </svg>
                </span>
              )}
              <div className="desk-step-text">
                <div className="desk-step-title">
                  <span>{step.title}</span>
                  {step.ai && (
                    <span className="desk-step-ai-chip" aria-label="AI">
                      <AiSparkle />
                      AI
                    </span>
                  )}
                </div>
                <div className="desk-step-sub">{step.sub}</div>
              </div>
            </div>
          ))}
        </div>

        {/* Dispatch confirmation — final phase */}
        <div className={'desk-dispatch desk-phase' + (phase >= 7 ? ' desk-phase-on' : '')}>
            <div className="desk-dispatch-av">
              <div className="desk-dispatch-av-pulse">
                <span className="live-dot" />
              </div>
            </div>
            <div className="desk-dispatch-info">
              <div className="desk-dispatch-title">Roy on the way · ETA 14:31</div>
              <div className="desk-dispatch-sub">Reeve · L2 Electrician · 8 min away</div>
            </div>
            <div className="desk-dispatch-num">14:31</div>
        </div>
      </div>
    </div>
  );
}

/* ── Comparison ─────────────────────────────────────────────────────── */
function Compare() {
  const Check = () => (
    <svg className="mark" width="14" height="14" viewBox="0 0 14 14">
      <path d="M3 7.2 5.8 10 11 4.2" stroke="var(--accent)" strokeWidth="1.6"
            fill="none" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
  const Dot = () => (
    <svg className="mark" width="14" height="14" viewBox="0 0 14 14">
      <circle cx="7" cy="7" r="1.5" fill="var(--ink-4)" />
    </svg>
  );
  return (
    <section className="section-pad" id="vs">
      <div className="wrap">
        <div className="section-split">
          <div>
            <h2 className="h-section">
              AI-powered coordination. <em>Real-world FM delivery</em>.
            </h2>
          </div>
          <p className="section-split-lede">
            Reeve uses AI to remove the manual coordination from FM — triage, routing, dispatch and reporting — then connects it to the people who do the work: your suppliers, Reeve operatives and vetted specialists. AI is the operating engine, not the whole pitch.
          </p>
        </div>

        <div className="compare-with-demo">
         <div className="compare-tiles">
          <div className="compare-col reeve">
            <h3 style={{ color: 'var(--bg)' }}>
              <img
                src="assets/img/brand/reeve-lockup-dark.svg"
                alt="Reeve"
                style={{ height: 22, width: 'auto', display: 'block' }}
              />
            </h3>
            <ul className="compare-list">
              <li><Check /><span>Owns the route to resolution</span></li>
              <li><Check /><span>Software <em>plus</em> managed FM delivery</span></li>
              <li><Check /><span>Supplier onboarding, SLAs &amp; performance</span></li>
              <li><Check /><span>Live building insights &amp; recurring-fault actions</span></li>
              <li><Check /><span>Reeve chases, escalates, evidences</span></li>
              <li><Check /><span>Resolution path reporting &amp; live portfolio intelligence</span></li>
            </ul>
          </div>
          <div className="compare-col legacy">
            <h3>Legacy CAFM &amp; logbooks</h3>
            <ul className="compare-list">
              <li><Dot /><span>Logs jobs</span></li>
              <li><Dot /><span>Software only</span></li>
              <li><Dot /><span>Supplier directory</span></li>
              <li><Dot /><span>Static monthly reports</span></li>
              <li><Dot /><span>Your team still chases</span></li>
              <li><Dot /><span>Tool-adoption risk</span></li>
            </ul>
          </div>
         </div>

         <div className="compare-demo">
          <DesktopWorkstation />
         </div>
        </div>
      </div>
    </section>
  );
}

/* ── Brand preservation ─────────────────────────────────────────────────── */

/* Brand colour swatches for the whitelabel preview panel. Same palette
   used previously inside the Modules carousel — restored here so the
   Brand section gets a live, interactive whitelabelling visual. */
const BRAND_COLORS = [
  { name: 'Navy',   value: 'oklch(28% 0.06 250)' },
  { name: 'Bronze', value: 'oklch(60% 0.13 50)'  },
  { name: 'Forest', value: 'oklch(48% 0.09 160)' },
  { name: 'Brick',  value: 'oklch(48% 0.10 30)'  },
];

/* Service-requests preview panel — mirrors a tenant-facing dashboard.
   Icon backgrounds + buttons retint to the active brand colour via the
   --sr-accent custom property. The "Delivered by …" label is bound to
   the active brand (Reeve before the upload animation completes,
   Marlow & Finch after) and shows a small brand chip in the active
   colour next to the name. */
function ServiceRequestsPanel({ color, brand, mark }) {
  // Each request has a current phase index (Logged → Resolved, 0..5)
  // plus a logical event log showing varied activity types: triage,
  // part ordering, approvals, scheduling, dispatch and completion.
  const REQUESTS = [
    {
      title: 'Fix the window blinds',
      description: 'The blinds in meeting room 4 are stuck halfway down.',
      phaseIdx: 1,
      activity: [
        { event: 'Part ordered · MK7 chain mechanism', by: 'Reeve · auto', ago: '1 day ago' },
        { event: 'Triaged · Mechanical · P3',          by: 'Reeve AI',    ago: '2 days ago' },
        { event: 'Request submitted',                  by: 'Adam Min',    ago: '3 days ago' },
      ],
    },
    {
      title: 'Shower is broken',
      description: 'Hot water not coming through in the 3rd floor showers.',
      phaseIdx: 2,
      activity: [
        { event: 'Visit scheduled · Fri 10:00',  by: 'Reeve · auto', ago: '4 hours ago' },
        { event: 'Plumber assigned · Roy Kendall',    by: 'Reeve · auto', ago: '6 hours ago' },
        { event: 'Triaged · Plumbing · P2',      by: 'Reeve AI',     ago: '8 hours ago' },
        { event: 'Request submitted',            by: 'Lara P.',      ago: '9 hours ago' },
      ],
    },
    {
      title: 'Install a projector stand',
      description: 'New AV cabinet delivered — needs mounting in the boardroom.',
      phaseIdx: 3,
      activity: [
        { event: 'Engineer dispatched · ETA 35 min',  by: 'Reeve · auto',  ago: '12 min ago' },
        { event: 'Quote approved · £180',             by: 'Adam Min',      ago: '2 hours ago' },
        { event: 'Quote received from LightFix Co',   by: 'Reeve · auto',  ago: '4 hours ago' },
        { event: 'Triaged · Multi-trade',             by: 'Reeve AI',      ago: '5 hours ago' },
        { event: 'Request submitted',                 by: 'Adam Min',      ago: '6 hours ago' },
      ],
    },
    {
      title: 'Quarterly electrical safety check',
      description: 'EICR re-test due across floors 4 – 6.',
      phaseIdx: 5,
      activity: [
        { event: 'Report uploaded · 0 findings', by: 'Reeve · auto', ago: 'Yesterday' },
        { event: 'Completed on site',            by: 'Tariq M.',     ago: 'Yesterday' },
        { event: 'Engineer on site',             by: 'Tariq M.',     ago: '2 days ago' },
        { event: 'Visit scheduled',              by: 'Reeve · auto', ago: '5 days ago' },
        { event: 'PPM created · Statutory',      by: 'Reeve · auto', ago: '14 days ago' },
      ],
    },
  ];

  const PHASES = ['Logged', 'Planning', 'Scheduled', 'On the way', 'On site', 'Resolved'];
  const PHASE_HEAD = {
    0: { title: 'Reviewing your request', desc: "We've logged your request and are working out what's needed." },
    1: { title: 'Planning the work',      desc: 'Lining up parts and the right specialist.' },
    2: { title: 'Scheduled',              desc: 'A visit is in the diary.' },
    3: { title: 'Engineer on the way',    desc: 'Travel updates and ETA below.' },
    4: { title: 'On site',                desc: 'The work is in progress.' },
    5: { title: 'Resolved',               desc: 'Job complete. Report uploaded.' },
  };

  const [openIdx, setOpenIdx] = React.useState(null);
  const openReq = openIdx != null ? REQUESTS[openIdx] : null;

  // ESC to close
  React.useEffect(() => {
    if (openIdx == null) return;
    const onKey = (e) => { if (e.key === 'Escape') setOpenIdx(null); };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [openIdx]);

  // Reusable alert-circle icon (circle + ! mark)
  const AlertIcon = ({ size = 16 }) => (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <circle cx="12" cy="12" r="9" />
      <line x1="12" y1="7.5" x2="12" y2="13" />
      <circle cx="12" cy="16.5" r="0.6" fill="currentColor" stroke="none" />
    </svg>
  );

  return (
    <div className="sr-panel" style={{ '--sr-accent': color }}>
      <div className="sr-header">
        <span className="sr-icon sr-icon-lg">
          <AlertIcon size={18} />
        </span>
        <div className="sr-header-text">
          <span className="sr-header-title">Recent service requests</span>
          <span className="sr-header-sub">Latest across your network</span>
        </div>
      </div>
      <div className="sr-rows">
        {REQUESTS.map((r, i) => (
          <div className="sr-row" key={r.title}>
            <span className="sr-icon">
              <AlertIcon size={16} />
            </span>
            <div className="sr-text">
              <span className="sr-title">{r.title}</span>
              <span className="sr-sub">
                <span className="sr-brand-mark">{mark}</span>
                <span>Delivered by {brand}</span>
              </span>
            </div>
            <button
              type="button"
              className="sr-btn"
              onClick={() => {
                // Modal is desktop-only — skip on narrow viewports.
                if (typeof window !== 'undefined' &&
                    window.matchMedia('(max-width: 600px)').matches) {
                  return;
                }
                setOpenIdx(i);
              }}
            >
              Open
            </button>
          </div>
        ))}
      </div>

      {openReq && (
        <div
          className="sr-modal"
          role="dialog"
          aria-modal="true"
          aria-label={openReq.title}
          onClick={() => setOpenIdx(null)}
        >
          <div className="sr-modal-card" onClick={(e) => e.stopPropagation()}>
            <button
              type="button"
              className="sr-modal-close"
              onClick={() => setOpenIdx(null)}
              aria-label="Close"
            >
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                <line x1="18" y1="6" x2="6" y2="18" />
                <line x1="6" y1="6" x2="18" y2="18" />
              </svg>
            </button>

            {/* Status banner */}
            <div className="sr-modal-status">
              <span className="sr-modal-status-icon" aria-hidden="true">
                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <circle cx="11" cy="11" r="8" />
                  <line x1="21" y1="21" x2="16.65" y2="16.65" />
                </svg>
              </span>
              <div className="sr-modal-status-text">
                <div className="sr-modal-status-title">{PHASE_HEAD[openReq.phaseIdx].title}</div>
                <div className="sr-modal-status-desc">{PHASE_HEAD[openReq.phaseIdx].desc}</div>
              </div>
            </div>

            {/* Phase track */}
            <div className="sr-modal-track">
              {PHASES.map((p, idx) => {
                const state =
                  idx < openReq.phaseIdx ? 'done' :
                  idx === openReq.phaseIdx ? 'active' :
                  'future';
                return (
                  <div key={p} className={'sr-modal-step sr-modal-step-' + state}>
                    <span className="sr-modal-step-dot" />
                    <span className="sr-modal-step-label">{p}</span>
                  </div>
                );
              })}
            </div>

            {/* Description */}
            <div className="sr-modal-section">
              <div className="sr-modal-section-head">
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                  <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
                  <polyline points="14 2 14 8 20 8" />
                </svg>
                <span>Description</span>
              </div>
              <p className="sr-modal-section-body">{openReq.description}</p>
            </div>

            {/* Activity */}
            <div className="sr-modal-section">
              <div className="sr-modal-section-head">
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                  <path d="M22 12h-4l-3 9L9 3l-3 9H2" />
                </svg>
                <span>Activity</span>
              </div>
              <ul className="sr-modal-activity">
                {openReq.activity.map((a, j) => (
                  <li key={j} className="sr-modal-activity-item">
                    <span className="sr-modal-activity-dot" />
                    <div>
                      <div className="sr-modal-activity-event">{a.event}</div>
                      <div className="sr-modal-activity-meta">by {a.by} · {a.ago}</div>
                    </div>
                  </li>
                ))}
              </ul>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

/* Brand showcase ─ orchestrated whitelabel demo. Plays out on mount:
   (1) type the customer's name slowly, (2) upload their logo with a
   progress bar, (3) switch the active brand colour. After the demo
   finishes the user can still click swatches to retint everything.
   Reeve is the implicit "before" state (activeIdx === -1 + uploaded
   === false) so the service-requests panel reads "Delivered by Reeve"
   until M&F's logo lands. */
function BrandShowcase() {
  const TARGET_NAME = 'Marlow & Finch';
  const [brandName, setBrandName] = React.useState('');
  const [activeIdx, setActiveIdx] = React.useState(-1);   // -1 = Reeve default
  const [uploaded, setUploaded] = React.useState(false);
  const [uploading, setUploading] = React.useState(false);
  // userOverride: true once the visitor has typed their own brand name.
  // Kills the canned auto-animation so they own the demo from there on.
  const [userOverride, setUserOverride] = React.useState(false);
  // Tone slider (0–100). 0=Formal · 50=Warm · 100=Friendly.
  const [tone, setTone] = React.useState(60);
  const toneLabel = tone < 34 ? 'Formal' : tone > 66 ? 'Friendly' : 'Warm';
  // customLogo: { dataUrl, name, size } — populated when the visitor
  // uploads a real image via the Replace button. Replaces the M&F text
  // thumb on the panel and the initials chip in service requests.
  const [customLogo, setCustomLogo] = React.useState(null);
  const fileInputRef = React.useRef(null);
  // Animation kicks off only once the section scrolls into view —
  // showcase ref + has-started flag drive that gating.
  const showcaseRef = React.useRef(null);
  const [hasStarted, setHasStarted] = React.useState(false);

  // Helpers — derive a chip's initials and a sensible "uploaded"
  // filename from whatever brand name we're displaying.
  const computeInitials = (name) =>
    name.split(/\s+/).filter(Boolean).map((w) => w[0].toUpperCase()).join('');
  const slugify = (name) =>
    name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');

  // Watch the showcase with IntersectionObserver. As soon as ~15% is
  // on screen, flip hasStarted (and disconnect — we only need to fire
  // once). Fallback: if IO is unavailable for any reason, start now.
  React.useEffect(() => {
    if (hasStarted) return;
    const el = showcaseRef.current;
    if (!el) return;
    if (typeof IntersectionObserver === 'undefined') {
      setHasStarted(true);
      return;
    }
    const io = new IntersectionObserver(
      (entries) => {
        for (const entry of entries) {
          if (entry.isIntersecting) {
            setHasStarted(true);
            io.disconnect();
            break;
          }
        }
      },
      { threshold: 0.15 }
    );
    io.observe(el);
    return () => io.disconnect();
  }, [hasStarted]);

  // Pre-upload: use Reeve's green. Post-upload: whichever swatch is active.
  const activeColor = activeIdx === -1
    ? 'var(--accent)'
    : BRAND_COLORS[activeIdx].value;
  const displayBrand    = uploaded ? brandName            : 'Reeve';
  const displayInitials = uploaded ? computeInitials(brandName) : 'R';
  // "Show file" — only once the upload has started or finished do we
  // expose filename / size in the meta area. Before then the panel is
  // in an explicit empty state (no logo uploaded yet).
  const showFile        = uploading || uploaded;
  const displayLogoFile = customLogo
    ? customLogo.name
    : uploaded
      ? slugify(brandName) + '.svg'
      : (uploading ? 'marlow-finch.svg' : '');
  const displayLogoSize = customLogo
    ? customLogo.size
    : (showFile ? '240 × 60' : '');
  // Inline mark in the service-requests subtitle:
  // - Visitor uploaded a real logo → small image thumb
  // - Brand identity established   → initials chip in active brand colour
  // - Reeve initial state          → real wordmark from brand.jsx
  const inlineMark = customLogo
    ? (
      <span className="sr-brand-mark-img" aria-hidden="true">
        <img src={customLogo.dataUrl} alt="" />
      </span>
    )
    : uploaded
      ? <span className="sr-brand-chip" aria-hidden="true">{displayInitials}</span>
      : (
        <img
          src="assets/img/brand/reeve-icon.svg"
          alt=""
          className="sr-brand-mark-icon"
          aria-hidden="true"
        />
      );

  // User typed something — kill the auto-animation, claim ownership of
  // the brand identity, default to a starting swatch if we're still
  // sitting on the Reeve green.
  const handleNameChange = (e) => {
    const v = e.target.value;
    setBrandName(v);
    if (!userOverride) setUserOverride(true);
    setUploading(false);
    if (v.length > 0) {
      setUploaded(true);
      if (activeIdx === -1) setActiveIdx(0);
    } else {
      setUploaded(false);
      setActiveIdx(-1);
      setCustomLogo(null);
    }
  };

  // Visitor picked a logo file via the hidden file input. Read as data
  // URL + measure the natural dimensions for the meta line.
  const handleLogoUpload = (e) => {
    const file = e.target.files && e.target.files[0];
    if (!file) return;
    if (!file.type.startsWith('image/')) return;
    const reader = new FileReader();
    reader.onload = (ev) => {
      const dataUrl = ev.target && ev.target.result;
      if (typeof dataUrl !== 'string') return;
      const probe = new Image();
      probe.onload = () => {
        const w = probe.naturalWidth;
        const h = probe.naturalHeight;
        const size = (w && h) ? `${w} × ${h}` : '—';
        setCustomLogo({ dataUrl, name: file.name, size });
        if (!userOverride) setUserOverride(true);
        setUploading(false);
        setUploaded(true);
        if (activeIdx === -1) setActiveIdx(0);
      };
      probe.onerror = () => {
        setCustomLogo({ dataUrl, name: file.name, size: '—' });
        if (!userOverride) setUserOverride(true);
        setUploading(false);
        setUploaded(true);
        if (activeIdx === -1) setActiveIdx(0);
      };
      probe.src = dataUrl;
    };
    reader.readAsDataURL(file);
    // Allow re-selecting the same file later
    e.target.value = '';
  };

  const triggerLogoUpload = () => {
    fileInputRef.current && fileInputRef.current.click();
  };

  React.useEffect(() => {
    // Wait until the section is in view before kicking off.
    if (!hasStarted) return;
    // Visitor has taken control of the input — stand down.
    if (userOverride) return;

    // Honour motion-off — jump to the final state immediately.
    if (typeof document !== 'undefined' &&
        document.documentElement.dataset.motion === 'off') {
      setBrandName(TARGET_NAME);
      setUploaded(true);
      setActiveIdx(0);
      return;
    }

    const timers = [];

    // Phase 3: upload completes — flip to M&F brand identity in Navy
    // and hold there. (The user can still click any swatch to retint.)
    const finishUpload = () => {
      setUploaded(true);
      setUploading(false);
      setActiveIdx(0);
    };

    // Phase 2: upload starts after typing.
    const startUpload = () => {
      setUploading(true);
      timers.push(setTimeout(finishUpload, 1500));
    };

    // Phase 1: type the name char-by-char (~250ms per char on average).
    const startTyping = () => {
      let i = 0;
      const typeChar = () => {
        i += 1;
        setBrandName(TARGET_NAME.slice(0, i));
        if (i < TARGET_NAME.length) {
          timers.push(setTimeout(typeChar, 200 + Math.random() * 110));
        } else {
          timers.push(setTimeout(startUpload, 600));
        }
      };
      typeChar();
    };

    timers.push(setTimeout(startTyping, 900));
    return () => timers.forEach(clearTimeout);
  }, [hasStarted, userOverride]);

  return (
    <div className="brand-showcase" ref={showcaseRef}>
      <div className="m-brand-panel brand-preview-panel">
        {/* Name field — auto-types Marlow & Finch on first view, but the
            input is fully editable so the visitor can type their own
            brand name (which kills the canned animation). */}
        <div className="m-brand-row">
          <span className="m-brand-row-label">Name</span>
          <input
            type="text"
            className="brand-name-input"
            value={brandName}
            onChange={handleNameChange}
            placeholder="Your brand name"
            aria-label="Brand name"
            spellCheck="false"
            autoComplete="off"
          />
        </div>

        {/* Logo row — empty placeholder → upload progress → M&F filled.
            Custom image upload via the hidden file input wired up below
            the Replace pill replaces the text thumb with a real image. */}
        <div className="m-brand-row">
          <span className="m-brand-row-label">Logo</span>
          <div className={'m-brand-logo' + (uploading ? ' m-brand-logo-uploading' : '')}>
            {customLogo ? (
              <div className="m-brand-logo-thumb m-brand-logo-thumb-img">
                <img src={customLogo.dataUrl} alt="Uploaded brand logo" />
              </div>
            ) : uploaded ? (
              <div
                className="m-brand-logo-thumb"
                style={{ background: activeColor, transition: 'background 280ms ease' }}
              >
                M&amp;F
              </div>
            ) : (
              <div className="m-brand-logo-thumb m-brand-logo-thumb-empty" aria-hidden="true">
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
                  <polyline points="17 8 12 3 7 8" />
                  <line x1="12" y1="3" x2="12" y2="15" />
                </svg>
              </div>
            )}
            <div className="m-brand-logo-meta">
              {(showFile || customLogo) ? (
                <>
                  <span className="m-brand-logo-name">{displayLogoFile}</span>
                  <span className="m-brand-logo-size">{displayLogoSize}</span>
                </>
              ) : (
                <span className="m-brand-logo-empty">No logo uploaded</span>
              )}
            </div>
            <button
              type="button"
              className="m-brand-replace"
              onClick={triggerLogoUpload}
            >
              {uploading ? 'Uploading' : (uploaded || customLogo) ? 'Replace' : 'Upload'}
            </button>
            <input
              ref={fileInputRef}
              type="file"
              accept="image/*"
              onChange={handleLogoUpload}
              style={{ display: 'none' }}
              aria-label="Upload brand logo"
            />
            {uploading && (
              <div className="m-brand-upload-bar" aria-hidden="true">
                <div className="m-brand-upload-fill" />
              </div>
            )}
          </div>
        </div>

        {/* Brand colour swatches */}
        <div className="m-brand-row">
          <span className="m-brand-row-label">Brand</span>
          <div className="m-brand-swatches">
            {BRAND_COLORS.map((c, i) => (
              <button
                key={c.name}
                type="button"
                className={'m-swatch' + (i === activeIdx ? ' m-swatch-active' : '')}
                style={{ background: c.value }}
                aria-label={c.name}
                aria-pressed={i === activeIdx}
                onClick={() => setActiveIdx(i)}
              />
            ))}
            <button type="button" className="m-swatch m-swatch-add" aria-label="Add colour">+</button>
          </div>
        </div>

        {/* Tone slider */}
        <div className="m-brand-row">
          <span className="m-brand-row-label">Tone</span>
          <div className="m-tone">
            <div className="m-tone-track">
              <div
                className="m-tone-fill"
                style={{ width: `${tone}%`, background: activeColor }}
              />
              <div
                className="m-tone-thumb"
                style={{ left: `${tone}%`, borderColor: activeColor }}
              />
              <input
                type="range"
                min="0"
                max="100"
                step="1"
                value={tone}
                onChange={(e) => setTone(Number(e.target.value))}
                className="m-tone-input"
                aria-label="Tone"
                aria-valuetext={toneLabel}
              />
            </div>
            <div className="m-tone-labels">
              <span>Formal</span>
              <span
                className="m-tone-current"
                style={{ color: activeColor, transition: 'color 280ms ease' }}
              >
                {toneLabel}
              </span>
              <span>Friendly</span>
            </div>
          </div>
        </div>
      </div>

      <ServiceRequestsPanel
        color={activeColor}
        brand={displayBrand}
        mark={inlineMark}
      />

      {/* Right rail — upper box crossfades between Reeve / M&F vans,
         lower box crossfades between Reeve-green / M&F-navy operative
         overalls. Both swap on the same `uploaded` signal so they
         flip in lockstep with the rest of the showcase. */}
      <div className="brand-extras">
        <div className="brand-extra-box brand-extra-box-van">
          <img
            className="brand-van-photo"
            src="assets/img/van-reeve.jpg"
            alt=""
            aria-hidden="true"
            data-active={!uploaded}
          />
          <img
            className="brand-van-photo"
            src="assets/img/van-mf.png"
            alt=""
            aria-hidden="true"
            data-active={uploaded}
          />
        </div>
        <div className="brand-extra-box brand-extra-box-van brand-extra-box-overall">
          <img
            className="brand-van-photo"
            src="assets/img/overall-reeve.jpg"
            alt=""
            aria-hidden="true"
            data-active={!uploaded}
          />
          <img
            className="brand-van-photo"
            src="assets/img/overall-mf.png"
            alt=""
            aria-hidden="true"
            data-active={uploaded}
          />
        </div>
      </div>
    </div>
  );
}

function Brand() {
  return (
    <section className="section-pad" id="brand">
      <div className="wrap">
        <div className="section-split">
          <div>
            <h2 className="h-section">
              Reeve keeps work moving, to support your brand.
            </h2>
          </div>
          <p className="section-split-lede">
            Reeve supports your property team as its FM partner, with the ability to white-label to your brand. Your team keeps the client relationship, priorities and oversight. Reeve coordinates the AI platform, helpdesk, suppliers, operatives, evidence and reporting that keep facilities moving.
          </p>
        </div>
        <BrandShowcase />
      </div>
    </section>
  );
}

/* ── Proof / Rollout ─────────────────────────────────────────────────── */
function Proof() {
  return (
    <section className="section-pad" id="proof" style={{ borderTop: '1px solid var(--rule)', borderBottom: '1px solid var(--rule)' }}>
      <div className="wrap">
        <div className="section-split">
          <div>
            <h2 className="h-section">The Resolution Gap.</h2>
          </div>
          <p className="section-split-lede">
            Facilities Managers without Reeve show 9 days average resolution time, with Reeve they show 1.8 days. That 7.2-day gap is where property teams lose time to chasing: suppliers, occupier updates, approvals, evidence and unresolved work.
          </p>
        </div>

        <div className="rollout-steps rollout-steps-horizontal">
          <div className="step">
            <span className="step-n">9.0d</span>
            <div>
              <div className="step-t">Industry Average</div>
              <div className="step-d">The slower baseline from Reeve platform data.</div>
            </div>
          </div>
          <div className="step">
            <span className="step-n">1.8d</span>
            <div>
              <div className="step-t">Reeve average</div>
              <div className="step-d">A faster operating route when Reeve owns the resolution path.</div>
            </div>
          </div>
          <div className="step">
            <span className="step-n">7.2d</span>
            <div>
              <div className="step-t">The chase</div>
              <div className="step-d">Supplier follow-up, occupier updates, approvals, evidence gaps and unresolved work.</div>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ── Closing CTA ─────────────────────────────────────────────────────── */
function ClosingCTA() {
  return (
    <section id="walk">
      <div className="close-cta">
        <svg className="close-cta-bg" viewBox="0 0 1200 400" preserveAspectRatio="none" aria-hidden="true">
          <g stroke="rgba(255,255,255,0.05)" fill="none" strokeWidth="1">
            <path d="M0 200 Q 300 80 600 200 T 1200 200" />
            <path d="M0 240 Q 300 120 600 240 T 1200 240" />
            <path d="M0 280 Q 300 160 600 280 T 1200 280" />
          </g>
        </svg>
        <div className="close-cta-inner">
          <div className="close-cta-stack">
            <h2 className="h-section">
              Your portfolio, under control.
            </h2>
            <p className="close-cta-body">
              Book a walkthrough and we'll map how Reeve could keep facilities moving
              across your portfolio, from helpdesk and AI triage to operative dispatch,
              supplier coordination and resolution path reporting.
            </p>
            <div className="close-cta-btns">
              <a
                href={CALENDAR_URL}
                target="_blank"
                rel="noopener noreferrer"
                className="btn btn-on-dark btn-primary"
                style={{ width: 'fit-content' }}
              >
                Book a demo <span className="arrow">→</span>
              </a>
              <a
                href={JOB_FORM_URL}
                target="_blank"
                rel="noopener noreferrer"
                className="btn btn-on-dark btn-primary"
                style={{ width: 'fit-content' }}
                onClick={openJobWidget}
              >
                Book a job <span className="arrow">→</span>
              </a>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ── Book a Job overlay ── fixed bottom-right iframe of the /embed chat.
   Hidden by default; opened by any "Book a Job" button via the
   'reeve:open-job' event. While open, body.job-open hides the WhatsApp
   bubble so the two floating widgets never overlap. ── */
function JobWidget() {
  const [open, setOpen] = React.useState(false);
  React.useEffect(() => {
    const onOpen = () => setOpen(true);
    window.addEventListener('reeve:open-job', onOpen);
    return () => window.removeEventListener('reeve:open-job', onOpen);
  }, []);
  React.useEffect(() => {
    document.body.classList.toggle('job-open', open);
    if (!open) return;
    const onKey = (e) => { if (e.key === 'Escape') setOpen(false); };
    window.addEventListener('keydown', onKey);
    return () => {
      window.removeEventListener('keydown', onKey);
      document.body.classList.remove('job-open');
    };
  }, [open]);
  if (!open) return null;
  return (
    <div className="job-widget" role="dialog" aria-label="Book a Job" aria-modal="false">
      <button
        type="button"
        className="job-widget-close"
        aria-label="Close Book a Job"
        onClick={() => setOpen(false)}
      >
        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg>
      </button>
      <iframe
        className="job-widget-frame"
        src={JOB_WIDGET_SRC}
        title="Book a Job"
        allow="clipboard-write"
      />
    </div>
  );
}

/* ── Footer ─────────────────────────────────────────────────────── */
/* ── Floating WhatsApp bubble ─ fixed bottom-right on every screen ── */
function WhatsAppBubble() {
  return (
    <a
      className="wa-bubble"
      href={WHATSAPP_URL}
      target="_blank"
      rel="noopener noreferrer"
      aria-label="Message Reeve on WhatsApp"
      title="Message us on WhatsApp"
    >
      <svg viewBox="0 0 24 24" width="30" height="30" fill="currentColor" aria-hidden="true">
        <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.149-1.255-.462-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.263.489 1.694.625.712.227 1.36.195 1.872.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/>
      </svg>
    </a>
  );
}

function Footer() {
  return (
    <footer className="foot">
      <div className="wrap">
        <div className="accreditations">
          <span className="accreditations-label">Accredited &amp; certified</span>
          <div className="accreditation-row">
            <img src="assets/img/iso27001.png" alt="ISO 27001 certified" />
            <svg
              width="36"
              height="36"
              viewBox="0 0 100 100"
              xmlns="http://www.w3.org/2000/svg"
              className="accreditation-bcorp"
              aria-label="Certified B Corporation"
            >
              <text x="50" y="14" textAnchor="middle" fontFamily="Inter Tight, ui-sans-serif, system-ui" fontSize="9" fontWeight="600" letterSpacing="0.5" fill="currentColor">
                CERTIFIED
              </text>
              <circle cx="50" cy="52" r="20" fill="none" stroke="currentColor" strokeWidth="2.6" />
              <text x="50" y="60" textAnchor="middle" fontFamily="Inter Tight, ui-sans-serif, system-ui" fontSize="26" fontWeight="700" fill="currentColor">
                B
              </text>
              <line x1="32" y1="80" x2="68" y2="80" stroke="currentColor" strokeWidth="1.4" />
              <text x="50" y="92" textAnchor="middle" fontFamily="Inter Tight, ui-sans-serif, system-ui" fontSize="9" fontWeight="600" letterSpacing="0.3" fill="currentColor">
                CORPORATION
              </text>
            </svg>
            <img src="assets/img/accreditations/iwfm.png" alt="IWFM member" />
          </div>
        </div>
        <div className="foot-grid">
          <div className="foot-col">
            <img
              src="assets/img/brand/reeve-lockup-dark.svg"
              alt="Reeve"
              style={{ height: 22, width: 'auto', display: 'block' }}
            />
            <p style={{ color: 'var(--ink-3)', fontSize: 13.5, marginTop: 12, maxWidth: 280 }}>
              Reeve is the FM partner for property teams.
            </p>
          </div>
          <div className="foot-col">
            <h4>Directory</h4>
            <ul>
              <li><a href="#product">Product</a></li>
              <li><a href="#brand">For property teams</a></li>
              <li><a href="#model">Model</a></li>
              <li><a href="#services">Services</a></li>
              <li><a href="#vs">Why Reeve</a></li>
            </ul>
          </div>
          <div className="foot-col">
            <h4>Talk</h4>
            <ul>
              <li><a href={CALENDAR_URL} target="_blank" rel="noopener noreferrer">Book a demo</a></li>
              <li><a href={JOB_FORM_URL} target="_blank" rel="noopener noreferrer" onClick={openJobWidget}>Book a job</a></li>
              <li>44-46 Sekforde Street, EC1R 0HA</li>
            </ul>
          </div>
        </div>
        <div className="foot-base">
          <span>© Reeve FM Ltd. 2026 · Registered in England &amp; Wales</span>
        </div>
      </div>
    </footer>
  );
}

Object.assign(window, {
  Nav, Hero, PlatformShowcase, DispatchLog, Certifications, ThreeWaysToStart, Trust, Problem, Routes, Services, Modules, Compare, Brand, Proof, ClosingCTA, Footer, JobWidget
});
