// Shared components + data
// Note: do not destructure hooks at module scope — page scripts also do, and Babel concatenates them.

const TWEAKS_DEFAULTS_BASE = {
  "theme": "light",
  "accent": "purple",
  "headline": "serif"
};

const ACCENTS = {
  purple: { light: "#5B3A9C", dark: "#C5A8FF" },
  green:  { light: "#1F6A3A", dark: "#6FE197" },
  amber:  { light: "#8B5A1A", dark: "#F0C674" },
  cobalt: { light: "#1F4FA8", dark: "#8AB6FF" },
};

// 3-year weekly data (157 pts, weekly ~Apr 2023 → Apr 18 2026)
const supplyTrend3Y = [
  // Q2 2023
  2.1,2.12,2.15,2.18,2.2,2.22,2.25,2.28,2.3,2.33,2.36,2.38,2.4,
  // Q3 2023
  2.42,2.45,2.48,2.52,2.55,2.58,2.62,2.65,2.7,2.75,2.8,2.85,2.9,
  // Q4 2023 — DeFi + memecoin season ignites
  2.95,3.05,3.2,3.4,3.65,3.9,4.1,4.25,4.4,4.5,4.58,4.62,4.65,
  // Q1 2024 — explosive growth
  4.8,5.1,5.5,5.9,6.3,6.6,6.9,7.1,7.2,7.3,7.38,7.42,7.45,
  // Q2 2024 — consolidation
  7.5,7.48,7.52,7.58,7.55,7.6,7.65,7.62,7.68,7.72,7.75,7.78,7.8,
  // Q3 2024 — steady rise
  7.85,7.9,8.0,8.05,8.1,8.15,8.2,8.28,8.35,8.42,8.5,8.55,8.6,
  // Q4 2024 — post-election bull run
  8.75,9.1,9.5,9.9,10.3,10.7,11.1,11.4,11.7,12.0,12.2,12.4,12.5,
  // Q1 2025
  12.4,12.6,12.72,12.8,12.75,12.88,12.95,12.92,13.0,13.08,13.14,13.2,13.25,
  // Q2 2025
  13.22,13.3,13.38,13.42,13.4,13.48,13.52,13.5,13.56,13.62,13.66,13.7,13.72,
  // Q3 2025
  13.8,13.9,14.02,14.08,14.12,14.18,14.24,14.32,14.42,14.52,14.62,14.72,14.82,
  // Q4 2025
  14.95,15.1,15.22,15.32,15.42,15.52,15.62,15.72,15.82,15.92,16.02,16.12,16.22,
  // Q1 2026 — ATH then pullback
  16.3,16.4,16.5,16.6,16.7,16.8,16.75,16.62,16.48,16.32,16.22,16.16,16.23,
  // Apr 14 2026
  16.23
];

const solPrice3Y = [
  // Q2 2023
  22,21.5,20.8,21.2,22.5,23.8,24.5,25.1,24.8,25.5,26.2,25.8,24.5,
  // Q3 2023
  23.8,22.5,21.8,22.2,21.5,20.8,21.2,22.0,23.5,25.0,26.8,28.5,30.2,
  // Q4 2023 — break-out
  29.5,31.0,35.0,42.0,52.0,55.0,60.0,65.0,70.0,80.0,95.0,105,115,
  // Q1 2024 — ATH cycle
  120,125,135,145,155,165,175,185,200,210,195,180,165,
  // Q2 2024 — correction
  158,152,148,145,142,140,138,135,133,132,130,132,135,
  // Q3 2024 — base building
  138,142,148,152,155,150,145,140,135,132,130,135,138,
  // Q4 2024 — election rally
  145,155,168,180,195,215,235,248,255,250,245,240,235,
  // Q1 2025 — profit taking
  230,222,215,208,200,193,186,180,175,170,165,160,155,
  // Q2 2025 — recovery
  152,150,148,152,155,158,162,165,168,170,172,175,178,
  // Q3 2025 — new push
  182,188,195,202,210,218,225,232,238,242,246,250,252,
  // Q4 2025 — distribution
  255,258,260,256,252,248,244,240,236,232,228,224,220,
  // Q1 2026 — downtrend
  215,208,200,192,185,178,172,167,162,158,155,153,153,
  // Apr 14 2026
  153
];

