function ProjectsGallery({ sansFont, cursiveFont }) {
  const projects = [
    { id: "google", title: "Google", place: "India", year: "2024" },
    { id: "dassault", title: "Dassault Aviation", place: "India", year: "2025" },
    { id: "biotech", title: "Dept. of Biotechnology", place: "India", year: "2025" },
    { id: "nestle", title: "Nestlé", place: "India", year: "2024" },
    { id: "urbanclap", title: "Urban Company", place: "India", year: "2024" },
    { id: "farmhouse", title: "Farm House", place: "India", year: "2024" },
    { id: "homeoffice", title: "Home Office", place: "India", year: "2024" },
    { id: "residence", title: "Private Residence", place: "India", year: "2024" },
  ];

  const n = projects.length;
  const [focus, setFocus] = React.useState(0);
  const [paused, setPaused] = React.useState(false);
  const drag = React.useRef({ on: false, x: 0, moved: false });

  // Panel geometry (px, base scale)
  const W = 240;   // panel width
  const H = 340;   // panel height
  const A = 58;    // fold angle for concertina panels (deg)
  const FA = 14;   // slight angle kept on the focused (open) panel — reads less flat/static

  // Shortest signed distance on the ring, e.g. for n=6: -2..3 collapsed to -2..2(+opposite)
  const ringOffset = React.useCallback((i, f) => {
    let d = i - f;
    while (d > n / 2) d -= n;
    while (d < -n / 2) d += n;
    return d;
  }, [n]);

  const maxVisible = Math.floor((n - 1) / 2); // panels shown each side of focus

  // Layout: a LOOPING hinged ribbon. Panels are placed by their ring distance
  // from the focused panel, so the open card always has wings on BOTH sides and
  // stays dead-centre — no empty gap at the ends.
  const layout = React.useMemo(() => {
    const rad = (d) => (d * Math.PI) / 180;
    const out = new Array(n);
    const fa = -FA;
    const fLeft = { x: -W / 2, z: 0 };

    // focused panel
    for (let i = 0; i < n; i++) {
      if (ringOffset(i, focus) === 0) out[i] = { x: fLeft.x, z: fLeft.z, a: fa, off: 0 };
    }

    // walk right (ring distance +k)
    let edge = { x: fLeft.x + W * Math.cos(rad(fa)), z: fLeft.z - W * Math.sin(rad(fa)) };
    for (let k = 1; k <= maxVisible; k++) {
      const a = k % 2 === 1 ? A : -A;
      const left = { ...edge };
      const idx = ((focus + k) % n + n) % n;
      if (out[idx] === undefined) out[idx] = { x: left.x, z: left.z, a, off: k };
      edge = { x: left.x + W * Math.cos(rad(a)), z: left.z - W * Math.sin(rad(a)) };
    }

    // walk left (ring distance -k), mirrored
    let ledge = { ...fLeft };
    for (let k = 1; k <= maxVisible; k++) {
      const a = k % 2 === 1 ? -A : A;
      const left = { x: ledge.x - W * Math.cos(rad(a)), z: ledge.z + W * Math.sin(rad(a)) };
      const idx = ((focus - k) % n + n) % n;
      if (out[idx] === undefined) out[idx] = { x: left.x, z: left.z, a, off: -k };
      ledge = { ...left };
    }

    // any leftover (the panel diametrically opposite) — tuck it far behind, hidden
    for (let i = 0; i < n; i++) {
      if (out[i] === undefined) out[i] = { x: 0, z: -900, a: 0, off: maxVisible + 1 };
    }

    // re-center on the focused panel's horizontal centre
    const fc = out.find((o) => o.off === 0);
    const centerX = fc.x + (W / 2) * Math.cos(rad(fc.a));
    for (let i = 0; i < n; i++) out[i].x -= centerX;
    return out;
  }, [focus, n, ringOffset, maxVisible]);

  const go = React.useCallback((dir) => {
    setFocus((f) => ((f + dir) % n + n) % n);
  }, [n]);

  // Autoplay — loops seamlessly in one direction
  // Scroll-driven: advance through projects as the section scrolls past.
  const sectionRef = React.useRef(null);
  React.useEffect(() => {
    const el = sectionRef.current;
    if (!el) return;
    let raf = 0;
    const update = () => {
      raf = 0;
      const rect = el.getBoundingClientRect();
      const vh = window.innerHeight || 1;
      // Progress from when the section top reaches the top of the viewport
      // to when its bottom reaches the bottom — mapped across all panels.
      const span = rect.height - vh;
      if (span <= 0) return;
      const p = Math.min(1, Math.max(0, -rect.top / span));
      const idx = Math.round(p * (n - 1));
      setFocus((f) => (idx !== f ? idx : f));
    };
    const onScroll = () => { if (!raf) raf = requestAnimationFrame(update); };
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    update();
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, [n]);

  React.useEffect(() => {
    const onKey = (e) => {
      if (e.key === "ArrowLeft") go(-1);
      else if (e.key === "ArrowRight") go(1);
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [go]);

  // Responsive scale of the whole stage
  const stageRef = React.useRef(null);
  const [scale, setScale] = React.useState(1);
  React.useEffect(() => {
    const el = stageRef.current;
    if (!el) return;
    const ro = new ResizeObserver(() => {
      const w = el.clientWidth;
      setScale(Math.min(1.15, Math.max(0.5, w / 1200)));
    });
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  const onPointerDown = (e) => { drag.current = { on: true, x: e.clientX, moved: false }; setPaused(true); };
  const onPointerMove = (e) => {
    if (!drag.current.on) return;
    const dx = e.clientX - drag.current.x;
    if (Math.abs(dx) > 55 && !drag.current.moved) { drag.current.moved = true; go(dx < 0 ? 1 : -1); }
  };
  const onPointerUp = () => { drag.current.on = false; setTimeout(() => setPaused(false), 4000); };

  return (
    <section
      ref={sectionRef}
      id="projects"
      className="relative bg-black text-white py-24 md:py-36 grain overflow-hidden"
      style={{ "--grain-opacity": 0.09 }}
    >
      <div className="px-5 md:px-10">
        <div className="flex items-end justify-between gap-6 mb-12 md:mb-16">
          <div>
            <div className="flex items-center gap-3 mb-5">
              <span className="w-10 h-px bg-white/40" />
              <span className="uppercase tracking-[0.35em] text-[11px] text-white/50">02 — In situ</span>
            </div>
            <h2 className="hero-title" style={{ fontFamily: sansFont, fontSize: "clamp(48px, 8vw, 140px)" }}>
              Offices, boardrooms,{" "}
              <span className="inline-block italic" style={{ fontFamily: cursiveFont, fontStyle: "normal", fontSize: "1.25em", transform: "rotate(-3deg)" }}>quiet</span>{" "}
              rooms.
            </h2>
          </div>
          <div className="hidden md:flex items-center gap-6 text-[11px] uppercase tracking-[0.28em] text-white/50">
            <span>2022 — 2025</span>
            <span>{String(n).padStart(2, "0")} projects</span>
          </div>
        </div>
      </div>

      {/* Folding-screen stage */}
      <div
        ref={stageRef}
        className="relative w-full select-none"
        style={{ perspective: "1500px", perspectiveOrigin: "50% 45%", height: "min(60vh, 560px)" }}
        onMouseEnter={() => setPaused(true)}
        onMouseLeave={() => { if (!drag.current.on) setPaused(false); }}
        onPointerDown={onPointerDown}
        onPointerMove={onPointerMove}
        onPointerUp={onPointerUp}
        onPointerLeave={onPointerUp}
      >
        <div
          className="absolute left-1/2 top-1/2"
          style={{ transformStyle: "preserve-3d", transform: `translate(-50%,-50%) scale(${scale})` }}
        >
          {projects.map((p, i) => {
            const L = layout[i] || { x: 0, z: 0, a: 0, off: 0 };
            const off = L.off || 0;
            const dist = Math.abs(off);
            const isFocus = off === 0;
            const hidden = dist > maxVisible;
            const dark = isFocus ? 0 : Math.min(0.62, 0.18 + dist * 0.16);
            return (
              <div
                key={p.id}
                onClick={() => !isFocus && setFocus(i)}
                className="absolute transition-all duration-[850ms]"
                style={{
                  width: W,
                  height: H,
                  top: -H / 2,
                  left: 0,
                  transformOrigin: "0% 50%",
                  transform: `translate3d(${L.x}px, 0, ${L.z}px) rotateY(${L.a}deg)`,
                  transitionTimingFunction: "cubic-bezier(.4,.05,.2,1)",
                  zIndex: 200 + Math.round(L.z),
                  opacity: hidden ? 0 : 1,
                  pointerEvents: hidden ? "none" : "auto",
                  cursor: isFocus ? "default" : "pointer",
                }}
              >
                <div
                  className="relative w-full h-full overflow-hidden bg-neutral-900"
                  style={{
                    boxShadow: isFocus ? "0 50px 90px -30px rgba(0,0,0,0.85)" : "0 30px 60px -30px rgba(0,0,0,0.7)",
                    outline: "1px solid rgba(255,255,255,0.08)",
                  }}
                >
                  <image-slot
                    id={p.id}
                    src={`assets/projects/${p.id}.webp`}
                    shape="rect"
                    fit="cover"
                    placeholder="Drop project photo"
                    style={{ position: "absolute", inset: 0, width: "100%", height: "100%" }}
                  ></image-slot>

                  {/* edge seam shading — sells the fold */}
                  <div
                    className="absolute inset-y-0 left-0 w-1/2 pointer-events-none transition-opacity duration-[850ms]"
                    style={{ background: "linear-gradient(to right, rgba(0,0,0,0.5), transparent)", opacity: isFocus ? 0 : (L.a > 0 ? 0.9 : 0) }}
                  ></div>
                  <div
                    className="absolute inset-y-0 right-0 w-1/2 pointer-events-none transition-opacity duration-[850ms]"
                    style={{ background: "linear-gradient(to left, rgba(0,0,0,0.5), transparent)", opacity: isFocus ? 0 : (L.a < 0 ? 0.9 : 0) }}
                  ></div>

                  {/* overall darken for depth */}
                  <div className="absolute inset-0 bg-black pointer-events-none transition-opacity duration-[850ms]" style={{ opacity: dark }}></div>

                  {/* caption on focus */}
                  <div
                    className="absolute inset-x-0 bottom-0 p-5 pointer-events-none transition-opacity duration-500"
                    style={{ opacity: isFocus ? 1 : 0, background: "linear-gradient(to top, rgba(0,0,0,0.72), transparent)" }}
                  >
                    <div className="text-[10px] uppercase tracking-[0.35em] text-white/60">Project {String(i + 1).padStart(2, "0")} — {p.year}</div>
                    <div className="mt-1.5 text-[22px] md:text-[26px] leading-tight" style={{ letterSpacing: "-0.02em" }}>{p.title}</div>
                    <div className="text-[12px] text-white/60 mt-0.5">{p.place}</div>
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      </div>

      {/* Controls */}
      <div className="px-5 md:px-10 mt-10 md:mt-14">
        <div className="flex items-center justify-between gap-6">
          <div className="flex items-center gap-3">
            <button aria-label="Previous" onClick={() => go(-1)} className="w-11 h-11 rounded-full ring-1 ring-white/20 hover:ring-white/60 hover:bg-white/5 transition flex items-center justify-center">
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4"><path d="M15 18l-6-6 6-6" /></svg>
            </button>
            <button aria-label="Next" onClick={() => go(1)} className="w-11 h-11 rounded-full ring-1 ring-white/20 hover:ring-white/60 hover:bg-white/5 transition flex items-center justify-center">
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4"><path d="M9 6l6 6-6 6" /></svg>
            </button>
          </div>
          <div className="flex items-center gap-2.5">
            {projects.map((p, i) => (
              <button key={p.id} aria-label={`Go to ${p.title}`} onClick={() => setFocus(i)} className="h-1.5 rounded-full transition-all duration-500" style={{ width: i === focus ? 28 : 8, background: i === focus ? "rgba(255,255,255,0.9)" : "rgba(255,255,255,0.25)" }}></button>
            ))}
          </div>
          <div className="hidden md:block text-[11px] uppercase tracking-[0.28em] text-white/40">Drag · or use ← →</div>
        </div>
      </div>

      {/* Quote */}
      <div className="px-5 md:px-10">
        <div className="mt-24 md:mt-32 max-w-4xl">
          <div className="text-[11px] uppercase tracking-[0.35em] text-white/50 mb-6">— On restraint</div>
          <p className="text-[28px] md:text-[44px] leading-[1.15]" style={{ fontFamily: sansFont, letterSpacing: "-0.02em", fontWeight: 400 }}>
            "A chair is not a shape. It is a{" "}
            <span className="italic" style={{ fontFamily: cursiveFont, fontStyle: "normal", fontSize: "1.2em" }}>posture</span>{" "}
            held in wood — the afternoon a room is allowed to rest."
          </p>
          <div className="mt-8 flex items-center gap-4 text-[12px] uppercase tracking-[0.28em] text-white/50">
            <span className="w-10 h-px bg-white/30" />
            <span>Lina Oliveira · Co-founder</span>
          </div>
        </div>
      </div>
    </section>
  );
}

const Projects = ProjectsGallery;
Object.assign(window, { Projects, ProjectsGallery });
