// Main sections of the UCreativeMedia site (theme-aware via CSS vars)
const { useState, useEffect, useRef, useMemo } = React;

// ─── Reveal-on-scroll wrapper ──────────────────────────────────────────────
function Reveal({ children, delay = 0, as = "div", className = "", style = {} }) {
  const ref = useRef(null);
  const [shown, setShown] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
      setShown(true);
      return;
    }
    // The observer is ALWAYS registered — it is the backstop for elements that
    // have no layout yet when this effect runs (Babel renders after paint).
    const obs = new IntersectionObserver(
      ([e]) => {if (e.isIntersecting) {setShown(true);obs.disconnect();}},
      { threshold: 0, rootMargin: "0px 0px -12% 0px" }
    );
    obs.observe(el);
    // Measure AFTER layout: anything already in the first viewport animates in
    // on the next frame instead of waiting for a scroll that may never come.
    let raf2 = 0;
    const raf1 = requestAnimationFrame(() => {
      const rect = el.getBoundingClientRect();
      const vh = window.innerHeight || document.documentElement.clientHeight;
      if (rect.top < vh && rect.bottom > 0 && rect.height > 0) {
        raf2 = requestAnimationFrame(() => setShown(true));
      }
    });
    // Last resort: never leave content invisible.
    const tm = setTimeout(() => setShown(true), 900);
    return () => {
      obs.disconnect();
      cancelAnimationFrame(raf1);
      if (raf2) cancelAnimationFrame(raf2);
      clearTimeout(tm);
    };
  }, []);
  const Tag = as;
  return (
    <Tag ref={ref} className={className} style={{
      ...style,
      opacity: shown ? 1 : 0,
      transform: shown ? "none" : "translateY(46px) scale(.985)",
      filter: shown ? "blur(0)" : "blur(6px)",
      willChange: shown ? "auto" : "opacity, transform, filter",
      transition: `opacity 900ms cubic-bezier(.16,.84,.24,1) ${delay}ms, transform 1050ms cubic-bezier(.16,.84,.24,1) ${delay}ms, filter 900ms cubic-bezier(.16,.84,.24,1) ${delay}ms`
    }}>
      {children}
    </Tag>);

}

// ─── Decorative placeholder (subtly striped) ────────────────────────────────
// `surface` = 1 (light/main) or 2 (dark/alt). Stripe + bg vary accordingly.
function PlaceholderCard({ ratio = "4/5", label, surface = 1, badge, accent }) {
  const cardBg = surface === 2 ? "var(--card-bg-2)" : "var(--card-bg-1)";
  const stripeColor = surface === 2 ? "var(--card-stripe-2)" : "var(--card-stripe-1)";
  const border = surface === 2 ? "var(--border-soft-2)" : "var(--border-soft-1)";
  const txt = surface === 2 ? "var(--ink-faint-2)" : "var(--ink-faint-1)";
  return (
    <div className="ph-card" style={{
      position: "relative", aspectRatio: ratio, background: cardBg,
      backgroundImage: `repeating-linear-gradient(135deg, ${stripeColor} 0 12px, transparent 12px 24px)`,
      border: `1px solid ${border}`, borderRadius: 2, overflow: "hidden",
      display: "flex", flexDirection: "column", justifyContent: "space-between",
      padding: 18, transition: "transform 400ms cubic-bezier(.2,.7,.2,1), border-color 300ms",
      cursor: "pointer", color: txt
    }}>
      {badge &&
      <div style={{
        display: "inline-block", alignSelf: "flex-start",
        fontFamily: "Nunito, sans-serif", fontWeight: 600, fontSize: 10, letterSpacing: ".18em",
        textTransform: "uppercase", color: accent,
        border: `1px solid ${accent}`, padding: "4px 8px", borderRadius: 999,
        background: surface === 2 ? "rgba(0,0,0,.3)" : "var(--pill-bg)"
      }}>{badge}</div>
      }
      <div style={{
        display: "flex", alignItems: "center", gap: 8,
        fontFamily: "ui-monospace, 'JetBrains Mono', monospace", fontSize: 11,
        letterSpacing: ".04em"
      }}>
        <span style={{ width: 6, height: 6, borderRadius: 999, background: "currentColor" }} />
        {label}
      </div>
    </div>);

}

// ─── Section heading block ─────────────────────────────────────────────────
// `surface` controls whether ink-1 or ink-2 vars are used.
function SectionHead({ title, sub, surface = 1, accent = "#00bf7a" }) {
  const txt = surface === 2 ? "var(--ink-2)" : "var(--ink-1)";
  const subTxt = surface === 2 ? "var(--ink-soft-2)" : "var(--ink-soft-1)";
  return (
    <div style={{ display: "grid", gridTemplateColumns: "1fr", gap: 20, marginBottom: 64 }}>
      <Reveal>
        <h2 style={{
          margin: 0, fontFamily: "Quicksand, sans-serif", fontWeight: 800,
          fontSize: "clamp(40px, 5.5vw, 76px)", lineHeight: 0.98, letterSpacing: "-0.02em",
          color: txt, textWrap: "balance"
        }}>{title}</h2>
      </Reveal>
      {sub &&
      <Reveal delay={80}>
          <p style={{
          margin: 0, fontFamily: "Nunito, sans-serif", fontWeight: 400, fontSize: 18,
          lineHeight: 1.5, color: subTxt, maxWidth: 560
        }}>{sub}</p>
        </Reveal>
      }
    </div>);

}

// ─── Animated hero graphic — minimalist "in motion" composition ────────────
function HeroMotion({ accent }) {
  // Stacked horizontal bars that breathe in width on staggered loops,
  // with one accent bar pulsing in green. Pure CSS animation = lightweight.
  const bars = [
  { w: 38, top: 8, delay: 0, color: "var(--ink-1)", o: 0.10 },
  { w: 64, top: 14, delay: 0.5, color: "var(--ink-1)", o: 0.14 },
  { w: 22, top: 22, delay: 1.0, color: "var(--ink-1)", o: 0.08 },
  { w: 78, top: 30, delay: 0.3, color: "var(--ink-1)", o: 0.18 },
  { w: 52, top: 38, delay: 0.9, color: "var(--ink-1)", o: 0.12 },
  { w: 32, top: 46, delay: 1.6, color: "var(--ink-1)", o: 0.10 },
  { w: 70, top: 54, delay: 0.7, color: accent, o: 0.95 }, // accent bar
  { w: 28, top: 62, delay: 1.3, color: "var(--ink-1)", o: 0.10 },
  { w: 58, top: 70, delay: 0.2, color: "var(--ink-1)", o: 0.16 },
  { w: 44, top: 78, delay: 0.8, color: "var(--ink-1)", o: 0.12 },
  { w: 24, top: 86, delay: 1.4, color: "var(--ink-1)", o: 0.08 }];

  return (
    <div aria-hidden className="hero-motion" style={{
      position: "absolute", right: "6vw", top: "20vh",
      width: "min(380px, 28vw)", height: "min(420px, 48vh)",
      pointerEvents: "none"
    }}>
      {/* Vertical guide line */}
      <div style={{
        position: "absolute", right: 0, top: 0, bottom: 0, width: 1,
        background: "linear-gradient(180deg, transparent 0%, var(--border-1) 20%, var(--border-1) 80%, transparent 100%)"
      }} />
      {/* Crosshair marker */}
      <div className="hero-cross" style={{
        position: "absolute", right: -4, top: "calc(54% - 4px)",
        width: 9, height: 9, border: `1.5px solid ${accent}`,
        borderRadius: 999, background: "transparent"
      }} />
      {/* Bars */}
      {bars.map((b, i) =>
      <div key={i} style={{
        position: "absolute", right: 14, top: `${b.top}%`,
        height: 2, width: `${b.w}%`, background: b.color, opacity: b.o,
        transformOrigin: "right center",
        animation: `heroBarPulse 5.6s ease-in-out ${b.delay}s infinite`
      }} />
      )}
      {/* Floating circle accent */}
      <div className="hero-orb" style={{
        position: "absolute", right: "32%", top: "12%",
        width: 10, height: 10, borderRadius: 999,
        background: accent, opacity: 0.85
      }} />
    </div>);

}

// ─── Hero ──────────────────────────────────────────────────────────────────
// A video item can be a local file (src) or a platform link (url).
function ytEmbedLoop(url) {
  const id = ytId(url);
  if (!id) return null;
  return `https://www.youtube-nocookie.com/embed/${id}?autoplay=1&mute=1&loop=1&playlist=${id}&controls=0&modestbranding=1&rel=0&playsinline=1&disablekb=1`;
}

// Silent, looping, no chrome — reads as footage, not as a YouTube player.
function LoopFrame({ url, title }) {
  const src = ytEmbedLoop(url);
  if (!src) return null;
  return (
    <div style={{ position: "absolute", inset: 0, overflow: "hidden" }}>
      <iframe src={src} title={title || "video"} loading="lazy" decoding="async" tabIndex={-1}
        allow="autoplay; encrypted-media; picture-in-picture"
        style={{
          position: "absolute", top: "50%", left: "50%",
          width: "100%", height: "100%", minWidth: "178%", minHeight: "178%",
          transform: "translate(-50%,-50%)", border: 0, pointerEvents: "none"
        }} />
    </div>);
}

