// sections.jsx — Hero / Process / Services / Work / Contact / Bio sections.
// Loaded after copy.js, before app.jsx.

// ── RotatingText — character-level spring animation (no external deps) ───
function RotatingText({ texts, bgColor = "#7B3FFF", rotationInterval = 2000, staggerDuration = 0.025 }) {
  const [idx, setIdx] = React.useState(0);
  const [renderKey, setRenderKey] = React.useState(0);

  React.useEffect(() => {
    const id = setInterval(() => {
      setIdx(i => (i + 1) % texts.length);
      setRenderKey(k => k + 1);
    }, rotationInterval);
    return () => clearInterval(id);
  }, [texts.length, rotationInterval]);

  const splitChars = (text) => {
    if (typeof Intl !== 'undefined' && Intl.Segmenter) {
      const seg = new Intl.Segmenter('en', { granularity: 'grapheme' });
      return Array.from(seg.segment(text), s => s.segment);
    }
    return Array.from(text);
  };

  const words = texts[idx].split(' ');
  const totalChars = words.reduce((n, w) => n + splitChars(w).length, 0);
  let ci = 0;

  return (
    <span className="text-rotate text-rotate--pill" style={{ backgroundColor: bgColor }}>
      <span className="text-rotate-sr-only">{texts[idx]}</span>
      <span key={renderKey} className="text-rotate text-rotate--anim" aria-hidden="true">
        {words.map((word, wi, arr) => (
          <span key={wi} className="text-rotate-word">
            {word}
            {wi < arr.length - 1 && <span className="text-rotate-space"> </span>}
          </span>
        ))}
      </span>
    </span>
  );
}

function renderBold(text) {
  if (!text || !text.includes("**")) return text;
  return text.split(/(\*\*[^*]+\*\*)/).map((part, i) =>
    part.startsWith("**") && part.endsWith("**")
      ? React.createElement("strong", { key: i }, part.slice(2, -2))
      : part
  );
}

