// Learn — "Backsplash" edition.
// The page is a marble backsplash. Each article is a stone tile set in warm grout.
// Filters re-tile the wall based on selected topic.

const { useState: useLS, useMemo: useLM } = React;

// ============================================================
// Data — softened copy, topic-relevant imagery hints
// ============================================================
const ARTICLES = [
  {
    id: "marble-restoration",
    slug: "complete-guide-to-marble-countertop-restoration-from-etch-removal-to-polishing",
    category: "Stone Science",
    title: "Marble Countertop Restoration: Complete Guide to Etch Removal and Polishing.",
    kicker: "A step-by-step look at how a dull, etched marble countertop is brought back to a like-new finish — and when it's worth the work.",
    pullquote: "Etching isn't a stain — it's a tiny chemical burn on the surface. The good news is it's fixable.",
    author: "Thomas Chow",
    date: "Jan 15",
    read: "7 min read",
    img: "assets/work-clear-1.jpg",
    topic: "Restoration",
    finish: "polished",
  },
  {
    id: "stoneguard-everything",
    slug: "everything-you-need-to-know-about-stoneguard",
    category: "StoneGuard",
    title: "Everything You Need to Know About StoneGuard.",
    kicker: "What the film is, how it's made, what it protects against, and why it's different from sealers and epoxies.",
    pullquote: "Think of it as an invisible layer of armor, sized for your exact countertop, installed in a day.",
    author: "Thomas Chow",
    date: "Oct 21, 2025",
    read: "3 min read",
    img: "assets/work-satin-3.png",
    topic: "StoneGuard 101",
    finish: "dark",
  },
  {
    id: "why-marble-stains",
    slug: "why-marble-stains-and-how-to-stop-it",
    category: "Stone Science",
    title: "Why Marble Stains (and How to Stop It): The Science Behind Protecting Your Countertops.",
    kicker: "Why wine, lemon, and tomato leave their mark on marble — and what actually stops them, explained in plain language.",
    pullquote: "Marble is made of the same stuff as antacid tablets. That's why acidic food eats into it.",
    author: "Thomas Chow",
    date: "Aug 20, 2025",
    read: "5 min read",
    img: "assets/work-clear-3.jpg",
    topic: "How marble works",
    finish: "light",
  },
  {
    id: "protect-with-power",
    slug: "protect-your-stone-countertops-with-the-power-of-stoneguard",
    category: "StoneGuard",
    title: "Protect Your Stone Countertops with the Power of StoneGuard.",
    kicker: "What a professional StoneGuard install actually looks like — from first visit and templating, to cure time, to the final hand-off.",
    pullquote: "One day of work. A lifetime of worry-free cooking, spilling, and living.",
    author: "M&T Team",
    date: "Oct 20, 2025",
    read: "3 min read",
    img: "assets/work-clear-4.jpg",
    topic: "Install day",
    finish: "dark",
  },
  {
    id: "benefits-of-mt",
    slug: "the-benefits-of-using-mt-surface-protector-to-install-stoneguard",
    category: "StoneGuard",
    title: "The Benefits of Using M&T Surface Protectors to Install StoneGuard.",
    kicker: "Why picking the right certified installer matters as much as the film itself — and what sets M&T apart on jobs from Manhattan to Greenwich.",
    pullquote: "Same two guys on every job — so the work stays as careful as the day we started.",
    author: "M&T Team",
    date: "Oct 16, 2025",
    read: "4 min read",
    img: "assets/service-1.jpg",
    topic: "Our approach",
    finish: "light",
  },
  {
    id: "lifesaver-marble",
    slug: "how-stoneguard-could-be-a-lifesaver-for-marble-stone-owners",
    category: "StoneGuard",
    title: "How StoneGuard Could Be a Lifesaver for Marble Stone Owners.",
    kicker: "The short read: why marble owners keep telling us StoneGuard is the single best decision they made after install.",
    pullquote: "Same countertop, same kids, same wine nights. One looks brand new. One doesn't.",
    author: "M&T Team",
    date: "Oct 24, 2023",
    read: "2 min read",
    img: "assets/work-clear-6.jpg",
    topic: "Real homes",
    finish: "polished",
  },
];

const CATEGORIES = ["All", "Stone Science", "StoneGuard"];


// ============================================================
// STONEYARD — pendulum-physics slab browser
// ============================================================
// The Learn page is a stoneyard. An overhead hoist carries slabs
// (articles) along a steel I-beam track. A joystick drives the
// carriage; each slab hangs from its clamp on a cable and swings
// with real pendulum physics. Infinite loop. Click a slab to read.
// ============================================================