// Click-to-play: thumbnail + play button; the real player (with sound and
// controls) loads only on click, so nothing from YouTube runs until then.
function ClickFrame({ url, title, accent }) {
  const id = ytId(url);
  const start = ytStart(url);
  const [playing, setPlaying] = useState(false);
  const [thumb, setThumb] = useState(id ? `https://img.youtube.com/vi/${id}/maxresdefault.jpg` : null);
  const [hover, setHover] = useState(false);
  if (!id) return null;
  if (playing) {
    return (
      <iframe src={`https://www.youtube-nocookie.com/embed/${id}?autoplay=1&rel=0&playsinline=1&modestbranding=1${start ? `&start=${start}` : ""}`}
        title={title || "YouTube"}
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        allowFullScreen
        style={{ position: "absolute", inset: 0, width: "100%", height: "100%", border: 0 }} />);
  }
  return (
    <button type="button" onClick={() => setPlaying(true)} aria-label={`Play ${title || "video"}`}
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{
        appearance: "none", border: 0, padding: 0, cursor: "pointer",
        position: "absolute", inset: 0, width: "100%", height: "100%", background: "#000"
      }}>
      {/* maxres isn't generated for every video; YouTube serves a 120px grey stub instead */}
      {thumb &&
        <img src={thumb} alt="" loading="lazy" decoding="async"
          onLoad={(e) => { if (e.currentTarget.naturalWidth <= 120) setThumb(`https://img.youtube.com/vi/${id}/hqdefault.jpg`); }}
          onError={() => setThumb(`https://img.youtube.com/vi/${id}/hqdefault.jpg`)}
          style={{
            position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", display: "block",
            transform: hover ? "scale(1.03)" : "scale(1)", transition: "transform 600ms cubic-bezier(.2,.7,.2,1)"
          }} />
      }
      <span aria-hidden style={{
        position: "absolute", top: "50%", left: "50%",
        transform: `translate(-50%,-50%) scale(${hover ? 1.08 : 1})`,
        width: 68, height: 68, borderRadius: 999,
        background: hover ? accent : "rgba(255,255,255,.92)", boxShadow: "0 6px 20px rgba(0,0,0,.35)",
        display: "flex", alignItems: "center", justifyContent: "center",
        transition: "transform 320ms cubic-bezier(.2,.7,.2,1), background 260ms"
      }}>
        <span style={{
          width: 0, height: 0, borderStyle: "solid", borderWidth: "11px 0 11px 18px",
          borderColor: `transparent transparent transparent ${hover ? "#fff" : "#141414"}`, marginLeft: 4
        }} />
      </span>
    </button>);
}

function VideoSurface({ item, sound }) {
  if (item.src) return <InViewVideo src={item.src} sound={sound} />;
  if (item.url) return <LoopFrame url={item.url} title={item.title} />;
  return null;
}

// ─── Hero reel — clips running in a continuous loop ────────────────────────
function HeroReel({ accent }) {
  const data = window.PORTFOLIO_DATA || {};
  const vids = (((data.chapters || []).find((c) => c.key === "video") || {}).items || [])
    // Portrait clips only — the cards are 9:16; landscape cuts live in the portfolio.
    .filter((v) => v && (v.src || v.url) && ratioNum(v.ratio) < 1);
  const [nx, setNx] = useState(0);
  const [hot, setHot] = useState(-1);

  useEffect(() => {
    let raf = 0;
    const onMove = (e) => {
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(() => {
        setNx((e.clientX / window.innerWidth - 0.5) * 2);
      });
    };
    window.addEventListener("mousemove", onMove, { passive: true });
    return () => { window.removeEventListener("mousemove", onMove); cancelAnimationFrame(raf); };
  }, []);

  if (!vids.length) return null;

  // Each unique clip once (repeated only if there are too few to fill the
  // strip), then doubled so the -50% keyframe lands on an identical frame.
  const uniq = vids.filter((v, i) => vids.findIndex((w) => (w.src || w.url) === (v.src || v.url)) === i);
  const base = [];
  while (base.length < 6) uniq.forEach((v) => base.push(v));
  const track = base.concat(base);

  return (
    <div>
      <style>{`@keyframes reel-run{from{transform:translateX(0)}to{transform:translateX(-50%)}}.reel-track{display:flex;gap:14px;width:max-content;animation:reel-run 42s linear infinite}.reel-mask:hover .reel-track{animation-play-state:paused}@media (prefers-reduced-motion:reduce){.reel-track{animation:none}}`}</style>
      <div className="reel-mask" style={{
        overflow: "hidden",
        maskImage: "linear-gradient(90deg, transparent 0, #000 9%, #000 91%, transparent 100%)",
        WebkitMaskImage: "linear-gradient(90deg, transparent 0, #000 9%, #000 91%, transparent 100%)",
        transform: `translateX(${nx * 10}px)`,
        transition: "transform 460ms cubic-bezier(.2,.7,.2,1)"
      }}>
        <div className="reel-track" style={{ animationDuration: `${base.length * 3.5}s` }}>
          {track.map((v, i) =>
            <div key={i}
              onMouseEnter={() => setHot(i)} onMouseLeave={() => setHot(-1)}
              style={{
              width: "clamp(150px, 15vw, 202px)", flexShrink: 0,
              aspectRatio: "1080 / 1920", borderRadius: 6, overflow: "hidden",
              background: "#000", border: "1px solid var(--border-1)",
              boxShadow: "0 22px 54px rgba(0,0,0,.3)"
            }}>
              <VideoSurface item={v} sound={hot === i} />
            </div>
          )}
        </div>
      </div>
    </div>);
}

function Hero({ t, accent, theme, showContact = true }) {
  return (
    <section id="top" style={{
      minHeight: "100vh", background: "var(--bg-1)", color: "var(--ink-1)",
      display: "flex", flexDirection: "column", justifyContent: "center",
      padding: "124px 6vw 76px",
      position: "relative", overflow: "hidden"
    }}>
      <style>{`.hero-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:clamp(28px,4vw,56px);align-items:center}@media (max-width:900px){.hero-grid{grid-template-columns:1fr;gap:34px}}`}</style>

      <div className="hero-grid">
        <div>

      <Reveal>
        <div style={{
          display: "inline-flex", alignItems: "center", gap: 10,
          fontFamily: "Nunito, sans-serif", fontWeight: 600, fontSize: 12,
          letterSpacing: ".22em", textTransform: "uppercase", color: accent,
          marginBottom: 18
        }}>
          <span style={{ width: 8, height: 8, borderRadius: 999, background: accent, boxShadow: `0 0 0 4px ${accent}22` }} />
          {t.hero.eyebrow}
        </div>
      </Reveal>

      <Reveal delay={100}>
        <h1 style={{
          margin: 0, fontFamily: "Quicksand, sans-serif", fontWeight: 800,
          fontSize: "clamp(44px, 7.4vw, 112px)", lineHeight: 0.92, letterSpacing: "-0.04em",
          color: "var(--ink-1)", textWrap: "balance"
        }}>
          {t.hero.titleA}
          <br />
          <span style={{ color: accent, fontStyle: "italic", fontWeight: 800 }}>
            {Array.from(t.hero.titleB).map((ch, i) =>
            <span key={i}
            className={"hero-moves-letter" + (ch === " " ? " space" : "")}
            style={{ animationDelay: `${i * 70}ms` }}>
                {ch === " " ? "\u00a0" : ch}
              </span>
            )}
          </span>
        </h1>
      </Reveal>

      <Reveal delay={220}>
        <div style={{
          display: "flex", justifyContent: "flex-start", alignItems: "flex-end",
          gap: 40, marginTop: 30, flexWrap: "wrap"
        }}>
          <div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
            <a href="#portfolio" className="btn btn-primary" style={{
              fontFamily: "Quicksand, sans-serif", fontWeight: 600, fontSize: 15,
              letterSpacing: ".02em", color: "var(--bg-1)", background: "var(--ink-1)",
              padding: "16px 28px", borderRadius: 999, textDecoration: "none",
              display: "inline-flex", alignItems: "center", gap: 10,
              border: "1px solid var(--ink-1)",
              transition: "transform 250ms, background 250ms"
            }}>
              {t.hero.cta}
              <span style={{ fontSize: 16, lineHeight: 1 }}>↓</span>
            </a>
            {showContact && <a href="#contact" className="btn btn-ghost" style={{
              fontFamily: "Quicksand, sans-serif", fontWeight: 600, fontSize: 15,
              letterSpacing: ".02em", color: "var(--ink-1)", background: "transparent",
              padding: "16px 28px", borderRadius: 999, textDecoration: "none",
              border: "1px solid var(--border-1)",
              transition: "border-color 250ms, color 250ms"
            }}>{t.hero.cta2}</a>}
          </div>
        </div>
      </Reveal>

        </div>

        <div className="hero-reel">
          <Reveal delay={180}>
            <HeroReel accent={accent} />
          </Reveal>
        </div>
      </div>

      {/* Scroll cue */}
      <div aria-hidden style={{
        position: "absolute", bottom: 14, right: "6vw",
        display: "flex", alignItems: "center", gap: 12,
        fontFamily: "ui-monospace, monospace", fontSize: 11, letterSpacing: ".22em",
        textTransform: "uppercase", color: "var(--ink-faint-1)"
      }}>
        <span>scroll</span>
        <span className="scroll-line" style={{
          display: "inline-block", width: 40, height: 1, background: "var(--ink-faint-1)"
        }} />
      </div>
    </section>);

}

