/* Home page — TabletHero (Polarsteps-style scroll reveal, fuses hero + project 01) + projects 02-05 + contact */

const { useState: useStateHome, useEffect: useEffectHome, useRef: useRefHome } = React;

/* Tablet screen image — resolves to an inlined blob URL in the standalone
   bundle, falls back to the file path for the live (non-bundled) preview. */
const TABLET_SCREEN_SRC =
  (typeof window !== "undefined" && window.__resources && window.__resources.tabletScreen) ||
  "assets/tablet-screen.jpg";
const TABLET_SHOT_SRC =
  (typeof window !== "undefined" && window.__resources && window.__resources.tabletShot) ||
  "assets/tablet-screenshot.png";
const TABLET_DASH_SRC =
  (typeof window !== "undefined" && window.__resources && window.__resources.tabletDash) ||
  "assets/tablet-dashboard.png";
const AG_HOME_FULL_SRC =
  (typeof window !== "undefined" && window.__resources && window.__resources.algoraHomeFull) ||
  "assets/algora-home-full.png";
const AG_HOME_FULL_NAV_SRC =
  (typeof window !== "undefined" && window.__resources && window.__resources.algoraHomeFullNav) ||
  "assets/algora-home-full-nav.png";
const PHONE_SHOT_SRC =
  (typeof window !== "undefined" && window.__resources && window.__resources.phoneShot) ||
  "assets/phone-screenshot.png";
const PHONE_SHOT2_SRC =
  (typeof window !== "undefined" && window.__resources && window.__resources.phoneShot2) ||
  "assets/phone-screenshot-2.png";
const SX_HOME_LIGHT_SRC =
  (typeof window !== "undefined" && window.__resources && window.__resources.stablexHomeLight) ||
  "assets/stablex-home-light.png";
const SX_HOME_SCROLL_SRC =
  (typeof window !== "undefined" && window.__resources && window.__resources.stablexHomeScroll) ||
  "assets/stablex-home-scroll.png";
const MP_HOME_SCROLL_SRC =
  (typeof window !== "undefined" && window.__resources && window.__resources.morparaHomeScroll) ||
  "assets/morpara-home-scroll.png";
const SX_BOTTOMNAV_SRC =
  (typeof window !== "undefined" && window.__resources && window.__resources.stablexBottomNav) ||
  "assets/stablex-bottom-nav.png";
const MP_BOTTOMNAV_SRC =
  (typeof window !== "undefined" && window.__resources && window.__resources.morparaBottomNav) ||
  "assets/morpara-bottom-nav.png";

/* Project brand-mark tiles for the new three-mark hero */
const SX_MARK_SRC =
  (typeof window !== "undefined" && window.__resources && window.__resources.stablexBrand) ||
  "assets/stablex-brand.png";
const MP_MARK_SRC =
  (typeof window !== "undefined" && window.__resources && window.__resources.morparaLogo) ||
  "assets/morpara-logo.png";
const AG_MARK_SRC =
  (typeof window !== "undefined" && window.__resources && window.__resources.algoraMark) ||
  "assets/algora-mark.svg";
const CV_SRC =
  (typeof window !== "undefined" && window.__resources && window.__resources.cvFile) ||
  "assets/Tuna_Aldemir_CV.pdf";

function Home({ navigate }) {
  return (
    <main>
      <HomeSectionNav navigate={navigate} />
      <MarksHero navigate={navigate} />
      <Closing navigate={navigate} />
    </main>
  );
}

