// project-page.jsx — Individual project case study page (Block · Light mode).
// Cream ivory ground, carbon ink, magenta primary, indigo secondary.
// Editorial layout: massive Fraunces headlines, Homemade Apple
// script accents, JetBrains Mono caps for rails and chips.

// ── Demo content (mockup, fallback) ───────────────────────────
const PROJECT_DEMO = {
  slug: 'lune-hotels',
  title: 'Lune Hotels',
  subtitle: 'Where the sea forgets the time.',
  client: 'Lune Hotels',
  role: 'Identity · Wayfinding · Web',
  year: '2024',
  location: 'Goa, India',
  brief: `Lune Hotels asked us to reimagine a coastal stay as a sequence of pauses — moments where the day slows enough to notice itself. Our task: build an identity system, signage, and digital presence that hold space for that quiet, without the resort cliché.`,
  approach: `We anchored the brand on a single image — moonlight on water — and let the system spread outward from there. A custom mark, a typographic palette built around contrast not noise, and a wayfinding language that feels like printed matter rather than commercial signage. Every touchpoint was designed to be felt before it was read.`,
  textBoxes: [
    { label: 'Brief', body: 'Reimagine a coastal stay as a sequence of pauses — moments where the day slows enough to notice itself.' },
    { label: 'Direction', body: 'Anchor the brand on a single image — moonlight on water — and let the system spread outward from there.' },
    { label: 'Type', body: 'A typographic palette built around contrast, not noise. Display set in a high-axis sans against a quiet sans for body and signage.' },
    { label: 'Outcome', body: 'A wayfinding system that feels like printed matter, a mark people screenshot, and a launch that booked out the first season.' },
  ],
  credits: [
    ['Creative Direction', 'Chapter 1'],
    ['Design', 'Studio Team'],
    ['Photography', 'Field Studio'],
    ['Web Build', 'Chapter 1 / Field'],
  ],
};

// Build a full project record from a WORK entry, merging demo defaults
// for fields the work archive doesn't yet carry (brief, approach, etc).
function buildProjectFromWork(w) {
  if (!w) return PROJECT_DEMO;
  return {
    ...PROJECT_DEMO,
    slug: window.slugify ? window.slugify(w.client) : w.client.toLowerCase().replace(/\s+/g, '-'),
    n: w.n,
    title: w.client,
    tagline: w.tagline || '',
    subtitle: w.tagline || PROJECT_DEMO.subtitle,
    kind: w.kind || '',
    hero: w.hero || null,
    client: w.client,
    role: (w.tags || []).join(' · ') || PROJECT_DEMO.role,
    year: w.year || PROJECT_DEMO.year,
    location: (w.kind || '').split(' · ')[1] || PROJECT_DEMO.location,
    palette: w.palette || PROJECT_DEMO.palette,
  };
}

// Merge a stored project onto the demo defaults so missing rich fields
// (brief, approach, textBoxes, credits) still render.
function mergeProject(p) {
  const merged = {
    ...PROJECT_DEMO,
    ...p,
    title: p.client || p.title || PROJECT_DEMO.title,
    role: p.role || (p.tags || []).join(' · ') || PROJECT_DEMO.role,
    location: p.location || (p.kind || '').split(' · ')[1] || PROJECT_DEMO.location,
    subtitle: p.tagline || p.subtitle || PROJECT_DEMO.subtitle,
    brief: p.brief || '',
    approach: p.approach || '',
    textBoxes: Array.isArray(p.textBoxes) ? p.textBoxes : PROJECT_DEMO.textBoxes,
    credits: Array.isArray(p.credits) ? p.credits : PROJECT_DEMO.credits,
  };
  return merged;
}

// Resolve a project: Store (richest) → window.WORK (fallback) → demo.
function resolveProject() {
  const id = window.getQueryParam ? window.getQueryParam('id') : null;
  if (!id) return PROJECT_DEMO;
  if (window.Store) {
    const stored = window.Store.getProject(id);
    if (stored) return mergeProject(stored);
  }
  if (window.WORK) {
    const hit = window.WORK.find((w) => w.n === id);
    if (hit) return buildProjectFromWork(hit);
  }
  return PROJECT_DEMO;
}

function findNextProject(currentId) {
  const list = window.WORK || [];
  if (!list.length) return null;
  const i = list.findIndex((w) => w.n === currentId);
  const next = list[(i + 1 + list.length) % list.length] || list[0];
  return next || null;
}

// Magazine grid — varied 1/2/3-col rows. Each entry: {span, ratio, label}
const GRID_ROWS = [
  { cols: [{ span: 12, ratio: 16/9, label: 'Hero secondary' }] },
  { cols: [{ span: 7, ratio: 4/5, label: 'Editorial portrait' }, { span: 5, ratio: 4/5, label: 'Detail crop' }] },
  { cols: [{ span: 4, ratio: 1, label: 'Mark' }, { span: 4, ratio: 1, label: 'Stationery' }, { span: 4, ratio: 1, label: 'Signage' }] },
  { cols: [{ span: 12, ratio: 21/9, label: 'Full-bleed environment' }] },
  { cols: [{ span: 5, ratio: 3/4, label: 'Type specimen' }, { span: 7, ratio: 3/4, label: 'Web mock' }] },
  { cols: [{ span: 6, ratio: 1, label: 'Pattern detail' }, { span: 6, ratio: 1, label: 'Material study' }] },
  { cols: [{ span: 12, ratio: 16/9, label: 'Closing wide' }] },
];