// ─── Service icons — minimalist line graphics ──────────────────────────────
function ServiceIcon({ kind, accent }) {
  const ink = "var(--ink-1)";
  const common = { width: 56, height: 56, viewBox: "0 0 56 56", fill: "none", "aria-hidden": true };
  if (kind === "video") {
    // Editing timeline: two clip blocks on stacked tracks + accent playhead
    return (
      <svg {...common}>
        <rect x="3" y="16" width="28" height="9" rx="2" stroke={ink} strokeWidth="2" />
        <rect x="25" y="31" width="28" height="9" rx="2" stroke={ink} strokeWidth="2" />
        <line x1="19" y1="7" x2="19" y2="46" stroke={accent} strokeWidth="2" strokeLinecap="round" />
        <path d="M14 6 H24 L19 12 Z" fill={accent} />
        <circle cx="19" cy="49" r="2.4" fill={accent} />
      </svg>);

  }
  // Graphic design: bezier curve with anchor nodes + accent control handles
  return (
    <svg {...common}>
      <path d="M6 48 C 10 24, 30 34, 50 9" stroke={ink} strokeWidth="2" strokeLinecap="round" />
      <line x1="6" y1="48" x2="19" y2="39" stroke={accent} strokeWidth="1.5" />
      <circle cx="19" cy="39" r="3" fill={accent} />
      <line x1="50" y1="9" x2="37" y2="18" stroke={accent} strokeWidth="1.5" />
      <circle cx="37" cy="18" r="3" fill={accent} />
      <rect x="2.5" y="44.5" width="7" height="7" rx="1" fill="var(--bg-1)" stroke={ink} strokeWidth="2" />
      <rect x="46.5" y="5.5" width="7" height="7" rx="1" fill="var(--bg-1)" stroke={ink} strokeWidth="2" />
    </svg>);

}

// ─── Services ──────────────────────────────────────────────────────────────
function Services({ t, accent }) {
  const icons = ["design", "video"];
  return (
    <section id="services" style={{
      background: "var(--bg-1)", color: "var(--ink-1)", padding: "120px 6vw"
    }}>
      <SectionHead title={t.services.title} accent={accent} />
      <div style={{
        display: "grid",
        gridTemplateColumns: "repeat(2, 1fr)",
        gap: 0,
        borderTop: "1px solid var(--border-1)"
      }} className="services-grid">
        {t.services.items.map((it, i) =>
        <Reveal key={i} delay={i * 80}>
            <div className="service" style={{
            padding: "32px 28px 40px",
            borderBottom: "1px solid var(--border-1)",
            borderRight: i === 0 ? "1px solid var(--border-1)" : "none",
            minHeight: 200, display: "flex", flexDirection: "column", justifyContent: "flex-start",
            gap: 18, position: "relative", transition: "background 300ms"
          }}>
              <div style={{ display: "flex", alignItems: "center" }}>
                <ServiceIcon kind={icons[i] || "design"} accent={accent} />
              </div>
              <div>
                <h3 style={{
                margin: "0 0 12px", fontFamily: "Quicksand, sans-serif", fontWeight: 700,
                fontSize: 28, letterSpacing: "-0.01em", color: "var(--ink-1)"
              }}>{it.t}</h3>
                <p style={{
                margin: 0, fontFamily: "Nunito, sans-serif", fontSize: 15, lineHeight: 1.55,
                color: "var(--ink-mute-1)"
              }}>{it.d}</p>
              </div>
            </div>
          </Reveal>
        )}
      </div>
    </section>);

}

// ─── Collaboration credit chip (optional, top of card) ─────────────────────
function CreditChip({ prefix, name, accent }) {
  if (!name) return null;
  return (
    <div style={{
      position: "absolute", top: 12, left: 12, zIndex: 3,
      display: "inline-flex", alignItems: "center", gap: 7,
      padding: "5px 11px 5px 9px", borderRadius: 999, maxWidth: "82%",
      background: "rgba(0,0,0,.58)", backdropFilter: "blur(5px)", WebkitBackdropFilter: "blur(5px)",
      color: "#fff", fontFamily: "Nunito, sans-serif", fontSize: 10.5, lineHeight: 1.2,
      letterSpacing: ".01em", border: "1px solid rgba(255,255,255,.12)"
    }}>
      <span style={{ width: 6, height: 6, borderRadius: 999, background: accent, flex: "0 0 auto" }} />
      <span style={{ whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
        {prefix} <strong style={{ fontWeight: 800 }}>{name}</strong>
      </span>
    </div>);

}

// ─── Media card — renders a real image/video, or falls back to placeholder ──
function isVideoSrc(src) {return /\.(mp4|webm|mov|m4v)$/i.test(src || "");}

function MediaCard({ item, label, surface = 1, accent, creditPrefix }) {
  const { src, title, ratio = "4/5", credit } = item || {};
  const vidRef = useRef(null);

  // No file yet → show the elegant placeholder slot
  if (!src) {
    return <PlaceholderCard ratio={ratio} label={label} badge={title} surface={surface} accent={accent} />;
  }

  const video = isVideoSrc(src);
  const border = surface === 2 ? "var(--border-2)" : "var(--border-1)";

  return (
    <div className="ph-card" style={{
      position: "relative", aspectRatio: ratio, borderRadius: 2, overflow: "hidden",
      border: `1px solid ${border}`, cursor: "pointer", background: "#000",
      transition: "transform 400ms cubic-bezier(.2,.7,.2,1)"
    }}
    onMouseEnter={() => {if (vidRef.current) vidRef.current.play().catch(() => {});}}
    onMouseLeave={() => {if (vidRef.current) {vidRef.current.pause();}}}>
      {video ?
      <video ref={vidRef} src={src} muted loop playsInline preload="none"
      style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} /> :
      <img src={src} alt={title || ""} loading="lazy" decoding="async"
      style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />}

      {/* Collaboration credit */}
      <CreditChip prefix={creditPrefix} name={credit} accent={accent} />

      {/* Play glyph for video (top-right so it never clashes with the credit) */}
      {video &&
      <div style={{
        position: "absolute", right: 14, top: 14, width: 30, height: 30, borderRadius: 999,
        background: "rgba(0,0,0,.55)", display: "flex", alignItems: "center", justifyContent: "center",
        backdropFilter: "blur(4px)", WebkitBackdropFilter: "blur(4px)"
      }}>
          <span style={{ color: "#fff", fontSize: 11, marginLeft: 2 }}>▶</span>
        </div>}

      {/* Title overlay */}
      {title &&
      <div style={{
        position: "absolute", left: 0, right: 0, bottom: 0, padding: "20px 16px 13px",
        background: "linear-gradient(0deg, rgba(0,0,0,.62) 0%, rgba(0,0,0,0) 100%)",
        color: "#fff", fontFamily: "Quicksand, sans-serif", fontWeight: 600, fontSize: 13,
        letterSpacing: ".01em"
      }}>{title}</div>}
    </div>);

}

// ─── Carousel card — multi-slide piece with inline nav + fullscreen viewer ──
function CarouselGlyph({ size = 13, color = "#fff" }) {
  return (
    <svg width={size} height={size} viewBox="0 0 16 16" fill="none" aria-hidden>
      <rect x="4.5" y="2.5" width="7" height="11" rx="1.5" stroke={color} strokeWidth="1.4" />
      <path d="M2.5 5 V11" stroke={color} strokeWidth="1.4" strokeLinecap="round" opacity="0.7" />
      <path d="M13.5 5 V11" stroke={color} strokeWidth="1.4" strokeLinecap="round" opacity="0.7" />
    </svg>);

}

function CarouselLightbox({ slides, idx, setIdx, title, accent, onClose, credit, creditPrefix }) {
  const n = slides.length;
  const go = (d) => setIdx((p) => (p + d + n) % n);
  useEffect(() => {
    const onKey = (e) => {
      if (e.key === "Escape") onClose();else
      if (e.key === "ArrowRight") go(1);else
      if (e.key === "ArrowLeft") go(-1);
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [n]);

  const navBtn = {
    position: "absolute", top: "50%", transform: "translateY(-50%)",
    width: 52, height: 52, borderRadius: 999, border: "1px solid rgba(255,255,255,.25)",
    background: "rgba(255,255,255,.08)", color: "#fff", fontSize: 26, lineHeight: 1,
    cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center",
    backdropFilter: "blur(6px)", WebkitBackdropFilter: "blur(6px)", transition: "background .2s"
  };

  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, zIndex: 1000,
      background: "rgba(8,10,18,.93)", backdropFilter: "blur(10px)", WebkitBackdropFilter: "blur(10px)",
      display: "flex", alignItems: "center", justifyContent: "center", padding: "5vh 5vw"
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        position: "absolute", top: 26, left: 28, color: "rgba(255,255,255,.85)",
        fontFamily: "ui-monospace, monospace", fontSize: 13, letterSpacing: ".1em",
        display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap", maxWidth: "60vw"
      }}>
        <CarouselGlyph /> {title}
        {credit &&
        <span style={{
          display: "inline-flex", alignItems: "center", gap: 6, padding: "3px 10px",
          borderRadius: 999, background: "rgba(255,255,255,.1)", fontSize: 11,
          fontFamily: "Nunito, sans-serif", letterSpacing: ".01em"
        }}>
            <span style={{ width: 5, height: 5, borderRadius: 999, background: accent }} />
            {creditPrefix} <strong style={{ fontWeight: 800 }}>{credit}</strong>
          </span>}
      </div>
      <div onClick={(e) => e.stopPropagation()} style={{
        position: "absolute", top: 26, right: 70, color: "rgba(255,255,255,.85)",
        fontFamily: "ui-monospace, monospace", fontSize: 13
      }}>{idx + 1} / {n}</div>
      <button onClick={onClose} style={{
        position: "absolute", top: 22, right: 24, width: 38, height: 38, borderRadius: 999,
        border: 0, background: "rgba(255,255,255,.1)", color: "#fff", fontSize: 17, cursor: "pointer"
      }}>✕</button>

      <button onClick={(e) => {e.stopPropagation();go(-1);}} style={{ ...navBtn, left: "3vw" }}>‹</button>
      <img src={slides[idx]} alt={title || ""} onClick={(e) => e.stopPropagation()} style={{
        maxHeight: "88vh", maxWidth: "min(620px, 88vw)", objectFit: "contain",
        borderRadius: 6, boxShadow: "0 24px 90px rgba(0,0,0,.55)", display: "block"
      }} />
      <button onClick={(e) => {e.stopPropagation();go(1);}} style={{ ...navBtn, right: "3vw" }}>›</button>

      <div onClick={(e) => e.stopPropagation()} style={{
        position: "absolute", bottom: "3.5vh", left: 0, right: 0,
        display: "flex", justifyContent: "center", gap: 7
      }}>
        {slides.map((_, i) =>
        <button key={i} onClick={() => setIdx(i)} aria-label={`Slide ${i + 1}`} style={{
          width: i === idx ? 26 : 8, height: 8, borderRadius: 999, border: 0, padding: 0, cursor: "pointer",
          background: i === idx ? accent : "rgba(255,255,255,.4)", transition: "width .25s, background .25s"
        }} />
        )}
      </div>
    </div>);

}