/* ---------- bottom floating section nav (reveals after first scroll) ---------- */
/* small monochrome glyphs symbolising each project's category */
function ProjIcon({ kind }) {
  const p = { width: 20, height: 20, viewBox: "0 0 24 24", className: "shrink-0" };
  if (kind === "ai") // AI visibility → sparkle
    return (<svg {...p} fill="currentColor"><path d="M12 2.5l1.7 5.3 5.3 1.7-5.3 1.7L12 16.5l-1.7-5.3L5 9.5l5.3-1.7z"/><path d="M18.5 14.5l.85 2.45 2.45.85-2.45.85-.85 2.45-.85-2.45-2.45-.85 2.45-.85z"/></svg>);
  if (kind === "btc") // crypto → bitcoin
    return (<svg {...p} fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M9 6v12"/><path d="M9 6h4.2a2.9 2.9 0 0 1 0 5.8H9"/><path d="M9 11.8h5a2.9 2.9 0 0 1 0 5.8H9"/><path d="M11 4v2M11 18v2M14 4v2M14 18v2"/></svg>);
  // wallet → digital wallet (filled)
  return (<svg {...p} fill="currentColor"><path d="M3 6.5A2.5 2.5 0 0 1 5.5 4H16a2 2 0 0 1 2 2v1H6.5a.5.5 0 0 0 0 1H20a1 1 0 0 1 1 1v8a2 2 0 0 1-2 2H5.5A2.5 2.5 0 0 1 3 16.5z"/><circle cx="16.5" cy="13.5" r="1.6" fill="#161616"/></svg>);
}

function HomeSectionNav({ navigate }) {
  // Quick-jump dock (Stablex → Morpara → Algora). Revealed the instant the
  // hero's two side thumbnails are fully erased; highlights the live section.
  const stops = [
    { id: "stablex", label: "Stablex", color: "#FF5A63" },
    { id: "morpara", label: "Morpara", color: "#8031A7" },
    { id: "algora",  label: "Algora",  color: "#15D997" },
  ];
  const [shown, setShown] = useStateHome(false);
  const [act, setAct] = useStateHome("stablex");
  useEffectHome(() => {
    const onScroll = () => {
      const y = window.scrollY;
      const closing = document.getElementById("contact");
      let inClosing = false;
      if (closing) { inClosing = closing.getBoundingClientRect().top < window.innerHeight * 0.6; }
      setShown(y > 640 && !inClosing);            // hide once the "What I bring" closing section is entered
      setAct(y >= 4220 ? "algora" : y >= 2540 ? "morpara" : "stablex");   // live section
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  const scrollTargets = { stablex: 1080, morpara: 3640, algora: 6140 };
  const go = (e, id) => {
    e.preventDefault();
    window.scrollTo({ top: scrollTargets[id] || 0, behavior: "smooth" });
  };
  return (
    <nav className="hidden md:block fixed left-1/2 z-40"
         style={{
           bottom: 24,
           transform: `translateX(-50%) translateY(${shown ? 0 : 28}px)`,
           opacity: shown ? 1 : 0,
           pointerEvents: shown ? "auto" : "none",
           transition: "opacity .5s cubic-bezier(0.16,1,0.3,1), transform .5s cubic-bezier(0.16,1,0.3,1)",
         }}>
      <div className="flex items-center gap-1 rounded-full px-2 py-2"
           style={{ background: "rgba(20,20,20,0.82)", border: "1px solid rgba(255,255,255,0.1)", backdropFilter: "blur(14px)", WebkitBackdropFilter: "blur(14px)", boxShadow: "0 16px 50px -18px rgba(0,0,0,0.9)" }}>
        {stops.map(({ id, label, color }) => {
          const on = act === id;
          return (
            <a key={id} href={`#${id}`} onClick={(e) => go(e, id)}
               className="inline-flex items-center gap-2 text-[12px] uppercase tracking-[0.14em] rounded-full pl-3.5 pr-3 py-1.5 transition-all duration-300 whitespace-nowrap"
               style={{ color: on ? "#fff" : "rgba(255,255,255,0.5)", background: on ? "rgba(255,255,255,0.10)" : "transparent" }}>
              {label}
              <span className="shrink-0" style={{ width: 13, height: 13, borderRadius: 4, background: color, opacity: on ? 1 : 0.85 }} />
            </a>
          );
        })}
      </div>
    </nav>
  );
}

/* ---------- MARKS HERO ----------
   Resting state: hero copy + three equal brand-mark tiles (Stablex · Morpara ·
   Algora) with a staggered fade-in. On scroll the marks resolve: Stablex
   scales up with a red glow while Morpara + Algora shrink and fade to ~25%.
   Stablex's tile then morphs into the Stablex phone mockup that takes over the
   viewport, and identity-level reveals + a CTA fade in on the right. */
/* Hero cursor glow — a soft white glow that tracks the cursor in real time. */
function HeroCursorGlow({ opacity = 1 }) {
  const glowRef = useRefHome(null);
  const tailRef = useRefHome(null);
  useEffectHome(() => {
    const el = glowRef.current;
    if (!el) return;
    const tails = tailRef.current ? Array.from(tailRef.current.children) : [];
    let tx = window.innerWidth / 2, ty = window.innerHeight / 2;
    let cx = tx, cy = ty, raf = 0;
    // each tail node trails the one before it → a soft comet tail
    const tn = tails.map(() => ({ x: cx, y: cy }));
    const move = (e) => { tx = e.clientX; ty = e.clientY; };
    const loop = () => {
      raf = requestAnimationFrame(loop);
      cx += (tx - cx) * 0.4;   // near-instant follow with a touch of smoothing
      cy += (ty - cy) * 0.4;
      el.style.transform = `translate(${cx}px, ${cy}px) translate(-50%, -50%)`;
      let px = cx, py = cy;
      for (let i = 0; i < tn.length; i++) {
        tn[i].x += (px - tn[i].x) * 0.28;
        tn[i].y += (py - tn[i].y) * 0.28;
        tails[i].style.transform = `translate(${tn[i].x}px, ${tn[i].y}px) translate(-50%, -50%)`;
        px = tn[i].x; py = tn[i].y;
      }
    };
    window.addEventListener("mousemove", move, { passive: true });
    loop();
    return () => { cancelAnimationFrame(raf); window.removeEventListener("mousemove", move); };
  }, []);

  const TAILS = 9;
  return (
    <div className="absolute inset-0 z-[1] overflow-hidden pointer-events-none" style={{ opacity }}>
      {/* comet tail — soft trailing dots, drawn under the head */}
      <div ref={tailRef}>
        {Array.from({ length: TAILS }).map((_, i) => {
          const k = 1 - i / TAILS;                 // 1 → near head, fading back
          const size = Math.round(165 * k + 36);
          return (
            <div key={i} className="absolute top-0 left-0" style={{
              width: size, height: size, borderRadius: "50%",
              background: `radial-gradient(closest-side, rgba(255,255,255,${(0.094 * k).toFixed(3)}), transparent 70%)`,
              filter: "blur(12px)", willChange: "transform",
            }} />
          );
        })}
      </div>
      {/* head — tighter, more pointed, +20% opacity */}
      <div ref={glowRef} className="absolute top-0 left-0"
           style={{
             width: 200, height: 200, borderRadius: "50%",
             background: "radial-gradient(closest-side, rgba(255,255,255,0.17), rgba(255,255,255,0.05) 30%, transparent 60%)",
             filter: "blur(10px)", willChange: "transform",
           }} />
    </div>
  );
}

function MarksHero({ navigate }) {
  const wrapRef = useRefHome(null);
  const [sy, setSy] = useStateHome(0);
  const [vw, setVw] = useStateHome((typeof window !== "undefined" && window.innerWidth) || 1280);
  const [vh, setVh] = useStateHome((typeof window !== "undefined" && window.innerHeight) || 800);
  const [sxAspect, setSxAspect] = useStateHome(750 / 3436);   // Stablex home-scroll image ratio
  const [mpAspect, setMpAspect] = useStateHome(780 / 2804);   // Morpara home-scroll image ratio
  const [agAspect, setAgAspect] = useStateHome(3624 / 5708);  // Algora dashboard image ratio (tall)
  const [hoverTile, setHoverTile] = useStateHome(null);       // hero tile hover-glow

  useEffectHome(() => {
    let raf = 0;
    const measure = () => {
      const el = wrapRef.current;
      if (!el) return;
      const rect = el.getBoundingClientRect();
      setSy(Math.max(0, -rect.top));
    };
    const onScroll = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(measure); };
    const onResize = () => { setVw(window.innerWidth || 1280); setVh(window.innerHeight || 800); measure(); };
    onResize();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onResize);
    window.addEventListener("load", onResize);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onResize);
      window.removeEventListener("load", onResize);
    };
  }, []);

  const clamp = (v, lo = 0, hi = 1) => Math.min(hi, Math.max(lo, v));
  const lerp = (a, b, t) => a + (b - a) * t;
  const easeOut = (t) => 1 - (1 - t) * (1 - t);
  const ease2 = (t) => 1 - (1 - t) * (1 - t);
  const isLg = vw >= 1024;

  /* scroll choreography (px) */
  const SEL_END = 460;          // selection completes
  const MORPH_LEN = 560;        // morph duration (tile → phone)
  const selP = clamp(sy / SEL_END);
  const mP = clamp((sy - SEL_END) / MORPH_LEN);
  const mEase = easeOut(mP);
  const rP = clamp((sy - 880) / 200);    // Stablex identity reveals (after the white bg is in)
  const whiteIn = clamp((sy - 640) / 200);   // black hero → white Stablex stop (once the side tiles vanish)

  /* Stablex inner home-scroll, badges, then triggered morph → Morpara */
  const SX_SCROLL_START = 1320, SX_SCROLL_LEN = 800;
  const sxScrollFrac = clamp((sy - SX_SCROLL_START) / SX_SCROLL_LEN);
  // Stablex floating badges reveal once the inner scroll finishes
  const sxl1 = ease2(clamp((sy - 2160) / 120));
  const sxl2 = ease2(clamp((sy - 2230) / 120));
  const sxl3 = ease2(clamp((sy - 2300) / 120));
  const MP_START = 3040, MP_LEN = 360;
  const mpP = clamp((sy - MP_START) / MP_LEN);   // scroll-linked Stablex → Morpara morph
  const sxLabelsOut = clamp(mpP / 0.4);   // Stablex badges leave with the text, before the mockup moves
  const mpE = easeOut(mpP);
  const mpTextIn = clamp((sy - 3440) / 200);     // Morpara text rises after the (slower) morph completes
  /* Morpara inner home-scroll (mirror of Stablex), then floating labels */
  const MP_SCROLL_START = 3520, MP_SCROLL_LEN = 780;
  const mpScrollFrac = clamp((sy - MP_SCROLL_START) / MP_SCROLL_LEN);
  // Morpara floating labels (scroll-linked, after the inner scroll finishes)
  const mpl1 = ease2(clamp((sy - 4360) / 150));
  const mpl2 = ease2(clamp((sy - 4460) / 150));
  const mpl3 = ease2(clamp((sy - 4560) / 150));
  /* Algora: phone (Morpara) → landscape tablet morph, then dashboard scroll + labels */
  const AG_START = 5420, AG_LEN = 480;
  const agP = clamp((sy - AG_START) / AG_LEN);
  const agE = easeOut(agP);
  const agTextIn = clamp((sy - 5940) / 200);     // Algora text rises after the (slower) morph completes
  const AG_SCROLL_START = 6000, AG_SCROLL_LEN = 940;
  const agScrollFrac = clamp((sy - AG_SCROLL_START) / AG_SCROLL_LEN);
  // 3D tilt — Stablex faces right (+13°); turns through flat as it slides and faces
  // its text (mirrored, −13°) for Morpara; then turns through flat again to face
  // right (+13°) for the Algora tablet.
  const tiltAngle = lerp(lerp(20, -20, mpP), 20, agP);
  const tiltCss = `perspective(1500px) rotateY(${tiltAngle.toFixed(2)}deg)`;
  const agl1 = ease2(clamp((sy - 7000) / 150));
  const agl2 = ease2(clamp((sy - 7100) / 150));
  const agl3 = ease2(clamp((sy - 7200) / 150));

  /* device + tile sizes (mockup shrunk 10%) */
  let phoneH = Math.round(Math.min(vh * 0.8, 660) * 0.9 * (isLg ? 1 : 0.855));
  let phoneW = Math.round(phoneH * (375 / 812));
  /* Algora landscape tablet target dims */
  const tabletW = isLg ? Math.min(Math.round(vw * 0.44 * 0.8), 560) : Math.round(vw * 0.82 * 0.92);
  const tabletH = Math.round(tabletW / 1.42);
  const TILE = 78;
  const spacing = isLg ? 150 : 104;

  /* hero copy fades up as selection begins */
  const heroOpacity = clamp(1 - sy / 320);
  const heroRise = -easeOut(clamp(sy / 460)) * 60;

  /* side marks (Morpara · Algora) shrink + fade as Stablex is selected */
  const sideScale = lerp(1, 0.84, easeOut(selP));
  const sideFade = lerp(1, 0.25, easeOut(selP)) * (1 - clamp(mP / 0.32));

  /* ONE continuous Stablex element: tile (rest) → grows on selection →
     morphs into the phone mockup. Sized via width/height (never transform
     scale) so the box is the SAME element throughout — no second mockup. */
  const targetCx = isLg ? -(40 + Math.round(phoneW / 2)) : 0;
  const mpCx     = isLg ?  (40 + Math.round(phoneW / 2)) : 0;   // Morpara: mirror of Stablex on the right
  const agCx     = isLg ? -(40 + Math.round(tabletW / 2)) : 0;  // Algora: tablet parked left (text on right)
  const sxSel = easeOut(selP);
  const restCx = lerp(-spacing, targetCx, mEase);
  const slideMp = easeOut(clamp((mpP - 0.4) / 0.6));                  // device slides only AFTER the text/badges have left
  const phoneCx = lerp(restCx, mpCx, slideMp);                        // scroll-linked slide on morph
  const sxW = mP > 0 ? lerp(TILE * 1.42, phoneW, mEase) : lerp(TILE, TILE * 1.42, sxSel);
  const sxH = mP > 0 ? lerp(TILE * 1.42, phoneH, mEase) : lerp(TILE, TILE * 1.42, sxSel);
  const sxRadius = lerp(20, 25, mEase);
  const sxPad = lerp(0, 5, mEase);
  /* Algora device morph: phone → landscape tablet (one continuous element) */
  const slideAg = easeOut(clamp((agP - 0.4) / 0.6));                 // tablet slides only AFTER Morpara text/badges leave
  const devW = Math.round(agP > 0 ? lerp(phoneW, tabletW, slideAg) : sxW);
  const devH = Math.round(agP > 0 ? lerp(phoneH, tabletH, slideAg) : sxH);
  const deviceCx = Math.round(agP > 0 ? lerp(mpCx, agCx, slideAg) : phoneCx);
  /* mobile: center the [device + 60px gap + text] block vertically; horizontal already centered */
  const mGroupTop = Math.max(72, (vh - (devH + 60 + 250)) / 2);
  const mDevCenterY = mGroupTop + devH / 2;
  const mTextTopPx = Math.round(mGroupTop + devH + 60);
  const mTextTopPhone = mTextTopPx - 20;
  const deviceBg = agP > 0.5 ? "#06140e" : (mpP > 0.5 ? "#0a0a0a" : "#050505");
  const deviceRing = agP > 0.5 ? "rgba(95,230,172,0.30)" : (mpP > 0.5 ? "rgba(255,255,255,0.18)" : (hoverTile === "stablex" ? "rgba(255,59,71,0.7)" : "rgba(255,59,71,0)"));
  const tileImgOpacity = 1 - clamp((mP - 0.1) / 0.5);   // brand fill dissolves
  const screenOpacity = clamp((mP - 0.3) / 0.5);        // phone screen resolves
  /* ── Mockup transition entrances ── the NEW screen slides up from the bottom
     INSIDE the morphing mockup, covering the previous screen (no cross-fade,
     no colour curtain). Snappy ease for an abrupt "ani giriş". */
  const easeOutCubic = (e) => 1 - Math.pow(1 - e, 3);
  const sxEnter = easeOutCubic(clamp((mP - 0.32) / 0.51));    // Stablex screen slides up over the brand mark
  const mpEnter = easeOutCubic(clamp((mpP - 0.30) / 0.51));   // Morpara screen slides up over Stablex
  const agEnter = easeOutCubic(clamp((agP - 0.30) / 0.51));   // Algora dashboard slides up over Morpara
  const screenRadius = Math.max(8, sxRadius - sxPad);          // matches the mockup's inner screen radius
  const sxLabelOp = (1 - clamp(mP / 0.25)) * (1 - mpP);
  const glowSize = lerp(TILE, TILE * 4.2, sxSel);

  /* Stablex inner home-scroll geometry */
  const sxInnerW = phoneW - 20;
  const sxInnerH = phoneH - 20;
  const sxDisplayH = sxInnerW / sxAspect;
  const sxRange = Math.max(0, sxDisplayH - sxInnerH);
  const sxScrollPx = -sxScrollFrac * sxRange;
  /* Morpara inner home-scroll geometry (same screen box as Stablex) */
  const mpInnerW = phoneW - 2 * sxPad;
  const mpInnerH = phoneH - 2 * sxPad;
  const mpDisplayH = mpInnerW / mpAspect;
  const mpRange = Math.max(0, mpDisplayH - mpInnerH);
  const mpScrollPx = -mpScrollFrac * mpRange;
  /* Algora inner dashboard scroll geometry (landscape tablet) */
  const agInnerW = tabletW - 2 * sxPad;
  const agInnerH = tabletH - 2 * sxPad;
  const agDisplayH = agInnerW / agAspect;
  const agRange = Math.max(0, agDisplayH - agInnerH);
  const agScrollPx = -agScrollFrac * agRange;
  const agCenterX = vw / 2 + agCx;                // Algora label anchor (tablet parked left)
  const phoneCenterX = vw / 2 + phoneCx;          // Morpara label anchor (phone parked right)
  const phoneCenterSx = vw / 2 + targetCx;        // Stablex label anchor (phone parked left)

  /* thumbnails sit 160px lower at rest; ease back to centre as the phone forms */
  const vOffset = 160 * (1 - mEase);

  const sideMarks = [
    { id: "morpara", img: MP_MARK_SRC, label: "Morpara", full: "Digital Wallet App", cx: 0,       rgb: "226,200,247" },
    { id: "algora",  img: AG_MARK_SRC, label: "Algora",  full: "AI Visibility SaaS",  cx: spacing, rgb: "255,255,255", ringW: 1.5 },
  ];

  return (
    <section ref={wrapRef} className="relative" style={{ height: "calc(100vh + 8200px)" }}>
      <style>{`
        @keyframes markIn { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }
      `}</style>
      <div className="sticky top-0 h-screen overflow-hidden bg-ink">
        {/* soft white glow that tracks the cursor */}
        <HeroCursorGlow opacity={heroOpacity} />
        {/* Stablex white backdrop (black hero → white; fades back to black for Morpara) */}
        <div className="absolute inset-0 z-[15] pointer-events-none" style={{ background: "#FFFFFF", opacity: whiteIn * (1 - mpP) }} />
        {/* Algora white backdrop (fades in over the black as the tablet forms) */}
        <div className="absolute inset-0 z-[15] pointer-events-none" style={{ background: "#FFFFFF", opacity: agP }} />

        {/* ── HERO COPY ── */}
        <div className="absolute inset-x-0 top-0 z-20 flex flex-col items-center text-center px-6"
             style={{ paddingTop: "calc(clamp(96px, 14vh, 190px) + 40px)", opacity: heroOpacity, transform: `translateY(${heroRise}px)`, pointerEvents: heroOpacity > 0.5 ? "auto" : "none" }}>
          <h1 className="hero-h1-intro font-display font-normal text-[40px] sm:text-[52px] md:text-[64px] leading-[1.04] tracking-[-0.025em] text-balance max-w-[20ch]"
              style={{ textShadow: "0 6px 80px rgba(0,0,0,.6)" }}>
            Turning <span className="text-accent">insight</span> into design, and design into <span className="text-accent">results</span><span className="text-white">.</span>
          </h1>
          <p className="hero-sub-intro mt-6 text-[16px] md:text-[18px] text-white/75 max-w-[52ch]">
            Sr. Product Designer · 8+ years · Specialized in SaaS, Banking &amp; Fintech
          </p>
        </div>

        {/* ── MARKS LAYER (centred in viewport, shifted down 120px at rest) ── */}
        <div className="absolute inset-0 z-30 pointer-events-none" style={{ transform: `translateY(${vOffset}px)` }}>
          {/* red selection glow behind Stablex */}
          <div className="absolute" aria-hidden="true"
               style={{
                 left: "50%", top: "50%",
                 width: glowSize, height: glowSize,
                 transform: `translate(-50%, -50%) translateX(${restCx}px)`,
                 background: "radial-gradient(closest-side, rgba(255,59,71,.5), transparent 72%)",
                 opacity: selP * (1 - clamp(mP / 0.4)) * 0.9, filter: "blur(8px)",
               }} />

          {/* hover glow removed per request */}

          {/* SIDE MARKS — Morpara · Algora (tile centred + label below, aligned with Stablex) */}
          {sideMarks.map((m, i) => (
            <div key={m.id} className="absolute" role="link" aria-label={m.label}
                 onClick={() => { navigate(`case/${m.id}`); window.scrollTo({ top: 0 }); }}
                 onMouseEnter={() => setHoverTile(m.id)} onMouseLeave={() => setHoverTile(null)}
                 style={{
                   left: "50%", top: "50%",
                   transform: `translate(-50%, -50%) translateX(${m.cx}px)`,
                   opacity: sideFade,
                   cursor: "pointer",
                   pointerEvents: sideFade > 0.35 ? "auto" : "none",
                 }}>
              <div style={{ animation: `markIn .7s cubic-bezier(.16,1,.3,1) ${1.6 + (i + 1) * 0.13}s both` }}>
                <div style={{ transform: `scale(${sideScale})`, transformOrigin: "center center", position: "relative" }}>
                  {/* hover title badge */}
                  <span style={{ position: "absolute", left: "50%", bottom: "100%", marginBottom: 14, transform: `translateX(-50%) translateY(${hoverTile === m.id ? 0 : 6}px)`, opacity: hoverTile === m.id ? 1 : 0, transition: "opacity .25s ease, transform .25s ease", background: "rgba(18,18,20,.92)", border: "1px solid rgba(255,255,255,.14)", color: "#fff", backdropFilter: "blur(8px)", WebkitBackdropFilter: "blur(8px)", borderRadius: 999, padding: "7px 14px", fontSize: 12, letterSpacing: ".02em", fontWeight: 500, whiteSpace: "nowrap", boxShadow: "0 12px 30px -12px rgba(0,0,0,.8)", pointerEvents: "none" }}>{m.full}</span>
                  <div className="overflow-hidden"
                       style={{ width: TILE, height: TILE, borderRadius: 20, boxShadow: `${hoverTile === m.id ? `0 0 0 ${m.ringW || 1}px rgba(${m.rgb},0.7), ` : ""}0 14px 40px -18px rgba(0,0,0,.7)` }}>
                    <img src={m.img} alt={m.label} draggable="false" className="w-full h-full object-cover select-none" />
                  </div>
                </div>
                <span className="absolute left-1/2 text-[12px] uppercase tracking-[0.18em] text-white/55 whitespace-nowrap"
                      style={{ top: "100%", marginTop: 12, transform: "translateX(-50%)" }}>{m.label}</span>
              </div>
            </div>
          ))}

          {/* STABLEX → phone → Morpara: single continuous element */}
          <div className="absolute" role="link" aria-label="Stablex"
               onClick={() => { if (mP < 0.15) { navigate("case/stablex"); window.scrollTo({ top: 0 }); } }}
               onMouseEnter={() => { if (mP < 0.15) setHoverTile("stablex"); }} onMouseLeave={() => setHoverTile(null)}
               style={{ left: "50%", top: isLg ? "50%" : `${Math.round(lerp(vh * 0.5, mDevCenterY, mEase))}px`, transform: `translate(-50%, -50%) translateX(${deviceCx}px) ${tiltCss}`, transformStyle: "preserve-3d", cursor: mP < 0.15 ? "pointer" : "default", pointerEvents: mP < 0.15 ? "auto" : "none" }}>
            <div style={{ animation: `markIn .7s cubic-bezier(.16,1,.3,1) 1.6s both` }}>
              <div className="relative"
                   style={{
                     width: devW, height: devH, borderRadius: sxRadius, padding: sxPad,
                     background: deviceBg,
                     boxShadow: `0 0 0 1px ${deviceRing}, ${agP > 0.5 ? "0 50px 120px -40px rgba(0,0,0,.85)" : mpP > 0.5 ? "0 50px 120px -40px rgba(0,0,0,.8)" : (mP > 0 ? "0 50px 130px -40px rgba(0,0,0,.9)" : `0 18px 50px -14px rgba(255,59,71,${0.2 + selP * 0.5})`)}`,
                   }}>
                <div className="relative w-full h-full overflow-hidden bg-[#0c0c0f]" style={{ borderRadius: Math.max(8, sxRadius - sxPad) }}>
                  {/* brand-mark fill (Stablex thumbnail) — covered as the Stablex screen slides up */}
                  <img src={SX_MARK_SRC} alt="Stablex" draggable="false" className="absolute inset-0 w-full h-full object-cover select-none" style={{ opacity: 1 }} />
                  {/* Stablex screen group — slides up from the bottom to enter */}
                  <div className="absolute inset-0 overflow-hidden will-change-transform" style={{ bottom: "-2px", transform: `translateY(${(1 - sxEnter) * 100}%)`, borderTopLeftRadius: screenRadius, borderTopRightRadius: screenRadius }}>
                    <div className="absolute inset-0 overflow-hidden">
                      <img src={SX_HOME_SCROLL_SRC} alt="Stablex — home" draggable="false"
                           onLoad={(e) => { const im = e.currentTarget; if (im.naturalWidth && im.naturalHeight) setSxAspect(im.naturalWidth / im.naturalHeight); }}
                           className="absolute top-0 left-0 w-full h-auto select-none will-change-transform" style={{ transform: `translateY(${sxScrollPx}px)` }} />
                    </div>
                    <img src={SX_BOTTOMNAV_SRC} alt="" draggable="false" className="absolute bottom-0 left-0 w-full h-auto select-none" style={{ filter: "drop-shadow(0 -8px 18px rgba(0,0,0,.10))" }} />
                  </div>
                  {/* Morpara screen group — slides up from the bottom, covering Stablex */}
                  <div className="absolute inset-0 overflow-hidden will-change-transform" style={{ bottom: "-2px", transform: `translateY(${(1 - mpEnter) * 100}%)`, borderTopLeftRadius: screenRadius, borderTopRightRadius: screenRadius }}>
                    <div className="absolute inset-0 overflow-hidden">
                      <img src={MP_HOME_SCROLL_SRC} alt="Morpara — home" draggable="false"
                           onLoad={(e) => { const im = e.currentTarget; if (im.naturalWidth && im.naturalHeight) setMpAspect(im.naturalWidth / im.naturalHeight); }}
                           className="absolute top-0 left-0 w-full h-auto select-none will-change-transform" style={{ transform: `translateY(${mpScrollPx}px)` }} />
                    </div>
                    <img src={MP_BOTTOMNAV_SRC} alt="" draggable="false" className="absolute bottom-0 left-0 w-full h-auto select-none" />
                  </div>
                  {/* Algora homepage group — slides up from the bottom, covering Morpara; nav stays sticky */}
                  <div className="absolute inset-0 overflow-hidden will-change-transform" style={{ bottom: "-2px", transform: `translateY(${(1 - agEnter) * 100}%)`, background: agP > 0 ? "#E6FBF4" : "transparent", borderTopLeftRadius: screenRadius, borderTopRightRadius: screenRadius }}>
                    <img src={AG_HOME_FULL_SRC} alt="Algora — homepage" draggable="false"
                         onLoad={(e) => { const im = e.currentTarget; if (im.naturalWidth && im.naturalHeight) setAgAspect(im.naturalWidth / im.naturalHeight); }}
                         className="absolute top-0 left-0 w-full h-auto select-none will-change-transform" style={{ transform: `translateY(${agScrollPx}px)` }} />
                    {/* sticky navbar — pinned strip identical to the page's own nav */}
                    <img src={AG_HOME_FULL_NAV_SRC} alt="" draggable="false"
                         className="absolute top-0 left-0 w-full h-auto select-none" style={{ borderTopLeftRadius: screenRadius, borderTopRightRadius: screenRadius }} />
                  </div>
                  <div className="absolute inset-0 pointer-events-none" style={{ background: "linear-gradient(125deg, rgba(255,255,255,.10), rgba(255,255,255,0) 38%)" }} />
                </div>
              </div>
              <span className="absolute left-1/2 text-[12px] uppercase tracking-[0.18em] text-white/55 whitespace-nowrap"
                    style={{ top: "100%", marginTop: 12, transform: "translateX(-50%)", opacity: sxLabelOp }}>Stablex</span>
            </div>
          </div>
          {/* STABLEX hover title badge (flat, not tilted) */}
          {isLg && (
            <div className="absolute pointer-events-none" style={{ left: "50%", top: "50%", width: devW, height: devH, transform: `translate(-50%, -50%) translateX(${deviceCx}px)` }}>
              <span style={{ position: "absolute", left: "50%", bottom: "100%", marginBottom: 16, transform: `translateX(-50%) translateY(${hoverTile === "stablex" && mP < 0.15 ? 0 : 6}px)`, opacity: hoverTile === "stablex" && mP < 0.15 ? 1 : 0, transition: "opacity .25s ease, transform .25s ease", background: "rgba(18,18,20,.92)", border: "1px solid rgba(255,255,255,.14)", color: "#fff", backdropFilter: "blur(8px)", WebkitBackdropFilter: "blur(8px)", borderRadius: 999, padding: "7px 14px", fontSize: 12, letterSpacing: ".02em", fontWeight: 500, whiteSpace: "nowrap", boxShadow: "0 12px 30px -12px rgba(0,0,0,.8)", pointerEvents: "none" }}>Crypto Trading App</span>
            </div>
          )}
        </div>

        {/* ── STABLEX case info — desktop: right of mockup · mobile: below mockup ── */}
        <div className="absolute inset-0 z-[34] pointer-events-none">
            <div style={isLg
              ? { position: "absolute", left: "calc(50% + 40px)", top: "50%", width: "min(440px, 40vw)", transform: `translateY(-50%) translateY(${(1 - rP) * 24 - clamp(mpP / 0.4) * 80}px)`, opacity: rP * (1 - clamp(mpP / 0.4)), pointerEvents: (mpP < 0.2 && rP > 0.6) ? "auto" : "none" }
              : { position: "absolute", left: 0, right: 0, top: mTextTopPhone, padding: "0 24px", transform: `translateY(${(1 - rP) * 18}px)`, opacity: rP * (1 - clamp(mpP / 0.4)), pointerEvents: (mpP < 0.2 && rP > 0.6) ? "auto" : "none" }}>
              <HeroProjectInfo project={window.PROJECTS && window.PROJECTS[0]} navigate={navigate} align="left" eyebrow="Stablex Crypto Trading App" title={<>The crypto exchange with <span style={{ color: "#E8001C" }}>demo account</span></>} accent="red" caseId="stablex" />
            </div>
          </div>

        {/* ── STABLEX — floating crypto badges (reveal once the inner scroll finishes) ── */}
        <div className="absolute inset-x-0 top-0 z-[32] h-screen pointer-events-none">
            {/* top-right of phone: trade filled */}
            <S2Float reveal={sxl1 * (1 - sxLabelsOut)} on={false} from={{ x: 30, y: -12 }} tilt={tiltCss}
                     style={isLg
                       ? { left: phoneCenterSx + phoneW / 2 - 46, top: `calc(50% - ${phoneH / 2 - 26}px)` }
                       : { left: 14, top: `calc(30% - ${phoneH / 2 - 10}px)` }}>
              <div className="flex items-center gap-3 rounded-2xl border border-white/10 bg-[#15151b]/95 backdrop-blur px-4 py-3 shadow-[0_24px_60px_-20px_rgba(0,0,0,.8)]">
                <span className="grid place-items-center size-9 rounded-full bg-accent/15 text-accent">
                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M20 6 9 17l-5-5"/></svg>
                </span>
                <div>
                  <div className="text-[12px] uppercase tracking-[0.16em] text-white/60 leading-none">Active users</div>
                  <div className="text-[15px] font-medium text-white mt-1">26% → 30%</div>
                </div>
              </div>
            </S2Float>
            {/* bottom-left: onboarding abandonment */}
            <S2Float reveal={sxl2 * (1 - sxLabelsOut)} on={false} from={{ x: -30, y: 16 }} tilt={tiltCss}
                     style={isLg
                       ? { left: phoneCenterSx - phoneW / 2 - 164, top: `calc(50% + ${phoneH / 2 - 116}px)` }
                       : { left: vw - 204, top: `calc(30% + ${phoneH / 2 - 64}px)` }}>
              <div className="rounded-2xl border border-white/10 bg-[#15151b]/95 backdrop-blur px-4 py-3.5 shadow-[0_24px_60px_-20px_rgba(0,0,0,.8)] w-[190px]">
                <div className="text-[12px] uppercase tracking-[0.16em] text-white/50">Onboarding abandonment</div>
                <div className="flex items-center gap-2 mt-1.5">
                  <span className="text-white line-through text-[15px]" style={{ textDecorationColor: "rgba(255,255,255,.4)" }}>36%</span>
                  <span style={{ color: "#15D997" }}>↓</span>
                  <span className="font-display text-2xl text-white">11%</span>
                </div>
              </div>
            </S2Float>
          </div>

        {/* ── MORPARA case info — desktop: left of mockup · mobile: below ── */}
        <div className="absolute inset-0 z-[34] pointer-events-none">
            <div style={isLg
              ? { position: "absolute", right: "calc(50% + 40px)", top: "50%", width: "min(440px, 40vw)", transform: `translateY(-50%) translateY(${(1 - mpTextIn) * 28 - clamp(agP / 0.4) * 80}px)`, opacity: mpTextIn * (1 - clamp(agP / 0.4)), pointerEvents: (mpTextIn > 0.6 && agP < 0.2) ? "auto" : "none" }
              : { position: "absolute", left: 0, right: 0, top: mTextTopPhone, padding: "0 24px", transform: `translateY(${(1 - mpTextIn) * 18}px)`, opacity: mpTextIn * (1 - clamp(agP / 0.4)), pointerEvents: (mpTextIn > 0.6 && agP < 0.2) ? "auto" : "none" }}>
              <HeroProjectInfo project={window.PROJECTS && window.PROJECTS[0]} navigate={navigate} dark align={isLg ? "right" : "left"} eyebrow="Morpara Digital Wallet App" title={<>Why didn't <span style={{ color: "#C9A6F2" }}>a perfect upgrade flow</span> work?</>} accent="purple" caseId="morpara" />
            </div>
          </div>

        {/* ── MORPARA — floating wallet labels (scroll-linked) ── */}
        <div className="absolute inset-x-0 top-0 z-[32] h-screen pointer-events-none">
            {/* top-left: account-upgrade rate */}
            <S2Float reveal={mpl1 * (1 - clamp(agP / 0.4))} on={false} from={{ x: -30, y: -12 }} tilt={tiltCss}
                     style={isLg
                       ? { left: phoneCenterX - phoneW / 2 - 40, top: `calc(50% - ${phoneH / 2 - 26}px)` }
                       : { left: 14, top: `calc(30% - ${phoneH / 2 - 10}px)` }}>
              <div className="rounded-2xl border border-black/5 bg-white px-4 py-3.5 shadow-[0_24px_60px_-24px_rgba(0,0,0,.3)] w-[196px]">
                <div className="text-[12px] uppercase tracking-[0.16em] text-black/40">Account-upgrade rate</div>
                <div className="flex items-center gap-2 mt-1.5">
                  <span className="text-black line-through text-[15px]" style={{ textDecorationColor: "rgba(0,0,0,.35)" }}>3.2%</span>
                  <span style={{ color: "#0E8C63" }}>→</span>
                  <span className="font-display text-2xl text-[#0e0e12]">14.1%</span>
                </div>
              </div>
            </S2Float>
            {/* bottom-right: campaign redemption rate */}
            <S2Float reveal={mpl2 * (1 - clamp(agP / 0.4))} on={false} from={{ x: 30, y: 16 }} tilt={tiltCss}
                     style={isLg
                       ? { left: phoneCenterX + phoneW / 2 - 140, top: `calc(50% + ${phoneH / 2 - 116}px)` }
                       : { left: vw - 210, top: `calc(30% + ${phoneH / 2 - 64}px)` }}>
              <div className="rounded-2xl border border-black/5 bg-white px-4 py-3.5 shadow-[0_24px_60px_-24px_rgba(0,0,0,.3)] w-[196px]">
                <div className="text-[12px] uppercase tracking-[0.16em] text-black/40">Campaign redemption rate</div>
                <div className="flex items-center gap-2 mt-1.5">
                  <span className="text-black line-through text-[15px]" style={{ textDecorationColor: "rgba(0,0,0,.35)" }}>1.1%</span>
                  <span style={{ color: "#0E8C63" }}>→</span>
                  <span className="font-display text-2xl text-[#0e0e12]">3.8%</span>
                </div>
              </div>
            </S2Float>
          </div>

        {/* ── ALGORA case info — desktop: right of tablet · mobile: below ── */}
        <div className="absolute inset-0 z-[34] pointer-events-none">
            <div style={isLg
              ? { position: "absolute", left: "calc(50% + 40px)", top: "50%", width: "min(440px, 40vw)", transform: `translateY(-50%) translateY(${(1 - agTextIn) * 28}px)`, opacity: agTextIn, pointerEvents: agTextIn > 0.6 ? "auto" : "none" }
              : { position: "absolute", left: 0, right: 0, top: mTextTopPx, padding: "0 24px", transform: `translateY(${(1 - agTextIn) * 18}px)`, opacity: agTextIn, pointerEvents: agTextIn > 0.6 ? "auto" : "none" }}>
              <HeroProjectInfo project={window.PROJECTS && window.PROJECTS[0]} navigate={navigate} align="left" eyebrow="Algora AI Visibility SaaS" title={<>From idea to live product: <span style={{ color: "#15D997" }}>a solo production.</span></>} accent="green" caseId="algora" />
            </div>
          </div>

        {/* ── ALGORA — floating dashboard labels (scroll-linked, mint on dark) ── */}
        <div className="absolute inset-x-0 top-0 z-[32] h-screen pointer-events-none">
            {/* top-left: what was shipped */}
            <S2Float reveal={agl1} on={false} from={{ x: -28, y: -14 }} tilt={tiltCss}
                     style={isLg
                       ? { left: agCenterX - tabletW / 2 - 40, top: `calc(50% - ${tabletH / 2 - 6}px)` }
                       : { left: 14, top: `calc(30% - ${tabletH / 2 - 6}px)` }}>
              <div className="rounded-2xl border border-white/10 bg-[#0B2A20]/95 backdrop-blur px-4 py-3.5 shadow-[0_24px_60px_-20px_rgba(0,0,0,.8)] w-[184px]">
                <div className="text-[11px] uppercase tracking-[0.16em]" style={{ color: "rgba(255,255,255,.5)" }}>Shipped</div>
                <div className="mt-2.5 flex flex-col gap-1.5">
                  {["SaaS product", "Marketing website"].map((t) => (
                    <div key={t} className="flex items-center gap-2">
                      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#5FE6AC" strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
                      <span className="text-[14px] font-medium text-white">{t}</span>
                    </div>
                  ))}
                </div>
              </div>
            </S2Float>
            {/* bottom-right: what I did */}
            <S2Float reveal={agl2} on={false} from={{ x: 28, y: 16 }} tilt={tiltCss}
                     style={isLg
                       ? { left: agCenterX + tabletW / 2 - 150, top: `calc(50% + ${tabletH / 2 - 24}px)` }
                       : { left: vw - 224, top: `calc(30% + ${tabletH / 2 - 6}px)` }}>
              <div className="rounded-2xl border border-white/10 bg-[#0B2A20]/95 backdrop-blur px-3.5 py-3 shadow-[0_24px_60px_-20px_rgba(0,0,0,.8)] w-[210px]">
                <div className="flex flex-wrap gap-1.5">
                  {["Owned", "Designed", "Coded", "Integrated", "Aired"].map((t) => (
                    <span key={t} className="inline-flex items-center rounded-full px-2.5 py-1 text-[11px] font-medium" style={{ background: "rgba(95,230,172,.14)", color: "#5FE6AC" }}>{t}</span>
                  ))}
                </div>
              </div>
            </S2Float>
          </div>

        {/* scroll hint */}
        <div className="absolute inset-x-0 bottom-4 z-20 flex flex-col items-center" style={{ opacity: clamp(1 - sy / 160) }}>
          <button type="button" aria-label="Scroll down"
                  onClick={() => {
                    const start = window.scrollY;
                    const target = 1200;   // Stablex section — phone fully formed with its screen
                    const dur = 1100, t0 = performance.now();
                    const html = document.documentElement;
                    const prevSB = html.style.scrollBehavior;
                    html.style.scrollBehavior = "auto";
                    const ease = (t) => 1 - Math.pow(1 - t, 3);
                    const step = (now) => {
                      const p = Math.min(1, (now - t0) / dur);
                      window.scrollTo(0, Math.round(start + (target - start) * ease(p)));
                      if (p < 1) requestAnimationFrame(step);
                      else html.style.scrollBehavior = prevSB;
                    };
                    requestAnimationFrame(step);
                  }}
                  className="cursor-pointer" style={{ pointerEvents: sy < 120 ? "auto" : "none", background: "transparent", border: "none", padding: 6 }}>
            <svg width="18" height="28" viewBox="0 0 18 28" fill="none" aria-hidden="true">
            <rect x="1" y="1" width="16" height="26" rx="8" stroke="rgba(255,255,255,.4)" strokeWidth="1.5" />
            <circle cx="9" cy="8" r="2" fill="rgba(255,255,255,.6)">
              <animate attributeName="cy" values="8;15;8" dur="1.6s" repeatCount="indefinite" calcMode="easeInOut" />
              <animate attributeName="opacity" values="1;0.2;1" dur="1.6s" repeatCount="indefinite" />
            </circle>
          </svg>
          </button>
        </div>
      </div>
    </section>
  );
}

/* ---------- TABLET HERO ----------
   Sticky section, Polarsteps-style. Hero text sits at the top; a tablet
   mockup is centered just below it, cut off at the fold. On scroll the
   tablet rises and slides slightly left, hero text fades, floating UI
   layers stagger in over the tablet, and the project-01 copy fades in
   on the right. */

function TabletHero({ navigate }) {
  const wrapRef = useRefHome(null);
  const [p, setP] = useStateHome(0);
  const [sy, setSy] = useStateHome(0);
  const [vw, setVw] = useStateHome((typeof window !== "undefined" && window.innerWidth) || 1280);
  const [vh, setVh] = useStateHome((typeof window !== "undefined" && window.innerHeight) || 800);
  const [vidAspect, setVidAspect] = useStateHome(16 / 10);   // tablet video ratio (landscape)
  const [phoneAspect, setPhoneAspect] = useStateHome(375 / 812);   // phone screenshot ratio (portrait), refined on load
  const [dashAspect, setDashAspect] = useStateHome(3624 / 5708);   // dashboard image ratio (very tall), refined on load
  const [sxAspect, setSxAspect] = useStateHome(750 / 3436);        // Stablex home screen ratio (very tall), refined on load
  const [morph, setMorph] = useStateHome(0);                 // unused (legacy)

  /* (device alternation removed — both devices now render together at rest) */

  useEffectHome(() => {
    let raf = 0;
    const measure = () => {
      const el = wrapRef.current;
      if (!el) return;
      const rect = el.getBoundingClientRect();
      const total = el.offsetHeight - window.innerHeight;
      const passed = Math.min(total, Math.max(0, -rect.top));
      setP(total > 0 ? passed / total : 0);
      setSy(passed);
    };
    const onScroll = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(measure); };
    const onResize = () => { setVw(window.innerWidth || 1280); setVh(window.innerHeight || 800); measure(); };
    let tries = 0;
    let pollRaf = 0;
    const poll = () => {
      onResize();
      if (window.innerWidth > 0 || tries++ > 10) return;
      pollRaf = requestAnimationFrame(poll);
    };
    poll();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onResize);
    window.addEventListener("load", onResize);
    return () => {
      cancelAnimationFrame(raf);
      cancelAnimationFrame(pollRaf);
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onResize);
      window.removeEventListener("load", onResize);
    };
  }, []);

  const clamp = (v, lo = 0, hi = 1) => Math.min(hi, Math.max(lo, v));
  const lerp  = (a, b, t) => a + (b - a) * t;
  const easeOut = (t) => 1 - (1 - t) * (1 - t);

  const isLg = vw >= 1024;

  /* ── scroll-px driven choreography (viewport-independent) ──
     The tablet rises + slides left + flattens over the first RISE_END px,
     then LOCKS (stays pinned, like a sticky hero element). The white
     "section 1" then scrolls UP over the black at 1:1 with scroll — so it
     reads like a normal page section, not an animated colour swap. */
  const RISE_END   = 700;   // tablet + hero text finish rising / sliding here
  const WHITE_START = 260;  // px scroll at which the white panel begins rising

  const t = easeOut(clamp(sy / RISE_END));
  /* white panel top edge: vh (below screen) → 0 (fully covering), 1:1 with scroll */
  const whiteTop = clamp(vh - (sy - WHITE_START) - 90, 0, vh);
  const whiteCover = 1 - whiteTop / vh;        // 0 = no cover, 1 = full cover
  const onWhite = whiteCover > 0.5;            // tablet centre is over white
  /* back tablet layers fan out + fade shortly after the main tablet starts moving */
  const sp = easeOut(clamp((sy - 40) / 180));

  /* device sizes — the TABLET (landscape) drives the stage choreography; the
     PHONE (portrait) is a separate element, 30% larger, that slides in/out. */
  const maxW = isLg ? 836 : Math.min(440, Math.round(vw * 0.9));
  const maxH = Math.round(vh * 0.7);
  let tabletW = Math.min(maxW, Math.round(maxH * vidAspect));
  let tabletH = Math.round(tabletW / vidAspect);
  if (tabletH > maxH) { tabletH = maxH; tabletW = Math.round(maxH * vidAspect); }
  // phone — 30% larger, height-led
  let phoneH = Math.round(vh * 0.66 * 1.3);
  if (phoneH > vh * 0.92) phoneH = Math.round(vh * 0.92);
  let phoneW = Math.round(phoneH * phoneAspect);
  if (phoneW > vw * 0.86) { phoneW = Math.round(vw * 0.86); phoneH = Math.round(phoneW / phoneAspect); }

  /* Both devices visible at rest: TABLET centered (20px higher); PHONE to its
     LEFT, tucked BEHIND it (its right ~50% hidden under the tablet, dropped
     lower per the perspective). On scroll the phone fades out quickly while
     the tablet continues its rise / slide-left / lock motion. */
  const phoneExit = clamp((sy - 120) / 280);            // stays through the rise, fades by ~400px
  const tabletExtraY = -20;                             // tablet sits 20px higher
  const tabletSwapOpacity = 1;
  const phoneCenterX = -tabletW * 0.5 + 8;              // phone's right half overlaps the tablet (+8px right at rest)
  const phoneExtraX = -t * 130;                         // on scroll: phone drifts left FASTER than the tablet (parallax)
  const phoneCenterY = -46;                             // raised so its top peeks above the tablet's top edge
  const phoneSwapScale = 0.94;
  const phoneSwapOpacity = clamp(1 - phoneExit);

  /* tablet position: ~38% visible at rest, rises to vertically-centered + slides left */
  const padTop = Math.min(200, Math.max(96, vh * 0.15));
  const heroTextBottom = padTop + 238;                 // H1 (nudged +50) + subtitle
  const restTop = Math.max(vh - 0.38 * tabletH, heroTextBottom + 24);
  const yRest = restTop - vh / 2 + tabletH / 2;
  /* locked vertical: align the tablet's CENTER to the text block's center
     (viewport middle). Compensate for the −20px lift and the 0.7 recede scale
     (which both shift the scaled center), so locked y = −0.7·(−20) ≈ +14. */
  const y = yRest * (1 - t) + (-90 + 104 * t);         // rest −90px; locked centers tablet on the text block
  /* locked: align tablet's LEFT edge to the grid's left content edge */
  const containerLeft = Math.max((vw - 1240) / 2, 0) + (isLg ? 40 : 24);
  const lockedX = containerLeft - vw / 2 + tabletW / 2;
  const x = (isLg ? lerp(0, lockedX, t) : 0) - 20 * t;  // locked: nudge tablet (+labels) 20px further left
  /* as the tablet seats into the viewport it recedes — shrink ~30% via scroll;
     starts at the halfway point of the tilt-to-flat motion and ends earlier too */
  const recede = easeOut(clamp((sy - 350) / 420));
  const scale = lerp(0.98, 1, t) * lerp(1, 0.7, recede);
  /* Star Wars crawl tilt at rest (top recedes, bottom forward) → flat on scroll */
  const tilt = (1 - t) * 42;

  /* hero text rises by the SAME travel as the tablet (lockstep) */
  const heroTextRise = yRest * t;
  const heroTextOpacity = clamp(1 - sy / 660);

  /* ── INNER PAGE: dashboard WIPES IN left→right when the tablet seats, then
     SCROLLS down (full width); only AFTER it has scrolled do the floating
     labels appear. When done the section releases and the next one begins. */
  const screenW = tabletW - 24;                         // p-3 padding both sides
  const screenH = tabletH - 24;
  const dashDisplayH = screenW / dashAspect;            // full-width, very tall
  const DASH_TRIGGER = 770;                             // scroll line that fires the slide-in (then it plays on its own)
  const DASH_SCROLL_START = 1150, DASH_SCROLL_LEN = 820; // vertical scroll begins WELL AFTER the slide-in settles
  const dashTriggered = sy > DASH_TRIGGER;
  const dashScroll = clamp((sy - DASH_SCROLL_START) / DASH_SCROLL_LEN);
  const dashTranslateY = -dashScroll * Math.max(0, dashDisplayH - screenH);
  const showDash = sy > DASH_TRIGGER - 240;             // mount early so the off-screen layer is ready to animate

  /* ── PHASE 2: hand off to the next section ──
     old text escapes up-left → once the tablet has seated, a SCROLL-TRIGGERED
     quick morph fires: tablet → phone (form + inner screen) while it travels
     right, and the background cross-fades white → black. Then new text rises. */
  const easeInOut = (q) => (q < 0.5 ? 2 * q * q : 1 - Math.pow(-2 * q + 2, 2) / 2);
  const textOut = easeOut(clamp((sy - 2420) / 240));     // old text leaves
  const MORPH_TRIGGER = 2720;                            // scroll line that fires the morph (then it plays on its own)
  const inPhase2 = sy > 2500;                            // enable CSS transitions only here
  const morphFired = sy > MORPH_TRIGGER;
  const newTextIn = morphFired ? 1 : 0;                  // section-2 text: TRIGGERED, the LAST step of the sequence
  const bgBlack = morphFired;
  const labelFade = morphFired ? 0 : 1;                  // tablet labels leave on trigger — FIRST step of the sequence
  // section-2 phone labels: SCROLL-LINKED (reveal with scroll AFTER the morph finishes), like the tablet labels
  const ease2 = (q) => 1 - (1 - q) * (1 - q);
  const s2l1 = ease2(clamp((sy - 3660) / 150));
  const s2l2 = ease2(clamp((sy - 3740) / 150));
  const s2l3 = ease2(clamp((sy - 3820) / 150));

  /* triggered morph: tablet → phone (10% larger than before), travel locked-left → parked-right */
  const STAGE_SCALE = 0.7;
  const PHONE2_SCALE = 0.935;                            // was 0.85 → +10%
  const phoneW2 = phoneW * PHONE2_SCALE;
  const phoneH2 = phoneH * PHONE2_SCALE;
  const deviceW = morphFired ? phoneW2 / STAGE_SCALE : tabletW;
  const deviceH = morphFired ? phoneH2 / STAGE_SCALE : tabletH;
  const deviceRadius = morphFired ? 44 : 28;
  const startCx = vw / 2 + (lockedX - 20);               // locked tablet screen-centre x
  const phoneLeftAnchor = (vw - containerLeft) - phoneW * 0.85 - 20 - 276; // LEFT edge anchored at the old size → gap preserved
  const parkedCx = phoneLeftAnchor + phoneW2 / 2;        // grow rightward only

  /* ── STABLEX inner-scroll: once the phone parks, the home screen scrolls
     vertically inside it (page pinned), with a fixed bottom tab-bar. Fits
     entirely inside the existing Stablex window so Morpara timing is intact. */
  const SX_SCROLL_START = 3260, SX_SCROLL_LEN = 360;     // generous entry dwell after park (2720→3260) so the FIRST scroll into Stablex does NOT move the screen; then it scrolls (ends ~3620)
  const sxScrollFrac = clamp((sy - SX_SCROLL_START) / SX_SCROLL_LEN);
  const sxFramePad = 12;                                 // p-1.5 → 6px each side
  const sxScreenW = (morphFired ? deviceW : tabletW) - sxFramePad;
  const sxScreenH = (morphFired ? deviceH : tabletH) - sxFramePad;
  const sxNavH = sxScreenW / (750 / 178);                // pinned bottom-nav height (image is 750×178)
  const sxDisplayH = sxScreenW / sxAspect;               // full-width, very tall
  const sxRange = Math.max(0, sxDisplayH - sxScreenH);   // scroll until the image bottom reaches the screen bottom (last content clears the fixed nav)
  const sxScrollPx = -sxScrollFrac * sxRange;

  /* ── PHASE 3: Morpara — MIRROR of Stablex ──
     after the Stablex labels, scrolling fires a triggered hand-off: the phone
     travels right → left (mirrored), its screen swaps red → purple, the bg
     cross-fades black → near-white grey, and Morpara text rises on the RIGHT. */
  const MORPH3_TRIGGER = 4260;                           // pushed back further so the Stablex badges dwell before the Morpara hand-off
  const inPhase3 = sy > 4160;                            // enable phase-3 transitions
  const morph3Fired = sy > MORPH3_TRIGGER;
  const bgGrey = morph3Fired;
  const newText3In = morph3Fired ? 1 : 0;                // Morpara text: triggered, last step
  const s2FadeOut = clamp((sy - 4200) / 90);             // Stablex labels/text leave as phase 3 begins
  // Morpara phone labels: SCROLL-LINKED after the mirror settles
  const s3l1 = ease2(clamp((sy - 4440) / 150));
  const s3l2 = ease2(clamp((sy - 4550) / 150));
  const s3l3 = ease2(clamp((sy - 4660) / 150));
  const parkedCx3 = vw - parkedCx - 60;                  // mirror device centre about the page centre, nudged 60px left

  const destCx = morph3Fired ? parkedCx3 : parkedCx;
  const morphDX = morphFired ? (destCx - startCx) / STAGE_SCALE : 0;
  const morphTransition = inPhase2
    ? "transform .58s cubic-bezier(.5,.05,.2,1) .16s, width .5s cubic-bezier(.5,.05,.2,1) .16s, height .5s cubic-bezier(.5,.05,.2,1) .16s"
    : "none";

  const project01 = window.PROJECTS[0];

  return (
    <section ref={wrapRef} className="relative" style={{ height: "calc(100vh + 5120px)" }}>
      <div className="sticky top-0 h-screen overflow-hidden bg-ink">
        {/* PHASE 3 — near-white grey backdrop fades in over the black */}
        <div className="absolute inset-0 z-[15] pointer-events-none" style={{ background: "#F1F1F2", opacity: bgGrey ? 1 : 0, transition: inPhase3 ? "opacity .6s ease" : "none" }} />
        {/* faint grid backdrop (black hero) */}
        <div
          className="absolute inset-0 z-0 pointer-events-none opacity-[0.06]"
          style={{
            backgroundImage:
              "linear-gradient(#fff 1px, transparent 1px), linear-gradient(90deg, #fff 1px, transparent 1px)",
            backgroundSize: "80px 80px",
            maskImage: "radial-gradient(ellipse at center, black 30%, transparent 75%)",
            WebkitMaskImage: "radial-gradient(ellipse at center, black 30%, transparent 75%)",
          }}
        />

        {/* ── HERO TEXT (rises lockstep with tablet, sits below the white panel) ── */}
        <div
          className="absolute inset-x-0 top-0 z-10 flex flex-col items-center text-center px-6"
          style={{
            paddingTop: "calc(clamp(96px, 15vh, 200px) + 50px)",
            opacity: heroTextOpacity,
            transform: `translateY(${-heroTextRise}px)`,
            pointerEvents: heroTextOpacity > 0.5 ? "auto" : "none",
          }}
        >
          <h1
            className="hero-h1-intro font-display font-normal text-[40px] sm:text-[52px] md:text-[64px] leading-[1.04] tracking-[-0.025em] text-balance max-w-[20ch]"
            style={{ textShadow: "0 6px 80px rgba(0,0,0,.6)" }}
          >
            Turning <span className="text-accent">insight</span> into design, and design into <span className="text-accent">results</span><span className="text-white">.</span>
          </h1>
          <p className="hero-sub-intro mt-6 text-[16px] md:text-[18px] text-white/75 max-w-[52ch]">
            Sr. Product Designer · 8+ years · Specialized in SaaS, Banking &amp; Fintech
          </p>
        </div>

        {/* ── SECTION 1 — white panel scrolls UP over the black, 1:1 with scroll ── */}
        <div
          id="work"
          className="absolute inset-x-0 top-0 z-20 h-screen will-change-transform"
          style={{ background: "#F1F1F2", transform: `translateY(${whiteTop}px)`, opacity: bgBlack ? 0 : 1, transition: inPhase2 ? "opacity .55s ease" : "none" }}
        >
          <div className="max-w-[1240px] mx-auto h-full px-6 md:px-10 grid grid-cols-1 lg:grid-cols-2 gap-10 items-center">
            <div className="hidden lg:block" aria-hidden="true" />
            <div className={isLg ? "pl-5" : "self-end pb-10"} style={isLg ? { maxWidth: "calc(50% + 90px)", marginLeft: "80px" } : undefined}>
              {/* escape: first UP (outer, vertical), then LEFT + fade (inner, delayed) */}
              <div style={{ transform: `translateY(${morphFired ? -190 : 0}px)`, transition: inPhase2 ? "transform .42s cubic-bezier(.5,.05,.2,1)" : "none" }}>
                <div style={{ transform: `translateX(${morphFired ? -160 : 0}px)`, opacity: morphFired ? 0 : 1, transition: inPhase2 ? "transform .42s cubic-bezier(.5,.05,.2,1) .34s, opacity .4s ease .42s" : "none" }}>
                  <HeroProjectInfo project={project01} navigate={navigate} title={<>From Idea to<br />Live Product, Solo</>} chips={["Research", "App Design", "Design System"]} caseId="algora" />
                </div>
              </div>
            </div>
          </div>
        </div>

        {/* ── SECTION 2 — text TRIGGERED in as the LAST step (delayed) over BLACK ── */}
        <div className="absolute inset-x-0 top-0 z-[25] h-screen pointer-events-none" style={{ opacity: morph3Fired ? 0 : newTextIn, transition: inPhase2 ? "opacity .5s ease" + (morph3Fired ? "" : " .72s") : "none" }}>
          <div className="max-w-[1240px] mx-auto h-full px-6 md:px-10 grid grid-cols-1 lg:grid-cols-2 gap-10 items-center">
            <div style={{ pointerEvents: (!morph3Fired && newTextIn > 0.5) ? "auto" : "none", transform: `translateY(${(1 - newTextIn) * 70 + 20}px)`, transition: inPhase2 ? "transform .58s cubic-bezier(.16,1,.3,1) .72s" : "none", marginLeft: isLg ? "162px" : undefined, maxWidth: isLg ? "calc(50% + 40px)" : undefined }}>
              <HeroProjectInfo project={project01} navigate={navigate} dark align="right" eyebrow="Stablex Crypto Trading App" accent="red" caseId="stablex" />
            </div>
            <div className="hidden lg:block" aria-hidden="true" />
          </div>
        </div>

        {/* ── SECTION 2 phone — floating crypto labels (TRIGGERED, after the text) ── */}
        {isLg && (
        <div className="absolute inset-x-0 top-0 z-[31] h-screen pointer-events-none" style={{ transform: "translateY(20px)" }}>
          {/* top-right: trade filled */}
          <S2Float reveal={s2l1 * (1 - s2FadeOut)} on={false} from={{ x: 30, y: -12 }}
                   style={{ left: parkedCx + phoneW2 / 2 - 46, top: `calc(50% - ${phoneH2 / 2 - 26}px)` }}>
            <div className="flex items-center gap-3 rounded-2xl border border-white/10 bg-[#15151b]/95 backdrop-blur px-4 py-3 shadow-[0_24px_60px_-20px_rgba(0,0,0,.8)]">
              <span className="grid place-items-center size-9 rounded-full bg-accent/15 text-accent">
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M20 6 9 17l-5-5"/></svg>
              </span>
              <div>
                <div className="text-[12px] text-white/60 leading-none">Trade filled</div>
                <div className="text-[15px] font-medium text-white mt-1">+0.84 BTC</div>
              </div>
            </div>
          </S2Float>
          {/* bottom-left: 24h portfolio */}
          <S2Float reveal={s2l2 * (1 - s2FadeOut)} on={false} from={{ x: -30, y: 16 }}
                   style={{ left: parkedCx - phoneW2 / 2 - 150, top: `calc(50% + ${phoneH2 / 2 - 116}px)` }}>
            <div className="rounded-2xl border border-white/10 bg-[#15151b]/95 backdrop-blur px-4 py-3.5 shadow-[0_24px_60px_-20px_rgba(0,0,0,.8)] w-[176px]">
              <div className="text-[12px] uppercase tracking-[0.16em] text-white/50">Portfolio · 24h</div>
              <div className="flex items-end justify-between mt-1">
                <div className="font-display text-2xl text-white">+18.3%</div>
                <div className="flex items-end gap-1 h-8">
                  {[30, 46, 40, 64, 88].map((h, i) => (
                    <span key={i} className="w-1.5 rounded-t" style={{ height: `${h}%`, background: i === 4 ? "#15D997" : "#2a2a35" }} />
                  ))}
                </div>
              </div>
            </div>
          </S2Float>
          {/* mid-left: live price chip */}
          <S2Float reveal={s2l3 * (1 - s2FadeOut)} on={false} from={{ x: -28, y: 12 }}
                   style={{ left: parkedCx - phoneW2 / 2 - 64, top: "calc(50% + 6px)" }}>
            <div className="flex items-center gap-2.5 rounded-full border border-white/10 bg-[#15151b]/95 backdrop-blur pl-2.5 pr-4 py-2 shadow-[0_24px_60px_-20px_rgba(0,0,0,.8)]">
              <span className="grid place-items-center size-7 rounded-full bg-accent/15 text-accent text-[12px] font-semibold">₿</span>
              <div className="leading-tight">
                <div className="text-[10px] text-white/55">BTC / TRY</div>
                <div className="text-[13px] font-medium text-white">₺2.10M</div>
              </div>
            </div>
          </S2Float>
        </div>
        )}

        {/* ── SECTION 3 (Morpara) — text TRIGGERED in on the RIGHT (mirror), dark on grey ── */}
        <div className="absolute inset-x-0 top-0 z-[26] h-screen pointer-events-none" style={{ opacity: newText3In, transition: inPhase3 ? "opacity .5s ease .72s" : "none" }}>
          <div className="max-w-[1240px] mx-auto h-full px-6 md:px-10 grid grid-cols-1 lg:grid-cols-2 gap-10 items-center">
            <div className="hidden lg:block" aria-hidden="true" />
            <div style={{ pointerEvents: newText3In > 0.5 ? "auto" : "none", transform: `translateX(-60px) translateY(${(1 - newText3In) * 70 + 20}px)`, transition: inPhase3 ? "transform .58s cubic-bezier(.16,1,.3,1) .72s" : "none", marginLeft: isLg ? "53px" : undefined, maxWidth: isLg ? "calc(50% + 40px)" : undefined }}>
              <HeroProjectInfo project={project01} navigate={navigate} align="left" eyebrow="Morpara Digital Wallet App" title="Why didn't a perfect upgrade flow work?" accent="purple" caseId="morpara" />
            </div>
          </div>
        </div>

        {/* ── SECTION 3 phone — floating wallet labels (mirror, SCROLL-LINKED) ── */}
        {isLg && (
        <div className="absolute inset-x-0 top-0 z-[32] h-screen pointer-events-none" style={{ transform: "translateY(20px)" }}>
          {/* top-left: payment sent */}
          <S2Float reveal={s3l1} on={false} from={{ x: -30, y: -12 }}
                   style={{ right: parkedCx + 60 + phoneW2 / 2 - 46, top: `calc(50% - ${phoneH2 / 2 - 26}px)` }}>
            <div className="flex items-center gap-3 rounded-2xl border border-black/5 bg-white px-4 py-3 shadow-[0_24px_60px_-24px_rgba(0,0,0,.3)]">
              <span className="grid place-items-center size-9 rounded-full bg-accent/15 text-accent">
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
              </span>
              <div>
                <div className="text-[12px] text-black/45 leading-none">Payment sent</div>
                <div className="text-[15px] font-medium text-[#0e0e12] mt-1">€240.00</div>
              </div>
            </div>
          </S2Float>
          {/* bottom-right: balance card */}
          <S2Float reveal={s3l2} on={false} from={{ x: 30, y: 16 }}
                   style={{ right: parkedCx + 60 - phoneW2 / 2 - 150, top: `calc(50% + ${phoneH2 / 2 - 116}px)` }}>
            <div className="rounded-2xl border border-black/5 bg-white px-4 py-3.5 shadow-[0_24px_60px_-24px_rgba(0,0,0,.3)] w-[176px]">
              <div className="text-[12px] uppercase tracking-[0.16em] text-black/40">Wallet balance</div>
              <div className="flex items-end justify-between mt-1">
                <div className="font-display text-2xl text-[#0e0e12]">₺20,967</div>
                <span className="grid place-items-center size-7 rounded-full bg-accent/15 text-accent text-[12px] font-semibold">₺</span>
              </div>
            </div>
          </S2Float>
          {/* mid-right: multi-currency chip */}
          <S2Float reveal={s3l3} on={false} from={{ x: 28, y: 12 }}
                   style={{ right: parkedCx + 60 - phoneW2 / 2 - 64, top: "calc(50% + 6px)" }}>
            <div className="flex items-center gap-2.5 rounded-full border border-black/5 bg-white pl-2.5 pr-4 py-2 shadow-[0_24px_60px_-24px_rgba(0,0,0,.3)]">
              <span className="grid place-items-center size-7 rounded-full bg-accent/15 text-accent text-[12px] font-semibold">€</span>
              <div className="leading-tight">
                <div className="text-[10px] text-black/45">EUR · GBP · TRY</div>
                <div className="text-[13px] font-medium text-[#0e0e12]">3 wallets</div>
              </div>
            </div>
          </S2Float>
        </div>
        )}

        {/* ── DEVICE STAGE (tablet + phone slide-swap; bridges black → white) ── */}
        <div
          className="absolute z-30 pointer-events-none"
          style={{
            top: "50%",
            left: "50%",
            width: 0,
            height: 0,
            transform: `translate(${x}px, ${y}px) scale(${scale})`,
            perspective: "1150px",
            perspectiveOrigin: "50% 50%",
          }}
        >
          {/* TABLET device (front) — SCROLL-TRIGGERED morph into a phone, travels right */}
          <div
            className="absolute will-change-transform"
            style={{
              left: 0,
              top: 0,
              width: deviceW,
              height: deviceH,
              zIndex: 2,
              opacity: tabletSwapOpacity,
              transform: `translate(calc(-50% + ${morphDX}px), calc(-50% + ${tabletExtraY + (morphFired ? 20 / scale : 0)}px)) rotateX(${tilt}deg)`,
              transition: morphTransition,
              transformStyle: "preserve-3d",
            }}
          >
            <BackTablet side="left" sp={sp} />
            <BackTablet side="right" sp={sp} />
            <TabletMockup sy={sy} width={deviceW} onWhite={bgGrey || (onWhite && !bgBlack)} variant="tablet" onAspect={setVidAspect}
                          showDash={showDash} dashTriggered={dashTriggered} dashTranslateY={dashTranslateY} onDashAspect={setDashAspect}
                          radiusOverride={deviceRadius} morphOverlaySrc={null} morphOverlayOpacity={morphFired ? 1 : 0}
                          sxScrollSrc={SX_HOME_SCROLL_SRC} sxScrollPx={sxScrollPx} sxBottomNavSrc={SX_BOTTOMNAV_SRC} onSxAspect={setSxAspect}
                          morphOverlay2Src={PHONE_SHOT2_SRC} morphOverlay2Opacity={morph3Fired ? 1 : 0}
                          morphTransitionOn={inPhase2} labelFade={labelFade} labelTransitionOn={inPhase2} frameColor={morph3Fired ? "#0a0a0a" : (morphFired ? "#998787" : undefined)} />
          </div>
        </div>
      </div>
    </section>
  );
}

