/* Logo Generator — interactive tool that lets anyone produce an
   on-brand logo asset in seconds. Pick a mark, set the colours, the
   background, padding and corner radius, then export PNG (any size)
   or the original vector SVG. */

/* Canonical paths — kept in sync with primitives.jsx + scripts/brand.js.
   Native viewBoxes:
     ARROW (emblem): 7.925 9.935 → 128.055 130.065  (120.13 × 120.13)
     NR (lockup):    arrow + monogram, combined 7.925 9.935 → 328.755 130.065  (320.83 × 120.13)
     FULL (wordmark): 0 0 → 815 215 (separate emblem path inside)
*/
const LG_ARROW_PATH = 'M128.055 9.93506H87.925H7.92499V50.0651H59.555L7.92499 101.685L22.115 115.875L36.305 130.065L87.935 78.4351V130.065H127.905L128.055 9.93506ZM124.625 126.635V50.0651L124.615 50.0551V13.3651H11.355V46.6351H67.845L61.985 52.4951L12.785 101.695L24.545 113.455L36.305 125.215L85.505 76.0151L91.365 70.1551V126.635H124.625Z';
const LG_NR_PATH = 'M300.785 89.2251C315.375 82.1851 324.475 70.1751 324.475 52.4951V52.1551C324.475 39.7951 320.865 31.0451 313.495 23.6651C305.075 15.2551 291.685 9.93506 270.235 9.93506H211.545V62.8051L168.945 9.93506H127.935L127.925 50.0551L127.935 50.0651L127.925 130.065H171.185V74.6351L215.805 130.065H251.705V95.7451H260.285L282.935 130.065H328.755L300.775 89.2251H300.785ZM269.035 67.0851C278.635 67.0851 284.475 62.7951 284.475 55.4151L284.485 55.4251V55.0751C284.485 47.3451 278.655 43.5751 269.215 43.5751H251.705V67.0851H269.035Z';
const LG_FULL_EMBLEM = 'M394.478 132.165C413.196 123.133 424.871 107.725 424.871 85.042V84.6058C424.871 68.7485 420.24 57.5227 410.784 48.0545C399.982 37.2649 382.803 30.4396 355.284 30.4396H279.988V98.2692L225.334 30.4396H177.121H125.636H23V81.9244H89.2387L23 148.15L41.2051 166.355L59.4101 184.56L125.649 118.322V184.56H228.208V113.446L285.453 184.56H331.511V140.53H342.519L371.577 184.56H430.362L394.465 132.165H394.478ZM172.72 81.9244V180.16H130.049V107.699L122.531 115.217L59.4101 178.338L44.3226 163.251L29.2351 148.163L92.3563 85.042L99.8743 77.5239H27.4005V34.8402H172.707V81.9116L172.72 81.9244ZM373.553 88.7882C373.553 98.2564 366.061 103.76 353.744 103.76H331.511V73.5981H353.975C366.086 73.5981 373.566 78.4348 373.566 88.352V88.801L373.553 88.7882Z';
const LG_FULL_WORDS = [
  'M723.901 112.471H742.542L772.204 150.575V112.471H791.974V184.56H774.578L743.684 145.02V184.56H723.914V112.471H723.901Z',
  'M658.099 112.471H716.075V129.355H677.869V140.376H712.47V156.13H677.869V167.561H716.588V184.548H658.099V112.459V112.471Z',
  'M583.059 148.715V148.522C583.059 128.329 598.903 112.484 620.29 112.484C632.568 112.484 640.984 116.154 648.412 122.491L637.122 136.155C632.068 131.895 627.423 129.625 620.393 129.625C610.488 129.625 602.675 137.938 602.675 148.535V148.728C602.675 160.018 610.501 168.036 621.483 168.036C626.23 168.036 629.797 167.048 632.568 165.162V156.746H619.007V142.685H650.786V173.771C643.358 180.006 633.453 184.56 620.688 184.56C599.006 184.56 583.059 169.807 583.059 148.715Z',
  'M517.256 112.471H575.233V129.355H537.027V140.376H571.628V156.13H537.027V167.561H575.746V184.548H517.256V112.459V112.471Z',
  'M440.228 112.471H476.484C488.313 112.471 496.306 115.358 501.463 120.195C505.954 124.416 508.25 130.073 508.25 136.976V137.181C508.25 148.304 502.118 155.617 492.47 159.427L510.765 184.548H486.337L470.891 162.712H461.474V184.548H440.228V112.459V112.471ZM475.612 147.073C482.732 147.073 487.004 143.775 487.004 138.528V138.323C487.004 132.562 482.514 129.676 475.496 129.676H461.474V147.085H475.599L475.612 147.073Z',
  'M731.175 91.0205L741.927 78.1782C748.996 83.7462 756.552 86.5302 764.519 86.5302C769.792 86.5302 772.576 84.8367 772.576 81.7576V81.5523C772.576 78.563 770.28 77.1775 760.735 74.7912C746.006 71.4042 734.473 67.3244 734.473 53.0067V52.8014C734.473 39.7667 744.723 30.5038 761.44 30.5038C773.384 30.5038 782.532 33.6855 790.101 39.7538L780.454 53.3916C774.09 48.7088 767.213 46.5278 760.953 46.5278C756.283 46.5278 753.986 48.3111 753.986 51.0053V51.2106C753.986 54.2896 756.373 55.8805 766.123 57.9717C782.044 61.4485 792 66.6316 792 79.6664V79.8717C792 94.0996 780.749 102.567 763.839 102.567C751.408 102.567 739.964 98.6797 731.201 91.0205H731.175Z',
  'M659.048 70.2111V30.4781H678.87V69.8005C678.87 80.0128 683.924 84.8624 691.917 84.8624C699.91 84.8624 705.055 80.3079 705.055 70.3009V30.4781H724.876V69.7107C724.876 92.4574 711.829 102.567 691.712 102.567C671.595 102.567 659.061 92.4574 659.061 70.2111H659.048Z',
  'M651.748 30.4781L628.847 65.5924L652.736 102.554H630.541L617.198 80.7184L603.753 102.554H582.045L605.934 65.9004L583.046 30.4781H605.241L617.493 50.7616L630.041 30.4781H651.748Z',
  'M517.256 30.4781H575.233V47.3617H537.027V58.3823H571.628V74.1369H537.027V85.568H575.746V102.554H517.256V30.4781Z',
  'M440.228 30.4781H458.869L488.531 68.5817V30.4781H508.301V102.567H490.905L460.011 63.0266V102.567H440.241V30.4781H440.228Z',
];