function CarouselCard({ item, label, surface = 1, accent, creditPrefix }) {
  const { slides = [], title, ratio = "4/5", credit } = item || {};
  const [idx, setIdx] = useState(0);
  const n = slides.length;

  if (!n) {
    return <PlaceholderCard ratio={ratio} label={label} badge={title} surface={surface} accent={accent} />;
  }

  const border = surface === 2 ? "var(--border-2)" : "var(--border-1)";
  const go = (d, e) => {if (e) e.stopPropagation();setIdx((p) => (p + d + n) % n);};

  return (
    <div className="ph-card carousel-card" style={{
      position: "relative", aspectRatio: ratio, borderRadius: 2, overflow: "hidden",
      border: `1px solid ${border}`, background: "#0e1530",
      transition: "transform 400ms cubic-bezier(.2,.7,.2,1)"
    }}>
      <img src={slides[idx]} alt={title || ""} loading="lazy" decoding="async"
      style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />

      {/* Collaboration credit */}
      <CreditChip prefix={creditPrefix} name={credit} accent={accent} />

      {/* carousel badge */}
        <div style={{
        position: "absolute", top: 12, right: 12, display: "flex", alignItems: "center", gap: 6,
        padding: "5px 9px", borderRadius: 999, background: "rgba(0,0,0,.55)",
        backdropFilter: "blur(4px)", WebkitBackdropFilter: "blur(4px)",
        color: "#fff", fontFamily: "ui-monospace, monospace", fontSize: 11
      }}>
          <CarouselGlyph /> {idx + 1}/{n}
        </div>

        {/* hover arrows */}
        <button className="car-arrow" onClick={(e) => go(-1, e)} aria-label="Anterior" style={{ left: 10 }}>‹</button>
        <button className="car-arrow" onClick={(e) => go(1, e)} aria-label="Următor" style={{ right: 10 }}>›</button>

        {/* dots */}
        <div style={{
        position: "absolute", left: 0, right: 0, bottom: 10,
        display: "flex", justifyContent: "center", gap: 5
      }}>
          {slides.map((_, i) =>
        <span key={i} style={{
          width: i === idx ? 16 : 5, height: 5, borderRadius: 999,
          background: i === idx ? accent : "rgba(255,255,255,.55)",
          transition: "width .25s, background .25s"
        }} />
        )}
        </div>
    </div>);

}

// ─── Presentation helpers ──────────────────────────────────────────────────
function ratioNum(r) {
  const m = String(r || "4/5").match(/^\s*([\d.]+)\s*\/\s*([\d.]+)\s*$/);
  if (m) return parseFloat(m[1]) / parseFloat(m[2]);
  const f = parseFloat(r);
  return isFinite(f) && f > 0 ? f : 0.8;
}

// Titles are authored as "Brand — descriptor"; split so the caption can sit
// under the piece with the brand leading, the way reference layouts do it.
function splitTitle(title) {
  const t = String(title || "");
  const m = t.split(/\s+[—–]\s+/);
  return m.length > 1 ? { brand: m[0], desc: m.slice(1).join(" — ") } : { brand: t, desc: "" };
}

// Video that plays itself while on screen — no click needed.
// Nothing is fetched until it actually scrolls into view, so a page with many
// clips never blocks its own load event.
// One hidden <video> per unique src, shared by every card that shows it; cards
// paint its frames onto their own canvas. However many cards repeat a clip,
// only one network stream exists, so images never starve for connections.
const videoHub = new Map();
function hubJoin(src, canvas) {
  let h = videoHub.get(src);
  if (!h) {
    const v = document.createElement("video");
    v.muted = true; v.loop = true; v.playsInline = true; v.preload = "auto";
    h = { v, views: new Set(), raf: 0 };
    const tick = () => {
      if (h.v.readyState >= 2) h.views.forEach((c) => {
        const vw = h.v.videoWidth, vh = h.v.videoHeight, cw = c.width, ch = c.height;
        if (!vw || !cw) return;
        const s = Math.max(cw / vw, ch / vh), w = vw * s, hh = vh * s;
        c.getContext("2d").drawImage(h.v, (cw - w) / 2, (ch - hh) / 2, w, hh);
      });
      h.raf = requestAnimationFrame(tick);
    };
    h.tick = tick;
    videoHub.set(src, h);
  }
  h.views.add(canvas);
  if (h.views.size === 1) {
    if (!h.v.getAttribute("src")) h.v.src = src;
    h.v.play().catch(() => {});
    h.raf = requestAnimationFrame(h.tick);
  }
}
function hubLeave(src, canvas) {
  const h = videoHub.get(src);
  if (!h) return;
  h.views.delete(canvas);
  if (!h.views.size) { h.v.muted = true; h.v.pause(); cancelAnimationFrame(h.raf); }
}
// Sound only while a card is hovered. Browsers allow unmuting only after the
// visitor has interacted with the page; if refused, fall back to silent play.
function hubSound(src, on) {
  const h = videoHub.get(src);
  if (!h) return;
  h.v.muted = !on;
  if (on) h.v.play().catch(() => { h.v.muted = true; h.v.play().catch(() => {}); });
}

function InViewVideo({ src, sound = false }) {
  const ref = useRef(null);
  useEffect(() => {
    const c = ref.current;
    if (!c) return;
    const fit = () => {
      const r = c.getBoundingClientRect(), d = Math.min(window.devicePixelRatio || 1, 2);
      c.width = Math.round(r.width * d); c.height = Math.round(r.height * d);
    };
    let io, on = false, dead = false;
    const start = () => {
      if (dead) return;
      io = new IntersectionObserver(([e]) => {
        if (e.isIntersecting && !on) { fit(); on = true; hubJoin(src, c); }
        else if (!e.isIntersecting && on) { on = false; hubLeave(src, c); }
      }, { threshold: 0.1 });
      io.observe(c);
    };
    if (document.readyState === "complete") start();
    else window.addEventListener("load", start, { once: true });
    return () => { dead = true; window.removeEventListener("load", start); io && io.disconnect(); if (on) hubLeave(src, c); };
  }, [src]);
  useEffect(() => {
    if (!sound) return;
    hubSound(src, true);
    return () => hubSound(src, false);
  }, [src, sound]);
  return <canvas ref={ref} style={{ width: "100%", height: "100%", display: "block", background: "#000" }} />;
}

// One piece: frame on top, caption underneath (brand, then descriptor).
function Piece({ item, accent, creditPrefix, label, noCaption }) {
  const { src, url, title, ratio = "4/5", credit } = item || {};
  const video = (src && isVideoSrc(src)) || !!url;
  const has = !!(src || url);
  const { brand, desc } = splitTitle(title);
  const [hover, setHover] = useState(false);
  return (
    <figure
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{ margin: 0, display: "flex", flexDirection: "column", gap: 14, minWidth: 0 }}>
      <div style={{
        position: "relative", aspectRatio: String(ratio), overflow: "hidden",
        background: video ? "#000" : item.bg ||
          (item.fit === "contain" ? "transparent" : src ? "var(--piece-paper)" : "var(--card-bg-2)"),
        border: item.fit === "contain" ? "1px solid transparent" : "1px solid var(--border-2)",
        borderRadius: 3,
        transform: hover ? "translateY(-6px) scale(1.035)" : "none",
        zIndex: hover ? 2 : 1,
        transition: "transform 520ms cubic-bezier(.2,.7,.2,1), box-shadow 520ms",
        boxShadow: hover ? "0 26px 60px rgba(0,0,0,.34)" : "none"
      }}>
        {has ?
          video ? <VideoSurface item={item} sound={hover} /> :
          <img src={src} alt={title || ""} loading="lazy" decoding="async"
            style={{
              width: "100%", height: "100%", display: "block",
              objectFit: item.fit || "cover",
              padding: item.fit === "contain" ? (item.pad != null ? item.pad : "7%") : 0,
              boxSizing: "border-box"
            }} /> :
          <div style={{
            position: "absolute", inset: 0,
            backgroundImage: "repeating-linear-gradient(135deg, var(--card-stripe-2) 0 14px, transparent 14px 28px)",
            display: "flex", alignItems: "center", justifyContent: "center",
            fontFamily: "ui-monospace, monospace", fontSize: 11, letterSpacing: ".16em",
            textTransform: "uppercase", color: "var(--ink-faint-2)", textAlign: "center", padding: 16
          }}>{label}</div>
        }
        <CreditChip prefix={creditPrefix} name={credit} accent={accent} />
      </div>
      {!noCaption && (brand || desc) &&
        <figcaption style={{
          display: "flex", flexDirection: "column", gap: 3, paddingRight: 8
        }}>
          <span style={{
            fontFamily: "Quicksand, sans-serif", fontWeight: 700, fontSize: 15,
            letterSpacing: "-0.01em", color: "var(--ink-2)", lineHeight: 1.25
          }}>{brand}</span>
          {desc &&
            <span style={{
              fontFamily: "Nunito, sans-serif", fontSize: 13,
              color: "var(--ink-soft-2)", lineHeight: 1.4
            }}>{desc}</span>
          }
        </figcaption>
      }
    </figure>);
}