/* ---------- Back tablet layers: stacked behind the main device, fan out + fade ---------- */
function BackTablet({ side, sp }) {
  const dir = side === "left" ? -1 : 1;
  const x = dir * (18 + 50 * sp);          // peeks at rest, slides out 50px
  const rot = dir * (2.5 + 4 * sp);
  const opacity = (1 - sp) * 0.6;
  return (
    <div
      className="absolute inset-0 will-change-transform"
      style={{
        transform: `translateX(${x}px) rotate(${rot}deg) scale(0.965)`,
        transformOrigin: "center 62%",
        opacity,
        pointerEvents: "none",
      }}
    >
      <div
        className="w-full h-full rounded-[34px] border border-white/10 bg-[#050505] p-3"
        style={{ boxShadow: "0 40px 100px -45px rgba(0,0,0,.85)" }}
      >
        <div className="relative w-full h-full rounded-[24px] overflow-hidden bg-[#0c0c0f]">
          <img src={TABLET_SCREEN_SRC} alt="" draggable="false" className="block w-full select-none" />
          {/* 50% black scrim to push these layers back */}
          <div className="absolute inset-0 bg-black/50 pointer-events-none" />
        </div>
      </div>
    </div>
  );
}

/* ---------- Tablet mockup: portrait device + parallax image screen + floating UI layers ---------- */
function TabletMockup({ sy, width, onWhite, variant = "tablet", phoneSrc = PHONE_SHOT_SRC, onAspect, onPhoneAspect, showDash, dashTriggered = false, dashTranslateY = 0, onDashAspect, radiusOverride, morphOverlaySrc, morphOverlayOpacity = 0, morphOverlay2Src, morphOverlay2Opacity = 0, morphTransitionOn = false, labelFade = 1, labelTransitionOn = false, frameColor, sxScrollSrc, sxScrollPx = 0, sxBottomNavSrc, onSxAspect }) {
  const clamp = (v, lo = 0, hi = 1) => Math.min(hi, Math.max(lo, v));
  const ease = (t) => 1 - (1 - t) * (1 - t);
  const isPhone = variant === "phone";
  const radius = radiusOverride != null ? radiusOverride : (isPhone ? 44 : 28);
  const screenRadius = Math.max(6, radius - 8);

  // floating layers stagger in AFTER the dashboard has scrolled
  const l1 = ease(clamp((sy - 2040) / 120)) * labelFade; // top-right toast
  const l2 = ease(clamp((sy - 2150) / 120)) * labelFade; // bottom-left conversion card
  const l3 = ease(clamp((sy - 2260) / 120)) * labelFade; // right-mid onboarding chip
  const innerScroll = clamp((sy - 740) / 460); // gentle inner parallax

  return (
    <div className="relative w-full h-full">
      {/* ground glow */}
      <div
        className="absolute left-1/2 -translate-x-1/2 bottom-[-6%] w-[78%] h-16 rounded-full"
        style={{ background: "radial-gradient(closest-side, rgba(21,217,151,.4), transparent 75%)", filter: "blur(26px)" }}
      />

      {/* device */}
      <div
        className={`relative w-full h-full border ${frameColor ? "p-1.5" : "p-3"}`}
        style={{
          background: frameColor || "#050505",
          borderRadius: radius,
          borderColor: frameColor ? "rgba(0,0,0,.10)" : (onWhite ? "rgba(0,0,0,.12)" : "rgba(255,255,255,.10)"),
          boxShadow: onWhite
            ? "0 40px 90px -30px rgba(0,0,0,.35), inset 0 0 0 1px rgba(255,255,255,.04)"
            : "0 50px 130px -40px rgba(0,0,0,.9), inset 0 0 0 1px rgba(255,255,255,.04)",
          transition: morphTransitionOn ? "border-radius .5s ease, box-shadow .5s ease, border-color .5s ease, background .5s ease" : undefined,
        }}
      >
        {/* camera */}
        <div className="absolute top-[18px] left-1/2 -translate-x-1/2 z-10 size-2 rounded-full bg-white/15" />
        {/* screen — single video per variant (tablet landscape / phone portrait) */}
        <div className="relative w-full h-full overflow-hidden bg-[#0c0c0f]" style={{ borderRadius: screenRadius, transition: morphTransitionOn ? "border-radius .5s ease" : undefined }}>
          {isPhone ? (
            <img
              src={phoneSrc}
              alt="Stablex — mobile app"
              draggable="false"
              onLoad={(e) => {
                const im = e.currentTarget;
                if (onPhoneAspect && im.naturalWidth && im.naturalHeight) onPhoneAspect(im.naturalWidth / im.naturalHeight);
              }}
              className="absolute inset-0 w-full h-full object-cover object-top select-none"
            />
          ) : (
            <img
              src={TABLET_SHOT_SRC}
              alt="Algora — AI Visibility homepage"
              draggable="false"
              onLoad={(e) => {
                const im = e.currentTarget;
                if (onAspect && im.naturalWidth && im.naturalHeight) onAspect(im.naturalWidth / im.naturalHeight);
              }}
              className="absolute inset-0 w-full h-full object-cover object-top select-none"
            />
          )}
          {/* DASHBOARD — slide-in LAYER (scroll-TRIGGERED, bounce easing) over the
              fixed homepage; the inner image scrolls vertically (scroll-linked). */}
          {!isPhone && showDash && (
            <div
              className="absolute inset-0 will-change-transform"
              style={{
                transform: `translateX(${dashTriggered ? "0%" : "-100%"})`,
                transition: "transform .6s cubic-bezier(.4,1.06,.55,1)",
                boxShadow: "16px 0 44px -8px rgba(0,0,0,0.28)",
              }}
            >
              {/* left bleed — covers the gap if the bounce overshoots, so the
                  layer behind (homepage) never peeks on the left */}
              <div className="absolute top-0 bottom-0" style={{ right: "100%", width: "60%", background: "#f4f6f9" }} />
              <img
                src={TABLET_DASH_SRC}
                alt="Algora — dashboard"
                draggable="false"
                onLoad={(e) => {
                  const im = e.currentTarget;
                  if (onDashAspect && im.naturalWidth && im.naturalHeight) onDashAspect(im.naturalWidth / im.naturalHeight);
                }}
                className="absolute top-0 left-0 w-full h-auto select-none will-change-transform"
                style={{ transform: `translateY(${dashTranslateY}px)` }}
              />
            </div>
          )}
          {/* subtle screen sheen */}
          <div
            className="absolute inset-0 pointer-events-none"
            style={{ background: "linear-gradient(125deg, rgba(255,255,255,.10), rgba(255,255,255,0) 38%)" }}
          />
          {/* STABLEX home screen — scrolls vertically inside the parked phone
              (page pinned), with a FIXED bottom tab-bar overlay. Crossfades in
              with the morph, like the old static overlay it replaces. */}
          {!isPhone && sxScrollSrc && (
            <div
              className="absolute inset-0 overflow-hidden select-none"
              style={{ opacity: morphOverlayOpacity, transition: morphTransitionOn ? "opacity .45s ease" : undefined }}
            >
              <img
                src={sxScrollSrc}
                alt="Stablex — home"
                draggable="false"
                onLoad={(e) => {
                  const im = e.currentTarget;
                  if (onSxAspect && im.naturalWidth && im.naturalHeight) onSxAspect(im.naturalWidth / im.naturalHeight);
                }}
                className="absolute top-0 left-0 w-full h-auto select-none will-change-transform"
                style={{ transform: `translateY(${sxScrollPx}px)` }}
              />
              {sxBottomNavSrc && (
                <img
                  src={sxBottomNavSrc}
                  alt=""
                  draggable="false"
                  className="absolute bottom-0 left-0 w-full h-auto select-none"
                  style={{ filter: "drop-shadow(0 -8px 18px rgba(0,0,0,.10))" }}
                />
              )}
            </div>
          )}
          {/* morph overlay — phone screen crossfades in as the tablet becomes a phone */}
          {!isPhone && morphOverlaySrc && (
            <img
              src={morphOverlaySrc}
              alt=""
              draggable="false"
              className="absolute inset-0 w-full h-full object-cover object-top select-none pointer-events-none"
              style={{ opacity: morphOverlayOpacity, transition: morphTransitionOn ? "opacity .45s ease" : undefined }}
            />
          )}
          {/* second morph overlay — Morpara purple screen crossfades over the red one */}
          {!isPhone && morphOverlay2Src && (
            <img
              src={morphOverlay2Src}
              alt=""
              draggable="false"
              className="absolute inset-0 w-full h-full object-cover object-top select-none pointer-events-none"
              style={{ opacity: morphOverlay2Opacity, transition: morphTransitionOn ? "opacity .45s ease" : undefined }}
            />
          )}
          {/* 3% black scrim over the whole screen (tablet + phone) */}
          <div className="absolute inset-0 pointer-events-none" style={{ background: "rgba(0,0,0,0.03)" }} />
        </div>
      </div>

      {/* floating layers only on the tablet (they reveal on scroll over the white section) */}
      {!isPhone && (<>
      {/* ── Floating layer 1: payment received toast (top-right) ── */}
      <FloatLayer reveal={l1} transition={labelTransitionOn ? "opacity .34s ease, transform .34s ease" : undefined} className="absolute -right-5 top-[14%]" from={{ x: 34, y: -10 }}>
        <div className="flex items-center gap-3 rounded-2xl border border-white/10 bg-[#15151b]/95 backdrop-blur px-4 py-3 shadow-[0_24px_60px_-20px_rgba(0,0,0,.8)]">
          <span className="grid place-items-center size-9 rounded-full bg-accent/15 text-accent">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M20 6 9 17l-5-5"/></svg>
          </span>
          <div>
            <div className="text-[12px] text-white/60 leading-none">Payment received</div>
            <div className="text-[15px] font-medium text-white mt-1">$ 4,200.00</div>
          </div>
        </div>
      </FloatLayer>

      {/* ── Floating layer 2: conversion card (bottom-left) ── */}
      <FloatLayer reveal={l2} transition={labelTransitionOn ? "opacity .34s ease .05s, transform .34s ease .05s" : undefined} className="absolute -left-6 bottom-[16%]" from={{ x: -34, y: 16 }}>
        <div className="rounded-2xl border border-white/10 bg-[#15151b]/95 backdrop-blur px-4 py-3.5 shadow-[0_24px_60px_-20px_rgba(0,0,0,.8)] w-[176px]">
          <div className="text-[12px] uppercase tracking-[0.16em] text-white/50">Trial → Paid</div>
          <div className="flex items-end justify-between mt-1">
            <div className="font-display text-2xl text-white">+42%</div>
            <div className="flex items-end gap-1 h-8">
              {[34, 52, 44, 70, 88].map((h, i) => (
                <span key={i} className="w-1.5 rounded-t" style={{ height: `${h}%`, background: i === 4 ? "#15D997" : "#2a2a35" }} />
              ))}
            </div>
          </div>
        </div>
      </FloatLayer>

      {/* ── Floating layer 3: onboarding chip (right-mid) ── */}
      <FloatLayer reveal={l3} transition={labelTransitionOn ? "opacity .34s ease .1s, transform .34s ease .1s" : undefined} className="absolute -right-4 bottom-[34%]" from={{ x: 30, y: 14 }}>
        <div className="flex items-center gap-3 rounded-full border border-white/10 bg-[#15151b]/95 backdrop-blur pl-2 pr-4 py-2 shadow-[0_24px_60px_-20px_rgba(0,0,0,.8)]">
          <div className="flex -space-x-2">
            {["#15D997", "#3FE3AC", "#0E9466"].map((c) => (
              <span key={c} className="size-6 rounded-full border-2 border-[#15151b]" style={{ background: c }} />
            ))}
          </div>
          <span className="text-[12px] text-white/85">3 admins onboarded</span>
        </div>
      </FloatLayer>
      </>)}
    </div>
  );
}