const tvlSeries3Y = [
  // Q2 2023
  0.82,0.84,0.85,0.87,0.88,0.9,0.92,0.95,0.98,1.0,1.02,1.05,1.08,
  // Q3 2023
  1.1,1.12,1.15,1.18,1.2,1.22,1.25,1.28,1.3,1.32,1.35,1.38,1.4,
  // Q4 2023
  1.45,1.5,1.6,1.75,1.9,2.1,2.3,2.5,2.7,2.85,3.0,3.1,3.2,
  // Q1 2024
  3.4,3.6,3.8,4.0,4.15,4.3,4.4,4.5,4.6,4.65,4.68,4.7,4.72,
  // Q2 2024
  4.7,4.65,4.6,4.55,4.5,4.48,4.45,4.42,4.4,4.42,4.45,4.48,4.5,
  // Q3 2024
  4.52,4.55,4.58,4.6,4.62,4.65,4.68,4.7,4.72,4.75,4.78,4.8,4.82,
  // Q4 2024
  4.85,4.9,4.95,5.0,5.05,5.1,5.15,5.2,5.25,5.3,5.35,5.4,5.45,
  // Q1 2025
  5.42,5.45,5.48,5.5,5.48,5.5,5.52,5.5,5.52,5.54,5.55,5.56,5.57,
  // Q2 2025
  5.55,5.56,5.57,5.56,5.57,5.58,5.57,5.58,5.59,5.6,5.61,5.62,5.62,
  // Q3 2025
  5.63,5.64,5.65,5.64,5.65,5.66,5.65,5.66,5.67,5.68,5.69,5.7,5.7,
  // Q4 2025
  5.7,5.7,5.71,5.7,5.71,5.72,5.71,5.72,5.72,5.72,5.72,5.72,5.72,
  // Q1 2026
  5.72,5.72,5.72,5.72,5.72,5.72,5.72,5.7,5.68,5.68,5.68,5.7,5.72,
  // Apr 14 2026
  5.72
];

// Legacy short series (kept for quarterly report pages)
const supplyTrend = supplyTrend3Y.slice(-29);
const solPrice90  = solPrice3Y.slice(-29);
const tvlSeries   = tvlSeries3Y.slice(-29);
const dates = ["Jan 18","Feb 1","Feb 15","Mar 1","Mar 15","Apr 1","Apr 18"];

// Generate x-axis date labels for a given number of weekly data points
function generateDateLabels(numPoints, numLabels) {
  const end = new Date('2026-04-18');
  const daysPerPoint = (3 * 365 + 1) / 156; // ~7 days
  const totalDays = (numPoints - 1) * daysPerPoint;
  const showYear = totalDays > 400;
  const n = numLabels != null ? numLabels : 6;
  const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  const labels = [];
  for (let i = 0; i <= n; i++) {
    const pointIdx = Math.round((numPoints - 1) * (i / n));
    const daysAgo = (numPoints - 1 - pointIdx) * daysPerPoint;
    const d = new Date(end.getTime() - daysAgo * 86400000);
    if (showYear) {
      labels.push(`${months[d.getMonth()]} '${String(d.getFullYear()).slice(2)}`);
    } else {
      labels.push(`${months[d.getMonth()]} ${d.getDate()}`);
    }
  }
  return labels;
}