const NEXT_PROJECT = {
  slug: 'bombay-canteen',
  client: 'Bombay Canteen',
  tagline: 'The city on a plate.',
  year: '2025',
};

// Local palette handle — falls back to literals if shared.jsx hasn't loaded.
const P = (typeof window !== 'undefined' && window.PALETTE) || {
  ivory:    '#efeae0',
  ivory2:   '#efeae0',
  carbon:   '#221a1d',
  merlot:   '#6b1a35',
  magenta:  '#bc2a4e',
  lilac:    '#c5a4be',
  indigo:   '#27557e',
  sage:     '#6fa57c',
  muted:    'rgba(34,26,29,0.55)',
};

const FONT_DISPLAY = "'Fraunces', Georgia, serif";
const FONT_BODY    = "'Montserrat', system-ui, sans-serif";
const FONT_SCRIPT  = "'Homemade Apple', cursive";
const FONT_MONO    = "'JetBrains Mono', ui-monospace, monospace";

// ── Reveal hook ──────────────────────────────────────────────────
function useReveal() {
  const ref = React.useRef(null);
  const [shown, setShown] = React.useState(false);
  React.useEffect(() => {
    if (!ref.current) return;
    const stage = document.querySelector('.c1-stage') || null;
    const io = new IntersectionObserver(
      ([entry]) => { if (entry.isIntersecting) { setShown(true); io.disconnect(); } },
      { root: stage, threshold: 0.12, rootMargin: '0px 0px -8% 0px' }
    );
    io.observe(ref.current);
    return () => io.disconnect();
  }, []);
  return [ref, shown];
}

// Parallax helper — translates child by a fraction of how much it has moved through viewport.
function useParallax(strength = 0.15) {
  const ref = React.useRef(null);
  const [y, setY] = React.useState(0);
  React.useEffect(() => {
    const stage = document.querySelector('.c1-stage');
    if (!stage || !ref.current) return;
    let raf = 0;
    const compute = () => {
      const r = ref.current.getBoundingClientRect();
      const stageH = stage.clientHeight;
      const center = r.top + r.height / 2 - stageH / 2;
      setY(-center * strength);
    };
    const onScroll = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(compute); };
    compute();
    stage.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    return () => { cancelAnimationFrame(raf); stage.removeEventListener('scroll', onScroll); window.removeEventListener('resize', onScroll); };
  }, [strength]);
  return [ref, y];
}

// ── Image placeholder (ivory ground, lilac wash) ────────────────
function ImagePlaceholder({ ratio = 1, label = 'Image' }) {
  const [pRef, py] = useParallax(0.06);
  const seed = label.split('').reduce((a, c) => a + c.charCodeAt(0), 0);
  return (
    <div style={{
      position: 'relative',
      width: '100%',
      aspectRatio: String(ratio),
      overflow: 'hidden',
      background: P.ivory2,
      border: `1px solid rgba(34,26,29,.10)`,
    }}>
      <div ref={pRef} style={{
        position: 'absolute', inset: '-10% 0',
        background: P.ivory,
        transform: `translate3d(0, ${py}px, 0)`,
        willChange: 'transform',
      }} />
      <svg style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', opacity: .10, pointerEvents: 'none' }}>
        <defs>
          <pattern id={`p-${seed}`} width="14" height="14" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
            <line x1="0" y1="0" x2="0" y2="14" stroke={P.carbon} strokeWidth=".5" />
          </pattern>
        </defs>
        <rect width="100%" height="100%" fill={`url(#p-${seed})`} />
      </svg>
    </div>
  );
}

// ── Reveal wrapper ──────────────────────────────────────────────
function PReveal({ children, delay = 0, y = 28 }) {
  const [r, shown] = useReveal();
  return (
    <div ref={r} style={{
      opacity: shown ? 1 : 0,
      transform: shown ? 'none' : `translate3d(0, ${y}px, 0)`,
      transition: `opacity .9s cubic-bezier(.16,1,.3,1) ${delay}ms, transform .9s cubic-bezier(.16,1,.3,1) ${delay}ms`,
      willChange: 'opacity, transform',
    }}>{children}</div>
  );
}

