// Case study modal — MVNO Mobile App.
// Full-bleed scrollable overlay with editorial layout.
// Opens via the carousel phone or "Open case study" CTA.

const MVNO_ACCENT = '#B0EBBB';
const MVNO_BG = '#0E1A20';
const MVNO_SURFACE = '#152830';
const MVNO_FG = '#F1E9DA';
const MVNO_MUTED = 'rgba(241,233,218,.65)';
const MVNO_DIM = 'rgba(241,233,218,.42)';
const MVNO_LINE = 'rgba(241,233,218,.12)';

// ── Atomic helpers ───────────────────────────────────────────────────────────
const Eyebrow = ({ children, color = MVNO_DIM }) =>
<div style={{
  fontFamily: 'DM Sans, sans-serif', fontSize: 11,
  letterSpacing: '.22em', textTransform: 'uppercase',
  color, fontWeight: 600
}}>{children}</div>;


const Heading = ({ children, size = 48, weight = 500, color = MVNO_FG, italic = false, style }) =>
<h2 style={{
  fontFamily: 'Libre Bodoni, serif',
  fontWeight: weight,
  fontSize: size,
  lineHeight: 1.05,
  letterSpacing: '-.02em',
  color,
  margin: 0,
  textWrap: 'balance',
  fontStyle: italic ? 'italic' : 'normal',
  ...style
}}>{children}</h2>;


const Body = ({ children, size = 16, color = MVNO_FG, opacity = .85, maxWidth = 640, style }) =>
<p style={{
  fontFamily: 'DM Sans, sans-serif',
  fontSize: size, lineHeight: 1.6,
  color, opacity, margin: 0,
  maxWidth, textWrap: 'pretty',
  ...style
}}>{children}</p>;


const Divider = ({ style }) =>
<div style={{ height: 1, background: MVNO_LINE, ...style }} />;


// ── Reveal — IntersectionObserver-driven entrance animation ─────────────────
// Wraps any block. Adds .reveal-on / .reveal-off depending on visibility;
// CSS at the bottom of the modal styles the transition.
const Reveal = ({ children, delay = 0, dir = 'up', style }) => {
  const ref = React.useRef(null);
  const [shown, setShown] = React.useState(false);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return undefined;
    if (typeof IntersectionObserver === 'undefined') {setShown(true);return undefined;}
    const io = new IntersectionObserver(
      (entries) => entries.forEach((e) => {if (e.isIntersecting) {setShown(true);io.disconnect();}}),
      { threshold: 0.12, rootMargin: '0px 0px -8% 0px' }
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);
  const offsets = { up: 'translateY(28px)', down: 'translateY(-22px)', left: 'translateX(-22px)', right: 'translateX(22px)', none: 'none' };
  return (
    <div ref={ref} style={{
      opacity: shown ? 1 : 0,
      transform: shown ? 'translate(0,0)' : offsets[dir] || offsets.up,
      transition: `opacity .8s cubic-bezier(.2,.7,.2,1) ${delay}ms, transform .9s cubic-bezier(.2,.7,.2,1) ${delay}ms`,
      ...style
    }}>{children}</div>);

};


// Bookkeeping wrapper — keeps section paddings consistent. Use side props
// to override per-section if a layout needs to break the rhythm.
const Section = ({ children, py = 96, bg, style }) =>
<section style={{
  padding: `${py}px max(56px, 5vw)`,
  background: bg,
  ...style
}}>{children}</section>;


// ── Hero ────────────────────────────────────────────────────────────────────
// Title + meta sit on top, full-width. The hero phone lives BELOW the text,
// with a tilt-on-mount + subtle mouse parallax. An animated gradient mesh
// pulses behind the phone for that hint-of-AI feel.
const HeroSection = () => {
  const wrapRef = React.useRef(null);
  const [parallax, setParallax] = React.useState({ x: 0, y: 0 });
  const [mounted, setMounted] = React.useState(false);
  React.useEffect(() => {
    const id = requestAnimationFrame(() => setMounted(true));
    return () => cancelAnimationFrame(id);
  }, []);
  const onMove = (e) => {
    const r = wrapRef.current?.getBoundingClientRect();
    if (!r) return;
    const x = ((e.clientX - r.left) / r.width - 0.5) * 2;
    const y = ((e.clientY - r.top) / r.height - 0.5) * 2;
    setParallax({ x, y });
  };
  const onLeave = () => setParallax({ x: 0, y: 0 });

  return (
    <Section py={0} style={{ position: 'relative', paddingTop: 100, paddingBottom: 80 }}>
      {/* AI-ish gradient mesh behind everything */}
      <div className="mvno-mesh" style={{
        position: 'absolute', inset: '8% -10% -10% -10%',
        pointerEvents: 'none', opacity: .9,
        background: `
          radial-gradient(50% 40% at 22% 30%, ${MVNO_ACCENT}24 0%, transparent 70%),
          radial-gradient(40% 35% at 78% 18%, #8875FF22 0%, transparent 70%),
          radial-gradient(60% 45% at 55% 85%, ${MVNO_ACCENT}1c 0%, transparent 65%)
        `,
        filter: 'blur(40px)'
      }} />

      <div style={{ position: 'relative', display: 'flex', flexDirection: 'column', gap: 48, alignItems: 'flex-start' }}>
        {/* Eyebrow row */}
        <Reveal delay={50}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
            <Eyebrow color={MVNO_ACCENT}>Case · 01</Eyebrow>
            <span style={{ width: 32, height: 1, background: MVNO_LINE }} />
            <Eyebrow>Telecom · MVNO</Eyebrow>
          </div>
        </Reveal>

        {/* Title */}
        <Reveal delay={120}>
          <Heading size={'clamp(64px, 8vw, 144px)'} weight={500}>
            MVNO{' '}
            <span style={{ fontStyle: 'italic', color: MVNO_ACCENT }}>Mobile App</span>
          </Heading>
        </Reveal>

        {/* Lede + meta */}
        <Reveal delay={220}>
          <div style={{ display: 'grid', gridTemplateColumns: '1.4fr 1fr',
            gap: 'clamp(40px, 5vw, 80px)', alignItems: 'start',
            width: '100%', maxWidth: 1080 }}>
            <Body size={20} maxWidth={600}>
              A self-service mobile concept for a Polish mobile virtual network operator —
              designed from zero on top of an existing API. Users pick a number, port one
              in, and get up and running in minutes.
            </Body>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, auto)', gap: '16px 48px' }}>
              {[
              ['Role', 'UX/UI Designer'],
              ['Type', 'Mobile app · concept'],
              ['Scope', 'UX → Design System'],
              ['Status', 'MVP · concept']].
              map(([k, v]) =>
              <div key={k} style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
                  <Eyebrow>{k}</Eyebrow>
                  <div style={{ fontFamily: 'DM Sans, sans-serif', fontSize: 15, color: MVNO_FG }}>{v}</div>
                </div>
              )}
            </div>
          </div>
        </Reveal>

        {/* Hero phone — full width below text, with mouse parallax */}
        <div ref={wrapRef} onMouseMove={onMove} onMouseLeave={onLeave}
        style={{
          width: '100%', display: 'flex', justifyContent: 'center',
          marginTop: 32, position: 'relative'
        }}>
          {/* glow plate */}
          <div style={{
            position: 'absolute', left: '50%', top: '50%',
            width: 480, height: 480, transform: 'translate(-50%, -45%)',
            background: `radial-gradient(circle, ${MVNO_ACCENT}26 0%, ${MVNO_ACCENT}10 35%, transparent 70%)`,
            filter: 'blur(30px)', pointerEvents: 'none'
          }} />
          <img src="case-assets/mvno/hero.png"
          alt="MVNO app — Create your account screen"
          style={{
            position: 'relative', width: '100%', maxWidth: 360, height: 'auto',
            filter: 'drop-shadow(0 30px 60px rgba(0,0,0,.5))',
            transform: `
                   translate(${parallax.x * 18}px, ${parallax.y * 12}px)
                   rotate(${mounted ? 0 : -6}deg)
                   scale(${mounted ? 1 : 0.94})
                 `,
            opacity: mounted ? 1 : 0,
            transition: `
                   transform ${mounted ? '.25s ease-out' : '1.4s cubic-bezier(.2,.7,.2,1)'},
                   opacity 1s ease ${mounted ? '0s' : '.1s'}
                 `,
            willChange: 'transform'
          }} />
        </div>
      </div>
    </Section>);

};