function LineChart({ data, yMin, yMax, height, yLabel, accent, dateLabels }) {
  const ref = React.useRef(null);
  const [dim, setDim] = React.useState({ w: 900, h: height || 280 });
  const [hover, setHover] = React.useState(null);

  React.useEffect(() => {
    if (!ref.current) return;
    const obs = new ResizeObserver(entries => {
      for (const e of entries) setDim({ w: e.contentRect.width, h: height || 280 });
    });
    obs.observe(ref.current);
    return () => obs.disconnect();
  }, [height]);

  const { w, h } = dim;
  const pad = { t: 24, r: 24, b: 30, l: 52 };
  const iw = w - pad.l - pad.r, ih = h - pad.t - pad.b;
  const min = yMin, max = yMax;

  const pts = data.map((v, i) =>
    v != null
      ? [pad.l + (i / (data.length - 1)) * iw, pad.t + ih - ((v - min) / (max - min)) * ih]
      : null
  );

  // Build line path with gap support for null values
  let pathStr = '';
  data.forEach((v, i) => {
    if (v == null || !pts[i]) return;
    const [x, y] = pts[i];
    const prevOk = i > 0 && data[i - 1] != null;
    pathStr += `${prevOk ? 'L' : 'M'}${x.toFixed(1)},${y.toFixed(1)} `;
  });

  // Area fill — only for contiguous segments
  const areaSegments = [];
  let seg = [];
  data.forEach((v, i) => {
    if (v == null || !pts[i]) {
      if (seg.length) { areaSegments.push(seg); seg = []; }
    } else {
      seg.push(pts[i]);
    }
  });
  if (seg.length) areaSegments.push(seg);

  const range = max - min;
  const yTicks = [min, min + range * 0.33, min + range * 0.66, max].map(v => Math.round(v * 10) / 10);
  const color = accent || 'var(--accent)';

  const handleMouseMove = React.useCallback((e) => {
    const svgEl = e.currentTarget;
    const rect = svgEl.getBoundingClientRect();
    const scaleX = w / rect.width;
    const mouseX = (e.clientX - rect.left) * scaleX;
    let closestIdx = -1, minDist = Infinity;
    data.forEach((v, i) => {
      if (v == null || !pts[i]) return;
      const dist = Math.abs(pts[i][0] - mouseX);
      if (dist < minDist) { minDist = dist; closestIdx = i; }
    });
    if (closestIdx >= 0) {
      setHover({ index: closestIdx, x: pts[closestIdx][0], y: pts[closestIdx][1], value: data[closestIdx] });
    }
  }, [w, data, pts]);

  // Last valid point for the live dot
  const lastValidIdx = data.reduce((acc, v, i) => v != null ? i : acc, -1);
  const lastPt = lastValidIdx >= 0 ? pts[lastValidIdx] : null;

  // Tooltip positioning: flip left when near right edge
  const ttW = 100, ttH = 36, ttPad = 10;
  const ttX = hover ? (hover.x + ttPad + ttW > w - pad.r ? hover.x - ttPad - ttW : hover.x + ttPad) : 0;
  const ttY = hover ? Math.max(pad.t, Math.min(hover.y - ttH / 2, pad.t + ih - ttH)) : 0;

  const xLabels = dateLabels || dates;

  return (
    <div ref={ref} style={{ width: '100%' }}>
      {w > 0 && (
        <svg
          viewBox={`0 0 ${w} ${h}`} width="100%" height={h}
          preserveAspectRatio="none"
          onMouseMove={handleMouseMove}
          onMouseLeave={() => setHover(null)}
          style={{ cursor: 'crosshair', display: 'block' }}
        >
          {/* Grid lines + y-axis labels */}
          {yTicks.map((t, i) => {
            const y = pad.t + ih - ((t - min) / (max - min)) * ih;
            return (
              <g key={i}>
                <line x1={pad.l} x2={w - pad.r} y1={y} y2={y} stroke="var(--rule-2)" strokeWidth="1"/>
                <text x={pad.l - 10} y={y + 4} fontSize="11" textAnchor="end" fill="var(--ink-4)" fontFamily="JetBrains Mono">{yLabel ? yLabel(t) : t}</text>
              </g>
            );
          })}

          {/* Area fills per segment */}
          {areaSegments.map((s, si) => {
            const aPath = s.map((p, i) => `${i ? 'L' : 'M'}${p[0].toFixed(1)},${p[1].toFixed(1)}`).join(' ')
              + ` L${s[s.length-1][0].toFixed(1)},${pad.t+ih} L${s[0][0].toFixed(1)},${pad.t+ih} Z`;
            return <path key={si} d={aPath} fill={color} opacity="0.1"/>;
          })}

          {/* Line path */}
          <path d={pathStr} fill="none" stroke={color} strokeWidth="2.5" vectorEffect="non-scaling-stroke"/>

          {/* Current value dot */}
          {lastPt && <circle cx={lastPt[0]} cy={lastPt[1]} r="4" fill={color}/>}

          {/* Hover crosshair */}
          {hover && (
            <>
              <line
                x1={hover.x} x2={hover.x} y1={pad.t} y2={pad.t + ih}
                stroke="var(--ink-3)" strokeWidth="1" strokeDasharray="4,3" opacity="0.7"
                vectorEffect="non-scaling-stroke"
              />
              <circle cx={hover.x} cy={hover.y} r="5" fill={color} stroke="var(--surface)" strokeWidth="2" vectorEffect="non-scaling-stroke"/>

              {/* Tooltip box */}
              <rect x={ttX} y={ttY} width={ttW} height={ttH} rx="5"
                fill="var(--surface)" stroke="var(--rule)" strokeWidth="1" opacity="0.97"/>
              <text x={ttX + ttW / 2} y={ttY + 13} fontSize="10" textAnchor="middle"
                fill="var(--ink-4)" fontFamily="JetBrains Mono">
                {dateLabels ? dateLabels[hover.index] : ''}
              </text>
              <text x={ttX + ttW / 2} y={ttY + 27} fontSize="13" textAnchor="middle"
                fill={color} fontFamily="JetBrains Mono" fontWeight="700">
                {yLabel ? yLabel(hover.value) : hover.value}
              </text>
            </>
          )}

          {/* X-axis date labels */}
          {xLabels.map((d, i) => {
            const x = pad.l + (i / (xLabels.length - 1)) * iw;
            return (
              <text key={i} x={x} y={h - 8} fontSize="11" textAnchor="middle"
                fill="var(--ink-4)" fontFamily="JetBrains Mono">{d}</text>
            );
          })}

          {/* Transparent overlay to capture mouse events across gaps */}
          <rect x={pad.l} y={pad.t} width={iw} height={ih} fill="transparent"/>
        </svg>
      )}
    </div>
  );
}