function FloatLayer({ reveal, from, className, children, transition }) {
  return (
    <div
      className={className}
      style={{
        opacity: reveal,
        transform: `translate(${(1 - reveal) * from.x}px, ${(1 - reveal) * from.y}px)`,
        transition,
        pointerEvents: reveal > 0.6 ? "auto" : "none",
      }}
    >
      {children}
    </div>
  );
}

/* Section-2 floating label — screen-positioned, triggered reveal */
function S2Float({ reveal, on, delay = "0s", from, style, tilt = "", children }) {
  return (
    <div
      className="absolute"
      style={{
        ...style,
        opacity: reveal,
        transform: `translate(${(1 - reveal) * from.x}px, ${(1 - reveal) * from.y}px) ${tilt}`,
        transition: on ? `opacity .5s ease ${delay}, transform .55s cubic-bezier(.16,1,.3,1) ${delay}` : "none",
      }}
    >
      {children}
    </div>
  );
}

/* ---------- Hero project-01 info card (white "section 1", custom layout) ---------- */
function HeroProjectInfo({ project, navigate, dark = false, eyebrow = "Algora AI Visibility SaaS", title = "The crypto exchange with demo account", chips = ["Research", "UX Design", "UI Design"], align = "left", caseId, accent }) {
  const eyebrowC = dark ? "text-white/60" : "text-black/55";
  const titleC   = dark ? "text-white" : "text-[#0e0e12]";
  const pillC    = dark ? "bg-white text-[#0e0e12]" : "bg-[#0e0e12] text-white";
  const linkC    = dark ? "text-white" : "text-[#0e0e12]";
  // optional red accent (Stablex): light-red chips + Stablex-red link
  const chipRed  = accent === "red";
  const chipPurple = accent === "purple";
  const chipGreen = accent === "green";
  const linkColor = accent === "red" ? (dark ? "#FF8A93" : "#E8001C") : accent === "purple" ? (dark ? "#C49BE6" : "#8031A7") : accent === "green" ? (dark ? "#5FE6AC" : "#0E8C63") : undefined;
  const right = align === "right";
  return (
    <div className={right ? "text-right" : undefined}>
      {/* eyebrow — plain text, no pill, no number/line */}
      <div className={`mt-5 text-xs uppercase tracking-[0.18em] ${eyebrowC}`}>
        {eyebrow}
      </div>

      <h3 className={`mt-4 font-display font-medium text-[29px] md:text-[40px] leading-[1.06] tracking-tight max-w-[16ch] ${titleC} ${right ? "ml-auto" : ""}`}>
        {title}
      </h3>

      <div className={`mt-5 flex flex-wrap gap-1.5 ${right ? "justify-end" : ""}`}>
        {chips.map((t) => (
          <span key={t}
            className={`inline-flex items-center rounded-full px-2.5 py-1 text-[9.2px] uppercase tracking-[0.16em] ${chipRed || chipPurple || chipGreen ? "" : pillC}`}
            style={chipRed ? { background: "rgba(232,0,28,0.10)", color: "#8C0014" } : chipPurple ? { background: "#E7DBF4", color: "#2B1243" } : chipGreen ? (dark ? { background: "rgba(95,230,172,0.14)", color: "#5FE6AC" } : { background: "#EAF3EE", color: "#0E8C63" }) : undefined}>
            {t}
          </span>
        ))}
      </div>

      <a
        href="#"
        onClick={(e) => { e.preventDefault(); navigate(`case/${caseId || project.id}`); window.scrollTo({ top: 0 }); }}
        className={`mt-7 inline-flex items-center gap-2 text-sm group ${linkColor ? "" : linkC} flex-row-reverse`}
        style={linkColor ? { color: linkColor } : undefined}
      >
        <span className="link-underline font-medium">View case study</span>
        <span aria-hidden="true" className="transition-transform group-hover:translate-x-1">→</span>
      </a>
    </div>
  );
}