// ── Challenge ───────────────────────────────────────────────────────────────
const ChallengeSection = () =>
<Section style={{ borderTop: `1px solid ${MVNO_LINE}` }}>
    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1.4fr', gap: 'clamp(48px, 6vw, 120px)',
    alignItems: 'start', maxWidth: 1280 }}>
      <Reveal dir="left">
        <div>
          <Eyebrow>The challenge</Eyebrow>
          <Heading size={'clamp(36px, 3.8vw, 56px)'} style={{ marginTop: 14 }}>
            A blank slate, bound by <span style={{ color: MVNO_ACCENT, fontStyle: 'italic' }}>regulation</span> and an API.
          </Heading>
        </div>
      </Reveal>
      <Reveal dir="right" delay={120}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 22 }}>
          <Body size={18}>
            The client wanted a simple app that lets users pick a new mobile number, activate
            it, or port one in — fast. No product yet, only a brief, an API and Polish telecom
            regulations to design around.
          </Body>
          <Body size={18}>
            I owned the journey end-to-end: competitive benchmarking, user research,
            information architecture, low- and high-fidelity prototypes, and the Design System
            we handed over to the team.
          </Body>
        </div>
      </Reveal>
    </div>
  </Section>;


// ── Responsibilities ────────────────────────────────────────────────────────
const ResponsibilitiesSection = () => {
  const items = [
  ['01', 'Research & benchmarks', 'Competitive analysis of MVNO and prepaid apps across PL and EU. User interviews and problem framing.'],
  ['02', 'IA & user flows', 'Sitemap, sign-up + porting flow, dashboard hierarchy. Mapped against the API and legal constraints.'],
  ['03', 'Prototypes', 'Low- and high-fidelity flows in Figma. Iterated with stakeholders and validated key paths.'],
  ['04', 'Design System', 'Tokens, components, motion principles, onboarding patterns. Handed over to dev with documentation.'],
  ['05', 'Workshops & mentoring', 'Facilitated design workshops with stakeholders. Mentored junior UX/UI designers on the team.']];

  return (
    <Section bg={MVNO_SURFACE} style={{ borderTop: `1px solid ${MVNO_LINE}`, borderBottom: `1px solid ${MVNO_LINE}` }}>
      <div style={{ maxWidth: 1280 }}>
        <Reveal>
          <Eyebrow>My role · responsibilities</Eyebrow>
          <Heading size={'clamp(36px, 3.8vw, 56px)'} style={{ marginTop: 14, marginBottom: 56 }}>
            What I owned end-to-end.
          </Heading>
        </Reveal>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: 32 }}>
          {items.map(([n, title, desc], i) =>
          <Reveal key={n} delay={i * 70}>
            <div style={{
              display: 'flex', flexDirection: 'column', gap: 12,
              paddingTop: 24, borderTop: `1px solid ${MVNO_LINE}`
            }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
                <span style={{ fontFamily: 'Libre Bodoni, serif', fontSize: 22, color: MVNO_ACCENT,
                  fontWeight: 500, fontStyle: 'italic' }}>{n}</span>
              </div>
              <div style={{ fontFamily: 'Libre Bodoni, serif', fontSize: 22, color: MVNO_FG,
                fontWeight: 500, lineHeight: 1.15, letterSpacing: '-.01em' }}>{title}</div>
              <Body size={14.5} opacity={.7} maxWidth={320}>{desc}</Body>
            </div>
          </Reveal>
          )}
        </div>
      </div>
    </Section>);

};

// ── Benchmarks ─────────────────────────────────────────────────────────────
// Three-column grid: Orange Flex · Heyah · Revolut. Each cell with a finding
// shows tiny abstract phone thumbnails + a pulsing colored dot. Hover the dot
// for a one-liner, click for the full finding card (01–08).
const COMPETITORS = [
{ id: 'orange', name: 'Orange Flex', color: '#FF7A00', tagline: 'Hybrid prepaid + contract, app-first.' },
{ id: 'heyah', name: 'Heyah', color: '#E2126D', tagline: 'Tone-of-voice driven, low friction.' },
{ id: 'revolut', name: 'Revolut', color: '#8875FF', tagline: 'Fintech onboarding adapted for telco.' }];


const FINDINGS = [
{ id: '01', comp: 'orange', topic: 'Know to How', desc: 'Accessible "Sign up / Sign in" buttons on every screen — usually after logout.' },
{ id: '02', comp: 'orange', topic: 'Sign up', desc: 'Lets the user pick an offer from a few plans before signing up.' },
{ id: '03', comp: 'orange', topic: 'Navigation', desc: 'Top bar with title + notifications, bottom tabs: Start · SIM · Shop · Help · Profile.' },
{ id: '04', comp: 'revolut', topic: 'Know to How', desc: 'Sign-up / Sign-in available on every onboarding card.' },
{ id: '05', comp: 'heyah', topic: 'Sign up', desc: 'Needs SIM (number + PUK). Agreements come first; second step explains how to register the SIM.' },
{ id: '06', comp: 'heyah', topic: 'Navigation', desc: 'Top bar with title; bottom tabs: Overview · Top up · Help · More — "More" unintuitive.' },
{ id: '07', comp: 'revolut', topic: 'Sign up', desc: 'Phone + OTP autofill. Long form — if the user breaks it, registration starts over.' },
{ id: '08', comp: 'revolut', topic: 'Navigation', desc: 'Top: profile/title/notifications; bottom: Home · Transfer · Hub. Profile takes effort to find.' }];


const TOPICS = ['Splash screen', 'Know to How', 'Sign up', 'Navigation'];

// Mini-thumbnails — abstract phone strips drawn with CSS, tinted per competitor
const PhoneStrip = ({ tint, count = 5 }) =>
<div style={{ display: 'flex', gap: 4, alignItems: 'flex-end' }}>
    {Array.from({ length: count }).map((_, i) =>
  <div key={i} style={{
    width: 14, height: 28, borderRadius: 3,
    background: tint,
    opacity: .25 + i % 3 * 0.18
  }} />
  )}
  </div>;