function BarChart({ data, yMin, yMax, height, yLabel, highlight }) {
  const ref = React.useRef(null);
  const [dim, setDim] = React.useState({ w: 900, h: height || 220 });
  const [hoverIdx, setHoverIdx] = React.useState(null);

  React.useEffect(() => {
    if (!ref.current) return;
    const obs = new ResizeObserver(entries => {
      for (const e of entries) setDim({ w: e.contentRect.width, h: height || 220 });
    });
    obs.observe(ref.current);
    return () => obs.disconnect();
  }, [height]);

  const { w, h } = dim;
  const pad = { t: 20, r: 16, b: 36, l: 52 };
  const iw = w - pad.l - pad.r, ih = h - pad.t - pad.b;
  const barW = iw / data.length * 0.8;
  const gap = iw / data.length * 0.2;
  const range = yMax - yMin;
  const yTicks = [yMin, yMin + range * 0.5, yMax].map(v => Math.round(v * 10) / 10);

  return (
    <div ref={ref} style={{ width: '100%' }}>
      {w > 0 && (
        <svg viewBox={`0 0 ${w} ${h}`} width="100%" height={h}>
          {yTicks.map((t, i) => {
            const y = pad.t + ih - ((t - yMin) / range) * ih;
            return (
              <g key={i}>
                <line x1={pad.l} x2={w - pad.r} y1={y} y2={y} stroke="var(--rule-2)"/>
                <text x={pad.l - 10} y={y + 4} fontSize="11" textAnchor="end" fill="var(--ink-4)" fontFamily="JetBrains Mono">{yLabel ? yLabel(t) : t}</text>
              </g>
            );
          })}
          {data.map((d, i) => {
            const bh = ((d.v - yMin) / range) * ih;
            const x = pad.l + i * (barW + gap) + gap / 2;
            const y = pad.t + ih - bh;
            const hi = highlight && highlight.includes(d.label);
            const isHov = hoverIdx === i;
            const barColor = hi ? 'var(--accent)' : 'var(--ink-3)';

            // Tooltip
            const ttW = 80, ttH = 32, ttPad = 6;
            const ttX = Math.min(x + barW / 2 - ttW / 2, w - pad.r - ttW);
            const ttY = Math.max(pad.t, y - ttH - ttPad);

            return (
              <g key={i}
                onMouseEnter={() => setHoverIdx(i)}
                onMouseLeave={() => setHoverIdx(null)}
                style={{ cursor: 'default' }}
              >
                <rect x={x} y={y} width={barW} height={bh}
                  fill={barColor} rx="2" opacity={isHov ? 1 : 0.85}/>
                <text x={x + barW / 2} y={h - 18} fontSize="11" textAnchor="middle"
                  fill="var(--ink-3)" fontFamily="JetBrains Mono">{d.label}</text>
                <text x={x + barW / 2} y={h - 4} fontSize="10" textAnchor="middle"
                  fill="var(--ink-4)" fontFamily="JetBrains Mono">{d.sub || ''}</text>
                {isHov && (
                  <>
                    <rect x={ttX} y={ttY} width={ttW} height={ttH} rx="5"
                      fill="var(--surface)" stroke="var(--rule)" strokeWidth="1"/>
                    <text x={ttX + ttW / 2} y={ttY + 21} fontSize="13" textAnchor="middle"
                      fill={barColor} fontFamily="JetBrains Mono" fontWeight="700">
                      {yLabel ? yLabel(d.v) : d.v}
                    </text>
                  </>
                )}
              </g>
            );
          })}
        </svg>
      )}
    </div>
  );
}

const PIE_COLORS = ['#9945FF', '#14F195', '#F59E0B', '#3B82F6', '#EF4444', '#EC4899'];

