// app.jsx — main app, router, pages, grid

const { useState, useEffect, useMemo } = React;

// Map aspect setting to CSS aspect-ratio value
function aspectCss(aspect) {
  if (aspect === '4-3') return '4 / 3';
  if (aspect === '3-2') return '3 / 2';
  if (aspect === '1-1') return '1 / 1';
  return null; // native
}

// Mixed/masonry per-tile sizing
function mixedSpan(i) {
  // rough pattern — produces a nice irregular rhythm
  const patt = [[2,2],[1,1],[1,1],[2,1],[1,2],[1,1],[1,1],[1,1],[2,1],[1,1],[1,1],[1,1],[1,1],[2,2],[1,1],[1,1],[1,1],[1,1],[1,1],[1,1]];
  return patt[i % patt.length];
}

function Header({ page, navigate }) {
  return (
    <header className="yl-header" style={{
      display: 'grid',
      gridTemplateColumns: '1fr auto 2fr auto',
      alignItems: 'start', gap: 40,
      padding: 'var(--pad-header)',
      borderBottom: '1px solid var(--border-soft)',
    }}>
      <button
        onClick={() => navigate({ page: 'home' })}
        style={{
          fontFamily: 'var(--font-display)',
          fontWeight: 600, fontSize: 'var(--name-size)',
          lineHeight: 0.95, letterSpacing: '-0.02em',
          background: 'none', border: 'none', cursor: 'pointer',
          color: 'var(--fg)', padding: 0, textAlign: 'left',
        }}
      >{BIO.name}</button>

      <div style={{ fontFamily: 'var(--font-ui)', fontSize: 13, lineHeight: 1.6 }}>
        <div>{BIO.email}</div>
        <div style={{ color: 'var(--dim)' }}>{BIO.role}</div>
      </div>

      <div />

      <nav style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 4, fontFamily: 'var(--font-ui)', fontSize: 13 }}>
        <NavLink active={page === 'home'} onClick={() => navigate({ page: 'home' })}>Projects</NavLink>
        <NavLink active={page === 'about'} onClick={() => navigate({ page: 'about' })}>About</NavLink>
      </nav>
    </header>
  );
}

function NavLink({ active, onClick, children }) {
  return (
    <button onClick={onClick} style={{
      background: 'none', border: 'none', cursor: 'pointer',
      fontFamily: 'inherit', fontSize: 'inherit',
      color: active ? '#000' : 'var(--fg)',
      padding: 0, textDecoration: active ? 'underline' : 'none',
      textUnderlineOffset: 3,
    }}>{children}</button>
  );
}

function ProjectTitles({ projects, activeId, setActiveId }) {
  return (
    <h2 style={{
      fontFamily: 'var(--font-display)',
      fontSize: 'var(--proj-size)', fontWeight: 500,
      lineHeight: 1.05, letterSpacing: '-0.02em',
      margin: 0, padding: 'var(--pad-projects)',
      display: 'flex', flexWrap: 'wrap', gap: '0 0.4em',
      alignItems: 'baseline',
    }}>
      {projects.map((p, i) => (
        <React.Fragment key={p.id}>
          <button
            onClick={() => setActiveId(p.id)}
            className="rise"
            style={{
              fontFamily: 'inherit', fontSize: 'inherit', fontWeight: 'inherit',
              background: 'none', border: 'none', cursor: 'pointer',
              padding: 0, color: p.id === activeId ? 'var(--fg)' : 'var(--dim-heavy)',
              transition: 'color 200ms ease',
              position: 'relative',
              animationDelay: `${80 + i * 80}ms`,
            }}
          >
            {p.title}
            <sup style={{
              fontFamily: 'var(--font-mono)',
              fontSize: '0.32em', fontWeight: 400,
              marginLeft: '0.2em', verticalAlign: 'super',
              color: p.id === activeId ? 'var(--dim)' : 'var(--dim-heavy)',
            }}>({p.photos.length})</sup>
          </button>
          {i < projects.length - 1 && (
            <span className="rise" style={{
              color: 'var(--dim-heavy)', fontWeight: 300,
              animationDelay: `${80 + i * 80 + 40}ms`,
            }}> / </span>
          )}
        </React.Fragment>
      ))}
    </h2>
  );
}

