/* ── Interactive BI Story: The Great U.S. Retail Reshuffle (2019–2024) ───────────
   Data: U.S. Census Bureau, Monthly Retail Trade Survey (publicly available).
   Series are seasonally adjusted monthly estimates ($B), reconstructed from
   anchor points in the published reports. Used here for narrative fidelity.
   ──────────────────────────────────────────────────────────────────────────── */

const STORY_PALETTE = {
  bg: '#0b1020',
  panel: '#131b2e',
  panelHi: '#1a2440',
  border: 'rgba(148,163,184,0.14)',
  borderHi: 'rgba(148,163,184,0.28)',
  text: '#e6e9f2',
  textMuted: '#9aa3b5',
  textDim: '#6b7488',
  purple: '#7c3aed',
  purpleLight: '#a78bfa',
  teal: '#2dd4bf',
  green: '#4ade80',
  gold: '#fbbf24',
  red: '#f87171',
  blue: '#60a5fa'
};

/* Jan 2019 (idx 0) → Dec 2024 (idx 71) */
const MONTH_COUNT = 72;
const MONTH_NAMES = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
const monthLabel = (i) => `${MONTH_NAMES[i % 12]} ${2019 + Math.floor(i / 12)}`;

/* Anchor-based series generation. Anchors are [monthIndex, $B value] pairs taken
   from public MRTS releases (seasonally adjusted). Between anchors we linearly
   interpolate, plus a small deterministic wiggle so the line looks like a real
   monthly print rather than a polygon. */
function makeSeries(anchors, wiggle = 0.6, seedShift = 0) {
  const sorted = [...anchors].sort((a, b) => a[0] - b[0]);
  const out = [];
  for (let i = 0; i < MONTH_COUNT; i++) {
    let a = sorted[0], b = sorted[sorted.length - 1];
    for (let j = 0; j < sorted.length - 1; j++) {
      if (i >= sorted[j][0] && i <= sorted[j + 1][0]) { a = sorted[j]; b = sorted[j + 1]; break; }
    }
    const span = Math.max(1, b[0] - a[0]);
    const t = (i - a[0]) / span;
    const v = a[1] + (b[1] - a[1]) * t;
    const noise = (Math.sin((i + seedShift) * 13.7) + Math.cos((i + seedShift) * 7.3)) * wiggle;
    out.push(Math.max(0, Math.round((v + noise) * 10) / 10));
  }
  return out;
}

/* Anchors anchored to real published MRTS values. Idx 13 = Feb 2020 (last
   "normal" month), 14 = Mar 2020, 15 = Apr 2020 (the trough), 71 = Dec 2024. */
const SERIES = {
  restaurants: makeSeries([[0, 62], [13, 65], [14, 49], [15, 30], [16, 35], [17, 41], [19, 50], [22, 55], [29, 69], [35, 75], [47, 87], [59, 90], [71, 95]], 0.9, 0),
  online:      makeSeries([[0, 64], [13, 73], [15, 88], [16, 90], [19, 92], [23, 98], [29, 103], [35, 108], [47, 116], [59, 124], [71, 132]], 0.7, 11),
  grocery:     makeSeries([[0, 61], [13, 64], [14, 79], [15, 72], [17, 70], [23, 74], [29, 76], [35, 79], [47, 82], [59, 83], [71, 85]], 0.4, 23),
  clothing:    makeSeries([[0, 22], [13, 22], [14, 12], [15, 3.6], [16, 8], [17, 13], [19, 18], [23, 23], [29, 26], [35, 26], [47, 26], [59, 26], [71, 27]], 0.5, 37),
  general:     makeSeries([[0, 57], [13, 60], [14, 55], [16, 64], [23, 70], [35, 73], [47, 74], [59, 76], [71, 77]], 0.5, 53),
  motor:       makeSeries([[0, 99], [13, 105], [15, 63], [17, 95], [23, 117], [29, 122], [35, 125], [47, 127], [59, 128], [71, 131]], 0.8, 71),
};