const BenchmarkTooltip = ({ f, comp }) =>
<div style={{
  position: 'absolute', left: '50%', bottom: 'calc(100% + 18px)',
  transform: 'translateX(-50%)',
  width: 'min(260px, 80vw)',
  padding: '14px 18px',
  borderRadius: 12,
  background: '#0a1418', border: `1px solid ${comp.color}55`,
  boxShadow: `0 18px 40px -10px rgba(0,0,0,.55), 0 0 24px ${comp.color}26`,
  pointerEvents: 'none', zIndex: 20,
  animation: 'bmkTooltipIn .2s ease-out both'
}}>
    <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 8 }}>
      <span style={{ width: 7, height: 7, borderRadius: '50%', background: comp.color,
      boxShadow: `0 0 10px ${comp.color}` }} />
      <span style={{ fontFamily: 'DM Sans, sans-serif', fontSize: 10,
      letterSpacing: '.18em', textTransform: 'uppercase', fontWeight: 600,
      color: MVNO_MUTED }}>{comp.name} · {f.id}</span>
    </div>
    <div style={{ fontFamily: 'Libre Bodoni, serif', fontSize: 16, fontWeight: 500,
    color: MVNO_FG, marginBottom: 6, lineHeight: 1.2 }}>{f.topic}</div>
    <div style={{ fontFamily: 'DM Sans, sans-serif', fontSize: 12.5, color: MVNO_MUTED,
    lineHeight: 1.5 }}>{f.desc}</div>
    <div style={{ marginTop: 10, fontFamily: 'DM Sans, sans-serif', fontSize: 9.5,
    letterSpacing: '.18em', textTransform: 'uppercase',
    color: comp.color, fontWeight: 600 }}>
      Click for details →
    </div>
    {/* Arrow */}
    <div style={{
    position: 'absolute', left: '50%', top: '100%', transform: 'translateX(-50%)',
    width: 0, height: 0, borderLeft: '7px solid transparent',
    borderRight: '7px solid transparent',
    borderTop: `7px solid #0a1418`
  }} />
  </div>;


const BenchmarkCell = ({ topic, comp, finding, hovered, setHovered, onOpen }) => {
  const isHover = hovered === finding?.id;
  return (
    <div style={{
      position: 'relative',
      padding: '20px 18px 22px',
      borderTop: `1px solid ${MVNO_LINE}`,
      minHeight: 96
    }}>
      <div style={{
        fontFamily: 'DM Sans, sans-serif', fontSize: 10,
        letterSpacing: '.18em', textTransform: 'uppercase',
        color: MVNO_DIM, fontWeight: 600, marginBottom: 12
      }}>{topic}</div>

      {finding ?
      <div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
          <PhoneStrip tint={comp.color} count={topic === 'Sign up' ? 3 : 5} />
          {/* Hotspot dot */}
          <button
          onMouseEnter={() => setHovered(finding.id)}
          onMouseLeave={() => setHovered(null)}
          onClick={() => onOpen(finding)}
          aria-label={`Open benchmark ${finding.id} — ${comp.name} ${topic}`}
          style={{
            position: 'relative',
            appearance: 'none', border: 'none', padding: 0,
            width: 32, height: 32, borderRadius: '50%',
            cursor: 'pointer',
            background: `radial-gradient(circle at 30% 30%, ${comp.color}, ${comp.color}cc)`,
            boxShadow: `0 0 0 1px rgba(255,255,255,.15), 0 0 16px ${comp.color}88`,
            animation: `bmkPulseDot 2.2s ease-in-out infinite`,
            animationDelay: `${parseInt(finding.id, 10) % 4 * 0.25}s`,
            transition: 'transform .2s ease',
            transform: isHover ? 'scale(1.18)' : 'scale(1)'
          }}>
            <span style={{
            position: 'absolute', inset: 0, display: 'flex',
            alignItems: 'center', justifyContent: 'center',
            fontFamily: 'DM Sans, sans-serif', fontSize: 10, fontWeight: 700,
            color: 'rgba(255,255,255,.95)', textShadow: '0 1px 2px rgba(0,0,0,.3)',
            pointerEvents: 'none'
          }}>{finding.id}</span>
          </button>
          {isHover && <BenchmarkTooltip f={finding} comp={comp} />}
        </div> :

      <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <PhoneStrip tint={comp.color} count={1} />
          <span style={{ fontFamily: 'DM Sans, sans-serif', fontSize: 11,
          color: MVNO_DIM, fontStyle: 'italic' }}>—</span>
        </div>
      }
    </div>);

};