function PieChart({ data }) {
  const W = 240, H = 240;
  const cx = W / 2, cy = H / 2, r = 88, rInner = 50;
  const total = data.reduce((s, d) => s + d.value, 0);
  let angle = -Math.PI / 2;
  const slices = data.map((d, i) => {
    const sweep = (d.value / total) * 2 * Math.PI;
    const x1 = cx + r * Math.cos(angle), y1 = cy + r * Math.sin(angle);
    const x2 = cx + r * Math.cos(angle + sweep), y2 = cy + r * Math.sin(angle + sweep);
    const xi1 = cx + rInner * Math.cos(angle), yi1 = cy + rInner * Math.sin(angle);
    const xi2 = cx + rInner * Math.cos(angle + sweep), yi2 = cy + rInner * Math.sin(angle + sweep);
    const large = sweep > Math.PI ? 1 : 0;
    const path = `M${xi1.toFixed(2)},${yi1.toFixed(2)} L${x1.toFixed(2)},${y1.toFixed(2)} A${r},${r} 0 ${large},1 ${x2.toFixed(2)},${y2.toFixed(2)} L${xi2.toFixed(2)},${yi2.toFixed(2)} A${rInner},${rInner} 0 ${large},0 ${xi1.toFixed(2)},${yi1.toFixed(2)} Z`;
    const midAngle = angle + sweep / 2;
    angle += sweep;
    return { ...d, path, midAngle, sweep, color: PIE_COLORS[i] };
  });
  const [hovered, setHovered] = React.useState(null);
  const hov = hovered !== null ? slices[hovered] : null;
  const callout = hov ? (() => {
    const lR1 = r + 10, lR2 = r + 22, tR = r + 28;
    const lx1 = cx + lR1 * Math.cos(hov.midAngle), ly1 = cy + lR1 * Math.sin(hov.midAngle);
    const lx2 = cx + lR2 * Math.cos(hov.midAngle), ly2 = cy + lR2 * Math.sin(hov.midAngle);
    const tx  = cx + tR  * Math.cos(hov.midAngle), ty  = cy + tR  * Math.sin(hov.midAngle);
    const anchor = tx > cx ? 'start' : 'end';
    return { lx1, ly1, lx2, ly2, tx, ty, anchor };
  })() : null;
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 24, flexWrap: 'wrap' }}>
      <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`} style={{ flexShrink: 0, overflow: 'visible' }}>
        {slices.map((s, i) => (
          <path
            key={s.label}
            d={s.path}
            fill={s.color}
            opacity={hovered === null || hovered === i ? 1 : 0.35}
            stroke="var(--surface)"
            strokeWidth="2"
            onMouseEnter={() => setHovered(i)}
            onMouseLeave={() => setHovered(null)}
            style={{ cursor: 'default', transition: 'opacity .15s' }}
          />
        ))}
        <text x={cx} y={cy - 6} textAnchor="middle" fontSize="14" fill="var(--ink)" fontFamily="JetBrains Mono" fontWeight="700">
          {hov ? hov.value.toFixed(1) + '%' : '100%'}
        </text>
        <text x={cx} y={cy + 11} textAnchor="middle" fontSize="10" fill="var(--ink-3)" fontFamily="JetBrains Mono">
          {hov ? hov.label : 'Solana'}
        </text>
        {callout && hov && (
          <>
            <line x1={callout.lx1} y1={callout.ly1} x2={callout.lx2} y2={callout.ly2}
              stroke={hov.color} strokeWidth="1.5" strokeLinecap="round"/>
            <text x={callout.tx} y={callout.ty + 4} textAnchor={callout.anchor}
              fontSize="11" fill={hov.color} fontFamily="JetBrains Mono" fontWeight="700">
              {hov.value.toFixed(1)}%
            </text>
          </>
        )}
      </svg>
      <div style={{ flex: 1, minWidth: 130 }}>
        {slices.map((s, i) => (
          <div
            key={s.label}
            style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 0',
              opacity: hovered === null || hovered === i ? 1 : 0.4, transition: 'opacity .15s', cursor: 'default' }}
            onMouseEnter={() => setHovered(i)}
            onMouseLeave={() => setHovered(null)}
          >
            <span style={{ width: 10, height: 10, borderRadius: 2, background: s.color, flexShrink: 0 }}/>
            <span style={{ fontFamily: '"JetBrains Mono", monospace', fontSize: 12, color: 'var(--ink)', flex: 1 }}>{s.label}</span>
            <span style={{ fontFamily: '"JetBrains Mono", monospace', fontSize: 12, color: 'var(--ink-3)' }}>{s.value.toFixed(1)}%</span>
          </div>
        ))}
      </div>
    </div>
  );
}

const LLM_ICONS = {
  chatgpt: (
    <svg width="20" height="20" viewBox="0 0 41 41" fill="none" xmlns="http://www.w3.org/2000/svg">
      <path d="M37.532 16.87a9.963 9.963 0 00-.856-8.184 10.078 10.078 0 00-10.855-4.835 9.963 9.963 0 00-7.504-3.356 10.079 10.079 0 00-9.614 6.977 9.967 9.967 0 00-6.664 4.834 10.08 10.08 0 001.24 11.817 9.965 9.965 0 00.856 8.185 10.079 10.079 0 0010.855 4.835 9.965 9.965 0 007.504 3.356 10.078 10.078 0 009.617-6.981 9.967 9.967 0 006.663-4.834 10.079 10.079 0 00-1.243-11.813zM22.498 37.886a7.474 7.474 0 01-4.799-1.735c.061-.033.168-.091.237-.134l7.964-4.6a1.294 1.294 0 00.655-1.134V19.054l3.366 1.944a.12.12 0 01.066.092v9.299a7.505 7.505 0 01-7.49 7.496zM6.392 31.006a7.471 7.471 0 01-.894-5.023c.06.036.162.099.237.141l7.964 4.6a1.297 1.297 0 001.308 0l9.724-5.614v3.888a.12.12 0 01-.048.103l-8.051 4.649a7.504 7.504 0 01-10.24-2.744zM4.297 13.62A7.469 7.469 0 018.2 10.333c0 .068-.004.19-.004.274v9.201a1.294 1.294 0 00.654 1.132l9.723 5.614-3.366 1.944a.12.12 0 01-.114.012L7.044 23.86a7.504 7.504 0 01-2.747-10.24zm27.658 6.437l-9.724-5.615 3.367-1.943a.121.121 0 01.114-.012l8.048 4.648a7.498 7.498 0 01-1.158 13.528v-9.476a1.293 1.293 0 00-.647-1.13zm3.35-5.043c-.059-.037-.162-.099-.236-.141l-7.965-4.6a1.298 1.298 0 00-1.308 0l-9.723 5.614v-3.888a.12.12 0 01.048-.103l8.05-4.645a7.497 7.497 0 0111.135 7.763zm-21.063 6.929l-3.367-1.944a.12.12 0 01-.065-.092v-9.299a7.497 7.497 0 0112.293-5.756 6.94 6.94 0 00-.236.134l-7.965 4.6a1.294 1.294 0 00-.654 1.132l-.006 11.225zm1.829-3.943l4.33-2.501 4.332 2.497v4.994l-4.331 2.5-4.331-2.5V18z" fill="currentColor"/>
    </svg>
  ),
  claude: (
    <img src="https://cdn.simpleicons.org/anthropic/1a1a1a" width="20" height="20" alt="" aria-hidden="true" />
  ),
  copy: (
    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
      <rect x="9" y="9" width="13" height="13" rx="2"/>
      <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
    </svg>
  ),
  markdown: (
    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
      <rect x="2" y="5" width="20" height="14" rx="2"/>
      <path d="M6 15v-6l3 3 3-3v6M16 9l2 2 2-2M18 11v4"/>
    </svg>
  ),
};

function LLMMenu() {
  const [open, setOpen] = React.useState(false);
  const [copied, setCopied] = React.useState(false);
  const ref = React.useRef(null);

  React.useEffect(() => {
    const close = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', close);
    return () => document.removeEventListener('mousedown', close);
  }, []);

  const prompt = encodeURIComponent('Ask questions about this page: ' + window.location.href);

  const copyPage = (e) => {
    e.preventDefault();
    navigator.clipboard.writeText(window.location.href).then(() => {
      setCopied(true);
      setTimeout(() => { setCopied(false); setOpen(false); }, 1200);
    }).catch(() => setOpen(false));
  };

  return (
    <div className="llm-menu" ref={ref}>
      <button className="llm-btn" onClick={() => setOpen(o => !o)} aria-expanded={open} aria-haspopup="true">
        For LLMs <span className="llm-caret" aria-hidden="true">{open ? '▴' : '▾'}</span>
      </button>
      {open && (
        <div className="llm-drop" role="menu">
          <a href={`https://chatgpt.com/?q=${prompt}`} target="_blank" rel="noopener noreferrer" role="menuitem" onClick={() => setOpen(false)}>
            <span className="llm-logo llm-logo-gpt">{LLM_ICONS.chatgpt}</span>
            <span className="llm-text">
              <span className="llm-label">Open in ChatGPT <span className="llm-ext">↗</span></span>
              <span className="llm-sub">Ask questions about this page</span>
            </span>
          </a>
          <a href={`https://claude.ai/new?q=${prompt}`} target="_blank" rel="noopener noreferrer" role="menuitem" onClick={() => setOpen(false)}>
            <span className="llm-logo llm-logo-claude">{LLM_ICONS.claude}</span>
            <span className="llm-text">
              <span className="llm-label">Open in Claude <span className="llm-ext">↗</span></span>
              <span className="llm-sub">Ask questions about this page</span>
            </span>
          </a>
          <div className="llm-divider"/>
          <a href="#" role="menuitem" onClick={copyPage}>
            <span className="llm-logo llm-logo-util">{LLM_ICONS.copy}</span>
            <span className="llm-text">
              <span className="llm-label">{copied ? 'Copied!' : 'Copy page'}</span>
              <span className="llm-sub">Copy page URL for LLMs</span>
            </span>
          </a>
          <a href={window.location.href + '?format=markdown'} target="_blank" rel="noopener noreferrer" role="menuitem" onClick={() => setOpen(false)}>
            <span className="llm-logo llm-logo-util">{LLM_ICONS.markdown}</span>
            <span className="llm-text">
              <span className="llm-label">View as Markdown <span className="llm-ext">↗</span></span>
              <span className="llm-sub">View this page as plain text</span>
            </span>
          </a>
        </div>
      )}
    </div>
  );
}