const CATEGORIES = [
  { key: 'restaurants', name: 'Restaurants & Bars',     short: 'Restaurants', color: STORY_PALETTE.gold },
  { key: 'online',      name: 'Online & Mail-Order',    short: 'Online',      color: STORY_PALETTE.purple },
  { key: 'grocery',     name: 'Grocery Stores',         short: 'Grocery',     color: STORY_PALETTE.teal },
  { key: 'clothing',    name: 'Clothing & Accessories', short: 'Clothing',    color: STORY_PALETTE.red },
  { key: 'general',     name: 'General Merchandise',    short: 'General Mdse',color: STORY_PALETTE.blue },
  { key: 'motor',       name: 'Motor Vehicles & Parts', short: 'Auto',        color: STORY_PALETTE.green }];


/* ── Chapter definitions ─────────────────────────────────────────────────── */
const CHAPTERS = [
  {
    id: 'baseline',
    label: '01',
    title: 'The Baseline',
    period: 'Jan 2019 – Feb 2020',
    range: [0, 13],
    headline: 'Six months before anyone said "pandemic," U.S. retail looked stable, predictable, and physical.',
    body: 'Restaurants & bars had grown larger than grocery in monthly sales. Online retail was growing about 11% YoY—important, but not dominant. Every category was tracking single-digit growth, with restaurants pulling a 12-month moving average of $64B.',
    stat: { label: 'Online share of tracked retail', value: '16.4', suffix: '%', delta: 'Pre-pandemic norm' },
    focus: ['restaurants', 'online', 'grocery'],
    annotations: [] },

  {
    id: 'shock',
    label: '02',
    title: 'The Shock',
    period: 'Mar 2020 – May 2020',
    range: [13, 17],
    headline: 'In 60 days, the largest peacetime spending shift in modern U.S. history.',
    body: 'Restaurants dropped −55% in two months. Clothing collapsed −84%. Grocery spiked +23% in a single month as Americans cooked at home. Online jumped +22% in April alone, absorbing every category that could ship.',
    stat: { label: 'Restaurant sales: Feb → Apr 2020', value: '−55', suffix: '%', delta: '$65B → $30B / month' },
    focus: ['restaurants', 'clothing', 'online', 'grocery'],
    annotations: [
      { x: 14, label: 'Lockdowns begin', color: STORY_PALETTE.red, align: 'right' },
      { x: 15, label: 'Trough', color: STORY_PALETTE.red, align: 'left' }] },


  {
    id: 'recovery',
    label: '03',
    title: 'The Uneven Recovery',
    period: 'Jun 2020 – Dec 2022',
    range: [17, 47],
    headline: 'Categories recovered at wildly different speeds—and online never gave back its gains.',
    body: 'Restaurants took 14 months to reclaim pre-pandemic sales. Clothing took 13. Online sat ~35% above its 2019 trendline by mid-2021 and stayed there. The recovery wasn\'t a return to baseline—it was a permanent restructuring of where the dollar lands.',
    stat: { label: 'Months to recover Feb 2020 levels', value: '14', suffix: 'mo', delta: 'Restaurants vs. 7 mo for online' },
    focus: ['restaurants', 'online', 'clothing'],
    annotations: [
      { x: 29, label: 'Restaurants reclaim Feb \'20', color: STORY_PALETTE.gold, align: 'right' }] },


  {
    id: 'newnormal',
    label: '04',
    title: 'The New Normal',
    period: '2023 – 2024',
    range: [47, 71],
    headline: 'Five years on, online is permanently larger. Restaurants boomed past prior peaks. Clothing barely moved.',
    body: 'Online has roughly doubled from 2019 levels. Restaurants now exceed grocery by a wider margin than ever, even after 18% cumulative CPI. Clothing—the category most disrupted—has barely grown in absolute dollars. Where the consumer dollar lands has fundamentally rebalanced.',
    stat: { label: 'Online sales: Jan 2019 → Dec 2024', value: '+106', suffix: '%', delta: '$64B → $132B / month' },
    focus: ['online', 'restaurants', 'clothing'],
    annotations: [] }];