// ── Hero block ──────────────────────────────────────────────────
function ProjectHero({ project }) {
  const Crosshair = window.CrosshairMarkers || (() => null);
  const Split = window.SplitText;
  const StampEl = window.Stamp;
  const heroSrc = project.hero?.src;
  const heroRatio = project.hero?.ratio || (16 / 9);
  const yearStr = project.year || '';
  const sectorStr = project.kind || '';
  const kindStr = project.role || '';
  const chapterNum = project.n ? `Chapter · ${project.n}` : 'Chapter · One';

  const title = (project.title || '').trim();

  return (
    <header style={{
      position: 'relative',
      padding: '160px 32px 80px',
      background: P.ivory,
      color: P.carbon,
      borderBottom: `1px solid rgba(34,26,29,.10)`,
    }}>
      <Crosshair color={P.carbon} />

      {/* Top mono meta row */}
      <PReveal>
        <div style={{
          fontFamily: FONT_MONO,
          fontSize: 11, letterSpacing: '.24em', textTransform: 'uppercase',
          color: P.muted,
          marginBottom: 60,
          display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
          gap: 24, flexWrap: 'wrap',
        }}>
          <span>[ {[yearStr, sectorStr, kindStr].filter(Boolean).join(' · ')} ]</span>
          <span style={{ textAlign: 'right' }}>{project.location}</span>
        </div>
      </PReveal>

      {/* Grid: huge title left, stamp right */}
      <div style={{
        display: 'grid', gridTemplateColumns: '1fr auto', gap: 40,
        alignItems: 'start',
      }}>
        <div>
          <PReveal>
            <h1 className="c1-project-title" style={{
              fontFamily: FONT_DISPLAY,
              fontSize: 'clamp(54px, 10.2vw, 170px)',
              lineHeight: .85,
              letterSpacing: '-.02em',
              margin: 0,
              color: P.carbon,
            }}>
              {Split ? <Split text={title} /> : title}
            </h1>

            {project.subtitle && (
              <p className="c1-project-subtitle" style={{
                marginTop: 36,
                maxWidth: '34ch',
                fontFamily: FONT_SCRIPT,
                color: P.magenta,
                fontSize: 'clamp(22px, 3vw, 36px)',
                lineHeight: 1.25,
                transform: 'rotate(-3deg)',
                transformOrigin: 'left center',
                display: 'inline-block',
              }}>
                {project.subtitle}
              </p>
            )}
          </PReveal>
        </div>

        {/* Stamp + chapter number */}
        <PReveal delay={220}>
          <div style={{
            display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 18,
          }}>
            {StampEl && (
              <StampEl
                size={140}
                color={P.magenta}
                label={`Chapter · One · ${project.n || '01'} · `}
              />
            )}
            <div style={{
              fontFamily: FONT_MONO,
              fontSize: 11, letterSpacing: '.24em', textTransform: 'uppercase',
              color: P.muted, textAlign: 'right',
            }}>
              {chapterNum}<br />Est. {project.year || ''}
            </div>
          </div>
        </PReveal>
      </div>

      {/* Hero media — full-bleed at the bottom of the hero block */}
      <PReveal delay={340}>
        <figure style={{ margin: '80px 0 0' }}>
          {heroSrc ? (
            <img
              src={cfImg(heroSrc, 1800)}
              alt={project.hero?.alt || project.title}
              style={{
                display: 'block',
                width: '100%',
                aspectRatio: heroRatio,
                objectFit: 'cover',
                background: P.ivory2,
              }}
            />
          ) : (
            <ImagePlaceholder ratio={heroRatio} label={project.hero?.area || project.title || 'Hero'} />
          )}
          <figcaption style={{
            display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
            marginTop: 14, gap: 16, flexWrap: 'wrap',
            fontFamily: FONT_MONO,
            fontSize: 10, letterSpacing: '.24em', textTransform: 'uppercase',
            color: P.muted,
          }}>
            <span>Fig. 01 — {project.hero?.area || project.title}</span>
            <span>{project.year}</span>
          </figcaption>
        </figure>
      </PReveal>

      {/* Client / Scope / Location row */}
      <PReveal delay={460}>
        <div style={{
          marginTop: 80,
          display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 40,
          paddingTop: 40,
          borderTop: `1px solid rgba(34,26,29,.10)`,
        }}>
          {[
            ['Client', project.client],
            ['Scope', project.role],
            ['Location', project.location],
          ].map(([k, v]) => (
            <div key={k}>
              <div style={{
                fontFamily: FONT_MONO,
                fontSize: 10, letterSpacing: '.24em', textTransform: 'uppercase',
                color: P.muted, marginBottom: 10,
              }}>{k}</div>
              <div style={{
                fontFamily: FONT_BODY, fontSize: 15, color: P.carbon, lineHeight: 1.5,
              }}>{v}</div>
            </div>
          ))}
        </div>
      </PReveal>
    </header>
  );
}