// A carousel shown as a deck: each page laid slightly over the previous one.
// The overlap is FIXED so the deck never changes size — hovering only lifts the
// page under the cursor (transform only, so neighbouring decks can't be pushed).
function CarouselStack({ item, accent, creditPrefix }) {
  const { slides = [], title, ratio = "4/5", credit } = item || {};
  const n = slides.length;
  const [lift, setLift] = useState(-1);
  const { brand, desc } = splitTitle(title);
  if (!n) return null;
  const o = Math.min(0.42, 0.2 + n * 0.03);
  const w = 100 / (1 + (n - 1) * (1 - o));
  return (
    <figure style={{ margin: 0, width: "100%" }}>
      <div style={{ display: "flex", width: "100%", alignItems: "flex-start" }}>
        {slides.map((sl, i) =>
          <div key={i}
            onMouseEnter={() => setLift(i)} onMouseLeave={() => setLift(-1)}
            style={{
              width: `${w}%`, flexShrink: 0, aspectRatio: String(ratio),
              marginLeft: i ? `${-(o * w)}%` : 0,
              zIndex: lift === i ? n + 1 : i + 1,
              position: "relative", overflow: "hidden", borderRadius: 3,
              border: "1px solid rgba(255,255,255,.16)", background: "#000",
              boxShadow: lift === i ? "-10px 0 46px rgba(0,0,0,.6)" :
                i ? "-18px 0 40px rgba(0,0,0,.5)" : "0 18px 40px rgba(0,0,0,.35)",
              transform: lift === i ? "translateY(-10px) scale(1.02)" : "none",
              transition: "transform 360ms cubic-bezier(.2,.7,.2,1), box-shadow 360ms"
            }}>
            <img src={sl} alt={i === 0 ? title || "" : ""} loading="lazy" decoding="async"
              style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
          </div>
        )}
      </div>
    </figure>);
}

// Continuous name strip — the brands the work was made for, always in motion.
function ClientMarquee({ names, accent }) {
  if (!names.length) return null;
  const run = names.concat(names);
  return (
    <div style={{
      background: "var(--bg-1)", borderTop: "1px solid var(--border-soft-1)",
      borderBottom: "1px solid var(--border-soft-1)",
      padding: "20px 0", overflow: "hidden", position: "relative"
    }}>
      <style>{`@keyframes mq-run{from{transform:translateX(0)}to{transform:translateX(-50%)}}.mq-track{display:flex;width:max-content;animation:mq-run 46s linear infinite}@media (prefers-reduced-motion:reduce){.mq-track{animation:none}}`}</style>
      <div className="mq-track">
        {run.map((nm, i) =>
          <span key={i} style={{
            display: "inline-flex", alignItems: "center", gap: 26, paddingRight: 26,
            fontFamily: "Quicksand, sans-serif", fontWeight: 700, fontSize: 15,
            letterSpacing: ".02em", color: "var(--ink-mute-1)", whiteSpace: "nowrap"
          }}>
            {nm}
            <span style={{
              width: 5, height: 5, borderRadius: 999, background: accent, flexShrink: 0
            }} />
          </span>
        )}
      </div>
    </div>);
}

// A deck of flat pieces laid over each other; click brings one to the front.
function PieceDeck({ items, accent }) {
  const n = items.length;
  const [front, setFront] = useState(-1);
  const [hover, setHover] = useState(-1);
  if (!n) return null;
  // Pages share a HEIGHT and keep their own true aspect, so every page fills
  // its frame; widths are expressed as a share of the total laid-out width.
  const GAP = 1.6; // % of the row, between pages
  const ratios = items.map((it) => ratioNum(it.ratio));
  const total = ratios.reduce((a, r) => a + r, 0);
  const avail = 100 - GAP * (n - 1);
  return (
    <div style={{ display: "flex", width: "100%", alignItems: "flex-start" }}>
      {items.map((it, i) => {
        const up = front === i;
        const lift = hover === i || up;
        const w = ratios[i] / total * avail;
        return (
          <div key={i}
            onClick={() => setFront(up ? -1 : i)}
            onMouseEnter={() => setHover(i)} onMouseLeave={() => setHover(-1)}
            style={{
              width: `${w}%`, flexShrink: 0, aspectRatio: String(it.ratio || "3/2"),
              marginLeft: i ? `${GAP}%` : 0,
              zIndex: up ? n + 2 : hover === i ? n + 1 : i + 1,
              position: "relative", overflow: "hidden", borderRadius: 3,
              cursor: "pointer",
              boxShadow: "0 16px 38px rgba(0,0,0,.3)",
              transform: lift ? "translateY(-10px) scale(1.03)" : "none",
              filter: front > -1 && !up ? "brightness(.72)" : "none",
              transition: "transform 420ms cubic-bezier(.2,.7,.2,1), filter 420ms"
            }}>
            <img src={it.src} alt={it.title || ""} loading="lazy" decoding="async"
              style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
          </div>);
      })}
    </div>);
}

// One chapter's grid, with an optional "more / less" gate (video, social).
function ChapterGrid({ ch, accent, t, stacks }) {
  const isVideo = ch.key === "video";
  const noCaption = ch.key === "social" || ch.key === "ambalaje" || ch.key === "print";
  const deck = ch.pieces.filter((p) => p.deck);
  // In the video chapter, landscape cuts close the chapter as a wide pair;
  // the vertical grid (and its "more" gate) holds only the portrait clips.
  const wide = isVideo ? ch.pieces.filter((p) => ratioNum(p.ratio) > 1.2) : [];
  const rest = isVideo ? ch.pieces.filter((p) => ratioNum(p.ratio) <= 1.2) :
    ch.pieces.filter((p) => !p.deck);
  // Chapters that show a "more / less" gate, and how many pieces stay visible.
  const GATE = isVideo ? 6 : ch.key === "social" ? 6 : 0;
  const [open, setOpen] = useState(false);
  const gated = GATE > 0 && rest.length > GATE && !open;
  const btnRef = useRef(null);
  // Collapsing removes a lot of height; keep the button where the eye is.
  const toggle = () => {
    setOpen((o) => !o);
    if (open) requestAnimationFrame(() => btnRef.current &&
      btnRef.current.scrollIntoView({ block: "center", behavior: "smooth" }));
  };
  const shown = gated ? rest.slice(0, GATE) : rest;
  return (
    <React.Fragment>
      {deck.length > 0 &&
        <Reveal>
          <div style={{ marginBottom: 56 }}>
            <PieceDeck items={deck} accent={accent} />
          </div>
        </Reveal>
      }
      <div className={ch.key === "print" ? "grid-print" : isVideo && !stacks ? "grid-video" : ""} style={{
        display: "grid",
        gridTemplateColumns: stacks ? "1fr" :
          isVideo ? "repeat(6, minmax(0, 1fr))" :
          ch.key === "social" ? "repeat(3, minmax(0, 1fr))" :
          ch.key === "print" ? "repeat(auto-fill, minmax(min(400px, 100%), 1fr))" :
          "repeat(auto-fill, minmax(min(290px, 100%), 1fr))",
        gap: stacks ? 72 : "40px 24px", alignItems: "start"
      }}>
        {shown.map((p, pi) =>
          <Reveal key={pi} delay={Math.min(pi, 5) * 90}
            className={
              // print: tall pieces span two rows, so two landscape pieces stack beside them
              ch.key === "print" && ratioNum(p.ratio) < 1 ? "print-tall" :
              !stacks && ch.key !== "social" && ch.key !== "video" && ch.key !== "print" && ratioNum(p.ratio) > 1.2 ? "piece-wide" : ""}>
            {p.stack ?
              <CarouselStack item={p} accent={accent} creditPrefix={t.portfolio.collab} /> :
              <Piece item={p} accent={accent} creditPrefix={t.portfolio.collab}
                label={t.portfolio.placeholder} noCaption={noCaption} />}
          </Reveal>
        )}
      </div>
      {wide.length > 0 && !gated &&
        <div className="grid-video-wide" style={{
          display: "grid", gap: "34px 24px", marginTop: 52
        }}>
          {wide.map((p, i) =>
            <Reveal key={"w" + i} delay={i * 110}>
              <Piece item={p} accent={accent} creditPrefix={t.portfolio.collab}
                label={t.portfolio.placeholder} noCaption={noCaption} />
            </Reveal>
          )}
        </div>
      }
      {GATE > 0 && rest.length > GATE &&
        <div ref={btnRef} style={{ display: "flex", justifyContent: "center", marginTop: 40 }}>
          <button onClick={toggle} aria-expanded={open} style={{
            display: "inline-flex", alignItems: "center", gap: 11,
            background: "transparent", color: "var(--ink-2)", cursor: "pointer",
            fontFamily: "Nunito, sans-serif", fontWeight: 800, fontSize: 12,
            letterSpacing: ".16em", textTransform: "uppercase",
            border: "1px solid var(--border-2)", borderRadius: 999,
            padding: "15px 30px", transition: "border-color 260ms, color 260ms"
          }}
            onMouseEnter={(e) => { e.currentTarget.style.borderColor = accent; }}
            onMouseLeave={(e) => { e.currentTarget.style.borderColor = "var(--border-2)"; }}>
            <span aria-hidden style={{
              width: 0, height: 0, borderStyle: "solid", borderWidth: "5px 0 5px 8px",
              borderColor: `transparent transparent transparent ${accent}`,
              transform: open ? "rotate(-90deg)" : "rotate(90deg)", transition: "transform 260ms"
            }} />
            {isVideo ? (open ? t.portfolio.less : t.portfolio.more) :
              (open ? t.portfolio.lessPosts : t.portfolio.morePosts)}
          </button>
        </div>
      }
    </React.Fragment>);
}