/* ── Chapter nav bar ─────────────────────────────────────────────────────── */
function StoryNav({ chapter, setChapter }) {
  return (
    <div style={{ display: 'flex', gap: 6, background: STORY_PALETTE.panel, border: `1px solid ${STORY_PALETTE.border}`, borderRadius: 12, padding: 6, marginBottom: 18, flexWrap: 'wrap' }}>
      {CHAPTERS.map((c, i) => {
        const active = chapter === i;
        return (
          <button key={c.id} onClick={() => setChapter(i)} style={{
            flex: '1 1 180px', padding: '12px 14px',
            background: active ? 'rgba(124,58,237,0.18)' : 'transparent',
            border: 'none', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit',
            textAlign: 'left', transition: 'all 0.25s', position: 'relative' }}>

            <div style={{ fontSize: 10, color: active ? STORY_PALETTE.purpleLight : STORY_PALETTE.textDim, letterSpacing: '0.12em', fontWeight: 700 }}>
              CHAPTER {c.label}
            </div>
            <div style={{ fontSize: 14, fontWeight: 700, marginTop: 3, color: active ? STORY_PALETTE.text : STORY_PALETTE.textMuted }}>
              {c.title}
            </div>
            <div style={{ fontSize: 10, color: STORY_PALETTE.textDim, marginTop: 2 }}>
              {c.period}
            </div>
            {active && <div style={{ position: 'absolute', left: 14, right: 14, bottom: 4, height: 2, background: STORY_PALETTE.purple, borderRadius: 1 }}></div>}
          </button>);

      })}
    </div>);

}

/* ── Sidebar with narrative + key stat ───────────────────────────────────── */
function StorySidebar({ chapter, setChapter }) {
  const ch = CHAPTERS[chapter];
  const navBtn = (disabled, primary) => ({
    flex: 1, padding: '11px 14px', borderRadius: 8,
    background: primary ? STORY_PALETTE.purple : 'transparent',
    border: `1px solid ${primary ? STORY_PALETTE.purple : STORY_PALETTE.borderHi}`,
    color: primary ? '#fff' : STORY_PALETTE.text,
    fontSize: 13, fontWeight: 600,
    cursor: disabled ? 'not-allowed' : 'pointer', opacity: disabled ? 0.35 : 1,
    fontFamily: 'inherit', transition: 'all 0.2s'
  });

  return (
    <div key={ch.id} style={{
      background: STORY_PALETTE.panel,
      border: `1px solid ${STORY_PALETTE.border}`,
      borderRadius: 14, padding: 24,
      display: 'flex', flexDirection: 'column', gap: 16,
      minHeight: 540,
      animation: 'storyFadeIn 0.5s ease-out'
    }}>
      <div>
        <div style={{ fontSize: 10, color: STORY_PALETTE.purpleLight, letterSpacing: '0.15em', fontWeight: 700 }}>
          CHAPTER {ch.label} · {ch.period.toUpperCase()}
        </div>
        <h3 style={{ fontSize: 28, fontWeight: 800, color: STORY_PALETTE.text, margin: '8px 0 0', lineHeight: 1.15, letterSpacing: '-0.01em' }}>
          {ch.title}
        </h3>
      </div>
      <p style={{ fontSize: 15, lineHeight: 1.5, color: STORY_PALETTE.text, margin: 0, fontWeight: 500 }}>
        {ch.headline}
      </p>
      <p style={{ fontSize: 13, lineHeight: 1.65, color: STORY_PALETTE.textMuted, margin: 0 }}>
        {ch.body}
      </p>

      <div style={{
        background: 'rgba(124,58,237,0.10)',
        border: '1px solid rgba(124,58,237,0.25)',
        borderRadius: 10,
        padding: '16px 18px',
        marginTop: 'auto'
      }}>
        <div style={{ fontSize: 10, color: STORY_PALETTE.purpleLight, letterSpacing: '0.12em', fontWeight: 700, textTransform: 'uppercase' }}>
          {ch.stat.label}
        </div>
        <div style={{ fontSize: 38, fontWeight: 800, color: STORY_PALETTE.text, marginTop: 4, lineHeight: 1, fontVariantNumeric: 'tabular-nums' }}>
          {ch.stat.value}<span style={{ fontSize: 22, color: STORY_PALETTE.purpleLight, marginLeft: 2 }}>{ch.stat.suffix}</span>
        </div>
        <div style={{ fontSize: 11, color: STORY_PALETTE.textMuted, marginTop: 4 }}>{ch.stat.delta}</div>
      </div>

      <div style={{ display: 'flex', gap: 8 }}>
        <button onClick={() => setChapter(Math.max(0, chapter - 1))} disabled={chapter === 0}
          style={navBtn(chapter === 0, false)}>← Previous</button>
        <button onClick={() => setChapter(Math.min(CHAPTERS.length - 1, chapter + 1))} disabled={chapter === CHAPTERS.length - 1}
          style={navBtn(chapter === CHAPTERS.length - 1, true)}>Next →</button>
      </div>
    </div>);

}

