/* Koolorama — homepage */

const { useState, useEffect, useRef } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "heroVariant": "centered",
  "headlineVariant": "brand",
  "portfolioVariant": "feature",
  "showBlob": true,
  "blobSize": 310,
  "blobOpacity": 78,
  "accentMoment": true,
  "ctaCopy": "Start a project",
  "heroPill": "23s comedy ad for a skin clinic",
  "heroSubline": "Koolorama makes cinematic AI short films — with humor, characters, and a point of view.\nDirected by a human. Produced with AI. Built to stop thumbs.",
  "finalCtaIntro": "Tell us what you're working on. We'll come back with a concept, a mood, and a plan — in 48 hours.",
  "finalCtaEmail": "hello@koolorama.com"
}/*EDITMODE-END*/;

const HEADLINES = {
  studio:    { line1: "Short films,",         line2: "crafted with AI." },
  ai:        { line1: "AI Video",              line2: "Studio." },
  social:    { line1: "Social-first",          line2: "AI films." },
  brand:     { line1: "Creative AI videos",    line2: "that stop the scroll." },
  fifteen:   { line1: "15 seconds.",           line2: "Cinema-grade." },
};

const PORTFOLIO = [
  { id: "bibites",      title: "Les Bibites",         client: "Dermagyms",             meta: "Clinic Ad",   palette: "bloom",    category: "Brand Film",  duration: "0:23", video: "assets/videos/bibites.mp4", poster: "assets/posters/bibites.jpg" },
  { id: "lignes-maman", title: "Les Lignes de Maman", client: "Dermagyms",             meta: "Clinic Ad",   palette: "form",     category: "Brand Film",  duration: "0:22", video: "assets/videos/lignes-maman.mp4", poster: "assets/posters/lignes-maman.jpg" },
  { id: "pinky",        title: "Pinky Upgrade",       client: "Acting School — Paris", meta: "Campaign",    palette: "mira",     category: "Brand Film",  duration: "0:19", video: "assets/videos/pinky.mp4", poster: "assets/posters/pinky.jpg" },
  { id: "pause-auto",   title: "Pause dans l'Auto",   client: "SPA — Montréal",        meta: "Brand Film",  palette: "glass",    category: "Brand Film",  duration: "0:24", video: "assets/videos/pause-auto.mp4", poster: "assets/posters/pause-auto.jpg" },
  { id: "rabais",       title: "60% de Rabais",       client: "Dermagyms",             meta: "Promo",       palette: "aether",   category: "Social Ad",   duration: "0:28", video: "assets/videos/rabais.mp4", poster: "assets/posters/rabais.jpg" },
  { id: "sphynx",       title: "Doux Sphynx",         client: "Dermagyms",             meta: "Laser Hair",  palette: "loop",     category: "Brand Film",  duration: "0:20", video: "assets/videos/sphynx.mp4", poster: "assets/posters/sphynx.jpg" },
  { id: "hair-pursuit", title: "Hair Pursuit",        client: "Dermagyms",             meta: "Laser Hair",  palette: "solstice", category: "Brand Film",  duration: "0:25", video: "assets/videos/hair-pursuit.mp4", poster: "assets/posters/hair-pursuit.jpg" },
  { id: "coastmint",    title: "Coastmint",           client: "Domaincooling.com",     meta: "Domain Spot", palette: "monolith", category: "Short Film",  duration: "0:42", video: "assets/videos/coastmint.mp4", poster: "assets/posters/coastmint.jpg" },
  { id: "highsnake",    title: "HighSnake",           client: "Domaincooling.com",     meta: "CBD",         palette: "form",     category: "Short Film",  duration: "0:39", video: "assets/videos/highsnake.mp4", poster: "assets/posters/highsnake.jpg" },
];

/* ====================================================== NAV ====== */
function Nav({ animateLogo = false }) {
  const [scrolled, setScrolled] = useState(false);
  useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 24);
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  const onNavClick = (e, hash) => {
    e.preventDefault();
    const el = document.querySelector(hash);
    if (!el) return;
    // elevator: smooth accel, long soft landing (easeInOutQuint)
    const ease = (t) =>
      t < 0.5
        ? 16 * t * t * t * t * t
        : 1 - Math.pow(-2 * t + 2, 5) / 2;
    if (window.__lenis) {
      window.__lenis.scrollTo(el, { duration: 1.9, easing: ease });
    } else {
      el.scrollIntoView({ behavior: "smooth", block: "start" });
    }
  };

  return (
    <nav
      style={{
        position: "fixed",
        top: 0, left: 0, right: 0,
        zIndex: 50,
        padding: "20px 40px",
        display: "flex",
        alignItems: "center",
        justifyContent: "space-between",
        background: scrolled ? "rgba(250,250,250,0.78)" : "transparent",
        backdropFilter: scrolled ? "blur(14px)" : "none",
        borderBottom: scrolled ? "1px solid var(--rule)" : "1px solid transparent",
        transition: "background .4s ease, border-color .4s ease, backdrop-filter .4s ease",
      }}
    >
      <Wordmark size={16} animateIn={animateLogo} />
      <div style={{ display: "flex", gap: 36, alignItems: "center" }}>
        {[
          { label: "Work",     hash: "#work" },
          { label: "Process",  hash: "#process" },
          { label: "Services", hash: "#services" },
          { label: "Pricing",  hash: "#pricing" },
          { label: "Studio",   hash: "#contact" },
        ].map((l) => (
          <a
            key={l.label}
            href={l.hash}
            onClick={(e) => onNavClick(e, l.hash)}
            style={{
              fontSize: 13,
              color: "var(--ink)",
              letterSpacing: "-0.005em",
              opacity: 0.82,
            }}
          >
            {l.label}
          </a>
        ))}
        <a
          href="#contact"
          onClick={(e) => onNavClick(e, "#contact")}
          style={{
            fontSize: 13,
            border: "1px solid var(--rule-strong)",
            borderRadius: 999,
            padding: "8px 16px",
            color: "var(--ink)",
          }}
        >
          Contact
        </a>
      </div>
    </nav>
  );
}

/* ====================================================== HERO ===== */
const HERO_PILL_ROTATION = [
  "23s comedy ad for a skin clinic",
  "19s cinematic spot for an acting school",
  "15s character-driven promo for a spa",
  "28s humorous social ad for a sale",
  "42s brand film for a startup",
  "20s storytelling reel for beauty",
  "12s quirky launch clip for a product",
  "9s punchy social ad for wellness",
  "24s Wes Anderson moment for a brand",
];

function RotatingPill({ items, animate, holdMs = 3500, inMs = 420, outMs = 320, initialMs = 1500 }) {
  const [idx, setIdx] = useState(0);
  const [leaving, setLeaving] = useState(false);
  const [hasRotated, setHasRotated] = useState(false);
  const [paused, setPaused] = useState(false);

  useEffect(() => {
    if (!animate || paused || leaving) return;
    const t1 = setTimeout(() => {
      setLeaving(true);
    }, hasRotated ? holdMs : holdMs + (initialMs - inMs));
    return () => clearTimeout(t1);
  }, [idx, animate, paused, holdMs, initialMs, inMs, leaving, hasRotated]);

  useEffect(() => {
    if (!leaving) return;
    const t2 = setTimeout(() => {
      setIdx((i) => (i + 1) % items.length);
      setLeaving(false);
      setHasRotated(true);
    }, outMs);
    return () => clearTimeout(t2);
  }, [leaving, outMs, items.length]);

  if (!animate) {
    return <span style={{ display: "inline-block", opacity: 0 }}>{items[0]}</span>;
  }

  const initialEntry = !hasRotated && !leaving;
  const enterDur = initialEntry ? initialMs : inMs;
  const animValue = leaving
    ? `pill-text-out ${outMs}ms cubic-bezier(.5,0,.7,.3) both`
    : `pill-text-in ${enterDur}ms cubic-bezier(.2,.7,.2,1) both`;

  return (
    <span
      style={{
        display: "inline-block",
        overflow: "hidden",
        verticalAlign: "bottom",
        lineHeight: 1.2,
      }}
      onMouseEnter={() => setPaused(true)}
      onMouseLeave={() => setPaused(false)}
    >
      <span style={{ display: "inline-block", animation: animValue }}>
        {items[idx]}
      </span>
    </span>
  );
}

