// Pages.jsx — Work, About, Play, Contact pages. Shared by both directions.
const { useState: useStatePages, useEffect: useEffectPages, useRef: useRefPages } = React;
const useIsMobilePages = window.useIsMobile;

// ---------- Scroll reveal ----------
// Light fade + translateY animation triggered by IntersectionObserver.
// Honors prefers-reduced-motion. Applies once per element.
function Reveal({ children, delay = 0, y = 24, duration = 700, once = true, as: Tag = 'div', style, className, ...rest }) {
  const ref = useRefPages(null);
  const [visible, setVisible] = useStatePages(false);

  useEffectPages(() => {
    const el = ref.current;
    if (!el) return;
    const reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (reduce) { setVisible(true); return; }

    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting) {
          setVisible(true);
          if (once) io.unobserve(e.target);
        } else if (!once) {
          setVisible(false);
        }
      });
    }, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' });
    io.observe(el);
    return () => io.disconnect();
  }, [once]);

  return (
    <Tag
      ref={ref}
      className={className}
      {...rest}
      style={{
        ...style,
        opacity: visible ? 1 : 0,
        transform: visible ? 'translateY(0)' : `translateY(${y}px)`,
        transition: `opacity ${duration}ms cubic-bezier(0.2,0.6,0.2,1) ${delay}ms, transform ${duration}ms cubic-bezier(0.2,0.6,0.2,1) ${delay}ms`,
        willChange: 'opacity, transform',
      }}>
      {children}
    </Tag>
  );
}

// Stagger helper — wraps each child of a list in a Reveal with incremental delay.
function RevealStagger({ children, base = 0, step = 60, ...rest }) {
  const arr = React.Children.toArray(children);
  return arr.map((child, i) => (
    <Reveal key={i} delay={base + i * step} {...rest}>{child}</Reveal>
  ));
}

// =================== WORK ===================
function WorkPage({ onNavigate }) {
  const [filter, setFilter] = useStatePages('all');
  const filters = ['all', 'immersive', 'led', 'concert', 'screen', 'pop-up', 'generative'];
  const filteredProjects = filter === 'all' ?
  PROJECTS :
  PROJECTS.filter((p) => p.tags.some((t) => t.toLowerCase().includes(filter.replace('-', ''))));
  const isMobile = useIsMobilePages();

  return (
    <div style={{ background: 'var(--ak-bg)', paddingBottom: isMobile ? 120 : 80 }}>
      <section style={{ padding: isMobile ? '112px 20px 24px' : '96px 32px 48px', background: 'var(--ak-bg)' }}>
        <div style={{ maxWidth: 1280, margin: '0 auto' }}>
          <Reveal>
            <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.18em', textTransform: 'uppercase', color: 'var(--ak-fg-2)', marginBottom: 16 }}>
              selected work · {SITE.yearStart}—now
            </div>
          </Reveal>
          <Reveal delay={120} y={32}>
            <h1 style={{
              fontFamily: "'PP Neue Montreal', 'Geist', system-ui, sans-serif",
              lineHeight: 0.96,
              letterSpacing: '-0.03em',
              fontWeight: 400,
              margin: 0,
              maxWidth: 1100,
              textWrap: 'balance',
              fontSize: isMobile ? 28 : 'clamp(40px, 8vw, 70px)'
            }}>
              work made for stages, screens,<br />
              <span style={{ color: 'var(--ak-fg-2)' }}>and the spaces between.</span>
            </h1>
          </Reveal>
        </div>
      </section>

      {/* filter rail — fixed at bottom, always visible */}
      <section className="ak-filter-rail" style={{
        position: 'fixed', bottom: 0, left: 0, right: 0, zIndex: 20,
        borderTop: '1px solid var(--ak-line)',
        padding: isMobile ? '10px 16px' : '12px 32px'
      }}>
        <div style={{
          maxWidth: 1280, margin: '0 auto',
          display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center'
        }}>
          <span style={{
            fontFamily: "'JetBrains Mono', monospace",
            fontSize: 11, letterSpacing: '0.14em',
            color: 'var(--ak-fg-2)', textTransform: 'uppercase',
            marginRight: 8
          }}>
            filter →
          </span>
          {filters.map((f) =>
          <button key={f}
          onClick={() => setFilter(f)}
          style={{
            padding: '8px 16px',
            borderRadius: 56,
            border: '1px solid var(--ak-line)',
            background: filter === f ? 'var(--ak-fg)' : 'transparent',
            color: filter === f ? 'var(--ak-bg)' : 'var(--ak-fg)',
            fontFamily: "'PP Neue Montreal', 'Geist', system-ui, sans-serif",
            fontSize: 13, fontWeight: 500,
            textTransform: 'lowercase',
            cursor: 'pointer',
            transition: 'all 200ms cubic-bezier(0.2,0.6,0.2,1)'
          }}>
              {f}
            </button>
          )}
          <span style={{ marginLeft: 'auto', fontSize: 12, color: 'var(--ak-fg-2)' }}>
            {filteredProjects.length} project{filteredProjects.length === 1 ? '' : 's'}
          </span>
        </div>
      </section>

      {/* alternating rows */}
      {filteredProjects.map((p, i) =>
      <WorkRow key={p.id} project={p} dark={i % 2 === 1} flip={i % 2 === 1} index={i} onNavigate={onNavigate} />
      )}

    </div>);

}