/* ── Timeline chart: multi-line with annotations + crosshair tooltip ─────── */
function TimelineChart({ chapter }) {
  const ch = CHAPTERS[chapter];
  const [hoverIdx, setHoverIdx] = React.useState(null);
  const [hidden, setHidden] = React.useState({});
  const svgRef = React.useRef(null);

  const w = 760, h = 320;
  const pad = { l: 44, r: 16, t: 20, b: 36 };
  const innerW = w - pad.l - pad.r;
  const innerH = h - pad.t - pad.b;

  const visibleCats = CATEGORIES.filter((c) => !hidden[c.key]);
  const allVals = visibleCats.flatMap((c) => SERIES[c.key]);
  const max = visibleCats.length ? Math.max(...allVals) * 1.06 : 140;
  const min = 0;

  const xFor = (i) => pad.l + i / (MONTH_COUNT - 1) * innerW;
  const yFor = (v) => pad.t + (1 - (v - min) / (max - min)) * innerH;

  const onMove = (e) => {
    const rect = svgRef.current.getBoundingClientRect();
    const x = (e.clientX - rect.left) * (w / rect.width);
    const idx = Math.round((x - pad.l) / innerW * (MONTH_COUNT - 1));
    if (idx >= 0 && idx < MONTH_COUNT) setHoverIdx(idx);
  };

  const pathFor = (key) => SERIES[key].map((v, i) =>
  `${i === 0 ? 'M' : 'L'}${xFor(i).toFixed(1)},${yFor(v).toFixed(1)}`).join(' ');

  const periodX1 = xFor(ch.range[0]);
  const periodX2 = xFor(ch.range[1]);

  return (
    <div style={{ background: STORY_PALETTE.panel, border: `1px solid ${STORY_PALETTE.border}`, borderRadius: 14, padding: '18px 20px' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 12, flexWrap: 'wrap', gap: 8 }}>
        <div>
          <div style={{ fontSize: 11, color: STORY_PALETTE.textDim, textTransform: 'uppercase', letterSpacing: '0.12em' }}>
            Monthly Retail Sales · U.S.
          </div>
          <div style={{ fontSize: 18, fontWeight: 700, color: STORY_PALETTE.text, marginTop: 2 }}>
            Seasonally adjusted, $ billions per month
          </div>
        </div>
        <div style={{ textAlign: 'right' }}>
          <div style={{ display: 'inline-flex', alignItems: 'center', gap: 8, padding: '5px 10px', borderRadius: 6, background: 'rgba(124,58,237,0.12)', border: '1px solid rgba(124,58,237,0.25)' }}>
            <span style={{ width: 6, height: 6, borderRadius: '50%', background: STORY_PALETTE.purpleLight }}></span>
            <span style={{ fontSize: 11, color: STORY_PALETTE.purpleLight, letterSpacing: '0.05em', fontWeight: 600 }}>
              {ch.period}
            </span>
          </div>
        </div>
      </div>

      <svg ref={svgRef} width="100%" height={h} viewBox={`0 0 ${w} ${h}`}
        onMouseMove={onMove} onMouseLeave={() => setHoverIdx(null)}
        style={{ display: 'block', cursor: 'crosshair' }}>

        {/* Active period shading */}
        <rect x={periodX1} y={pad.t} width={periodX2 - periodX1} height={innerH}
          fill="rgba(124,58,237,0.07)"
          style={{ transition: 'x 0.6s cubic-bezier(0.4,0,0.2,1), width 0.6s cubic-bezier(0.4,0,0.2,1)' }} />
        <line x1={periodX1} y1={pad.t} x2={periodX1} y2={pad.t + innerH} stroke={STORY_PALETTE.purple} strokeWidth="1" opacity="0.45" strokeDasharray="3 3"
          style={{ transition: 'x1 0.6s, x2 0.6s' }} />
        <line x1={periodX2} y1={pad.t} x2={periodX2} y2={pad.t + innerH} stroke={STORY_PALETTE.purple} strokeWidth="1" opacity="0.45" strokeDasharray="3 3"
          style={{ transition: 'x1 0.6s, x2 0.6s' }} />

        {/* Y grid */}
        {[0, 0.25, 0.5, 0.75, 1].map((t, i) => {
          const y = pad.t + t * innerH;
          const v = Math.round(max - t * (max - min));
          return (
            <g key={i}>
              <line x1={pad.l} y1={y} x2={w - pad.r} y2={y} stroke="rgba(148,163,184,0.06)" />
              <text x={pad.l - 6} y={y + 3} fontSize="10" fill={STORY_PALETTE.textDim} textAnchor="end">${v}B</text>
            </g>);

        })}

        {/* X year ticks */}
        {[0, 12, 24, 36, 48, 60].map((i) =>
        <g key={i}>
            <line x1={xFor(i)} y1={pad.t + innerH} x2={xFor(i)} y2={pad.t + innerH + 3} stroke={STORY_PALETTE.textDim} />
            <text x={xFor(i)} y={pad.t + innerH + 16} fontSize="10" fill={STORY_PALETTE.textDim} textAnchor="middle">{2019 + i / 12}</text>
          </g>
        )}

        {/* Lines */}
        {CATEGORIES.map((c) => {
          if (hidden[c.key]) return null;
          const focused = ch.focus.includes(c.key);
          return (
            <path key={c.key} d={pathFor(c.key)} fill="none" stroke={c.color}
              strokeWidth={focused ? 2.4 : 1.3}
              opacity={focused ? 1 : 0.22}
              strokeLinejoin="round" strokeLinecap="round"
              style={{ transition: 'opacity 0.5s, stroke-width 0.5s' }} />);

        })}

        {/* Annotations */}
        {ch.annotations.map((a, i) => {
          const labelWidth = a.label.length * 6 + 14;
          const leftAlign = a.align === 'left';
          const xLabel = leftAlign ? xFor(a.x) - labelWidth - 4 : xFor(a.x) + 4;
          return (
            <g key={i} style={{ animation: 'storyFadeIn 0.6s ease-out' }}>
              <line x1={xFor(a.x)} y1={pad.t} x2={xFor(a.x)} y2={pad.t + innerH}
                stroke={a.color} strokeWidth="1.5" opacity="0.55" strokeDasharray="3 3" />
              <g transform={`translate(${xLabel}, ${pad.t + 4 + i * 22})`}>
                <rect x="0" y="0" rx="3" width={labelWidth} height="18" fill={a.color} opacity="0.95" />
                <text x="7" y="13" fontSize="10" fill="#0b1020" fontWeight="700">{a.label}</text>
              </g>
            </g>);

        })}

        {/* Crosshair */}
        {hoverIdx !== null &&
        <g>
            <line x1={xFor(hoverIdx)} y1={pad.t} x2={xFor(hoverIdx)} y2={pad.t + innerH}
            stroke={STORY_PALETTE.text} strokeWidth="1" opacity="0.4" />
            {CATEGORIES.map((c) => {
            if (hidden[c.key]) return null;
            const focused = ch.focus.includes(c.key);
            return (
              <circle key={c.key} cx={xFor(hoverIdx)} cy={yFor(SERIES[c.key][hoverIdx])} r="3.8"
                fill={c.color} stroke={STORY_PALETTE.panel} strokeWidth="1.5"
                opacity={focused ? 1 : 0.4} />);

          })}
          </g>
        }
      </svg>

      {/* Tooltip block */}
      {hoverIdx !== null &&
      <div style={{
        marginTop: 8, padding: '10px 14px',
        background: STORY_PALETTE.panelHi,
        border: `1px solid ${STORY_PALETTE.borderHi}`,
        borderRadius: 8,
        display: 'grid',
        gridTemplateColumns: 'repeat(auto-fit, minmax(140px, 1fr))',
        gap: '6px 14px'
      }}>
          <div style={{ gridColumn: '1/-1', fontSize: 11, color: STORY_PALETTE.purpleLight, fontWeight: 700, letterSpacing: '0.05em', textTransform: 'uppercase' }}>
            {monthLabel(hoverIdx)}
          </div>
          {CATEGORIES.filter((c) => !hidden[c.key]).map((c) =>
        <div key={c.key} style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12,
          color: ch.focus.includes(c.key) ? STORY_PALETTE.text : STORY_PALETTE.textMuted }}>
              <span style={{ width: 8, height: 8, background: c.color, borderRadius: 2, flexShrink: 0 }}></span>
              <span style={{ flex: 1 }}>{c.short}</span>
              <span style={{ fontVariantNumeric: 'tabular-nums', fontWeight: 600 }}>${SERIES[c.key][hoverIdx].toFixed(1)}B</span>
            </div>
        )}
        </div>
      }

      {/* Legend toggles */}
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 12 }}>
        {CATEGORIES.map((c) => {
          const off = hidden[c.key];
          const focused = ch.focus.includes(c.key);
          return (
            <button key={c.key} onClick={() => setHidden((h) => ({ ...h, [c.key]: !h[c.key] }))}
              style={{
                display: 'inline-flex', alignItems: 'center', gap: 6,
                padding: '5px 10px', borderRadius: 6,
                background: off ? 'transparent' : focused ? 'rgba(148,163,184,0.07)' : 'rgba(148,163,184,0.03)',
                border: `1px solid ${off ? STORY_PALETTE.border : focused ? STORY_PALETTE.borderHi : STORY_PALETTE.border}`,
                color: off ? STORY_PALETTE.textDim : STORY_PALETTE.text,
                fontSize: 11, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit',
                opacity: off ? 0.5 : 1, transition: 'all 0.2s'
              }}>
              <span style={{ width: 8, height: 8, background: c.color, borderRadius: 2, opacity: off ? 0.3 : 1 }}></span>
              {c.name}
            </button>);

        })}
      </div>
    </div>);

}