function Grid({ project, gridMode, aspect, captions, density, onOpen }) {
  const photos = project.photos;
  const ar = aspectCss(aspect);

  if (gridMode === 'masonry') {
    return (
      <div style={{
        columnCount: 'var(--col-count)',
        columnGap: 'var(--grid-gap)',
        padding: 'var(--pad-grid)',
      }}>
        {photos.map((p, i) => (
          <Tile key={p.id} photo={p} index={i} onOpen={onOpen} captions={captions}
                containerStyle={{ breakInside: 'avoid', marginBottom: 'var(--grid-gap)' }}
                aspect={null} />
        ))}
      </div>
    );
  }

  if (gridMode === 'mixed') {
    return (
      <div style={{
        display: 'grid',
        gridTemplateColumns: 'repeat(var(--col-count), 1fr)',
        gridAutoRows: '160px',
        gap: 'var(--grid-gap)',
        padding: 'var(--pad-grid)',
      }}>
        {photos.map((p, i) => {
          const [cs, rs] = mixedSpan(i);
          return (
            <Tile key={p.id} photo={p} index={i} onOpen={onOpen} captions={captions}
                  containerStyle={{ gridColumn: `span ${cs}`, gridRow: `span ${rs}` }}
                  aspect={null}
                  fullHeight />
          );
        })}
      </div>
    );
  }

  // uniform
  return (
    <div style={{
      display: 'grid',
      gridTemplateColumns: 'repeat(var(--col-count), 1fr)',
      gap: 'var(--grid-gap)',
      padding: 'var(--pad-grid)',
    }}>
      {photos.map((p, i) => (
        <Tile key={p.id} photo={p} index={i} onOpen={onOpen} captions={captions} aspect={ar} />
      ))}
    </div>
  );
}

function Tile({ photo, index, onOpen, captions, aspect, containerStyle, fullHeight }) {
  const [hover, setHover] = useState(false);
  // Stagger tiles: first few come in individually, later ones group up
  const delay = 340 + Math.min(index, 20) * 40 + Math.max(0, index - 20) * 10;
  return (
    <figure
      onClick={() => onOpen(index)}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      className="rise"
      style={{
        margin: 0, cursor: 'pointer',
        position: 'relative', overflow: 'hidden',
        animationDelay: `${delay}ms`,
        ...containerStyle,
      }}
    >
      <div style={{
        position: 'relative', overflow: 'hidden',
        aspectRatio: aspect || undefined,
        height: fullHeight ? '100%' : undefined,
        background: 'var(--placeholder)',
      }}>
        <img
          src={photo.src}
          alt={photo.caption}
          loading="lazy"
          style={{
            width: '100%', height: '100%', objectFit: 'cover',
            transform: hover ? 'scale(1.02)' : 'scale(1)',
            filter: hover ? 'brightness(0.96)' : 'none',
            transition: 'transform 400ms ease, filter 200ms ease',
            display: 'block',
          }}
        />
      </div>
      {captions !== 'hidden' && (
        <figcaption style={{
          display: 'flex', justifyContent: 'space-between', gap: 10,
          marginTop: 8,
          fontFamily: 'var(--font-mono)', fontSize: 10,
          letterSpacing: '0.04em', textTransform: 'uppercase',
          color: 'var(--dim)',
          opacity: captions === 'always' ? 1 : (hover ? 1 : 0),
          transition: 'opacity 180ms ease',
        }}>
          <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{photo.caption}</span>
          <span style={{ flexShrink: 0 }}>{photo.place}</span>
        </figcaption>
      )}
    </figure>
  );
}

function Home({ activeId, setActiveId, onOpenPhoto }) {
  const project = PROJECTS.find(p => p.id === activeId) || PROJECTS[0];
  const tw = window.__tweakState;
  return (
    <>
      <div style={{ padding: '80px 0 0', borderBottom: '1px solid var(--border-soft)' }}>
        <ProjectTitles projects={PROJECTS} activeId={activeId} setActiveId={setActiveId} />
        <div className="yl-meta-row" style={{
          padding: '0 var(--pad-x) 40px',
          display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 40,
          alignItems: 'end',
        }}>
          <p className="rise" style={{
            fontFamily: 'var(--font-ui)', fontSize: 14, lineHeight: 1.55,
            color: 'var(--dim)', margin: 0, maxWidth: '38em',
            animationDelay: '180ms',
          }}>{project.blurb}</p>
          <div className="rise" style={{
            fontFamily: 'var(--font-mono)', fontSize: 11,
            letterSpacing: '0.06em', textTransform: 'uppercase',
            color: 'var(--dim)',
            display: 'grid', gridTemplateColumns: 'auto 1fr', gap: '4px 24px',
            justifySelf: 'end',
            animationDelay: '260ms',
          }}>
            <span>Year</span><span style={{ color: 'var(--fg)' }}>{project.year}</span>
            <span>Locations</span><span style={{ color: 'var(--fg)' }}>{project.location}</span>
            <span>Frames</span><span style={{ color: 'var(--fg)' }}>{project.photos.length}</span>
          </div>
        </div>
      </div>

      <Grid
        key={project.id}
        project={project}
        gridMode={tw.grid}
        aspect={tw.aspect}
        captions={tw.captions}
        density={tw.density}
        onOpen={(i) => onOpenPhoto(project, i)}
      />

      <Footer />
    </>
  );
}