// ─── Portfolio as one continuous presentation (no tabs, no arrows) ─────────
function Portfolio({ t, accent, density, lang }) {
  const data = window.PORTFOLIO_DATA || {};
  const en = lang === "en";

  const chapters = (data.chapters || []).map((ch) => {
    let pieces = [];
    (ch.items || []).forEach((it) => {
      if (!it) return;
      if (it.slides) { if (it.slides.length) pieces.push({ ...it, stack: true }); }
      else if (it.src || it.url) pieces.push(it);
    });
    if (!pieces.some((p) => p.stack) && ch.key !== "social" && ch.key !== "video" && ch.key !== "print") {
      pieces = pieces.slice().sort((a, b) => ratioNum(a.ratio) - ratioNum(b.ratio));
    }
    return { key: ch.key, label: en ? ch.labelEn || ch.label : ch.label, pieces };
  }).filter((ch) => ch.pieces.length);

  return (
    <section id="portfolio" style={{
      background: "var(--bg-2)", color: "var(--ink-2)",
      padding: "104px 0 130px", borderTop: "1px solid var(--border-soft-2)"
    }}>
      <style>{`@media (min-width:700px){.piece-wide{grid-column:span 2}}
.grid-print{grid-template-columns:repeat(2,minmax(0,1fr))!important}
@media (min-width:1040px){.grid-print{grid-template-columns:repeat(3,minmax(0,1fr))!important}}
@media (max-width:620px){.grid-print{grid-template-columns:1fr!important}}
@media (min-width:621px){.grid-print .print-tall{grid-row:span 2}}
.grid-video-wide{grid-template-columns:repeat(2,minmax(0,1fr))}
@media (max-width:720px){.grid-video-wide{grid-template-columns:1fr}}
@media (max-width:1040px){.grid-video{grid-template-columns:repeat(4,minmax(0,1fr))!important}}
@media (max-width:720px){.grid-video{grid-template-columns:repeat(3,minmax(0,1fr))!important}}
@media (max-width:480px){.grid-video{grid-template-columns:repeat(2,minmax(0,1fr))!important}}`}</style>

      {/* section opener — badge left, collaborators right */}
      <div style={{ padding: "0 6vw" }}>
        <Reveal>
          <div style={{
            display: "flex", alignItems: "center", justifyContent: "space-between",
            gap: 24, flexWrap: "wrap"
          }}>
            <span style={{
              display: "inline-flex", alignItems: "center", gap: 9,
              background: accent, color: "#0d0d0f",
              fontFamily: "Nunito, sans-serif", fontWeight: 800, fontSize: 11,
              letterSpacing: ".2em", textTransform: "uppercase",
              padding: "8px 15px", borderRadius: 999, flexShrink: 0
            }}>
              <span style={{
                width: 5, height: 5, borderRadius: 999, background: "#0d0d0f", flexShrink: 0
              }} />
              {t.nav.portfolio}
            </span>

            <div style={{
              display: "flex", alignItems: "center", gap: 16, flexWrap: "wrap",
              justifyContent: "flex-end"
            }}>
              <span style={{
                fontFamily: "Nunito, sans-serif", fontSize: 14.5, lineHeight: 1.45,
                color: "var(--ink-soft-2)", maxWidth: 260, textAlign: "right"
              }}>{t.portfolio.collabNote}</span>
              <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
                {[
                  { src: "assets/logos/collab-crowne.png", alt: "Crowne Marketing", h: 30 },
                  { src: "assets/logos/collab-evomonday.png", alt: "EvoMonday", h: 46 },
                  { src: "assets/logos/collab-fluxivo.png", alt: "Fluxivo Marketing Agency", h: 46 }
                ].map((lg, i) =>
                  <span key={i} style={{
                    display: "inline-flex", alignItems: "center", justifyContent: "center",
                    background: "var(--piece-paper)", borderRadius: 3, padding: "8px 13px"
                  }}>
                    <img src={lg.src} alt={lg.alt} loading="lazy" decoding="async"
                      style={{ height: lg.h, width: "auto", display: "block" }} />
                  </span>
                )}
              </div>
            </div>
          </div>
        </Reveal>
      </div>

      {chapters.map((ch, ci) => {
        const stacks = ch.pieces.some((p) => p.stack);
        return (
          <div key={ch.key} style={{ padding: "0 6vw", marginTop: ci === 0 ? 40 : 118 }}>
            {/* chapter head: the chapter name */}
            <Reveal>
              <div style={{ marginBottom: 34 }}>
                <div style={{
                  display: "flex", alignItems: "flex-end", justifyContent: "space-between",
                  gap: 24, flexWrap: "wrap",
                  paddingBottom: 18, borderBottom: "1px solid var(--border-2)"
                }}>
                  <h3 style={{
                    margin: 0, fontFamily: "Quicksand, sans-serif", fontWeight: 800,
                    fontSize: "clamp(26px, 3.6vw, 52px)", lineHeight: 1,
                    letterSpacing: "-0.03em", color: "var(--ink-2)"
                  }}>{ch.label}</h3>
                </div>
              </div>
            </Reveal>

            {/* one grid per chapter; video is gated at 4 */}
            <ChapterGrid ch={ch} accent={accent} t={t} stacks={stacks} />
          </div>);
      })}

    </section>);
}

// ─── Media helpers (embeds) ─────────────────────────────────────────────────
function ytId(url) {
  const m = String(url).match(/(?:v=|youtu\.be\/|embed\/)([\w-]{11})/);
  return m ? m[1] : null;
}
function ytStart(url) {
  const m = String(url).match(/[?&]t=(\d+)/);
  return m ? m[1] : null;
}
function igPermalink(url) {
  return String(url).split("?")[0];
}

// ─── Embed card (opens lightbox) ────────────────────────────────────────────
function EmbedCard({ item, accent, ratio = "4/5", badge, platform, onOpen }) {
  const isYT = item.type === "youtube";
  const id = isYT ? ytId(item.url) : null;
  const initialThumb = item.thumb || (id ? `https://img.youtube.com/vi/${id}/hqdefault.jpg` : null);
  const [thumb, setThumb] = useState(initialThumb);
  const [hover, setHover] = useState(false);

  const stripeColor = "var(--card-stripe-1)";
  const open = () => onOpen && onOpen();

  return (
    <div role="button" tabIndex={0} onClick={open}
      onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); open(); } }}
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{
        position: "relative", aspectRatio: ratio, borderRadius: 2, overflow: "hidden",
        border: "1px solid var(--border-soft-1)", cursor: "pointer",
        background: "var(--card-bg-1)",
        backgroundImage: thumb ? "none" : `repeating-linear-gradient(135deg, ${stripeColor} 0 12px, transparent 12px 24px)`,
        transform: hover ? "translateY(-3px)" : "translateY(0)",
        transition: "transform 400ms cubic-bezier(.2,.7,.2,1), border-color 300ms",
        borderColor: hover ? accent : "var(--border-soft-1)"
      }}>
      {thumb &&
        <img src={thumb} alt={item.title || platform} loading="lazy" decoding="async"
          onError={() => setThumb(null)}
          style={{
            position: "absolute", inset: 0, width: "100%", height: "100%",
            objectFit: "cover", display: "block",
            transform: hover ? "scale(1.05)" : "scale(1)",
            transition: "transform 600ms cubic-bezier(.2,.7,.2,1)"
          }} />
      }
      {/* legibility gradient */}
      <div style={{
        position: "absolute", inset: 0,
        background: "linear-gradient(to top, rgba(10,10,12,.72) 0%, rgba(10,10,12,.15) 42%, rgba(10,10,12,0) 70%)"
      }} />

      {/* top-left platform badge */}
      <div style={{
        position: "absolute", top: 14, left: 14,
        fontFamily: "Nunito, sans-serif", fontWeight: 700, fontSize: 10, letterSpacing: ".14em",
        textTransform: "uppercase", color: "#fff",
        border: "1px solid rgba(255,255,255,.55)", padding: "4px 9px", borderRadius: 999,
        background: "rgba(0,0,0,.28)", backdropFilter: "blur(2px)"
      }}>{platform}</div>

      {/* center affordance */}
      <div style={{
        position: "absolute", top: "50%", left: "50%", transform: `translate(-50%,-50%) scale(${hover ? 1.08 : 1})`,
        width: 56, height: 56, borderRadius: 999,
        background: "rgba(255,255,255,.92)", boxShadow: "0 6px 20px rgba(0,0,0,.28)",
        display: "flex", alignItems: "center", justifyContent: "center",
        transition: "transform 320ms cubic-bezier(.2,.7,.2,1)"
      }}>
        {isYT ?
          <span style={{
            width: 0, height: 0, borderStyle: "solid",
            borderWidth: "9px 0 9px 15px",
            borderColor: "transparent transparent transparent #141414", marginLeft: 3
          }} /> :
          <span style={{ position: "relative", width: 20, height: 20 }}>
            <span style={{ position: "absolute", top: 0, left: 0, width: 14, height: 14, border: "2px solid #141414", borderRadius: 2, background: "rgba(255,255,255,.92)" }} />
            <span style={{ position: "absolute", bottom: 0, right: 0, width: 14, height: 14, border: "2px solid #141414", borderRadius: 2, background: "rgba(255,255,255,.92)" }} />
          </span>
        }
      </div>

      {/* bottom: title */}
      <div style={{
        position: "absolute", left: 14, right: 14, bottom: 14,
        display: "flex", flexDirection: "column", gap: 6
      }}>
        {item.title &&
          <span style={{
            fontFamily: "Quicksand, sans-serif", fontWeight: 600, fontSize: 14,
            color: "#fff", lineHeight: 1.25, letterSpacing: "-0.01em"
          }}>{item.title}</span>
        }
      </div>
    </div>);

}