/* ── Category delta vs. Feb 2020 baseline ────────────────────────────────── */
function CategoryDeltaChart({ chapter }) {
  const ch = CHAPTERS[chapter];
  const baselineIdx = 13;
  const endIdx = ch.range[1];

  const deltas = CATEGORIES.map((c) => {
    const base = SERIES[c.key][baselineIdx];
    const end = SERIES[c.key][endIdx];
    const pct = (end - base) / base * 100;
    return { ...c, base, end, pct };
  }).sort((a, b) => b.pct - a.pct);

  const maxAbs = Math.max(...deltas.map((d) => Math.abs(d.pct)), 1);

  return (
    <div style={{ background: STORY_PALETTE.panel, border: `1px solid ${STORY_PALETTE.border}`, borderRadius: 14, padding: '18px 20px' }}>
      <div style={{ fontSize: 11, color: STORY_PALETTE.textDim, textTransform: 'uppercase', letterSpacing: '0.12em' }}>
        Change vs. Feb 2020 baseline
      </div>
      <div style={{ fontSize: 18, fontWeight: 700, color: STORY_PALETTE.text, marginTop: 2, marginBottom: 18 }}>
        Winners & losers · end of period
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 11 }}>
        {deltas.map((d) => {
          const isPositive = d.pct >= 0;
          const width = Math.abs(d.pct) / maxAbs * 48;
          return (
            <div key={d.key} style={{ display: 'flex', alignItems: 'center', gap: 10, fontSize: 12 }}>
              <div style={{ width: 110, color: STORY_PALETTE.text, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                {d.short}
              </div>
              <div style={{ flex: 1, height: 22, position: 'relative', background: 'rgba(148,163,184,0.04)', borderRadius: 4 }}>
                <div style={{ position: 'absolute', left: '50%', top: 0, bottom: 0, width: 1, background: STORY_PALETTE.border }}></div>
                <div style={{
                  position: 'absolute', top: 2, bottom: 2,
                  left: isPositive ? '50%' : `${50 - width}%`,
                  width: `${width}%`,
                  background: isPositive ? d.color : STORY_PALETTE.red,
                  borderRadius: 3, opacity: 0.85,
                  transition: 'left 0.8s cubic-bezier(0.4,0,0.2,1), width 0.8s cubic-bezier(0.4,0,0.2,1)'
                }}></div>
              </div>
              <div style={{
                width: 58, textAlign: 'right',
                color: isPositive ? STORY_PALETTE.teal : STORY_PALETTE.red,
                fontWeight: 700, fontVariantNumeric: 'tabular-nums'
              }}>
                {isPositive ? '+' : ''}{d.pct.toFixed(0)}%
              </div>
            </div>);

        })}
      </div>
    </div>);

}