function About() {
  return (
    <div>
      {/* Hero block — mirrors the Home project-title treatment */}
      <section style={{ padding: '80px 0 0', borderBottom: '1px solid var(--border-soft)' }}>
        <h2 className="rise" style={{
          fontFamily: 'var(--font-display)',
          fontSize: 'var(--proj-size)', fontWeight: 500,
          lineHeight: 1.05, letterSpacing: '-0.02em',
          margin: 0, padding: 'var(--pad-projects)',
          animationDelay: '80ms',
        }}>
          About
          <sup style={{
            fontFamily: 'var(--font-mono)',
            fontSize: '0.32em', fontWeight: 400,
            marginLeft: '0.2em', verticalAlign: 'super',
            color: 'var(--dim)',
          }}>(01)</sup>
        </h2>

        <div className="yl-meta-row" style={{
          padding: '0 var(--pad-x) 40px',
          display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 40,
          alignItems: 'end',
        }}>
          <p className="rise" style={{
            fontFamily: 'var(--font-ui)', fontSize: 14, lineHeight: 1.55,
            color: 'var(--fg)', margin: 0, maxWidth: '38em',
            animationDelay: '180ms',
          }}>
            Yingjie Liang is a photographer based between Shenzhen and the road.
            An entrepreneur until 2014, she has been traveling and photographing ever since.
          </p>
          <div className="rise" style={{
            fontFamily: 'var(--font-mono)', fontSize: 11,
            letterSpacing: '0.06em', textTransform: 'uppercase',
            color: 'var(--dim)',
            display: 'grid', gridTemplateColumns: 'auto 1fr', gap: '4px 24px',
            justifySelf: 'end',
            animationDelay: '260ms',
          }}>
            <span>Since</span><span style={{ color: 'var(--fg)' }}>2014</span>
            <span>Based</span><span style={{ color: 'var(--fg)' }}>Shenzhen · in transit</span>
            <span>Status</span><span style={{ color: 'var(--fg)' }}>Open to collaboration</span>
          </div>
        </div>
      </section>

      {/* Three simple sections, same label/value rhythm as the header info */}
      <AboutList label="Travels" items={BIO.journal.map(e => [e.year, e.places])} />
      <AboutList label="Exhibitions" items={BIO.exhibitions.map(e => [e.year, e.item])} />
      <AboutList
        label="Contact"
        items={[
          ['Email', BIO.email, `mailto:${BIO.email}`],
          ['Instagram', BIO.instagram, 'https://instagram.com'],
        ]}
      />

      <Footer />
    </div>
  );
}