/* Stroke attrs to thicken any path. Stroke matches fill so it visually
   "grows" the glyph; paint-order keeps the fill on top so corners stay
   crisp. `thickness` is in path-coordinate units (the canonical paths
   are ~120 units wide), so `2` is subtle, `8` is heavy. */
function lgStrokeAttrs(fill, thickness) {
  if (!thickness) return '';
  return ` stroke="${fill}" stroke-width="${thickness}" stroke-linejoin="round" paint-order="stroke fill"`;
}

/* --- Mark variants. Each describes the geometry of a logo type so the
   generator can compose preview SVG and export-ready SVG identically. */
const MARKS = {
  emblem: {
    label: 'Emblem (arrow)',
    aspect: 1, // 120.13 × 120.13 → square
    viewBox: '7.925 9.935 120.13 120.13',
    paths: (fill, _word, thickness = 0) => (
      `<path d="${LG_ARROW_PATH}" fill="${fill}" fill-rule="evenodd" clip-rule="evenodd"${lgStrokeAttrs(fill, thickness)}/>`
    ),
    desc: 'Square mark. Favicons, app icons, social avatars.',
  },
  nr: {
    label: 'NR lockup',
    aspect: 320.83 / 120.13,
    viewBox: '7.925 9.935 320.83 120.13',
    paths: (fill, _word, thickness = 0) => (
      `<path d="${LG_ARROW_PATH}" fill="${fill}" fill-rule="evenodd" clip-rule="evenodd"${lgStrokeAttrs(fill, thickness)}/>` +
      `<path d="${LG_NR_PATH}" fill="${fill}" fill-rule="evenodd" clip-rule="evenodd"${lgStrokeAttrs(fill, thickness)}/>`
    ),
    desc: 'Arrow + NR monogram. Small-scale chrome, partner tiles.',
  },
  wordmark: {
    label: 'Primary wordmark',
    aspect: 815 / 215,
    viewBox: '0 0 815 215',
    paths: (fill, word, thickness = 0) => (
      `<path d="${LG_FULL_EMBLEM}" fill="${fill}"${lgStrokeAttrs(fill, thickness)}/>` +
      LG_FULL_WORDS.map((d) => `<path d="${d}" fill="${word}"/>`).join('')
    ),
    desc: 'Full Nexus ReGen lockup. Headers, decks, press, signatures.',
    twoColor: true,
  },
};

/* --- Curated swatches. Free hex input also supported. */
const LG_SWATCHES = {
  navy:    { hex: '#121541', label: 'Penn Blue 600' },
  navyDk:  { hex: '#0E1034', label: 'Penn Blue 700' },
  navyXl:  { hex: '#06081C', label: 'Penn Blue 900' },
  yellow:  { hex: '#F7EC33', label: 'Aureolin 400' },
  yellowDk:{ hex: '#E0D420', label: 'Aureolin 500' },
  white:   { hex: '#FFFFFF', label: 'Paper' },
  paper:   { hex: '#F8F9FC', label: 'Brand 25' },
  black:   { hex: '#000000', label: 'Black' },
  none:    { hex: 'transparent', label: 'Transparent' },
};

/* --- Build the full SVG string for the configured logo. Used both
   for the inline preview and for downloads. */