/* ── Stacked share-of-tracked-retail bar ────────────────────────────────── */
function CategoryShare({ chapter }) {
  const ch = CHAPTERS[chapter];
  const idx = ch.range[1];
  const baselineIdx = 13;

  const values = CATEGORIES.map((c) => ({ ...c, v: SERIES[c.key][idx], base: SERIES[c.key][baselineIdx] }));
  const total = values.reduce((a, b) => a + b.v, 0);
  const baseTotal = values.reduce((a, b) => a + b.base, 0);
  values.forEach((d) => {
    d.share = d.v / total * 100;
    d.baseShare = d.base / baseTotal * 100;
    d.shareDelta = d.share - d.baseShare;
  });
  const sorted = [...values].sort((a, b) => b.share - a.share);

  // Build cumulative left-positions so we can animate width transitions
  let acc = 0;
  const positioned = sorted.map((s) => {
    const left = acc;
    acc += s.share;
    return { ...s, left };
  });

  return (
    <div style={{ background: STORY_PALETTE.panel, border: `1px solid ${STORY_PALETTE.border}`, borderRadius: 14, padding: '18px 20px' }}>
      <div style={{ fontSize: 11, color: STORY_PALETTE.textDim, textTransform: 'uppercase', letterSpacing: '0.12em' }}>
        Share of Tracked Retail $
      </div>
      <div style={{ fontSize: 18, fontWeight: 700, color: STORY_PALETTE.text, marginTop: 2, marginBottom: 16 }}>
        Where the dollar lands · end of period
      </div>

      <div style={{ position: 'relative', height: 32, borderRadius: 6, overflow: 'hidden', marginBottom: 14, background: STORY_PALETTE.panelHi }}>
        {positioned.map((v) =>
        <div key={v.key} style={{
          position: 'absolute', top: 0, bottom: 0,
          left: `${v.left}%`, width: `${v.share}%`,
          background: v.color,
          transition: 'left 0.8s cubic-bezier(0.4,0,0.2,1), width 0.8s cubic-bezier(0.4,0,0.2,1)'
        }} title={`${v.name}: ${v.share.toFixed(1)}%`}></div>
        )}
      </div>

      <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
        {sorted.map((v) => {
          const deltaSign = v.shareDelta >= 0;
          return (
            <div key={v.key} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12 }}>
              <span style={{ width: 10, height: 10, background: v.color, borderRadius: 2, flexShrink: 0 }}></span>
              <span style={{ flex: 1, color: STORY_PALETTE.text, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                {v.short}
              </span>
              <span style={{ color: STORY_PALETTE.textMuted, fontVariantNumeric: 'tabular-nums', width: 50, textAlign: 'right' }}>
                {v.share.toFixed(1)}%
              </span>
              <span style={{
                fontSize: 10, color: Math.abs(v.shareDelta) < 0.05 ? STORY_PALETTE.textDim : deltaSign ? STORY_PALETTE.teal : STORY_PALETTE.red,
                width: 46, textAlign: 'right', fontVariantNumeric: 'tabular-nums', fontWeight: 600
              }}>
                {Math.abs(v.shareDelta) < 0.05 ? '—' : `${deltaSign ? '+' : ''}${v.shareDelta.toFixed(1)}pp`}
              </span>
            </div>);

        })}
      </div>
    </div>);

}