function WorkRow({ project, dark, flip, index, onNavigate }) {
  // dark/flip alternate the row layout. In dark mode, "dark" rows become the
  // INVERSE of the page (so they read as light) using --ak-bg-3 tokens.
  const bg = dark ? 'var(--ak-bg-3)' : 'var(--ak-bg)';
  const fg = dark ? 'var(--ak-fg-on3)' : 'var(--ak-fg)';
  const fg2 = dark ? 'var(--ak-fg-2-on3)' : 'var(--ak-fg-2)';
  const line = dark ? 'var(--ak-line-on3)' : 'var(--ak-line)';
  const isMobile = useIsMobilePages();

  // Single source of truth for "click anywhere to open the case study".
  const caseRoute = (
    project.id === '23-springs' ? 'case-23-springs' :
    project.id === '299-park' ? 'case-299-park' :
    project.id === 'cadillac-us-open-2025' ? 'case-cadillac' :
    project.id === 'batting-lab' ? 'case-batting-lab' :
    project.id === 'jackson-wang-magical-portal' ? 'case-jackson-wang' :
    null
  );
  const goToCase = caseRoute ? () => onNavigate && onNavigate(caseRoute) : null;

  return (
    <section style={{ background: bg, color: fg, padding: isMobile ? (index === 0 ? '24px 20px 56px' : '56px 20px') : (index === 0 ? '32px 32px 96px' : '96px 32px'), borderTop: '1px solid ' + line }}>
      <div style={{
        maxWidth: 1280, margin: '0 auto',
        display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr',
        gap: isMobile ? 28 : 56, alignItems: 'center'
      }}>
        <Reveal as="div" y={28} style={{ order: isMobile ? 2 : (flip ? 2 : 1) }}>
          <div style={{
            fontFamily: "'JetBrains Mono', monospace",
            fontSize: 12, letterSpacing: '0.14em',
            color: fg2, marginBottom: 20
          }}>
            0{index + 1} / {String(PROJECTS.length).padStart(2, '0')}
          </div>
          <div style={{
            fontSize: 11, fontWeight: 700, letterSpacing: '0.12em',
            color: fg2, marginBottom: 16
          }}>
            {project.tags.join(' / ')}
          </div>
          <h2 style={{
            fontFamily: "'PP Neue Montreal', 'Geist', system-ui, sans-serif",
            fontSize: 'clamp(40px, 5vw, 64px)',
            lineHeight: 1.0,
            letterSpacing: '-0.02em',
            fontWeight: 400,
            margin: 0, marginBottom: 20,
            textWrap: 'balance',
            whiteSpace: 'pre-line'
          }}>
            {project.title}
          </h2>
          <div style={{ fontSize: 14, color: fg2, marginBottom: 20 }}>
            {project.client} · {project.year} · {project.role}
          </div>
          <p style={{
            fontFamily: "'PP Neue Montreal', 'Geist', system-ui, sans-serif",
            fontSize: 18, lineHeight: 1.56,
            color: fg2, margin: 0, marginBottom: 28, maxWidth: 480
          }}>
            {project.desc}
          </p>
          <button className="ak-btn ak-btn--lower"
          onClick={goToCase || undefined}
          style={dark ?
          { background: 'var(--ak-fg-on3)', borderColor: 'var(--ak-fg-on3)', color: 'var(--ak-bg-3)' } :
          { background: 'var(--ak-fg)', borderColor: 'var(--ak-fg)', color: 'var(--ak-bg)' }
          }>
            open case study <span className="ak-arrow-target">→</span>
          </button>
        </Reveal>
        <Reveal as="div" delay={120} y={32} style={{ order: isMobile ? 1 : (flip ? 1 : 2) }}>
          <div
            onClick={goToCase || undefined}
            role={goToCase ? 'button' : undefined}
            tabIndex={goToCase ? 0 : undefined}
            onKeyDown={goToCase ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); goToCase(); } } : undefined}
            style={{
              aspectRatio: '4/3',
              borderRadius: isMobile ? 20 : 32,
              background: project.image ? `#000 center/cover no-repeat url("${project.image}")` : project.media,
              border: '1px solid ' + line,
              position: 'relative', overflow: 'hidden',
              cursor: goToCase ? 'pointer' : 'default',
            }}>
            <div style={{
              position: 'absolute', top: 24, left: 24,
              fontFamily: "'JetBrains Mono', monospace",
              fontSize: 10, letterSpacing: '0.14em',
              color: 'rgba(255,255,255,0.7)',
              textTransform: 'uppercase'
            }}>
              {project.__placeholder ? '[ placeholder media ]' : project.title.toLowerCase()}
            </div>
            <div style={{
              position: 'absolute', bottom: 24, right: 24,
              fontFamily: "'JetBrains Mono', monospace",
              fontSize: 10, letterSpacing: '0.14em',
              color: 'rgba(255,255,255,0.7)',
              textTransform: 'uppercase'
            }}>
              {project.year}
            </div>
          </div>
        </Reveal>
      </div>
    </section>);

}