// ── Project CTA buttons ────────────────────────────────────
// Up to three custom links (e.g. "Visit site", "Press kit", "Behind the
// scenes"). Stored on the project record as `ctas: [{label, href}, …]`.
function ProjectCTAs({ project }) {
  const ctas = (project.ctas || []).filter((c) =>
    c && (c.label || '').trim() && (c.href || '').trim()
  );
  if (!ctas.length) return null;
  return (
    <section style={{ padding: '60px 32px 0', background: P.ivory }}>
      <PReveal>
        <div style={{
          maxWidth: 1200, margin: '0 auto',
          display: 'flex', flexWrap: 'wrap', gap: 14,
        }}>
          {ctas.slice(0, 3).map((c, i) => (
            <a key={i}
              href={c.href}
              target={c.href.startsWith('http') ? '_blank' : undefined}
              rel={c.href.startsWith('http') ? 'noopener noreferrer' : undefined}
              data-cursor="Open"
              style={{
                display: 'inline-flex', alignItems: 'center', gap: 12,
                padding: '14px 22px',
                border: `1px solid ${P.carbon}`,
                color: P.carbon,
                fontSize: 11, letterSpacing: '.22em', textTransform: 'uppercase',
                fontFamily: FONT_MONO,
                background: 'transparent',
                textDecoration: 'none',
                transition: 'background .25s, color .25s, border-color .25s',
              }}
              onMouseEnter={(e) => {
                e.currentTarget.style.background = P.magenta;
                e.currentTarget.style.color = P.ivory;
                e.currentTarget.style.borderColor = P.magenta;
              }}
              onMouseLeave={(e) => {
                e.currentTarget.style.background = 'transparent';
                e.currentTarget.style.color = P.carbon;
                e.currentTarget.style.borderColor = P.carbon;
              }}>
              {c.label} <span style={{ marginLeft: 2 }}>↗</span>
            </a>
          ))}
        </div>
      </PReveal>
    </section>
  );
}

// Split a section body into lead + rest.
function _splitLead(body) {
  const text = (body || '').replace(/\r\n/g, '\n').trim();
  if (!text) return { lead: '', rest: '' };
  const dbl = text.indexOf('\n\n');
  if (dbl > 0) {
    return { lead: text.slice(0, dbl).trim(), rest: text.slice(dbl + 2).trim() };
  }
  const re = /[^.!?…]+[.!?…]+/g;
  const sentences = [];
  let m;
  while ((m = re.exec(text)) !== null) sentences.push(m[0].trim());
  if (sentences.length < 2) return { lead: text, rest: '' };
  const oneSentence = sentences[0];
  const twoSentence = sentences.slice(0, 2).join(' ');
  const useTwo = oneSentence.length < 120 && twoSentence.length <= 220;
  const lead = useTwo ? twoSentence : oneSentence;
  const remainder = text.slice(lead.length).trim();
  if (remainder.length < 40) return { lead: text, rest: '' };
  return { lead, rest: remainder };
}

// ── Section block (interleaved or single-template) ──────────
function InlineTextBox({ box, index, scale }) {
  const { lead, rest } = _splitLead(box.body || '');
  const num = String((index || 0) + 1).padStart(2, '0');
  return (
    <PReveal>
      <section style={{
        padding: '100px 32px',
        background: P.ivory,
        borderTop: `1px solid rgba(34,26,29,.10)`,
      }}>
        <div style={{
          maxWidth: 1200, margin: '0 auto',
          display: 'grid', gridTemplateColumns: '180px 1fr', gap: 60, alignItems: 'start',
        }}>
          {/* Left rail: number + label */}
          <div style={{ position: 'sticky', top: 100 }}>
            <div style={{
              fontFamily: FONT_MONO,
              fontSize: 11, letterSpacing: '.24em', textTransform: 'uppercase',
              color: P.muted,
            }}>
              {num} · {box.label || 'Section'}
            </div>
          </div>

          {/* Right: heading + body */}
          <div style={{ display: 'grid', gap: 28 }}>
            {box.label && (
              <h2 style={{
                fontFamily: FONT_DISPLAY,
                fontSize: `clamp(${40*scale}px, ${6*scale}vw, ${72*scale}px)`,
                lineHeight: .92, letterSpacing: '-.03em',
                margin: 0,
                color: P.carbon,
              }}>
                {box.label}
                <span style={{
                  fontFamily: FONT_SCRIPT,
                  color: P.magenta,
                  fontSize: '.45em',
                  letterSpacing: 0,
                  display: 'inline-block',
                  transform: 'rotate(-3deg)',
                  marginLeft: '.4em',
                }}>·</span>
              </h2>
            )}
            {lead && (
              <p style={{
                margin: 0,
                fontFamily: FONT_BODY,
                fontWeight: 500,
                fontSize: `clamp(${20*scale}px, ${1.8*scale}vw, ${26*scale}px)`,
                lineHeight: 1.45, letterSpacing: '-.005em',
                color: P.carbon, maxWidth: '60ch',
                whiteSpace: 'pre-wrap',
              }}>{lead}</p>
            )}
            {rest && (
              <p style={{
                margin: 0,
                fontFamily: FONT_BODY,
                fontWeight: 400,
                fontSize: `clamp(${15*scale}px, ${1*scale}vw, ${17*scale}px)`,
                lineHeight: 1.7, letterSpacing: '.005em',
                color: P.muted, maxWidth: '64ch',
                whiteSpace: 'pre-wrap',
              }}>{rest}</p>
            )}
          </div>
        </div>
      </section>
    </PReveal>
  );
}