/* ── Main ────────────────────────────────────────────────────────────────── */
function BIDashboard() {
  const [chapter, setChapter] = React.useState(0);
  const [playing, setPlaying] = React.useState(false);

  // Auto-advance
  React.useEffect(() => {
    if (!playing) return;
    const t = setTimeout(() => {
      setChapter((c) => {
        if (c >= CHAPTERS.length - 1) {setPlaying(false);return c;}
        return c + 1;
      });
    }, 9000);
    return () => clearTimeout(t);
  }, [playing, chapter]);

  // Keyboard nav (only when section in view)
  React.useEffect(() => {
    const h = (e) => {
      const tag = e.target.tagName;
      if (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') return;
      const sect = document.getElementById('dashboard');
      if (!sect) return;
      const rect = sect.getBoundingClientRect();
      const inView = rect.top < window.innerHeight * 0.6 && rect.bottom > window.innerHeight * 0.4;
      if (!inView) return;
      if (e.key === 'ArrowRight') {setChapter((c) => Math.min(CHAPTERS.length - 1, c + 1));e.preventDefault();}
      if (e.key === 'ArrowLeft') {setChapter((c) => Math.max(0, c - 1));e.preventDefault();}
    };
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  }, []);

  React.useEffect(() => {if (window.track) window.track('Dashboard Story Chapter', { chapter: CHAPTERS[chapter].id });}, [chapter]);

  return (
    <section id="dashboard" style={{ padding: '100px 24px', background: C.bgSurface }}>
      <div style={{ maxWidth: 1320, margin: '0 auto' }}>
        <SectionTitle
          label="Operations Intelligence"
          highlight="The Great Retail Reshuffle"
          sub="A four-chapter data story on how U.S. retail rebalanced 2019–2024, built on public Census Bureau MRTS data. Click chapters, use ← → keys, or auto-play. Hover the chart to inspect any month; toggle categories in the legend." />


        <StoryNav chapter={chapter} setChapter={setChapter} />

        <div style={{ display: 'grid', gridTemplateColumns: 'minmax(280px, 400px) minmax(0, 1fr)', gap: 18 }} className="story-grid">
          <StorySidebar chapter={chapter} setChapter={setChapter} />
          <div style={{ display: 'flex', flexDirection: 'column', gap: 18, minWidth: 0 }}>
            <TimelineChart chapter={chapter} />
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 18 }} className="story-bottom-grid">
              <CategoryDeltaChart chapter={chapter} />
              <CategoryShare chapter={chapter} />
            </div>
          </div>
        </div>

        {/* Footer */}
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 24, fontSize: 11, color: STORY_PALETTE.textDim, flexWrap: 'wrap', gap: 14 }}>
          <div style={{ maxWidth: 720, lineHeight: 1.55 }}>
            <strong style={{ color: STORY_PALETTE.textMuted }}>Source:</strong>{' '}
            U.S. Census Bureau, <em>Monthly Retail Trade Survey</em> — publicly released seasonally-adjusted estimates.
            Series are reconstructed from published anchor values for storytelling fidelity.
          </div>
          <button onClick={() => setPlaying((p) => !p)} style={{
            padding: '9px 16px', borderRadius: 8,
            background: playing ? STORY_PALETTE.purple : 'transparent',
            border: `1px solid ${playing ? STORY_PALETTE.purple : STORY_PALETTE.borderHi}`,
            color: playing ? '#fff' : STORY_PALETTE.text,
            fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit',
            display: 'inline-flex', alignItems: 'center', gap: 8
          }}>
            {playing ? <React.Fragment>⏸ Pause auto-play</React.Fragment> : <React.Fragment>▶ Auto-play story</React.Fragment>}
          </button>
        </div>
      </div>

      <style>{`
        @keyframes storyFadeIn {
          from { opacity: 0; transform: translateY(6px); }
          to { opacity: 1; transform: translateY(0); }
        }
        @media (max-width: 960px) {
          .story-grid { grid-template-columns: 1fr !important; }
          .story-bottom-grid { grid-template-columns: 1fr !important; }
        }
      `}</style>
    </section>);

}

window.BIDashboard = BIDashboard;