function buildLogoSVG({ markKey, fill, word, bg, paddingPct, radius, thickness = 0, exportW, exportH }) {
  const m = MARKS[markKey];
  // The viewBox is normalised to a [0..outerW] × [0..outerH] coordinate
  // system that already includes padding around the mark.
  const innerW = 1000;
  const innerH = innerW / m.aspect;
  const padW = innerW * (paddingPct / 100);
  const padH = innerH * (paddingPct / 100);
  const outerW = innerW + padW * 2;
  const outerH = innerH + padH * 2;

  // Native mark coordinates → translate into the inner area
  const [vx, vy, vw] = m.viewBox.split(' ').map(Number);
  const scale = innerW / vw;
  const tx = padW - vx * scale;
  const ty = padH - vy * scale;

  const w = exportW || Math.round(outerW);
  const h = exportH || Math.round(outerH);

  const bgRect = bg && bg !== 'transparent'
    ? `<rect width="${outerW}" height="${outerH}" rx="${radius}" fill="${bg}"/>` : '';

  const paths = m.twoColor ? m.paths(fill, word, thickness) : m.paths(fill, null, thickness);

  return `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${outerW} ${outerH}" fill="none">
  ${bgRect}
  <g transform="translate(${tx} ${ty}) scale(${scale})">${paths}</g>
</svg>`;
}

function LG_Swatch({ value, onChange, label }) {
  return (
    <div>
      <Label>{label}</Label>
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
        {Object.entries(LG_SWATCHES).map(([k, s]) => (
          <button
            key={k}
            onClick={() => onChange(s.hex)}
            title={s.label}
            style={{
              width: 28, height: 28, borderRadius: 6,
              padding: 0, cursor: 'pointer',
              background: s.hex === 'transparent' ? 'repeating-conic-gradient(#ddd 0% 25%, #fff 0% 50%) 0 / 12px 12px' : s.hex,
              border: value === s.hex ? '2px solid var(--brand-600)'
                : '1px solid var(--border-secondary)',
              boxShadow: value === s.hex ? '0 0 0 2px var(--accent-400)' : 'none',
            }}
          />
        ))}
        <input
          type="color"
          value={value && value.startsWith('#') ? value : '#121541'}
          onChange={(e) => onChange(e.target.value.toUpperCase())}
          title="Custom colour"
          style={{
            width: 28, height: 28, padding: 0, border: '1px solid var(--border-secondary)',
            borderRadius: 6, cursor: 'pointer', background: 'var(--bg-primary)',
          }}
        />
      </div>
      <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11,
        color: 'var(--text-quaternary)', marginTop: 6 }}>
        {value || 'transparent'}
      </div>
    </div>
  );
}

function LG_Number({ label, value, min, max, step = 1, suffix, onChange }) {
  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
        <Label>{label}</Label>
        <span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text-tertiary)' }}>
          {value}{suffix || ''}
        </span>
      </div>
      <input
        type="range" min={min} max={max} step={step} value={value}
        onChange={(e) => onChange(Number(e.target.value))}
        style={{ width: '100%', accentColor: 'var(--brand-600)' }}
      />
    </div>
  );
}

/* Convert SVG string to PNG via canvas. Returns blob URL (revoke on cleanup). */
async function svgToPngBlobUrl(svg, width, height) {
  return new Promise((resolve, reject) => {
    const blob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const img = new Image();
    img.onload = () => {
      const c = document.createElement('canvas');
      c.width = width; c.height = height;
      const ctx = c.getContext('2d');
      ctx.clearRect(0, 0, width, height);
      ctx.drawImage(img, 0, 0, width, height);
      URL.revokeObjectURL(url);
      c.toBlob((b) => {
        if (!b) return reject(new Error('Canvas toBlob failed'));
        resolve(URL.createObjectURL(b));
      }, 'image/png');
    };
    img.onerror = (e) => { URL.revokeObjectURL(url); reject(e); };
    img.src = url;
  });
}