function TopNav({ tweaks, setTweaks, onTweak, current }) {
  const [navOpen, setNavOpen] = React.useState(false);

  const toggleTheme = () => {
    const next = tweaks.theme === 'dark' ? 'light' : 'dark';
    setTweaks({...tweaks, theme: next});
    window.parent.postMessage({type:'__edit_mode_set_keys', edits:{theme:next}}, '*');
  };

  const handleAnchor = (id) => (e) => {
    const el = document.getElementById(id);
    if (el) { e.preventDefault(); el.scrollIntoView({ behavior: 'smooth' }); }
  };

  return (
    <header className="top" role="banner">
      <div className="top-inner">
        <a href="index.html" className="brand" aria-label="Solana Stablecoin Analytics home">
          <span className="brand-mark" aria-hidden="true">$</span>
          Solana Stablecoin Analytics
        </a>
        <nav className="top-nav" aria-label="Primary">
          <a href="index.html" aria-current={current === 'home' ? 'page' : undefined}>Home</a>
          <a href="live.html"  aria-current={current === 'live' ? 'page' : undefined}>Dashboard</a>
          <a href="q1-2026.html" aria-current={current === 'q1' ? 'page' : undefined}>Q1 2026 Report</a>
        </nav>
        <div className="top-right">
          <LLMMenu/>
          <button className="icon-btn" onClick={onTweak} aria-label="Open tweaks panel">⚙</button>
          <a href="live.html" className="btn-primary"><span className="pulse" aria-hidden="true"/> Live</a>
          <button className="icon-btn mob-menu-btn" onClick={() => setNavOpen(o => !o)} aria-label={navOpen ? 'Close menu' : 'Open menu'} aria-expanded={navOpen}>
            {navOpen ? '✕' : '☰'}
          </button>
        </div>
      </div>
      {navOpen && (
        <nav className="mob-nav" aria-label="Mobile navigation">
          <a href="index.html" aria-current={current === 'home' ? 'page' : undefined} onClick={() => setNavOpen(false)}>Home</a>
          <a href="live.html"  aria-current={current === 'live' ? 'page' : undefined} onClick={() => setNavOpen(false)}>Dashboard</a>
          <a href="q1-2026.html" aria-current={current === 'q1' ? 'page' : undefined} onClick={() => setNavOpen(false)}>Q1 2026 Report</a>
        </nav>
      )}
    </header>
  );
}