function Hero({ t, animatePill = false }) {
  const h = HEADLINES[t.headlineVariant] || HEADLINES.studio;
  return (
    <section
      style={{
        position: "relative",
        minHeight: "100vh",
        display: "grid",
        placeItems: "center",
        padding: "120px 40px 80px",
        overflow: "hidden",
      }}
    >
      {/* prism — drifts behind the headline at reduced opacity */}
      {t.showBlob && (
        <div
          style={{
            position: "absolute",
            top: "46%",
            left: "50%",
            transform: "translate(-50%, -50%)",
            zIndex: 0,
            opacity: t.blobOpacity / 100,
            pointerEvents: "none",
          }}
        >
          <IridescentBlob size={t.blobSize} />
        </div>
      )}

      <div
        style={{
          position: "relative",
          zIndex: 1,
          textAlign: "center",
          maxWidth: 1080,
        }}
      >
        <Reveal>
          <div
            className="eyebrow"
            style={{ marginBottom: 36 }}
          >
            <span style={{
              display: "inline-flex",
              alignItems: "center",
              gap: 10,
              padding: "8px 14px",
              border: "1px solid var(--rule-strong)",
              borderRadius: 999,
              background: "rgba(255,255,255,0.6)",
              backdropFilter: "blur(8px)",
            }}>
              <span style={{
                width: 6, height: 6, borderRadius: "50%",
                background: "#0a0a0a",
                display: "inline-block",
                flexShrink: 0,
              }} />
              <RotatingPill items={HERO_PILL_ROTATION} animate={animatePill} />
            </span>
          </div>
        </Reveal>

        <Reveal delay={120}>
          <h1
            style={{
              fontFamily: "var(--sans)",
              fontWeight: 400,
              fontSize: "clamp(56px, 9.4vw, 156px)",
              lineHeight: 0.92,
              letterSpacing: "-0.045em",
              margin: 0,
              color: "var(--ink)",
            }}
          >
            {h.line1}
            <br />
            <span style={{
              fontFamily: "var(--serif)",
              fontStyle: "italic",
              fontWeight: 400,
              letterSpacing: "-0.02em",
            }}>{h.line2}</span>
          </h1>
        </Reveal>

        <Reveal delay={260}>
          <p
            style={{
              maxWidth: 560,
              margin: "44px auto 0",
              fontSize: 17,
              lineHeight: 1.5,
              color: "var(--muted)",
              letterSpacing: "-0.005em",
              whiteSpace: "pre-line",
            }}
          >
            {t.heroSubline}
          </p>
        </Reveal>

        <Reveal delay={420}>
          <div
            style={{
              marginTop: 44,
              display: "flex",
              gap: 12,
              justifyContent: "center",
              flexWrap: "wrap",
            }}
          >
            <button className="btn" data-hero-cta="true">
              <span>{t.ctaCopy}</span>
              <span className="arrow" style={{ display: "inline-flex", marginLeft: 2 }}>
                <svg width="8" height="10" viewBox="0 0 10 12" fill="none">
                  <path d="M1 1L9 6L1 11V1Z" stroke="currentColor" strokeWidth="1.1" strokeLinejoin="round" />
                </svg>
              </span>
            </button>
          </div>
        </Reveal>
      </div>

      {/* tiny scroll cue */}
      <div
        style={{
          position: "absolute",
          bottom: 28,
          left: "50%",
          transform: "translateX(-50%)",
          fontFamily: "var(--mono)",
          fontSize: 10,
          letterSpacing: "0.18em",
          textTransform: "uppercase",
          color: "var(--muted-2)",
          zIndex: 1,
          display: "flex",
          flexDirection: "column",
          alignItems: "center",
          gap: 10,
        }}
      >
        <span>Selected work</span>
        <svg className="scroll-cue-arrow" width="10" height="12" viewBox="0 0 10 12" fill="none" aria-hidden="true">
          <path d="M1 1L5 9L9 1H1Z" stroke="currentColor" strokeWidth="1.2" />
        </svg>
      </div>
    </section>
  );
}

/* ====================================================== TICKER === */
function Ticker() {
  const items = [
    "Reels", "Characters", "TikTok", "Humor", "Cinema", "Shorts", "Story", "Campaign",
    "Reels", "Characters", "TikTok", "Humor", "Cinema", "Shorts", "Story", "Campaign",
  ];
  return (
    <div style={{
      borderTop: "1px solid var(--rule)",
      borderBottom: "1px solid var(--rule)",
      overflow: "hidden",
      padding: "22px 0",
    }}>
      <div style={{
        display: "flex",
        gap: 56,
        whiteSpace: "nowrap",
        animation: "marquee 38s linear infinite",
        width: "max-content",
      }}>
        {items.concat(items).map((w, i) => (
          <span key={i} style={{
            display: "inline-flex",
            alignItems: "center",
            gap: 56,
            fontFamily: "var(--serif)",
            fontStyle: "italic",
            fontSize: 28,
            color: "var(--ink)",
            letterSpacing: "-0.01em",
          }}>
            {w}
            <span style={{
              display: "inline-block",
              width: 6, height: 6, borderRadius: "50%",
              background: "var(--ink)",
            }} />
          </span>
        ))}
      </div>
    </div>
  );
}

/* ============================================== PORTFOLIO ======== */
function Portfolio({ t }) {
  const [active, setActive] = useState(0);
  const variant = t.portfolioVariant;

  if (variant === "strip") {
    return <PortfolioStrip items={PORTFOLIO} />;
  }
  if (variant === "gallery") {
    return <PortfolioGallery items={PORTFOLIO} />;
  }
  return <PortfolioFeature items={PORTFOLIO} active={active} setActive={setActive} />;
}

function SectionHeader({ index, eyebrow, title, note }) {
  return (
    <div style={{
      padding: "0 40px",
      display: "grid",
      gridTemplateColumns: "1fr auto 1fr",
      alignItems: "end",
      gap: 40,
      marginBottom: 64,
    }}>
      <div style={{
        fontFamily: "var(--mono)",
        fontSize: 11,
        letterSpacing: "0.18em",
        textTransform: "uppercase",
        color: "var(--muted)",
      }}>
        {index} — {eyebrow}
      </div>
      <h2 style={{
        margin: 0,
        fontFamily: "var(--sans)",
        fontWeight: 400,
        fontSize: "clamp(40px, 5.6vw, 84px)",
        lineHeight: 0.95,
        letterSpacing: "-0.035em",
        textAlign: "center",
        whiteSpace: "nowrap",
      }}>
        {title}
      </h2>
      <div style={{
        fontSize: 13,
        color: "var(--muted)",
        textAlign: "right",
        maxWidth: 280,
        marginLeft: "auto",
      }}>
        {note}
      </div>
    </div>
  );
}