// ── Magazine grid section ──────────────────────────────────
function ImageGrid({ rows, density }) {
  const gap = density === 'tight' ? 8 : density === 'generous' ? 28 : 16;
  const padX = density === 'tight' ? 8 : density === 'generous' ? 60 : 32;
  return (
    <section style={{
      padding: `48px ${padX}px`,
      display: 'grid', gap,
      background: P.ivory,
    }}>
      {rows.map((row, ri) => (
        <PReveal key={ri} y={40}>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(12, 1fr)', gap }}>
            {row.cols.map((c, ci) => (
              <figure key={ci} style={{ gridColumn: `span ${c.span}`, margin: 0 }}>
                {c.src ? (
                  <img
                    src={cfImg(c.src, Math.max(400, Math.round((c.span || 12) / 12 * 1920)))}
                    alt={c.alt || c.label || ''}
                    loading="lazy"
                    style={{
                      width: '100%', aspectRatio: c.ratio, objectFit: 'cover',
                      display: 'block', background: P.ivory2,
                    }}
                  />
                ) : (
                  <ImagePlaceholder ratio={c.ratio} label={c.label} />
                )}
                {c.label && (
                  <figcaption style={{
                    marginTop: 10,
                    fontFamily: FONT_MONO,
                    fontSize: 10, letterSpacing: '.22em', textTransform: 'uppercase',
                    color: P.muted,
                  }}>
                    Fig. {String(ri + 1).padStart(2, '0')}.{ci + 1} — {c.label}
                  </figcaption>
                )}
              </figure>
            ))}
          </div>
        </PReveal>
      ))}
    </section>
  );
}

// ── Tag chips row (services applied) ───────────────────────
function TagChips({ project }) {
  // Pull whichever set of tags the project has on it. Sources differ between
  // admin-edited records and seeded WORK rows — accept any of them.
  const tags = (project.services && project.services.length ? project.services
              : project.tags && project.tags.length ? project.tags
              : (project.role || '').split('·').map((s) => s.trim()).filter(Boolean));
  if (!tags.length) return null;
  return (
    <section style={{
      padding: '60px 32px',
      background: P.ivory,
      borderTop: `1px solid rgba(34,26,29,.10)`,
    }}>
      <div style={{ maxWidth: 1200, margin: '0 auto' }}>
        <div style={{
          fontFamily: FONT_MONO,
          fontSize: 11, letterSpacing: '.24em', textTransform: 'uppercase',
          color: P.muted, marginBottom: 22,
        }}>
          [ Services Applied ]
        </div>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10 }}>
          {tags.map((t, i) => (
            <span key={i} style={{
              display: 'inline-flex', alignItems: 'center',
              padding: '10px 16px',
              border: `1px solid ${P.carbon}`,
              color: P.carbon,
              fontFamily: FONT_MONO,
              fontSize: 11, letterSpacing: '.22em', textTransform: 'uppercase',
              transition: 'background .25s, color .25s',
            }}
              onMouseEnter={(e) => {
                e.currentTarget.style.background = P.magenta;
                e.currentTarget.style.color = P.ivory;
                e.currentTarget.style.borderColor = P.magenta;
              }}
              onMouseLeave={(e) => {
                e.currentTarget.style.background = 'transparent';
                e.currentTarget.style.color = P.carbon;
                e.currentTarget.style.borderColor = P.carbon;
              }}>
              {t}
            </span>
          ))}
        </div>
      </div>
    </section>
  );
}

// ── Pull quote ─────────────────────────────────────────────
function PullQuote({ scale, project }) {
  const text = (project && project.quote) || '';
  if (!text.trim()) return null;
  const by = (project && project.quoteBy) || '';

  // Pull one short word and make it script-italic for accent.
  const words = text.trim().split(/\s+/);
  const accentIdx = words.findIndex((w, i) => i > 0 && w.length >= 4 && w.length <= 10);
  const idx = accentIdx > 0 ? accentIdx : Math.floor(words.length / 2);

  return (
    <section style={{
      padding: '160px 32px',
      background: P.lilac,
      color: P.carbon,
      borderTop: `1px solid rgba(34,26,29,.10)`,
      borderBottom: `1px solid rgba(34,26,29,.10)`,
    }}>
      <div style={{ maxWidth: 1200, margin: '0 auto' }}>
        <PReveal>
          <div style={{
            fontFamily: FONT_MONO,
            fontSize: 11, letterSpacing: '.24em', textTransform: 'uppercase',
            color: P.muted, marginBottom: 40,
          }}>
            [ Quote · Verbatim ]
          </div>
          <blockquote style={{
            margin: 0, maxWidth: 1100,
            fontFamily: FONT_DISPLAY,
            fontSize: `clamp(${36*scale}px, ${5*scale}vw, ${72*scale}px)`,
            lineHeight: 1.05, letterSpacing: '-.03em',
            color: P.carbon,
            whiteSpace: 'pre-wrap',
          }}>
            <span style={{ color: P.carbon, marginRight: 8 }}>“</span>
            {words.map((w, i) => (
              i === idx ? (
                <span key={i} style={{
                  fontFamily: FONT_SCRIPT, color: P.carbon,
                  fontSize: '.85em', letterSpacing: 0,
                  display: 'inline-block', transform: 'rotate(-3deg)',
                  margin: '0 .15em',
                }}>{w} </span>
              ) : <span key={i}>{w} </span>
            ))}
            <span style={{ color: P.carbon, marginLeft: 0 }}>”</span>
          </blockquote>
          {by && (
            <div style={{
              marginTop: 36,
              fontFamily: FONT_MONO,
              fontSize: 11, letterSpacing: '.24em', textTransform: 'uppercase',
              color: P.muted,
            }}>
              — {by}
            </div>
          )}
        </PReveal>
      </div>
    </section>
  );
}