// ── Hero ─────────────────────────────────────────────────────────────────
function Hero({ copy, variant, lang }) {
  const v = copy.heroes[variant] || copy.heroes[0];
  const canvasRef = React.useRef(null);
  const mouseRef = React.useRef({ x: -1000, y: -1000 });

  React.useEffect(() => {
    const cvs = canvasRef.current;
    if (!cvs) return;
    const ctx = cvs.getContext("2d");
    let raf, w, h, dpr;
    const resize = () => {
      const r = cvs.getBoundingClientRect();
      dpr = Math.min(window.devicePixelRatio || 1, 2);
      w = r.width; h = r.height;
      cvs.width = w * dpr; cvs.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(cvs);
    const onMove = (e) => {
      const r = cvs.getBoundingClientRect();
      mouseRef.current.x = e.clientX - r.left;
      mouseRef.current.y = e.clientY - r.top;
    };
    const onLeave = () => { mouseRef.current.x = -1000; mouseRef.current.y = -1000; };
    cvs.addEventListener("mousemove", onMove);
    cvs.addEventListener("mouseleave", onLeave);

    let t = 0;
    const cols = 60;
    const rows = 28;
    const draw = () => {
      ctx.clearRect(0, 0, w, h);
      const sx = w / cols;
      const sy = h / rows;
      ctx.lineWidth = 1;
      for (let j = 0; j < rows; j++) {
        ctx.beginPath();
        for (let i = 0; i <= cols; i++) {
          const x = i * sx;
          const baseY = j * sy + sy / 2;
          // base sine wave
          let y = baseY + Math.sin((i * 0.18) + t * 0.012 + j * 0.22) * 6
                        + Math.cos((i * 0.07) - t * 0.008 + j * 0.11) * 4;
          // mouse displacement
          const mx = mouseRef.current.x;
          const my = mouseRef.current.y;
          const dx = x - mx;
          const dy = baseY - my;
          const dist = Math.sqrt(dx*dx + dy*dy);
          const radius = 180;
          if (dist < radius) {
            const force = (1 - dist / radius);
            y += Math.sin(dist * 0.05 - t * 0.05) * force * 18;
            y -= force * 14;
          }
          if (i === 0) ctx.moveTo(x, y);
          else ctx.lineTo(x, y);
        }
        const alpha = 0.05 + (j / rows) * 0.18;
        ctx.strokeStyle = `rgba(255,255,255,${alpha})`;
        ctx.stroke();
      }
      t += 1;
      raf = requestAnimationFrame(draw);
    };
    draw();
    return () => {
      cancelAnimationFrame(raf);
      ro.disconnect();
      cvs.removeEventListener("mousemove", onMove);
      cvs.removeEventListener("mouseleave", onLeave);
    };
  }, []);

  const statement = lang === "es"
    ? { a: "Experiencias para personas,", b: "orientadas a resultados" }
    : { a: "Experiences for people,", b: "oriented to results" };

  return (
    <section className="hero hero-stage shell" id="top" data-screen-label="01 Hero">
      <div className="hero-card reveal">
        <canvas ref={canvasRef} className="hero-card-waves" aria-hidden="true"></canvas>
        <div className="hero-card-grain" aria-hidden="true"></div>
        <h1 className="hero-stage-statement">
          <span className="hs-line">{statement.a}</span>
          <span className="hs-line">{statement.b}</span>
        </h1>
      </div>
    </section>
  );
}

// ── Section header ──────────────────────────────────────────────────────
function SectionHead({ kicker, title, lede }) {
  return (
    <header className={"s-head reveal" + (kicker ? "" : " s-head-no-kicker")}>
      {kicker && (
        <div className="s-head-label">
          <span className="kicker">{kicker}</span>
        </div>
      )}
      <div>
        <h2 className="s-head-title">{title}</h2>
        <p className="lede">{lede}</p>
      </div>
    </header>
  );
}

// ── Process — tabs (desktop) + accordion (mobile) ────────────────────────
function ProcList({ steps }) {
  const [active, setActive]   = React.useState(0);
  const [shown, setShown]     = React.useState(0);
  const [textIn, setTextIn]   = React.useState(true);
  const [openAcc, setOpenAcc] = React.useState(0);

  React.useEffect(() => {
    setTextIn(false);
    const t = setTimeout(() => { setShown(active); setTextIn(true); }, 180);
    return () => clearTimeout(t);
  }, [active]);

  const icons = [
    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>,
    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2"/><rect x="9" y="3" width="6" height="4" rx="1"/><line x1="9" y1="12" x2="15" y2="12"/><line x1="9" y1="16" x2="13" y2="16"/></svg>,
    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>,
    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>,
    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>,
  ];

  const chevron = (
    <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
  );

  return (
    <div className="proc-wrap reveal">

      {/* ── Desktop: horizontal tabs ── */}
      <div className="proc-tabs-view">
        <div className="proc-tabs-bar" role="tablist">
          {steps.map((s, i) => (
            <button
              key={i}
              role="tab"
              aria-selected={i === active}
              className={"proc-tab interactive" + (i === active ? " is-active" : "")}
              onClick={() => setActive(i)}
            >
              <span className="proc-tab-icon" aria-hidden="true">{icons[i]}</span>
              <span className="proc-tab-name">{s.t}</span>
            </button>
          ))}
        </div>
        <div className="proc-tabs-panel" style={{ opacity: textIn ? 1 : 0, transition: "opacity .18s ease" }}>
          <p className="proc-tabs-desc">{steps[shown].d}</p>
        </div>
      </div>

      {/* ── Mobile: accordion ── */}
      <div className="proc-accordion-view">
        {steps.map((s, i) => {
          const isOpen = i === openAcc;
          return (
            <div key={i} className={"proc-acc-item" + (isOpen ? " is-open" : "")}>
              <button
                className="proc-acc-trigger interactive"
                onClick={() => setOpenAcc(isOpen ? -1 : i)}
                aria-expanded={isOpen}
              >
                <span className="proc-acc-left">
                  <span className="proc-acc-icon" aria-hidden="true">{icons[i]}</span>
                  <span className="proc-acc-name">{s.t}</span>
                </span>
                <span className="proc-acc-chevron" aria-hidden="true">{chevron}</span>
              </button>
              <div className="proc-acc-body">
                <p className="proc-acc-desc">{s.d}</p>
              </div>
            </div>
          );
        })}
      </div>

    </div>
  );
}

// ── Services page ────────────────────────────────────────────────────────
function ServicesPage({ copy, lang }) {
  const s = copy.services;

  return (
    <main className="bio-page shell" data-screen-label="Services" style={{ paddingBottom: 0 }}>
      <header className="bio-hero reveal">
        <h1 className="bio-hero-title">{s.title}</h1>
      </header>

      <WaveDivider />

      <div className="svc-grid reveal">
        {s.items.map((item, i) => (
          <div key={i} className="svc-item">
            <h3 className="svc-item-title">{item.t}</h3>
            <p className="svc-item-desc">{item.d}</p>
          </div>
        ))}
      </div>

      <header className="bio-hero reveal" style={{ marginTop: "clamp(60px, 8vh, 96px)" }}>
        <h2 className="bio-hero-title">{lang === "es" ? "Mi forma de trabajar" : "My way of working"}</h2>
      </header>

      <WaveDivider />

      <div className="proc-lede-wrap reveal">
        <p className="bio-prose-p" style={{ margin: 0 }}>{renderBold(copy.process.lede)}</p>
      </div>

      <ProcList steps={copy.process.steps} />
    </main>
  );
}

// ── Projects Stack ──────────────────────────────────────────────────────
// Six cards stacked centered (like a deck of cards), slightly rotated.
// Click to fan them out to organic positions across the viewport;
// hovering a card reveals its quote in the center.
function ProjectsStack({ copy, lang }) {
  const items = copy.items || [];
  const [open, setOpen] = React.useState(false);
  const [hover, setHover] = React.useState(null);
  const [isMobile, setIsMobile] = React.useState(() => window.innerWidth <= 720);
  React.useEffect(() => {
    const mq = window.matchMedia('(max-width: 720px)');
    const handler = (e) => setIsMobile(e.matches);
    mq.addEventListener('change', handler);
    return () => mq.removeEventListener('change', handler);
  }, []);

  // Stack rotations — small, deck-like
  const stackRots = React.useMemo(
    () => items.map((_, i) => {
      // deterministic pseudo-random in [-7, 7] degrees
      const seed = (i * 37 + 11) % 100;
      return ((seed / 100) * 14) - 7;
    }),
    [items.length]
  );

  // Scattered base positions (used as the starting point after the deck opens).
  // Once open, each card's live position lives in `cardPos` so the user can drag it.
  const baseScattered = React.useMemo(() => {
    // Hand-tuned positions to feel composed, not random-looking.
    // Avoid the centered quote zone (~ 50vw, 50vh, ±18vw / ±14vh).
    // Y values are kept inside [22, 82] vh to avoid the topbar (~80px) and
    // bottom edge — half-card height is ~150px, so this leaves a comfortable margin.
    const pos = [
      { x: 16, y: 26, r: -9 },   // top-left
      { x: 78, y: 24, r: 7 },    // top-right
      { x: 10, y: 56, r: 4 },    // mid-left
      { x: 84, y: 58, r: -6 },   // mid-right
      { x: 28, y: 80, r: 11 },   // bottom-left
      { x: 66, y: 80, r: -3 }    // bottom-right
    ];
    return items.map((_, i) => pos[i % pos.length]);
  }, [items.length]);

  // cardPos: live position for each card (in viewport px once dragging begins,
  // null while still on its base scattered slot).
  const [cardPos, setCardPos] = React.useState(() => items.map(() => null));
  // The drag handler updates positions in px (left/top in pixels).
  const dragRef = React.useRef(null); // { idx, startX, startY, originLeft, originTop, moved }
  const [dragging, setDragging] = React.useState(null); // index being dragged or null
  const [topZ, setTopZ] = React.useState(items.length + 1);

  // When the deck opens for the first time, after the scatter animation finishes,
  // freeze each card into pixel coordinates so dragging can take over without a jump.
  React.useEffect(() => {
    if (!open) return;
    // The scatter transition is 1.8s + last stagger (5 * 220 = 1100ms).
    // Convert vw/vh -> px once at the end so dragging is in absolute coords.
    const t = setTimeout(() => {
      setCardPos((prev) => {
        const vw = window.innerWidth;
        const vh = window.innerHeight;
        return prev.map((p, i) => {
          if (p) return p;
          const sc = baseScattered[i];
          return { x: sc.x * vw / 100, y: sc.y * vh / 100, r: sc.r };
        });
      });
    }, 1.8 * 1000 + (items.length - 1) * 220 + 50);
    return () => clearTimeout(t);
  }, [open, baseScattered, items.length]);

  const activeQuote = hover != null ? items[hover].quote : copy.stackQuote;
  const activeAttr = hover != null
    ? `№ ${items[hover].n} · ${items[hover].name}`
    : copy.stackQuoteAttr;

  const onStackClick = () => { if (!open) setOpen(true); };

  // Pointer drag handlers — attached only when scattered.
  const justDraggedRef = React.useRef(false);
  const onCardPointerDown = (i) => (e) => {
    if (!open) return;
    if (e.button !== undefined && e.button !== 0) return;
    e.preventDefault();
    const el = e.currentTarget;
    el.setPointerCapture && el.setPointerCapture(e.pointerId);
    const rect = el.getBoundingClientRect();
    // Card is centered (translate -50%, -50%) on its left/top
    const originLeft = rect.left + rect.width / 2;
    const originTop = rect.top + rect.height / 2;
    dragRef.current = {
      idx: i,
      pointerId: e.pointerId,
      startX: e.clientX,
      startY: e.clientY,
      originLeft,
      originTop,
      moved: false
    };
  };
  const onCardPointerMove = (e) => {
    const d = dragRef.current;
    if (!d) return;
    const dx = e.clientX - d.startX;
    const dy = e.clientY - d.startY;
    if (!d.moved && (Math.abs(dx) > 4 || Math.abs(dy) > 4)) {
      d.moved = true;
      setDragging(d.idx);
    }
    if (d.moved) {
      e.preventDefault();
      setCardPos((prev) => {
        const next = prev.slice();
        const cur = next[d.idx] || (() => {
          const sc = baseScattered[d.idx];
          const vw = window.innerWidth, vh = window.innerHeight;
          return { x: sc.x * vw / 100, y: sc.y * vh / 100, r: sc.r };
        })();
        next[d.idx] = {
          x: d.originLeft + dx,
          y: d.originTop + dy,
          r: cur.r
        };
        return next;
      });
    }
  };
  const onCardPointerUp = (i, p) => (e) => {
    const d = dragRef.current;
    if (!d || d.idx !== i) return;
    const wasDrag = d.moved;
    dragRef.current = null;
    setDragging(null);
    if (wasDrag) {
      justDraggedRef.current = true;
      // Reset on next tick — after the synthetic click has fired and been
      // suppressed by onCardClick.
      setTimeout(() => { justDraggedRef.current = false; }, 0);
    }
  };
  const onCardClick = (i, p) => (e) => {
    if (justDraggedRef.current) {
      e.preventDefault();
      e.stopPropagation();
      return;
    }
    if (!open && !isMobile) {
      e.preventDefault();
      onStackClick();
    }
  };

  const words = copy.stackLeadWords || [];
  const wordColors = ["#FF4D2E", "#2D5BFF", "#7B3FFF"];

  // Card colors — saturated editorial palette. Each card gets its own hue
  // so the stack reads as a curated set of pieces, not a uniform deck.
  // Order matches items (index 0 = top of stack = №06, etc).
  const cardColors = [
    "#FF4D2E", // electric orange
    "#2D5BFF", // electric blue
    "#C8FF3D", // acid lime
    "#00D9C0", // cyan-teal
    "#FF2E93", // hot magenta
    "#7B3FFF"  // electric violet
  ];

  // Per-card size + orientation. Width × height in CSS px; portrait/landscape mix.
  // Order matches `items` (index 0 = top of stack = №06, etc).
  const cardSizes = [
    { w: 380, h: 330 }, // landscape — bone
    { w: 360, h: 320 }, // landscape — dust
    { w: 235, h: 305 }, // small portrait — stone
    { w: 410, h: 280 }, // wide landscape — graphite
    { w: 305, h: 410 }, // tall portrait — sand
    { w: 260, h: 260 }  // square — ash
  ];

  return (
    <main className="ps-page" data-screen-label="Projects · Stack">
      {/* Stage occupies the rest of the viewport */}
      <section
        className={"ps-stage" + (open ? " is-open" : "")}
        aria-label={copy.title}
      >
        {/* Center editorial quote — fades in only when scattered. Static; one rotating word inside. */}
        <div className={"ps-quote" + (open ? " is-visible" : "")}>
          {(copy.stackLeadBefore || words.length > 0) && (
            <p className="ps-quote-lead">
              {copy.stackGreeting && (
                <span className="ps-quote-line ps-quote-line--greet">
                  {copy.stackGreeting}
                  {copy.stackGreetingEmoji && (
                    <span className="ps-quote-emoji" aria-hidden="true"> {copy.stackGreetingEmoji}</span>
                  )}
                </span>
              )}
              <span className="ps-quote-line">
                {copy.stackLeadBefore}{" "}
                <RotatingText
                  texts={words}
                  bgColor="#7B3FFF"
                  rotationInterval={3200}
                  staggerDuration={0.025}
                />{" "}
                {copy.stackLeadMid || "pensadas para personas,"}
              </span>{" "}
              <span className="ps-quote-line">
                {copy.stackLeadAfter}
              </span>
            </p>
          )}
          {copy.stackQuote && <p className="ps-quote-text">{copy.stackQuote}</p>}
          {copy.stackQuoteAttr && (
            <p className="ps-quote-attr">— {copy.stackQuoteAttr}</p>
          )}
        </div>

        {/* Stack hint — only before opening */}
        {!open && (
          <button
            type="button"
            className="ps-stack-hint interactive"
            onClick={onStackClick}
            aria-label={copy.stackHint}
          >
            <span className="ps-stack-hint-line" />
            <span className="ps-stack-hint-label">{copy.stackHint}</span>
          </button>
        )}

        {/* The cards */}
        <div className={"ps-cards" + (isMobile ? " reveal-stagger" : "")} onClick={(!open && !isMobile) ? onStackClick : undefined}>
          {items.map((p, i) => {
            const stackRot = stackRots[i];
            const sc = baseScattered[i];
            const z = items.length - i; // first item on top of stack
            const live = cardPos[i]; // null until first frozen, then {x,y,r} in px
            const isDragging = dragging === i;
            const sz = p.cardSize || cardSizes[i % cardSizes.length];

            // Style branches:
            //   closed         → stack centered
            //   open + !live   → base scatter (vw/vh) with stagger transition
            //   open + live    → absolute px (drag takes over)
            let finalStyle;
            if (!open) {
              finalStyle = {
                left: "50%",
                top: "50%",
                transform: `translate(-50%, -50%) rotate(${stackRot}deg)`,
                zIndex: z,
                transitionDelay: `${(items.length - 1 - i) * 60}ms`,
                "--card-bg": cardColors[i % cardColors.length],
                width: `${sz.w}px`,
                height: `${sz.h}px`
              };
            } else if (!live) {
              finalStyle = {
                left: `${sc.x}vw`,
                top: `${sc.y}vh`,
                transform: `translate(-50%, -50%) rotate(${sc.r}deg)`,
                zIndex: hover === i ? 99 : z,
                transitionDelay: `${i * 220}ms`,
                "--card-bg": cardColors[i % cardColors.length],
                width: `${sz.w}px`,
                height: `${sz.h}px`
              };
            } else {
              finalStyle = {
                left: `${live.x}px`,
                top: `${live.y}px`,
                transform: `translate(-50%, -50%) rotate(${live.r}deg)` + (isDragging ? " scale(1.04)" : ""),
                zIndex: isDragging ? 200 : (hover === i ? 99 : z),
                transition: isDragging ? "transform .25s cubic-bezier(.2,.7,.2,1), box-shadow .25s ease" : undefined,
                "--card-bg": cardColors[i % cardColors.length],
                cursor: isDragging ? "grabbing" : "grab",
                width: `${sz.w}px`,
                height: `${sz.h}px`
              };
            }

            const linkProps = (open || isMobile)
              ? { href: "/pages/project.html?id=" + p.n }
              : {};

            return (
              <a
                key={p.n}
                {...linkProps}
                className={
                  "ps-card" +
                  (open ? " is-scattered interactive" : "") +
                  (hover === i ? " is-hover" : "") +
                  (isDragging ? " is-dragging" : "")
                }
                style={finalStyle}
                onMouseEnter={() => open && setHover(i)}
                onMouseLeave={() => open && setHover((h) => h === i ? null : h)}
                onFocus={() => open && setHover(i)}
                onBlur={() => open && setHover((h) => h === i ? null : h)}
                onPointerDown={onCardPointerDown(i)}
                onPointerMove={onCardPointerMove}
                onPointerUp={onCardPointerUp(i, p)}
                onPointerCancel={onCardPointerUp(i, p)}
                onClick={onCardClick(i, p)}
                aria-label={(open || isMobile) ? `${p.name} · ${p.year}` : undefined}
                tabIndex={(open || isMobile) ? 0 : -1}
              >
                <div className="ps-card-photo" aria-hidden="true"
                  style={p.coverImage ? { backgroundImage: `url(${p.coverImage})`, backgroundSize: "cover", backgroundPosition: "center" } : undefined} />
                <div className="ps-card-label">
                  <span className="ps-card-name">{p.name}</span>
                </div>
              </a>
            );
          })}
        </div>
      </section>
    </main>
  );
}

function ImageComparisonCell({ before, after }) {
  const [isDragging, setIsDragging] = React.useState(false);
  const [pos, setPos] = React.useState(50);

  const move = (e) => {
    if (!isDragging) return;
    const rect = e.currentTarget.getBoundingClientRect();
    const clientX = e.touches ? e.touches[0].clientX : e.clientX;
    setPos(Math.min(Math.max(((clientX - rect.left) / rect.width) * 100, 0), 100));
  };

  return (
    <div
      style={{ position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "ew-resize", userSelect: "none", borderRadius: "inherit" }}
      onMouseDown={(e) => { setIsDragging(true); move(e); }}
      onMouseMove={move}
      onMouseUp={() => setIsDragging(false)}
      onMouseLeave={() => setIsDragging(false)}
      onTouchStart={() => setIsDragging(true)}
      onTouchMove={move}
      onTouchEnd={() => setIsDragging(false)}
    >
      <img src={after}   alt="" style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover" }} draggable={false} />
      <img src={before}  alt="" style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", clipPath: `inset(0 ${100 - pos}% 0 0)` }} draggable={false} />
      <div style={{ position: "absolute", inset: "0 auto 0", left: `${pos}%`, width: "2px", transform: "translateX(-50%)", background: "rgba(255,255,255,0.5)", backdropFilter: "blur(4px)", pointerEvents: "none" }}>
        <div style={{ position: "absolute", top: "50%", left: "50%", width: "36px", height: "36px", borderRadius: "50%", background: "white", transform: "translate(-50%,-50%)", boxShadow: "0 2px 12px rgba(0,0,0,0.18)", display: "flex", alignItems: "center", justifyContent: "center" }}>
          <svg width="20" height="14" viewBox="0 0 20 14" fill="none" xmlns="http://www.w3.org/2000/svg">
            <path d="M6 1L1 7L6 13" stroke="#666" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/>
            <path d="M14 1L19 7L14 13" stroke="#666" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/>
          </svg>
        </div>
      </div>
      <span style={{ position: "absolute", bottom: "16px", left: "16px", fontFamily: "var(--f-sans)", fontSize: "11px", letterSpacing: "0.06em", color: "rgba(0,0,0,0.4)", pointerEvents: "none" }}>Antes</span>
      <span style={{ position: "absolute", bottom: "16px", right: "16px", fontFamily: "var(--f-sans)", fontSize: "11px", letterSpacing: "0.06em", color: "rgba(0,0,0,0.4)", pointerEvents: "none" }}>Ahora</span>
    </div>
  );
}

function ProjectDetail({ project: p, allProjects, lang }) {
  const coverRef = React.useRef(null);
  const cell2Ref = React.useRef(null);
  const cell1Ref = React.useRef(null);

  React.useEffect(() => {
    const fig = coverRef.current;
    if (!fig || !p.coverImage) return;
    const img = fig.querySelector(".proj-cover-img");
    if (!img) return;
    const onScroll = () => {
      const top = fig.getBoundingClientRect().top;
      img.style.transform = `translateY(${-top * 0.28}px)`;
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener("scroll", onScroll);
  }, [p.coverImage]);

  React.useEffect(() => {
    const cell = cell1Ref.current;
    if (!cell || !p.gridImage0) return;
    const img = cell.querySelector(".proj-parallax-img");
    if (!img) return;
    const onScroll = () => {
      const top = cell.getBoundingClientRect().top;
      const limit = cell.offsetHeight * 0.25;
      const shift = Math.max(-limit, Math.min(limit, -top * 0.25));
      img.style.transform = `translateY(${shift}px)`;
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener("scroll", onScroll);
  }, [p.gridImage0]);

  React.useEffect(() => {
    const cell = cell2Ref.current;
    if (!cell || !p.teamImage) return;
    const img = cell.querySelector(".proj-parallax-img");
    if (!img) return;
    const onScroll = () => {
      const top = cell.getBoundingClientRect().top;
      const limit = cell.offsetHeight * 0.25;
      const shift = Math.max(-limit, Math.min(limit, -top * 0.25));
      img.style.transform = `translateY(${shift}px)`;
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener("scroll", onScroll);
  }, [p.teamImage]);

  const labels = lang === "en"
    ? { back: "← Back", client: "Client", year: "Year", type: "Type", role: "Role", duration: "Duration", stack: "Stack", participants: p.participantsLabel?.en || "Also contributed" }
    : { back: "← Volver", client: "Cliente", year: "Año", type: "Tipo", role: "Rol", duration: "Duración", stack: "Stack", participants: p.participantsLabel?.es || "También participaron" };

  const fallbackProse = lang === "en"
    ? [
        "Every decision in this project went through the same question: what can be removed without losing meaning? The answer shaped the architecture, the typographic system and the cadence of the screens. The result is a tool that becomes invisible while in use — probably the highest compliment an interface can receive.",
        "The process was deliberately slow. Conversations with the client team every two weeks, review sessions in real code, and a discipline of not adding anything until we had justified why it couldn't be missing."
      ]
    : [
        "Cada decisión de este proyecto pasó por la misma pregunta: ¿qué se elimina sin perder el sentido? La respuesta dio forma a la arquitectura, al sistema tipográfico y a la cadencia de las pantallas. El resultado es una herramienta que se vuelve invisible mientras se usa —que es, probablemente, el mejor cumplido que puede recibir una interfaz.",
        "El proceso fue lento de forma deliberada. Conversaciones con el equipo cliente cada dos semanas, sesiones de revisión en código real, y una disciplina de no agregar nada hasta haber justificado por qué no podía faltar."
      ];

  const bodyParas = Array.isArray(p.body) ? p.body : fallbackProse;

  const all = allProjects || [];
  const idx = all.findIndex((x) => x.n === p.n);
  const prev = all.length > 1 ? all[(idx - 1 + all.length) % all.length] : null;
  const next = all.length > 1 ? all[(idx + 1) % all.length] : null;
  const projCardColors = ["#FF4D2E","#2D5BFF","#C8FF3D","#00D9C0","#FF2E93","#7B3FFF"];
  const nextIdx = next ? all.findIndex((x) => x.n === next.n) : -1;
  const nextBg = next && next.coverImage
    ? { backgroundImage: `url(${next.coverImage})`, backgroundSize: "cover", backgroundPosition: "center" }
    : { background: projCardColors[nextIdx >= 0 ? nextIdx % projCardColors.length : 0] };

  return (
    <main className="bio-page proj-detail-page shell" data-screen-label="Project">

      {prev && (
        <a className="proj-nav proj-nav--prev interactive" href={"/pages/project.html?id=" + prev.n} aria-label={prev.name}>
          <span className="proj-nav-arrow">←</span>
          <span className="proj-nav-name">{prev.name}</span>
        </a>
      )}
      {next && (
        <a className="proj-nav proj-nav--next interactive" href={"/pages/project.html?id=" + next.n} aria-label={next.name}>
          <span className="proj-nav-name">{next.name}</span>
          <span className="proj-nav-arrow">→</span>
        </a>
      )}

      {p.colorGrid && p.gridLayout === "vuelta" && (
        <div className="proj-color-grid">
          {/* Row 1: card imagen ajustada + card relleno + card cuadrada */}
          <div className="proj-vuelta-row1" style={{ gridColumn: "span 12", display: "flex", gap: "10px", height: "var(--grid-row-h)" }}>
            <div ref={cell1Ref} className="proj-color-cell proj-vuelta-cell1 reveal" style={{ flexShrink: 0, background: p.colorGrid[0], transitionDelay: ".08s", overflow: "hidden", display: "flex", alignItems: "center" }}>
              {p.gridImage0 && (
                <div className="proj-grid-cap-wrap" style={{ width: "100%", height: "100%" }}>
                  <img src={p.gridImage0} alt="" style={{ height: "100%", width: "auto", display: "block" }} />
                  {p.gridCaptions?.mockup && <span className="proj-grid-caption">{p.gridCaptions.mockup}</span>}
                </div>
              )}
            </div>
            <div className="proj-color-cell proj-vuelta-cell2 reveal" style={{ flex: 1, background: p.gridImage3 ? "none" : p.colorGrid[1], transitionDelay: ".18s", overflow: "hidden", position: "relative" }}>
              {p.gridImage3 && (
                <div className="proj-grid-cap-wrap">
                  <img src={p.gridImage3} alt="" style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", objectPosition: "left", display: "block" }} />
                  {p.gridCaptions?.app && <span className="proj-grid-caption">{p.gridCaptions.app}</span>}
                </div>
              )}
            </div>
            <div className="proj-color-cell proj-vuelta-cell3 reveal" style={{ width: "var(--grid-row-h)", flexShrink: 0, background: p.gridImage1 ? "none" : p.colorGrid[2], transitionDelay: ".28s", overflow: "hidden", position: "relative" }}>
              {p.gridImage1 && (
                <div className="proj-grid-cap-wrap">
                  <img src={p.gridImage1} alt="" style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                  {p.gridCaptions?.logo && <span className="proj-grid-caption">{p.gridCaptions.logo}</span>}
                </div>
              )}
            </div>
          </div>
          {/* Row 2: card + card dividida horizontalmente */}
          <div className="proj-vuelta-row2" style={{ gridColumn: "span 12", display: "flex", gap: "10px", height: "var(--grid-row-h)" }}>
            <div className="proj-color-cell reveal" style={{ flex: 1, background: p.gridImage0a ? "none" : p.colorGrid[1], transitionDelay: ".48s", overflow: "hidden", position: "relative" }}>
              {p.gridImage0a && (
                <div className="proj-grid-cap-wrap">
                  <img src={p.gridImage0a} alt="" style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                  {p.gridCaptions?.apps && <span className="proj-grid-caption">{p.gridCaptions.apps}</span>}
                </div>
              )}
            </div>
            <div className="reveal" style={{ flex: 1, display: "flex", flexDirection: "column", gap: "10px", transitionDelay: ".68s" }}>
              <div className="proj-color-cell" style={{ flex: 2, background: p.gridImage4 ? "none" : p.colorGrid[3], overflow: "hidden", position: "relative" }}>
                {p.gridImage4 && (
                  <div className="proj-grid-cap-wrap">
                    <img src={p.gridImage4} alt="" style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                    {p.gridCaptions?.iconos && <span className="proj-grid-caption">{p.gridCaptions.iconos}</span>}
                  </div>
                )}
              </div>
              <div className="proj-color-cell" style={{ flex: 1, background: p.gridImage2 ? "none" : p.colorGrid[4], overflow: "hidden", position: "relative" }}>
                {p.gridImage2 && (
                  <div className="proj-grid-cap-wrap">
                    <img src={p.gridImage2} alt="" style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                    {p.gridCaptions?.colores && <span className="proj-grid-caption">{p.gridCaptions.colores}</span>}
                  </div>
                )}
              </div>
            </div>
          </div>
        </div>
      )}

      {p.colorGrid && p.gridLayout === "oriental" && (
        <div className="proj-color-grid" style={{ gridTemplateRows: "500px 380px" }}>
          {/* Row 1: full width */}
          <div className="proj-oriental-row1" style={{ gridColumn: "span 12", display: "flex" }}>
            <div className="proj-color-cell reveal" style={{ flex: 1, background: p.gridWideImage ? "none" : p.colorGrid[0], transitionDelay: ".08s", overflow: "hidden" }}>
              {p.gridWideImage && (
                <div className="proj-grid-cap-wrap">
                  <img src={p.gridWideImage} alt="" style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                  {p.gridCaptions?.wide && <span className="proj-grid-caption">{p.gridCaptions.wide}</span>}
                </div>
              )}
            </div>
          </div>
          {/* Row 2: rectangular + square (default), or N equal cards (row2Count) */}
          <div className="proj-oriental-row2" style={{ gridColumn: "span 12", display: "flex", gap: "10px" }}>
            {p.row2Count ? (
              Array.from({ length: p.row2Count }).map((_, j) => {
                const img = p.gridRow2Images?.[j];
                return (
                  <div key={j} className="proj-color-cell reveal" style={{ flex: 1, background: img ? "none" : (p.colorGrid[1 + j] || p.colorGrid[1]), transitionDelay: (.28 + j * .2) + "s", overflow: "hidden" }}>
                    {img && (
                      <div className="proj-grid-cap-wrap">
                        <img src={img} alt="" style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                        {p.gridCaptions?.row2?.[j] && <span className="proj-grid-caption">{p.gridCaptions.row2[j]}</span>}
                      </div>
                    )}
                  </div>
                );
              })
            ) : (
              <>
                <div className="proj-color-cell reveal" style={{ flex: 1, background: p.gridRectImage ? "none" : p.colorGrid[1], transitionDelay: ".28s", overflow: "hidden" }}>
                  {p.gridRectImage && (
                    <div className="proj-grid-cap-wrap">
                      <img src={p.gridRectImage} alt="" className="proj-rect-contain-img" style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                      <span className="proj-grid-caption">Responsividad</span>
                    </div>
                  )}
                </div>
                <div className="proj-color-cell reveal" style={{ aspectRatio: "1", flexShrink: 0, background: p.gridSquareImage ? "none" : p.colorGrid[2], transitionDelay: ".48s", overflow: "hidden" }}>
                  {p.gridSquareImage && (
                    <div className="proj-grid-cap-wrap">
                      <img src={p.gridSquareImage} alt="" style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                      <span className="proj-grid-caption">Isotipo</span>
                    </div>
                  )}
                </div>
              </>
            )}
          </div>
        </div>
      )}

      {p.colorGrid && p.gridLayout !== "vuelta" && p.gridLayout !== "oriental" && (
        <div className="proj-color-grid">
          {/* Fila 1 */}
          {p.row1Squares ? (
            <div className="proj-row-1" style={{ gridColumn: "span 12", display: "flex", gap: "10px", height: "var(--grid-row-h)" }}>
              <div className="proj-color-cell proj-color-cell--0 reveal" style={{ flex: 2, transitionDelay: ".08s", ...(!p.comparison && !p.gridImage0 && { background: p.colorGrid[0] }) }}>
                {p.comparison && <ImageComparisonCell before={p.comparison.before} after={p.comparison.after} />}
                {p.gridImage0 && (
                  <div className="proj-grid-cap-wrap">
                    <img src={p.gridImage0} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                    {p.gridCaptions?.stickers && <span className="proj-grid-caption">{p.gridCaptions.stickers}</span>}
                  </div>
                )}
              </div>
              <div className="proj-color-cell proj-color-cell--5 reveal" style={{ background: "none", transitionDelay: ".28s" }}>
                <div className="proj-portrait-icon-pair" style={{ display: "flex", flexDirection: "column", height: "100%", gap: "10px" }}>
                  <div className="proj-grid-cap-wrap" style={{ borderRadius: "14px", flex: 1, height: "auto", background: p.portraitImage ? "none" : p.colorGrid[5] }}>
                    {p.portraitImage && <img src={p.portraitImage} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />}
                    {p.gridCaptions?.portrait && <span className="proj-grid-caption">{p.gridCaptions.portrait}</span>}
                  </div>
                  <div className="proj-grid-cap-wrap" style={{ borderRadius: "14px", flex: 1, height: "auto", background: p.iconImage ? "none" : (p.colorGrid[6] || p.colorGrid[5]) }}>
                    {p.iconImage && <img src={p.iconImage} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />}
                    {p.gridCaptions?.icon && <span className="proj-grid-caption">{p.gridCaptions.icon}</span>}
                  </div>
                </div>
              </div>
              <div className="proj-color-cell proj-color-cell--1 reveal" style={Object.assign(p.gridImage ? {} : { background: p.colorGrid[1] }, { flex: 1, overflow: "hidden", transitionDelay: ".48s" })}>
                {p.gridImage && (
                  <div className="proj-grid-cap-wrap">
                    {p.gridImage.endsWith(".mp4") ? (
                      <video src={p.gridImage} autoPlay loop muted playsInline style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                    ) : (
                      <img src={p.gridImage} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                    )}
                    {p.gridCaptions?.merch && <span className="proj-grid-caption">{p.gridCaptions.merch}</span>}
                  </div>
                )}
              </div>
            </div>
          ) : (
            <>
              <div className={`proj-color-cell proj-color-cell--0 reveal${p.swapRows12 && p.row3SquareColors ? " proj-comp-icons-cell" : ""}`} style={{ transitionDelay: ".08s", ...(!p.comparison && { background: p.colorGrid[0] }), ...(p.swapRows12 && { gridRow: "2" }), ...(p.swapRows12 && p.row3SquareColors && { gridColumn: "span 12", display: "flex", gap: "10px" }) }}>
                {(p.swapRows12 && p.row3SquareColors) ? (
                  <>
                    <div className="proj-comparison-wrap" style={{ flex: 2, position: "relative", overflow: "hidden", borderRadius: "14px" }}>
                      {p.comparison && <ImageComparisonCell before={p.comparison.before} after={p.comparison.after} />}
                    </div>
                    <div className="proj-icons-grid" style={{ flex: 1, display: "grid", gridTemplateColumns: "1fr 1fr", gap: "10px" }}>
                      {p.row3SquareColors.map((color, j) => {
                        const img = p.row3SquareImages?.[j];
                        return (
                          <div key={j} style={{ borderRadius: "10px", background: img ? "none" : color, overflow: "hidden" }}>
                            {img && <img src={img} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />}
                          </div>
                        );
                      })}
                    </div>
                  </>
                ) : (
                  p.comparison && <ImageComparisonCell before={p.comparison.before} after={p.comparison.after} />
                )}
              </div>
              <div className="proj-color-cell proj-color-cell--1 reveal" style={Object.assign(p.gridImage || p.backendImage ? {} : { background: p.colorGrid[1] }, { transitionDelay: ".28s" }, p.swapRows12 ? { gridRow: (p.row3SquareColors ? "3" : "2 / 4"), ...(p.row3SquareColors && { gridColumn: "span 8" }) } : {})}>
                {p.backendImage && !p.gridImage && (
                  <div className="proj-grid-cap-wrap proj-no-zoom proj-backend-wrap">
                    <img src={p.backendImage} alt="" className="proj-backend-img" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                    {p.gridCaptions?.backend && <span className="proj-grid-caption">{p.gridCaptions.backend}</span>}
                  </div>
                )}
                {p.gridImage && (
                  <div className="proj-grid-cap-wrap">
                    {p.gridImage.endsWith(".mp4") ? (
                      <video src={p.gridImage} autoPlay loop muted playsInline style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                    ) : (
                      <img src={p.gridImage} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                    )}
                    {p.gridCaptions?.merch && <span className="proj-grid-caption">{p.gridCaptions.merch}</span>}
                  </div>
                )}
              </div>
            </>
          )}
          {/* Fila 2 */}
          {p.teamImage && (
            <div ref={cell2Ref} className="proj-color-cell proj-color-cell--2 reveal" style={{ transitionDelay: ".48s", ...(p.swapRows12 && { gridRow: "1" }) }}>
              <div className="proj-grid-cap-wrap">
                <img src={p.teamImage} alt="" className="proj-parallax-img" style={{ position: "absolute", width: "100%", height: "150%", top: "-25%", objectFit: "cover", display: "block", willChange: "transform" }} />
                {p.gridCaptions?.team && <span className="proj-grid-caption">{p.gridCaptions.team}</span>}
              </div>
            </div>
          )}
          {/* Fila 3 */}
          {!p.row1Squares && (
            <div className="proj-row-3" style={p.swapRows12 ? { gridColumn: (p.row3SquareColors ? "span 4" : "span 8") } : {}}>
              {(p.swapRows12 && p.row3SquareColors) ? (
                <div className="proj-color-cell proj-color-cell--3 reveal" style={{ flex: 1, transitionDelay: ".68s" }}>
                  {p.libretaImage && (
                    <div className="proj-grid-cap-wrap">
                      <img src={p.libretaImage} alt="" style={{ width: "100%", height: "100%", objectFit: "contain", display: "block" }} />
                      {p.gridCaptions?.libreta && <span className="proj-grid-caption">{p.gridCaptions.libreta}</span>}
                    </div>
                  )}
                </div>
              ) : (
              <>
              <div className="proj-color-cell proj-color-cell--3 reveal" style={Object.assign(p.libretaImage ? {} : { background: p.colorGrid[3] }, { transitionDelay: ".68s" }, p.row3SquareColors ? { flex: 1 } : {})}>
                {p.libretaImage && (
                  <div className="proj-grid-cap-wrap">
                    <img src={p.libretaImage} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                    {p.gridCaptions?.libreta && <span className="proj-grid-caption">{p.gridCaptions.libreta}</span>}
                  </div>
                )}
              </div>
              {(p.row3SquareColors && !p.swapRows12) ? (
                <div className="proj-color-cell proj-row-3-squares reveal" style={{ flexShrink: 0, width: "var(--grid-row-h)", background: "none", transitionDelay: ".84s" }}>
                  <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "10px", height: "100%" }}>
                    {p.row3SquareColors.map((color, j) => {
                      const img = p.row3SquareImages?.[j];
                      return (
                        <div key={j} style={{ borderRadius: "10px", background: img ? "none" : color, overflow: "hidden" }}>
                          {img && <img src={img} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />}
                        </div>
                      );
                    })}
                  </div>
                </div>
              ) : (
                <>
                  {!p.swapRows12 && (
                    <div className="proj-color-cell proj-color-cell--4 reveal" style={Object.assign(p.socialImage ? {} : { background: p.colorGrid[4] }, { transitionDelay: ".84s" })}>
                      {p.socialImage && (
                        <div className="proj-grid-cap-wrap">
                          <img src={p.socialImage} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                          {p.gridCaptions?.social && <span className="proj-grid-caption">{p.gridCaptions.social}</span>}
                        </div>
                      )}
                    </div>
                  )}
                  <div className="proj-color-cell proj-color-cell--5 reveal" style={{ background: "none", transitionDelay: "1s" }}>
                    <div className="proj-portrait-icon-pair" style={{ display: "flex", flexDirection: "column", height: "100%", gap: "10px" }}>
                      <div className="proj-grid-cap-wrap" style={{ borderRadius: "14px", flex: 1, height: "auto", background: p.portraitImage ? "none" : p.colorGrid[5] }}>
                        {p.portraitImage && <img src={p.portraitImage} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />}
                        {p.gridCaptions?.portrait && <span className="proj-grid-caption">{p.gridCaptions.portrait}</span>}
                      </div>
                      <div className="proj-grid-cap-wrap" style={{ borderRadius: "14px", flex: 1, height: "auto", background: p.iconImage ? "none" : (p.colorGrid[6] || p.colorGrid[5]) }}>
                        {p.iconImage && <img src={p.iconImage} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />}
                        {p.gridCaptions?.icon && <span className="proj-grid-caption">{p.gridCaptions.icon}</span>}
                      </div>
                    </div>
                  </div>
                </>
              )}
              </>
              )}
            </div>
          )}
          {/* Fila 4 */}
          {p.colorGrid[7] && (
            <div className="proj-row-4">
              {/* Primera celda: 2×2 cuadrados */}
              <div className="proj-color-cell--r4 reveal" style={{ background: "none", transitionDelay: "1.1s" }}>
                <div className="proj-faces-grid" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "10px", height: "100%" }}>
                  {[0,1,2,3].map(j => {
                    const sq = Array.isArray(p.row4Images?.[0]) ? p.row4Images[0][j] : null;
                    return (
                      <div key={j} style={{ borderRadius: "10px", background: sq ? "none" : p.colorGrid[7], overflow: "hidden" }}>
                        {sq && <img src={sq} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />}
                      </div>
                    );
                  })}
                </div>
              </div>
              {/* Segunda y tercera celda */}
              {[8, 9].map((ci, i) => {
                const img = p.row4Images?.[i + 1];
                return (
                  <div key={i} className="proj-color-cell--r4 reveal"
                    style={{ background: img ? "none" : (p.colorGrid[ci] || p.colorGrid[0]), transitionDelay: (1.25 + i * 0.15) + "s" }}>
                    {img && (
                      <div className="proj-grid-cap-wrap">
                        {img.endsWith(".mp4") ? (
                          <video src={img} autoPlay loop muted playsInline style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                        ) : (
                          <img src={img} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                        )}
                        {i === 1 && p.gridCaptions?.colorear && <span className="proj-grid-caption">{p.gridCaptions.colorear}</span>}
                      </div>
                    )}
                  </div>
                );
              })}
            </div>
          )}
        </div>
      )}

      {p.gallery && p.gallery.length > 0 && (
        <section className="gallery-section reveal">
          <div className="masonry">
            {p.gallery.map((src, i) => (
              <div key={i} className="masonry-item">
                <img src={src} alt="" style={{ width: "100%", display: "block", borderRadius: "10px" }} />
              </div>
            ))}
          </div>
        </section>
      )}

      <div className="proj-detail-body">
        <section className="proj-detail-prose reveal">
          <p className="bio-prose-p">{renderBold(p.summary)}</p>
          {bodyParas.map((para, i) => (
            <p key={i} className="bio-prose-p">{renderBold(para)}</p>
          ))}
        </section>

        <aside className="proj-detail-sidebar reveal">
          <p className="proj-sidebar-title">{lang === "es" ? "Detalles" : "Details"}</p>
          <div className="proj-meta-grid">
            <div className="row"><span className="k">{labels.year}</span><span className="v">{p.year}</span></div>
            {(p.clientFull || p.client) && <div className="row"><span className="k">{labels.client}</span><span className="v">{p.clientFull || p.client}</span></div>}
            <div className="row"><span className="k">{labels.type}</span><span className="v">{p.type}</span></div>
            {p.participants && (
              <div className="row"><span className="k">{labels.participants}</span><span className="v">{p.participants}</span></div>
            )}
          </div>
          {p.links && p.links.length > 0 && (
            <div className="proj-links">
              {p.links.map((l, i) => (
                <a key={i} {...(l.href ? { href: l.href, target: "_blank", rel: "noopener noreferrer" } : {})} className={"proj-link-btn interactive" + (!l.href ? " proj-link-btn--disabled" : "")} data-label={l.label}>
                  <span className="proj-link-btn-inner">{l.label}</span>
                </a>
              ))}
            </div>
          )}
        </aside>
      </div>

      {next && (
        <a className="proj-next-preview reveal interactive" href={"/pages/project.html?id=" + next.n}>
          <div className="proj-next-preview-bg" style={nextBg} />
          <div className="proj-next-preview-info">
            <span className="proj-next-preview-label">{lang === "es" ? "Próximo proyecto" : "Next project"}</span>
            <span className="proj-next-preview-name">{next.name}</span>
          </div>
        </a>
      )}
    </main>
  );
}