function LogoGenerator() {
  const [markKey, setMarkKey] = useState('emblem');
  const [fill, setFill] = useState('#F7EC33');
  const [word, setWord] = useState('#FFFFFF');
  const [bg, setBg] = useState('#121541');
  const [paddingPct, setPaddingPct] = useState(15);
  const [radius, setRadius] = useState(0);
  const [thickness, setThickness] = useState(0);
  const [exportSize, setExportSize] = useState(1024);
  const [downloads, setDownloads] = useState({});
  const previewSvg = useMemo(
    () => buildLogoSVG({ markKey, fill, word, bg, paddingPct, radius, thickness }),
    [markKey, fill, word, bg, paddingPct, radius, thickness]
  );

  const m = MARKS[markKey];
  const exportW = exportSize;
  const exportH = Math.round(exportSize / m.aspect);

  // Refresh PNG download URL whenever the config changes
  useEffect(() => {
    let cancelled = false;
    const svg = buildLogoSVG({ markKey, fill, word, bg, paddingPct, radius, thickness, exportW, exportH });
    svgToPngBlobUrl(svg, exportW, exportH).then((url) => {
      if (cancelled) { URL.revokeObjectURL(url); return; }
      setDownloads((prev) => {
        if (prev.png) URL.revokeObjectURL(prev.png);
        const svgBlob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });
        if (prev.svg) URL.revokeObjectURL(prev.svg);
        const svgUrl = URL.createObjectURL(svgBlob);
        return { png: url, svg: svgUrl };
      });
    }).catch(() => {});
    return () => { cancelled = true; };
  }, [markKey, fill, word, bg, paddingPct, radius, thickness, exportSize, exportW, exportH]);

  const filename = `nexus-${markKey}-${exportW}x${exportH}`;

  return (
    <Card pad={0} style={{ overflow: 'hidden' }}>
      <div style={{ display: 'grid', gridTemplateColumns: '340px 1fr', minHeight: 540 }}>
        {/* Controls */}
        <div style={{
          padding: 20, borderRight: '1px solid var(--border-tertiary)',
          background: 'var(--bg-secondary)',
          display: 'flex', flexDirection: 'column', gap: 16,
          maxHeight: '78vh', overflowY: 'auto',
        }}>
          <div>
            <Label>Mark</Label>
            <div style={{ display: 'grid', gap: 6 }}>
              {Object.entries(MARKS).map(([k, mk]) => (
                <button key={k} onClick={() => setMarkKey(k)} style={{
                  textAlign: 'left', padding: '10px 12px', borderRadius: 8,
                  border: markKey === k ? '1px solid var(--brand-600)' : '1px solid var(--border-secondary)',
                  background: markKey === k ? 'var(--brand-50)' : 'var(--bg-primary)',
                  cursor: 'pointer',
                }}>
                  <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)' }}>{mk.label}</div>
                  <div style={{ fontSize: 11, color: 'var(--text-tertiary)', marginTop: 2 }}>{mk.desc}</div>
                </button>
              ))}
            </div>
          </div>

          <LG_Swatch label={m.twoColor ? 'Mark colour (arrow + NR)' : 'Fill colour'} value={fill} onChange={setFill} />
          {m.twoColor && (
            <LG_Swatch label="Wordmark colour (NEXUS / REGEN)" value={word} onChange={setWord} />
          )}
          <LG_Swatch label="Background" value={bg} onChange={setBg} />

          <LG_Number label="Padding" value={paddingPct} min={0} max={40} suffix="%" onChange={setPaddingPct} />
          <LG_Number label="Corner radius" value={radius} min={0} max={200} step={4} suffix="px" onChange={setRadius} />
          <LG_Number label="Stroke weight" value={thickness} min={0} max={12} step={0.5} suffix="" onChange={setThickness} />

          <div>
            <Label>Export width</Label>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 4,
              background: 'var(--bg-primary)', padding: 3, borderRadius: 8,
              border: '1px solid var(--border-secondary)' }}>
              {[256, 512, 1024, 2048].map((s) => (
                <button key={s} onClick={() => setExportSize(s)} style={{
                  border: 'none', padding: '8px 4px', borderRadius: 6,
                  background: exportSize === s ? 'var(--brand-600)' : 'transparent',
                  color: exportSize === s ? '#fff' : 'var(--text-tertiary)',
                  cursor: 'pointer', fontSize: 12, fontWeight: 600,
                  fontFamily: 'var(--font-mono)',
                }}>{s}</button>
              ))}
            </div>
            <div style={{ fontSize: 11, fontFamily: 'var(--font-mono)',
              color: 'var(--text-quaternary)', marginTop: 6 }}>
              Output: {exportW} × {exportH} px
            </div>
          </div>

          <div style={{ display: 'flex', gap: 8, marginTop: 4 }}>
            <a href={downloads.png} download={`${filename}.png`}
              style={{
                flex: 1, textDecoration: 'none', display: 'inline-flex', alignItems: 'center',
                justifyContent: 'center', gap: 6,
                background: 'var(--brand-600)', color: '#fff',
                padding: '12px 14px', borderRadius: 8,
                fontWeight: 700, fontSize: 13, fontFamily: 'var(--font-mono)',
                border: '1px solid var(--brand-700)',
                opacity: downloads.png ? 1 : 0.5,
                pointerEvents: downloads.png ? 'auto' : 'none',
              }}>↓ PNG</a>
            <a href={downloads.svg} download={`${filename}.svg`}
              style={{
                flex: 1, textDecoration: 'none', display: 'inline-flex', alignItems: 'center',
                justifyContent: 'center', gap: 6,
                background: 'var(--accent-400)', color: 'var(--brand-700)',
                padding: '12px 14px', borderRadius: 8,
                fontWeight: 700, fontSize: 13, fontFamily: 'var(--font-mono)',
                border: '1px solid var(--accent-500)',
                opacity: downloads.svg ? 1 : 0.5,
                pointerEvents: downloads.svg ? 'auto' : 'none',
              }}>↓ SVG</a>
          </div>

          <button
            onClick={() => copy(previewSvg)}
            style={{
              border: '1px solid var(--border-primary)',
              background: 'var(--bg-primary)',
              color: 'var(--text-secondary)',
              borderRadius: 8, padding: '10px 14px',
              fontSize: 12, fontWeight: 600, fontFamily: 'var(--font-mono)',
              cursor: 'pointer',
            }}>
            ⧉ Copy SVG markup
          </button>
        </div>

        {/* Preview */}
        <div style={{
          padding: 24,
          display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
          gap: 16,
          background: 'var(--bg-primary)',
          backgroundImage: 'repeating-conic-gradient(rgba(0,0,0,.03) 0% 25%, transparent 0% 50%)',
          backgroundSize: '24px 24px',
        }}>
          <div
            style={{ maxWidth: '100%', maxHeight: '60vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
            dangerouslySetInnerHTML={{ __html: previewSvg.replace(/<\?xml[^?]*\?>/, '').replace(/width="\d+"/, 'width="100%"').replace(/height="\d+"/, 'height="auto"').replace(/<svg /, '<svg style="max-width:520px;max-height:60vh;display:block;" ') }}
          />
          <div style={{
            fontSize: 12, fontFamily: 'var(--font-mono)', color: 'var(--text-quaternary)',
            background: 'var(--bg-secondary)', padding: '6px 10px', borderRadius: 6,
            border: '1px solid var(--border-tertiary)',
          }}>
            {m.label} · {exportW}×{exportH} · {bg === 'transparent' ? 'no bg' : bg}
          </div>
        </div>
      </div>
    </Card>
  );
}