function FooterBar() {
  const handleAnchor = (id) => (e) => {
    const el = document.getElementById(id);
    if (el) { e.preventDefault(); el.scrollIntoView({ behavior: 'smooth' }); }
  };

  return (
    <footer>
      <div className="wrap">
        <div className="foot-top">
          <div>
            <div className="foot-brand">Solana Stablecoin Analytics</div>
            <p style={{fontSize:14, lineHeight:1.6, maxWidth:'40ch', margin:0}}>
              Independent research on stablecoin flows on Solana. Free forever. Built by one person, updated quarterly.
            </p>
          </div>
          <div>
            <div className="foot-h">Data</div>
            <div className="foot-list">
              <a href="live.html">Live dashboard</a>
              <a href="index.html#methodology" onClick={handleAnchor('methodology')}>Methodology</a>
            </div>
          </div>
          <div>
            <div className="foot-h">Research</div>
            <div className="foot-list">
              <a href="q1-2026.html">Q1 2026 report</a>
              <a href="index.html#research" onClick={handleAnchor('research')}>All reports</a>
            </div>
          </div>
          <div>
            <div className="foot-h">Site</div>
            <div className="foot-list">
              <a href="index.html#faq" onClick={handleAnchor('faq')}>FAQ</a>
            </div>
          </div>
        </div>
        <div className="foot-bottom">
          <span>Data · DefiLlama · Not financial advice</span>
          <span>Built by <a href="https://x.com/0xAditya_k" target="_blank" rel="noopener noreferrer" style={{color:'var(--accent)', textDecoration:'underline', textUnderlineOffset:3}}>@0xAditya_k</a> · © 2026</span>
        </div>
      </div>
    </footer>
  );
}

function EmailModal({ open, onClose, source }) {
  const [email, setEmail] = React.useState('');
  const [status, setStatus] = React.useState('idle');

  const submit = async (e) => {
    e.preventDefault();
    setStatus('loading');
    try {
      const res = await fetch('/api/subscribe', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email: email.trim(), source: source || 'Home' }),
      });
      if (res.ok) {
        setStatus('success');
        setTimeout(onClose, 2000);
      } else {
        setStatus('error');
      }
    } catch {
      setStatus('error');
    }
  };

  React.useEffect(() => {
    if (open) { setEmail(''); setStatus('idle'); }
  }, [open]);

  if (!open) return null;
  return (
    <div
      className="email-modal open"
      role="dialog" aria-modal="true" aria-label="Get email updates"
      onClick={(e) => e.target === e.currentTarget && onClose()}
    >
      <div className="card-modal">
        <button
          onClick={onClose}
          aria-label="Close"
          style={{position:'absolute', top:14, right:14, background:'none', border:'none',
            color:'#9B9485', fontSize:20, cursor:'pointer', lineHeight:1}}
        >×</button>
        <div className="em-icon" aria-hidden="true"><i/></div>
        <div className="em-eye">Quarterly research</div>
        <h3 className="em-title">New reports, straight to your inbox.</h3>
        <p className="em-sub">No spam. One email per quarter, the moment a new report drops.</p>
        {status === 'success' ? (
          <p style={{textAlign:'center', color:'var(--accent)', fontSize:14, margin:'12px 0'}}>✓ You're on the list.</p>
        ) : (
          <>
            <form className="em-form" onSubmit={submit}>
              <input
                type="email" required placeholder="your@email.com"
                className="em-input" value={email}
                onChange={(e) => setEmail(e.target.value)}
                aria-label="Email address"
                disabled={status === 'loading'}
              />
              <button type="submit" className="em-submit" disabled={status === 'loading'}>
                {status === 'loading' ? '...' : 'Subscribe'}
              </button>
            </form>
            {status === 'error' && (
              <p style={{textAlign:'center', color:'#E49180', fontSize:12, margin:'4px 0 0'}}>Something went wrong. Please try again.</p>
            )}
            <button className="em-no" onClick={onClose}>No thanks</button>
          </>
        )}
      </div>
    </div>
  );
}