// =================== ABOUT ===================
function AboutPage({ onNavigate }) {
  const sections = [
  { key: 'award', label: 'awards' },
  { key: 'keynote', label: 'keynotes' },
  { key: 'exhibition', label: 'exhibitions' },
  { key: 'performance', label: 'performances' }];
  const isMobile = useIsMobilePages();

  return (
    <div style={{ background: 'var(--ak-bg)' }}>
      <section style={{ padding: isMobile ? '96px 20px 32px' : '96px 32px 64px' }}>
        <div style={{ maxWidth: 1100, margin: '0 auto' }}>
          <Reveal>
            <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.18em', textTransform: 'uppercase', color: 'var(--ak-fg-2)', marginBottom: 16 }}>
              about
            </div>
          </Reveal>
          <Reveal delay={120} y={32}>
            <h1 style={{
              fontFamily: "'PP Neue Montreal', 'Geist', system-ui, sans-serif",
              fontSize: 'clamp(56px, 7vw, 96px)',
              lineHeight: 0.96,
              letterSpacing: '-0.03em',
              fontWeight: 400,
              margin: '0 0 16px',
              textWrap: 'balance'
            }}>
              Pasakorn Nontananandh<span style={{ color: '#0052ff' }}>.</span>
            </h1>
          </Reveal>
          <Reveal delay={220}>
            <div style={{ fontSize: 16, color: 'var(--ak-fg-2)', marginBottom: 48 }}>
              aka Akaliko8 · based between {SITE.basedIn}
            </div>
          </Reveal>
        </div>
      </section>

      <section style={{ padding: isMobile ? '0 20px 56px' : '0 32px 96px' }}>
        <div style={{ maxWidth: 1100, margin: '0 auto', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 2fr', gap: isMobile ? 32 : 64 }}>
          <Reveal as="div" y={28}>
            <div style={{
              aspectRatio: '4/5',
              borderRadius: 16,
              overflow: 'hidden',
              border: '1px solid var(--ak-line)',
              position: 'relative'
            }}>
              <img src="site/assets/PAS-DEC-2024-6292-Edit-Edit.jpg"
              alt="Pasakorn Nontananandh"
              style={{
                width: '100%', height: '100%',
                objectFit: 'cover', display: 'block'
              }} />
            </div>
            <div style={{
              marginTop: 16,
              fontSize: 13, color: 'var(--ak-fg-2)',
              fontFamily: "'JetBrains Mono', monospace",
              letterSpacing: '0.04em'
            }}>
              portrait · dec 2024
            </div>
          </Reveal>
          <Reveal as="div" delay={140} y={28}>
            <p style={{
              fontFamily: "'PP Neue Montreal', 'Geist', system-ui, sans-serif",

              lineHeight: 1.25,
              letterSpacing: '-0.01em',
              fontWeight: 400,
              color: 'var(--ak-fg)',
              margin: 0, marginBottom: 24,
              textWrap: 'balance', fontSize: "25px"
            }}>
              {ABOUT_BIO_1}
            </p>
            <p style={{
              fontFamily: "'PP Neue Montreal', 'Geist', system-ui, sans-serif",
              fontSize: 17, lineHeight: 1.6,
              color: 'var(--ak-fg-2)',
              margin: 0
            }}>
              {ABOUT_BIO_2}
            </p>
            <div style={{
              marginTop: 40,
              padding: '24px 0',
              borderTop: '1px solid var(--ak-line)',
              borderBottom: '1px solid var(--ak-line)',
              display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(3,1fr)', gap: isMobile ? 16 : 24
            }}>
              <MetaPair label="based" value={SITE.basedIn.toLowerCase()} />
              <MetaPair label="practice since" value={SITE.yearStart} />
              <MetaPair label="status" value={SITE.bookingStatus} />
            </div>
          </Reveal>
        </div>
      </section>

      {/* timeline sections */}
      {sections.map((s, idx) => {
        const items = ABOUT_TIMELINE.filter((t) => t.kind === s.key);
        const dark = idx % 2 === 1;
        const bg = dark ? 'var(--ak-bg-3)' : 'var(--ak-bg)';
        const fg = dark ? 'var(--ak-fg-on3)' : 'var(--ak-fg)';
        const fg2 = dark ? 'var(--ak-fg-2-on3)' : 'var(--ak-fg-2)';
        const line = dark ? 'var(--ak-line-on3)' : 'var(--ak-line)';
        return (
          <section key={s.key} style={{
            background: bg,
            color: fg,
            padding: isMobile ? '56px 20px' : '96px 32px',
            borderTop: '1px solid ' + line
          }}>
            <div style={{ maxWidth: 1100, margin: '0 auto' }}>
              <Reveal>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 32 }}>
                  <h3 style={{
                    fontFamily: "'PP Neue Montreal', 'Geist', system-ui, sans-serif",
                    fontSize: 'clamp(36px, 4vw, 56px)',
                    lineHeight: 1.0,
                    letterSpacing: '-0.02em',
                    fontWeight: 400,
                    margin: 0
                  }}>
                    {s.label}
                  </h3>
                  <div style={{
                    fontFamily: "'JetBrains Mono', monospace",
                    fontSize: 11, letterSpacing: '0.14em',
                    color: fg2,
                    textTransform: 'uppercase'
                  }}>
                    0{idx + 1} / 04
                  </div>
                </div>
              </Reveal>
              <ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>
                {items.map((t, i) => {
                  const showCity = s.key !== 'award';
                  return (
                  <Reveal key={i} as="li" delay={i * 60} y={16} style={{
                    display: 'grid',
                    gridTemplateColumns: isMobile ? '1fr' : (showCity ? '80px 1fr 1.4fr 100px' : '80px 1fr 1.4fr'),
                    gap: isMobile ? 4 : 24,
                    padding: isMobile ? '14px 0' : '20px 0',
                    borderTop: '1px solid ' + line,
                    alignItems: 'baseline'
                  }}>
                    <div style={{
                      fontFamily: "'JetBrains Mono', monospace",
                      fontSize: isMobile ? 11 : 13, letterSpacing: '0.04em',
                      color: fg2
                    }}>
                      {t.year}
                    </div>
                    <div style={{
                      fontFamily: "'PP Neue Montreal', 'Geist', system-ui, sans-serif",
                      fontSize: isMobile ? 17 : 20, lineHeight: 1.2,
                      letterSpacing: '-0.01em', fontWeight: 400
                    }}>
                      {t.title}
                    </div>
                    <div style={{ fontSize: isMobile ? 13 : 14, color: fg2 }}>
                      {t.venue}{isMobile && showCity && t.city && t.city !== '—' ? ` · ${t.city}` : ''}
                    </div>
                    {showCity && !isMobile && (
                      <div style={{
                        fontSize: 13,
                        color: fg2,
                        textAlign: 'right',
                        textTransform: 'lowercase'
                      }}>
                        {t.city}
                      </div>
                    )}
                  </Reveal>
                  );
                })}
                <li style={{ borderTop: '1px solid ' + line, height: 0 }} />
              </ul>
            </div>
          </section>);

      })}
    </div>);

}