function TwinWheels({ items, activeIndex, onPick, height }) {
  // Split into two columns; preserve each film's real index in the full list.
  const colA = items.map((f, i) => ({ ...f, _realIndex: i })).filter((_, i) => i % 2 === 0);
  const colB = items.map((f, i) => ({ ...f, _realIndex: i })).filter((_, i) => i % 2 === 1);

  const containerRef = useRef(null);
  const colARef = useRef(null);
  const colBRef = useRef(null);
  // Which column is currently under the cursor — controls the sign of
  // wheel input so the column being pointed at always scrolls in the
  // natural direction (the OTHER one goes opposite).
  const hoveredColRef = useRef("A");
  const offsetRef = useRef(0);   // signed scroll position in px
  const velocityRef = useRef(0); // px per frame
  const setHARef = useRef(0);    // height of one full set in column A
  const setHBRef = useRef(0);

  // We render N copies of each column so the modulo wrap never shows a seam.
  const REPEATS = 6;
  const repeat = (list) => {
    const out = [];
    for (let k = 0; k < REPEATS; k++) {
      list.forEach((item, i) => {
        out.push({ ...item, _key: `${item.id}-${k}-${i}` });
      });
    }
    return out;
  };

  // Measure single-set height after layout. Re-measure on resize and
  // after posters finish loading (their decode can shift heights).
  useEffect(() => {
    const measure = () => {
      if (colARef.current) setHARef.current = colARef.current.scrollHeight / REPEATS;
      if (colBRef.current) setHBRef.current = colBRef.current.scrollHeight / REPEATS;
    };
    measure();
    const t1 = setTimeout(measure, 250);
    const t2 = setTimeout(measure, 1200);
    window.addEventListener("resize", measure);
    return () => {
      clearTimeout(t1); clearTimeout(t2);
      window.removeEventListener("resize", measure);
    };
  }, [items.length]);

  // Physics loop — velocity decays each frame; column transforms wrap
  // modulo one set's height so each column reads as an infinite wheel.
  // Column A moves UP as offset grows, column B moves DOWN by the same
  // amount — true counter-rotation.
  useEffect(() => {
    let raf;
    const tick = () => {
      const v = velocityRef.current;
      offsetRef.current += v;
      velocityRef.current = v * 0.94;
      if (Math.abs(velocityRef.current) < 0.01) velocityRef.current = 0;

      const offset = offsetRef.current;
      const setA = setHARef.current;
      const setB = setHBRef.current;

      if (colARef.current && setA > 0) {
        const wrapped = ((offset % setA) + setA) % setA;
        colARef.current.style.transform = `translate3d(0, ${-wrapped}px, 0)`;
      }
      if (colBRef.current && setB > 0) {
        // Opposite direction: column B starts shifted up by one set so it
        // has buffer above. As offset grows, it slides DOWN.
        const wrapped = ((offset % setB) + setB) % setB;
        colBRef.current.style.transform = `translate3d(0, ${wrapped - setB}px, 0)`;
      }
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, []);

  // Wheel — feeds velocity (so the wheel keeps spinning briefly after
  // the user stops, giving it "weight" like the page's smooth scroll).
  // Sign flips when the user is hovering column B, so whichever column
  // is under the cursor scrolls in the natural direction.
  useEffect(() => {
    const el = containerRef.current;
    if (!el) return;
    const onWheel = (e) => {
      e.preventDefault();
      e.stopPropagation();
      const sign = hoveredColRef.current === "B" ? -1 : 1;
      velocityRef.current += e.deltaY * 0.32 * sign;
      const max = 80;
      if (velocityRef.current > max)  velocityRef.current = max;
      if (velocityRef.current < -max) velocityRef.current = -max;
    };
    el.addEventListener("wheel", onWheel, { passive: false });
    return () => el.removeEventListener("wheel", onWheel);
  }, []);

  // Touch — drag-to-scroll with release momentum.
  useEffect(() => {
    const el = containerRef.current;
    if (!el) return;
    let touchY = null, lastT = 0;
    const onStart = (e) => {
      touchY = e.touches[0].clientY;
      lastT = performance.now();
      velocityRef.current = 0;
    };
    const onMove = (e) => {
      if (touchY == null) return;
      e.preventDefault();
      const y = e.touches[0].clientY;
      const dy = touchY - y;
      offsetRef.current += dy;
      const now = performance.now();
      const dt = Math.max(1, now - lastT);
      velocityRef.current = dy * 16.67 / dt;
      touchY = y;
      lastT = now;
    };
    const onEnd = () => { touchY = null; };
    el.addEventListener("touchstart",  onStart, { passive: true });
    el.addEventListener("touchmove",   onMove,  { passive: false });
    el.addEventListener("touchend",    onEnd);
    el.addEventListener("touchcancel", onEnd);
    return () => {
      el.removeEventListener("touchstart",  onStart);
      el.removeEventListener("touchmove",   onMove);
      el.removeEventListener("touchend",    onEnd);
      el.removeEventListener("touchcancel", onEnd);
    };
  }, []);

  const colStyle = { flex: 1, minWidth: 0, overflow: "hidden" };
  const innerStyle = { display: "flex", flexDirection: "column", gap: 14, willChange: "transform" };

  return (
    <div
      ref={containerRef}
      data-lenis-prevent="true"
      style={{
        height,
        overflow: "hidden",
        display: "flex",
        gap: 14,
        position: "relative",
        WebkitMaskImage: "linear-gradient(180deg, transparent 0, #000 36px, #000 calc(100% - 36px), transparent 100%)",
        maskImage: "linear-gradient(180deg, transparent 0, #000 36px, #000 calc(100% - 36px), transparent 100%)",
        touchAction: "none",
      }}>
      <div style={colStyle} onMouseEnter={() => { hoveredColRef.current = "A"; }}>
        <div ref={colARef} style={innerStyle}>
          {repeat(colA).map((f) => (
            <Thumb
              key={f._key}
              film={f}
              active={f._realIndex === activeIndex}
              onClick={() => onPick(f._realIndex)} />
          ))}
        </div>
      </div>
      <div style={colStyle} onMouseEnter={() => { hoveredColRef.current = "B"; }}>
        <div ref={colBRef} style={innerStyle}>
          {repeat(colB).map((f) => (
            <Thumb
              key={f._key}
              film={f}
              active={f._realIndex === activeIndex}
              onClick={() => onPick(f._realIndex)} />
          ))}
        </div>
      </div>
    </div>
  );
}

/* Variant A: Feature + thumbnail strip (default) */
function Thumb({ film, active, onClick }) {
  const [hover, setHover] = useState(false);
  // Inactive thumbs sit a bit translucent; hover or "active" brings them
  // back to full opacity. Click bubbles to onClick (set as featured).
  const lit = active || hover;
  return (
    <div
      onClick={onClick}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        cursor: "pointer",
        opacity: lit ? 1 : 0.42,
        transform: lit ? "scale(1.0)" : "scale(0.985)",
        transition: "opacity .35s ease, transform .35s ease",
      }}>
      <FilmStill
        paletteKey={film.palette}
        title={film.title}
        client={film.client}
        meta={film.meta}
        index={film._realIndex}
        duration={film.duration}
        category={film.category}
        posterSrc={film.poster} />
    </div>
  );
}