/* ---------- Project info card (reused by TabletHero + ProjectStop) ---------- */
function ProjectInfo({ project, navigate, light = false }) {
  const numC   = light ? "text-black/35" : "text-muted";
  const lineC  = light ? "bg-black/15" : "bg-line";
  const titleC = light ? "text-[#0e0e12]" : "";
  const typeC  = light ? "text-black/45" : "text-muted";
  const dotC   = light ? "text-black/25" : "text-border2";
  const valueC = light ? "text-[#0e0e12]" : "text-white";
  const labelC = light ? "text-black/55" : "text-muted";
  const linkC  = light ? "text-[#0e0e12]" : "text-white";

  return (
    <div>
      <div className="flex items-center gap-3 mb-5">
        <span className={`font-display text-2xl tabular-nums ${numC}`}>{project.n}</span>
        <span className={`h-px w-10 ${lineC}`} />
        {light ? (
          <span className="inline-flex items-center gap-2 rounded-btn border border-black/12 bg-black/[0.03] px-3 py-1 text-xs tracking-tight text-[#0e0e12]">
            <span className="size-1.5 rounded-full bg-accent" />
            {project.tag}
          </span>
        ) : (
          <Tag>{project.tag}</Tag>
        )}
      </div>

      <h3 className={`font-display font-medium text-[32px] md:text-[44px] leading-[1.04] tracking-tight max-w-[18ch] ${titleC}`}>
        {project.title}
      </h3>

      <div className={`mt-5 flex flex-wrap gap-x-2 gap-y-1 text-xs uppercase tracking-[0.18em] ${typeC}`}>
        {project.types.map((t, i) => (
          <span key={t} className="inline-flex items-center gap-2">
            {i > 0 && <span className={dotC}>·</span>}
            <span>{t}</span>
          </span>
        ))}
      </div>

      <div className="mt-7 space-y-4">
        {project.metrics.map((m) => (
          <div key={m.label} className="border-l-2 border-accent pl-4">
            <div className={`font-display text-2xl md:text-3xl ${valueC}`}>{m.value}</div>
            <div className={`text-sm mt-1 max-w-[42ch] ${labelC}`}>{m.label}</div>
          </div>
        ))}
      </div>

      <a
        href="#"
        onClick={(e) => { e.preventDefault(); navigate(`case/${project.id}`); window.scrollTo({ top: 0 }); }}
        className={`mt-8 inline-flex items-center gap-2 text-sm group ${linkC}`}
      >
        <span className="link-underline">View case study</span>
        <span aria-hidden="true" className="transition-transform group-hover:translate-x-1">→</span>
      </a>
    </div>
  );
}