function MetaPair({ label, value }) {
  return (
    <div>
      <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--ak-fg-2)', marginBottom: 6 }}>
        {label}
      </div>
      <div style={{ fontSize: 15, color: 'var(--ak-fg)' }}>
        {value}
      </div>
    </div>);

}

// =================== PLAY ===================
// Editorial collage: large overlapping tiles in a freeform composition,
// followed by a tighter feed grid. No frames, no tape, no rotation.
function PlayPage() {
  const [filter, setFilter] = useStatePages('all');
  const kinds = ['all', 'sketch', 'photo', 'r&d', 'live', 'keynote'];
  const filtered = filter === 'all' ? PLAY_ITEMS : PLAY_ITEMS.filter(it => it.kind === filter);

  // Hero collage — 6 hand-placed tiles on a 12-col × 8-row grid, intentionally overlapping.
  // Uses real assets where available; gradient placeholders elsewhere.
  const collage = [
    { col: '1 / 7',  row: '1 / 6', z: 2, kind: 'live',    caption: 'av set · feedback loop',  year: 2024,
      bg: 'radial-gradient(ellipse at 60% 40%,#0052ff 0%,#0a0b0d 70%)' },
    { col: '6 / 11', row: '2 / 5', z: 4, kind: 'motion',  caption: 'akaliko · loop study',    year: 2025,
      img: 'site/assets/Akaliko_Square_Large.gif' },
    { col: '4 / 9',  row: '5 / 9', z: 3, kind: 'photo',   caption: 'studio · 4am',            year: 2025,
      bg: 'linear-gradient(135deg,#1a2030 0%,#0052ff 60%,#0a0b0d 100%)' },
    { col: '9 / 13', row: '1 / 4', z: 1, kind: 'sketch',  caption: 'node graph · water sim',  year: 2025,
      bg: 'linear-gradient(135deg,#0a0b0d 0%,#282b31 100%)' },
    { col: '9 / 13', row: '5 / 8', z: 2, kind: 'r&d',     caption: 'shader test 048',         year: 2024,
      bg: 'radial-gradient(circle at 30% 70%,#578bfa 0%,#0a0b0d 70%)' },
    { col: '1 / 5',  row: '6 / 9', z: 1, kind: 'keynote', caption: 'tokyo · backstage',       year: 2024,
      bg: 'linear-gradient(180deg,#eef0f3 0%,#c8d2e0 60%,#5b616e 100%)' },
  ];

  return (
    <div style={{ background: 'var(--ak-bg)', minHeight: '100vh' }}>
      {/* header */}
      <section style={{ padding: '96px 32px 48px' }}>
        <div style={{ maxWidth: 1280, margin: '0 auto', display: 'grid', gridTemplateColumns: '1fr auto', gap: 32, alignItems: 'end' }}>
          <div>
            <Reveal>
              <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.18em', textTransform: 'uppercase', color: 'var(--ak-fg-2)', marginBottom: 16 }}>
                play · sketchbook
              </div>
            </Reveal>
            <Reveal delay={120} y={32}>
              <h1 style={{
                fontFamily: "'PP Neue Montreal', 'Geist', system-ui, sans-serif",
                fontSize: 'clamp(56px, 7vw, 96px)',
                lineHeight: 0.96,
                letterSpacing: '-0.03em',
                fontWeight: 400,
                margin: '0 0 16px',
                textWrap: 'balance'
              }}>
                sketches, r&amp;d,<br />
                <span style={{ color: 'var(--ak-fg-2)' }}>and the in-between.</span>
              </h1>
            </Reveal>
            <Reveal delay={220}>
              <p style={{
                fontFamily: "'PP Neue Montreal', 'Geist', system-ui, sans-serif",
                fontSize: 17, lineHeight: 1.55,
                color: 'var(--ak-fg-2)',
                margin: '24px 0 0',
                maxWidth: 640
              }}>
                casual feed. drafts and sketches, behind-the-scenes photos, keynote stills, r&amp;d experiments, and live performance frames.
              </p>
            </Reveal>
          </div>
          <Reveal delay={300}>
            <div style={{
              fontFamily: "'JetBrains Mono', monospace",
              fontSize: 11, letterSpacing: '0.14em',
              color: 'var(--ak-fg-2)', textTransform: 'uppercase',
              textAlign: 'right',
              paddingBottom: 8,
            }}>
              {PLAY_ITEMS.length} entries<br />
              {SITE.yearNow}
            </div>
          </Reveal>
        </div>
      </section>

      {/* collage — overlapping editorial composition */}
      <section style={{ padding: '0 32px 96px' }}>
        <div style={{
          maxWidth: 1280, margin: '0 auto',
          display: 'grid',
          gridTemplateColumns: 'repeat(12, 1fr)',
          gridTemplateRows: 'repeat(8, 64px)',
          gap: 0,
          position: 'relative',
        }}>
          {collage.map((it, i) => (
            <CollageTile key={i} {...it} revealDelay={i * 80} />
          ))}
        </div>
      </section>

      {/* filter chips */}
      <section style={{ padding: '0 32px 24px', borderTop: '1px solid var(--ak-line)' }}>
        <div style={{ maxWidth: 1280, margin: '0 auto', padding: '32px 0', display: 'flex', alignItems: 'baseline', gap: 24, flexWrap: 'wrap' }}>
          <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.18em', textTransform: 'uppercase', color: 'var(--ak-fg-2)' }}>
            feed
          </div>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            {kinds.map(k => (
              <button key={k}
                onClick={() => setFilter(k)}
                style={{
                  padding: '6px 14px',
                  borderRadius: 999,
                  border: '1px solid var(--ak-line)',
                  background: filter === k ? 'var(--ak-fg)' : 'transparent',
                  color: filter === k ? 'var(--ak-bg)' : 'var(--ak-fg)',
                  fontFamily: "'JetBrains Mono', monospace",
                  fontSize: 11, letterSpacing: '0.10em',
                  textTransform: 'uppercase',
                  cursor: 'pointer',
                  transition: 'all 160ms cubic-bezier(0.2,0.6,0.2,1)',
                }}>
                {k}
              </button>
            ))}
          </div>
          <div style={{
            marginLeft: 'auto',
            fontFamily: "'JetBrains Mono', monospace",
            fontSize: 11, color: 'var(--ak-fg-2)', letterSpacing: '0.10em', textTransform: 'uppercase',
          }}>
            {filtered.length} / {PLAY_ITEMS.length}
          </div>
        </div>
      </section>

      {/* feed grid — clean, varied aspects, slight overlap via negative margins on every 5th */}
      <section style={{ padding: '0 32px 120px' }}>
        <div style={{
          maxWidth: 1280, margin: '0 auto',
          display: 'grid',
          gridTemplateColumns: 'repeat(12, 1fr)',
          gap: 16,
        }}>
          {filtered.map((it, i) => {
            // varied placement: alternate widths/heights for rhythm
            const layouts = [
              { col: 'span 4', aspect: '4/5' },
              { col: 'span 5', aspect: '4/3' },
              { col: 'span 3', aspect: '1/1' },
              { col: 'span 3', aspect: '1/1' },
              { col: 'span 4', aspect: '3/4' },
              { col: 'span 5', aspect: '4/3' },
              { col: 'span 4', aspect: '4/5' },
              { col: 'span 4', aspect: '1/1' },
              { col: 'span 4', aspect: '4/5' },
            ];
            const L = layouts[i % layouts.length];
            return (
              <FeedTile key={i} item={it} col={L.col} aspect={L.aspect} index={i} />
            );
          })}
        </div>

        {/* end note */}
        <div style={{
          maxWidth: 640, margin: '120px auto 0',
          textAlign: 'center',
          fontFamily: "'PP Neue Montreal', 'Geist', system-ui, sans-serif",
          fontSize: 14, color: 'var(--ak-fg-2)',
          lineHeight: 1.6
        }}>
          <div style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: 11, letterSpacing: '0.16em', textTransform: 'uppercase', marginBottom: 12 }}>
            end of feed · for now
          </div>
          new entries land when something feels worth keeping. no schedule.
        </div>
      </section>
    </div>);

}