// ─── Lightbox (YouTube iframe / Instagram embed) ────────────────────────────
function Lightbox({ item, onClose }) {
  const isYT = item.type === "youtube";

  useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", onKey);
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => { document.removeEventListener("keydown", onKey); document.body.style.overflow = prev; };
  }, [onClose]);

  useEffect(() => {
    if (item.type !== "instagram") return;
    let cancelled = false;
    const run = () => { if (!cancelled && window.instgrm) window.instgrm.Embeds.process(); };
    if (window.instgrm) { run(); return; }
    let s = document.getElementById("ig-embed-js");
    if (!s) {
      s = document.createElement("script");
      s.id = "ig-embed-js";
      s.src = "https://www.instagram.com/embed.js";
      s.async = true;
      document.body.appendChild(s);
    }
    const onLoad = () => run();
    s.addEventListener("load", onLoad);
    const poll = setInterval(() => { if (window.instgrm) { run(); clearInterval(poll); } }, 300);
    const stop = setTimeout(() => clearInterval(poll), 6000);
    return () => { cancelled = true; s && s.removeEventListener("load", onLoad); clearInterval(poll); clearTimeout(stop); };
  }, [item]);

  const id = isYT ? ytId(item.url) : null;
  const start = isYT ? ytStart(item.url) : null;
  const ytSrc = id ?
    `https://www.youtube-nocookie.com/embed/${id}?autoplay=1&rel=0&playsinline=1${start ? `&start=${start}` : ""}` : null;
  const ytWatch = id ?
    `https://www.youtube.com/watch?v=${id}${start ? `&t=${start}s` : ""}` : item.url;

  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, zIndex: 1000,
      background: "rgba(8,8,10,.82)", backdropFilter: "blur(6px)",
      display: "flex", alignItems: "center", justifyContent: "center",
      padding: "5vh 20px", animation: "lb-fade 220ms ease"
    }}>
      <style>{`@keyframes lb-fade{from{opacity:0}to{opacity:1}}@keyframes lb-rise{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}`}</style>
      <button onClick={onClose} aria-label="Închide" style={{
        position: "fixed", top: 22, right: 24, zIndex: 1001,
        width: 44, height: 44, borderRadius: 999, border: "1px solid rgba(255,255,255,.3)",
        background: "rgba(255,255,255,.08)", color: "#fff", fontSize: 24, lineHeight: 1,
        cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center"
      }}>×</button>

      <div onClick={(e) => e.stopPropagation()} style={{
        width: isYT ? "min(960px, 94vw)" : "min(540px, 94vw)",
        maxHeight: "90vh", overflowY: isYT ? "visible" : "auto",
        animation: "lb-rise 300ms cubic-bezier(.2,.7,.2,1)"
      }}>
        {isYT ?
          <div>
            <div style={{
              position: "relative", width: "100%", aspectRatio: "16/9",
              background: "#000", borderRadius: 8, overflow: "hidden",
              boxShadow: "0 30px 90px rgba(0,0,0,.55)"
            }}>
              <iframe src={ytSrc} title={item.title || "YouTube"}
                allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
                allowFullScreen
                style={{ position: "absolute", inset: 0, width: "100%", height: "100%", border: 0 }} />
            </div>
            <div style={{
              display: "flex", justifyContent: "flex-end", marginTop: 12
            }}>
              <a href={ytWatch} target="_blank" rel="noopener noreferrer"
                style={{
                  display: "inline-flex", alignItems: "center", gap: 8,
                  fontFamily: "Quicksand, sans-serif", fontWeight: 600, fontSize: 13,
                  color: "#fff", textDecoration: "none",
                  border: "1px solid rgba(255,255,255,.3)", background: "rgba(255,255,255,.08)",
                  padding: "9px 16px", borderRadius: 999
                }}>
                Deschide pe YouTube <span aria-hidden="true">↗</span>
              </a>
            </div>
          </div> :
          <blockquote className="instagram-media"
            data-instgrm-permalink={igPermalink(item.url)}
            data-instgrm-version="14"
            style={{
              background: "#fff", border: 0, borderRadius: 8, margin: 0,
              width: "100%", minWidth: 0, boxShadow: "0 30px 90px rgba(0,0,0,.45)"
            }} />
        }
      </div>
    </div>);

}

// ─── Inline embeds (no lightbox — they just play in place) ─────────────────
// The IG script REPLACES its blockquote, so React must never own that node:
// we inject it into an empty host div imperatively and keep all state outside.
function IgEmbed({ url, title, accent }) {
  const hostRef = useRef(null);
  const [failed, setFailed] = useState(false);
  const [ok, setOk] = useState(false);

  useEffect(() => {
    const host = hostRef.current;
    if (!host) return;
    setFailed(false); setOk(false);
    const bq = document.createElement("blockquote");
    bq.className = "instagram-media";
    bq.setAttribute("data-instgrm-permalink", igPermalink(url));
    bq.setAttribute("data-instgrm-version", "14");
    bq.style.cssText = "background:var(--card-bg-1);border:1px solid var(--border-1);border-radius:6px;margin:0;width:100%;min-width:0";
    host.appendChild(bq);

    let cancelled = false;
    const process = () => { if (!cancelled && window.instgrm) window.instgrm.Embeds.process(); };
    let poll, stopPoll;
    if (window.instgrm) process();
    else {
      let sc = document.getElementById("ig-embed-js");
      if (!sc) {
        sc = document.createElement("script");
        sc.id = "ig-embed-js";
        sc.src = "https://www.instagram.com/embed.js";
        sc.async = true;
        document.body.appendChild(sc);
      }
      sc.addEventListener("load", process);
      poll = setInterval(() => { if (window.instgrm) { process(); clearInterval(poll); } }, 300);
      stopPoll = setTimeout(() => clearInterval(poll), 8000);
    }

    const watch = setInterval(() => {
      const fr = host.querySelector("iframe");
      if (fr && fr.getBoundingClientRect().height > 80) { setOk(true); clearInterval(watch); }
    }, 400);
    const giveUp = setTimeout(() => {
      const fr = host.querySelector("iframe");
      if (!fr || fr.getBoundingClientRect().height < 80) setFailed(true);
      clearInterval(watch);
    }, 4500);

    return () => {
      cancelled = true;
      clearInterval(watch); clearTimeout(giveUp);
      if (poll) clearInterval(poll);
      if (stopPoll) clearTimeout(stopPoll);
      host.innerHTML = "";
    };
  }, [url]);

  return (
    <div style={{ width: "100%", minHeight: ok || failed ? 0 : 580 }}>
      {/* React-owned wrapper, but this host stays empty as far as React knows */}
      <div ref={hostRef} style={{ display: failed ? "none" : "block", width: "100%" }} />
      {failed &&
        <div style={{
          width: "100%", aspectRatio: "4/5", position: "relative", overflow: "hidden",
          borderRadius: 6, border: "1px solid var(--border-1)", background: "var(--card-bg-1)",
          backgroundImage: "repeating-linear-gradient(135deg, var(--card-stripe-1) 0 14px, transparent 14px 28px)",
          display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 16, padding: 28
        }}>
          <span style={{
            fontFamily: "ui-monospace, monospace", fontSize: 11, letterSpacing: ".16em",
            textTransform: "uppercase", color: "var(--ink-faint-1)"
          }}>Instagram</span>
          {title &&
            <span style={{
              fontFamily: "Quicksand, sans-serif", fontWeight: 600, fontSize: 16,
              color: "var(--ink-1)", textAlign: "center", lineHeight: 1.3
            }}>{title}</span>
          }
          <a href={igPermalink(url)} target="_blank" rel="noopener noreferrer" style={{
            fontFamily: "Quicksand, sans-serif", fontWeight: 600, fontSize: 13,
            color: "var(--ink-1)", textDecoration: "none",
            border: `1px solid ${accent}`, padding: "10px 18px", borderRadius: 999
          }}>Deschide pe Instagram <span aria-hidden="true">↗</span></a>
        </div>
      }
    </div>);
}

function YtEmbed({ item }) {
  const id = ytId(item.url);
  const start = ytStart(item.url);
  const src = id ? `https://www.youtube-nocookie.com/embed/${id}?rel=0&playsinline=1${start ? `&start=${start}` : ""}` : null;
  const watch = id ? `https://www.youtube.com/watch?v=${id}${start ? `&t=${start}s` : ""}` : item.url;
  return (
    <div style={{ width: "100%" }}>
      <div style={{
        position: "relative", width: "100%", aspectRatio: "16/9",
        background: "#000", borderRadius: 6, overflow: "hidden",
        border: "1px solid var(--border-1)"
      }}>
        <iframe src={src} title={item.title || "YouTube"} loading="lazy" decoding="async"
          allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
          allowFullScreen
          style={{ position: "absolute", inset: 0, width: "100%", height: "100%", border: 0 }} />
      </div>
      <div style={{
        display: "flex", justifyContent: "space-between", alignItems: "center",
        gap: 14, marginTop: 12, flexWrap: "wrap"
      }}>
        <span style={{
          fontFamily: "Quicksand, sans-serif", fontWeight: 600, fontSize: 14, color: "var(--ink-1)"
        }}>{item.title}</span>
        <a href={watch} target="_blank" rel="noopener noreferrer" style={{
          fontFamily: "Nunito, sans-serif", fontSize: 12, letterSpacing: ".02em",
          color: "var(--ink-mute-1)", textDecoration: "none"
        }}>YouTube <span aria-hidden="true">↗</span></a>
      </div>
    </div>);
}