const BenchmarkFindingModal = ({ open, finding, comp, onClose }) => {
  React.useEffect(() => {
    if (!open) return undefined;
    const onKey = (e) => {if (e.key === 'Escape') onClose();};
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [open, onClose]);

  // Portal so the modal isn't positioned relative to the case-modal's
  // transformed sheet — fixed-positioning inside a transform parent gets
  // clamped to that parent, which pinned the dialog to the section instead
  // of the viewport.
  if (typeof document === 'undefined') return null;

  const node = (
    <div aria-hidden={!open}
    onClick={onClose}
    style={{
      position: 'fixed', inset: 0, zIndex: 9000,
      pointerEvents: open ? 'auto' : 'none',
      background: 'rgba(0,0,0,.72)',
      opacity: open ? 1 : 0,
      transition: 'opacity .3s ease',
      backdropFilter: 'blur(6px)', WebkitBackdropFilter: 'blur(6px)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      padding: 'clamp(24px, 5vw, 64px)'
    }}>
      {open && finding && comp &&
      <div onClick={(e) => e.stopPropagation()}
      style={{
        width: 'min(1080px, 100%)', maxHeight: '90vh', overflowY: 'auto',
        borderRadius: 18, padding: 'clamp(24px, 3vw, 40px)',
        background: '#0a1418',
        border: `1px solid ${comp.color}40`,
        boxShadow: `0 0 0 1px rgba(255,255,255,.04), 0 40px 80px rgba(0,0,0,.55), 0 0 80px ${comp.color}1a`,
        transform: open ? 'scale(1) translateY(0)' : 'scale(.96) translateY(12px)',
        transition: 'transform .35s cubic-bezier(.2,.7,.2,1)'
      }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start',
          gap: 24, marginBottom: 24 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
              <span style={{
              width: 44, height: 44, borderRadius: '50%',
              background: `radial-gradient(circle at 30% 30%, ${comp.color}, ${comp.color}aa)`,
              boxShadow: `0 0 20px ${comp.color}66`,
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              fontFamily: 'DM Sans, sans-serif',
              color: '#fff', fontSize: 14, fontWeight: 700
            }}>{finding.id}</span>
              <div>
                <Eyebrow color={comp.color}>Bench {finding.id} · {comp.name}</Eyebrow>
                <div style={{ fontFamily: 'Libre Bodoni, serif',
                fontSize: 'clamp(24px, 2.8vw, 36px)',
                fontWeight: 500, color: MVNO_FG, marginTop: 6,
                letterSpacing: '-.01em' }}>
                  {finding.topic}
                </div>
              </div>
            </div>
            <button onClick={onClose} aria-label="Close benchmark finding" style={{
            appearance: 'none', border: 'none', background: 'rgba(255,255,255,.05)',
            cursor: 'pointer', color: MVNO_FG, padding: 10, borderRadius: 999
          }}>
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true">
                <path d="M6 6l12 12M18 6L6 18" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" />
              </svg>
            </button>
          </div>

          <Body size={15} opacity={.8} maxWidth={760} style={{ marginBottom: 24 }}>
            {finding.desc}
          </Body>

          {/* The full finding card image */}
          <div style={{
          borderRadius: 14, overflow: 'hidden',
          background: '#000', border: `1px solid rgba(255,255,255,.06)`
        }}>
            <img src={`case-assets/mvno/bench-${finding.id}.png`}
          alt={`${comp.name} — ${finding.topic} benchmark`}
          style={{ display: 'block', width: '100%', height: 'auto' }} />
          </div>
        </div>
      }
    </div>);


  return ReactDOM.createPortal(node, document.body);
};

const BenchmarkSection = () => {
  const [hovered, setHovered] = React.useState(null);
  const [openFinding, setOpenFinding] = React.useState(null);

  const byCompTopic = (cId, t) =>
  FINDINGS.find((f) => f.comp === cId && f.topic === t);
  const compFor = (cId) => COMPETITORS.find((c) => c.id === cId);

  return (
    <Section style={{ borderTop: `1px solid ${MVNO_LINE}` }}>
      <div style={{ maxWidth: 1280 }}>
        <Reveal>
          <Eyebrow>Research</Eyebrow>
          <Heading size={'clamp(48px, 5.4vw, 84px)'} italic
          style={{ marginTop: 14, marginBottom: 24, lineHeight: 1 }}>
            Benchmarking<br />
            <span style={{ color: MVNO_ACCENT }}>3 competitors</span>
          </Heading>
          <Body size={17} opacity={.78} maxWidth={760} style={{ marginBottom: 56 }}>
            I analysed three MVNO operators — evaluating sign-up flows, number selection UX,
            onboarding patterns and navigation models. Key insights shaped the IA and reduced
            friction in our own flow.
          </Body>
        </Reveal>

        <Reveal delay={120}>
          <div style={{
            position: 'relative',
            borderRadius: 18,
            background: `
              radial-gradient(circle at 18% 22%, rgba(255,122,0,.08) 0%, transparent 45%),
              radial-gradient(circle at 50% 70%, rgba(226,18,109,.08) 0%, transparent 45%),
              radial-gradient(circle at 82% 28%, rgba(136,117,255,.08) 0%, transparent 45%),
              linear-gradient(180deg, ${MVNO_BG}, #0a1418)
            `,
            border: `1px solid ${MVNO_LINE}`,
            padding: 'clamp(24px, 3vw, 40px)',
            overflow: 'hidden'
          }}>
            {/* Hint */}
            <div style={{
              display: 'flex', alignItems: 'center', gap: 10,
              marginBottom: 24,
              fontFamily: 'DM Sans, sans-serif', fontSize: 10,
              letterSpacing: '.18em', textTransform: 'uppercase',
              color: MVNO_DIM, fontWeight: 600
            }}>
              <span style={{
                width: 6, height: 6, borderRadius: '50%',
                background: MVNO_ACCENT,
                animation: 'bmkPulse 2s ease-in-out infinite'
              }} />
              hover the dots · click to inspect
            </div>

            {/* 3-column grid */}
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 24 }}>
              {COMPETITORS.map((c) =>
              <div key={c.id} style={{
                background: 'rgba(0,0,0,.18)', borderRadius: 14,
                border: `1px solid ${c.color}22`,
                overflow: 'hidden'
              }}>
                  <div style={{
                  padding: '24px 22px 18px',
                  borderBottom: `1px solid ${MVNO_LINE}`
                }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 6 }}>
                      <span style={{ width: 9, height: 9, borderRadius: '50%', background: c.color,
                      boxShadow: `0 0 12px ${c.color}` }} />
                      <span style={{ fontFamily: 'Libre Bodoni, serif', fontSize: 24,
                      fontWeight: 500, color: MVNO_FG, letterSpacing: '-.01em' }}>
                        {c.name}
                      </span>
                    </div>
                    <div style={{ fontFamily: 'DM Sans, sans-serif', fontSize: 12,
                    color: MVNO_MUTED }}>{c.tagline}</div>
                  </div>
                  {TOPICS.map((t) =>
                <BenchmarkCell key={t} topic={t} comp={c}
                finding={byCompTopic(c.id, t)}
                hovered={hovered} setHovered={setHovered}
                onOpen={setOpenFinding} />
                )}
                </div>
              )}
            </div>

            <style>{`
              @keyframes bmkPulse {
                0%, 100% { opacity: 1; transform: scale(1); }
                50%      { opacity: .4; transform: scale(1.4); }
              }
              @keyframes bmkTooltipIn {
                from { opacity: 0; transform: translateX(-50%) translateY(4px); }
                to   { opacity: 1; transform: translateX(-50%) translateY(0); }
              }
              @keyframes bmkPulseDot {
                0%, 100% { box-shadow: 0 0 0 1px rgba(255,255,255,.15), 0 0 16px currentColor; transform: scale(1); }
                50%      { box-shadow: 0 0 0 1px rgba(255,255,255,.15), 0 0 28px currentColor; transform: scale(1.08); }
              }
            `}</style>
          </div>
        </Reveal>
      </div>

      <BenchmarkFindingModal open={!!openFinding}
      finding={openFinding}
      comp={openFinding ? compFor(openFinding.comp) : null}
      onClose={() => setOpenFinding(null)} />
    </Section>);

};
const SignupSection = () =>
<Section>
    <div style={{ maxWidth: 1280 }}>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'clamp(48px, 5vw, 100px)', alignItems: 'center' }}>
        <Reveal dir="left">
          <div style={{ display: 'flex', flexDirection: 'column', gap: 28 }}>
            <Eyebrow>Sign-up · number selection</Eyebrow>
            <Heading size={'clamp(36px, 3.8vw, 56px)'}>
              Pick a number, or <span style={{ fontStyle: 'italic', color: MVNO_ACCENT }}>bring your own.</span>
            </Heading>
            <Body size={17}>
              The first decision in the app. We split it into two clear actions —
              port an existing number, or pick a fresh one from a refreshable list. A 300-second
              timer keeps the inventory honest; the green underline signals selection without a
              heavy active state.
            </Body>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 14, marginTop: 8 }}>
              {[
            'Two-path entry — port-in vs new number',
            'Inventory refresh every 5 minutes',
            'OTP autofill on Android',
            'Tight 7-step funnel from brief to active SIM'].
            map((t) =>
            <div key={t} style={{ display: 'flex', gap: 14, alignItems: 'flex-start' }}>
                  <span style={{ width: 6, height: 6, borderRadius: '50%', background: MVNO_ACCENT, marginTop: 9, flexShrink: 0 }} />
                  <Body size={15} opacity={.78}>{t}</Body>
                </div>
            )}
            </div>
          </div>
        </Reveal>
        <Reveal dir="right" delay={140}>
          <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', position: 'relative', minHeight: 480 }}>
            <div style={{
              position: 'absolute', inset: '5% 20%',
              background: `radial-gradient(circle, ${MVNO_ACCENT}18 0%, transparent 65%)`,
              filter: 'blur(20px)', pointerEvents: 'none'
            }} />
            {/* Animated countdown ring sitting behind the phone — telecom-y "300s timer" feel */}
            <svg width="320" height="320" viewBox="0 0 320 320" style={{
              position: 'absolute', left: '50%', top: '50%', transform: 'translate(-50%, -50%)',
              opacity: .55, pointerEvents: 'none',
            }}>
              <circle cx="160" cy="160" r="150" fill="none"
                      stroke={MVNO_LINE} strokeWidth="1" />
              <circle cx="160" cy="160" r="150" fill="none"
                      stroke={MVNO_ACCENT} strokeWidth="1.5"
                      strokeDasharray="6 12" strokeLinecap="round"
                      transform="rotate(-90 160 160)"
                      style={{ animation: 'mvnoCountdownRing 12s linear infinite' }} />
            </svg>

            {/* The phone */}
            <img src="case-assets/mvno/signup-numbers.png" alt="Create your account — number selection"
              style={{ position: 'relative', width: '100%', maxWidth: 240, height: 'auto',
                borderRadius: 28, boxShadow: '0 30px 70px -20px rgba(0,0,0,.6), 0 0 0 1px rgba(255,255,255,.06)' }} />

            {/* Annotation callouts — pinning real UX decisions to the phone */}
            {[
              { x: -14, y: 8,   label: '300s', sub: 'inventory timer', side: 'left' },
              { x: 110, y: 36,  label: 'Mint underline', sub: 'subtle selection state', side: 'right' },
              { x: -22, y: 76,  label: 'Refresh →', sub: 'pull new pool', side: 'left' },
            ].map((c, i) => (
              <div key={i} style={{
                position: 'absolute',
                left: c.side === 'right' ? `calc(50% + ${c.x}px)` : 'auto',
                right: c.side === 'left' ? `calc(50% + ${-c.x}px)` : 'auto',
                top: `${c.y}%`,
                display: 'flex', flexDirection: 'column', gap: 4,
                animation: `signupCallout 4s ease-in-out infinite`,
                animationDelay: `${i * 0.4}s`,
              }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8,
                                flexDirection: c.side === 'left' ? 'row-reverse' : 'row' }}>
                  <span style={{ width: 8, height: 8, borderRadius: '50%', background: MVNO_ACCENT,
                                   boxShadow: `0 0 10px ${MVNO_ACCENT}` }} />
                  <span style={{ width: 32, height: 1, background: `linear-gradient(to ${c.side === 'left' ? 'left' : 'right'}, ${MVNO_ACCENT}, transparent)` }} />
                </div>
                <div style={{
                  fontFamily: 'Libre Bodoni, serif', fontSize: 16, fontStyle: 'italic',
                  color: MVNO_ACCENT, lineHeight: 1, fontWeight: 500,
                  textAlign: c.side === 'left' ? 'right' : 'left',
                }}>{c.label}</div>
                <div style={{
                  fontFamily: 'DM Sans, sans-serif', fontSize: 10,
                  letterSpacing: '.14em', textTransform: 'uppercase',
                  color: MVNO_MUTED, fontWeight: 600,
                  textAlign: c.side === 'left' ? 'right' : 'left',
                }}>{c.sub}</div>
              </div>
            ))}
          </div>
        </Reveal>
      </div>
    </div>
  </Section>;