function AboutList({ label, items }) {
  return (
    <section style={{
      padding: '80px var(--pad-x)',
      borderBottom: '1px solid var(--border-soft)',
    }}>
      <div className="yl-about-list rise" style={{
        display: 'grid', gridTemplateColumns: '200px 1fr',
        gap: 40, alignItems: 'start',
        animationDelay: '60ms',
      }}>
        <div style={{
          fontFamily: 'var(--font-mono)', fontSize: 11,
          letterSpacing: '0.06em', textTransform: 'uppercase',
          color: 'var(--dim)',
          position: 'sticky', top: 40,
        }}>
          {label}
        </div>
        <dl style={{ margin: 0, maxWidth: 640 }}>
          {items.map((row, i) => {
            const [k, v, href] = row;
            const inner = (
              <div className="rise" style={{
                display: 'grid', gridTemplateColumns: '120px 1fr auto',
                gap: 20, alignItems: 'baseline',
                padding: '16px 0',
                borderTop: i === 0 ? 'none' : '1px solid var(--border-soft)',
                animationDelay: `${120 + i * 50}ms`,
              }}>
                <dt style={{
                  fontFamily: 'var(--font-mono)', fontSize: 11,
                  letterSpacing: '0.04em', color: 'var(--dim)',
                }}>{k}</dt>
                <dd style={{
                  margin: 0,
                  fontFamily: 'var(--font-ui)', fontSize: 15,
                  color: 'var(--fg)', lineHeight: 1.45,
                }}>{v}</dd>
                {href && (
                  <span style={{
                    fontFamily: 'var(--font-mono)', fontSize: 13,
                    color: 'var(--dim)',
                  }}>↗</span>
                )}
              </div>
            );
            return href ? (
              <a key={i} href={href} target="_blank" rel="noreferrer"
                 style={{ textDecoration: 'none', color: 'inherit', display: 'block' }}>
                {inner}
              </a>
            ) : <React.Fragment key={i}>{inner}</React.Fragment>;
          })}
        </dl>
      </div>
    </section>
  );
}

function Section({ label, children }) {
  return (
    <section style={{
      padding: '0 var(--pad-x)',
      marginBottom: 'clamp(80px, 14vh, 140px)',
    }}>
      <div className="rise" style={{
        fontFamily: 'var(--font-mono)', fontSize: 11,
        letterSpacing: '0.14em', textTransform: 'uppercase',
        color: 'var(--dim)', marginBottom: 32,
        animationDelay: '40ms',
      }}>
        {label}
      </div>
      <div style={{ maxWidth: 820 }}>{children}</div>
    </section>
  );
}

function ContactLine({ label, value, href, delay = 0 }) {
  const [hover, setHover] = useState(false);
  const content = (
    <div
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      className="rise"
      style={{
        display: 'grid', gridTemplateColumns: '120px 1fr auto',
        gap: 20, alignItems: 'center',
        padding: '18px 0',
        borderTop: '1px solid var(--border-soft)',
        cursor: href ? 'pointer' : 'default',
        animationDelay: `${delay}ms`,
      }}
    >
      <span style={{
        fontFamily: 'var(--font-mono)', fontSize: 11,
        letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--dim)',
      }}>{label}</span>
      <span style={{
        fontFamily: 'var(--font-display)',
        fontSize: 22, fontStyle: href ? 'italic' : 'normal',
        color: 'var(--fg)',
      }}>{value}</span>
      {href && (
        <span style={{
          fontFamily: 'var(--font-mono)', fontSize: 14,
          color: hover ? 'var(--accent)' : 'var(--dim)',
          transform: hover ? 'translateX(4px)' : 'none',
          transition: 'all 200ms ease',
        }}>→</span>
      )}
    </div>
  );
  return href ? <a href={href} target="_blank" rel="noreferrer" style={{ textDecoration: 'none', color: 'inherit' }}>{content}</a> : content;
}