function PortfolioFeature({ items, active, setActive }) {
  const featured = items[active];

  // Sound state. Starts muted (browser autoplay policy needs it muted
  // before any user interaction). Flips to unmuted the moment the user
  // clicks a thumbnail or the sound toggle — both count as user gestures.
  const [muted, setMuted] = useState(true);

  // Featured plays whenever its container is in the viewport. We watch
  // the wrapper div, not the video, so we keep observing across swaps.
  const featuredWrapRef = useRef(null);
  const [inView, setInView] = useState(false);
  useEffect(() => {
    const el = featuredWrapRef.current;
    if (!el) return;
    const io = new IntersectionObserver(
      ([entry]) => setInView(entry.isIntersecting),
      { threshold: 0.35 }
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);

  // Pause when tab is hidden — saves CPU and avoids audio bleed.
  const [pageVisible, setPageVisible] = useState(
    typeof document !== "undefined" ? !document.hidden : true
  );
  useEffect(() => {
    const onVis = () => setPageVisible(!document.hidden);
    document.addEventListener("visibilitychange", onVis);
    return () => document.removeEventListener("visibilitychange", onVis);
  }, []);

  const playing = inView && pageVisible;

  const handleThumbClick = (i) => {
    if (i === active) return;
    setActive(i);
    // Treat the swap itself as the user's intent to actually watch — unmute.
    setMuted(false);
  };

  return (
    <section id="work" style={{ padding: "140px 0 140px" }}>
      <Reveal>
        <SectionHeader
          index="01"
          eyebrow="Selected work"
          title="A gallery of short films."
          note={<><a href="mailto:hello@koolorama.com" style={{color: 'inherit', textDecoration: 'underline', textUnderlineOffset: '3px'}}>Tell us</a> what you think.</>}
        />
      </Reveal>

      <div style={{
        // Film height: capped at 720px or what fits the viewport with
        // breathing room for nav + section padding + caption row.
        "--film-h": "min(720px, calc(100vh - 240px))",
        padding: "0 40px",
        display: "flex",
        gap: 56,
        alignItems: "flex-start",
        justifyContent: "center",
        maxWidth: 1480,
        margin: "0 auto",
      }}>
        {/* Featured */}
        <Reveal style={{ flexShrink: 0 }}>
          <div
            ref={featuredWrapRef}
            style={{
              width: "calc(var(--film-h) * 9 / 16)",
              display: "flex",
              flexDirection: "column",
            }}>
            <div style={{
              fontFamily: "var(--mono)",
              fontSize: 10,
              letterSpacing: "0.18em",
              textTransform: "uppercase",
              color: "var(--muted)",
              marginBottom: 16,
              display: "flex",
              justifyContent: "space-between",
            }}>
              <span>Now showing</span>
              <span>{String(active + 1).padStart(2, "0")} / {String(items.length).padStart(2, "0")}</span>
            </div>
            <FilmStill
              key={featured.id}
              paletteKey={featured.palette}
              title={featured.title}
              client={featured.client}
              meta={featured.meta}
              index={active}
              active
              duration={featured.duration}
              category={featured.category}
              videoSrc={featured.video}
              posterSrc={featured.poster}
              playing={playing}
              muted={muted}
              onSoundToggle={() => setMuted((m) => !m)}
              showControls
            />
            <div style={{
              marginTop: 22,
              display: "flex",
              justifyContent: "space-between",
              alignItems: "center",
            }}>
              <div>
                <div style={{ fontSize: 14, color: "var(--ink)", letterSpacing: "-0.005em" }}>
                  {featured.client} — <span style={{ color: "var(--muted)" }}>{featured.meta}</span>
                </div>
              </div>
              <button
                onClick={() => setActive((a) => (a + 1) % items.length)}
                style={{
                  background: "transparent",
                  border: "none",
                  cursor: "pointer",
                  display: "inline-flex",
                  alignItems: "center",
                  gap: 10,
                  fontSize: 13,
                  color: "var(--ink)",
                  padding: 0,
                }}
              >
                Next
                <svg width="14" height="10" viewBox="0 0 14 10" fill="none">
                  <path d="M1 5H13M13 5L9 1M13 5L9 9" stroke="currentColor" strokeWidth="1.2" />
                </svg>
              </button>
            </div>
          </div>
        </Reveal>

        {/* Twin counter-rotating wheels — same height as the featured film,
            infinite vertical scroll with momentum, columns drift apart. */}
        <div style={{
          width: 380,
          flexShrink: 0,
          paddingTop: 30,
        }}>
          <TwinWheels
            items={items}
            activeIndex={active}
            onPick={handleThumbClick}
            height="var(--film-h)" />
        </div>
      </div>
    </section>
  );
}

/* Variant B: Horizontal film strip */
function PortfolioStrip({ items }) {
  const ref = useRef(null);
  return (
    <section id="work" style={{ padding: "140px 0 140px" }}>
      <Reveal>
        <SectionHeader
          index="01"
          eyebrow="Selected work"
          title="A gallery of short films."
          note="Scroll horizontally. Each film runs between 9 and 22 seconds."
        />
      </Reveal>

      <div
        ref={ref}
        className="no-scrollbar"
        style={{
          overflowX: "auto",
          padding: "0 40px 40px",
          scrollSnapType: "x mandatory",
        }}
      >
        <div style={{ display: "inline-flex", gap: 24, paddingRight: 40 }}>
          {items.map((f, i) => (
            <div key={f.id} style={{
              width: 280,
              flex: "0 0 280px",
              scrollSnapAlign: "start",
            }}>
              <FilmStill
                paletteKey={f.palette}
                title={f.title}
                client={f.client}
                meta={f.meta}
                index={i}
                duration={f.duration}
                category={f.category}
              />
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* Variant C: floating gallery grid */
function PortfolioGallery({ items }) {
  return (
    <section id="work" style={{ padding: "140px 40px 140px" }}>
      <Reveal>
        <SectionHeader
          index="01"
          eyebrow="Selected work"
          title="A gallery of short films."
          note="Hand-curated. Click any frame to enter."
        />
      </Reveal>
      <div style={{
        maxWidth: 1280,
        margin: "0 auto",
        display: "grid",
        gridTemplateColumns: "repeat(4, 1fr)",
        gap: 24,
      }}>
        {items.map((f, i) => (
          <Reveal
            key={f.id}
            delay={i * 50}
            style={{ marginTop: i % 2 === 1 ? 64 : 0 }}
          >
            <FilmStill
              paletteKey={f.palette}
              title={f.title}
              client={f.client}
              meta={f.meta}
              index={i}
              duration={f.duration}
              category={f.category}
            />
          </Reveal>
        ))}
      </div>
    </section>
  );
}

/* ============================================== PROCESS ========== */
function Process() {
  const steps = [
    { n: "01", t: "Concept",        d: "Brief, narrative, references. We write the film before we make it.",  icon: "assets/process/step-01-concept.png" },
    { n: "02", t: "Art Direction",  d: "Visual system, palette, motion language. Locked by humans.",          icon: "assets/process/step-02-art-direction.png" },
    { n: "03", t: "AI Production",  d: "Generation across image, motion and sound — directed shot by shot.",  icon: "assets/process/step-03-ai-production.png" },
    { n: "04", t: "Edit",           d: "Cut, grade, sound design. The 15 seconds are earned in this room.",   icon: "assets/process/step-04-edit.png" },
    { n: "05", t: "Delivery",       d: "Master, social cuts, vertical / square / horizontal exports.",        icon: "assets/process/step-05-delivery.png" },
    { n: "06", t: "Review",         d: "One consolidated round of revisions is always included.", icon: "assets/process/step-06-review.png" },
  ];

  // JS-driven float + scroll-gravity. Each icon oscillates independently
  // (phase staggered by 0.7s) and reacts to scroll velocity. Going DOWN
  // crushes icons more when they're at the bottom of their float cycle;
  // going UP lifts them more when they're at the top — so each icon
  // "feels its weight" differently depending on where it is.
  const iconRefs = useRef(steps.map(() => React.createRef()));
  // Color overlays animated separately: a "wave" sweeps across them
  // (fill from left, drain through right), staggered 0.2s per icon.
  const colorOverlayRefs = useRef(steps.map(() => React.createRef()));
  const sectionRef = useRef(null);
  const waveTRef = useRef(null); // null = no active wave; otherwise wave time in seconds
  const autoTimerRef = useRef(null);
  // Index of the currently-hovered step (-1 = none). When set, the wave
  // is clamped at the moment that icon finishes filling — it freezes
  // there until the user releases the hover.
  const hoveredIconRef = useRef(-1);

  const scrollVelRef = useRef(0);
  const lastScrollY = useRef(typeof window !== "undefined" ? window.scrollY : 0);
  // Tracks last-tick timestamp so we can compute frame-rate-independent
  // delta times for the hover-follow lerp.
  const lastTickTimeRef = useRef(typeof window !== "undefined" ? performance.now() : 0);

  // Fire a single wave through the icons. By default reschedules the
  // next auto-fire 15s out; pass `false` to skip the reschedule (used
  // while a step is hovered — the cycle is paused).
  const fireWave = React.useCallback((scheduleNext = true) => {
    waveTRef.current = 0; // start at t=0 — icons fire in order from i=0
    if (autoTimerRef.current) clearTimeout(autoTimerRef.current);
    if (scheduleNext) {
      autoTimerRef.current = setTimeout(() => fireWave(true), 15000);
    }
  }, []);

  // Step hover handlers — hover area covers the entire step (icon + text).
  // While hovered, the wave smoothly chases the cursor's step. Leaving
  // a step lets the wave resume its natural advance to the end.
  const onStepEnter = React.useCallback((i) => {
    hoveredIconRef.current = i;
    if (waveTRef.current == null) {
      // No active wave — start one. It will chase the hovered target.
      fireWave(false);
    } else {
      // A wave is already in flight (auto-cycle or another hover). Just
      // pause the 15s auto-cycle while the user is hovering.
      if (autoTimerRef.current) {
        clearTimeout(autoTimerRef.current);
        autoTimerRef.current = null;
      }
    }
  }, [fireWave]);
  const onStepLeave = React.useCallback((i) => {
    if (hoveredIconRef.current !== i) return;
    hoveredIconRef.current = -1;
    // Resume the 15s cycle from now.
    if (autoTimerRef.current) clearTimeout(autoTimerRef.current);
    autoTimerRef.current = setTimeout(() => fireWave(true), 15000);
  }, [fireWave]);

  // First trigger: 2s after the section first enters the viewport.
  useEffect(() => {
    const el = sectionRef.current;
    if (!el) return;
    let fired = false;
    const io = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting && !fired) {
        fired = true;
        setTimeout(fireWave, 2000);
        io.disconnect();
      }
    }, { threshold: 0.25 });
    io.observe(el);
    return () => io.disconnect();
  }, [fireWave]);

  // Cleanup the auto timer on unmount.
  useEffect(() => () => {
    if (autoTimerRef.current) clearTimeout(autoTimerRef.current);
  }, []);

  useEffect(() => {
    const onScroll = () => {
      const y = window.scrollY;
      const dy = y - lastScrollY.current;
      lastScrollY.current = y;
      // Each scroll tick injects displacement into the spring "velocity".
      // Tuned for a perceptible nudge without overshoot.
      scrollVelRef.current += dy * 0.22;
      // Hard ceiling so a flick of the wheel can't rocket icons offscreen.
      const HARD = 60;
      if (scrollVelRef.current >  HARD) scrollVelRef.current =  HARD;
      if (scrollVelRef.current < -HARD) scrollVelRef.current = -HARD;
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  useEffect(() => {
    const PERIOD     = 4.6;   // seconds — float cycle length
    const AMPL_Y     = 3.5;   // px — float vertical amplitude
    const SCALE_AMPL = 0.04;  // float scale: 1 → 1.04
    const ROT_AMPL   = 0.6;   // deg — float rotation
    const FRICTION   = 0.90;  // per-frame decay of scroll velocity
    const MIN_VEL    = 0.04;  // snap-to-zero threshold
    const MAX_DISPL  = 24;    // px — clamp on scroll-induced offset

    const start = performance.now();
    let raf;
    const tick = () => {
      const now = performance.now();
      const t = (now - start) / 1000;

      // Decay scroll velocity each frame.
      scrollVelRef.current *= FRICTION;
      if (Math.abs(scrollVelRef.current) < MIN_VEL) scrollVelRef.current = 0;
      const vel = scrollVelRef.current;

      iconRefs.current.forEach((ref, i) => {
        const el = ref.current;
        if (!el) return;
        // Per-icon phase: each icon staggered by 0.7s.
        const phase = (((t + i * 0.7) % PERIOD) / PERIOD);
        const a = phase * Math.PI * 2;
        // heightNorm: 0 = at floor (low), 1 = at apex (high). Smooth ease.
        const heightNorm = (1 - Math.cos(a)) / 2;
        // Float Y is 0 at low and -AMPL at high (CSS Y axis: up is negative).
        const yFloat = -AMPL_Y * heightNorm;
        const scale  = 1 + SCALE_AMPL * heightNorm;
        const rot    = ROT_AMPL * Math.sin(a);

        // Scroll-induced offset, modulated by where the icon is in its
        // cycle. Down-force (vel > 0) crushes more when the icon is LOW;
        // up-force (vel < 0) lifts more when the icon is HIGH.
        let factor;
        if (vel >= 0) {
          factor = 1 - heightNorm * 0.7;          // [0.3 high, 1 low]
        } else {
          factor = 0.3 + heightNorm * 0.7;        // [0.3 low, 1 high]
        }
        let yScroll = vel * factor;
        if (yScroll >  MAX_DISPL) yScroll =  MAX_DISPL;
        if (yScroll < -MAX_DISPL) yScroll = -MAX_DISPL;

        const y = yFloat + yScroll;
        el.style.transform =
          `translate3d(0, ${y.toFixed(2)}px, 0) scale(${scale.toFixed(3)}) rotate(${rot.toFixed(2)}deg)`;
      });

      // Color-flow wave — sweeps fill→drain across each icon when
      // waveTRef is set, staggered by 0.2s per icon.
      // We store waveT directly (not derived from a start timestamp) so
      // that during hover we can hold the wave still and let the lerp
      // fully converge to the target — otherwise the natural per-frame
      // advance creates a residual gap that leaves the hovered icon at
      // ~98% filled instead of 100%.
      const STAGGER   = 0.2;
      const FILL_DUR  = 0.5;
      const DRAIN_DUR = 0.5;
      const ICON_DUR  = FILL_DUR + DRAIN_DUR;
      const FOLLOW_RATE = 18.0;
      const SNAP_EPS    = 0.004; // seconds — snap to target when this close

      if (waveTRef.current != null) {
        // Compute frame delta locally so this block doesn't depend on
        // when the scroll-gravity section ran.
        const dt = Math.min(0.1, (performance.now() - lastTickTimeRef.current) / 1000);

        const hov = hoveredIconRef.current;
        if (hov >= 0) {
          // Hovered: lerp waveT toward the target. NO natural advance
          // while hovered — that way the lerp actually reaches target.
          const pauseTargetT = hov * STAGGER + FILL_DUR;
          const factor = 1 - Math.exp(-FOLLOW_RATE * dt);
          let newWaveT = waveTRef.current + (pauseTargetT - waveTRef.current) * factor;
          if (Math.abs(newWaveT - pauseTargetT) < SNAP_EPS) {
            newWaveT = pauseTargetT;
          }
          waveTRef.current = newWaveT;
        } else {
          // Not hovered: wave advances naturally at wall-clock speed.
          waveTRef.current += dt;
        }
        const waveT = waveTRef.current;

        colorOverlayRefs.current.forEach((ref, i) => {
          const el = ref.current;
          if (!el) return;
          const iconT = waveT - i * STAGGER;
          let left = 0, right = 0;
          if (iconT > 0 && iconT < ICON_DUR) {
            if (iconT < FILL_DUR) {
              left = 0;
              right = (iconT / FILL_DUR) * 100;
            } else {
              left  = ((iconT - FILL_DUR) / DRAIN_DUR) * 100;
              right = 100;
            }
          }
          el.style.setProperty("--color-flow-left",  left  + "%");
          el.style.setProperty("--color-flow-right", right + "%");
        });
        // All icons done? Clear the wave so we stop touching DOM.
        // Only auto-clear when NOT hovered — otherwise we'd snap empty
        // mid-hover.
        const lastIconEnd = (iconRefs.current.length - 1) * STAGGER + ICON_DUR;
        if (hov < 0 && waveT > lastIconEnd + 0.1) {
          waveTRef.current = null;
          colorOverlayRefs.current.forEach((ref) => {
            const el = ref.current;
            if (!el) return;
            el.style.setProperty("--color-flow-left",  "0%");
            el.style.setProperty("--color-flow-right", "0%");
          });
        }
      }

      raf = requestAnimationFrame(tick);
      lastTickTimeRef.current = performance.now();
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, []);
  return (
    <section id="process" ref={sectionRef} style={{
      padding: "140px 40px 140px",
      borderTop: "1px solid var(--rule)",
    }}>
      <Reveal>
        <SectionHeader
          index="02"
          eyebrow="Process"
          title="Directed, not prompted."
          note="Every film moves through six rooms. People are in every one of them."
        />
      </Reveal>

      <div style={{
        maxWidth: 1280,
        margin: "0 auto",
        display: "grid",
        gridTemplateColumns: "repeat(6, 1fr)",
        gap: 24,
        position: "relative",
      }}>
        {/* connecting hairline — full-width; each icon's wider opaque
            "slot" (icon + 20px padding on each side) hides the line
            beneath it, producing the 20px gap on each side of each icon. */}
        <div style={{
          position: "absolute",
          top: 12, // vertically centered on the 25px icon (12.5 ≈ 12)
          left: 0,
          right: 0,
          height: 1,
          background: "var(--rule-strong)",
        }} />
        {steps.map((s, i) => (
          <Reveal key={s.n} delay={i * 80}>
            <div
              style={{ position: "relative" }}
              onMouseEnter={() => onStepEnter(i)}
              onMouseLeave={() => onStepLeave(i)}
            >
              {/* Iridescent icon — 25px square, sitting inside a wider
                  75px slot (25px padding each side) that hides the
                  connecting line for that span.
                  Stacked structure: a desaturated base + a color overlay
                  with an animated CSS mask. A wave of color sweeps across
                  the overlay every 15s, staggered per-icon. */}
              <div style={{
                position: "relative",
                width: 75,
                height: 25,
                marginLeft: -25,
                marginBottom: 22,
                background: "var(--bg)",
                display: "flex",
                alignItems: "center",
                justifyContent: "center",
              }}>
                <div
                  ref={iconRefs.current[i]}
                  style={{
                    position: "relative",
                    width: 25,
                    height: 25,
                    willChange: "transform",
                    cursor: "default",
                  }}
                >
                  {/* Grayscale base — always visible, desaturated. */}
                  <img
                    src={s.icon}
                    alt=""
                    aria-hidden="true"
                    width="25"
                    height="25"
                    loading="lazy"
                    decoding="async"
                    style={{
                      position: "absolute",
                      inset: 0,
                      width: 25,
                      height: 25,
                      objectFit: "contain",
                      userSelect: "none",
                      pointerEvents: "none",
                      filter: "grayscale(1) brightness(1.05)",
                    }} />
                  {/* Color overlay — masked band of color sweeps across.
                      Per-icon delay produces the "current" passing from
                      step 01 → step 06. */}
                  <img
                    ref={colorOverlayRefs.current[i]}
                    src={s.icon}
                    alt=""
                    aria-hidden="true"
                    className="icon-color-overlay"
                    width="25"
                    height="25"
                    loading="lazy"
                    decoding="async"
                    style={{
                      position: "absolute",
                      inset: 0,
                      width: 25,
                      height: 25,
                      objectFit: "contain",
                      userSelect: "none",
                      pointerEvents: "none",
                    }} />
                </div>
              </div>
              <div style={{
                fontFamily: "var(--mono)",
                fontSize: 10,
                letterSpacing: "0.18em",
                textTransform: "uppercase",
                color: "var(--muted)",
                marginBottom: 14,
              }}>{s.n}</div>
              <div style={{
                fontFamily: "var(--sans)",
                fontSize: 22,
                fontWeight: 400,
                letterSpacing: "-0.02em",
                color: "var(--ink)",
                marginBottom: 12,
              }}>{s.t}</div>
              <div style={{
                fontSize: 13,
                lineHeight: 1.5,
                color: "var(--muted)",
              }}>{s.d}</div>
            </div>
          </Reveal>
        ))}
      </div>
    </section>
  );
}

/* ============================================== SERVICES ========= */
function Services() {
  const items = [
    { t: "Character-Driven Spots", d: "Humor, emotion, story. Mini-films with AI actors.", dur: "15–22s" },
    { t: "Scroll-Stopping Ads",   d: "The kind people actually watch. Reels, TikTok, Shorts.", dur: "9–15s" },
    { t: "Product Videos",         d: "Hero shots, in-context, on-loop.",    dur: "10–20s" },
    { t: "Launch Clips",           d: "Teasers, drops, countdown moments.",  dur: "8–15s" },
    { t: "Brand Moments",          d: "Cinematic identity pieces. Mood, tone, vibe.", dur: "5–20s" },
    { t: "Campaign Kits",          d: "One concept, every format. Feed, story, square, wide.", dur: "Series" },
  ];
  return (
    <section id="services" style={{
      padding: "140px 40px 140px",
      borderTop: "1px solid var(--rule)",
    }}>
      <Reveal>
        <SectionHeader
          index="03"
          eyebrow="Services"
          title="What we make."
          note="Short forms, premium output. Always built for a feed."
        />
      </Reveal>
      <div style={{
        maxWidth: 1080,
        margin: "0 auto",
      }}>
        {items.map((s, i) => (
          <Reveal key={s.t} delay={i * 50}>
            <a
              href="#"
              style={{
                display: "grid",
                gridTemplateColumns: "60px 1.4fr 1fr 120px 24px",
                alignItems: "center",
                padding: "28px 0",
                borderTop: i === 0 ? "1px solid var(--rule-strong)" : "none",
                borderBottom: "1px solid var(--rule-strong)",
                color: "var(--ink)",
                transition: "background .35s ease",
              }}
              onMouseEnter={(e) => e.currentTarget.style.background = "rgba(10,10,10,0.02)"}
              onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}
            >
              <span style={{
                fontFamily: "var(--mono)",
                fontSize: 11,
                letterSpacing: "0.18em",
                color: "var(--muted-2)",
              }}>{String(i + 1).padStart(2, "0")}</span>
              <span style={{
                fontFamily: "var(--sans)",
                fontSize: "clamp(28px, 3.2vw, 44px)",
                fontWeight: 400,
                letterSpacing: "-0.025em",
                lineHeight: 1.0,
              }}>{s.t}</span>
              <span style={{
                fontSize: 14,
                color: "var(--muted)",
              }}>{s.d}</span>
              <span style={{
                fontFamily: "var(--mono)",
                fontSize: 11,
                letterSpacing: "0.18em",
                color: "var(--muted)",
                textTransform: "uppercase",
              }}>{s.dur}</span>
              <span style={{ justifySelf: "end", color: "var(--muted-2)" }}>
                <svg width="14" height="10" viewBox="0 0 14 10" fill="none">
                  <path d="M1 5H13M13 5L9 1M13 5L9 9" stroke="currentColor" strokeWidth="1.2" />
                </svg>
              </span>
            </a>
          </Reveal>
        ))}
      </div>
    </section>
  );
}

/* ============================================== PRICING ========= */
function Pricing() {
  const tiers = [
    {
      name: "First Film",
      price: "€300",
      period: "one-time",
      description: "One film to see if we click.",
      features: [
        "1 original film (15–30s)",
        "Creative brief included",
        "2 revision rounds",
        "Delivered in days",
      ],
      note: "€200 credit toward Monthly if you sign within 14 days.",
      cta: "Start here",
      highlight: false,
    },
    {
      name: "Monthly",
      price: "€600",
      period: "/month",
      description: "Consistent presence. Cancel anytime.",
      features: [
        "3 original films/month",
        "Ongoing creative direction",
        "2 revision rounds per film",
        "Extra film: €150",
        "Variation: €90",
      ],
      note: null,
      cta: "Start a project",
      highlight: true,
    },
    {
      name: "Scale",
      price: "€1,800",
      period: "/month",
      description: "For brands that move fast.",
      features: [
        "6+ films/month",
        "3 revision rounds per film",
        "Priority delivery",
        "Multi-format exports",
        "Dedicated creative strategy",
      ],
      note: null,
      cta: "Book a call",
      highlight: false,
    },
  ];

  return (
    <section id="pricing" style={{
      padding: "140px 40px 140px",
      borderTop: "1px solid var(--rule)",
    }}>
      <Reveal>
        <SectionHeader
          index="04"
          eyebrow="Pricing"
          title="Simple, honest."
          note="No hidden fees. No lock-in. Start small or go big."
        />
      </Reveal>

      <div style={{
        maxWidth: 1080,
        margin: "0 auto",
        display: "grid",
        gridTemplateColumns: "repeat(3, 1fr)",
        gap: 1,
        background: "var(--rule-strong)",
        border: "1px solid var(--rule-strong)",
      }}>
        {tiers.map((tier, i) => (
          <Reveal key={tier.name} delay={i * 80}>
            <div style={{
              padding: "40px 32px",
              background: tier.highlight ? "rgba(10,10,10,0.025)" : "var(--bg)",
              display: "flex",
              flexDirection: "column",
              minHeight: 480,
            }}>
              <div style={{
                fontFamily: "var(--mono)",
                fontSize: 10,
                letterSpacing: "0.18em",
                textTransform: "uppercase",
                color: "var(--muted)",
                marginBottom: 20,
              }}>
                {tier.name}
              </div>

              <div style={{
                display: "flex",
                alignItems: "baseline",
                gap: 4,
                marginBottom: 8,
              }}>
                <span style={{
                  fontFamily: "var(--sans)",
                  fontSize: 48,
                  fontWeight: 400,
                  letterSpacing: "-0.035em",
                  color: "var(--ink)",
                  lineHeight: 1,
                }}>
                  {tier.price}
                </span>
                <span style={{
                  fontFamily: "var(--mono)",
                  fontSize: 12,
                  color: "var(--muted)",
                  letterSpacing: "0.05em",
                }}>
                  {tier.period}
                </span>
              </div>

              <div style={{
                fontSize: 14,
                color: "var(--muted)",
                marginBottom: 32,
                lineHeight: 1.4,
              }}>
                {tier.description}
              </div>

              <div style={{
                flex: 1,
                display: "flex",
                flexDirection: "column",
                gap: 14,
                marginBottom: 32,
              }}>
                {tier.features.map((f) => (
                  <div key={f} style={{
                    fontSize: 13,
                    color: "var(--ink)",
                    lineHeight: 1.4,
                    display: "flex",
                    gap: 10,
                    alignItems: "flex-start",
                  }}>
                    <span style={{
                      color: "var(--muted-2)",
                      fontSize: 11,
                      lineHeight: "18px",
                      flexShrink: 0,
                    }}>—</span>
                    {f}
                  </div>
                ))}
              </div>

              {tier.note && (
                <div style={{
                  fontSize: 12,
                  color: "var(--muted)",
                  fontStyle: "italic",
                  marginBottom: 24,
                  lineHeight: 1.4,
                  paddingTop: 16,
                  borderTop: "1px solid var(--rule)",
                }}>
                  {tier.note}
                </div>
              )}

              <a
                href="#contact"
                onClick={(e) => {
                  e.preventDefault();
                  const el = document.querySelector("#contact");
                  if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
                }}
                className="btn"
                style={{
                  textAlign: "center",
                  marginTop: "auto",
                  display: "block",
                  padding: "14px 0",
                }}
              >
                {tier.cta}
              </a>
            </div>
          </Reveal>
        ))}
      </div>
    </section>
  );
}

/* ============================================== WHY ============== */
function Why({ t }) {
  const pairs = [
    { not: "Not DIY",            yes: "Directed by a creative who gives a damn." },
    { not: "Not a generator",    yes: "A studio that writes before it generates." },
    { not: "Not a template kit", yes: "Bespoke scripts, characters, tone." },
    { not: "Not slow",           yes: "Production in days, not weeks." },
    { not: "Not horizontal",     yes: "Vertical-native. Social-first." },
    { not: "Not generic AI",     yes: "The kind of AI video that doesn't look like AI." },
  ];
  return (
    <section style={{
      padding: "160px 40px 160px",
      borderTop: "1px solid var(--rule)",
      position: "relative",
      overflow: "hidden",
    }}>
      {t.accentMoment && (
        <div style={{
          position: "absolute",
          right: "-120px",
          top: "30%",
          opacity: 0.55,
          pointerEvents: "none",
        }}>
          <IridescentBlob size={420} />
        </div>
      )}

      <div style={{ maxWidth: 1280, margin: "0 auto" }}>
        <Reveal>
          <div style={{
            fontFamily: "var(--mono)",
            fontSize: 11,
            letterSpacing: "0.18em",
            textTransform: "uppercase",
            color: "var(--muted)",
            marginBottom: 40,
          }}>
            05 — Why Koolorama
          </div>
        </Reveal>

        <Reveal delay={120}>
          <p style={{
            fontFamily: "var(--sans)",
            fontWeight: 300,
            fontSize: "clamp(36px, 4.6vw, 72px)",
            lineHeight: 1.05,
            letterSpacing: "-0.035em",
            margin: 0,
            maxWidth: 1100,
            color: "var(--ink)",
            textWrap: "pretty",
          }}>
            We make films people actually watch. <span style={{
              fontFamily: "var(--serif)",
              fontStyle: "italic",
              color: "var(--muted)",
            }}>The AI is in the room, but it doesn't write the jokes.</span> Concept, craft and cut are directed by humans — so the work feels made, not generated.
          </p>
        </Reveal>

        <div style={{
          marginTop: 80,
          display: "grid",
          gridTemplateColumns: "repeat(3, 1fr)",
          gap: 0,
          borderTop: "1px solid var(--rule-strong)",
        }}>
          {pairs.map((p, i) => (
            <Reveal key={p.not} delay={i * 60}>
              <div style={{
                padding: "32px 32px 32px 0",
                borderBottom: "1px solid var(--rule)",
                borderRight: (i % 3 !== 2) ? "1px solid var(--rule)" : "none",
                paddingLeft: (i % 3 === 0) ? 0 : 32,
                minHeight: 140,
              }}>
                <div style={{
                  fontFamily: "var(--mono)",
                  fontSize: 11,
                  letterSpacing: "0.16em",
                  textTransform: "uppercase",
                  color: "var(--muted-2)",
                  marginBottom: 14,
                  textDecoration: "line-through",
                }}>{p.not}</div>
                <div style={{
                  fontFamily: "var(--sans)",
                  fontSize: 22,
                  fontWeight: 400,
                  letterSpacing: "-0.02em",
                  color: "var(--ink)",
                  lineHeight: 1.15,
                }}>{p.yes}</div>
              </div>
            </Reveal>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ============================================== FINAL CTA ======== */
function FinalCTA({ t }) {
  return (
    <section id="contact" style={{
      padding: "180px 40px 120px",
      borderTop: "1px solid var(--rule)",
      position: "relative",
      overflow: "hidden",
    }}>
      <div style={{
        maxWidth: 1080,
        margin: "0 auto",
        textAlign: "center",
        position: "relative",
      }}>
        <Reveal>
          <div className="eyebrow" style={{ marginBottom: 36 }}>
            06 — Studio
          </div>
        </Reveal>
        <Reveal delay={100}>
          <h2 style={{
            fontFamily: "var(--sans)",
            fontWeight: 400,
            fontSize: "clamp(56px, 9.6vw, 168px)",
            lineHeight: 0.92,
            letterSpacing: "-0.045em",
            margin: 0,
            color: "var(--ink)",
          }}>
            Let's make
            <br />
            <span style={{
              fontFamily: "var(--serif)",
              fontStyle: "italic",
              letterSpacing: "-0.02em",
            }}>a short.</span>
          </h2>
        </Reveal>
        <Reveal delay={240}>
          <p style={{
            margin: "40px auto 0",
            maxWidth: 520,
            fontSize: 17,
            lineHeight: 1.5,
            color: "var(--muted)",
          }}>
            {t.finalCtaIntro}
          </p>
        </Reveal>
        <Reveal delay={360}>
          <form
            onSubmit={(e) => e.preventDefault()}
            style={{
              marginTop: 48,
              display: "flex",
              gap: 8,
              maxWidth: 520,
              margin: "48px auto 0",
              border: "1px solid var(--rule-strong)",
              borderRadius: 999,
              padding: 6,
              background: "rgba(255,255,255,0.7)",
              backdropFilter: "blur(8px)",
            }}
          >
            <input
              type="email"
              placeholder="your@studio.com"
              style={{
                flex: 1,
                border: "none",
                outline: "none",
                background: "transparent",
                padding: "10px 18px",
                fontSize: 14,
                fontFamily: "var(--sans)",
                color: "var(--ink)",
              }}
            />
            <button className="btn" type="submit" style={{ padding: "12px 22px" }}>
              <span>{t.ctaCopy}</span>
              <span className="arrow">
                <svg width="14" height="10" viewBox="0 0 14 10" fill="none">
                  <path d="M1 5H13M13 5L9 1M13 5L9 9" stroke="currentColor" strokeWidth="1.4" />
                </svg>
              </span>
            </button>
          </form>
        </Reveal>
        <Reveal delay={500}>
          <div style={{
            marginTop: 28,
            fontFamily: "var(--mono)",
            fontSize: 11,
            letterSpacing: "0.18em",
            textTransform: "uppercase",
            color: "var(--muted-2)",
          }}>
            Or write directly — {t.finalCtaEmail}
          </div>
        </Reveal>
      </div>
    </section>
  );
}

/* ============================================== FOOTER ========== */
function Footer() {
  return (
    <footer style={{
      padding: "40px 40px 40px",
      borderTop: "1px solid var(--rule)",
      display: "grid",
      gridTemplateColumns: "1fr 1fr 1fr",
      alignItems: "center",
      gap: 24,
      fontSize: 12,
      color: "var(--muted)",
    }}>
      <div style={{ display: "flex", alignItems: "center", gap: 20 }}>
        <Wordmark size={14} />
        <span style={{ fontFamily: "var(--mono)", fontSize: 11, letterSpacing: "0.18em", textTransform: "uppercase" }}>
          © 2026
        </span>
      </div>
      <div style={{ textAlign: "center", fontFamily: "var(--mono)", fontSize: 11, letterSpacing: "0.18em", textTransform: "uppercase" }}>
        Paris · Montreal · Remote
      </div>
      <div style={{
        display: "flex",
        justifyContent: "flex-end",
        gap: 22,
        fontSize: 12,
      }}>
        <a href="#">Instagram</a>
        <a href="#">Vimeo</a>
        <a href="#">LinkedIn</a>
      </div>
    </footer>
  );
}

/* ============================================== INTRO =========== */
// Geometry of the prism inside the intro video and inside the static prism.png,
// measured from pixel data. We use these to make the video's final frame land
// on the hero prism at the exact same on-screen size + position.
const INTRO_VIDEO = {
  // Final-frame prism bounding box, normalized to the 1:1 video frame.
  prismWidthPct:  0.6712,   // prism width  / video width
  prismHeightPct: 0.7627,   // prism height / video height
  prismCxPct:     0.5076,   // prism center X / video width
  prismCyPct:     0.5629,   // prism center Y / video height
};
const HERO_PRISM = {
  // Static prism.png — content bbox vs. image bounds
  contentWidthPct:  0.9817,
  contentHeightPct: 0.9403,
  contentCxPct:     0.5012,
  contentCyPct:     0.4916,
};

function Intro({ onDone, heroPrismSize }) {
  const videoRef = useRef(null);
  const [ready, setReady] = useState(false);
  const [ending, setEnding] = useState(false);
  const [entered, setEntered] = useState(false); // gates the initial fade-in

  // Match the prism HEIGHT between video final frame and hero prism container.
  // Hero prism visible height = heroPrismSize * HERO_PRISM.contentHeightPct.
  // Video display height = that / INTRO_VIDEO.prismHeightPct.
  const heroContentH = heroPrismSize * HERO_PRISM.contentHeightPct;
  const heroContentW = heroPrismSize * HERO_PRISM.contentWidthPct;
  // Use the average of the two scales for a balanced fit (video rotation
  // changes aspect mid-flight; final frame is taller than the static png).
  const scaleByH = heroContentH / (INTRO_VIDEO.prismHeightPct);
  const scaleByW = heroContentW / (INTRO_VIDEO.prismWidthPct);
  const videoSize = (scaleByH + scaleByW) / 2; // px, square

  // Position so video's prism-center sits where hero's prism-center will sit.
  // Hero blob container: left:50%, top:46%, translate(-50%,-50%), size=heroPrismSize
  // So hero prism center on screen ≈ (50vw, 46vh) corrected by HERO_PRISM center offset:
  //   dx = (HERO_PRISM.contentCxPct - 0.5) * heroPrismSize
  //   dy = (HERO_PRISM.contentCyPct - 0.5) * heroPrismSize
  const heroDx = (HERO_PRISM.contentCxPct - 0.5) * heroPrismSize;
  const heroDy = (HERO_PRISM.contentCyPct - 0.5) * heroPrismSize;
  // Place video center accordingly, then offset within video so prism center aligns
  // video offset from its own center to its prism center:
  const videoDx = (INTRO_VIDEO.prismCxPct - 0.5) * videoSize;
  const videoDy = (INTRO_VIDEO.prismCyPct - 0.5) * videoSize;

  useEffect(() => {
    document.body.style.overflow = "hidden";
    window.scrollTo(0, 0);
    if (window.__lenis) window.__lenis.stop();
    // Trigger the initial fade-in on next frame (so the transition runs)
    const raf = requestAnimationFrame(() => setEntered(true));
    const v = videoRef.current;
    if (!v) return () => cancelAnimationFrame(raf);
    const onCanPlay = () => setReady(true);
    const onPlaying = () => setReady(true);
    // Start the fade-out BEFORE the video actually ends so the white
    // background appears to bleed through under the final frame rather
    // than cutting after it. The video keeps playing during the fade.
    const FADE_LEAD = 0.55; // seconds before duration to begin fade
    const FADE_DURATION = 600; // ms — matches the CSS transition below
    let endTimer = null;
    let faded = false;
    const beginFade = () => {
      if (faded) return;
      faded = true;
      setEnding(true);
      endTimer = setTimeout(() => {
        document.body.style.overflow = "";
        if (window.__lenis) window.__lenis.start();
        onDone();
      }, FADE_DURATION);
    };
    const onTimeUpdate = () => {
      if (!v.duration || isNaN(v.duration)) return;
      if (v.currentTime >= v.duration - FADE_LEAD) beginFade();
    };
    // Safety net — if timeupdate misses (rare), the ended event still triggers.
    const onEnded = () => beginFade();
    v.addEventListener("canplay", onCanPlay, { once: true });
    v.addEventListener("playing", onPlaying, { once: true });
    v.addEventListener("timeupdate", onTimeUpdate);
    v.addEventListener("ended", onEnded, { once: true });
    const p = v.play();
    if (p && p.catch) p.catch(() => { setReady(true); setTimeout(beginFade, 200); });
    return () => {
      cancelAnimationFrame(raf);
      if (endTimer) clearTimeout(endTimer);
      v.removeEventListener("canplay", onCanPlay);
      v.removeEventListener("playing", onPlaying);
      v.removeEventListener("timeupdate", onTimeUpdate);
      v.removeEventListener("ended", onEnded);
    };
  }, [onDone]);

  return (
    <div
      style={{
        position: "fixed",
        inset: 0,
        zIndex: 100,
        background: "#fafafa",
        opacity: ending ? 0 : 1,
        transition: "opacity 0.6s ease",
        pointerEvents: ending ? "none" : "auto",
      }}
    >
      {/* Single fade wrapper: everything inside fades in together, only once. */}
      <div
        style={{
          position: "absolute",
          inset: 0,
          opacity: entered ? 1 : 0,
          transition: "opacity 1.2s cubic-bezier(.4,0,.2,1)",
        }}
      >
        {/* Static poster placed exactly where the video will sit — instant first paint.
            Hidden the instant the video is ready (no fade, no double-blend with the video). */}
        <img
          src="assets/intro-poster.png"
          alt=""
          aria-hidden="true"
          style={{
            position: "absolute",
            left: "50%",
            top: "46%",
            width: videoSize,
            height: videoSize,
            transform: `translate(${-videoSize/2 - videoDx + heroDx}px, ${-videoSize/2 - videoDy + heroDy}px)`,
            opacity: ready ? 0 : 1,
            objectFit: "contain",
            pointerEvents: "none",
          }}
        />
        <video
          ref={videoRef}
          muted
          playsInline
          autoPlay
          preload="auto"
          fetchpriority="high"
          style={{
            position: "absolute",
            left: "50%",
            top: "46%",
            width: videoSize,
            height: videoSize,
            transform: `translate(${-videoSize/2 - videoDx + heroDx}px, ${-videoSize/2 - videoDy + heroDy}px)`,
            opacity: ready ? 1 : 0,
            objectFit: "contain",
          }}
        >
          <source src="assets/intro-v3.webm" type="video/webm" />
          <source src="assets/intro-v3.mp4"  type="video/mp4" />
        </video>
      </div>
    </div>
  );
}

/* ============================================== APP ============= */
function App() {
  const [tweaks, setTweak] = window.useTweaks(TWEAK_DEFAULTS);
  const [introDone, setIntroDone] = useState(false);

  useEffect(() => {
    if (!introDone) return;
    window.dispatchEvent(new CustomEvent("koolorama:hero-shown"));
  }, [introDone]);

  return (
    <div data-screen-label="Koolorama Home" data-om-validate="page">
      {!introDone && <Intro onDone={() => setIntroDone(true)} heroPrismSize={tweaks.blobSize} />}
      <div
        style={{
          opacity: introDone ? 1 : 0,
          transition: "opacity 0.9s ease 0.05s",
        }}
      >
      <Nav animateLogo={introDone} />
      <main>
        <Hero t={tweaks} animatePill={introDone} />
        <Ticker />
        <Portfolio t={tweaks} />
        <Process />
        <Services />
        <Pricing />
        <Why t={tweaks} />
        <FinalCTA t={tweaks} />
      </main>
      <Footer />
      </div>

      <window.TweaksPanel title="Tweaks">
        <window.TweakSection label="Hero">
          <window.TweakText
            label="Pill"
            value={tweaks.heroPill}
            onChange={(v) => setTweak("heroPill", v)}
          />
          <window.TweakSelect
            label="Headline"
            value={tweaks.headlineVariant}
            onChange={(v) => setTweak("headlineVariant", v)}
            options={[
              { value: "studio",  label: "Short films, crafted with AI." },
              { value: "ai",      label: "AI Video Studio." },
              { value: "social",  label: "Social-first AI films." },
              { value: "brand",   label: "Creative AI videos that stop the scroll." },
              { value: "fifteen", label: "15 seconds. Cinema-grade." },
            ]}
          />
          <window.TweakText
            label="Subline"
            value={tweaks.heroSubline}
            onChange={(v) => setTweak("heroSubline", v)}
          />
          <window.TweakText
            label="CTA copy"
            value={tweaks.ctaCopy}
            onChange={(v) => setTweak("ctaCopy", v)}
          />
        </window.TweakSection>

        <window.TweakSection label="Prism">
          <window.TweakToggle
            label="Show in hero"
            value={tweaks.showBlob}
            onChange={(v) => setTweak("showBlob", v)}
          />
          <window.TweakSlider
            label="Size"
            value={tweaks.blobSize}
            min={200} max={760} step={10}
            onChange={(v) => setTweak("blobSize", v)}
          />
          <window.TweakSlider
            label="Opacity"
            value={tweaks.blobOpacity}
            min={20} max={100} step={2} unit="%"
            onChange={(v) => setTweak("blobOpacity", v)}
          />
          <window.TweakToggle
            label="Accent in “Why”"
            value={tweaks.accentMoment}
            onChange={(v) => setTweak("accentMoment", v)}
          />
        </window.TweakSection>

        <window.TweakSection label="Portfolio">
          <window.TweakRadio
            label="Layout"
            value={tweaks.portfolioVariant}
            onChange={(v) => setTweak("portfolioVariant", v)}
            options={[
              { value: "feature", label: "Feature" },
              { value: "strip",   label: "Strip" },
              { value: "gallery", label: "Gallery" },
            ]}
          />
        </window.TweakSection>

        <window.TweakSection label="Final CTA">
          <window.TweakText
            label="Intro"
            value={tweaks.finalCtaIntro}
            onChange={(v) => setTweak("finalCtaIntro", v)}
          />
          <window.TweakText
            label="Email"
            value={tweaks.finalCtaEmail}
            onChange={(v) => setTweak("finalCtaEmail", v)}
          />
        </window.TweakSection>
      </window.TweaksPanel>
    </div>
  );
}

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

/* ------------------------------------------------------------------ */
/* Lenis — weighted scroll                                            */
/* ------------------------------------------------------------------ */
(function initLenis() {
  if (typeof Lenis === "undefined") return;
  const lenis = new Lenis({
    duration: 1.5,                    // weight — higher = heavier feel
    easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
    smoothWheel: true,
    wheelMultiplier: 0.95,            // very slight damping on wheel input
    touchMultiplier: 1.2,
  });
  window.__lenis = lenis;
  function raf(time) {
    lenis.raf(time);
    requestAnimationFrame(raf);
  }
  requestAnimationFrame(raf);
})();