function ProjectNotFound({ id }) {
  return (
    <main className="proj-page shell" data-screen-label="Project not found">
      <article className="proj-sheet proj-sheet--page">
        <a className="proj-close interactive" href="/#work">← Volver</a>
        <header className="proj-head">
          <span className="kicker">№ {id || "—"}</span>
          <h2 className="proj-title">Proyecto no encontrado<em>.</em></h2>
          <p className="proj-quote">El proyecto que buscás no existe o fue movido.</p>
        </header>
      </article>
    </main>
  );
}

// ── Contact ─────────────────────────────────────────────────────────────
function Contact({ copy }) {
  return (
    <section className="section shell" id="contact" data-screen-label="05 Contact">
      <header className="s-head reveal" style={{ marginBottom: "clamp(48px, 8vh, 80px)" }}>
        <div className="s-head-label">
          <span className="kicker">{copy.kicker}</span>
        </div>
        <div>
          <p className="lede">{copy.lede}</p>
        </div>
      </header>

      <div className="contact-block reveal">
        <h2 className="contact-headline">
          <a href={"mailto:" + copy.email}>
            {copy.title.replace(".", "")}<em>.</em>
          </a>
        </h2>
        <div className="contact-aside">
          <ul className="socials">
            {copy.socials.map((s, i) => (
              <li key={i} className="social-row interactive" style={{ display: "grid" }}>
                <a href={s.href} style={{ display: "contents" }}>
                  <span className="k">{s.label}</span>
                  <span className="v">{s.value}</span>
                  <span className="arr">↗</span>
                </a>
              </li>
            ))}
          </ul>
        </div>
      </div>
    </section>
  );
}