// ── Credits ────────────────────────────────────────────────
function Credits({ project }) {
  const rows = (project.credits || []).filter(([role, name]) =>
    (role || '').trim() || (name || '').trim()
  );
  if (!rows.length) return null;
  return (
    <section style={{
      padding: '120px 32px',
      background: P.ivory,
      borderTop: `1px solid rgba(34,26,29,.10)`,
    }}>
      <div style={{
        maxWidth: 1200, margin: '0 auto',
        display: 'grid', gridTemplateColumns: '180px 1fr', gap: 60, alignItems: 'start',
      }}>
        <PReveal>
          <div style={{
            fontFamily: FONT_MONO,
            fontSize: 11, letterSpacing: '.24em', textTransform: 'uppercase',
            color: P.muted,
          }}>
            [ Credits ]
          </div>
        </PReveal>
        <PReveal delay={120}>
          <div className="c1-credits-grid" style={{
            display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '20px 60px',
          }}>
            {rows.map(([role, name], i) => (
              <div key={(role || '') + ':' + i} style={{
                display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
                gap: 16,
                borderBottom: `1px solid rgba(34,26,29,.12)`,
                paddingBottom: 14,
              }}>
                <span style={{
                  fontFamily: FONT_MONO,
                  fontSize: 10, letterSpacing: '.24em', textTransform: 'uppercase',
                  color: P.muted,
                }}>{role}</span>
                <span style={{
                  fontFamily: FONT_BODY, fontSize: 14, color: P.carbon,
                  textAlign: 'right',
                }}>{name}</span>
              </div>
            ))}
          </div>
        </PReveal>
      </div>
    </section>
  );
}

// ── Next project teaser ────────────────────────────────────
function NextProject({ next }) {
  const [hover, setHover] = React.useState(false);
  if (!next) return null;
  const label = next.client || NEXT_PROJECT.client;
  const tagline = next.tagline || NEXT_PROJECT.tagline;
  const href = next.n ? `project.html?id=${next.n}` : 'project.html';
  return (
    <a href={href} style={{ display: 'block', textDecoration: 'none' }}>
      <section
        onMouseEnter={() => setHover(true)}
        onMouseLeave={() => setHover(false)}
        data-cursor="Next →"
        style={{
          position: 'relative', padding: '160px 32px',
          background: P.ivory, color: P.carbon,
          borderTop: `1px solid rgba(34,26,29,.10)`,
          textAlign: 'center', overflow: 'hidden',
          cursor: 'pointer',
        }}>
        <div style={{
          position: 'absolute', inset: 0,
          background: P.ivory,
          opacity: hover ? 1 : 0,
          transition: 'opacity .8s ease',
        }} />
        <div style={{ position: 'relative' }}>
          <PReveal>
            <div style={{
              fontFamily: FONT_MONO,
              fontSize: 11, letterSpacing: '.28em', textTransform: 'uppercase',
              color: P.muted, marginBottom: 32,
            }}>
              Next chapter →
            </div>
          </PReveal>
          <PReveal delay={120}>
            <h2 style={{
              fontFamily: FONT_DISPLAY,
              fontSize: 'clamp(64px, 12vw, 200px)',
              margin: 0, lineHeight: .85, letterSpacing: '-.02em',
              color: P.carbon,
              transform: hover ? 'translateY(-6px)' : 'none',
              transition: 'transform .6s cubic-bezier(.16,1,.3,1)',
            }}>
              {label}
            </h2>
          </PReveal>
          <PReveal delay={240}>
            <p style={{
              marginTop: 32,
              fontFamily: FONT_BODY,
              fontSize: 17, fontWeight: 500,
              color: P.muted, maxWidth: '40ch', marginLeft: 'auto', marginRight: 'auto',
            }}>
              {tagline}
            </p>
          </PReveal>
        </div>
      </section>
    </a>
  );
}