function CollageTile({ col, row, z, kind, caption, year, bg, img, revealDelay = 0 }) {
  const [hover, setHover] = useStatePages(false);
  const [shown, setShown] = useStatePages(false);
  const ref = useRefPages(null);
  useEffectPages(() => {
    const el = ref.current;
    if (!el) return;
    const reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (reduce) { setShown(true); return; }
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => { if (e.isIntersecting) { setShown(true); io.unobserve(e.target); } });
    }, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return (
    <div
      ref={ref}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        gridColumn: col, gridRow: row,
        zIndex: hover ? 10 : z,
        position: 'relative',
        overflow: 'hidden',
        borderRadius: 4,
        background: bg || '#0a0b0d',
        boxShadow: hover
          ? '0 32px 80px rgba(10,11,13,0.28), 0 2px 8px rgba(10,11,13,0.10)'
          : '0 12px 32px rgba(10,11,13,0.16), 0 1px 2px rgba(10,11,13,0.06)',
        opacity: shown ? 1 : 0,
        transform: shown
          ? (hover ? 'translateY(-4px) scale(1.015)' : 'translateY(0) scale(1)')
          : 'translateY(28px) scale(0.985)',
        transition: `opacity 700ms cubic-bezier(0.2,0.6,0.2,1) ${revealDelay}ms, transform 700ms cubic-bezier(0.2,0.6,0.2,1) ${revealDelay}ms, box-shadow 320ms cubic-bezier(0.2,0.6,0.2,1)`,
        cursor: 'pointer',
        willChange: 'opacity, transform',
      }}>
      {img && (
        <img src={img} alt=""
          style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
      )}
      {/* meta strip — fades in on hover */}
      <div style={{
        position: 'absolute', inset: 0,
        background: 'linear-gradient(180deg, rgba(0,0,0,0) 60%, rgba(0,0,0,0.55) 100%)',
        opacity: hover ? 1 : 0,
        transition: 'opacity 240ms cubic-bezier(0.2,0.6,0.2,1)',
        pointerEvents: 'none',
      }} />
      <div style={{
        position: 'absolute', top: 12, left: 12,
        padding: '4px 10px',
        background: 'rgba(255,255,255,0.92)',
        borderRadius: 999,
        fontFamily: "'JetBrains Mono', monospace",
        fontSize: 9, letterSpacing: '0.14em',
        color: '#0a0b0d',
        textTransform: 'uppercase',
        fontWeight: 600,
      }}>
        {kind}
      </div>
      <div style={{
        position: 'absolute', bottom: 12, left: 12, right: 12,
        display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
        color: '#fff',
        fontFamily: "'JetBrains Mono', monospace",
        fontSize: 11, letterSpacing: '0.04em',
        opacity: hover ? 1 : 0,
        transform: hover ? 'translateY(0)' : 'translateY(4px)',
        transition: 'opacity 240ms cubic-bezier(0.2,0.6,0.2,1), transform 240ms cubic-bezier(0.2,0.6,0.2,1)',
        textShadow: '0 1px 2px rgba(0,0,0,0.4)',
      }}>
        <span>{caption}</span>
        <span style={{ opacity: 0.7 }}>{year}</span>
      </div>
    </div>
  );
}