// ── Key screens ─────────────────────────────────────────────────────────────
const ScreensSection = () => {
  const screens = [
  { src: 'case-assets/mvno/dashboard.png', label: 'Dashboard', sub: 'Local data, wallet & active services in one glance.' },
  { src: 'case-assets/mvno/wallet-main.png', label: 'Wallet', sub: 'Top up, withdraw, manage international roaming.' },
  { src: 'case-assets/mvno/wallet-topup.png', label: 'Top up', sub: 'Three preset amounts; one-tap recharge.' },
  { src: 'case-assets/mvno/profile.png', label: 'Profile', sub: 'Account, language, biometry, payment, privacy.' }];

  return (
    <Section bg={MVNO_SURFACE} style={{ borderTop: `1px solid ${MVNO_LINE}`, borderBottom: `1px solid ${MVNO_LINE}` }}>
      <div style={{ maxWidth: 1280 }}>
        <Reveal>
          <Eyebrow>The product</Eyebrow>
          <Heading size={'clamp(36px, 3.8vw, 56px)'} style={{ marginTop: 14, marginBottom: 56, maxWidth: 720 }}>
            A four-tab core — built for the <span style={{ fontStyle: 'italic', color: MVNO_ACCENT }}>under-30-seconds</span> use case.
          </Heading>
        </Reveal>
        <div style={{
          display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 32,
          alignItems: 'start'
        }}>
          {screens.map((s, i) =>
          <Reveal key={i} delay={i * 90}>
            <figure style={{
              margin: 0,
              display: 'grid',
              gridTemplateRows: 'auto auto auto',
              gap: 14,
              alignItems: 'start'
            }}>
              <figcaption style={{
                fontFamily: 'Libre Bodoni, serif', fontSize: 22, color: MVNO_FG,
                fontWeight: 500, letterSpacing: '-.01em', lineHeight: 1.1,
                minHeight: 28
              }}>{s.label}</figcaption>
              <div style={{
                fontFamily: 'DM Sans, sans-serif', fontSize: 13.5, color: MVNO_FG,
                opacity: .6, lineHeight: 1.55,
                minHeight: 42
              }}>{s.sub}</div>
              <div style={{
                background: '#000', borderRadius: 32, padding: 6,
                boxShadow: '0 24px 60px -16px rgba(0,0,0,.55), 0 0 0 1px rgba(255,255,255,.05)',
                marginTop: 8
              }}>
                <img src={s.src} alt={`${s.label} screen`}
                style={{ width: '100%', height: 'auto', display: 'block', borderRadius: 26 }} />
              </div>
            </figure>
          </Reveal>
          )}
        </div>
      </div>
    </Section>);

};