/* ---------- WORK: projects 02 — 05 (project 01 is part of TabletHero) ---------- */
function Work({ navigate }) {
  return (
    <section className="relative" id="projects">
      {window.PROJECTS.slice(1).map((p, i) => (
        <ProjectStop key={p.id} project={p} index={i + 1} navigate={navigate} />
      ))}
    </section>
  );
}

function ProjectStop({ project, index, navigate }) {
  const wrapRef = useRefHome(null);
  const [progress, setProgress] = useStateHome(0);

  useEffectHome(() => {
    const el = wrapRef.current;
    if (!el) return;
    const onScroll = () => {
      const rect = el.getBoundingClientRect();
      const vh = window.innerHeight;
      const total = el.offsetHeight - vh;
      const passed = Math.min(total, Math.max(0, -rect.top));
      setProgress(total > 0 ? passed / total : 0);
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  const isPhone = project.layout === "phone";
  const hue = 165 + index * 4;

  const textOpacity = Math.min(1, Math.max(0, (progress - 0.05) * 2.4));
  const textTy = (1 - textOpacity) * 30;

  return (
    <div ref={wrapRef} className="relative" style={{ height: "200vh" }}>
      <div className="sticky top-0 h-screen flex items-center">
        <div className="max-w-[1240px] mx-auto w-full px-6 md:px-10">
          <div className={`grid grid-cols-1 ${isPhone ? "lg:grid-cols-[420px_1fr]" : "lg:grid-cols-[1fr_400px]"} gap-12 lg:gap-20 items-center`}>
            <div className={`${isPhone ? "" : "lg:order-1"} flex justify-center`}>
              {isPhone
                ? <PhoneMockup progress={progress} accentHue={hue} label={project.n} />
                : <DesktopMockup progress={progress} accentHue={hue} label={project.n} />}
            </div>
            <div
              className={`${isPhone ? "" : "lg:order-2"}`}
              style={{
                opacity: textOpacity,
                transform: `translateY(${textTy}px)`,
                transition: "opacity .2s linear, transform .2s linear",
              }}
            >
              <ProjectInfo project={project} navigate={navigate} />
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ---------- CLOSING: "What I bring" → scroll-driven band → "Let's work together" ----------
   One pinned section. The "What I bring" closer sits at the vertical centre; as you
   scroll, the capabilities band sweeps across that same centre line (scroll-linked,
   not auto-animated); once it clears, "Let's work together." slides in from the left
   onto the very same centre line, so both headings share one height. */
function Closing({ navigate }) {
  const items = (typeof window !== "undefined" && window.CAPABILITIES) || [
    "End-to-end UX", "Interaction Design", "Information Architecture",
    "Wireframing", "Prototyping", "Design Systems",
    "In-depth Interviews", "Moderated Usability Testing",
    "Figma (Auto Layout v5, Variables, Dev Mode)",
  ];
  const wrapRef = useRefHome(null);
  const bandRef = useRefHome(null);
  const [p, setP] = useStateHome(0);
  const [vw, setVw] = useStateHome((typeof window !== "undefined" && window.innerWidth) || 1280);
  const [bandW, setBandW] = useStateHome(1600);

  useEffectHome(() => {
    const measure = () => {
      const el = wrapRef.current;
      if (!el) return;
      const rect = el.getBoundingClientRect();
      const total = Math.max(1, el.offsetHeight - window.innerHeight);
      setP(clamp(-rect.top / total));
      setVw(window.innerWidth);
      if (bandRef.current) setBandW(bandRef.current.scrollWidth);
    };
    measure();
    window.addEventListener("scroll", measure, { passive: true });
    window.addEventListener("resize", measure);
    return () => { window.removeEventListener("scroll", measure); window.removeEventListener("resize", measure); };
  }, []);

  const clamp = (v, lo = 0, hi = 1) => Math.min(hi, Math.max(lo, v));
  const lerp = (a, b, t) => a + (b - a) * t;
  const easeOut = (t) => 1 - Math.pow(1 - t, 3);

  /* choreography */
  const whatP   = clamp((p - 0.03) / 0.07);                 // heading block fades/rises in
  const bandP   = clamp((p - 0.10) / 0.81);                 // band sweeps right → left (50% slower)
  const bandX   = lerp(vw, -bandW, bandP);
  // heading starts moving the instant the last (right-most) word enters the screen,
  // then tracks the band's leftward motion 1:1 — they leave together.
  const headX   = Math.min(0, bandX - (vw - bandW));
  const headOp  = whatP * clamp(1 + headX / (vw * 0.85));
  const bandFade = clamp(bandP / 0.06) * (1 - clamp((bandP - 0.96) / 0.04));
  const lwtP    = clamp((p - 0.88) / 0.12);                 // "Let's work together" enters from the RIGHT
  const lwtX    = lerp(vw * 0.6, 0, easeOut(lwtP));
  const ctaP    = clamp((p - 0.95) / 0.05);                 // email + socials

  return (
    <section ref={wrapRef} id="contact" className="relative" style={{ height: "340vh" }}>
      <div className="sticky top-0 h-screen overflow-hidden">
        {/* Heading block — fixed while the band flows below it, then slides left */}
        <div className="absolute inset-x-0 px-6 text-center pointer-events-none"
             style={{ top: "38%", transform: `translateY(-50%) translateX(${headX}px) translateY(${(1 - whatP) * 24}px)`, opacity: headOp }}>
          <SectionEyebrow center noDot>What I bring</SectionEyebrow>
          <h2 className="mt-5 font-display font-medium text-[34px] md:text-[52px] leading-[1.04] tracking-tight max-w-[20ch] mx-auto">
            <span style={{ color: "#15D997" }}>A generalist's range</span>, with a systems designer's discipline.
          </h2>
        </div>

        {/* Band — below the heading, scroll-linked sweep, half-size */}
        <div className="absolute inset-x-0 overflow-hidden pointer-events-none" style={{ top: "63%", transform: "translateY(-50%)", opacity: bandFade }}>
          <div ref={bandRef} className="flex gap-6 whitespace-nowrap will-change-transform" style={{ transform: `translateX(${bandX}px)`, width: "max-content" }}>
            {items.map((t, i) => (
              <span key={i} className="font-normal text-[17px] md:text-[28px] tracking-tight text-muted">
                {t} <span className="text-accent">●</span>
              </span>
            ))}
          </div>
        </div>

        {/* "Let's work together." — slides in from the right onto the centre line */}
        <div className="absolute inset-x-0 px-6 text-center" style={{ top: "50%", transform: `translateY(-50%) translateX(${lwtX}px)`, opacity: lwtP }}>
          <h2 className="font-display font-normal text-[48px] md:text-[80px] leading-[0.98] tracking-tight">
            Let's work<span className="text-accent"> together</span><span className="text-muted">.</span>
          </h2>
        </div>

        {/* CTA — email + socials, anchored below the centre line */}
        <div className="absolute inset-x-0 px-6 text-center" style={{ top: "calc(50% + 84px)", opacity: ctaP, transform: `translateY(${(1 - ctaP) * 20}px)` }}>
          <a href="mailto:tunaaldemir@gmail.com"
             className="inline-block font-display font-normal text-[16px] md:text-[22px] tracking-tight link-underline">
            tunaaldemir@gmail.com
          </a>
          <div className="mt-2">
            <a href={/iPhone|iPad|iPod|Android/i.test(navigator.userAgent) ? "tel:+31634483433" : "facetime:+31634483433"}
               className="inline-block font-display font-normal text-[14px] md:text-[17px] tracking-tight link-underline"
               style={{ color: "rgba(255,255,255,0.55)" }}>
              +31 06 34 48 34 33
            </a>
          </div>
          <div className="mt-10 flex items-center justify-center gap-3">
              <a href="https://wa.me/905353130311" target="_blank" rel="noopener noreferrer" aria-label="WhatsApp"
                 className="transition-transform hover:-translate-y-0.5">
                <svg width="44" height="44" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
                  <circle cx="17" cy="17" r="17" fill="#fff"/>
                  <g transform="translate(7 7) scale(0.833)">
                    <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.149-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.885-9.885 9.885M20.52 3.449C18.24 1.245 15.24 0 12.045 0 5.463 0 .104 5.334.101 11.892c0 2.096.549 4.142 1.595 5.945L0 24l6.335-1.652a12.062 12.062 0 005.71 1.447h.005c6.585 0 11.946-5.335 11.949-11.893a11.821 11.821 0 00-3.484-8.45" fill="#331110"/>
                  </g>
                </svg>
              </a>
              <a href="https://www.linkedin.com/in/tuna-aldemir/" target="_blank" rel="noopener noreferrer" aria-label="LinkedIn"
                 className="transition-transform hover:-translate-y-0.5">
                <svg width="44" height="44" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
                  <circle cx="17" cy="17" r="17" fill="#fff"/>
                  <path d="M12.6 13.2a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm-1.27 1.15h2.53v8.15h-2.53v-8.15Zm4.12 0h2.42v1.11h.04c.34-.62 1.16-1.28 2.39-1.28 2.56 0 3.03 1.64 3.03 3.78v4.54h-2.53v-4.03c0-.96-.02-2.2-1.36-2.2-1.36 0-1.57 1.04-1.57 2.13v4.1h-2.53v-8.15Z" fill="#331110"/>
                </svg>
              </a>
              <a href={CV_SRC} target="_blank" rel="noopener noreferrer" aria-label="View CV (PDF)" className="transition-transform hover:-translate-y-0.5">
                <span className="grid place-items-center size-11 rounded-full" style={{ background: "#fff", color: "#331110" }}>
                  <span className="font-display font-semibold text-[14px] tracking-tight">CV</span>
                </span>
              </a>
            </div>
        </div>
      </div>
    </section>
  );
}

/* ---------- CONTACT (legacy, unused) ---------- */
function Contact() {
  return (
    <section id="contact-legacy" className="py-32 px-6 hidden">
      <div className="max-w-[900px] mx-auto text-center">
        <Reveal delay={80}>
          <h2 className="font-display font-normal text-[48px] md:text-[80px] leading-[0.98] tracking-tight">
            Let's work
            <span className="text-accent"> together</span>
            <span className="text-muted">.</span>
          </h2>
        </Reveal>
        <Reveal delay={240}>
          <a
            href="mailto:tunaaldemir@gmail.com"
            className="mt-12 inline-block font-display font-normal text-[16px] md:text-[22px] tracking-tight link-underline"
          >
            tunaaldemir@gmail.com
          </a>
        </Reveal>

        <Reveal delay={320}>
          <div className="mt-10 flex items-center justify-center gap-3">
            <a href="https://wa.me/905353130311" target="_blank" rel="noopener noreferrer" aria-label="WhatsApp"
               className="transition-transform hover:-translate-y-0.5">
              <svg width="44" height="44" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
                <circle cx="17" cy="17" r="17" fill="#fff"/>
                <g transform="translate(7 7) scale(0.833)">
                  <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.149-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.885-9.885 9.885M20.52 3.449C18.24 1.245 15.24 0 12.045 0 5.463 0 .104 5.334.101 11.892c0 2.096.549 4.142 1.595 5.945L0 24l6.335-1.652a12.062 12.062 0 005.71 1.447h.005c6.585 0 11.946-5.335 11.949-11.893a11.821 11.821 0 00-3.484-8.45" fill="#331110"/>
                </g>
              </svg>
            </a>
            <a href="https://www.linkedin.com/in/tuna-aldemir/" target="_blank" rel="noopener noreferrer" aria-label="LinkedIn"
               className="transition-transform hover:-translate-y-0.5">
              <svg width="44" height="44" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
                <circle cx="17" cy="17" r="17" fill="#fff"/>
                <path d="M12.6 13.2a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm-1.27 1.15h2.53v8.15h-2.53v-8.15Zm4.12 0h2.42v1.11h.04c.34-.62 1.16-1.28 2.39-1.28 2.56 0 3.03 1.64 3.03 3.78v4.54h-2.53v-4.03c0-.96-.02-2.2-1.36-2.2-1.36 0-1.57 1.04-1.57 2.13v4.1h-2.53v-8.15Z" fill="#331110"/>
              </svg>
            </a>
            <a href={CV_SRC} target="_blank" rel="noopener noreferrer" aria-label="View CV (PDF)" className="transition-transform hover:-translate-y-0.5">
              <span className="grid place-items-center size-11 rounded-full" style={{ background: "#fff", color: "#331110" }}>
                <span className="font-display font-semibold text-[14px] tracking-tight">CV</span>
              </span>
            </a>
          </div>
        </Reveal>

        <Reveal delay={400}>
        </Reveal>
      </div>
    </section>
  );
}

window.Home = Home;