function TweaksPanel({ tweaks, setTweaks, open, setOpen }) {
  const set = (k, v) => {
    const next = {...tweaks, [k]: v};
    setTweaks(next);
    window.parent.postMessage({type:'__edit_mode_set_keys', edits:{[k]: v}}, '*');
  };
  return (
    <div className={`tweaks${open ? ' open' : ''}`} role="dialog" aria-label="Tweaks">
      <h4>Tweaks <button onClick={() => setOpen(false)} aria-label="Close">×</button></h4>
      <div className="row">
        <span>Theme</span>
        <div className="seg">
          <button className={tweaks.theme === 'light' ? 'on' : ''} onClick={() => set('theme','light')}>light</button>
          <button className={tweaks.theme === 'dark' ? 'on' : ''} onClick={() => set('theme','dark')}>dark</button>
        </div>
      </div>
      <div className="row">
        <span>Accent</span>
        <select value={tweaks.accent} onChange={e => set('accent', e.target.value)}>
          {Object.keys(ACCENTS).map(k => <option key={k}>{k}</option>)}
        </select>
      </div>
      <div className="row">
        <span>Headline font</span>
        <div className="seg">
          <button className={tweaks.headline === 'serif' ? 'on' : ''} onClick={() => set('headline','serif')}>serif</button>
          <button className={tweaks.headline === 'sans' ? 'on' : ''} onClick={() => set('headline','sans')}>sans</button>
        </div>
      </div>
    </div>
  );
}

function useTweaks(defaults) {
  const init = React.useMemo(() => {
    try {
      const saved = JSON.parse(localStorage.getItem('ssa-tweaks') || '{}');
      return { ...defaults, ...saved };
    } catch { return defaults; }
  }, []);

  const [tweaks, _setTweaks] = React.useState(init);
  const [open, setOpen] = React.useState(false);

  const setTweaks = React.useCallback((next) => {
    _setTweaks(next);
    try { localStorage.setItem('ssa-tweaks', JSON.stringify(next)); } catch {}
    window.parent.postMessage({type:'__edit_mode_set_keys', edits: next}, '*');
  }, []);

  React.useEffect(() => {
    const onMsg = (e) => {
      if (!e.data) return;
      if (e.data.type === '__activate_edit_mode') setOpen(true);
      if (e.data.type === '__deactivate_edit_mode') setOpen(false);
    };
    window.addEventListener('message', onMsg);
    window.parent.postMessage({type:'__edit_mode_available'}, '*');
    return () => window.removeEventListener('message', onMsg);
  }, []);

  React.useEffect(() => {
    const root = document.documentElement;
    root.setAttribute('data-theme', tweaks.theme === 'dark' ? 'dark' : 'light');
    const a = ACCENTS[tweaks.accent] || ACCENTS.purple;
    const c = tweaks.theme === 'dark' ? a.dark : a.light;
    root.style.setProperty('--accent', c);
    root.style.setProperty('--accent-2', c);
    document.querySelectorAll('h1.headline, h2.section-title, .rf-title, .def-title, .up-t, .method-t, .chart-title, .card h3, .cta-block h3, .live-title, .report-headline, .tldr-big, .report-body h2, .tldr-pt-t, .about-t, .chart-inline-title, .appendix h3').forEach(e => {
      if (tweaks.headline === 'sans') {
        e.style.fontFamily = '"Inter", sans-serif';
        e.style.fontWeight = '700';
        e.style.letterSpacing = '-0.02em';
      } else {
        e.style.fontFamily = '';
        e.style.fontWeight = '';
        e.style.letterSpacing = '';
      }
    });
  }, [tweaks]);

  return { tweaks, setTweaks, open, setOpen };
}

// Expose globally
Object.assign(window, {
  TWEAKS_DEFAULTS_BASE, ACCENTS,
  supplyTrend, solPrice90, tvlSeries, dates,
  supplyTrend3Y, solPrice3Y, tvlSeries3Y,
  generateDateLabels,
  LineChart, BarChart, PieChart,
  TopNav, FooterBar, TweaksPanel, useTweaks, EmailModal
});