// ------------------------------------------------------------
// useAnimationLoop — run an RAF loop that calls cb with dt seconds
// ------------------------------------------------------------
function useAnimationLoop(cb, deps = []) {
  const cbRef = React.useRef(cb);
  cbRef.current = cb;
  React.useEffect(() => {
    let raf, last = performance.now();
    const tick = (now) => {
      const dt = Math.min(0.05, (now - last) / 1000); // clamp to 50ms for tab-switches
      last = now;
      cbRef.current(dt);
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, deps);
}

// ------------------------------------------------------------
// SlabFace — the visible article slab (marble surface + content)
// ------------------------------------------------------------
function SlabFace({ article, swingAngle, isCenter, onOpen }) {
  const a = article;
  const isDark = a.finish === "dark" || a.finish === "polished";

  return (
    <div
      onClick={isCenter ? onOpen : undefined}
      style={{
        position: "relative",
        width: "100%",
        height: "100%",
        background: "#141412",
        cursor: isCenter ? "pointer" : "default",
        overflow: "hidden",
        boxShadow: `
          0 0 0 2px rgba(20,16,12,0.8),
          0 40px 80px -20px rgba(0,0,0,0.75),
          0 20px 40px -10px rgba(0,0,0,0.5),
          inset 0 2px 0 rgba(255,255,255,0.05),
          inset 0 -2px 0 rgba(0,0,0,0.6)
        `,
      }}
    >
      {/* Stone image background */}
      <img src={a.img} alt="" style={{
        position: "absolute", inset: 0,
        width: "100%", height: "100%", objectFit: "cover",
        filter: isDark ? "brightness(0.48) saturate(0.85)" : "brightness(0.82)",
      }}/>

      {/* Overlay gradient for text legibility */}
      <div style={{
        position: "absolute", inset: 0,
        background: isDark
          ? "linear-gradient(180deg, rgba(10,9,8,0.35) 0%, rgba(10,9,8,0.15) 40%, rgba(10,9,8,0.85) 100%)"
          : "linear-gradient(180deg, rgba(10,9,8,0.6) 0%, rgba(10,9,8,0.35) 40%, rgba(10,9,8,0.88) 100%)",
      }}/>

      {/* Polish reflection band — subtle sheen, shifts with swing */}
      <div style={{
        position: "absolute", inset: 0, pointerEvents: "none",
        background: `linear-gradient(${118 + swingAngle * 2}deg,
          rgba(255,255,255,0.0) 30%,
          rgba(255,255,255,0.08) 50%,
          rgba(255,255,255,0.0) 70%)`,
        mixBlendMode: "soft-light",
      }}/>

      {/* Centered content — title, kicker, pullquote only */}
      <div style={{
        position: "absolute", inset: 0,
        padding: "44px 56px",
        display: "flex", flexDirection: "column",
        justifyContent: "center", alignItems: "center",
        textAlign: "center",
        zIndex: 3,
      }}>
        <div className="serif" style={{
          fontSize: "clamp(32px, 3.4vw, 54px)",
          lineHeight: 1.04,
          letterSpacing: "-0.025em",
          color: "#F5F2ED",
          textShadow: "0 2px 24px rgba(0,0,0,0.6), 0 0 1px rgba(255,255,255,0.08)",
          marginBottom: 28,
          maxWidth: "92%",
        }}>
          {a.title}
        </div>

        <div className="serif" style={{
          fontSize: "clamp(15px, 1.2vw, 18px)",
          lineHeight: 1.45,
          fontStyle: "italic",
          color: "rgba(245,242,237,0.92)",
          maxWidth: 560,
          marginBottom: 24,
        }}>
          {a.pullquote}
        </div>

        {isCenter && (
          <div className="mono" style={{
            fontSize: 10, letterSpacing: "0.24em", textTransform: "uppercase",
            color: "#E8D4A8",
            padding: "10px 18px",
            border: "1px solid rgba(232,212,168,0.55)",
            display: "inline-flex", alignItems: "center", gap: 8,
            background: "rgba(20,16,12,0.35)",
            backdropFilter: "blur(4px)",
          }}>
            Click to Read
            <svg width="10" height="10" viewBox="0 0 10 10" fill="none">
              <path d="M1 5h8m0 0L5 1m4 4L5 9" stroke="currentColor" strokeWidth="1.1"/>
            </svg>
          </div>
        )}
      </div>
    </div>
  );
}

// ------------------------------------------------------------
// Joystick — just the circular base + stick. No panel, no counter.
// ------------------------------------------------------------
function Joystick({ onChange }) {
  const [dx, setDx] = React.useState(0); // -1..1
  const draggingRef = React.useRef(false);
  const rectRef = React.useRef(null);
  const baseRef = React.useRef(null);
  const [isDragging, setIsDragging] = React.useState(false);

  // Propagate value up
  React.useEffect(() => { onChange(dx); }, [dx, onChange]);

  // Keyboard
  React.useEffect(() => {
    const keys = { ArrowLeft: -1, ArrowRight: 1, a: -1, d: 1 };
    const held = new Set();
    const tick = () => {
      let v = 0;
      if (held.has('ArrowLeft')  || held.has('a')) v -= 1;
      if (held.has('ArrowRight') || held.has('d')) v += 1;
      setDx(v);
    };
    const down = (e) => { if (e.key in keys) { held.add(e.key); tick(); e.preventDefault(); } };
    const up   = (e) => { if (e.key in keys) { held.delete(e.key); tick(); } };
    window.addEventListener('keydown', down);
    window.addEventListener('keyup', up);
    return () => {
      window.removeEventListener('keydown', down);
      window.removeEventListener('keyup', up);
    };
  }, []);

  // Pointer handlers — drag the orange grip
  const onDown = (e) => {
    draggingRef.current = true;
    setIsDragging(true);
    rectRef.current = baseRef.current.getBoundingClientRect();
    onMove(e);
    e.currentTarget.setPointerCapture?.(e.pointerId);
  };
  const onMove = (e) => {
    if (!draggingRef.current || !rectRef.current) return;
    const r = rectRef.current;
    const cx = r.left + r.width / 2;
    const nx = (e.clientX - cx) / (r.width / 2);
    setDx(Math.max(-1, Math.min(1, nx)));
  };
  const onUp = () => {
    draggingRef.current = false;
    setIsDragging(false);
    setDx(0);
  };

  const stickAngle = dx * 32; // tilt degrees
  const ballX = dx * 42;      // px shift of the ball

  return (
    <div
      ref={baseRef}
      onPointerDown={onDown}
      onPointerMove={onMove}
      onPointerUp={onUp}
      onPointerCancel={onUp}
      style={{
        position: "relative",
        width: 168,
        height: 110, // tall enough to cover the ball which sits above the base
        cursor: isDragging ? "grabbing" : "grab",
        touchAction: "none",
        userSelect: "none",
      }}
    >
      {/* Inner anchor — everything below positions relative to this, at the bottom */}
      <div style={{ position: "absolute", left: 0, right: 0, bottom: 0, height: 62 }}>
      {/* Drop shadow pool on ground beneath base — cements it as a 3D object */}
      <div style={{
        position: "absolute",
        left: "50%", bottom: -5, transform: "translateX(-50%)",
        width: 168, height: 11,
        background: "radial-gradient(ellipse at center, rgba(20,16,12,0.35) 0%, transparent 75%)",
        pointerEvents: "none",
      }}/>

      {/* Circular base — multi-layered for 3D read */}
      {/* Outer ring (darkest, the beveled edge) */}
      <div style={{
        position: "absolute",
        left: "50%", top: "50%", transform: "translate(-50%,-50%)",
        width: 160, height: 50,
        borderRadius: "50%",
        background: `
          radial-gradient(ellipse at 50% 20%,
            #5A5451 0%,
            #2A2724 45%,
            #0A0908 85%,
            #050403 100%)
        `,
        boxShadow: `
          0 8px 18px rgba(0,0,0,0.45),
          0 2px 5px rgba(0,0,0,0.35),
          inset 0 -3px 5px rgba(0,0,0,0.65),
          inset 0 2px 2px rgba(255,255,255,0.1)
        `,
      }}/>

      {/* Middle ring (dark metal, defines the rim) */}
      <div style={{
        position: "absolute",
        left: "50%", top: "50%", transform: "translate(-50%,-50%)",
        width: 130, height: 36,
        borderRadius: "50%",
        background: `
          radial-gradient(ellipse at 50% 30%,
            #2A2724 0%,
            #0A0908 70%,
            #050403 100%)
        `,
        boxShadow: `
          inset 0 3px 6px rgba(0,0,0,0.85),
          inset 0 -1px 0 rgba(255,255,255,0.08)
        `,
      }}/>

      {/* Inner dark well where the stick emerges — gives depth */}
      <div style={{
        position: "absolute",
        left: "50%", top: "50%", transform: "translate(-50%,-50%)",
        width: 44, height: 14,
        borderRadius: "50%",
        background: "radial-gradient(ellipse at 50% 30%, #0a0604 0%, #000000 100%)",
        boxShadow: "inset 0 2px 4px rgba(0,0,0,0.95)",
      }}/>

      {/* L / R labels etched on the base */}
      <div className="mono" style={{
        position: "absolute",
        left: 18, top: "50%", transform: "translateY(-50%)",
        fontSize: 9, letterSpacing: "0.14em", fontWeight: 700,
        color: "rgba(200,195,190,0.45)",
        textShadow: "0 1px 1px rgba(0,0,0,0.85)",
        pointerEvents: "none",
      }}>L</div>
      <div className="mono" style={{
        position: "absolute",
        right: 18, top: "50%", transform: "translateY(-50%)",
        fontSize: 9, letterSpacing: "0.14em", fontWeight: 700,
        color: "rgba(200,195,190,0.45)",
        textShadow: "0 1px 1px rgba(0,0,0,0.85)",
        pointerEvents: "none",
      }}>R</div>

      {/* The stick — tilts left/right */}
      <div style={{
        position: "absolute",
        left: "50%", top: "50%",
        transform: `translate(calc(-50% + ${ballX}px), -100%) rotate(${stickAngle}deg)`,
        transformOrigin: "50% 100%",
        transition: isDragging ? "none" : "transform 0.5s cubic-bezier(.2,.7,.2,1)",
        pointerEvents: "none",
      }}>
        {/* Shaft with strong side shading */}
        <div style={{
          width: 8, height: 38,
          background: "linear-gradient(90deg, #0A0908 0%, #3E3936 35%, #7A7470 50%, #3E3936 65%, #0A0908 100%)",
          borderRadius: 2,
          boxShadow: "0 3px 5px rgba(0,0,0,0.5)",
        }}/>
      </div>

      {/* The orange grip ball — sits atop the shaft, same tilt */}
      <div style={{
        position: "absolute",
        left: "50%", top: "50%",
        transform: `translate(calc(-50% + ${ballX + Math.sin(stickAngle * Math.PI / 180) * 38}px), calc(-100% + ${-Math.cos(stickAngle * Math.PI / 180) * 38 + 2}px))`,
        transition: isDragging ? "none" : "transform 0.5s cubic-bezier(.2,.7,.2,1)",
        pointerEvents: "none",
      }}>
        <div style={{
          width: 34, height: 34, borderRadius: "50%",
          background: "radial-gradient(circle at 32% 25%, #d85a5c 0%, #a82628 20%, #6B1416 55%, #2a0608 100%)",
          boxShadow: `
            0 5px 12px rgba(0,0,0,0.45),
            0 2px 3px rgba(0,0,0,0.35),
            inset 0 -3px 6px rgba(30,4,6,0.6),
            inset 0 2px 2px rgba(255,200,200,0.35)
          `,
          border: "1px solid #1a0404",
        }}/>
        {/* Base ring on grip — attaches it to the shaft visually */}
        <div style={{
          position: "absolute", bottom: -2, left: "50%",
          transform: "translateX(-50%)",
          width: 30, height: 4,
          background: "linear-gradient(180deg, #1c0506 0%, #060102 100%)",
          borderRadius: 3,
          boxShadow: "0 1px 2px rgba(0,0,0,0.6)",
        }}/>
      </div>
      </div>
    </div>
  );
}

// ------------------------------------------------------------
// Stoneyard — the full scene + physics
// ------------------------------------------------------------
function Stoneyard() {
  // Normalized carriage position along the virtual track (in "slab units").
  // Each slab is 1 unit apart. We loop: pos mod N.
  const [carriagePos, setCarriagePos] = React.useState(0);
  // Slab swing angle (degrees, +right, -left) — pendulum on cable
  const [swingAngle, setSwingAngle] = React.useState(0);
  // Joystick value (-1..1). Force input.
  const stickRef = React.useRef(0);
  // Internal velocity state refs (don't retrigger renders)
  const posRef = React.useRef(0);
  const velRef = React.useRef(0);        // carriage velocity (slab-units/sec)
  const swingRef = React.useRef(0);      // swing angle (deg)
  const swingVelRef = React.useRef(0);   // swing angular velocity (deg/sec)
  const lastCarriageVel = React.useRef(0);
  // Modal state
  const [openArticle, setOpenArticle] = React.useState(null);

  const N = ARTICLES.length;

  // Animation loop — Euler integration of coupled hoist + pendulum
  useAnimationLoop((dt) => {
    if (openArticle) return; // pause physics while reading

    const stick = stickRef.current;

    // ─── Carriage (hoist) dynamics ──────────────────────────────
    // Target velocity proportional to stick. Spring toward target.
    // Heavy machinery: slow to accelerate AND slow to stop.
    const MAX_VEL = 0.85;               // slab-units per second at full stick
    const ACCEL = 2.4;                  // how fast velocity approaches target
    const DECEL = 3.2;                  // how fast velocity approaches 0 when released
    const targetVel = stick * MAX_VEL;
    const diff = targetVel - velRef.current;
    const rate = (Math.abs(stick) > 0.02) ? ACCEL : DECEL;
    velRef.current += diff * Math.min(1, rate * dt);

    // Integrate position
    posRef.current += velRef.current * dt;

    // Compute carriage acceleration (for pendulum forcing)
    const carriageAccel = (velRef.current - lastCarriageVel.current) / Math.max(dt, 1e-4);
    lastCarriageVel.current = velRef.current;

    // ─── Pendulum (slab) dynamics ───────────────────────────────
    // θ'' + 2ζω θ' + ω² θ = -(accel/L) * (180/π)  (linearized pendulum, forced)
    // We work in degrees throughout. Tune by ear.
    const OMEGA = 2.2;   // natural freq (rad/s) — lower = slower swing
    const ZETA = 0.12;   // damping ratio — lower = swings longer
    const FORCING_GAIN = 38; // how strongly carriage accel kicks the slab

    // Spring-back to vertical + damping + carriage forcing
    const angAccel =
      - (OMEGA * OMEGA) * swingRef.current
      - (2 * ZETA * OMEGA) * swingVelRef.current
      - FORCING_GAIN * carriageAccel;

    swingVelRef.current += angAccel * dt;
    swingRef.current += swingVelRef.current * dt;

    // Clamp to prevent runaway
    if (Math.abs(swingRef.current) > 35) {
      swingRef.current = Math.sign(swingRef.current) * 35;
      swingVelRef.current *= 0.4;
    }

    // Push to state (at ~60fps — cheap enough)
    setCarriagePos(posRef.current);
    setSwingAngle(swingRef.current);
  }, []);

  // Which slab is "center" — the one the camera is looking at.
  // carriagePos in slab-units; center is the floor of (pos + 0.5) mod N.
  const centerIdx = ((Math.round(posRef.current) % N) + N) % N;

  // Escape key closes modal
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') setOpenArticle(null); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  // Render positions: we show up to 5 slabs around center (center ±2)
  // to keep DOM small while giving peek of neighbors.
  const visible = [];
  for (let offset = -2; offset <= 2; offset++) {
    const slabIdx = ((centerIdx + offset) % N + N) % N;
    // Each slab is drawn at its own track position, minus the camera position
    const targetTrackPos = Math.round(posRef.current) + offset; // whole-slab positions nearest the camera
    const slabX = targetTrackPos - posRef.current;              // -2..2 range in slab-units
    visible.push({ slabIdx, slabX, key: `${slabIdx}-${targetTrackPos}` });
  }

  // Activity — warm glow intensity, based on current carriage velocity.
  // 0 when idle, ramps to 1 as machine works.
  const activity = Math.min(1, Math.abs(velRef.current) / 0.7);

  // Constants — 4:3 landscape slabs, smaller overall system
  const SLAB_W_VW = 34;                 // vw slab width (original size)
  const SLAB_H_VW = SLAB_W_VW * 0.75;   // 4:3 = 25.5vw tall
  const SLAB_GAP_VW = 40;               // vw between centers — wider than slabs so no overlap
  const CABLE_LEN = 6.5;                // vh cable length from beam to slab top (unchanged)

  return (
    <section style={{
      position: "relative",
      padding: "0 0 200px",
      background: "var(--bone)",
      overflow: "hidden",
      minHeight: "1140px",
    }}>
      {/* ─── Background texture (very subtle stone grain on bone) ─── */}
      <svg style={{
        position: "absolute", inset: 0, width: "100%", height: "100%",
        opacity: 0.018, pointerEvents: "none", mixBlendMode: "multiply",
      }}>
        <filter id="stone-grain-y">
          <feTurbulence type="fractalNoise" baseFrequency="0.82" numOctaves="2" seed="5"/>
          <feColorMatrix values="0 0 0 0 0.3  0 0 0 0 0.25  0 0 0 0 0.2  0 0 0 1 0"/>
        </filter>
        <rect width="100%" height="100%" filter="url(#stone-grain-y)"/>
      </svg>

      {/* (floor shadow removed to keep background uniform) */}

      {/* ─── Overhead I-beam track — CURVED (arcs up at each side like a 3D rail) ─── */}
      <svg
        viewBox="-200 0 1400 180"
        preserveAspectRatio="none"
        style={{
          position: "absolute",
          top: 200,
          left: "-10%",
          width: "120%",
          height: 140,
          pointerEvents: "none",
          zIndex: 15,
          overflow: "visible",
        }}
      >
        <defs>
          {/* Beam metal gradient — same charcoal as straight beam */}
          <linearGradient id="beam-metal" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%"  stopColor="#1A1816"/>
            <stop offset="45%" stopColor="#0A0908"/>
            <stop offset="55%" stopColor="#2A2724"/>
            <stop offset="100%" stopColor="#0A0908"/>
          </linearGradient>
          {/* Soft drop-shadow for the beam */}
          <filter id="beam-shadow" x="-5%" y="-50%" width="110%" height="200%">
            <feGaussianBlur in="SourceAlpha" stdDeviation="3"/>
            <feOffset dy="4"/>
            <feComponentTransfer><feFuncA type="linear" slope="0.45"/></feComponentTransfer>
            <feMerge>
              <feMergeNode/>
              <feMergeNode in="SourceGraphic"/>
            </feMerge>
          </filter>
        </defs>

        {/* Beam body — extends FAR past viewport so curves never clip at edges */}
        <path
          d="M -200 -80 C 200 105 200 105 500 105 C 800 105 800 105 1200 -80"
          stroke="url(#beam-metal)"
          strokeWidth="24"
          fill="none"
          filter="url(#beam-shadow)"
          strokeLinecap="butt"
        />
        {/* Top highlight line on the beam */}
        <path
          d="M -200 -80 C 200 105 200 105 500 105 C 800 105 800 105 1200 -80"
          stroke="rgba(255,255,255,0.07)"
          strokeWidth="1.2"
          fill="none"
          transform="translate(0, -12)"
        />
        {/* Bottom deep-shadow line */}
        <path
          d="M -200 -80 C 200 105 200 105 500 105 C 800 105 800 105 1200 -80"
          stroke="rgba(0,0,0,0.9)"
          strokeWidth="1.2"
          fill="none"
          transform="translate(0, 12)"
        />
        {/* Rivets — small dark circles marching along the beam's centerline */}
        {Array.from({ length: 22 }).map((_, i) => {
          const t = i / 21; // 0..1 along curve
          const x = -200 + 1400 * t;
          // match the parabolic approximation of the beam curve vertical
          // beam goes y=-80 at t=0, peak y=105 at t=0.5, y=-80 at t=1
          const y = -80 + 185 * 4 * t * (1 - t);
          return (
            <circle
              key={i}
              cx={x}
              cy={y}
              r="1.2"
              fill="rgba(0,0,0,0.7)"
            />
          );
        })}
      </svg>

      {/* ─── Hoist carriage (the moving box on the beam) ─── */}
      <div style={{
        position: "absolute",
        top: 252,
        left: "50%",
        transform: `translateX(calc(-50% + ${-swingAngle * 0.08}px))`,  // tiny sway empathy
        width: 78,
        height: 72,
        zIndex: 16,
        pointerEvents: "none",
      }}>
        {/* Wheels on the beam */}
        <div style={{
          position: "absolute", top: 3, left: 9, width: 15, height: 15,
          borderRadius: "50%",
          background: "radial-gradient(circle at 40% 40%, #5A5451 0%, #0A0908 80%)",
          boxShadow: "inset 0 -2px 3px rgba(0,0,0,0.7), 0 2px 3px rgba(0,0,0,0.6)",
          transform: `rotate(${posRef.current * 360}deg)`,
        }}>
          <div style={{
            position: "absolute", inset: 3, borderRadius: "50%",
            background: "#2A2724",
            borderTop: "1px solid rgba(255,255,255,0.1)",
          }}/>
        </div>
        <div style={{
          position: "absolute", top: 3, right: 9, width: 15, height: 15,
          borderRadius: "50%",
          background: "radial-gradient(circle at 40% 40%, #5A5451 0%, #0A0908 80%)",
          boxShadow: "inset 0 -2px 3px rgba(0,0,0,0.7), 0 2px 3px rgba(0,0,0,0.6)",
          transform: `rotate(${posRef.current * 360}deg)`,
        }}>
          <div style={{
            position: "absolute", inset: 3, borderRadius: "50%",
            background: "#2A2724",
            borderTop: "1px solid rgba(255,255,255,0.1)",
          }}/>
        </div>

        {/* Motor housing — taller & beefier for a sturdy industrial look */}
        <div style={{
          position: "absolute", top: 14, left: 0, right: 0, height: 58,
          background: "linear-gradient(180deg, #2A2724 0%, #0A0908 100%)",
          border: "1.5px solid rgba(0,0,0,0.8)",
          boxShadow: "inset 0 1px 0 rgba(255,255,255,0.08), 0 8px 14px rgba(0,0,0,0.5), 0 2px 4px rgba(0,0,0,0.4)",
          overflow: "hidden",
        }}>
          {/* Gold caution stripe — thicker. Stripes get warm glow when active. */}
          <div style={{
            position: "absolute", top: 4, left: 4, right: 4, height: 10,
            background: "repeating-linear-gradient(-45deg, #C9A961 0 7px, #141412 7px 14px)",
            opacity: 0.9,
            boxShadow: activity > 0.02
              ? `0 0 ${2 + activity * 4}px rgba(255,190,100,${activity * 0.7})`
              : "none",
            transition: "box-shadow 0.25s ease-out",
          }}/>
          {/* M&T HOIST stamp — centered on two lines, debossed, glows amber when active */}
          <div className="mono" style={{
            position: "absolute", left: 0, right: 0, top: 22, bottom: 4,
            display: "flex", flexDirection: "column",
            alignItems: "center", justifyContent: "center",
            textAlign: "center",
            letterSpacing: "0.22em", fontWeight: 800, lineHeight: 1.15,
            color: `rgba(${210 + activity * 30},${175 + activity * 45},${120 + activity * 35},${0.6 + activity * 0.35})`,
            textShadow: activity > 0.02
              ? `0 1px 0 rgba(0,0,0,0.85), 0 0 ${2 + activity * 4}px rgba(255,190,100,${activity * 0.85})`
              : "0 1px 0 rgba(0,0,0,0.85)",
            transition: "color 0.25s ease-out, text-shadow 0.25s ease-out",
          }}>
            <div style={{ fontSize: 11 }}>M&amp;T</div>
            <div style={{ fontSize: 8.5, letterSpacing: "0.28em", opacity: 0.85 }}>HOIST</div>
          </div>
          {/* Lower horizontal seam line for detail */}
          <div style={{
            position: "absolute", left: 3, right: 3, bottom: 20, height: 1,
            background: "rgba(0,0,0,0.6)",
            boxShadow: "0 1px 0 rgba(255,255,255,0.05)",
          }}/>
        </div>
      </div>

      {/* ─── Slab rendering — each visible slab with its cable + clamp ─── */}
      <div style={{
        position: "absolute",
        top: 0, left: 0, right: 0, bottom: 0,
        overflow: "hidden",
        pointerEvents: "none",
        zIndex: 3,
      }}>
        {visible.map(({ slabIdx, slabX, key }) => {
          const article = ARTICLES[slabIdx];
          const isCenter = Math.abs(slabX) < 0.5;

          // Swing behavior:
          // The pendulum physics drives `swingAngle` when the carriage accelerates.
          // Each slab gets a smooth fraction of that swing based on distance from center —
          // no hard switch when a slab becomes "center", so you never see a pop.
          // At slabX=0 (dead center) → 100% swing
          // At slabX=±0.5 → ~78% swing (smooth)
          // At slabX=±1   → ~37% swing
          // At slabX=±2   → ~5%  swing (barely tilts when peeking in)
          const swingFalloff = Math.exp(-Math.abs(slabX) * Math.abs(slabX) * 0.75);
          const slabSwing = swingAngle * swingFalloff;

          // Position: horizontal offset in vw, vertical = beam + cable
          const xOffsetVW = slabX * SLAB_GAP_VW;

          // ── 3D arc path, matched to beam curve (re-derived) ──
          // Beam cubic bezier was sampled at slabX=0 and slabX=1 and fit to a parabola:
          //   rail_screen_y(slabX) ≈ 281.7 - 36 * slabX^2
          // Pivot sits on the rail's BOTTOM edge (rail stroke is 24 → +11 offset).
          const absX = Math.abs(slabX);
          const depthScale = Math.max(0.58, 1 - absX * 0.17); // shrink with distance

          const pivotX = `calc(50% + ${xOffsetVW}vw)`;
          const pivotY = 293 - 36 * slabX * slabX; // rail bottom edge, matches curve

          // Cable gets LONGER as slab moves toward the edges — the rail has risen
          // high up there, so the cable needs extra reach to stay attached.
          // Also compensates for depthScale shrinking the visual cable length.
          const cableExtraPx = Math.min(118, absX * absX * 40); // +0 at center, +40 at ±1, +118 at ±2

          return (
            <div key={key} style={{
              position: "absolute",
              left: pivotX, top: pivotY,
              width: 0, height: 0,
              transform: `rotate(${slabSwing}deg) scale(${depthScale})`,
              transformOrigin: "0 0",
              transition: "none",
              willChange: "transform",
              zIndex: isCenter ? 10 : Math.max(1, 8 - Math.round(absX * 2)),
            }}>
              {/* Cable — top starts at the rail's underside, length grows toward edges */}
              <div style={{
                position: "absolute",
                left: -1.5, top: -cableExtraPx,
                width: 3,
                height: `calc(${CABLE_LEN}vh + ${cableExtraPx}px)`,
                background: "linear-gradient(90deg, #0A0908 0%, #3E3936 50%, #0A0908 100%)",
                boxShadow: "0 0 2px rgba(0,0,0,0.5)",
              }}/>

              {/* Clamp head (small pulley/hook above slab) */}
              <div style={{
                position: "absolute",
                left: -14, top: `calc(${CABLE_LEN}vh - 4px)`,
                width: 28, height: 14,
                background: "linear-gradient(180deg, #2A2724 0%, #0A0908 100%)",
                border: "1px solid rgba(0,0,0,0.55)",
                boxShadow: "0 2px 4px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.09)",
              }}/>

              {/* The SLAB itself — 4:3 landscape */}
              <div style={{
                position: "absolute",
                left: `-${SLAB_W_VW / 2}vw`,
                top: `calc(${CABLE_LEN}vh + 12px)`,
                width: `${SLAB_W_VW}vw`,
                height: `${SLAB_H_VW}vw`,
                maxHeight: "420px",
                pointerEvents: isCenter ? "auto" : "none",
                opacity: isCenter ? 1 : 0.48,
                filter: isCenter ? "none" : "brightness(0.92) blur(0.8px)",
                transition: "opacity 0.5s ease, filter 0.5s ease",
              }}>
                <SlabFace
                  article={article}
                  swingAngle={slabSwing}
                  isCenter={isCenter}
                  onOpen={() => setOpenArticle(article)}
                />
              </div>
            </div>
          );
        })}
      </div>

      {/* ─── Joystick HUD — top-center, operator's control pedestal above the beam ─── */}
      <div style={{
        position: "absolute",
        top: 70,
        left: "50%",
        transform: "translateX(-50%)",
        zIndex: 20,
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
      }}>
        <Joystick
          onChange={(v) => { stickRef.current = v; }}
        />

        {/* Simple left-right instruction with arrows */}
        <div style={{
          marginTop: 22,
          display: "flex", alignItems: "center", gap: 16,
        }}>
          {/* Left arrow */}
          <svg width="22" height="14" viewBox="0 0 22 14" fill="none"
               style={{ color: "var(--ink-2)" }}>
            <path d="M21 7H1M1 7l6-6M1 7l6 6" stroke="currentColor" strokeWidth="1.4"
                  strokeLinecap="square" strokeLinejoin="miter"/>
          </svg>

          {/* Middle text */}
          <div className="mono" style={{
            fontSize: 10, letterSpacing: "0.32em", textTransform: "uppercase",
            color: "var(--maroon)",
            fontWeight: 500,
          }}>
            Overhead Gantry Crane
          </div>

          {/* Right arrow */}
          <svg width="22" height="14" viewBox="0 0 22 14" fill="none"
               style={{ color: "var(--ink-2)" }}>
            <path d="M1 7h20m0 0l-6-6m6 6l-6 6" stroke="currentColor" strokeWidth="1.4"
                  strokeLinecap="square" strokeLinejoin="miter"/>
          </svg>
        </div>
      </div>

      {/* ─── Article modal (Notion-style preview) ─── */}
      {openArticle && (
        <ArticleModal
          article={openArticle}
          onClose={() => setOpenArticle(null)}
        />
      )}
    </section>
  );
}

// ------------------------------------------------------------
// ArticleModal — Notion-style centered preview overlay
// ------------------------------------------------------------
function ArticleModal({ article, onClose }) {
  return (
    <div
      onClick={onClose}
      style={{
        position: "fixed",
        inset: 0,
        background: "rgba(10,9,8,0.75)",
        backdropFilter: "blur(8px)",
        WebkitBackdropFilter: "blur(8px)",
        zIndex: 100,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        padding: "40px 20px",
        animation: "modal-fade 0.35s ease",
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          position: "relative",
          width: "min(860px, 100%)",
          maxHeight: "85vh",
          overflow: "auto",
          background: "var(--bone)",
          color: "var(--ink)",
          boxShadow: "0 40px 100px rgba(0,0,0,0.75), 0 0 0 1px rgba(0,0,0,0.5)",
          animation: "modal-rise 0.4s cubic-bezier(.2,.7,.2,1)",
        }}
      >
        {/* Close button */}
        <button
          onClick={onClose}
          style={{
            position: "absolute", top: 20, right: 20,
            width: 36, height: 36,
            background: "transparent",
            border: "1px solid rgba(20,20,18,0.2)",
            cursor: "pointer",
            display: "flex", alignItems: "center", justifyContent: "center",
            zIndex: 2,
          }}
        >
          <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
            <path d="M1 1l10 10M11 1L1 11" stroke="#141412" strokeWidth="1.4"/>
          </svg>
        </button>

        {/* Hero image — the slab face */}
        <div style={{
          position: "relative",
          height: 320,
          overflow: "hidden",
          background: "#141412",
        }}>
          <img src={article.img} alt="" style={{
            position: "absolute", inset: 0,
            width: "100%", height: "100%", objectFit: "cover",
            filter: "brightness(0.7)",
          }}/>
          <div style={{
            position: "absolute", inset: 0,
            background: "linear-gradient(180deg, rgba(10,9,8,0.3) 0%, rgba(10,9,8,0.85) 100%)",
          }}/>
          <div style={{
            position: "absolute", bottom: 32, left: 40, right: 40,
          }}>
            <div className="mono" style={{
              fontSize: 10, letterSpacing: "0.26em", textTransform: "uppercase",
              color: "#E8D4A8", marginBottom: 14,
            }}>
              {article.category} &middot; {article.topic}
            </div>
            <h2 className="serif" style={{
              fontSize: "clamp(28px, 3.2vw, 44px)",
              lineHeight: 1.08, letterSpacing: "-0.022em",
              color: "#F5F2ED",
            }}>
              {article.title}
            </h2>
          </div>
        </div>

        {/* Body */}
        <div style={{ padding: "44px 52px 52px", maxWidth: 680, margin: "0 auto" }}>
          <div className="mono" style={{
            fontSize: 10, letterSpacing: "0.22em", textTransform: "uppercase",
            color: "var(--mute)", marginBottom: 24,
            display: "flex", gap: 14, flexWrap: "wrap",
          }}>
            <span>by {article.author}</span>
            <span>·</span>
            <span>{article.date}</span>
            <span>·</span>
            <span>{article.read}</span>
          </div>

          <p style={{
            fontSize: 20, lineHeight: 1.55,
            color: "var(--ink)", fontWeight: 400,
            marginBottom: 32,
          }}>
            {article.kicker}
          </p>

          <blockquote className="serif" style={{
            fontSize: 22, lineHeight: 1.35, fontStyle: "italic",
            color: "var(--ink)",
            borderLeft: "3px solid var(--maroon)",
            paddingLeft: 20,
            margin: "0 0 36px",
          }}>
            <span style={{ color: "var(--maroon)", fontStyle: "normal" }}>“</span>
            {article.pullquote}
            <span style={{ color: "var(--maroon)", fontStyle: "normal" }}>”</span>
          </blockquote>

          <p style={{
            fontSize: 16, lineHeight: 1.7, color: "var(--ink-2)",
            marginBottom: 20,
          }}>
            This is a preview of the full article. In the live site, the full body would appear
            here &mdash; Michael and Thomas's full write-up on this topic, with photos, diagrams,
            and the kind of plain-English explanations you won't find on a manufacturer's PDF.
          </p>
          <p style={{
            fontSize: 16, lineHeight: 1.7, color: "var(--ink-2)",
            marginBottom: 36,
          }}>
            For now, the essentials are above: the topic, the author, and the core idea.
          </p>

          <div style={{
            display: "flex", gap: 14, flexWrap: "wrap",
            paddingTop: 28,
            borderTop: "1px solid var(--line)",
          }}>
            <a href="#" className="btn" style={{
              background: "var(--ink)", color: "var(--bone)",
            }}>
              Read the full piece
              <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
                <path d="M1 6h10m0 0L6 1m5 5L6 11" stroke="currentColor" strokeWidth="1.2"/>
              </svg>
            </a>
            <button onClick={onClose} className="btn" style={{
              border: "1px solid var(--line)",
              background: "transparent",
              color: "var(--ink)",
              cursor: "pointer",
            }}>
              Close
            </button>
          </div>
        </div>
      </div>

      <style>{`
        @keyframes modal-fade {
          from { opacity: 0; }
          to   { opacity: 1; }
        }
        @keyframes modal-rise {
          from { opacity: 0; transform: translateY(24px) scale(0.97); }
          to   { opacity: 1; transform: translateY(0) scale(1); }
        }
      `}</style>
    </div>
  );
}



// ============================================================
// Masthead — warmer, more personal copy
// ============================================================
function LearnMasthead() {
  return (
    <section style={{ padding: "140px 0 60px", background: "var(--bone)" }}>
      <div className="container">
        {/* Breadcrumb */}
        <div className="mono" style={{
          fontSize: 10, letterSpacing: "0.24em", textTransform: "uppercase",
          color: "var(--mute)", marginBottom: 56,
        }}>
          <a href="index.html" style={{ borderBottom: "1px solid var(--line)" }}>M&amp;T</a>
          &nbsp;·&nbsp;Learn
        </div>

        <div style={{
          display: "grid",
          gridTemplateColumns: "1.4fr 1fr",
          gap: 80,
          alignItems: "end",
        }} className="learn-masthead-grid">
          <h1 className="serif rise" style={{
            fontSize: "clamp(44px, 5.8vw, 88px)",
            lineHeight: 1.0,
            letterSpacing: "-0.028em",
          }}>
            What we've learned.<br/>
            Written down <em style={{ color: "var(--maroon)", fontStyle: "italic" }}>for you.</em>
          </h1>

          <div className="rise rise-d1">
            <p style={{ fontSize: 17, lineHeight: 1.6, color: "var(--ink-2)", marginBottom: 24 }}>
              Plain-English writing on stone care &mdash; why things stain, what StoneGuard
              actually does, and how to keep your countertop looking the day it was installed.
            </p>
            <p style={{ fontSize: 14, lineHeight: 1.55, color: "var(--mute)" }}>
              Written by Michael and Thomas, in between jobs. No jargon. Each piece is a slab
              in our yard &mdash; use the crane below to move through them.
            </p>
          </div>
        </div>
      </div>
    </section>
  );
}


// ============================================================
// Bottom CTA — warmer copy
// ============================================================
function LearnFooterCTA() {
  return (
    <section style={{ padding: "140px 0 160px", background: "var(--ink)", color: "var(--bone)" }}>
      <div className="container" style={{ maxWidth: 960, textAlign: "center", margin: "0 auto" }}>
        <div className="mono" style={{
          fontSize: 10, letterSpacing: "0.24em", textTransform: "uppercase",
          color: "rgba(245,242,237,0.45)", marginBottom: 28,
        }}>
          Running into something we didn't cover?
        </div>
        <h2 className="serif" style={{
          fontSize: "clamp(44px, 5.4vw, 80px)",
          lineHeight: 1.02, letterSpacing: "-0.022em",
          marginBottom: 36,
        }}>
          Shoot us a text &mdash;<br/>
          <span style={{ color: "#E8D4A8", fontStyle: "italic" }}>
            we'll see how we can help.
          </span>
        </h2>
        <p style={{
          fontSize: 17, lineHeight: 1.65, color: "rgba(245,242,237,0.78)",
          maxWidth: 560, margin: "0 auto 48px",
        }}>
          Send us a photo of your countertop and tell us what's going on &mdash; we'll tell you what
          stone it is, how it's holding up, and what we'd do to protect it. Usually within the hour.
          No pressure, no sales script, just a real person with advice.
        </p>
        <div style={{ display: "flex", gap: 14, justifyContent: "center", flexWrap: "wrap" }}>
          <a href="tel:+19088661919" className="btn" style={{ background: "var(--bone)", color: "var(--ink)" }}>
            Text Michael directly
            <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
              <path d="M1 6h10m0 0L6 1m5 5L6 11" stroke="currentColor" strokeWidth="1.2"/>
            </svg>
          </a>
          <a href="index.html#services" className="btn" style={{ border: "1px solid rgba(245,242,237,0.3)", color: "var(--bone)" }}>
            See how we help
          </a>
        </div>
      </div>
    </section>
  );
}

// ============================================================
// Main
// ============================================================
function LearnPage() {
  return (
    <>
      <Nav onInquire={() => { window.location.href = "index.html"; }} />
      <LearnMasthead />
      <Stoneyard />
      <LearnFooterCTA />
      <Footer />

      <style>{`
        @media (max-width: 960px){
          .learn-masthead-grid{
            grid-template-columns: 1fr !important;
            gap: 48px !important;
          }
        }
      `}</style>
    </>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<LearnPage />);
