// Prickly — production microsite, single page
// Nav · Hero · Quiz · How it works · Kit explorer · Timeline · What we screen ·
// Eligibility · Clinical & closed-loop · Pricing · About · Experience · FAQ · Newsletter · Footer
//
// Reads global components attached by prickly-interactive.jsx.

// Citation popover — superscript shows a pop-up on hover.
// Uses position:fixed so it overlays correctly in all browsers (Safari inline-block quirk).
function CitationRef({ n, children }) {
  const [open, setOpen] = React.useState(false);
  const [popStyle, setPopStyle] = React.useState({});
  const btnRef = React.useRef(null);
  const hideTimer = React.useRef(null);

  function computeStyle() {
    if (btnRef.current) {
      const r = btnRef.current.getBoundingClientRect();
      const width = 300;
      const left = Math.min(r.right - width, window.innerWidth - width - 8);
      setPopStyle({ position: 'fixed', bottom: window.innerHeight - r.top + 8, left: Math.max(8, left), width });
    }
  }

  function show() {
    clearTimeout(hideTimer.current);
    computeStyle();
    setOpen(true);
  }

  function hide() {
    hideTimer.current = setTimeout(() => setOpen(false), 120);
  }

  return (
    <span className="pr-cite-wrap">
      <button
        ref={btnRef}
        className="pr-cite-sup"
        aria-label={`Reference ${n}`}
        onMouseEnter={show}
        onMouseLeave={hide}
        onFocus={show}
        onBlur={hide}
      >
        {n}
      </button>
      {open && ReactDOM.createPortal(
        <span
          className="pr-cite-pop"
          role="tooltip"
          style={popStyle}
          onMouseEnter={() => clearTimeout(hideTimer.current)}
          onMouseLeave={hide}
        >
          {children}
        </span>,
        document.body
      )}
    </span>
  );
}

// Smooth scroll to top — rAF cubic ease-out: starts immediately at speed, decelerates into top.
function scrollToTopSmooth(duration = 720) {
  const start = window.scrollY;
  if (start === 0) return;
  const startTime = performance.now();
  function step(now) {
    const elapsed = now - startTime;
    const progress = Math.min(elapsed / duration, 1);
    const ease = 1 - Math.pow(1 - progress, 3); // cubic ease-out
    window.scrollTo(0, start * (1 - ease));
    if (progress < 1) requestAnimationFrame(step);
  }
  requestAnimationFrame(step);
}

const ACCENT_OPTIONS = {
  sage:  { hex: '#7DAA8B', label: 'Sage' },
  clay:  { hex: '#D26941', label: 'Clay' },
  blue:  { hex: '#7A9DB5', label: 'Blue' },
  ochre: { hex: '#D99B3A', label: 'Ochre' },
  plum:  { hex: '#9C5C68', label: 'Plum' },
};

const HERO_COPY = {
  a: {
    kicker: "Born in Naarm · Available Australia-wide",
    pre: "The STI screen you've been ",
    rotate: ["getting to.", "seeking out.", "putting off.", "waiting on.", "wary of.", "asking for."],
    sub: "Requested online. Delivered to your door. Reviewed by Australian clinicians. No waiting room, no small talk."
  },
  b: {
    kicker: "Now screening Australia-wide",
    pre: "Awkward in your head. ",
    rotate: ["Easy in your kitchen.", "Quiet in the post.", "Sorted in a week."],
    sub: "An at-home STI screen with grown-ups on the other end. Three minutes to check eligibility, package at your door, results properly handled."
  },
  c: {
    kicker: "Clinician-led · at-home · Australia",
    pre: "Sexual health, minus the ",
    rotate: ["eye contact.", "waiting room.", "small talk.", "guesswork."],
    sub: "A clinician-led at-home STI screen. Self-collected samples, returned to a NATA-accredited Australian lab, with a Telehealth follow-up if anything needs it."
  },
  d: {
    kicker: "STI screening · Australia",
    pre: "All the screen. None of the ",
    rotate: ["waiting room.", "phone tag.", "rebooking.", "shame circus."],
    sub: "Three-minute eligibility check, specimen collection equipment posted in plain packaging, results read by Australian clinicians. Pick a time you're free."
  }
};

// ── Atoms ─────────────────────────────────────────────────────────────────
function Container({ children, narrow }) {
  return <div className={'pr-container' + (narrow ? ' narrow' : '')}>{children}</div>;
}
function Kicker({ children }) {return <span className="pr-kicker">{children}</span>;}

function AccentTri({ accent, onAccentChange, className = '' }) {
  const [spinning, setSpinning] = React.useState(false);
  const keys = Object.keys(ACCENT_OPTIONS);
  const nextKey = keys[(keys.indexOf(accent) + 1) % keys.length];
  return (
    <button
      className={'pr-nav-accent-tri' + (spinning ? ' spin' : '') + (className ? ' ' + className : '')}
      onClick={() => { onAccentChange(nextKey); setSpinning(true); }}
      onAnimationEnd={() => setSpinning(false)}
aria-label="Change accent colour"
      type="button"
    ></button>
  );
}