/* === Banner / OG generator with custom dimensions ============== */
function CustomCanvasGenerator() {
  const [preset, setPreset] = useState('og');
  const [customW, setCustomW] = useState(1200);
  const [customH, setCustomH] = useState(630);
  const [headline, setHeadline] = useState('Materially smarter construction.');
  const [accent, setAccent] = useState('smarter');
  const [sub, setSub] = useState('Plan, source, move and prove every material on every project.');
  const [theme, setTheme] = useState('navy');
  const [layout, setLayout] = useState('standard');
  const [download, setDownload] = useState(null);
  const canvasRef = useRef(null);

  const presets = {
    og:           { w: 1200, h: 630,  label: 'Open Graph · 1200×630' },
    square:       { w: 1200, h: 1200, label: 'Square · 1200×1200' },
    li_personal:  { w: 1584, h: 396,  label: 'LinkedIn personal · 1584×396' },
    li_company:   { w: 1128, h: 191,  label: 'LinkedIn company · 1128×191' },
    twitter:      { w: 1500, h: 500,  label: 'X / Twitter header · 1500×500' },
    yt_channel:   { w: 2560, h: 1440, label: 'YouTube channel art · 2560×1440' },
    yt_thumb:     { w: 1280, h: 720,  label: 'YouTube thumbnail · 1280×720' },
    fb_cover:     { w: 1640, h: 859,  label: 'Facebook cover · 1640×859' },
    ig_post:      { w: 1080, h: 1080, label: 'Instagram post · 1080×1080' },
    ig_story:     { w: 1080, h: 1920, label: 'Instagram story · 1080×1920' },
    pinterest:    { w: 1000, h: 1500, label: 'Pinterest pin · 1000×1500' },
    email:        { w: 1200, h: 320,  label: 'Email signature · 1200×320' },
    zoom:         { w: 1920, h: 1080, label: 'Zoom background · 1920×1080' },
    desktop:      { w: 2560, h: 1440, label: 'Desktop wallpaper · 2560×1440' },
    mobile:       { w: 1080, h: 1920, label: 'Mobile wallpaper · 1080×1920' },
    custom:       { w: customW, h: customH, label: 'Custom' },
  };

  const themes = {
    navy:    { bg: '#121541', fg: '#FFFFFF', accent: '#F7EC33', sub: '#B4B7D4', mark: '#F7EC33' },
    yellow:  { bg: '#F7EC33', fg: '#0E1034', accent: '#0E1034', sub: 'rgba(14,16,52,.65)', mark: '#0E1034' },
    paper:   { bg: '#FFFFFF', fg: '#0E1034', accent: '#121541', sub: '#475467', mark: '#121541' },
    inkBlack:{ bg: '#03040E', fg: '#FFFFFF', accent: '#F7EC33', sub: '#8488B6', mark: '#F7EC33' },
    minimal: { bg: '#F8F9FC', fg: '#121541', accent: '#E0D420', sub: '#475467', mark: '#121541' },
  };

  const drawArrow = (ctx, x, y, size, fill) => {
    const scale = size / 120.13;
    ctx.save();
    ctx.translate(x - 7.925 * scale, y - 9.935 * scale);
    ctx.scale(scale, scale);
    const p = new Path2D(LG_ARROW_PATH);
    ctx.fillStyle = fill;
    ctx.fill(p, 'evenodd');
    ctx.restore();
  };

  const render = () => {
    const c = canvasRef.current;
    if (!c) return;
    const p = presets[preset];
    const w = p.w, h = p.h;
    c.width = w; c.height = h;
    const t = themes[theme];
    const ctx = c.getContext('2d');

    ctx.fillStyle = t.bg;
    ctx.fillRect(0, 0, w, h);

    // Layout-driven decorations
    if (layout !== 'minimal') {
      ctx.globalAlpha = 0.1;
      const bigArrow = Math.max(w, h) * 0.75;
      drawArrow(ctx, w - bigArrow * 0.55, -bigArrow * 0.15, bigArrow, t.accent);
      ctx.globalAlpha = 1;
    }

    const tWidth = Math.min(w - 160, 1100);
    const baseUnit = Math.min(w, h * 1.6) / 15;

    // Top-left wordmark cluster
    const emblemSize = Math.min(w, h) * 0.075;
    drawArrow(ctx, 80, 90, emblemSize, t.mark);
    ctx.fillStyle = t.fg;
    ctx.font = `700 ${baseUnit * 0.2}px Inter, system-ui, sans-serif`;
    ctx.textBaseline = 'middle';
    ctx.fillText('NEXUS REGEN', 80 + emblemSize + 14, 90 + emblemSize / 2);

    if (layout === 'minimal') {
      // Mark-only layout: centred big mark
      const big = Math.min(w, h) * 0.45;
      drawArrow(ctx, w / 2 - big / 2 + 7.925 * (big / 120.13), h / 2 - big / 2 + 9.935 * (big / 120.13), big, t.mark);
      // Small URL bottom
      ctx.font = `600 ${baseUnit * 0.22}px 'JetBrains Mono', ui-monospace, monospace`;
      ctx.fillStyle = t.sub;
      ctx.textAlign = 'center';
      ctx.fillText('nexusregen.com', w / 2, h - 60);
      ctx.textAlign = 'left';
      finish();
      return;
    }

    // Headline + accent-word logic
    const hSize = baseUnit * (layout === 'tall' ? 0.85 : 1);
    ctx.font = `600 ${hSize}px Inter, system-ui, sans-serif`;
    ctx.fillStyle = t.fg;

    const words = headline.split(' ');
    const lines = [];
    let line = '';
    for (const wd of words) {
      const test = line ? line + ' ' + wd : wd;
      if (ctx.measureText(test).width > tWidth && line) {
        lines.push(line);
        line = wd;
      } else {
        line = test;
      }
    }
    if (line) lines.push(line);

    const startY = h * (layout === 'tall' ? 0.32 : 0.42);
    const lh = hSize * 1.05;
    lines.forEach((l, i) => {
      if (accent && l.includes(accent)) {
        const parts = l.split(accent);
        let x = 80;
        for (let pIdx = 0; pIdx < parts.length; pIdx++) {
          if (parts[pIdx]) {
            ctx.fillStyle = t.fg;
            ctx.fillText(parts[pIdx], x, startY + i * lh);
            x += ctx.measureText(parts[pIdx]).width;
          }
          if (pIdx < parts.length - 1) {
            ctx.fillStyle = t.accent;
            ctx.fillText(accent, x, startY + i * lh);
            x += ctx.measureText(accent).width;
          }
        }
      } else {
        ctx.fillText(l, 80, startY + i * lh);
      }
    });

    if (sub) {
      ctx.font = `400 ${baseUnit * 0.32}px Inter, system-ui, sans-serif`;
      ctx.fillStyle = t.sub;
      // Wrap subhead too
      const subWords = sub.split(' ');
      const subLines = [];
      let sl = '';
      for (const sw of subWords) {
        const test = sl ? sl + ' ' + sw : sw;
        if (ctx.measureText(test).width > tWidth && sl) { subLines.push(sl); sl = sw; } else { sl = test; }
      }
      if (sl) subLines.push(sl);
      const subStart = startY + lines.length * lh + baseUnit * 0.35;
      subLines.forEach((l, i) => ctx.fillText(l, 80, subStart + i * baseUnit * 0.42));
    }

    ctx.font = `600 ${baseUnit * 0.22}px 'JetBrains Mono', ui-monospace, monospace`;
    ctx.fillStyle = t.sub;
    ctx.fillText('nexusregen.com', 80, h - 60);

    const emBig = Math.min(w, h) * 0.1;
    drawArrow(ctx, w - emBig - 80, h - emBig - 60, emBig, t.mark);
    finish();

    function finish() {
      c.toBlob((blob) => {
        if (blob) {
          if (download) URL.revokeObjectURL(download);
          setDownload(URL.createObjectURL(blob));
        }
      }, 'image/png');
    }
  };

  useEffect(() => { render(); }, [preset, customW, customH, headline, accent, sub, theme, layout]);

  const p = presets[preset];

  return (
    <Card pad={0} style={{ overflow: 'hidden' }}>
      <div style={{ display: 'grid', gridTemplateColumns: '340px 1fr' }}>
        <div style={{
          padding: 20, borderRight: '1px solid var(--border-tertiary)',
          background: 'var(--bg-secondary)',
          display: 'flex', flexDirection: 'column', gap: 14,
          maxHeight: '78vh', overflowY: 'auto',
        }}>
          <div>
            <Label>Format</Label>
            <select value={preset} onChange={(e) => setPreset(e.target.value)} style={selectStyle}>
              {Object.entries(presets).map(([k, v]) => (
                <option key={k} value={k}>{v.label}</option>
              ))}
            </select>
          </div>

          {preset === 'custom' && (
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
              <div>
                <Label>Width</Label>
                <input type="number" min={100} max={5000} value={customW}
                  onChange={(e) => setCustomW(Number(e.target.value))} style={inputStyle} />
              </div>
              <div>
                <Label>Height</Label>
                <input type="number" min={100} max={5000} value={customH}
                  onChange={(e) => setCustomH(Number(e.target.value))} style={inputStyle} />
              </div>
            </div>
          )}

          <div>
            <Label>Theme</Label>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 4,
              background: 'var(--bg-primary)', padding: 3, borderRadius: 8,
              border: '1px solid var(--border-secondary)' }}>
              {Object.entries(themes).map(([k, v]) => (
                <button key={k} onClick={() => setTheme(k)} title={k} style={{
                  border: 'none', padding: '8px 4px', borderRadius: 6,
                  background: theme === k ? v.bg : 'transparent',
                  color: theme === k ? v.fg : 'var(--text-tertiary)',
                  cursor: 'pointer', fontSize: 11, fontWeight: 600,
                }}>{k === 'inkBlack' ? 'Ink' : k}</button>
              ))}
            </div>
          </div>

          <div>
            <Label>Layout</Label>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4,
              background: 'var(--bg-primary)', padding: 3, borderRadius: 8,
              border: '1px solid var(--border-secondary)' }}>
              {[
                { k: 'standard', l: 'Standard' },
                { k: 'tall',     l: 'Tall' },
                { k: 'minimal',  l: 'Minimal' },
              ].map((o) => (
                <button key={o.k} onClick={() => setLayout(o.k)} style={{
                  border: 'none', padding: '8px 4px', borderRadius: 6,
                  background: layout === o.k ? 'var(--brand-600)' : 'transparent',
                  color: layout === o.k ? '#fff' : 'var(--text-tertiary)',
                  cursor: 'pointer', fontSize: 12, fontWeight: 600,
                }}>{o.l}</button>
              ))}
            </div>
          </div>

          <div>
            <Label>Headline</Label>
            <input value={headline} onChange={(e) => setHeadline(e.target.value)} style={inputStyle} />
          </div>
          <div>
            <Label>Accent word (highlighted)</Label>
            <input value={accent} onChange={(e) => setAccent(e.target.value)} style={inputStyle}
              placeholder="(word inside headline to highlight)" />
          </div>
          <div>
            <Label>Subhead</Label>
            <input value={sub} onChange={(e) => setSub(e.target.value)} style={inputStyle} />
          </div>

          <a href={download} download={`nexus-${preset}-${p.w}x${p.h}.png`}
            onClick={(e) => { if (!download) e.preventDefault(); }}
            style={{
              textDecoration: 'none', display: 'inline-flex', alignItems: 'center',
              justifyContent: 'center', gap: 6,
              background: 'var(--brand-600)', color: '#fff',
              padding: '12px 14px', borderRadius: 8,
              fontWeight: 700, fontSize: 13, fontFamily: 'var(--font-mono)',
              border: '1px solid var(--brand-700)',
              opacity: download ? 1 : 0.5,
              pointerEvents: download ? 'auto' : 'none',
            }}>
            ↓ Download {p.w}×{p.h} PNG
          </a>
        </div>

        <div style={{
          padding: 20, background: 'var(--bg-primary)',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          minHeight: 380,
        }}>
          <div style={{ maxWidth: '100%', maxHeight: 560, overflow: 'hidden',
            borderRadius: 8, border: '1px solid var(--border-secondary)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            background: 'var(--bg-secondary)',
          }}>
            <canvas ref={canvasRef} style={{ maxWidth: '100%', maxHeight: 560, display: 'block' }} />
          </div>
        </div>
      </div>
    </Card>
  );
}

