// PHYSIO SAMOS — Home page
const { useState, useEffect } = React;

// Same six as the About reviews column, so a reviewer keeps their colour across pages.
const REVIEW_AVATAR_COLORS = ["#68a29b", "#d97757", "#2d4a47", "#c8956d", "#86c1ba", "#3a6b65"];

const HomeHero = () => {
  const { useRef } = React;
  const [t] = useT();
  const headingRef = useRef(null);
  return (
    <>
    <section className="hero-full reveal in" data-screen-label="Hero">
      <div className="hero-full__bg" role="img"
           aria-label="The Clinical Pilates studio at PHYSIO SAMOS: a wooden reformer under exposed beams" />
      <div className="hero-full__scrim" aria-hidden="true" />
      <div className="th__grain" aria-hidden="true" />
      <div className="hero-full__rays">
        <window.SideRays origin="top-right" rayColor1="#f5f1ea" rayColor2="#a8c8c2"
                         speed={1.2} intensity={1.1} spread={1.6} falloff={1.7}
                         saturation={1.1} blend={0.6} opacity={0.55} />
      </div>
      <div className="wrap hero-full__content">
        {/* Social proof, built entirely from data the clinic actually has: the four initials
            are the first four real reviewers in CONTENT.reviews.items, and the score and
            count are the same two strings the About page's aggregate renders. Nothing here
            is a number invented for the hero — if the reviews are refreshed, this follows
            them. The count string already names Google in all five languages. */}
        <div className="hero-proof">
          <span className="hero-proof__avatars" aria-hidden="true">
            {t.reviews.items.slice(0, 4).map((r, i) => (
              <span key={i} style={{ background: REVIEW_AVATAR_COLORS[i % REVIEW_AVATAR_COLORS.length] }}>
                {r.initial}
              </span>
            ))}
          </span>
          <window.Stars n={5} size={15} />
          {/* The aggregate is split so the muted half can be muted on its own. Setting the
              whole span to 82% and then reaching for `currentColor` on the <strong> does not
              work: inside a muted parent, currentColor *is* the muted value, so the score
              silently loses its emphasis. */}
          <span className="hero-proof__text">
            <strong>{t.reviews.score}</strong>
            <span className="hero-proof__meta"> · {t.reviews.count}</span>
          </span>
        </div>
        <h1 ref={headingRef}>
          <window.VariableProximity label={t.hero.title_a} containerRef={headingRef} {...window.PS.PROX} /><br />
          <span className="serif" style={{ color: "var(--sage-light)" }}>
            <window.VariableProximity label={t.hero.title_b} containerRef={headingRef} {...window.PS.PROX} />
          </span>
        </h1>
        <p className="hero-full__lead">{t.hero.lead}</p>
        <div className="hero-full__actions">
          <button className="btn btn-primary btn-lg" onClick={() => window.PS.openBooking()}>
            {t.hero.cta_primary}<span className="arrow">→</span>
          </button>
          <a href="/services" className="btn btn-ghost btn-lg">{t.hero.cta_secondary}</a>
        </div>
      </div>
    </section>
    </>
  );
};