function Contact() {
  const [state, setState] = useState({ name: '', email: '', subject: 'editorial', message: '' });
  const [sent, setSent] = useState(false);
  const submit = (e) => {
    e.preventDefault();
    setSent(true);
    setTimeout(() => setSent(false), 4000);
  };

  return (
    <div style={{ padding: '80px var(--pad-x) 120px' }}>
      <h1 style={{
        fontFamily: 'var(--font-display)', fontSize: 'var(--proj-size)',
        fontWeight: 500, lineHeight: 1.05, letterSpacing: '-0.02em',
        margin: '0 0 16px',
      }}>Get in touch</h1>
      <p style={{
        fontFamily: 'var(--font-ui)', fontSize: 16, color: 'var(--dim)',
        maxWidth: '36em', lineHeight: 1.55, margin: '0 0 80px',
      }}>For editorial assignments, print inquiries, and exhibitions. I respond within a week.</p>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1.4fr', gap: 80, alignItems: 'start' }}>
        <dl style={{ margin: 0, fontFamily: 'var(--font-ui)', fontSize: 14, lineHeight: 1.55 }}>
          {[
            ['Email', BIO.email],
            ['Instagram', BIO.instagram],
            ['Based', BIO.based],
            ['Representation', 'Direct — no agency'],
            ['Print sales', 'Editions of 7, signed'],
          ].map(([k, v]) => (
            <div key={k} style={{
              display: 'grid', gridTemplateColumns: '120px 1fr',
              padding: '14px 0', borderTop: '1px solid var(--border-soft)',
              gap: 20,
            }}>
              <dt style={{ fontFamily: 'var(--font-mono)', fontSize: 11, letterSpacing: '0.06em', textTransform: 'uppercase', color: 'var(--dim)' }}>{k}</dt>
              <dd style={{ margin: 0 }}>{v}</dd>
            </div>
          ))}
        </dl>

        <form onSubmit={submit} style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
          <Field label="Your name">
            <input required value={state.name} onChange={e => setState({...state, name: e.target.value})} style={inputStyle} />
          </Field>
          <Field label="Email">
            <input required type="email" value={state.email} onChange={e => setState({...state, email: e.target.value})} style={inputStyle} />
          </Field>
          <Field label="Regarding">
            <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
              {[['editorial','Editorial'],['print','Prints'],['exhibition','Exhibition'],['other','Other']].map(([k, l]) => (
                <button key={k} type="button" onClick={() => setState({...state, subject: k})} style={{
                  fontFamily: 'var(--font-ui)', fontSize: 13,
                  padding: '8px 14px', borderRadius: 999,
                  border: '1px solid ' + (state.subject===k ? 'var(--fg)' : 'var(--border)'),
                  background: state.subject===k ? 'var(--fg)' : 'transparent',
                  color: state.subject===k ? 'var(--bg)' : 'var(--fg)',
                  cursor: 'pointer',
                }}>{l}</button>
              ))}
            </div>
          </Field>
          <Field label="Message">
            <textarea required rows={6} value={state.message} onChange={e => setState({...state, message: e.target.value})}
              style={{ ...inputStyle, resize: 'vertical', minHeight: 140, fontFamily: 'var(--font-ui)' }} />
          </Field>
          <div style={{ display: 'flex', alignItems: 'center', gap: 20 }}>
            <button type="submit" style={{
              fontFamily: 'var(--font-ui)', fontSize: 14,
              padding: '12px 28px', borderRadius: 2,
              background: 'var(--fg)', color: 'var(--bg)',
              border: 'none', cursor: 'pointer', letterSpacing: '0.02em',
            }}>Send message →</button>
            {sent && (
              <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, letterSpacing: '0.06em', textTransform: 'uppercase', color: 'var(--accent)' }}>
                ✓ Message queued
              </span>
            )}
          </div>
        </form>
      </div>
      <Footer />
    </div>
  );
}

function Field({ label, children }) {
  return (
    <label style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, letterSpacing: '0.06em', textTransform: 'uppercase', color: 'var(--dim)' }}>{label}</span>
      {children}
    </label>
  );
}

const inputStyle = {
  fontFamily: 'var(--font-ui)', fontSize: 15,
  padding: '10px 0', background: 'transparent',
  border: 'none', borderBottom: '1px solid var(--border)',
  color: 'var(--fg)', outline: 'none',
  borderRadius: 0,
};

function Footer() {
  return (
    <footer className="yl-footer" style={{
      display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 40,
      padding: '60px var(--pad-x) 40px',
      borderTop: '1px solid var(--border-soft)',
      fontFamily: 'var(--font-mono)', fontSize: 10,
      letterSpacing: '0.08em', textTransform: 'uppercase',
      color: 'var(--dim)', marginTop: 80,
    }}>
      <div>© {new Date().getFullYear()} Yingjie Liang</div>
      <div style={{ textAlign: 'center' }}>All photographs author's own</div>
      <div style={{ textAlign: 'right' }}>Last updated · Apr 2026</div>
    </footer>
  );
}