/* === Pattern generator — tileable arrow motif rendered on demand === */
function PatternGenerator() {
  const [tile, setTile] = useState(120);
  const [bg, setBg] = useState('#121541');
  const [fg, setFg] = useState('#0E1034');
  const [opacity, setOpacity] = useState(40);
  const [angle, setAngle] = useState(0);
  const [thickness, setThickness] = useState(0);
  const [exportSize, setExportSize] = useState(1920);
  const [download, setDownload] = useState(null);
  const canvasRef = useRef(null);

  const render = () => {
    const c = canvasRef.current;
    if (!c) return;
    const w = exportSize, h = Math.round(exportSize * 9 / 16);
    c.width = w; c.height = h;
    const ctx = c.getContext('2d');

    ctx.fillStyle = bg;
    ctx.fillRect(0, 0, w, h);

    ctx.save();
    if (angle) {
      ctx.translate(w / 2, h / 2);
      ctx.rotate((angle * Math.PI) / 180);
      ctx.translate(-w / 2, -h / 2);
    }
    ctx.globalAlpha = opacity / 100;
    const scale = tile / 120.13;
    const pad = tile * 0.15;
    const stride = tile + pad;
    const stretch = Math.max(w, h) * 1.5;
    for (let y = -stride; y < stretch; y += stride) {
      for (let x = -stride; x < stretch; x += stride) {
        ctx.save();
        ctx.translate(x - 7.925 * scale, y - 9.935 * scale);
        ctx.scale(scale, scale);
        const path = new Path2D(LG_ARROW_PATH);
        ctx.fillStyle = fg;
        ctx.fill(path, 'evenodd');
        if (thickness) {
          ctx.strokeStyle = fg;
          ctx.lineWidth = thickness;
          ctx.lineJoin = 'round';
          ctx.stroke(path);
        }
        ctx.restore();
      }
    }
    ctx.restore();

    c.toBlob((blob) => {
      if (blob) {
        if (download) URL.revokeObjectURL(download);
        setDownload(URL.createObjectURL(blob));
      }
    }, 'image/png');
  };

  useEffect(() => { render(); }, [tile, bg, fg, opacity, angle, thickness, exportSize]);

  return (
    <Card pad={0} style={{ overflow: 'hidden' }}>
      <div style={{ display: 'grid', gridTemplateColumns: '300px 1fr' }}>
        <div style={{
          padding: 20, borderRight: '1px solid var(--border-tertiary)',
          background: 'var(--bg-secondary)',
          display: 'flex', flexDirection: 'column', gap: 14,
        }}>
          <LG_Swatch label="Background" value={bg} onChange={setBg} />
          <LG_Swatch label="Arrow colour" value={fg} onChange={setFg} />
          <LG_Number label="Tile size" value={tile} min={40} max={400} step={4} suffix="px" onChange={setTile} />
          <LG_Number label="Opacity" value={opacity} min={5} max={100} suffix="%" onChange={setOpacity} />
          <LG_Number label="Rotation" value={angle} min={-45} max={45} suffix="°" onChange={setAngle} />
          <LG_Number label="Stroke weight" value={thickness} min={0} max={12} step={0.5} suffix="" onChange={setThickness} />
          <div>
            <Label>Export width</Label>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 4,
              background: 'var(--bg-primary)', padding: 3, borderRadius: 8,
              border: '1px solid var(--border-secondary)' }}>
              {[1280, 1920, 2560, 3840].map((s) => (
                <button key={s} onClick={() => setExportSize(s)} style={{
                  border: 'none', padding: '8px 4px', borderRadius: 6,
                  background: exportSize === s ? 'var(--brand-600)' : 'transparent',
                  color: exportSize === s ? '#fff' : 'var(--text-tertiary)',
                  cursor: 'pointer', fontSize: 11, fontWeight: 600, fontFamily: 'var(--font-mono)',
                }}>{s}</button>
              ))}
            </div>
          </div>
          <a href={download} download={`nexus-pattern-${exportSize}.png`}
            onClick={(e) => { if (!download) e.preventDefault(); }}
            style={{
              textDecoration: 'none', display: 'inline-flex', alignItems: 'center',
              justifyContent: 'center', gap: 6,
              background: 'var(--brand-600)', color: '#fff',
              padding: '12px 14px', borderRadius: 8,
              fontWeight: 700, fontSize: 13, fontFamily: 'var(--font-mono)',
              border: '1px solid var(--brand-700)',
              opacity: download ? 1 : 0.5,
              pointerEvents: download ? 'auto' : 'none',
            }}>↓ PNG ({exportSize}px wide)</a>
        </div>

        <div style={{ padding: 20, background: 'var(--bg-primary)',
          display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: 360 }}>
          <div style={{ maxWidth: '100%', maxHeight: 480, overflow: 'hidden',
            borderRadius: 8, border: '1px solid var(--border-secondary)' }}>
            <canvas ref={canvasRef} style={{ maxWidth: '100%', maxHeight: 480, display: 'block' }} />
          </div>
        </div>
      </div>
    </Card>
  );
}

function S_GenLogo() {
  return (
    <Section
      id="gen-logo"
      eyebrow="01 · Generators"
      title="Logo generator"
      intro="Pick a mark, set the colours, choose a size — get PNG and SVG. Every output is rendered from the canonical paths and tokens, so you can't drift off-brand."
    >
      <LogoGenerator />
    </Section>
  );
}

function S_GenBanner() {
  return (
    <Section
      id="gen-banner"
      eyebrow="02 · Generators"
      title="Banner & social card generator"
      intro="Open Graph, LinkedIn, Twitter/X, YouTube, Instagram, Pinterest, Zoom, wallpapers, custom — fifteen presets and one custom slot."
    >
      <CustomCanvasGenerator />
    </Section>
  );
}

function S_GenPattern() {
  return (
    <Section
      id="gen-pattern"
      eyebrow="03 · Generators"
      title="Pattern generator"
      intro="Tileable arrow motif — pick colours, density and rotation; export at any resolution."
    >
      <PatternGenerator />
    </Section>
  );
}

window.S_GenLogo = S_GenLogo;
window.S_GenBanner = S_GenBanner;
window.S_GenPattern = S_GenPattern;