// ── Information architecture ─────────────────────────────────────────────────
// The user-flow image auto-scrolls horizontally forever. On hover, it pauses
// and a magnifier lens follows the cursor, revealing a 5× zoomed crop.
// A stats bar + live "what's on screen" indicator keep it engaging while it loops.
const SitemapSection = () => {
  const lensRef = React.useRef(null);
  const wrapRef = React.useRef(null);
  const trackRef = React.useRef(null);
  const [hovered, setHovered] = React.useState(false);
  const [lens, setLens] = React.useState({ x: 0, y: 0, visible: false });

  const FLOW_HEIGHT = 320;
  const FLOW_AR = 14049 / 2739;
  const flowDisplayWidth = FLOW_HEIGHT * FLOW_AR;
  const ZOOM = 5; // magnifier power
  const LENS = 220; // lens diameter

  const onMove = (e) => {
    const wrap = wrapRef.current;
    const track = trackRef.current;
    if (!wrap || !track) return;
    const wr = wrap.getBoundingClientRect();
    const lx = e.clientX - wr.left;
    const ly = e.clientY - wr.top;
    const tr = track.getBoundingClientRect();
    const imgX = e.clientX - tr.left;
    setLens({ x: lx, y: ly, imgX, imgY: ly, visible: true });
  };

  // Live step indicator — what segment of the flow is visible right now.
  // The marquee scrolls one full image-width every (flowDisplayWidth/80)s, so
  // pick a step out of N based on time modulo that period. Pauses on hover.
  const STEPS = 24;
  const PERIOD_MS = (flowDisplayWidth / 80) * 1000;
  const [step, setStep] = React.useState(1);
  React.useEffect(() => {
    if (hovered) return undefined;
    let raf;
    const start = performance.now();
    const tick = (t) => {
      const phase = ((t - start) % PERIOD_MS) / PERIOD_MS;
      setStep(Math.floor(phase * STEPS) + 1);
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [hovered, PERIOD_MS]);

  const stats = [
    ['24', 'Steps'],
    ['02', 'Paths · Personal · Business'],
    ['07', 'Decision points'],
    ['00', 'Dead ends'],
  ];

  return (
    <Section>
      <div style={{ maxWidth: 1280 }}>
        <Reveal>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
            gap: 48, marginBottom: 36, flexWrap: 'wrap' }}>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 14, maxWidth: 520 }}>
              <Eyebrow>User flow</Eyebrow>
              <Heading size={'clamp(32px, 3.4vw, 48px)'}>
                One flow. Two account types. <span style={{ color: MVNO_ACCENT, fontStyle: 'italic' }}>Zero dead ends.</span>
              </Heading>
            </div>
            <Body size={15} opacity={.7} maxWidth={420}>
              The journey splits early between Personal and Business — every branch lands users
              on the dashboard with a known, recoverable state.
            </Body>
          </div>
        </Reveal>

        {/* Stats bar — engagement layer */}
        <Reveal delay={80}>
          <div style={{
            display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))',
            gap: 0, marginBottom: 28,
            border: `1px solid ${MVNO_LINE}`, borderRadius: 14,
            overflow: 'hidden', background: 'rgba(0,0,0,.18)',
          }}>
            {stats.map(([n, label], i) => (
              <div key={label} style={{
                padding: '20px 24px',
                borderRight: i < stats.length - 1 ? `1px solid ${MVNO_LINE}` : 'none',
                display: 'flex', flexDirection: 'column', gap: 6,
              }}>
                <div style={{
                  fontFamily: 'Libre Bodoni, serif', fontWeight: 500,
                  fontSize: 'clamp(28px, 3vw, 40px)', lineHeight: 1,
                  color: MVNO_ACCENT, letterSpacing: '-.02em',
                }}>{n}</div>
                <div style={{
                  fontFamily: 'DM Sans, sans-serif', fontSize: 11,
                  letterSpacing: '.16em', textTransform: 'uppercase',
                  color: MVNO_MUTED, fontWeight: 600,
                }}>{label}</div>
              </div>
            ))}
          </div>
        </Reveal>

        <Reveal delay={120}>
          <div ref={wrapRef}
            onMouseEnter={() => setHovered(true)}
            onMouseLeave={() => { setHovered(false); setLens((s) => ({ ...s, visible: false })); }}
            onMouseMove={onMove}
            style={{
              borderRadius: 18, overflow: 'hidden',
              border: `1px solid ${MVNO_LINE}`,
              background: '#1a1f22',
              position: 'relative',
              height: FLOW_HEIGHT,
              cursor: hovered ? 'none' : 'default',
            }}>
            {/* Live step indicator (top-left) */}
            <div style={{
              position: 'absolute', top: 14, left: 18, zIndex: 5,
              display: 'flex', alignItems: 'center', gap: 10,
              fontFamily: 'DM Sans, sans-serif', fontSize: 11,
              letterSpacing: '.16em', textTransform: 'uppercase',
              color: MVNO_FG, fontWeight: 600,
              background: 'rgba(0,0,0,.45)', padding: '8px 12px', borderRadius: 999,
              border: `1px solid ${MVNO_ACCENT}55`,
              backdropFilter: 'blur(8px)', WebkitBackdropFilter: 'blur(8px)',
            }}>
              <span style={{
                width: 7, height: 7, borderRadius: '50%', background: MVNO_ACCENT,
                boxShadow: `0 0 8px ${MVNO_ACCENT}`,
                animation: hovered ? 'none' : 'bmkPulse 1.4s ease-in-out infinite',
              }} />
              <span style={{ fontVariantNumeric: 'tabular-nums', color: MVNO_ACCENT }}>
                {String(hovered ? '—' : step).padStart(2, '0')}
              </span>
              <span style={{ color: MVNO_DIM }}>/</span>
              <span style={{ color: MVNO_DIM, fontVariantNumeric: 'tabular-nums' }}>{STEPS}</span>
              <span style={{ width: 1, height: 12, background: MVNO_LINE, margin: '0 4px' }} />
              <span style={{ color: hovered ? MVNO_ACCENT : MVNO_MUTED }}>
                {hovered ? 'Paused' : 'Live'}
              </span>
            </div>

            {/* Marquee track — two copies side by side */}
            <div ref={trackRef}
              style={{
                display: 'flex',
                width: flowDisplayWidth * 2,
                height: '100%',
                animation: `mvnoFlowMarquee ${flowDisplayWidth / 80}s linear infinite`,
                animationPlayState: hovered ? 'paused' : 'running',
                willChange: 'transform',
              }}>
              <img src="case-assets/mvno/user-flow.png"
                alt="MVNO sign-up user flow"
                style={{ display: 'block', height: '100%', width: flowDisplayWidth, flexShrink: 0,
                  userSelect: 'none', pointerEvents: 'none' }} />
              <img src="case-assets/mvno/user-flow.png" alt=""
                aria-hidden="true"
                style={{ display: 'block', height: '100%', width: flowDisplayWidth, flexShrink: 0,
                  userSelect: 'none', pointerEvents: 'none' }} />
            </div>

            {/* Fade hints both sides */}
            <div style={{
              position: 'absolute', left: 0, top: 0, bottom: 0, width: 60,
              background: 'linear-gradient(to left, transparent, #1a1f22)',
              pointerEvents: 'none',
            }} />
            <div style={{
              position: 'absolute', right: 0, top: 0, bottom: 0, width: 80,
              background: 'linear-gradient(to right, transparent, #1a1f22)',
              pointerEvents: 'none',
            }} />

            {/* Magnifier lens */}
            {hovered && lens.visible &&
              <div ref={lensRef}
                aria-hidden="true"
                style={{
                  position: 'absolute',
                  left: lens.x - LENS / 2, top: lens.y - LENS / 2,
                  width: LENS, height: LENS, borderRadius: '50%',
                  border: `1.5px solid ${MVNO_ACCENT}`,
                  boxShadow: `0 0 0 4px ${MVNO_ACCENT}22, 0 0 60px ${MVNO_ACCENT}33, 0 24px 50px -10px rgba(0,0,0,.6)`,
                  pointerEvents: 'none', zIndex: 4, overflow: 'hidden',
                }}>
                <img src="case-assets/mvno/user-flow.png"
                  alt=""
                  style={{
                    position: 'absolute',
                    height: FLOW_HEIGHT * ZOOM,
                    width: flowDisplayWidth * ZOOM,
                    left: -(lens.imgX * ZOOM) + LENS / 2,
                    top: -(lens.imgY * ZOOM) + LENS / 2,
                    maxWidth: 'none', pointerEvents: 'none',
                  }} />
                {/* Crosshair */}
                <div style={{ position: 'absolute', left: '50%', top: '50%',
                  width: 10, height: 10, marginLeft: -5, marginTop: -5,
                  borderRadius: '50%',
                  boxShadow: `0 0 0 2px ${MVNO_ACCENT}, 0 0 0 4px rgba(0,0,0,.5)` }} />
                {/* Zoom badge */}
                <div style={{
                  position: 'absolute', bottom: 10, left: '50%', transform: 'translateX(-50%)',
                  fontFamily: 'DM Sans, sans-serif', fontSize: 9, fontWeight: 700,
                  letterSpacing: '.18em', textTransform: 'uppercase',
                  color: '#0a1418', background: MVNO_ACCENT, padding: '3px 8px', borderRadius: 999,
                }}>{ZOOM}×</div>
              </div>
            }

            {/* Hover affordance */}
            <div style={{
              position: 'absolute', bottom: 14, right: 18,
              fontFamily: 'DM Sans, sans-serif', fontSize: 10,
              letterSpacing: '.18em', textTransform: 'uppercase',
              color: MVNO_DIM, pointerEvents: 'none',
              opacity: hovered ? 0 : 1, transition: 'opacity .25s ease',
              display: 'flex', alignItems: 'center', gap: 8,
            }}>
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none">
                <circle cx="10" cy="10" r="6" stroke="currentColor" strokeWidth="1.6" />
                <path d="M15 15l5 5" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" />
              </svg>
              Hover · {ZOOM}× zoom
            </div>
          </div>
        </Reveal>

        {/* Progress timeline below the strip */}
        <Reveal delay={180}>
          <div style={{ marginTop: 18, display: 'flex', alignItems: 'center', gap: 12 }}>
            <span style={{ fontFamily: 'DM Sans, sans-serif', fontSize: 10,
                            letterSpacing: '.18em', textTransform: 'uppercase',
                            color: MVNO_DIM, fontWeight: 600 }}>Start</span>
            <div style={{ flex: 1, position: 'relative', height: 2,
                            background: MVNO_LINE, borderRadius: 999 }}>
              <div style={{
                position: 'absolute', left: `${((step - 1) / (STEPS - 1)) * 100}%`,
                top: '50%', transform: 'translate(-50%, -50%)',
                width: 12, height: 12, borderRadius: '50%',
                background: MVNO_ACCENT,
                boxShadow: `0 0 12px ${MVNO_ACCENT}, 0 0 0 4px rgba(176,235,187,.18)`,
                transition: hovered ? 'left .2s ease' : 'none',
              }} />
              <div style={{
                position: 'absolute', left: 0, top: 0, bottom: 0,
                width: `${((step - 1) / (STEPS - 1)) * 100}%`,
                background: `linear-gradient(to right, transparent, ${MVNO_ACCENT})`,
                borderRadius: 999,
              }} />
            </div>
            <span style={{ fontFamily: 'DM Sans, sans-serif', fontSize: 10,
                            letterSpacing: '.18em', textTransform: 'uppercase',
                            color: MVNO_DIM, fontWeight: 600 }}>Dashboard</span>
          </div>
        </Reveal>
      </div>
    </Section>);

};