// Theme vars generator
function buildThemeVars(state) {
  const accentMap = {
    crimson: '#d83434',
    marigold: '#e8a33d',
    forest: '#2f5d3a',
    cobalt: '#2e52c7',
    ink: '#111111',
  };

  const themes = {
    editorial: {
      '--bg': '#ffffff',
      '--fg': '#0a0a0a',
      '--dim': '#7a7a7a',
      '--dim-heavy': '#c8c8c8',
      '--border': '#d8d8d8',
      '--border-soft': '#ececec',
      '--placeholder': '#f2f2f2',
      '--tw-bg': '#ffffff',
      '--tw-fg': '#0a0a0a',
      '--tw-dim': '#7a7a7a',
      '--tw-border': '#d8d8d8',
      '--hero-shadow': 'none',
    },
    magazine: {
      '--bg': '#f6f1e8',
      '--fg': '#201a12',
      '--dim': '#7a6d5a',
      '--dim-heavy': '#cdc0a8',
      '--border': '#d5c9b1',
      '--border-soft': '#e8dec9',
      '--placeholder': '#ece3d0',
      '--tw-bg': '#f6f1e8',
      '--tw-fg': '#201a12',
      '--tw-dim': '#7a6d5a',
      '--tw-border': '#d5c9b1',
      '--hero-shadow': '0 24px 60px rgba(80,60,30,0.08)',
    },
    experimental: {
      '--bg': '#0d0d0f',
      '--fg': '#f0ece4',
      '--dim': '#78757b',
      '--dim-heavy': '#3b3a3e',
      '--border': '#2e2d31',
      '--border-soft': '#1c1b1f',
      '--placeholder': '#1a1a1c',
      '--tw-bg': '#0d0d0f',
      '--tw-fg': '#f0ece4',
      '--tw-dim': '#78757b',
      '--tw-border': '#2e2d31',
      '--hero-shadow': 'none',
    },
  };

  const fonts = {
    grotesk: {
      '--font-display': '"Neue Haas Grotesk Display Pro", "Neue Haas Grotesk", "Helvetica Neue", Helvetica, Arial, sans-serif',
      '--font-ui': '"Neue Haas Grotesk Text Pro", "Helvetica Neue", Helvetica, Arial, sans-serif',
      '--font-mono': '"JetBrains Mono", ui-monospace, "SFMono-Regular", Menlo, monospace',
    },
    serif: {
      '--font-display': '"Instrument Serif", "Cormorant Garamond", "Times New Roman", Times, serif',
      '--font-ui': '"Inter", "Helvetica Neue", Helvetica, Arial, sans-serif',
      '--font-mono': '"JetBrains Mono", ui-monospace, Menlo, monospace',
    },
    mono: {
      '--font-display': '"JetBrains Mono", ui-monospace, Menlo, monospace',
      '--font-ui': '"JetBrains Mono", ui-monospace, Menlo, monospace',
      '--font-mono': '"JetBrains Mono", ui-monospace, Menlo, monospace',
    },
  };

  const densities = {
    tight: {
      '--pad-x': '24px',
      '--pad-header': '20px 24px',
      '--pad-projects': '120px 24px 24px',
      '--pad-grid': '24px',
      '--grid-gap': '8px',
      '--col-count': '3',
      '--name-size': '36px',
      '--proj-size': '40px',
    },
    normal: {
      '--pad-x': '48px',
      '--pad-header': '32px 48px',
      '--pad-projects': '160px 48px 40px',
      '--pad-grid': '48px',
      '--grid-gap': '16px',
      '--col-count': '3',
      '--name-size': '42px',
      '--proj-size': '52px',
    },
    airy: {
      '--pad-x': '80px',
      '--pad-header': '48px 80px',
      '--pad-projects': '200px 80px 60px',
      '--pad-grid': '80px',
      '--grid-gap': '28px',
      '--col-count': '2',
      '--name-size': '48px',
      '--proj-size': '64px',
    },
  };

  return {
    ...themes[state.theme],
    ...fonts[state.font],
    ...densities[state.density],
    '--accent': accentMap[state.accent] || accentMap.crimson,
    '--mobile-cols': String(state.mobileCols || 2),
  };
}