// ── Bio page ────────────────────────────────────────────────────────────

function LegalPage({ copy, lang }) {
  const l = copy.legal;
  return (
    <main className="bio-page shell" data-screen-label="Legal">
      <header className="bio-hero reveal">
        <h1 className="bio-hero-title">{l.kicker}</h1>
      </header>
      <WaveDivider />
      <div className="legal-sections reveal">
        {l.items.map((item, i) => (
          <React.Fragment key={i}>
            <div className="legal-section">
              <h3 className="legal-section-title">{item.t}</h3>
              <p className="legal-section-body">{item.d}</p>
            </div>
            {i < l.items.length - 1 && <WaveDivider />}
          </React.Fragment>
        ))}
      </div>
    </main>
  );
}

function WaveDivider() {
  const pathRef = React.useRef(null);
  const stateRef = React.useRef({ progress: 0, x: 0.5, time: Math.PI / 2, reqId: null });

  const setPath = React.useCallback((progress) => {
    const el = pathRef.current;
    if (!el) return;
    const width = el.parentElement.parentElement.clientWidth;
    const x = stateRef.current.x;
    el.setAttributeNS(null, "d", `M0 100 Q${width * x} ${100 + progress * 0.6}, ${width} 100`);
  }, []);

  React.useEffect(() => {
    setPath(0);
    const onResize = () => setPath(stateRef.current.progress);
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, [setPath]);

  const lerp = (x, y, a) => x * (1 - a) + y * a;

  const resetAnimation = () => {
    stateRef.current.time = Math.PI / 2;
    stateRef.current.progress = 0;
  };

  const animateOut = React.useCallback(() => {
    const s = stateRef.current;
    const newProgress = s.progress * Math.sin(s.time);
    s.progress = lerp(s.progress, 0, 0.06);
    s.time += 0.2;
    setPath(newProgress);
    if (Math.abs(s.progress) > 0.75) {
      s.reqId = requestAnimationFrame(animateOut);
    } else {
      resetAnimation();
      setPath(0);
    }
  }, [setPath]);

  const onEnter = () => {
    const s = stateRef.current;
    if (s.reqId) {
      cancelAnimationFrame(s.reqId);
      s.reqId = null;
      resetAnimation();
    }
  };

  const onMove = (e) => {
    const s = stateRef.current;
    const el = pathRef.current;
    if (!el) return;
    const rect = el.getBoundingClientRect();
    s.x = (e.clientX - rect.left) / rect.width;
    s.progress += e.movementY;
    setPath(s.progress);
  };

  const onLeave = () => {
    animateOut();
  };

  return (
    <div className="wave-divider" aria-hidden="true">
      <div
        className="wave-hit"
        onMouseEnter={onEnter}
        onMouseMove={onMove}
        onMouseLeave={onLeave}
      />
      <svg className="wave-svg" preserveAspectRatio="none">
        <path ref={pathRef} fill="none" stroke="currentColor" strokeWidth="1" />
      </svg>
    </div>
  );
}

function BioPage({ copy, lang }) {
  const [showPhotos, setShowPhotos] = React.useState(false);
  const photos = (copy.photos || []).filter(p => p.src && !p.hidden);

  React.useEffect(() => {
    if (!showPhotos) return;

    const check = () => {
      const vh = window.innerHeight;
      document.querySelectorAll(".bio-photo-stagger:not(.in)").forEach((el) => {
        const r = el.getBoundingClientRect();
        if (r.top < vh * 0.93 && r.bottom > 0) el.classList.add("in");
      });
    };

    const init = () => {
      // Sort items by visual position (top → left) and assign delays in that order
      Array.from(document.querySelectorAll(".bio-photo-stagger"))
        .sort((a, b) => {
          const ra = a.getBoundingClientRect(), rb = b.getBoundingClientRect();
          const dt = ra.top - rb.top;
          return Math.abs(dt) > 5 ? dt : ra.left - rb.left;
        })
        .forEach((el, i) => { el.style.transitionDelay = (i * 0.07) + "s"; });
      check();
    };

    const t = setTimeout(init, 80);
    window.addEventListener("scroll", check, { passive: true });
    return () => { clearTimeout(t); window.removeEventListener("scroll", check); };
  }, [showPhotos]);

  return (
    <main className="bio-page shell" data-screen-label="Bio">
      <header className="bio-hero reveal">
        <h1 className="bio-hero-title">
          {showPhotos
            ? (lang === "es" ? "Yo, pero cuando cierro la compu" : "Me, but when I close the laptop")
            : (lang === "es" ? "Conocéme" : "About me")}
        </h1>
        <button className="bio-toggle-btn interactive" onClick={() => setShowPhotos(v => !v)}>
          <span>{showPhotos
            ? (lang === "es" ? "En línea" : "Back online")
            : (lang === "es" ? "Fuera de oficina" : "Out of office")}</span>
        </button>
      </header>

      <WaveDivider />

      {!showPhotos && (
        <section className="bio-prose-block bio-fade-in">
          <div className="bio-prose-inner">
            <div className="bio-prose-photo">
              <img src="/assets/perfil.png" alt="" />
            </div>
            <div className="bio-prose-text">
              {copy.paragraphs.map((p, i) => (
                <p key={i} className="bio-prose-p">{typeof p === "string" ? p : p.body}</p>
              ))}
            </div>
          </div>
        </section>
      )}

      {showPhotos && photos.length > 0 && (
        <section className="bio-gallery">
          <div className="masonry">
            {photos.map((p, i) => (
              <div key={i} className="masonry-item bio-photo-item bio-photo-stagger">
                <img src={p.src} alt={p.desc || p.caption || ""} style={{ width: "100%", display: "block", borderRadius: "10px" }} />
                <div className="bio-photo-overlay">
                  {p.desc && <p className="bio-photo-desc">{p.desc}</p>}
                </div>
              </div>
            ))}
          </div>
        </section>
      )}
    </main>
  );
}


// ── Contact page ───────────────────────────────────────────────────────
function ContactPage({ copy, lang }) {
  const [name, setName]       = React.useState("");
  const [project, setProject] = React.useState("");
  const [need, setNeed]       = React.useState("");
  const [budget, setBudget]   = React.useState("");
  const [contact, setContact] = React.useState("");
  const [details, setDetails] = React.useState("");
  const [privacy, setPrivacy] = React.useState(false);
  const [status, setStatus]   = React.useState("idle");
  const [tried, setTried]     = React.useState(false);

  const onSubmit = (e) => {
    e.preventDefault();
    if (status === "sending") return;
    if (!name || !project || !need || !budget || !contact || !privacy) {
      setTried(true);
      return;
    }
    setStatus("sending");
    fetch("https://formsubmit.co/ajax/607f780a69a9ee7db0b3e756242d81e1", {
      method: "POST",
      headers: { "Content-Type": "application/json", "Accept": "application/json" },
      body: JSON.stringify({
        Nombre: name,
        Proyecto: project,
        Necesidad: need,
        Presupuesto: budget,
        Contacto: contact,
        Detalles: details || "—",
        _subject: "Nuevo mensaje desde el portfolio · " + name
      })
    })
      .then(r => r.json())
      .then(d => setStatus(d.success ? "sent" : "error"))
      .catch(() => setStatus("error"));
  };

  const reset = () => {
    setName(""); setProject(""); setNeed(""); setBudget("");
    setContact(""); setDetails(""); setPrivacy(false); setStatus("idle");
  };

  const autoGrow = (e) => {
    e.target.style.height = "auto";
    e.target.style.height = e.target.scrollHeight + "px";
  };

  const es = lang === "es";

  const budgetOptions = es
    ? ["UYU 10.000 – 20.000", "UYU 20.000 – 30.000", "UYU 30.000 – 40.000+"]
    : ["UYU 10,000 – 20,000", "UYU 20,000 – 30,000", "UYU 30,000 – 40,000+"];

  return (
    <main className="bio-page shell" data-screen-label="Contact">
      <header className="bio-hero reveal">
        <h1 className="bio-hero-title">
          {es ? "Conversemos" : "Let's talk"}
        </h1>
      </header>

      <WaveDivider />

      {status !== "sent" ? (
        <form className="nlf-form reveal" onSubmit={onSubmit} noValidate>

          <p className="nlf-line">
            <span className="nlf-text">{es ? "¡Buenas! 👋 Soy " : "Hi! 👋 I'm "}</span>
            <input className={"nlf-input interactive" + (tried && !name ? " nlf-input--error" : "")} type="text" value={name} onChange={e => setName(e.target.value)}
              placeholder={es ? "Tu nombre" : "Your name"} required style={{ maxWidth: "110px" }} />
            <span className="nlf-text">{es ? " y formo parte de " : " and I'm part of "}</span>
            <input className={"nlf-input interactive" + (tried && !project ? " nlf-input--error" : "")} type="text" value={project} onChange={e => setProject(e.target.value)}
              placeholder={es ? "Tu proyecto o marca" : "Your project or brand"} required style={{ flex: 1 }} />
            <span className="nlf-text">.</span>
          </p>

          <p className="nlf-line">
            <span className="nlf-text">{es ? "Te escribo porque necesito una mano para " : "I'm writing because I need help with "}</span>
            <input className={"nlf-input interactive" + (tried && !need ? " nlf-input--error" : "")} type="text" value={need} onChange={e => setNeed(e.target.value)}
              placeholder={es ? "¿Qué necesitás diseñar o armar?" : "What do you need?"} required style={{ flex: 1 }} />
            <span className="nlf-text">.</span>
          </p>

          <p className="nlf-line">
            <span className="nlf-text">{es ? "Para esto, cuento con un presupuesto de " : "For this, I have a budget of "}</span>
            <select className={"nlf-select interactive" + (tried && !budget ? " nlf-input--error" : "")} value={budget} onChange={e => setBudget(e.target.value)} required>
              <option value="">{es ? "Seleccioná un rango" : "Select a range"}</option>
              {budgetOptions.map(o => <option key={o} value={o}>{o}</option>)}
            </select>
            <span className="nlf-text">.</span>
          </p>

          <p className="nlf-line">
            <span className="nlf-text">{es ? "Dejo mi contacto: " : "You can reach me at: "}</span>
            <input className={"nlf-input interactive" + (tried && !contact ? " nlf-input--error" : "")} type="text" value={contact} onChange={e => setContact(e.target.value)}
              placeholder={es ? "Tu email o teléfono" : "Your email or phone"} required />
            <span className="nlf-text">{es ? " así seguimos la charla por ahí." : " so we can keep the conversation going."}</span>
          </p>

          <p className="nlf-line nlf-line--block">
            <span className="nlf-text">{es ? "Ah, y te sumo un par de detalles más:" : "Oh, and a few more details:"}</span>
            <textarea className="nlf-textarea interactive" value={details} onChange={e => { setDetails(e.target.value); autoGrow(e); }}
              placeholder={es ? "Escribí lo que quieras acá..." : "Tell me more..."} rows={1} />
          </p>

          <div className="nlf-footer">
            <div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
              <label className="nlf-privacy interactive">
                <input type="checkbox" checked={privacy} onChange={e => setPrivacy(e.target.checked)} />
                <span>{es ? "Acepto la " : "I accept the "}
                  <a href="/pages/terminos.html" className="nlf-privacy-link">{es ? "Política de Privacidad" : "Privacy Policy"}</a>
                </span>
              </label>
              {tried && (
                <span className="nlf-hint">
                  {status === "error"
                    ? (es ? "Algo salió mal. Intentá de nuevo." : "Something went wrong. Try again.")
                    : (!name || !project || !need || !budget || !contact)
                      ? (es ? "No te olvides de completar los campos requeridos y aceptar la política de privacidad." : "Don't forget to fill in all required fields and accept the privacy policy.")
                      : (es ? "No te olvides de aceptar la política de privacidad." : "Don't forget to accept the privacy policy.")}
                </span>
              )}
            </div>
            <button type="submit" className="nlf-submit interactive" disabled={status === "sending"}>
              <span>{status === "sending" ? (es ? "Enviando..." : "Sending...") : (es ? "Enviar" : "Send")}</span>
            </button>
          </div>

        </form>
      ) : (
        <div className="nlf-success reveal">
          <p className="nlf-success-text">{es ? "¡Mensaje recibido! Te escribo pronto." : "Message received! I'll be in touch soon."}</p>
          <button className="nlf-submit interactive" onClick={reset}>
            <span>{es ? "Enviar otro mensaje" : "Send another"}</span>
          </button>
        </div>
      )}
    </main>
  );
}