// ── Other Case Studies (up to 3 OTHER projects, excluding current) ─
function OtherCaseStudies({ currentId }) {
  const picks = React.useMemo(() => {
    const arr = (window.WORK || []).filter((w) => w.n !== currentId);
    for (let i = arr.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [arr[i], arr[j]] = [arr[j], arr[i]];
    }
    return arr.slice(0, 3);
  }, [currentId]);
  if (!picks.length) return null;
  return (
    <section style={{
      position: 'relative',
      padding: '140px 32px',
      background: P.ivory,
      color: P.carbon,
      borderTop: `1px solid rgba(34,26,29,.10)`,
      overflow: 'hidden',
    }}>
      <PReveal>
        <div style={{
          display: 'grid', gridTemplateColumns: '1fr auto', alignItems: 'end',
          marginBottom: 60, gap: 24,
        }}>
          <h2 style={{
            fontFamily: FONT_DISPLAY,
            fontSize: 'clamp(48px, 8vw, 120px)', margin: 0,
            lineHeight: .9, letterSpacing: '-.02em',
            color: P.carbon,
          }}>
            Other{' '}
            <span style={{
              fontFamily: FONT_SCRIPT,
              color: P.magenta,
              fontSize: '.85em',
              letterSpacing: 0,
              display: 'inline-block',
              transform: 'rotate(-3deg) translateY(-.05em)',
            }}>chapters.</span>
          </h2>
          <a href="work.html" data-cursor="All" style={{
            fontFamily: FONT_MONO,
            fontSize: 11, letterSpacing: '.24em', textTransform: 'uppercase',
            color: P.muted, textDecoration: 'none',
          }}>View all →</a>
        </div>
      </PReveal>
      <div className="c1-other-scroller" style={{
        display: 'flex',
        gap: 20,
        overflowX: 'auto',
        overflowY: 'hidden',
        scrollSnapType: 'x mandatory',
        marginLeft: -32, marginRight: -32,
        paddingLeft: 32, paddingRight: 32,
        paddingBottom: 24,
        WebkitOverflowScrolling: 'touch',
      }}>
        <style>{`
          .c1-other-scroller::-webkit-scrollbar { height: 2px; }
          .c1-other-scroller::-webkit-scrollbar-thumb { background: rgba(34,26,29,.35); }
          .c1-other-scroller::-webkit-scrollbar-track { background: rgba(34,26,29,.08); }
          .c1-other-scroller > * {
            flex: 0 0 calc((100% - 40px) / 3);
            min-width: 280px;
            scroll-snap-align: start;
          }
          @media (max-width: 900px) {
            .c1-other-scroller > * { flex-basis: calc((100% - 20px) / 2); min-width: 260px; }
          }
          @media (max-width: 600px) {
            .c1-other-scroller > * { flex-basis: 78%; min-width: 0; }
          }
        `}</style>
        {picks.map((w, i) => {
          const heroSrc = w.hero && w.hero.src ? w.hero.src : null;
          const PlaceholderImage = window.PlaceholderImage || (() => null);
          return (
            <PReveal key={w.n} delay={i * 80} y={40}>
              <a href={`project.html?id=${w.n}`} data-cursor="Open →" style={{
                display: 'block', textDecoration: 'none', color: 'inherit',
              }}>
                <article style={{
                  position: 'relative', overflow: 'hidden', height: 460,
                  background: P.ivory2,
                }}>
                  {heroSrc
                    ? <img src={cfImg(heroSrc, 1000)} alt={(w.hero && w.hero.alt) || w.client} loading="lazy"
                        style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
                    : <PlaceholderImage palette={w.palette} style={{ height: '100%' }} />}
                  <div style={{
                    position: 'absolute', inset: 0, padding: 24,
                    display: 'flex', flexDirection: 'column', justifyContent: 'space-between',
                    background: `linear-gradient(to top, rgba(34,26,29,.6) 0%, transparent 55%)`,
                    color: P.ivory,
                  }}>
                    <div style={{ display: 'flex', justifyContent: 'space-between' }}>
                      <span style={{
                        fontFamily: FONT_MONO,
                        fontSize: 11, letterSpacing: '.22em', textTransform: 'uppercase',
                      }}>{w.n}</span>
                      <span style={{
                        fontFamily: FONT_MONO,
                        fontSize: 11, letterSpacing: '.22em', textTransform: 'uppercase',
                      }}>{w.year}</span>
                    </div>
                    <div>
                      {w.kind && (
                        <div style={{
                          fontFamily: FONT_MONO,
                          fontSize: 10, letterSpacing: '.22em', textTransform: 'uppercase',
                          color: P.ivory, marginBottom: 8,
                        }}>{w.kind}</div>
                      )}
                      <h3 style={{
                        fontFamily: FONT_DISPLAY,
                        fontSize: 'clamp(24px, 2.4vw, 36px)', margin: 0,
                        letterSpacing: '-.025em', lineHeight: .98, color: P.ivory,
                      }}>{w.client}</h3>
                      {w.tagline && (
                        <p className="c1-worklink-tagline" style={{
                          margin: '8px 0 0',
                          fontFamily: FONT_SCRIPT,
                          color: P.lilac,
                          fontSize: 16, lineHeight: 1.3,
                          transform: 'rotate(-3deg)',
                          transformOrigin: 'left center',
                          display: 'inline-block',
                          maxWidth: '32ch',
                        }}>{w.tagline}</p>
                      )}
                    </div>
                  </div>
                </article>
              </a>
            </PReveal>
          );
        })}
      </div>
    </section>
  );
}