// ── Nav ───────────────────────────────────────────────────────────────────
function Nav({ accent, onAccentChange }) {
  const [scrolled, setScrolled] = React.useState(false);
  const [progress, setProgress] = React.useState(0);
  const [open, setOpen] = React.useState(false);
  const [activeSection, setActiveSection] = React.useState(null);
  const scrollLocked = React.useRef(false);
  const scrollLockTimer = React.useRef(null);

  const lockScroll = (href = null) => {
    if (href !== null) setActiveSection(href);
    scrollLocked.current = true;
    clearTimeout(scrollLockTimer.current);
    scrollLockTimer.current = setTimeout(() => { scrollLocked.current = false; }, 900);
  };

  React.useEffect(() => {
    const onScroll = () => {
      const y = window.scrollY;
      const doc = document.documentElement;
      const total = doc.scrollHeight - window.innerHeight;
      setProgress(total > 0 ? y / total : 0);
      setScrolled(y > 32);
    };
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, []);

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

  const links = [
  ['#how-it-works', 'How it works'],
  ['#why-prickly', 'Why prickly'],
  ['#pricing', 'Pricing'],
  ['#quality-and-safety', 'Quality & safety'],
  ['#faq', 'FAQ']];

  React.useEffect(() => {
    const ids = links.map(([h]) => h.slice(1));
    const update = () => {
      if (scrollLocked.current) return;
      const threshold = window.innerHeight * 0.4;
      let active = null;
      for (const id of ids) {
        const el = document.getElementById(id);
        if (el && el.getBoundingClientRect().top <= threshold) active = '#' + id;
      }
      setActiveSection(active);
    };
    update();
    window.addEventListener('scroll', update, { passive: true });
    return () => window.removeEventListener('scroll', update);
  }, []);

  return (
    <>
      <nav className={'pr-nav ' + (scrolled ? 'scrolled' : '')} data-screen-label="00 Nav">
        <a href="#top" className="pr-nav-logo" onClick={e => { e.preventDefault(); setActiveSection(null); lockScroll(); scrollToTopSmooth(); }}>
          prickly<span className="pr-nav-logo-dot" />
        </a>
        <div className="pr-nav-links">
          {links.map(([h, l]) => <a key={h} href={h} className={activeSection === h ? 'active' : ''} onClick={() => lockScroll(h)}>{l}</a>)}
        </div>
        {onAccentChange && <AccentTri accent={accent} onAccentChange={onAccentChange} className="pr-nav-accent-tri-all" />}
        <a className="pr-btn sm pr-nav-cta" href="#eligibility">
          Check eligibility
        </a>
        <button className={'pr-nav-burger' + (open ? ' open' : '')} onClick={() => setOpen(!open)} aria-label={open ? 'Close menu' : 'Open menu'}>
          <svg width="22" height="22" viewBox="0 0 22 22" fill="none">
            <line className="line-top" x1="1" y1="11" x2="21" y2="11" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
            <line className="line-bot" x1="1" y1="11" x2="21" y2="11" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
          </svg>
        </button>
        <div className="pr-nav-progress" style={{ transform: 'scaleX(' + progress + ')' }} />
      </nav>

      <div className={'pr-mob-menu-wrap' + (open ? ' open' : '')}>
        <div className="pr-mob-menu">
          <nav>
            {links.map(([h, l]) =>
            <a key={h} href={h} onClick={() => { setOpen(false); lockScroll(h); }}>{l}</a>
            )}
          </nav>
          <a className="pr-btn sage" href="#eligibility" onClick={() => setOpen(false)}>
            Check eligibility
          </a>
        </div>
      </div>
    </>);

}

// ── Hero ──────────────────────────────────────────────────────────────────
function Hero({ copy }) {
  return (
    <section id="top" className="pr-hero" data-screen-label="01 Hero">
      <Container>
        <div className="pr-hero-meta">
          <Kicker>{copy.kicker}</Kicker>
        </div>

        <Reveal as="h1" className="pr-h1 pr-hero-headline">
          <span className="line">{copy.pre}</span>
          <span className="line">
            <RotatingHeadline words={copy.rotate} />
          </span>
        </Reveal>

        <div className="pr-hero-grid">
          <div>
            <Reveal as="p" className="pr-hero-sub" d={2}>{Array.isArray(copy.sub) ? <>{copy.sub[0]}<br />{copy.sub[1]}</> : copy.sub}</Reveal>
            <Reveal className="pr-hero-cta-row" d={3}>
              <a className="pr-btn lg" href="#eligibility">
                Take the eligibility check
              </a>
              <span className="mono" style={{ fontSize: 11.5, textTransform: 'uppercase', letterSpacing: '0.14em', opacity: 0.6, paddingLeft: 4 }}>
                Medicare · OSHC/OVHC · Private
              </span>
            </Reveal>
          </div>
        </div>
      </Container>

      <div className="pr-marquee" aria-hidden>
        <div className="pr-marquee-track">
          {Array.from({ length: 2 }).flatMap((_, k) =>
          ['ISO 15189 NATA Accredited Lab', 'AHPRA-registered clinicians', 'TGA-listed specimen collection equipment',
          'ISO 27001 Secure Medical records', 'Australian Privacy Principles compliant', 'Clinical guideline driven'].map((s) =>
          <span key={k + s}>{s}</span>
          )
          )}
        </div>
      </div>
    </section>);
}

// ── Quiz section ──────────────────────────────────────────────────────────
function QuizSection() {
  return (
    <section id="eligibility" className="pr-section" data-screen-label="02 Quiz">
      <Container>
        <div className="pr-shead">
          <div>
            <Reveal d={0}><Kicker>Eligibility</Kicker></Reveal>
            <Reveal as="h2" className="pr-h2" d={1} style={{ marginTop: 18 }}>
              <RevealWords text="A few short questions to decide if prickly is right for you." />
            </Reveal>
          </div>
          <div className="right">If we can't help, we'll tell you who can.</div>
        </div>
        <Reveal d={2}>
          <EligibilityQuiz />
        </Reveal>
      </Container>
    </section>);

}

// ── How it works (combined: steps · equipment · tests) ────────────────────
function HowItWorks() {
  return (
    <section id="how-it-works" className="pr-section tinted" data-screen-label="03 How it works">

      {/* ── Steps ── */}
      <Container>
        <div className="pr-shead">
          <div>
            <Reveal d={0}><Kicker>How it works</Kicker></Reveal>
            <Reveal as="h2" className="pr-h2" d={1} style={{ marginTop: 18 }}>
              No waiting room. No appointment.<br />Just results.
            </Reveal>
          </div>
          <div className="right">Shipping times vary<br />based on where you are<br />in Australia</div>
        </div>
        <Timeline />
      </Container>

      {/* ── Equipment · STIs (combined) ── */}
      <div id="kit" className="pr-how-stripe">
        <Container>
          <div className="pr-sub-divider">
            <div className="pr-shead">
              <div>
                <Reveal as="h3" className="pr-h3" d={1}>
                  <RevealWords text="The big four STIs." /><br />
                  <RevealWords text="Personalised specimen collection." startDelay={4} /><br />
                  <RevealWords text="Gold standard analysis." startDelay={7} />
                </Reveal>
              </div>
              <div className="right">TGA-listed specimen<br />collection equipment<br />from our partner lab</div>
            </div>
            <Reveal d={2}>
              <KitExplorer />
            </Reveal>
          </div>
        </Container>
      </div>

    </section>);
}

// ── Clinical / closed-loop ────────────────────────────────────────────────
const CREDENTIALS = [
  { id: 'app', label: 'Privacy Act 1988',
    name: 'Australian Privacy Principles',
    p: 'Health information is collected, stored, and handled in accordance with the Australian Privacy Principles, including strict limits on use, disclosure, and retention.' },
  { id: 'ahpra', label: 'AHPRA',
    name: 'Registered clinicians',
    p: 'Every clinical decision — eligibility check, result review, follow-up consultation — is made by a practitioner registered with the Australian Health Practitioner Regulation Agency.' },
  { id: 'guidelines', label: 'Australian standards',
    name: 'Clinical guidelines',
    p: <span>We follow the ASHM Health <a href="https://sti.guidelines.org.au" target="_blank" rel="noopener noreferrer">Australian STI Management Guidelines</a> for asymptomatic STI screening, and the AHPRA/RACGP guidelines for provision of telehealth and advertising of health services.</span> },
  { id: 'iso27001', label: 'ISO 27001',
    name: 'Secure medical records',
    p: 'Health records are stored in a system certified to ISO 27001 — the international standard for information security management. Natively integrated with digital health services including Medicare, My Health Record and eRx.' },
  { id: 'tga', label: 'TGA-listed',
    name: 'Specimen collection equipment',
    p: 'Our partner lab supplies medical devices listed with the Therapeutic Goods Administration, Australia\'s regulator for safety and performance of medical devices and diagnostics.' },
  { id: 'nata', label: 'ISO 15189',
    name: 'NATA Accredited Lab',
    p: 'Our partner lab holds NATA accreditation to ISO 15189 — the international standard for medical laboratory quality and competence, the same standard required of hospital pathology.' },
  { id: 'ashm', label: 'In progress',
    name: 'ASHM Sexual Health Check',
    p: 'We are working towards ASHM Health Sexual Health Check accreditation — the national standard for digital sexual health providers.' },
  { id: 'legitscript', label: 'In progress',
    name: 'LegitScript certification',
    p: 'We are working towards LegitScript certification, the international standard for legitimacy and compliance for online healthcare providers.' },
];

function Clinical() {
  return (
    <section id="quality-and-safety" className="pr-section inverse" data-screen-label="08 Closed-loop care">
      <Container>
        <div className="pr-shead">
          <div>
            <Reveal d={0}><Kicker>Quality & safety</Kicker></Reveal>
            <Reveal as="h2" className="pr-h2" d={1} style={{ marginTop: 18 }}>
              <RevealWords text="Accredited, registered, and regulated." />
            </Reveal>
          </div>
          <div className="right">Independently verified<br />Not self-certified</div>
        </div>

        <div className="pr-creds">
          {CREDENTIALS.map((c, i) => (
            <Reveal key={c.id} className="pr-cred" d={i + 2}>
              <span className="pr-cred-label">{c.label}</span>
              <h4>{c.name}</h4>
              <p>{c.p}</p>
            </Reveal>
          ))}
        </div>

        <div className="pr-creds-loop-head">
          <div className="pr-shead">
            <div>
              <Reveal as="h3" className="pr-h3" d={9} style={{ marginTop: 0 }}>
                <RevealWords text="Closed-loop &amp; compliant care, every time." />
              </Reveal>
            </div>
            <div className="right">Peer reviewed and periodically audited</div>
          </div>
        </div>
        <Reveal d={11}>
          <ClosedLoopDiagram />
        </Reveal>
      </Container>
    </section>);

}

// ── Pricing ───────────────────────────────────────────────────────────────
function Pricing() {
  const plans = [
  {
    tag: 'Residents & Reciprocal Health Care Agreements', name: 'Medicare', price: '69', per: '',
    fineprint: 'Not eligible for Medicare rebate',
    desc: 'All pathology is bulk billed to Medicare. If your Reciprocal Health Care Agreement doesn\'t cover out of hospital care, you will receive a bill for ~$70 from our partner lab.',
    featured: false,
    incl: ['Clinician-reviewed request', 'Self-collected swabs/urine', 'Fingerprick self tests', 'Shipping', 'Clinician-reviewed results', 'Telehealth follow-up*']
  },
  {
    tag: 'Non-residents (Overseas students & visitors)', name: 'OSHC/OVHC', price: '69', per: '',
    fineprint: 'Not eligible for insurance rebate',
    desc: 'All pathology is direct billed to your Overseas Student/Visitor Health Cover insurer. If your insurer doesn\'t cover pathology, you will receive a bill for ~$70 from our partner lab.',
    featured: false,
    incl: ['Clinician-reviewed request', 'Self-collected swabs/urine', 'Fingerprick self tests', 'Shipping', 'Clinician-reviewed results', 'Telehealth follow-up']
  },
  {
    tag: '100% discretion, tourists & no insurance', name: 'Private', price: '139', per: '',
    fineprint: 'Not eligible for any rebate',
    desc: 'Nothing is claimed on Medicare, uploaded to My Health Record or sent to your GP. Your screen stays completely off the record — your business is your business.',
    featured: false,
    incl: ['Clinician-reviewed request', 'Self-collected swabs/urine', 'Fingerprick self tests', 'Shipping', 'Clinician-reviewed results', 'Telehealth follow-up']
  }];


  return (
    <section id="pricing" className="pr-section tinted" data-screen-label="09 Pricing">
      <Container>
        <div className="pr-shead">
          <div>
            <Reveal d={0}><Kicker>Pricing</Kicker></Reveal>
            <Reveal as="h2" className="pr-h2" d={1} style={{ marginTop: 18 }}>
              Flat prices.<br />No surprises.
            </Reveal>
          </div>
          <div className="right">All prices in AUD (including GST where applicable)</div>
        </div>

        <div className="pr-pricing-grid">
          {plans.map((p, i) =>
          <Reveal key={p.name} className={'pr-plan ' + (p.featured ? 'featured' : '')} d={i + 3}>
              <span className="pr-pill">{p.tag}</span>
              <h3>{p.name}</h3>
              <p className="desc">{p.desc}</p>
              <div className="pr-plan-price-block">
                <div className="pr-plan-price">${p.price}<span>{p.per}</span></div>
                <div className="pr-plan-fineprint">{p.fineprint}</div>
              </div>
              <ul>
                {p.incl.map((i, idx) => <li key={idx}>{i}</li>)}
              </ul>
              <a className={'pr-btn ' + (p.featured ? '' : 'sage')} href="#eligibility">
                Start eligibility check
              </a>
            </Reveal>
          )}
        </div>

        <p className="pr-pricing-fineprint">
          *Telehealth follow-up is bulk-billed to Medicare if eligible, otherwise there is no out-of-pocket cost.
        </p>
      </Container>
    </section>);

}

// ── Stat people ───────────────────────────────────────────────────────────
function StatPeople({ value, label }) {
  const fullIcons = Math.floor(value / 10);
  const partialFraction = (value % 10) / 10;
  const clipId = `pr-clip-${value}`;
  const [count, setCount] = React.useState(0);
  const [active, setActive] = React.useState(false);
  const ref = React.useRef(null);

  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const obs = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) { setActive(true); obs.disconnect(); }
    }, { threshold: 0.2 });
    obs.observe(el);
    return () => obs.disconnect();
  }, []);

  React.useEffect(() => {
    if (!active) return;
    let raf, start;
    const duration = 1200;
    const ease = t => 1 - Math.pow(1 - t, 3);
    const step = ts => {
      if (!start) start = ts;
      const p = Math.min((ts - start) / duration, 1);
      setCount(Math.round(ease(p) * value));
      if (p < 1) raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [active, value]);

  return (
    <div ref={ref} className={'pr-stat-people' + (active ? ' active' : '')}>
      <div className="pr-stat-num">
        {count}<span className="pr-stat-pct-sign">%</span>
      </div>
      <div className="pr-person-strip">
        {Array.from({ length: 10 }, (_, i) => {
          const isOn = i < fullIcons;
          const isPartial = i === fullIcons && partialFraction > 0;
          if (isPartial) {
            return (
              <svg key={i} viewBox="0 0 12 20"
                className="pr-person partial"
                style={{ '--stagger': active ? `${i * 55}ms` : '0ms' }}
                aria-hidden="true"
              >
                <defs>
                  <clipPath id={clipId}>
                    <rect x="0" y="0" width={12 * partialFraction} height="20" />
                  </clipPath>
                </defs>
                <g className="pr-person-muted">
                  <circle cx="6" cy="4.5" r="3.5" />
                  <path d="M0 20v-4c0-3.9 2.7-5.5 6-5.5s6 1.6 6 5.5v4z" />
                </g>
                <g className="pr-person-accent" clipPath={`url(#${clipId})`}>
                  <circle cx="6" cy="4.5" r="3.5" />
                  <path d="M0 20v-4c0-3.9 2.7-5.5 6-5.5s6 1.6 6 5.5v4z" />
                </g>
              </svg>
            );
          }
          return (
            <svg key={i} viewBox="0 0 12 20"
              className={'pr-person' + (isOn ? ' on' : '')}
              style={{ '--stagger': active ? `${i * 55}ms` : '0ms' }}
              aria-hidden="true"
            >
              <circle cx="6" cy="4.5" r="3.5" />
              <path d="M0 20v-4c0-3.9 2.7-5.5 6-5.5s6 1.6 6 5.5v4z" />
            </svg>
          );
        })}
      </div>
      <p className="pr-stat-lbl">{label}</p>
    </div>
  );
}

// ── STI Trend sparkline (linear regression) ───────────────────────────────
function StiTrend({ label, headline, sublabel, data }) {
  const [active, setActive] = React.useState(false);
  const ref = React.useRef(null);

  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const obs = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) { setActive(true); obs.disconnect(); }
    }, { threshold: 0.3 });
    obs.observe(el);
    return () => obs.disconnect();
  }, []);

  const W = 200, H = 56, n = data.length;
  const meanX = (n - 1) / 2;
  const meanY = data.reduce((a, b) => a + b, 0) / n;
  const slope = data.reduce((s, y, i) => s + (i - meanX) * (y - meanY), 0) /
                data.reduce((s, _, i) => s + (i - meanX) ** 2, 0);
  const intercept = meanY - slope * meanX;
  const yStart = intercept;
  const yEnd = slope * (n - 1) + intercept;
  const minY = Math.min(yStart, yEnd);
  const maxY = Math.max(yStart, yEnd);
  const pad = Math.max(maxY - minY, maxY * 0.04) * 0.6;
  const toSY = v => H - ((v - (minY - pad)) / (maxY + pad - (minY - pad))) * H;

  return (
    <div ref={ref} className={'pr-sti-trend' + (active ? ' active' : '')}>
      <p className="pr-stat-lbl" style={{ marginBottom: 8 }}>{label}</p>
      <div className="pr-sti-headline">{headline}</div>
      <svg viewBox={`0 0 ${W} ${H}`} className="pr-sti-sparkline" preserveAspectRatio="none" aria-hidden="true">
        <line x1={0} y1={toSY(yStart).toFixed(1)} x2={W} y2={toSY(yEnd).toFixed(1)} className="pr-sti-line" pathLength="1000" />
      </svg>
      <div className="pr-sti-years"><span>2015</span><span>2024</span></div>
      <p className="pr-stat-lbl" style={{ marginTop: 6, opacity: 0.5 }}>{sublabel}<sup>2</sup></p>
    </div>
  );
}

