// Scoped in an IIFE: this file shares global scope with the other page
// entries, and the router now loads all of them into one document. Names
// like App, Hero, Footer, Cover and C are declared by several of them, so
// leaking them at top level lets the last-loaded page silently clobber the
// others' components. Only window.Pages.<ns> is exported.
(function () {
const { useState, useEffect, useRef } = React;
const C = window.POSTER_C;
const M = window.Marks;

// Doodle line-art glyphs (same vocabulary as the homepage organs:
// viewBox 0 0 400 400, ink stroke, round joins). Inherit currentColor.
const PencilGlyph = ({ s = 120 }) => (
  <svg width={s} height={s} viewBox="0 0 400 400" fill="none" stroke="currentColor"
    strokeWidth="16" strokeLinecap="round" strokeLinejoin="round" style={{ overflow: 'visible' }}>
    <path d="M255.42 125.116C250.255 118.261 237.442 113.896 233.061 121.402C212.422 156.766 162.075 218.142 144.553 251.795C142.723 266.842 132.131 300.448 133.057 302.672C133.446 303.603 160.556 281.443 167.647 273.33C174.737 265.216 245.857 163.62 265.308 141.635" />
    <path d="M142.822 254.882C151.683 253.099 156.277 257.106 162.251 266.417" />
    <path d="M237.117 114.677C247.573 100.339 250.526 90.7448 266.998 105.083" />
    <path fillRule="evenodd" clipRule="evenodd" d="M138.6 286.729C144.231 288.888 147.047 290.631 147.047 291.956C147.047 293.28 144.231 294.326 138.6 295.092V286.729Z" />
  </svg>
);

const BookGlyph = ({ s = 120 }) => (
  <svg width={s} height={s} viewBox="0 0 400 400" fill="none" stroke="currentColor"
    strokeWidth="16" strokeLinecap="round" strokeLinejoin="round" style={{ overflow: 'visible' }}>
    <path d="M93.5508 141.483C124.869 126.981 161.616 126.301 191.462 145.61" />
    <path d="M200.759 141.479C227.619 119.606 259.81 106.349 293.197 114.351" />
    <path d="M295.933 115.531C295.337 160.416 291.011 205.171 291.011 250.003" />
    <path d="M90.2695 148.557C103.126 185 105.153 224.545 113.79 261.799" />
    <path d="M192.553 146.79C195.18 186.791 189.82 226.715 189.82 266.517" />
    <path d="M119.806 266.516C146.259 254.871 171.912 258.644 192.552 277.721" />
    <path d="M199.663 271.824C225.199 256.622 263.229 239.373 291.011 256.488" />
    <path d="M309.062 132.634C311.538 178.736 303.011 225.444 306.872 271.233" />
    <path d="M204.587 285.39C228.22 255.857 271.867 242.926 299.762 273.004" />
    <path d="M124.182 278.311C148.34 268.73 182.21 266.565 201.854 287.749" />
  </svg>
);

// --- Striped placeholder "image" — intentional, with a mono caption ---
function Cover({ color, label, big = false, mark }) {
  const Mark = mark && M[mark];
  return (
    <div style={{
      position: 'relative',
      width: '100%',
      height: '100%',
      background: color,
      overflow: 'hidden',
      borderRadius: big ? 28 : 22,
      color: 'var(--ink)',
    }}>
      {/* diagonal hatch */}
      <div style={{
        position: 'absolute', inset: 0,
        backgroundImage: 'repeating-linear-gradient(45deg, rgba(26,26,26,0.10) 0, rgba(26,26,26,0.10) 1.5px, transparent 1.5px, transparent 11px)',
      }} />
      {Mark && (
        <div style={{ position: 'absolute', top: big ? 26 : 16, right: big ? 28 : 16, opacity: 0.9 }}>
          <Mark s={big ? 40 : 26} />
        </div>
      )}
      <div style={{
        position: 'absolute', left: big ? 26 : 14, bottom: big ? 22 : 12,
        display: 'flex', alignItems: 'center', gap: 8,
        fontFamily: "'JetBrains Mono', monospace",
        fontSize: big ? 12 : 10, letterSpacing: '0.14em', textTransform: 'uppercase',
        opacity: 0.7,
      }}>
        <span style={{ display: 'inline-block', width: 6, height: 6, background: 'var(--ink)' }} />
        {label}
      </div>
    </div>
  );
}

// --- Tiny Markdown renderer (poster-styled) ---
// Supports: # ## ### headings, paragraphs, > quotes, - / 1. lists,
// --- rules, **bold**, *italic*, `code`, [links](url), ![images](src).
function renderInline(text, kp) {
  const out = [];
  let rest = String(text), k = 0;
  const re = /(\*\*([^*]+)\*\*|\*([^*]+)\*|_([^_]+)_|`([^`]+)`|\[([^\]]+)\]\(([^)]+)\))/;
  while (rest.length) {
    const m = rest.match(re);
    if (!m) { out.push(rest); break; }
    if (m.index > 0) out.push(rest.slice(0, m.index));
    if (m[2] != null) out.push(<strong key={kp + k}>{m[2]}</strong>);
    else if (m[3] != null) out.push(<em key={kp + k}>{m[3]}</em>);
    else if (m[4] != null) out.push(<em key={kp + k}>{m[4]}</em>);
    else if (m[5] != null) out.push(<code key={kp + k} style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.85em', background: 'rgba(26,26,26,0.08)', padding: '2px 6px', borderRadius: 5 }}>{m[5]}</code>);
    else if (m[6] != null) out.push(<a key={kp + k} href={m[7]} style={{ textUnderlineOffset: 3 }}>{m[6]}</a>);
    rest = rest.slice(m.index + m[0].length);
    k++;
  }
  return out;
}

function MarkdownBody({ md }) {
  const lines = md.replace(/\r\n/g, '\n').split('\n');
  const blocks = [];
  const isStart = (l) => /^(#{1,3}\s|>\s?|[-*]\s+|\d+\.\s+|!\[)/.test(l) || l.trim() === '---';
  let i = 0;
  while (i < lines.length) {
    const line = lines[i];
    if (!line.trim()) { i++; continue; }
    let m;
    if ((m = line.match(/^(#{1,3})\s+(.*)$/))) { blocks.push({ t: 'h' + m[1].length, x: m[2] }); i++; continue; }
    if (line.trim() === '---' || line.trim() === '***') { blocks.push({ t: 'hr' }); i++; continue; }
    if ((m = line.trim().match(/^!\[([^\]]*)\]\(([^)]+)\)$/))) { blocks.push({ t: 'img', alt: m[1], src: m[2] }); i++; continue; }
    if (/^>\s?/.test(line)) {
      const buf = []; while (i < lines.length && /^>\s?/.test(lines[i])) { buf.push(lines[i].replace(/^>\s?/, '')); i++; }
      blocks.push({ t: 'q', x: buf.join(' ') }); continue;
    }
    if (/^[-*]\s+/.test(line)) {
      const items = []; while (i < lines.length && /^[-*]\s+/.test(lines[i])) { items.push(lines[i].replace(/^[-*]\s+/, '')); i++; }
      blocks.push({ t: 'ul', items }); continue;
    }
    if (/^\d+\.\s+/.test(line)) {
      const items = []; while (i < lines.length && /^\d+\.\s+/.test(lines[i])) { items.push(lines[i].replace(/^\d+\.\s+/, '')); i++; }
      blocks.push({ t: 'ol', items }); continue;
    }
    const buf = []; while (i < lines.length && lines[i].trim() && !isStart(lines[i])) { buf.push(lines[i]); i++; }
    blocks.push({ t: 'p', x: buf.join(' ') });
  }

  const h = { fontWeight: 700, letterSpacing: '-0.01em', lineHeight: 1.1, margin: '40px 0 14px' };
  return (
    <React.Fragment>
      {blocks.map((b, i) => {
        if (b.t === 'h1') return <h2 key={i} style={{ ...h, fontSize: 34, margin: '46px 0 16px' }}>{renderInline(b.x, i + 'a')}</h2>;
        if (b.t === 'h2') return <h3 key={i} style={{ ...h, fontSize: 26 }}>{renderInline(b.x, i + 'b')}</h3>;
        if (b.t === 'h3') return <h4 key={i} style={{ ...h, fontSize: 20, margin: '32px 0 10px' }}>{renderInline(b.x, i + 'c')}</h4>;
        if (b.t === 'hr') return <hr key={i} style={{ border: 'none', borderTop: '1px solid var(--ink)', opacity: 0.4, margin: '40px 0' }} />;
        if (b.t === 'q') return (
          <blockquote key={i} style={{
            margin: '36px 0', padding: '4px 0 4px 26px', borderLeft: '3px solid var(--ink)',
            fontFamily: "'Cormorant Garamond', serif", fontStyle: 'italic',
            fontSize: 'clamp(24px, 3.4vw, 32px)', lineHeight: 1.25, textWrap: 'pretty',
          }}>{renderInline(b.x, i + 'q')}</blockquote>
        );
        if (b.t === 'img') return (
          <figure key={i} style={{ margin: '34px 0' }}>
            <img src={b.src} alt={b.alt} style={{ width: '100%', borderRadius: 22, display: 'block' }} />
            {b.alt && <figcaption style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: 10, letterSpacing: '0.1em', textTransform: 'uppercase', opacity: 0.6, marginTop: 10 }}>{b.alt}</figcaption>}
          </figure>
        );
        if (b.t === 'ul') return <ul key={i} style={{ margin: '0 0 22px', paddingLeft: 24 }}>{b.items.map((it, j) => <li key={j} style={{ margin: '0 0 8px', textWrap: 'pretty', opacity: 0.92 }}>{renderInline(it, i + 'u' + j)}</li>)}</ul>;
        if (b.t === 'ol') return <ol key={i} style={{ margin: '0 0 22px', paddingLeft: 24 }}>{b.items.map((it, j) => <li key={j} style={{ margin: '0 0 8px', textWrap: 'pretty', opacity: 0.92 }}>{renderInline(it, i + 'o' + j)}</li>)}</ol>;
        return <p key={i} style={{ margin: '0 0 22px', textWrap: 'pretty', opacity: 0.92 }}>{renderInline(b.x, i + 'p')}</p>;
      })}
    </React.Fragment>
  );
}

// --- Hero ---
function Hero({ posts }) {
  return (
    <section className="page-section frame-wrap" style={{ padding: '26px 0 60px' }}>
      <div style={{
        fontFamily: "'JetBrains Mono', monospace", fontSize: 11,
        letterSpacing: '0.18em', textTransform: 'uppercase', opacity: 0.6,
        display: 'flex', alignItems: 'center', gap: 12, marginBottom: 18,
      }}>
        <span style={{ width: 28, height: 1.5, background: 'var(--ink)' }} />
        Field notes &amp; essays
      </div>

      <div className="writing-hero-grid" style={{
        display: 'grid', gridTemplateColumns: 'minmax(0,1fr) minmax(0,440px)',
        gap: 48, alignItems: 'end',
      }}>
        <div>
          <h1 className="hero-title" style={{
            margin: 0, fontWeight: 700, letterSpacing: '-0.03em',
            lineHeight: 0.92, fontSize: 'clamp(56px, 9vw, 132px)',
          }}>
            Writing
          </h1>
          <p style={{
            margin: '22px 0 0', maxWidth: 520,
            fontFamily: "'Cormorant Garamond', serif", fontStyle: 'italic',
            fontSize: 'clamp(20px, 2.6vw, 28px)', lineHeight: 1.3, opacity: 0.9,
            textWrap: 'pretty',
          }}>
            Notes from building things that see, listen, and speak — on the craft
            and the failures behind machines that almost feel alive.
          </p>
        </div>

        {/* book + pencil glyph blobs — same treatment as the homepage organs */}
        <div className="writing-hero-art" style={{ position: 'relative', height: 260, minWidth: 0 }}>
          {/* book — mustard, top-right */}
          <div style={{
            position: 'absolute', right: 0, top: 0, width: '62%', height: 168,
            background: C.mustard, borderRadius: 60, overflow: 'hidden', color: 'var(--on-accent)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}>
            <div style={{ position: 'absolute', left: 18, top: 18 }}><M.Plus s={16} /></div>
            <div style={{ position: 'absolute', right: 16, bottom: 14 }}><M.DotGrid cols={4} rows={3} gap={9} /></div>
            <BookGlyph s={118} />
          </div>
          {/* pencil — blue, lower-left, overlapping */}
          <div style={{
            position: 'absolute', left: 0, top: 78, width: '48%', height: 150,
            background: C.blue, borderRadius: 56, overflow: 'hidden', color: 'var(--on-accent)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}>
            <div style={{ position: 'absolute', left: 16, bottom: 14 }}><M.SmallPlus s={14} /></div>
            <PencilGlyph s={110} />
          </div>
          {/* pink accent */}
          <div style={{
            position: 'absolute', right: '4%', bottom: 0, width: '34%', height: 84,
            background: C.pink, borderRadius: 46, overflow: 'hidden',
          }}>
            <div style={{ position: 'absolute', left: '50%', top: '50%', transform: 'translate(-50%,-50%)' }}><M.Squiggle w={66} amp={9} cycles={3} /></div>
          </div>
        </div>
      </div>

      <div style={{
        marginTop: 46, borderTop: '1px solid var(--ink)', paddingTop: 12,
        display: 'flex', justifyContent: 'space-between',
        fontFamily: "'JetBrains Mono', monospace", fontSize: 10,
        letterSpacing: '0.1em', textTransform: 'uppercase', opacity: 0.7,
      }}>
        <span>{posts.length} {posts.length === 1 ? 'essay' : 'essays'}</span>
        <span>Updated {posts[0].date}</span>
        <span>Flat, minimal &amp; geometric</span>
      </div>
    </section>
  );
}

// --- Post card ---
function PostCard({ post, onOpen, i }) {
  const [hover, setHover] = useState(false);
  return (
    <article
      onClick={() => onOpen(post)}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        cursor: 'pointer', display: 'flex', flexDirection: 'column',
        animation: `fade-up 520ms cubic-bezier(.2,.8,.2,1) ${i * 60}ms both`,
        transform: hover ? 'translateY(-4px)' : 'translateY(0)',
        transition: 'transform 400ms cubic-bezier(.2,.8,.2,1)',
      }}>
      <div style={{
        height: 220, marginBottom: 16,
        boxShadow: hover ? '0 18px 44px -18px rgba(0,0,0,0.32)' : 'none',
        borderRadius: 22, transition: 'box-shadow 400ms',
      }}>
        <Cover color={post.color} label={post.cover} mark={CARD_MARKS[i % CARD_MARKS.length]} />
      </div>
      <div style={{
        display: 'flex', gap: 12, alignItems: 'center', marginBottom: 9,
        fontFamily: "'JetBrains Mono', monospace", fontSize: 10,
        letterSpacing: '0.1em', textTransform: 'uppercase', opacity: 0.65,
      }}>
        <span>{post.n}</span>
        <span style={{ width: 14, height: 1, background: 'currentColor', opacity: 0.5 }} />
        <span>{post.date}</span>
        <span>·</span>
        <span>{post.read}</span>
      </div>
      <h2 style={{
        margin: '0 0 8px', fontSize: 24, fontWeight: 700,
        letterSpacing: '-0.02em', lineHeight: 1.05,
        textDecoration: hover ? 'underline' : 'none', textUnderlineOffset: 4,
      }}>
        {post.title}
      </h2>
      <p style={{ margin: '0 0 14px', fontSize: 14.5, lineHeight: 1.5, opacity: 0.82, textWrap: 'pretty' }}>
        {post.dek}
      </p>
      <div style={{ display: 'flex', gap: 7, marginTop: 'auto' }}>
        {post.sample && (
          <span style={{
            fontFamily: "'JetBrains Mono', monospace", fontSize: 10,
            letterSpacing: '0.04em', padding: '4px 10px',
            border: '1.2px solid var(--ink)', borderRadius: 999, opacity: 0.85,
            background: 'var(--ink)', color: 'var(--paper)',
          }}>SAMPLE</span>
        )}
        {post.tags.map(t => (
          <span key={t} style={{
            fontFamily: "'JetBrains Mono', monospace", fontSize: 10,
            letterSpacing: '0.04em', padding: '4px 10px',
            border: '1.2px solid var(--ink)', borderRadius: 999, opacity: 0.85,
          }}>{t}</span>
        ))}
      </div>
    </article>
  );
}

const CARD_MARKS = ['Sunburst', 'Target', 'Plus', 'Bowtie', 'Asterisk', 'XMark'];

// --- Post grid ---
function PostGrid({ posts, onOpen }) {
  return (
    <section className="page-section frame-wrap" style={{ paddingBottom: 90 }}>
      <div className="card-grid" style={{
        display: 'grid', gap: '54px 40px',
        gridTemplateColumns: 'repeat(auto-fill, minmax(310px, 1fr))',
      }}>
        {posts.map((p, i) => <PostCard key={p.id} post={p} onOpen={onOpen} i={i} />)}
      </div>
    </section>
  );
}

// --- Article reader ---
function Article({ post, posts, onBack }) {
  const [md, setMd] = useState(null);
  useEffect(() => { window.scrollTo({ top: 0 }); }, [post.id]);
  useEffect(() => {
    setMd(null);
    if (!post.file) return;
    let live = true;
    fetch(post.file)
      .then(r => { if (!r.ok) throw new Error(r.status); return r.text(); })
      .then(t => {
        const { body } = window.Markdown.parseFrontmatter(t);
        if (live) setMd(body);
      })
      .catch(() => { if (live) setMd(null); }); // fall back to structured body
    return () => { live = false; };
  }, [post.id]);
  return (
    <div style={{ animation: 'fade-in 300ms ease both' }}>
      <div className="measure-wrap" style={{ padding: '8px 0 30px' }}>
        <button onClick={onBack} style={{
          display: 'inline-flex', alignItems: 'center', gap: 9,
          background: 'transparent', border: '1.2px solid var(--ink)',
          borderRadius: 999, padding: '8px 16px', cursor: 'pointer',
          color: 'var(--ink)', fontFamily: "'JetBrains Mono', monospace",
          fontSize: 11, letterSpacing: '0.04em', transition: 'all 180ms',
        }}
          onMouseEnter={(e) => { e.currentTarget.style.background = 'var(--ink)'; e.currentTarget.style.color = 'var(--paper)'; }}
          onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; e.currentTarget.style.color = 'var(--ink)'; }}>
          ← All writing
        </button>
      </div>

      <article className="measure-wrap" style={{ paddingBottom: 100 }}>
        <div className="detail-meta" style={{
          display: 'flex', gap: 12, alignItems: 'center', marginBottom: 18,
          fontFamily: "'JetBrains Mono', monospace", fontSize: 11,
          letterSpacing: '0.12em', textTransform: 'uppercase', opacity: 0.65,
        }}>
          <span>{post.kicker}</span>
          <span style={{ width: 16, height: 1, background: 'currentColor', opacity: 0.5 }} />
          <span>{post.date}</span>
          <span>·</span>
          <span>{post.read}</span>
        </div>

        <h1 style={{
          margin: '0 0 20px', fontWeight: 700, letterSpacing: '-0.03em',
          lineHeight: 0.98, fontSize: 'clamp(40px, 6vw, 68px)', textWrap: 'balance',
        }}>
          {post.title}
        </h1>

        <p style={{
          margin: '0 0 34px', fontFamily: "'Cormorant Garamond', serif",
          fontStyle: 'italic', fontSize: 'clamp(20px, 2.6vw, 26px)',
          lineHeight: 1.35, opacity: 0.9, textWrap: 'pretty',
        }}>
          {post.dek}
        </p>

        <div className="detail-cover" style={{ height: 320, marginBottom: 44 }}>
          <Cover color={post.color} label={post.cover} big mark="DotGrid" />
        </div>

        <div className="detail-body" style={{ fontSize: 18.5, lineHeight: 1.72 }}>
          {(md || post.md) ? (
            <MarkdownBody md={md || post.md} />
          ) : (
            (post.body || []).map((blk, i) => {
              if (blk.t === 'h') return (
                <h3 key={i} style={{
                  margin: '40px 0 14px', fontSize: 24, fontWeight: 700,
                  letterSpacing: '-0.01em', lineHeight: 1.1,
                }}>{blk.x}</h3>
              );
              if (blk.t === 'q') return (
                <blockquote key={i} style={{
                  margin: '36px 0', padding: '4px 0 4px 26px',
                  borderLeft: '3px solid var(--ink)',
                  fontFamily: "'Cormorant Garamond', serif", fontStyle: 'italic',
                  fontSize: 'clamp(24px, 3.4vw, 32px)', lineHeight: 1.25, textWrap: 'pretty',
                }}>{blk.x}</blockquote>
              );
              return <p key={i} style={{ margin: '0 0 22px', textWrap: 'pretty', opacity: 0.92 }}>{blk.x}</p>;
            })
          )}
        </div>

        <div className="tag-row" style={{ borderTop: '1px solid var(--ink)', marginTop: 30, paddingTop: 20, display: 'flex', gap: 8 }}>
          {post.tags.map(t => (
            <span key={t} style={{
              fontFamily: "'JetBrains Mono', monospace", fontSize: 10,
              letterSpacing: '0.06em', padding: '5px 12px',
              border: '1.2px solid var(--ink)', borderRadius: 999, opacity: 0.85,
            }}>{t}</span>
          ))}
        </div>
      </article>

      <NextUp current={post} posts={posts} onBack={onBack} />
    </div>
  );
}

function NextUp({ current, posts, onBack }) {
  const others = posts.filter(p => p.id !== current.id).slice(0, 3);
  if (!others.length) return null;
  return (
    <section className="page-section frame-wrap" style={{ paddingBottom: 90 }}>
      <div style={{
        fontFamily: "'JetBrains Mono', monospace", fontSize: 11,
        letterSpacing: '0.16em', textTransform: 'uppercase', opacity: 0.6,
        borderTop: '1px solid var(--ink)', paddingTop: 14, marginBottom: 26,
      }}>Keep reading</div>
      <div className="card-grid" style={{ display: 'grid', gap: 40, gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))' }}>
        {others.map((p, i) => <PostCard key={p.id} post={p} onOpen={onBack ? () => { window.__open(p); } : () => {}} i={i} />)}
      </div>
    </section>
  );
}

// --- Footer ---
function Footer() {
  return (
    <footer style={{
      borderTop: '1px solid var(--ink)', marginTop: 10,
    }}>
      <div className="frame-wrap" style={{
        padding: '26px 0 40px',
        display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', flexWrap: 'wrap', gap: 20,
        fontFamily: "'JetBrains Mono', monospace", fontSize: 10,
        letterSpacing: '0.1em', textTransform: 'uppercase', opacity: 0.75,
      }}>
        <div>
          <div style={{ fontFamily: "'Cormorant Garamond', serif", fontStyle: 'italic', fontSize: 15, textTransform: 'none', letterSpacing: 0, opacity: 0.9 }}>sensory</div>
          <div style={{ fontSize: 18, fontWeight: 700, fontFamily: "'Space Grotesk', sans-serif", letterSpacing: '-0.01em', textTransform: 'none' }}>Writing</div>
        </div>
        <div style={{ textAlign: 'right' }}>
          <div style={{ fontWeight: 700 }}>AI ENGINEER</div>
          <div style={{ opacity: 0.65 }}>© 2026</div>
        </div>
      </div>
    </footer>
  );
}

// --- Loading state (shown while article index is being fetched) ---
function LoadingState() {
  return (
    <div className="frame-wrap" style={{
      padding: '140px 0',
      fontFamily: "'JetBrains Mono', monospace", fontSize: 12,
      letterSpacing: '0.12em', textTransform: 'uppercase', opacity: 0.55,
    }}>
      Loading…
    </div>
  );
}

// --- Error state (shown when the article index fails to load entirely) ---
function ErrorState() {
  return (
    <div className="frame-wrap" style={{
      padding: '140px 0',
      fontFamily: "'JetBrains Mono', monospace", fontSize: 12,
      letterSpacing: '0.12em', textTransform: 'uppercase', opacity: 0.55,
    }}>
      Couldn&rsquo;t load writing — please refresh or try again later.
    </div>
  );
}

// --- App ---
function App() {
  const [posts, setPosts] = useState(null); // null while loading
  const [active, setActive] = useState(null);
  const [loadError, setLoadError] = useState(false);

  useEffect(() => {
    window.__open = (p) => { setActive(p); window.scrollTo({ top: 0 }); };
  }, []);

  useEffect(() => {
    let live = true;
    Promise.all(
      window.POST_INDEX.map((entry) =>
        fetch(entry.file)
          .then((r) => { if (!r.ok) throw new Error(r.status); return r.text(); })
          .then((raw) => {
            const { meta } = window.Markdown.parseFrontmatter(raw);
            return { ...entry, ...meta, color: C[meta.color] || meta.color };
          })
          .catch(() => null)
      )
    ).then((loaded) => {
      if (!live) return;
      const ok = loaded.filter(Boolean);
      if (ok.length === 0 && window.POST_INDEX.length > 0) {
        setLoadError(true);
        return;
      }
      setPosts(ok);
    }).catch(() => { if (live) setLoadError(true); });
    return () => { live = false; };
  }, []);

  const open = (p) => { setActive(p); };
  const back = () => { setActive(null); };

  return (
    <div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
      <window.Chrome.Nav active="Blog" />
      <main style={{ flex: 1 }}>
        {loadError ? (
          <ErrorState />
        ) : !posts ? (
          <LoadingState />
        ) : active ? (
          <Article post={active} posts={posts} onBack={back} />
        ) : (
          <React.Fragment>
            <Hero posts={posts} />
            <PostGrid posts={posts} onOpen={open} />
          </React.Fragment>
        )}
      </main>
      <Footer />
    </div>
  );
}

// Registered, not mounted: router.js owns mounting so Barba can swap
// containers and re-mount without a full page load.
window.Pages = window.Pages || {};
window.Pages.blog = App;
})();