// ── Footer ───────────────────────────────────────────────────────
// ── Main project page component ────────────────────────────
function ProjectPage({ tweaks, onBack }) {
  const t = tweaks || {};
  const accent = t.accent || P.magenta;
  const density = t.gridDensity || t.density || 'normal';   // 'tight'|'normal'|'generous'
  const typeScale = t.typeScale || 1;                       // .9 .. 1.2
  const visible = t.sections || { quote: true, credits: true };

  // Subscribe to live Store updates so admin edits flow through without reload.
  const [, force] = React.useReducer((x) => x + 1, 0);
  React.useEffect(() => {
    if (!window.Store || typeof window.Store.subscribe !== 'function') return;
    return window.Store.subscribe(() => force());
  }, []);

  const project = React.useMemo(() => resolveProject(), []);
  const next = React.useMemo(() => {
    const n = findNextProject(project.n);
    return n ? buildProjectFromWork(n) : null;
  }, [project.n]);

  const template = project.template || t.template || 'single';  // 'single' | 'interleaved'
  const rawRows = (Array.isArray(project.imageRows) && project.imageRows.length) ? project.imageRows : GRID_ROWS;
  // Some CMS projects have the hero photo uploaded a SECOND time as the first
  // gallery image (same picture, different upload URL) — so the "first photo"
  // shows twice: once as the hero, then again right below it. Drop any gallery
  // image that is a re-upload of the hero, matched on the original filename
  // (uploads are stored as "<random>-<originalName>", so stripping the random
  // prefix recovers the source name). Placeholder rows (no src) are untouched.
  const stemOf = (u) => {
    if (!u || typeof u !== 'string') return '';
    const file = u.split('/').pop().split('?')[0];
    return file.replace(/^[a-z0-9]{6,14}-/i, '').toLowerCase();
  };
  const heroStem = stemOf(project.hero && project.hero.src);
  const rows = heroStem
    ? rawRows
        .map((row) => ({ ...row, cols: (row.cols || []).filter((c) => !(c && c.src && stemOf(c.src) === heroStem)) }))
        .filter((row) => (row.cols || []).length > 0)
    : rawRows;

  // CSS vars for downstream consumers (cursor, mobile.css, etc).
  React.useEffect(() => {
    const root = document.documentElement;
    root.style.setProperty('--accent', accent);
    root.style.setProperty('--ink', P.carbon);
    root.style.setProperty('--bg', P.ivory);
    root.style.setProperty('--muted', P.muted);
    root.style.setProperty('--rule', 'rgba(34,26,29,.12)');
  }, [accent]);

  // Quartered slices for interleaved layout.
  const q = Math.ceil(rows.length / 4);
  const slice1 = rows.slice(0, q);
  const slice2 = rows.slice(q, q * 2);
  const slice3 = rows.slice(q * 2, q * 3);
  const slice4 = rows.slice(q * 3);

  return (
    <div style={{
      background: P.ivory,
      color: P.carbon,
      minHeight: '100%',
      fontFamily: FONT_BODY,
    }}>
      <style>{`
        @media (max-width: 900px) {
          .c1-other-grid { grid-template-columns: repeat(2, 1fr) !important; }
          .c1-credits-grid { grid-template-columns: 1fr !important; }
        }
        @media (max-width: 600px) {
          .c1-other-grid { grid-template-columns: 1fr !important; gap: 24px !important; }
        }
      `}</style>

      <BlockNav activePage="project" />
      <ProjectHero project={project} />
      <ProjectCTAs project={project} />

      {template === 'single' ? (
        <>
          {(project.textBoxes || []).map((b, i) => (
            <InlineTextBox key={i} box={b} index={i} scale={typeScale} />
          ))}
          <ImageGrid rows={rows} density={density} />
        </>
      ) : (
        <>
          <ImageGrid rows={slice1} density={density} />
          {project.textBoxes?.[0] && <InlineTextBox box={project.textBoxes[0]} index={0} scale={typeScale} />}
          <ImageGrid rows={slice2} density={density} />
          {project.textBoxes?.[1] && <InlineTextBox box={project.textBoxes[1]} index={1} scale={typeScale} />}
          <ImageGrid rows={slice3} density={density} />
          {project.textBoxes?.[2] && <InlineTextBox box={project.textBoxes[2]} index={2} scale={typeScale} />}
          <ImageGrid rows={slice4} density={density} />
          {(project.textBoxes || []).slice(3).map((b, i) => (
            <InlineTextBox key={'extra-' + i} box={b} index={i + 3} scale={typeScale} />
          ))}
        </>
      )}

      <TagChips project={project} />
      {visible.quote !== false && <PullQuote scale={typeScale} project={project} />}
      {visible.credits !== false && <Credits project={project} />}
      <NextProject next={next} />
      <OtherCaseStudies currentId={project.n} />
      <SiteFooter />
    </div>
  );
}

Object.assign(window, { ProjectPage });