const StatsRow = () => {
  const [t] = useT();
  return (
    <section className="reveal in" style={{ paddingTop: 0, paddingBottom: 0 }}>
      <div className="wrap" style={{ marginTop: 56, marginBottom: 8 }}>
        <div className="g g--stats" style={{ "--cols": "repeat(3, auto)", gap: "clamp(40px, 8vw, 120px)" }}>
          {/* The figure and its suffix are separate now: CountUp animates a number, and "k+"
              or "+" is not part of one. These are not in the dictionaries — only the labels
              below them are — so splitting them costs no key parity. */}
          {[
            // Years open is computed, not written: the clinic opened in CLINIC.since (2017), so the
            // figure steps up by itself each January — "9+" in 2026, "10+" in 2027 — instead of
            // going stale the way a hardcoded 9 would (owner's call, 2026-09-05).
            { n: new Date().getFullYear() - window.PS.CLINIC.since, suffix: "+", v: t.hero.stat_years },
            { n: 1.2, suffix: "k+", v: t.hero.stat_patients },
            { n: 24, suffix: "", v: t.hero.stat_protocols },
          ].map((s, i) => (
            <div key={i}>
              <div className="stat-num">
                {/* Staggered by index so the three do not tick in lockstep. */}
                <window.CountUp to={s.n} duration={1.1} delay={i * 0.12} />{s.suffix}
              </div>
              <div style={{ marginTop: 8, fontFamily: "var(--font-mono)", fontSize: 11,
                            letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--muted)" }}>
                {s.v}
              </div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
};

// One glyph per entry in CONTENT.values.items, named rather than indexed so a reordered
// dictionary cannot hand a value someone else's icon.
const VALUE_ICONS = ["valAssess", "valTech", "valPilates", "valWomens"];

const ValuesSection = () => {
  const [t] = useT();
  return (
    <section className="reveal" data-screen-label="Values">
      <div className="wrap">
        <div className="g g--stack" style={{ "--cols": "0.8fr 1.7fr", gap: 56, marginBottom: 80, alignItems: "stretch" }}>
          <div className="split-head">
            <span className="eyebrow">{t.values.eyebrow}</span>
            <h2 className="values-title" style={{ marginTop: 18 }}>
              <window.BlurText text={t.values.title_a} /><br />
              <window.BlurText text={t.values.title_b} /><br />
              <span className="serif" style={{ color: "var(--sage-dark)" }}><window.BlurText text={t.values.title_c} /></span>
            </h2>
            <p style={{ marginTop: 24, fontSize: 18, color: "var(--ink-3)", lineHeight: 1.6, maxWidth: 440 }}>
              {t.values.lead}
            </p>
          </div>
          {/* The premises. Labels reuse strings the clinic page already carries, rather than
              inventing five more in five languages. The owner paired the two beds on
              2026-09-05: the frame in `massage-room-shelf` is the TREATMENT room and the one in
              `treatment-room-bed` is the MASSAGE room — the slugs were named from a guess at the
              photographs and read the wrong way round. Renaming them is a manifest edit plus a
              rebuild and has not been done; the pairing below is the owner's word. */}
          <div className="values-gallery">
            <window.AccordionGallery
              items={[
                // Four of the five are people-free — verified frame by frame at full size,
                // not assumed from the filename or from a contact-sheet thumbnail. That is
                // also why tecar-device is a crop rather than a frame (see the crop column in
                // media-src/manifest.tsv), and why studio-reformer-still is unused: its mean
                // luma is 14/255, so it renders as a black rectangle.
                //   The studio panel is the site's old hero render, brought back for it. That
                // matters because there is NO people-free photograph of that room — every studio
                // still in media-src has patients on the reformers, and the whole unclaimed set
                // was swept to confirm it. A real frame was used here for one day and did show
                // patients; the render restores the deck's people-free property, so it still
                // never needs a release. The trade is that this one panel is a generated image
                // rather than the premises, which the slug name states outright.
                //   Reuse: normatec-boots is also in the clinic gallery, and is the only overlap.
                { slug: "massage-room-shelf",   label: t.clinic.rooms[0].name },
                { slug: "tecar-device",         label: t.clinic.motion_items[0] },
                { slug: "normatec-boots",       label: t.clinic.equipment_title },
                { slug: "pilates-studio-render", label: t.clinic.rooms[3].name },
                { slug: "treatment-room-bed",   label: t.clinic.motion_items[1] },
              ].map(({ slug, label }) => ({
                image: window.PS.img(slug, 800),
                srcSet: (window.PS_MEDIA[slug] || []).map((w) => `media/${slug}-${w}.jpg ${w}w`).join(", "),
                sizes: "(max-width: 560px) 100vw, (max-width: 900px) 90vw, 45vw",
                label, alt: label,
              }))}
              defaultIndex={0} height={460} gap={10} radius={16} expandRatio={0.52}
              trigger="hover" dim={0.08} grayscale={false}
              accentColor="#a8c8c2" overlayColor="#1a1f1e" textColor="#f5f1ea" />
          </div>
        </div>
        {/* --rule, not --line: this block sits on a below level now, over the animated
            gradient rather than a flat slab, and --line disappeared against it. Mixed from
            --ink so it inverts with the theme and stays a step stronger than the card
            borders. The stacked layout below 900px reads the same property. */}
        <div className="g g--rule" style={{ "--cols": "repeat(4, 1fr)", gap: 0,
                      "--rule": "color-mix(in srgb, var(--ink) 26%, transparent)",
                      borderTop: "1px solid var(--rule)" }}>
          {t.values.items.map((v, i) => (
            <div key={i} style={{
              padding: "40px 28px 40px 0",
              borderRight: i < 3 ? "1px solid var(--rule)" : "none",
              paddingLeft: i > 0 ? 28 : 0,
            }}>
              <span className="value-dot"><Icon name={VALUE_ICONS[i % VALUE_ICONS.length]} size={20} /></span>
              <h4 style={{ marginBottom: 12, fontSize: 20 }}>{v.title}</h4>
              <p style={{ fontSize: 14, color: "var(--ink-3)", lineHeight: 1.55 }}>{v.body}</p>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
};

const FeaturedImageBlock = () => {
  const [t] = useT();
  return (
    <section className="reveal" data-screen-label="Featured" style={{ paddingBottom: 0 }}>
      <div className="wrap-wide">
        <div className="g g--stack g--featured" style={{ "--cols": "1.2fr 0.8fr", gap: 24 }}>
          <div className="img-overlay featured-left" style={{ position: "relative", borderRadius: "var(--radius-lg)", overflow: "hidden",
                        background: "var(--bg-2)" }}>
            {/* The gym floor mid-exercise, which is what the caption names. `position` is
                horizontal because the crop is: a 16:9 photo in a near-square tile crops on the
                x axis, so a vertical value here would do nothing at all — the trap the old
                `center 32%` fell into. */}
            <window.Img name="pilates-mat-class" w={1600}
                        sizes="(max-width: 900px) 100vw, 55vw" position="center center"
                        style={{ width: "100%", height: "100%", objectFit: "cover" }} />
            <window.PhotoCaption size="lg"
                                 title={t.services.items[1].title}
                                 sub={t.services.items[1].subtitle} />
          </div>
          <div style={{ display: "grid", gridTemplateRows: "1fr 1fr", gap: 24 }}>
            <div className="img-overlay" style={{ position: "relative", borderRadius: "var(--radius-lg)", overflow: "hidden",
                          background: "var(--bg-2)" }}>
              {/* Stamatina working a knee, hands-on — which is what the caption says. The
                  device frames from the same session (ultrasound applicator on the knee) were
                  the closer match for "her face is barely visible", but an instrument under a
                  "Manual Therapy" label claims the wrong thing. She is centre-left, so the
                  crop leans that way rather than centring on the empty screen behind her. */}
              <window.Img name="manual-therapy-knee" w={1200}
                          sizes="(max-width: 900px) 100vw, 36vw" position="35% center"
                          style={{ width: "100%", height: "100%", objectFit: "cover" }} />
              <window.PhotoCaption size="sm" title={t.ui.manual_therapy} sub={t.ui.hands_on} />
            </div>
            <div style={{ position: "relative", borderRadius: "var(--radius-lg)", overflow: "hidden",
                          background: "var(--band)", color: "var(--on-band)",
                          padding: 32, display: "flex", flexDirection: "column", justifyContent: "space-between" }}>
              <div>
                {/* The primary specialism only, not the whole `focus` string. This card's column is
                    258px at 901 and never exceeds 456px, so the full list ran to four lines of
                    uppercase mono above the quote (five in French) — and the team row directly
                    beside it already prints the string in full, so nothing is lost. Splitting on
                    the separator every language uses; a string without one falls through to
                    itself, which is the old behaviour rather than an empty label. */}
                <span className="eyebrow" style={{ color: "var(--sage-light)" }}>
                  {t.team.members[0].focus.split(" · ")[0]}
                </span>
                {/* The whole sentence, not a slice. It was cut at 110 characters, which broke
                    mid-word in Greek ("βελτίωσ…") and — because Turkish is 109 characters —
                    rendered that language's full stop followed by an ellipsis. It is the
                    owner's own words either way, so truncating it was the wrong trade; the
                    type steps down instead. */}
                <p style={{ marginTop: 18, fontFamily: "var(--font-serif)", fontStyle: "italic",
                            fontSize: "clamp(17px, 1.45vw, 21px)", lineHeight: 1.3,
                            color: "var(--on-band)" }}>
                  “{t.team.members[0].quote}”
                </p>
              </div>
              <div>
                {/* Name only. The role line that sat under it came off at the owner's request
                    (2026-09-05): with three titles it ran to two lines of 12px in a footer that
                    wants one, and the About page carries the full line beside her portrait. */}
                <div style={{ fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 16 }}>
                  — {t.team.members[0].name}
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
};


// Home services showcase: a snap carousel of cards, three per view on desktop. Each card
// gets a line icon and its own muted accent — related tones, not the brand pair repeated.
const SVC_ACCENTS = ["#5F9A94", "#C08552", "#A9827C", "#8A9A5B", "#B49B57", "#547671", "#7C8FA6"];
// One Lucide glyph per service, in the order of CONTENT.services.items. Named rather
// than indexed so a reordered dictionary cannot silently hand a service the wrong icon.
const SVC_ICONS = ["svcOrtho", "svcExercise", "svcWomens", "svcFoot",
                   "svcMassage", "svcAcupuncture", "svcModalities"];
// Three copies of the list, so the track can wrap in either direction without the user
// ever reaching an edge. The middle copy is home; once a scroll settles outside it, we
// jump back by exactly one copy width — which is always a valid snap point, so the swap
// is invisible.
const SVC_REPEATS = 3;

// How long each card holds before the carousel steps on. The countdown bar across the top
// of the active card is driven from the same number, through --svc-dur, so the bar can never
// disagree with the timer.
const SVC_AUTOPLAY = 5200;

const ServicesCardCarousel = () => {
  const [t] = useT();
  const trackRef = React.useRef(null);
  const rootRef = React.useRef(null);
  const rafRef = React.useRef(0);
  const [activeKey, setActiveKey] = React.useState(null);
  const [paused, setPaused] = React.useState(false);
  const items = t.services.items;

  const reduced = window.matchMedia
    && window.matchMedia("(prefers-reduced-motion: reduce)").matches;

  const copyWidth = () => {
    const el = trackRef.current;
    return el ? el.scrollWidth / SVC_REPEATS : 0;
  };
  // One card plus one gap. The carousel steps by a single card now rather than a full page
  // of three, because "time left until the next one" only means anything if the next one is
  // a card and not a screenful.
  const cardStep = () => {
    const el = trackRef.current;
    if (!el || el.children.length < 2) return 0;
    return el.children[1].offsetLeft - el.children[0].offsetLeft;
  };

  // Whichever card sits nearest the middle of the scrollport is the active one. Derived from
  // scroll position rather than tracked as an index, so a trackpad swipe, a snap settle and
  // the autoplay timer all agree without any of them telling the others.
  const measureActive = () => {
    rafRef.current = 0;
    const el = trackRef.current;
    if (!el) return;
    const mid = el.scrollLeft + el.clientWidth / 2;
    let best = null, bestD = Infinity;
    Array.from(el.children).forEach((c) => {
      const d = Math.abs(c.offsetLeft + c.offsetWidth / 2 - mid);
      if (d < bestD) { bestD = d; best = c.dataset.svcKey; }
    });
    setActiveKey((prev) => (prev === best ? prev : best));
  };
  const scheduleMeasure = () => {
    if (!rafRef.current) rafRef.current = requestAnimationFrame(measureActive);
  };

  React.useEffect(() => {
    const el = trackRef.current;
    if (!el) return;
    el.scrollLeft = copyWidth();
    measureActive();
  }, [items.length]);

  React.useEffect(() => {
    const el = trackRef.current;
    if (!el) return;
    let timer;
    const recentre = () => {
      const w = copyWidth();
      if (!w) return;
      // Wait for the scroll to settle before teleporting: doing it mid-animation would
      // fight the smooth scroll, which is targeting an absolute position.
      if (el.scrollLeft < w * 0.5) el.scrollLeft += w;
      else if (el.scrollLeft > w * 1.5) el.scrollLeft -= w;
      measureActive();
    };
    const onScroll = () => { scheduleMeasure(); clearTimeout(timer); timer = setTimeout(recentre, 150); };
    el.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", scheduleMeasure);
    return () => {
      el.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", scheduleMeasure);
      clearTimeout(timer);
      if (rafRef.current) cancelAnimationFrame(rafRef.current);
    };
  }, [items.length]);

  const step = (dir) => {
    const el = trackRef.current;
    if (!el) return;
    const w = copyWidth();
    // Recentre before the step rather than after it: the jump is instantaneous and
    // invisible, and it leaves the smooth scroll a safe range to animate through instead
    // of running into the end of the track. The scroll listener above stays as the
    // safety net for trackpad swipes, which arrive without going through here.
    if (w) {
      if (dir < 0 && el.scrollLeft - el.clientWidth < w * 0.25) el.scrollLeft += w;
      else if (dir > 0 && el.scrollLeft + el.clientWidth > w * 1.75) el.scrollLeft -= w;
    }
    el.scrollBy({ left: dir * (cardStep() || el.clientWidth), behavior: reduced ? "auto" : "smooth" });
  };

  // The timer restarts whenever the active card changes, so a manual step resets the
  // countdown instead of leaving the bar mid-way through someone else's turn.
  React.useEffect(() => {
    if (reduced || paused || activeKey === null) return;
    const id = setTimeout(() => step(1), SVC_AUTOPLAY);
    return () => clearTimeout(id);
  }, [activeKey, paused, reduced]);

  // The middle copy is the one at rest, so it is the one that carries the real links;
  // the outer two are scenery and stay out of the accessibility tree and the tab order.
  const REAL_COPY = 1;
  // Same sequence as the services page's stack (PS.SVC_ORDER). `i` stays the DICTIONARY
  // index throughout — it is what keys the accent, the glyph and the `#service-N` link —
  // so only the position changes here.
  const loop = [];
  const order = window.PS.svcOrder(items, SVC_ICONS);
  for (let r = 0; r < SVC_REPEATS; r++) {
    order.forEach((i) => loop.push({ sv: items[i], i, key: `${r}-${i}`, real: r === REAL_COPY }));
  }

  // **Hover no longer pauses it** — the carousel advances whether the pointer is over it or
  // not. It used to stop on `mouseenter`, which meant a pointer resting anywhere over the row
  // froze the rotation indefinitely, and on a wide screen that is most of where the pointer
  // sits while reading. Focus still pauses: a keyboard user who has tabbed into a card should
  // not have it scrolled out from under them, and that is about the caret, not the mouse.
  const hold = (on) => () => setPaused(on);

  return (
    <div className={`svc-carousel${paused ? " is-paused" : ""}`} ref={rootRef}
         onFocusCapture={hold(true)} onBlurCapture={hold(false)}>
      <div className="svc-track" ref={trackRef} tabIndex={0} aria-label={t.services.eyebrow}>
        {loop.map(({ sv, i, key, real }) => {
          const c = SVC_ACCENTS[i % SVC_ACCENTS.length];
          const isActive = key === activeKey;
          return (
            <a key={key} data-svc-key={key} href={`/services#service-${i}`}
               className={`svc-card${isActive ? " is-active" : ""}`}
               style={{ "--svc": c, "--svc-dur": `${SVC_AUTOPLAY}ms` }}
               aria-hidden={real ? undefined : "true"} tabIndex={real ? undefined : -1}>
              {/* The service's own glyph, blown up and pushed into the corner until it is
                  texture rather than an icon. */}
              <span className="svc-card__mark" aria-hidden="true">
                <window.Icon name={SVC_ICONS[i % SVC_ICONS.length]} size={340} />
              </span>
              {/* Keyed on the active card so React remounts it at every change, which is
                  what restarts the CSS animation from zero. */}
              <span className="svc-card__bar" aria-hidden="true">
                <i key={isActive ? activeKey : "idle"} />
              </span>
              <span className="svc-card__icon"><window.Icon name={SVC_ICONS[i % SVC_ICONS.length]} size={22} /></span>
              <h3 className="svc-card__title">{sv.title}</h3>
              <p className="svc-card__desc">{sv.subtitle}</p>
              <span className="svc-card__more mono">{t.ui.read_more} →</span>
            </a>
          );
        })}
      </div>
      <div className="svc-nav">
        <button type="button" className="ctl-round" aria-label="Previous" onClick={() => step(-1)}><window.Icon name="chevronLeft" size={18} /></button>
        <button type="button" className="ctl-round" aria-label="Next" onClick={() => step(1)}><window.Icon name="chevronRight" size={18} /></button>
      </div>
    </div>
  );
};

const ServicesPreview = () => {
  const [t] = useT();
  return (
    <section className="reveal" data-screen-label="Services preview">
      <div className="wrap">
        <div className="sec-head">
          <span className="eyebrow">{t.services.eyebrow}</span>
          <h2 className="sec-head__title">
            <window.BlurText text={t.services.title_a} />{" "}
            <span className="serif" style={{ color: "var(--sage-dark)" }}><window.BlurText text={t.services.title_b} /></span>
          </h2>
          <p className="sec-head__lead">{t.services.lead}</p>
          <a href="/services" className="btn btn-ghost" style={{ marginTop: 28 }}>{t.services.see_all} →</a>
        </div>
        <ServicesCardCarousel />
      </div>
    </section>
  );
};

const ConditionsSection = () => {
  const [t] = useT();
  return (
    <section className="reveal" data-screen-label="Conditions" style={{ background: "var(--bg-2)" }}>
      <div className="wrap">
        <div className="sec-head">
          <span className="eyebrow">{t.conditions.eyebrow}</span>
          <h2 className="sec-head__title">
            <window.BlurText text={t.conditions.title_a} />{" "}
            <span className="serif" style={{ color: "var(--sage-dark)" }}><window.BlurText text={t.conditions.title_b} /></span>
          </h2>
          <p className="sec-head__lead">
            {t.conditions.lead}
          </p>
        </div>
        <div className="g g--cards" style={{ "--cols": "repeat(3, 1fr)", gap: 24 }}>
          {t.conditions.groups.map((g, i) => (
            <div key={i} style={{
              background: "var(--bg)", borderRadius: "var(--radius)", padding: 28,
              border: "1px solid var(--line)",
            }}>
              <h4 style={{ fontSize: 22, marginBottom: 16 }}>{g.title}</h4>
              <ul style={{ padding: 0, listStyle: "none", display: "flex", flexWrap: "wrap", gap: 6 }}>
                {g.items.map((it, j) => (
                  <li key={j} style={{
                    fontSize: 12, padding: "5px 10px", borderRadius: 999,
                    background: "var(--bg-2)", color: "var(--ink-2)",
                    border: "1px solid var(--line)", whiteSpace: "nowrap",
                  }}>{it}</li>
                ))}
              </ul>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
};

const TeamPreview = () => {
  const [t] = useT();
  // Same design language as the service cards (accent tick, mono role, accent link),
  // but the section keeps its portraits-beside-text layout rather than becoming cards.
  const accents = ["#5F9A94", "#C08552"];
  const photos = ["team-maria-portrait", "team-stamatina-portrait"];
  return (
    <section className="reveal" data-screen-label="Team preview">
      <div className="wrap">
        <div className="g g--stack" style={{ "--cols": "1fr 1.2fr", gap: 72, alignItems: "center" }}>
          <div className="g team-portraits" style={{ "--cols": "1fr 1fr", gap: 16 }}>
            {t.team.members.map((m, i) => (
              <div key={i} className="img-overlay team-portrait" style={{ marginTop: i === 1 ? 40 : 0 }}>
                <window.Img name={photos[i]} w={800} sizes="(max-width: 900px) 45vw, 22vw"
                            position="center 20%" alt={m.name}
                            style={{ width: "100%", height: "100%", objectFit: "cover" }} />
                {/* Just the accent tick: the list beside these portraits already carries
                    each name, so repeating it here said the same thing twice. The colour
                    is what ties a photo to its row. */}
                <span className="team-portrait__tick team-tick"
                      style={{ background: accents[i] }} aria-hidden="true" />
              </div>
            ))}
          </div>
          <div className="split-head">
            <span className="eyebrow">{t.team.eyebrow}</span>
            <h2 className="team-title" style={{ marginTop: 18, marginBottom: 20 }}>
              <window.BlurText text={t.team.title_a} /><br />
              <span className="serif" style={{ color: "var(--sage-dark)" }}><window.BlurText text={t.team.title_b} /></span>
            </h2>
            <p style={{ fontSize: 17, color: "var(--ink-3)", lineHeight: 1.65 }}>{t.team.lead}</p>
            <div className="team-list">
              {/* The accent is the name's colour now, rather than a rule beside it. */}
              {t.team.members.map((m, i) => (
                <div key={i} className="team-row" style={{ "--svc": accents[i] }}>
                  <div className="team-row__name">{m.name}</div>
                  <div className="team-row__role mono">{m.focus}</div>
                </div>
              ))}
            </div>
            <div style={{ marginTop: 32, display: "flex", gap: 12, flexWrap: "wrap" }}>
              <a href="/about" className="btn btn-primary">{t.ui.meet_us} →</a>
              <a href="/clinic" className="btn btn-ghost">{t.nav.clinic}</a>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
};

// ---------- FAQ ----------
// Moved here when the contact page was folded away.
const FAQSection = () => {
  const [t, lang] = useT();
  const [openIdx, setOpenIdx] = useState(0);
  const faqs = t.faq.items;
  return (
    <section data-screen-label="FAQ">
      <div className="wrap">
        <div className="g g--stack" style={{ "--cols": "0.9fr 1.1fr", gap: 80, alignItems: "stretch" }}>
          <div className="faq-intro split-head">
            <span className="eyebrow">{t.faq.eyebrow}</span>
            <h2 className="faq-title" style={{ marginTop: 18 }}>
              <window.BlurText text={t.faq.title_a} /><br />
              <span className="serif" style={{ color: "var(--sage-dark)" }}>
                <window.BlurText text={t.faq.title_b} />
              </span>
            </h2>
            <p style={{ marginTop: 24, color: "var(--ink-3)", fontSize: 16, lineHeight: 1.6 }}>
              {t.faq.lead}
            </p>
            {/* Fills the corner the heading leaves empty beside a tall list of questions.
                Coloured from --line-strong, the same hairline the questions are ruled with,
                so it reads as part of the same drawing rather than a new element. */}
            <div className="faq-lines">
              {/* No rows/columns/size: it fills the corner it is given and works the grid
                  out from the measured area, so the density holds at any width. */}
              <window.MagnetLines cell={30} lineWidth={2} lineHeight={13}
                                  lineColor="var(--line-strong)" baseAngle={-10} />
            </div>
          </div>
          <div className="faq-list">
            {faqs.map((f, i) => {
              const open = openIdx === i;
              return (
                <div key={i} className={`faq-item${open ? " is-open" : ""}`}>
                  <button type="button" className="faq-q" onClick={() => setOpenIdx(open ? null : i)}
                          aria-expanded={open} aria-controls={`faq-a-${i}`}>
                    {/* The label keeps min-width:0 (in .faq-q__label) so a long question can shrink
                        and wrap: without it the flex item keeps its min-content width and pushes
                        the row wider than the page, which German does at 375px. */}
                    <span className="faq-q__label">{f.q}</span>
                    <span className="faq-q__icon" aria-hidden="true"><Icon name="plus" size={18} /></span>
                  </button>
                  {/* Always mounted: opening and closing is a height transition (grid-template-rows
                      0fr -> 1fr, see .faq-a in main.css) and a node that unmounts cannot animate
                      shut. Closed, it is zero height, clipped, and hidden from assistive tech. */}
                  <div className="faq-a" id={`faq-a-${i}`} aria-hidden={!open}>
                    <div className="faq-a__inner">
                      <div className="faq-a__text">{f.a}</div>
                    </div>
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      </div>
    </section>
  );
};

const App = () => {
  window.PS.useReveal();
  return (
    <>
      <PageBackdrop />
      <Nav current="home" />
      <Level at="above"><HomeHero /><Marquee /></Level>
      <Level at="below"><StatsRow /><ValuesSection /></Level>
      <Level at="above"><FeaturedImageBlock /><ServicesPreview /></Level>
      <Level at="below"><TeamPreview /></Level>
      <Level at="above"><FAQSection /></Level>
      <Level at="below"><Footer /></Level>
      <BookingModal />
    </>
  );
};
window.HomeApp = App;