// ── Combined stat (discuss + discomfort) ─────────────────────────────────
function CombinedStatPeople() {
  const [active, setActive] = React.useState(false);
  const [count1, setCount1] = React.useState(0);
  const [count2, setCount2] = React.useState(0);
  const ref = React.useRef(null);

  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const obs = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) { setActive(true); obs.disconnect(); }
    }, { threshold: 0.2 });
    obs.observe(el);
    return () => obs.disconnect();
  }, []);

  React.useEffect(() => {
    if (!active) return;
    let raf, start;
    const dur = 1200;
    const ease = t => 1 - Math.pow(1 - t, 3);
    const step = ts => {
      if (!start) start = ts;
      const p = Math.min((ts - start) / dur, 1);
      setCount1(Math.round(ease(p) * 50));
      setCount2(Math.round(ease(p) * 33));
      if (p < 1) raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [active]);

  const icon = (on, i) => (
    <svg key={i} viewBox="0 0 12 20"
      className={'pr-person' + (on ? ' on' : '')}
      style={{ '--stagger': active ? `${i * 55}ms` : '0ms' }}
      aria-hidden="true"
    >
      <circle cx="6" cy="4.5" r="3.5" />
      <path d="M0 20v-4c0-3.9 2.7-5.5 6-5.5s6 1.6 6 5.5v4z" />
    </svg>
  );

  return (
    <div ref={ref} className="pr-combined-stat">
      <div className={'pr-stat-people' + (active ? ' active' : '')}>
        <div className="pr-stat-num">{count1}<span className="pr-stat-pct-sign">%</span></div>
        <div className="pr-person-strip">
          {Array.from({ length: 10 }, (_, i) => icon(i < 5, i))}
        </div>
        <p className="pr-stat-lbl">of Australians discuss sexual health with a healthcare provider<CitationRef n={1}>Ogilvie E, McManus H, Callander D, et al. The Third Australian Study of Health and Relationships: Report to the NSW Ministry of Health. Sydney, Australia: UNSW Sydney; 2025 May 6.</CitationRef></p>
      </div>
      <div className={'pr-combined-subset pr-stat-people' + (active ? ' active' : '')}>
        <div className="pr-stat-num">{count2}<span className="pr-stat-pct-sign">%</span></div>
        <div className="pr-person-strip">
          {Array.from({ length: 3 }, (_, i) => icon(i < 1, i + 10))}
        </div>
        <p className="pr-stat-lbl">of those express discomfort in doing so<CitationRef n={1}>Ogilvie E, McManus H, Callander D, et al. The Third Australian Study of Health and Relationships: Report to the NSW Ministry of Health. Sydney, Australia: UNSW Sydney; 2025 May 6.</CitationRef></p>
      </div>
    </div>
  );
}

// ── About ────────────────────────────────────────────────────────────────
function About() {
  return (
    <section id="why-prickly" className="pr-section" data-screen-label="10 About">
      <Container>
        <div className="pr-shead">
          <div>
            <Reveal d={0}><Kicker>Why prickly</Kicker></Reveal>
            <Reveal as="h2" className="pr-h2" d={1} style={{ marginTop: 18 }}>
              <RevealWords text="Routine screening should feel routine." />
            </Reveal>
          </div>
          <div className="right">Clinician designed<br />to remove barriers</div>
        </div>
        <div className="pr-about">
          <div className="pr-about-stats">
            <CombinedStatPeople />
            <StatPeople
              value={17}
              label={<>of Australians aged 16–49 have ever been tested for an STI<CitationRef n={1}>Ogilvie E, McManus H, Callander D, et al. The Third Australian Study of Health and Relationships: Report to the NSW Ministry of Health. Sydney, Australia: UNSW Sydney; 2025 May 6.</CitationRef></>}
            />
          </div>
          <div>
            <Reveal as="p" className="pr-p" d={4} style={{ maxWidth: 520, marginBottom: 16, opacity: 0.82 }}>
              Despite STI rates continuing to increase, sexual health remains one of the most under-discussed areas of healthcare in Australia. Stigma, discomfort, and limited access all play a role — and the figures reflect exactly that.
            </Reveal>
            <Reveal as="p" className="pr-p" d={5} style={{ maxWidth: 520, marginBottom: 0, opacity: 0.82 }}>
              Prickly was built by Australian sexual health clinicians to overcome these barriers, without compromising on clinical standards: gold-standard PCR testing for chlamydia and gonorrhoea, TGA-listed rapid tests for HIV and syphilis, and a clinician who reviews and manages every result — all from home, on your terms.
            </Reveal>
          </div>
        </div>
      </Container>
    </section>);

}

// ── Experience ───────────────────────────────────────────────────────────
function Experience() {
  // AHPRA s.133: testimonials about clinical aspects (symptoms, diagnosis, treatment,
  // outcome, practitioner skill) are prohibited. These quotes focus on packaging,
  // communication, and kit usability only.
  const quotes = [
  { tag: 'Packaging', q: "The plain packaging was the first thing I noticed — really thoughtful.", m: 'Anonymous · Melbourne' },
  { tag: 'Communication', q: "Replies came back within hours, not days. That was the bit I wasn't expecting.", m: 'Anonymous · Sydney' },
  { tag: 'Usability', q: "The instructions were genuinely clear. No googling required.", m: 'Anonymous · Brisbane' },
  { tag: 'Process', q: "Ordering was straightforward — I knew exactly what to expect at each step.", m: 'Anonymous · Perth' },
  { tag: 'Delivery', q: "The return envelope made the whole thing feel completely sorted.", m: 'Anonymous · Adelaide' },
  { tag: 'Experience', q: "Everything arrived exactly as described. No surprises at all.", m: 'Anonymous · Hobart' }];

  const VISIBLE = 3;
  const GAP = 16;
  const maxStart = quotes.length - VISIBLE;
  const [startIdx, setStartIdx] = React.useState(0);
  const viewportRef = React.useRef(null);

  React.useEffect(() => {
    const el = viewportRef.current;
    if (!el) return;
    const update = () => {
      const cardW = (el.offsetWidth - GAP * (VISIBLE - 1)) / VISIBLE;
      el.style.setProperty('--carousel-card-w', cardW + 'px');
      el.style.setProperty('--carousel-tx', (-startIdx * (cardW + GAP)) + 'px');
    };
    update();
    const ro = new ResizeObserver(update);
    ro.observe(el);
    return () => ro.disconnect();
  }, [startIdx]);

  const prev = () => setStartIdx(i => Math.max(0, i - 1));
  const next = () => setStartIdx(i => Math.min(maxStart, i + 1));

  return (
    <section id="experience" className="pr-section" data-screen-label="11 Experience notes">
      <Container>
        <div className="pr-sub-divider">
          <div className="pr-shead">
            <div>
              <Reveal as="h3" className="pr-h3" d={0}>
                From people who've<br />used the service.
              </Reveal>
            </div>
            <div className="right">Feedback from real patients without compensation</div>
          </div>
          <div className="pr-carousel">
            <div className="pr-carousel-viewport" ref={viewportRef}>
              <div className="pr-carousel-track">
                {quotes.map((q, i) => (
                  <div key={i} className="pr-quote">
                    <p className="pr-quote-body">{q.q}</p>
                    <span className="pr-quote-meta">— {q.m}</span>
                  </div>
                ))}
              </div>
            </div>
            <div className="pr-carousel-controls">
              <button className="pr-btn ghost sm pr-carousel-btn" onClick={prev} disabled={startIdx === 0} aria-label="Previous quote"><span className="pr-arrow">←</span></button>
              <div className="pr-carousel-dots">
                {Array.from({ length: maxStart + 1 }, (_, i) => (
                  <button key={i} className={'pr-carousel-dot' + (i === startIdx ? ' active' : '')} onClick={() => setStartIdx(i)} aria-label={`Position ${i + 1}`} />
                ))}
              </div>
              <button className="pr-btn ghost sm pr-carousel-btn" onClick={next} disabled={startIdx === maxStart} aria-label="Next quote"><span className="pr-arrow">→</span></button>
            </div>
          </div>
          <p className="pr-quote-note">
            In line with AHPRA advertising regulations, we do not publish testimonials about clinical aspects, e.g. symptoms, diagnosis, treatment, outcome, or practitioner skill.
          </p>
        </div>
      </Container>
    </section>);

}

// ── FAQ ───────────────────────────────────────────────────────────────────
function Faq() {
  const categories = [
    { name: 'Delivery & postage', items: [
      { q: "Will anyone know what's inside the box when it's delivered?",
        a: "Not a chance. Your specimen collection equipment arrives in an unmarked, plain brown cardboard mailer box. The shipping label will show the sender as “Prickly Health Pty Ltd” for official mail handling, but there are absolutely no mentions of STIs, testing, or sexual health on the outside. It looks like any other regular online order." },
      { q: "What does the specimen collection equipment look like once I open the outer packaging?",
        a: "We skip the fancy, over-engineered boxes to keep our costs low—and pass those savings directly on to you. Inside, you’ll find exactly what you need without the fluff: a straightforward instructional card, your medical specimen collection equipment, your pathology form, and a return shipping label. It’s clean, functional, and simple." },
      { q: "How do I return my sample to the lab? Is return shipping free?",
        a: "Yes, return shipping is completely covered. We include a return shipping label right inside your package. Once you’ve collected your samples, just stick that return label directly over the original shipping label on your box. Then drop it off at any Australia Post office or pop it into a standard red street post box." },
      { q: "Can I use a Parcel Locker or PO Box?",
        a: "Absolutely. We ship all of our packages via Australia Post, which means we can smoothly deliver to any valid address in the country—including PO Boxes, Parcel Lockers, businesses, or residential homes." },
      { q: "What happens if my package gets lost in the mail or arrives damaged?",
        a: "Don’t stress. If your package goes missing in transit or any of the contents arrive damaged, just let us know. We’ll sort it out immediately and send you replacement specimen collection equipment so you can get tested without a hitch." },
    ]},
    { name: 'Testing process', items: [
      { q: "What exactly does the Prickly specimen collection equipment test for?",
        a: "We screen for Chlamydia, Gonorrhoea, Syphilis, and HIV using a two-phase hybrid system. For blood-borne infections (HIV & Syphilis), you’ll perform and interpret high-accuracy rapid self-tests right at home. For bacterial infections (Chlamydia & Gonorrhoea), you’ll collect a urine or swab sample and send it to our laboratory partners for molecular screening. Our clinicians review your intake form to ensure the right tests are ordered for your profile." },
      { q: "I’m terrified of needles. Does the finger-prick hurt?",
        a: "We understand needles can be daunting, but the finger-prick blood collection for your home HIV and Syphilis rapid tests is designed to be as quick and painless as possible. We use tiny, single-use lancets that activate in a fraction of a second. It feels like a brief, minor pinch and is over before you know it." },
      { q: "How do I know if I’ve collected enough of a sample?",
        a: "Just make sure to closely follow the instructions provided by Prickly and the medical device manufacturer in your kit. If a sample fails to yield a result or is deemed insufficient, don’t panic. We treat failed, positive, and indeterminate samples with the same clinical care: we arrange a mandatory telehealth follow-up to support you, and we will send out replacement specimen collection equipment so you can retest safely." },
      { q: "How accurate are at-home tests compared to a regular clinic?",
        a: "They are identical in quality. Your rapid self-tests are evidence-based and highly accurate, while your returned fluid samples are analysed by our partner NATA-accredited laboratories. This means you are getting the exact same gold-standard technology used by traditional GPs and sexual health clinics. No shortcuts, no compromise." },
      { q: "Do I need to be experiencing symptoms to use Prickly?",
        a: "No—in fact, Prickly is strictly for routine screening of people who do NOT have symptoms. If you currently have symptoms (like abnormal discharge, sores, or pelvic pain), have had a potential HIV exposure in the last 72 hours, or were treated for Chlamydia/Gonorrhoea in the past 4 weeks, our clinical triage will block you from ordering. You must visit a local GP or sexual health clinic immediately for an in-person consultation." },
    ]},
    { name: 'Timelines & windows', items: [
      { q: "How long does it take to get my results back?",
        a: (<>
          <p>Total turnaround time depends on where you live in Australia. Here’s exactly how the timeline breaks down:</p>
          <ul>
            <li><strong>Review &amp; Dispatch:</strong> Your clinical questionnaire is reviewed and your specimen collection equipment ships the very next business day.</li>
            <li><strong>Delivery to You:</strong> 2–6 business days depending on your location.</li>
            <li><strong>Return Shipping:</strong> Another 2–6 business days back to our lab.</li>
            <li><strong>Lab Processing:</strong> 1–3 days once the lab safely receives your package.</li>
          </ul>
          <p>Your home rapid tests give you answers for HIV and Syphilis in just a few minutes while you wait for the lab.</p>
        </>) },
      { q: "I had sex recently. How long should I wait before using the service?",
        a: "You can test at any time, but you need to keep standard medical “window periods” in mind for accuracy—the time it takes for an infection to become detectable. For Chlamydia and Gonorrhoea, the window period is about 2 weeks. For HIV and Syphilis, it is about 6 weeks." },
      { q: "Is there an expiry date on the specimen collection equipment?",
        a: "There is no set expiration date printed on our outer mailer box, and the equipment has a long enough shelf life to easily cover shipping times. However, you should complete your test as soon as possible after it arrives to ensure all collection components stay fresh and within the manufacturer’s expiration dates." },
      { q: "Can I hold onto my collected samples before posting them?",
        a: "No, please don’t wait. To ensure the highest laboratory accuracy, you must post your samples as soon as possible after collecting them—most definitely on the exact same day." },
      { q: "What is the best day of the week to mail my samples back?",
        a: "We highly recommend dropping your samples in the mail between Monday and Thursday. Sending them early to mid-week ensures the fastest possible delivery to our lab and prevents your biological samples from sitting idle in an Australia Post facility over the weekend." },
    ]},
    { name: 'Results & follow-up', items: [
      { q: "How will I receive my results?",
        a: "You will receive a generic email notification as soon as your results are finalised. If everything comes back negative, the email will let you know you’re all clear. If a result is positive, indeterminate, or failed, the email will use completely neutral language to protect your privacy—it will simply state that action is required and provide a secure link to book your telehealth appointment." },
      { q: "What happens if I test positive for something? Is treatment included?",
        a: (<>
          <p>First, take a deep breath. If you test positive, your mandatory telehealth follow-up is completely covered—bulk-billed if you are Medicare-eligible, or otherwise at no extra cost to you.</p>
          <ul>
            <li><strong>For Chlamydia:</strong> Our clinicians can issue an electronic prescription directly to your phone, or send it to our partner delivery pharmacy to be couriered to your door (the medication itself is an additional cost).</li>
            <li><strong>For Gonorrhoea, Syphilis, or HIV:</strong> These require specialised management or injectable treatments, so our clinicians will provide a formal medical referral to a local GP or specialist health service.</li>
          </ul>
        </>) },
      { q: "Are my results shared with My Health Record or Medicare?",
        a: "Prickly itself does not upload anything to your My Health Record (MHR). However, Australian pathology laboratories default to uploading results there automatically. We provide a prominent option in our intake questionnaire to request an MHR opt-out, but we cannot legally guarantee the external lab will process it correctly. Additionally, if you use Medicare or OSHC/OVHC, a standard billing record of the clinician review and pathology tests performed will appear on your government account history." },
      { q: "Can I download a copy of my pathology results?",
        a: "We do not provide downloadable pathology reports for negative results. However, if you receive a positive result, we will provide a comprehensive clinical summary that you can easily forward to your chosen GP. Your full laboratory pathology reports will also default to appearing on your personal MHR portal unless you have opted out of the system entirely." },
      { q: "What happens if I forget to check my results or book my telehealth appointment?",
        a: "We have a strict 14-day welfare safety net. If you receive a positive or indeterminate result and do not book your telehealth follow-up within 14 days, our team will initiate a manual outreach protocol—including direct phone calls, legally mandated reporting of the unresolved infectious risk to your state Public Health Department, and if necessary, a physical letter to your address or contact with your nominated emergency contact." },
    ]},
    { name: 'Pricing & access', items: [
      { q: "Is Prickly covered by Medicare?",
        a: "Medicare does not cover asynchronous care, which means our initial online clinical questionnaire, review, and service fee are entirely out-of-pocket and not eligible for a rebate. However, the downstream laboratory pathology testing is bulk-billed if you have a valid Medicare card. Just note that if you are under a reciprocal healthcare agreement that doesn’t cover out-of-hospital costs, the pathology will incur a private lab fee." },
      { q: "Can I claim this on my private health insurance or OSHC/OVHC?",
        a: "Because OSHC and OVHC policies do not cover asynchronous care, our service fee and questionnaire cannot be claimed back. For the lab testing itself, we direct-bill the pathology to our partner lab under your insurance. If your specific OSHC/OVHC policy excludes pathology coverage entirely, the lab testing component will incur a private cost." },
      { q: "Why should I pay for Prickly when I could just go to a clinic for free?",
        a: "You are paying for pure convenience, absolute discretion, and total control. Free clinics are an incredible resource, but they often mean long wait times, taking time off work, and having vulnerable face-to-face conversations. Prickly removes the friction entirely. We do not use AI or automated algorithms; your file is reviewed entirely by real, human Australian clinicians, giving you premium clinical care right from the comfort of your own home." },
      { q: "Do I need a doctor’s referral before I order?",
        a: "Nope, you don’t need to see a GP beforehand. Our online clinical questionnaire acts as your digital intake. Once you submit it, our clinical team reviews your answers to ensure you are safe for remote screening and generates the required laboratory pathology referral for you behind the scenes." },
      { q: "Can I request an STI screen for my partner or a friend?",
        a: "No, you can only request STI screening for yourself. Because Prickly is an official clinical service, every single request must be legally tied to the individual performing the test. You must be at least 16 years old, physically located in Australia, and your identity must be securely verified against official health records during registration. Your partner or friend will need to submit their own request." },
    ]},
  ];

  const [activeTab, setActiveTab] = React.useState(0);
  const [open, setOpen] = React.useState(-1);

  function switchTab(i) {
    setActiveTab(i);
    setOpen(-1);
  }

  return (
    <section id="faq" className="pr-section" data-screen-label="12 FAQ">
      <Container>
        <div className="pr-faq-header">
          <Reveal><Kicker>FAQ</Kicker></Reveal>
          <Reveal as="h2" className="pr-h2" d={1}>
            The actual questions<br />people ask.
          </Reveal>
        </div>
        <div className="pr-faq">
          <div className="pr-faq-side">
            <nav className="pr-faq-tabs">
              {categories.map((cat, i) => (
                <button
                  key={cat.name}
                  className={'pr-faq-tab' + (activeTab === i ? ' active' : '')}
                  onClick={() => switchTab(i)}
                >
                  {cat.name}
                </button>
              ))}
            </nav>
          </div>
          <div className="pr-faq-list">
            {categories[activeTab].items.map((it, i) =>
              <div key={it.q} className={'pr-faq-item ' + (open === i ? 'open' : '')}>
                <button className="pr-faq-q" onClick={() => setOpen(open === i ? -1 : i)} aria-expanded={open === i}>
                  <span className="pr-faq-q-text">{it.q}</span>
                  <span className="pr-faq-toggle" aria-hidden>
                    <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
                      <line x1="7" y1="1" x2="7" y2="13" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
                      <line x1="1" y1="7" x2="13" y2="7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
                    </svg>
                  </span>
                </button>
                <div className="pr-faq-a"><div className="pr-faq-a-inner">{it.a}</div></div>
              </div>
            )}
          </div>
          <p className="pr-faq-blurb pr-p sm" style={{ maxWidth: 320, opacity: 0.78 }}>
            Can't find what you're looking for?{' '}
            <a href="mailto:hello@prickly.health" style={{ textDecoration: 'underline', textUnderlineOffset: 3 }}>
              hello@prickly.health
            </a>{' '}— a clinician or operations team member replies, not a bot.
          </p>
        </div>
      </Container>
    </section>
  );
}


// ── Footer ────────────────────────────────────────────────────────────────
function Footer() {
  return (
    <footer className="pr-footer" data-screen-label="14 Footer">
      <Container>
        <div className="pr-footer-grid">
          <div>
            <a href="#" className="pr-footer-wordmark-link" onClick={e => { e.preventDefault(); scrollToTopSmooth(); }}>
              <Reveal as="div" className="pr-footer-wordmark">
                prickly<span className="dot" aria-hidden="true" />
              </Reveal>
            </a>
            <p className="pr-footer-blurb">
              Private, clinician-supported STI screening delivered to you Australia-wide.
            </p>
            <span className="mono" style={{ fontSize: 11, opacity: 0.55, letterSpacing: '0.12em', textTransform: 'uppercase' }}>
              <a href="https://prickly.health" style={{ color: 'inherit', textDecoration: 'none' }}>prickly.health</a> · <a href="mailto:hello@prickly.health" style={{ color: 'inherit', textDecoration: 'none' }}>hello@prickly.health</a>
            </span>
          </div>
          <div>
            <h5>The service</h5>
            <ul>
              <li><a href="#how-it-works">How it works</a></li>
              <li><a href="#quality-and-safety">Quality & safety</a></li>
              <li><a href="#pricing">Pricing</a></li>
              <li><a href="#why-prickly">Why prickly</a></li>
              <li><a href="#faq">FAQ</a></li>
            </ul>
          </div>
          <div>
            <h5>Legal</h5>
            <ul>
              <li><a href="privacy-policy.html">Privacy policy</a></li>
              <li><a href="terms-and-conditions.html">Terms &amp; conditions</a></li>
              <li><a href="fulfilment-shipping-returns-policy.html">Fulfilment, shipping &amp; returns policy</a></li>
            </ul>
          </div>
        </div>
        <div className="pr-footer-ack">
          <p>We acknowledge the Traditional Custodians of Country throughout Australia and recognise the disproportionate health burden First Nations peoples face.</p>
          <p>Prickly is a proud LGBTIQ+ and disability ally. Sexual health is for everyone.</p>
        </div>
        <div className="pr-footer-meta">
          <span>© {new Date().getFullYear()} Prickly Health Pty Ltd · All Rights Reserved. · ABN 26 686 380 156</span>
          <span>By using our website, you consent to our use of cookies in accordance with our <a href="privacy-policy.html">privacy policy</a>.</span>
        </div>
      </Container>
    </footer>);

}

// ── Site composition ──────────────────────────────────────────────────────
function PricklySite({ tweaks, onAccentChange }) {
  const t = tweaks;
  const accent = ACCENT_OPTIONS[t.accent].hex;
  const copy = HERO_COPY[t.heroCopy];
  const motion = t.motion;

  const [pos, setPos] = React.useState(null);

  React.useEffect(() => {
    if (!window.matchMedia('(hover: hover)').matches) return;
    const BLANK = 'url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7") 0 0, none';
    const styleEl = document.createElement('style');
    styleEl.textContent = `*, *::before, *::after { cursor: ${BLANK} !important; }`;
    document.head.appendChild(styleEl);
    const onMove = (e) => setPos({ x: e.clientX, y: e.clientY });
    window.addEventListener('mousemove', onMove);
    return () => {
      window.removeEventListener('mousemove', onMove);
      document.head.removeChild(styleEl);
    };
  }, []);

  const style = {
    '--accent': accent,
    '--motion': motion === 'off' ? 1 : motion === 'high' ? 1.4 : motion === 'low' ? 0.6 : 1
  };
  const motionAttr = motion === 'off' ? 'off' : motion;

  return (
    <div className="pr-site" style={{...style, cursor: window.matchMedia('(hover: hover)').matches ? 'none' : 'auto'}} data-motion={motionAttr}>
      {pos && <div aria-hidden style={{
        position: 'fixed',
        top: 0, left: 0,
        width: 0, height: 0,
        borderLeft: '11px solid transparent',
        borderRight: '11px solid transparent',
        borderBottom: `20px solid ${accent}`,
        transform: `translate(${pos.x - 11}px, ${pos.y}px)`,
        filter: 'drop-shadow(0px 0px 2px rgba(0,0,0,0.3))',
        pointerEvents: 'none',
        zIndex: 9999,
        willChange: 'transform',
      }} />}
      <Nav accent={t.accent} onAccentChange={onAccentChange} />
      <Hero copy={copy} />
      <QuizSection />
      <HowItWorks />
      <About />
      <Experience />
      <Pricing />
      <Clinical />
      <Faq />
      <Footer />
    </div>);

}

Object.assign(window, {
  PricklySite,
  ACCENT_OPTIONS
});