function FeedTile({ item, col, aspect, index = 0 }) {
  const [hover, setHover] = useStatePages(false);
  const [shown, setShown] = useStatePages(false);
  const ref = useRefPages(null);
  useEffectPages(() => {
    const el = ref.current;
    if (!el) return;
    const reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (reduce) { setShown(true); return; }
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => { if (e.isIntersecting) { setShown(true); io.unobserve(e.target); } });
    }, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  const delay = (index % 6) * 70;
  return (
    <div
      ref={ref}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        gridColumn: col,
        position: 'relative',
        cursor: 'pointer',
        opacity: shown ? 1 : 0,
        transform: shown ? 'translateY(0)' : 'translateY(24px)',
        transition: `opacity 700ms cubic-bezier(0.2,0.6,0.2,1) ${delay}ms, transform 700ms cubic-bezier(0.2,0.6,0.2,1) ${delay}ms`,
        willChange: 'opacity, transform',
      }}>
      <div style={{
        aspectRatio: aspect,
        background: item.media,
        borderRadius: 4,
        position: 'relative',
        overflow: 'hidden',
        transition: 'transform 280ms cubic-bezier(0.2,0.6,0.2,1)',
        transform: hover ? 'scale(1.015)' : 'scale(1)',
      }}>
        <div style={{
          position: 'absolute', inset: 0,
          background: 'linear-gradient(180deg, rgba(0,0,0,0) 55%, rgba(0,0,0,0.55) 100%)',
          opacity: hover ? 1 : 0,
          transition: 'opacity 220ms cubic-bezier(0.2,0.6,0.2,1)',
        }} />
        <div style={{
          position: 'absolute', top: 10, left: 10,
          padding: '3px 9px',
          background: 'rgba(255,255,255,0.92)',
          borderRadius: 999,
          fontFamily: "'JetBrains Mono', monospace",
          fontSize: 9, letterSpacing: '0.12em',
          color: '#0a0b0d',
          textTransform: 'uppercase',
          fontWeight: 600,
        }}>
          {item.kind}
        </div>
      </div>
      <div style={{
        marginTop: 10,
        display: 'flex', justifyContent: 'space-between',
        alignItems: 'baseline',
        fontFamily: "'JetBrains Mono', monospace",
        fontSize: 11,
        color: 'var(--ak-fg-2)',
        letterSpacing: '0.02em',
      }}>
        <span>{item.caption}</span>
        <span>{item.year}</span>
      </div>
    </div>
  );
}