// ── Design system snippet ───────────────────────────────────────────────────
const SystemSection = () =>
<Section bg={MVNO_SURFACE} style={{ borderTop: `1px solid ${MVNO_LINE}` }}>
    <div style={{ maxWidth: 1280 }}>
      <Reveal>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
        gap: 48, marginBottom: 48, flexWrap: 'wrap' }}>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 14, maxWidth: 520 }}>
            <Eyebrow>Design system</Eyebrow>
            <Heading size={'clamp(32px, 3.4vw, 48px)'}>
              Tokens, components, <span style={{ fontStyle: 'italic', color: MVNO_ACCENT }}>motion.</span>
            </Heading>
          </div>
          <Body size={15} opacity={.7} maxWidth={420}>
            Three palettes (Core / Alternative / Neutral), four button families with full state
            coverage, and a motion language shared with the dev team.
          </Body>
        </div>
      </Reveal>
      <Reveal delay={120}>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1.4fr', gap: 32, alignItems: 'stretch' }}>
          <div style={{
          borderRadius: 18, overflow: 'hidden',
          border: `1px solid ${MVNO_LINE}`, background: MVNO_BG
        }}>
            <img src="case-assets/mvno/colors.png" alt="Color palette"
          style={{ width: '100%', height: 'auto', display: 'block' }} />
          </div>
          <div style={{
          borderRadius: 18, overflow: 'hidden',
          border: `1px solid ${MVNO_LINE}`, background: MVNO_BG
        }}>
            <img src="case-assets/mvno/buttons.png" alt="Button states"
          style={{ width: '100%', height: 'auto', display: 'block' }} />
          </div>
        </div>
      </Reveal>

      {/* Testimonial */}
      <Reveal delay={200}>
        <figure style={{
        margin: '64px 0 0', padding: 'clamp(40px, 5vw, 72px)',
        borderRadius: 18, border: `1px solid ${MVNO_LINE}`,
        background: `linear-gradient(135deg, ${MVNO_ACCENT}0e 0%, transparent 60%), ${MVNO_BG}`,
        display: 'grid', gridTemplateColumns: 'auto 1fr', gap: 'clamp(24px, 4vw, 56px)',
        alignItems: 'start'
      }}>
          {/* Big bodoni quote mark */}
          <span aria-hidden="true" style={{
          fontFamily: 'Libre Bodoni, serif', fontWeight: 500, fontStyle: 'italic',
          fontSize: 'clamp(96px, 12vw, 180px)', lineHeight: .8,
          color: MVNO_ACCENT, opacity: .9, letterSpacing: '-.05em',
          marginTop: -8
        }}>“</span>
          <blockquote style={{ margin: 0, display: 'flex', flexDirection: 'column', gap: 24 }}>
            <p style={{
            fontFamily: 'Libre Bodoni, serif', fontWeight: 400,
            fontSize: 'clamp(24px, 2.4vw, 34px)', lineHeight: 1.3,
            letterSpacing: '-.01em', margin: 0, color: MVNO_FG,
            textWrap: 'balance'
          }}>
              This is really great work — <span style={{ fontStyle: 'italic', color: MVNO_ACCENT }}>I’m impressed.</span>
            </p>
            <figcaption style={{ display: 'flex', alignItems: 'center', gap: 14,
            fontFamily: 'DM Sans, sans-serif', fontSize: 12,
            letterSpacing: '.18em', textTransform: 'uppercase',
            color: MVNO_MUTED, fontWeight: 600 }}>
              <span style={{ width: 28, height: 1, background: MVNO_LINE }} />
              <span>Design Strategy Lead</span>
              <span style={{ color: MVNO_DIM }}>·</span>
              <span style={{ color: MVNO_DIM }}>CX</span>
            </figcaption>
          </blockquote>
        </figure>
      </Reveal>
    </div>
  </Section>;