function App() {
  const [state, setState] = useState(() => {
    const defaults = window.__TWEAK_DEFAULTS;
    try {
      const saved = JSON.parse(localStorage.getItem('yl_tweaks') || 'null');
      if (saved) return { ...defaults, ...saved };
    } catch {}
    return defaults;
  });

  useEffect(() => {
    try { localStorage.setItem('yl_tweaks', JSON.stringify(state)); } catch {}
    window.__tweakState = state;
  }, [state]);

  const [route, setRoute] = useState(() => {
    try {
      const saved = JSON.parse(localStorage.getItem('yl_route') || 'null');
      if (saved) return saved;
    } catch {}
    return { page: 'home' };
  });
  const [activeProjectId, setActiveProjectId] = useState(PROJECTS[0].id);
  const [lightbox, setLightbox] = useState(null); // { project, index } or null

  useEffect(() => {
    try { localStorage.setItem('yl_route', JSON.stringify(route)); } catch {}
  }, [route]);

  const themeVars = useMemo(() => buildThemeVars(state), [state]);
  window.__tweakState = state;

  const navigate = (r) => { setRoute(r); window.scrollTo(0, 0); };

  const openPhoto = (project, i) => setLightbox({ project, index: i });

  return (
    <div className="yl-root" style={{
      ...themeVars,
      background: 'var(--bg)',
      color: 'var(--fg)',
      minHeight: '100vh',
      fontFamily: 'var(--font-ui)',
    }}>
      <style>{`
        * { box-sizing: border-box; }
        body { margin: 0; background: var(--bg); }
        html { scroll-behavior: smooth; }
        ::selection { background: var(--accent); color: var(--bg); }
        @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
        @keyframes imgFade { from { opacity: 0.2; transform: scale(0.995); } to { opacity: 1; transform: none; } }
        @keyframes pageFade {
          from { opacity: 0; transform: translateY(12px); filter: blur(4px); }
          to { opacity: 1; transform: none; filter: none; }
        }
        @keyframes rise {
          from { opacity: 0; transform: translateY(24px); }
          to { opacity: 1; transform: none; }
        }
        @keyframes reveal {
          from { clip-path: inset(0 100% 0 0); opacity: 0; }
          to { clip-path: inset(0 0 0 0); opacity: 1; }
        }
        .rise {
          opacity: 0;
          animation: rise 900ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
        }

        .reveal {
          opacity: 0;
          animation: reveal 1400ms cubic-bezier(0.65, 0, 0.35, 1) forwards;
        }
        input:focus, textarea:focus { border-bottom-color: var(--fg) !important; }
        @media (prefers-reduced-motion: reduce) {
          .rise, .reveal { animation: none !important; opacity: 1 !important; }
        }

        /* ─── Mobile (≤720px) ─────────────────────────────────────── */
        @media (max-width: 720px) {
          :root, body { --col-count: var(--mobile-cols) !important; }
          .yl-root {
            --pad-x: 16px !important;
            --pad-header: 16px 16px 14px !important;
            --pad-projects: 56px 16px 20px !important;
            --pad-grid: 16px !important;
            --grid-gap: 6px !important;
            --name-size: 22px !important;
            --proj-size: 30px !important;
            --col-count: var(--mobile-cols) !important;
          }
          /* Header — single row, name + nav only */
          .yl-header {
            grid-template-columns: 1fr auto !important;
            gap: 16px !important;
            align-items: center !important;
          }
          .yl-header > *:nth-child(2) { display: none !important; }    /* contact line */
          .yl-header > *:nth-child(3) { display: none !important; }    /* spacer */
          .yl-header nav { flex-direction: row !important; gap: 16px !important; }

          /* Meta rows on Home + About: stack */
          .yl-meta-row {
            grid-template-columns: 1fr !important;
            gap: 20px !important;
            padding: 0 var(--pad-x) 24px !important;
          }
          .yl-meta-row > div:last-child { justify-self: start !important; }

          /* About list: stack the sticky label above the items */
          .yl-about-list {
            grid-template-columns: 1fr !important;
            gap: 20px !important;
          }
          .yl-about-list > div:first-child { position: static !important; }

          /* Footer: stack, left-align */
          .yl-footer {
            grid-template-columns: 1fr !important;
            gap: 10px !important;
            padding: 32px 16px 24px !important;
            margin-top: 40px !important;
          }
          .yl-footer > div { text-align: left !important; }

          /* Project titles — wrap nicely */
          .yl-root h2 { line-height: 1.1 !important; }
        }`}</style>

      <Header page={route.page} navigate={navigate} />

      <main key={route.page} style={{ animation: 'pageFade 600ms cubic-bezier(0.22, 1, 0.36, 1)' }} data-screen-label={route.page}>
        {route.page === 'about' ? <About /> : (
          <Home activeId={activeProjectId} setActiveId={setActiveProjectId} onOpenPhoto={openPhoto} />
        )}
      </main>

      {lightbox && (
        <Lightbox
          project={lightbox.project}
          startIndex={lightbox.index}
          onClose={() => setLightbox(null)}
          themeVars={themeVars}
        />
      )}

      <TweaksPanel state={state} setState={setState} />
    </div>
  );
}

window.App = App;