// =================== CONTACT ===================
const FORMSPREE_ENDPOINT = 'https://formspree.io/f/xjglkeee';

function ContactPage() {
  const [status, setStatus] = useStatePages('idle'); // 'idle' | 'sending' | 'sent' | 'error'
  const [errorMsg, setErrorMsg] = useStatePages('');
  const isMobile = useIsMobilePages();
  return (
    <div style={{ background: 'var(--ak-bg)' }}>
      <section style={{ padding: isMobile ? '96px 20px 64px' : '96px 32px 120px' }}>
        <div style={{ maxWidth: 880, margin: '0 auto', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: isMobile ? 32 : 64 }}>
          <div>
            <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.18em', textTransform: 'uppercase', color: 'var(--ak-fg-2)', marginBottom: 16 }}>
              contact
            </div>
            <h1 style={{
              fontFamily: "'PP Neue Montreal', 'Geist', system-ui, sans-serif",
              fontSize: 'clamp(48px, 6vw, 80px)',
              lineHeight: 0.96,
              letterSpacing: '-0.03em',
              fontWeight: 400,
              margin: '0 0 24px',
              textWrap: 'balance'
            }}>
              tell me<br />about it<span style={{ color: '#0052ff' }}>.</span>
            </h1>
            <p style={{ fontSize: 16, color: 'var(--ak-fg-2)', lineHeight: 1.55, marginBottom: 32 }}>
              dates, venue, scale, anything you&rsquo;ve got. responses inside 48 hours, monday — friday.
            </p>
            <div style={{ borderTop: '1px solid var(--ak-line)', paddingTop: 20, fontSize: 14, color: 'var(--ak-fg-2)' }}>
              <div style={{ marginBottom: 8 }}>{SITE.email}</div>
              <div>{SITE.bookingStatus}</div>
            </div>
          </div>
          <form onSubmit={async (e) => {
            e.preventDefault();
            const form = e.currentTarget;
            const fd = new FormData(form);
            const name = (fd.get('name') || '').toString().trim();
            const projectType = (fd.get('projectType') || '').toString().trim();
            // Pretty subject for the email Formspree forwards.
            fd.set('_subject', `New inquiry${name ? ` from ${name}` : ''}${projectType ? ` — ${projectType}` : ''}`);
            setStatus('sending');
            setErrorMsg('');
            try {
              const res = await fetch(FORMSPREE_ENDPOINT, {
                method: 'POST',
                body: fd,
                headers: { Accept: 'application/json' },
              });
              if (res.ok) {
                setStatus('sent');
                form.reset();
              } else {
                const data = await res.json().catch(() => ({}));
                const msg = data?.errors?.[0]?.message || `${res.status} ${res.statusText}`;
                setErrorMsg(msg);
                setStatus('error');
              }
            } catch (err) {
              setErrorMsg(err?.message || 'network error');
              setStatus('error');
            }
          }}
          style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
            {status === 'sent' ?
            <div style={{
              padding: 32,
              background: 'var(--ak-bg-3)', color: 'var(--ak-fg-on3)',
              borderRadius: 16,
              fontFamily: "'PP Neue Montreal', 'Geist', system-ui, sans-serif",
              fontSize: 24, lineHeight: 1.2,
              letterSpacing: '-0.01em'
            }}>
                received.<br />
                <span style={{ color: 'var(--ak-fg-2-on3)', fontSize: 16 }}>
                  thanks — i&rsquo;ll get back inside 48 hours.
                </span>
              </div> :

            <>
                <Field name="name" label="name" placeholder="your name" />
                <Field name="email" label="email" type="email" placeholder="you@studio.com" />
                <Field name="projectType" label="project type" placeholder="immersive · led · concert · screen · other" />
                <Field name="brief" label="brief" placeholder="dates, venue, scale, anything you've got." multiline />
                {/* honeypot — bots fill this; humans don't see it */}
                <input type="text" name="_gotcha" tabIndex="-1" autoComplete="off"
                  style={{ position: 'absolute', left: '-9999px', width: 1, height: 1, opacity: 0 }} />
                {status === 'error' && (
                  <div style={{ fontSize: 13, color: '#c0392b' }}>
                    couldn&rsquo;t send — {errorMsg}. try again or email {SITE.email} directly.
                  </div>
                )}
                <button type="submit" disabled={status === 'sending'} className="ak-btn ak-btn--lower"
              style={{
                alignSelf: 'flex-start', marginTop: 8,
                background: 'var(--ak-fg)', borderColor: 'var(--ak-fg)', color: 'var(--ak-bg)',
                opacity: status === 'sending' ? 0.6 : 1,
                cursor: status === 'sending' ? 'wait' : 'pointer',
              }}>
                  {status === 'sending' ? 'sending…' : <>send <span className="ak-arrow-target">→</span></>}
                </button>
              </>
            }
          </form>
        </div>
      </section>
    </div>);

}

function Field({ name, label, placeholder, multiline, type = 'text' }) {
  const T = multiline ? 'textarea' : 'input';
  const extra = multiline ? {} : { type };
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      <label style={{ fontSize: 12, fontWeight: 600, color: 'var(--ak-fg-2)', textTransform: 'lowercase' }}>{label}</label>
      <T name={name} placeholder={placeholder} {...extra}
      style={{
        fontFamily: "'PP Neue Montreal', 'Geist', system-ui, sans-serif",
        fontSize: 15, padding: '12px 14px',
        border: '1px solid var(--ak-line)',
        borderRadius: 12, background: 'var(--ak-bg)', color: 'var(--ak-fg)',
        outline: 'none',
        minHeight: multiline ? 140 : 'auto',
        resize: 'vertical'
      }} />
    </div>);

}

window.WorkPage = WorkPage;
window.AboutPage = AboutPage;
window.PlayPage = PlayPage;
window.ContactPage = ContactPage;
window.Reveal = Reveal;