// ── Impact ──────────────────────────────────────────────────────────────────
const ImpactSection = () => {
  const stats = [
  ['−40%', 'UI inconsistencies', 'After rolling out the new Design System and motion principles.'],
  ['−20%', 'Flow errors', 'Across the sign-up and porting journeys, validated after launch.'],
  ['7 → 30s', 'From brief to activation', 'Onboarding comprehension rose; the median funnel time dropped.']];

  return (
    <Section style={{ borderTop: `1px solid ${MVNO_LINE}` }}>
      <div style={{ maxWidth: 1280 }}>
        <Reveal>
          <Eyebrow>Impact</Eyebrow>
          <Heading size={'clamp(36px, 3.8vw, 56px)'} style={{ marginTop: 14, marginBottom: 64, maxWidth: 720 }}>
            The numbers that <span style={{ fontStyle: 'italic', color: MVNO_ACCENT }}>made it to production.</span>
          </Heading>
        </Reveal>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: 48 }}>
          {stats.map(([n, label, desc], i) =>
          <Reveal key={label} delay={i * 110}>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 14,
              paddingTop: 24, borderTop: `1px solid ${MVNO_LINE}` }}>
              <div style={{
                fontFamily: 'Libre Bodoni, serif', fontWeight: 500,
                fontSize: 'clamp(56px, 6vw, 88px)', lineHeight: 1, color: MVNO_ACCENT,
                letterSpacing: '-.02em',
                textShadow: `0 0 32px ${MVNO_ACCENT}33`
              }}>{n}</div>
              <div style={{ fontFamily: 'DM Sans, sans-serif', fontSize: 14, color: MVNO_FG,
                letterSpacing: '.14em', textTransform: 'uppercase', fontWeight: 600 }}>{label}</div>
              <Body size={14.5} opacity={.7} maxWidth={280}>{desc}</Body>
            </div>
          </Reveal>
          )}
        </div>
      </div>
    </Section>);

};

// ── Modal shell ─────────────────────────────────────────────────────────────
const MVNOCaseStudy = ({ open, onClose }) => {
  // Scroll lock body while open
  React.useEffect(() => {
    if (!open) return undefined;
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => {document.body.style.overflow = prev;};
  }, [open]);

  // Close on Esc
  React.useEffect(() => {
    if (!open) return undefined;
    const onKey = (e) => {if (e.key === 'Escape') onClose();};
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [open, onClose]);

  // Track scroll progress inside the modal for the top bar
  const scrollRef = React.useRef(null);
  const [progress, setProgress] = React.useState(0);
  const onScroll = (e) => {
    const el = e.currentTarget;
    const max = el.scrollHeight - el.clientHeight;
    setProgress(max > 0 ? el.scrollTop / max : 0);
  };

  // Reset scroll when (re)opening
  React.useEffect(() => {
    if (open && scrollRef.current) {
      scrollRef.current.scrollTop = 0;
      setProgress(0);
    }
  }, [open]);

  return (
    <div aria-hidden={!open}
    style={{
      position: 'fixed', inset: 0, zIndex: 100,
      pointerEvents: open ? 'auto' : 'none'
    }}>
      {/* Backdrop */}
      <div onClick={onClose} style={{
        position: 'absolute', inset: 0, background: 'rgba(0,0,0,.55)',
        opacity: open ? 1 : 0,
        transition: 'opacity .5s ease',
        backdropFilter: 'blur(4px)', WebkitBackdropFilter: 'blur(4px)'
      }} />

      {/* Sheet */}
      <div ref={scrollRef} onScroll={onScroll}
      style={{
        position: 'absolute', inset: 0,
        background: MVNO_BG, color: MVNO_FG,
        transform: open ? 'translateY(0)' : 'translateY(40px)',
        opacity: open ? 1 : 0,
        transition: 'transform .55s cubic-bezier(.5,0,.2,1), opacity .45s ease',
        overflowY: 'auto', overflowX: 'hidden',
        scrollbarColor: `${MVNO_ACCENT}66 transparent`
      }}>
        <style>{`
          @keyframes mvnoFlowMarquee {
            0%   { transform: translate3d(0, 0, 0); }
            100% { transform: translate3d(-50%, 0, 0); }
          }
          @keyframes mvnoMeshDrift {
            0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
            50%      { transform: translate3d(-20px, 12px, 0) scale(1.04); }
          }
          @keyframes mvnoCountdownRing {
            from { transform: rotate(-90deg); }
            to   { transform: rotate(270deg); }
          }
          @keyframes signupCallout {
            0%, 100% { opacity: .55; transform: translateY(0); }
            50%      { opacity: 1;   transform: translateY(-3px); }
          }
          .mvno-mesh { animation: mvnoMeshDrift 14s ease-in-out infinite; }
        `}</style>
        {/* Top progress + close bar */}
        <div style={{
          position: 'sticky', top: 0, zIndex: 50,
          background: `linear-gradient(${MVNO_BG} 70%, ${MVNO_BG}00)`,
          padding: '20px max(56px, 5vw)'
        }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <button onClick={onClose} style={{
              appearance: 'none', border: 'none', background: 'none', padding: 6,
              color: MVNO_FG, cursor: 'pointer',
              display: 'inline-flex', alignItems: 'center', gap: 12,
              fontFamily: 'DM Sans, sans-serif', fontSize: 12,
              letterSpacing: '.18em', textTransform: 'uppercase', fontWeight: 600
            }}>
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true">
                <path d="M19 12H5M11 6l-6 6 6 6" stroke="currentColor" strokeWidth="1.6"
                strokeLinecap="round" strokeLinejoin="round" />
              </svg>
              <span>Back to portfolio</span>
            </button>

            <div style={{ display: 'inline-flex', alignItems: 'center', gap: 18 }}>
              <Eyebrow color={MVNO_DIM}>Case 01 · MVNO</Eyebrow>
              <button onClick={onClose} aria-label="Close" style={{
                appearance: 'none', border: 'none', background: 'none', cursor: 'pointer',
                color: MVNO_FG, padding: 6, display: 'inline-flex'
              }}>
                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true">
                  <path d="M6 6l12 12M18 6L6 18" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" />
                </svg>
              </button>
            </div>
          </div>
          {/* Scroll progress bar */}
          <div style={{ position: 'relative', height: 1.5, marginTop: 16,
            background: MVNO_LINE, borderRadius: 999 }}>
            <div style={{
              position: 'absolute', left: 0, top: 0, bottom: 0,
              width: `${progress * 100}%`,
              background: MVNO_ACCENT, borderRadius: 999,
              transition: 'width .1s linear'
            }} />
          </div>
        </div>

        <HeroSection />
        <ChallengeSection />
        <ResponsibilitiesSection />
        <BenchmarkSection />
        <SitemapSection />
        <SignupSection />
        <ScreensSection />
        <SystemSection />
        <ImpactSection />

        {/* Footer */}
        <Section py={72} style={{ borderTop: `1px solid ${MVNO_LINE}` }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 32 }}>
            <div>
              <Eyebrow>End of case · 01 / 04</Eyebrow>
              <Heading size={32} style={{ marginTop: 8 }}>
                More work — coming soon.
              </Heading>
            </div>
            <button onClick={onClose} style={{
              appearance: 'none', border: `1px solid ${MVNO_LINE}`, background: 'transparent',
              color: MVNO_FG, padding: '18px 32px', borderRadius: 999, cursor: 'pointer',
              fontFamily: 'DM Sans, sans-serif', fontSize: 13,
              letterSpacing: '.18em', textTransform: 'uppercase', fontWeight: 600,
              display: 'inline-flex', alignItems: 'center', gap: 14
            }}>
              <span>Back to portfolio</span>
              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true">
                <path d="M19 12H5M11 6l-6 6 6 6" stroke="currentColor" strokeWidth="1.6"
                strokeLinecap="round" strokeLinejoin="round" />
              </svg>
            </button>
          </div>
        </Section>
      </div>
    </div>);

};

Object.assign(window, { MVNOCaseStudy });