// ─── Universe of Content — both series flowing, embeds inline ──────────────
function UniverseSection({ t, accent, theme }) {
  const logoSrc = theme === "dark" ?
    "assets/logos/uoc-full-light.png" :
    "assets/logos/uoc-full-dark.png";
  const u = t.universe;
  const series = [
    { data: u.travel, label: u.tabs[1], logo: "assets/logos/utravelseries.png" },
    { data: u.tech, label: u.tabs[0], logo: "assets/logos/utech-channel.png" }
  ];

  return (
    <section id="universe" style={{
      background: "var(--bg-1)", color: "var(--ink-1)", padding: "84px 6vw 96px"
    }}>
      <style>{`.chan-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(28px,4vw,56px);align-items:start}@media (max-width:880px){.chan-grid{grid-template-columns:1fr;gap:64px}}
.uoc-head{display:flex;align-items:center;justify-content:space-between;gap:clamp(24px,4vw,60px);padding-bottom:22px;border-bottom:1px solid var(--border-1);margin-bottom:44px}
.uoc-head img{width:clamp(190px,22vw,300px);height:auto;display:block;flex-shrink:0}
.uoc-title{margin:0;text-align:right;font-family:Quicksand,sans-serif;font-weight:800;font-size:clamp(18px,2.2vw,31px);line-height:1.02;letter-spacing:-0.035em;color:var(--ink-1);text-wrap:balance}
@media (max-width:700px){.uoc-head{flex-direction:column;align-items:flex-start;gap:22px}.uoc-title{text-align:left}}`}</style>

      <Reveal>
        <div className="uoc-head">
          <img src={logoSrc} alt="Universe of Content" />
          <h3 className="uoc-title">
            {(() => {
              const w = String(u.channelsTitle).trim().split(/\s+/);
              const last = w.pop();
              return (
                <React.Fragment>
                  {w.join(" ")}{w.length ? " " : ""}
                  <em style={{
                    color: accent, fontStyle: "italic", fontWeight: 800
                  }}>{last}</em>
                </React.Fragment>);
            })()}
          </h3>
        </div>
      </Reveal>

      <div className="chan-grid">
        {series.map((s, si) => {
          const d = s.data;
          const featured = d.featured ||
            ((d.items || []).find((it) => it.type === "youtube") || {}).url || "";
          return (
            <Reveal key={si} delay={si * 110}>
              <div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
                {/* landscape frame — the channel's face */}
                <div style={{
                  position: "relative", width: "100%", aspectRatio: "16/9",
                  borderRadius: 5, overflow: "hidden", background: "#000",
                  border: "1px solid var(--border-1)"
                }}>
                  {featured ?
                    <ClickFrame url={featured} title={s.label} accent={accent} /> :
                    <div style={{
                      position: "absolute", inset: 0, background: "var(--card-bg-1)",
                      backgroundImage: "repeating-linear-gradient(135deg, var(--card-stripe-1) 0 14px, transparent 14px 28px)",
                      display: "flex", alignItems: "center", justifyContent: "center",
                      fontFamily: "ui-monospace, monospace", fontSize: 11, letterSpacing: ".18em",
                      textTransform: "uppercase", color: "var(--ink-faint-1)"
                    }}>{u.soon}</div>
                  }
                </div>

                <div style={{
                  display: "grid", gridTemplateColumns: "auto minmax(0,1fr)",
                  gap: 22, alignItems: "center"
                }}>
                  <img src={s.logo} alt={s.label}
                    style={{ height: 96, width: "auto", display: "block", flexShrink: 0 }} />
                  <div>
                    <h4 style={{
                      margin: "0 0 9px", fontFamily: "Quicksand, sans-serif", fontWeight: 800,
                      fontSize: "clamp(19px, 1.9vw, 25px)", lineHeight: 1.15,
                      letterSpacing: "-0.01em", color: "var(--ink-1)"
                    }}>{d.kicker}</h4>
                    <p style={{
                      margin: 0, fontFamily: "Nunito, sans-serif", fontSize: 15,
                      lineHeight: 1.55, color: "var(--ink-mute-1)"
                    }}>{d.desc}</p>
                  </div>
                </div>

                {/* topics wrap internally; the channel link stays on the first line */}
                <div style={{
                  display: "flex", flexWrap: "nowrap", gap: 12, alignItems: "flex-start"
                }}>
                  <div style={{
                    display: "flex", flexWrap: "wrap", gap: 8, flex: "1 1 auto", minWidth: 0
                  }}>
                    {d.topics.map((tp, i) =>
                      <span key={i} style={{
                        fontFamily: "Quicksand, sans-serif", fontWeight: 600, fontSize: 12,
                        padding: "6px 12px", borderRadius: 999,
                        border: "1px solid var(--border-1)", color: "var(--ink-mute-1)",
                        background: "var(--pill-bg)"
                      }}>{tp}</span>
                    )}
                  </div>
                  <a href={d.channelUrl || "#"} target={d.channelUrl ? "_blank" : undefined}
                    rel="noopener noreferrer"
                    onClick={(e) => { if (!d.channelUrl) e.preventDefault(); }}
                    style={{
                      display: "inline-flex", alignItems: "center", gap: 7,
                      flexShrink: 0,
                      fontFamily: "Quicksand, sans-serif", fontWeight: 700, fontSize: 12,
                      color: d.channelUrl ? accent : "var(--ink-faint-1)",
                      textDecoration: "none", whiteSpace: "nowrap",
                      padding: "6px 14px", borderRadius: 999,
                      border: `1px solid ${d.channelUrl ? accent : "var(--border-1)"}`,
                      cursor: d.channelUrl ? "pointer" : "default",
                      transition: "background 240ms, color 240ms"
                    }}
                    onMouseEnter={(e) => {
                      if (!d.channelUrl) return;
                      e.currentTarget.style.background = accent;
                      e.currentTarget.style.color = "#12120f";
                    }}
                    onMouseLeave={(e) => {
                      e.currentTarget.style.background = "transparent";
                      e.currentTarget.style.color = d.channelUrl ? accent : "var(--ink-faint-1)";
                    }}>
                    {u.cta}<span aria-hidden="true">↗</span>
                  </a>
                </div>
              </div>
            </Reveal>);
        })}
      </div>
    </section>);
}

// ─── Contact ───────────────────────────────────────────────────────────────
function Contact({ t, accent }) {
  const email = "hello@ucreativemedia.com";

  return (
    <section id="contact" style={{
      background: "var(--bg-2)", color: "var(--ink-2)", padding: "120px 6vw 80px",
      borderTop: "1px solid var(--border-soft-2)"
    }}>
      <Reveal>
        <div style={{ display: "flex", flexDirection: "column", gap: 24 }}>
          {/* "Contact" heading */}
          <h3 style={{
            margin: 0, fontFamily: "Quicksand, sans-serif", fontWeight: 700,
            fontSize: "clamp(32px, 5vw, 56px)", letterSpacing: "-0.02em", lineHeight: 1,
            color: "var(--ink-2)"
          }}>Contact</h3>

          {/* Email + CTA framed together on one row */}
          <div style={{
            display: "flex", alignItems: "center", flexWrap: "wrap", gap: 20
          }}>
            <a href={`mailto:${email}`} className="contact-email" style={{
              fontFamily: "Quicksand, sans-serif", fontWeight: 600,
              fontSize: "clamp(17px, 2.2vw, 24px)", letterSpacing: "-0.01em",
              color: "var(--ink-2)", textDecoration: "none", width: "fit-content",
              borderBottom: "2px solid transparent", transition: "border-color 220ms, color 220ms"
            }}
            onMouseEnter={(e) => {e.currentTarget.style.color = accent;e.currentTarget.style.borderBottomColor = accent;}}
            onMouseLeave={(e) => {e.currentTarget.style.color = "var(--ink-2)";e.currentTarget.style.borderBottomColor = "transparent";}}>
              {email}
            </a>
            <a href={`mailto:${email}`} style={{
              display: "inline-flex", alignItems: "center", gap: 10,
              fontFamily: "Quicksand, sans-serif", fontWeight: 600, fontSize: 14,
              color: "#1f1f1f", background: accent, padding: "14px 24px",
              borderRadius: 999, textDecoration: "none", letterSpacing: ".02em",
              transition: "transform 250ms"
            }}
            onMouseEnter={(e) => e.currentTarget.style.transform = "translateY(-2px)"}
            onMouseLeave={(e) => e.currentTarget.style.transform = "translateY(0)"}>
              {t.contact.cta} <span>→</span>
            </a>
          </div>
        </div>
      </Reveal>
    </section>);

}

// ─── Footer ────────────────────────────────────────────────────────────────
function Footer({ t, accent, theme }) {
  return (
    <footer style={{
      background: "var(--bg-2)", color: "var(--ink-soft-2)", padding: "48px 6vw",
      borderTop: "1px solid var(--border-soft-2)",
      fontFamily: "Nunito, sans-serif", fontSize: 13
    }}>
      <div style={{
        display: "flex", justifyContent: "space-between", alignItems: "center",
        gap: 24, flexWrap: "wrap"
      }}>
        {/* Footer always sits on dark surface, so always use light-body logo */}
        <UCreativeLogoImg theme="dark" height={56} />
        <div style={{ display: "flex", gap: 24, flexWrap: "wrap", alignItems: "center" }}>
          <span>© {new Date().getFullYear()} UCreativeMedia · {t.footer.rights}</span>
        </div>
      </div>
    </footer>);

}

Object.assign(window, { Hero, Services, Portfolio, UniverseSection, Contact, Footer, Reveal });