// Big, interactive Science section.
// Replaces the small "You forget 80%..." block.
//
// What it does:
//  • Hero stat — animates 100 → 20 ("80% gone in 7 days") with a draining ring.
//  • Big interactive chart — drag the day-handle (0–30) and watch retention
//    react live on both curves. Toggle "Add reviews" to compare.
//  • Stat readout under the chart updates with the handle.
//  • Three small "what spaced repetition does" cards underneath.
//
// All built with the existing palette (var(--blue), --line, --muted, etc.) so
// it inherits light/dark themes automatically.

const SI_W = 920, SI_H = 480;
const SI_PAD_L = 64, SI_PAD_R = 32, SI_PAD_T = 40, SI_PAD_B = 80;
const SI_INNER_W = SI_W - SI_PAD_L - SI_PAD_R;
const SI_INNER_H = SI_H - SI_PAD_T - SI_PAD_B;
const SI_DAYS = 30;
const SI_REVIEWS = [1, 3, 7, 16, 30];

// Build curve once at module load — no need to recompute per render
const buildNoReview = () => {
  const arr = [];
  for (let i = 0; i <= 200; i++) {
    const x = (i / 200) * SI_DAYS;
    const y = 0.18 + 0.82 * Math.exp(-x / 3.5);
    arr.push([x, y]);
  }
  return arr;
};
const buildWithReview = () => {
  const arr = [];
  for (let i = 0; i <= 400; i++) {
    const x = (i / 400) * SI_DAYS;
    let activeReview = 0, activeDecay = 3.5;
    for (let r = 0; r < SI_REVIEWS.length; r++) {
      if (x >= SI_REVIEWS[r]) {
        activeReview = SI_REVIEWS[r];
        activeDecay = 3.5 + r * 4;
      }
    }
    const dx = x - activeReview;
    const y = 0.2 + 0.8 * Math.exp(-dx / activeDecay);
    arr.push([x, y]);
  }
  return arr;
};

const SI_CURVE_NO = buildNoReview();
const SI_CURVE_YES = buildWithReview();

// Analytic versions of the curve functions — used for the live readouts
// so values match the rendered curve EXACTLY at any day, including at
// the rep step boundaries (where sample interpolation would smooth across
// the discontinuity and lie about retention).
const noReviewYAt = (day) => 0.18 + 0.82 * Math.exp(-day / 3.5);
const withReviewYAt = (day) => {
  let activeReview = 0, activeDecay = 3.5;
  for (let r = 0; r < SI_REVIEWS.length; r++) {
    if (day >= SI_REVIEWS[r]) {
      activeReview = SI_REVIEWS[r];
      activeDecay = 3.5 + r * 4;
    }
  }
  const dx = day - activeReview;
  return 0.2 + 0.8 * Math.exp(-dx / activeDecay);
};

const siXScale = (x) => SI_PAD_L + (x / SI_DAYS) * SI_INNER_W;
const siYScale = (y) => SI_PAD_T + (1 - y) * SI_INNER_H;

const yAtDay = (curve, day) => {
  // linear interp between nearest two samples
  const stepDays = SI_DAYS / (curve.length - 1);
  const idx = Math.min(curve.length - 2, Math.floor(day / stepDays));
  const [x0, y0] = curve[idx];
  const [x1, y1] = curve[idx + 1];
  const t = (day - x0) / (x1 - x0);
  return y0 + (y1 - y0) * t;
};

// Big animated counter for the hero stat
const SiStatCounter = ({ from = 100, to = 20, durationMs = 1800, suffix = "%", inView }) => {
  const [val, setVal] = React.useState(from);
  React.useEffect(() => {
    if (!inView) return;
    let raf, start;
    const tick = (ts) => {
      if (!start) start = ts;
      const p = Math.min((ts - start) / durationMs, 1);
      const eased = 1 - Math.pow(1 - p, 3);
      setVal(Math.round(from + (to - from) * eased));
      if (p < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [inView, from, to, durationMs]);
  return <>{val}{suffix}</>;
};

// Drainable ring next to the hero stat — fills from 100% → 20%
// Animation duration must match SiStatCounter's so the ring drains exactly
// in lock-step with the number ticking down.
const SiDrainRing = ({ inView, durationMs = 1800 }) => {
  const [pct, setPct] = React.useState(1);
  React.useEffect(() => {
    if (!inView) return;
    let raf, start;
    const tick = (ts) => {
      if (!start) start = ts;
      const p = Math.min((ts - start) / durationMs, 1);
      const eased = 1 - Math.pow(1 - p, 3);
      setPct(1 - eased * 0.8); // 1 → 0.2
      if (p < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [inView, durationMs]);
  const r = 80, c = 2 * Math.PI * r;
  return (
    <svg viewBox="0 0 200 200" style={{ width: "100%", height: "auto", display: "block" }}>
      <circle cx="100" cy="100" r={r} fill="none" stroke="var(--line)" strokeWidth="14" />
      <circle
        cx="100" cy="100" r={r} fill="none"
        stroke="var(--blue)" strokeWidth="14" strokeLinecap="round"
        strokeDasharray={c}
        strokeDashoffset={c * (1 - pct)}
        transform="rotate(-90 100 100)"
      />
    </svg>
  );
};

const ScienceInteractive = () => {
  const heroRef = React.useRef(null);
  const chartRef = React.useRef(null);
  const [heroInView, setHeroInView] = React.useState(false);
  const [chartInView, setChartInView] = React.useState(false);
  const [day, setDay] = React.useState(7);            // current handle position
  const [showReviews, setShowReviews] = React.useState(false);
  const [draggedOnce, setDraggedOnce] = React.useState(false);
  const dragRef = React.useRef(null);
  const [drawT, setDrawT] = React.useState(0);        // 0 → 1, draw-in animation

  // Observe each block independently — hero animates when hero is on screen,
  // chart animates only when the chart card is properly in view. With a tall
  // section, observing the whole thing fires too eagerly.
  React.useEffect(() => {
    const el = heroRef.current;
    if (!el) return;
    const obs = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) setHeroInView(true);
    }, { threshold: 0.6 });
    obs.observe(el);
    return () => obs.disconnect();
  }, []);

  React.useEffect(() => {
    const el = chartRef.current;
    if (!el) return;
    const obs = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) setChartInView(true);
    }, { threshold: 0.5 });
    obs.observe(el);
    return () => obs.disconnect();
  }, []);

  // Curve draw-in animation — fires when chart enters view
  React.useEffect(() => {
    if (!chartInView) return;
    let raf, start;
    const tick = (ts) => {
      if (!start) start = ts;
      const p = Math.min((ts - start) / 1600, 1);
      setDrawT(1 - Math.pow(1 - p, 3));
      if (p < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [chartInView]);

  // Auto-toggle reviews on after the curve has drawn
  React.useEffect(() => {
    if (!chartInView) return;
    const tm = setTimeout(() => setShowReviews(true), 3800);
    return () => clearTimeout(tm);
  }, [chartInView]);

  // Drag interaction on the chart
  const onPointerDown = (e) => {
    if (!dragRef.current) return;
    const move = (clientX) => {
      const rect = dragRef.current.getBoundingClientRect();
      const ratio = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
      // Convert pixel ratio to day ratio (account for the SVG inner padding)
      const innerRatio = (ratio * SI_W - SI_PAD_L) / SI_INNER_W;
      const clamped = Math.max(0, Math.min(SI_DAYS, innerRatio * SI_DAYS));
      // Snap to integer days for a discrete feel — keeps the handle
      // landing on rep points instead of dipping between them.
      setDay(Math.round(clamped));
      setDraggedOnce(true);
    };
    move(e.clientX);
    const onMove = (ev) => move(ev.clientX);
    const onUp = () => {
      window.removeEventListener("pointermove", onMove);
      window.removeEventListener("pointerup", onUp);
    };
    window.addEventListener("pointermove", onMove);
    window.addEventListener("pointerup", onUp);
  };

  // Path builder w/ draw-in clip
  const visibleX = drawT * SI_DAYS;
  const buildPath = (curve) => {
    const visible = curve.filter(([x]) => x <= visibleX);
    if (!visible.length) return "";
    return visible
      .map(([x, y], i) => `${i === 0 ? "M" : "L"} ${siXScale(x).toFixed(1)} ${siYScale(y).toFixed(1)}`)
      .join(" ");
  };
  const fillPath = (curve) => {
    const visible = curve.filter(([x]) => x <= visibleX);
    if (!visible.length) return "";
    const path = visible
      .map(([x, y], i) => `${i === 0 ? "M" : "L"} ${siXScale(x).toFixed(1)} ${siYScale(y).toFixed(1)}`)
      .join(" ");
    const lastX = visible[visible.length - 1][0];
    return `${path} L ${siXScale(lastX).toFixed(1)} ${siYScale(0).toFixed(1)} L ${siXScale(0).toFixed(1)} ${siYScale(0).toFixed(1)} Z`;
  };

  const yNo = noReviewYAt(day);
  const yYes = withReviewYAt(day);

  const dayLabels = [0, 7, 14, 21, 30];
  const yLabels = [
    { y: 1, label: "100%" },
    { y: 0.5, label: "50%" },
    { y: 0.2, label: "20%" }
  ];

  return (
    <div className="si-root">
      {/* Hero block — giant stat + headline + drain ring */}
      <div ref={heroRef} className="si-hero">
        <div className="si-hero-text">
          <div className="section-eyebrow">The science</div>
          <h2 className="si-headline">
            <span className="si-headline-num">
              <SiStatCounter from={100} to={20} inView={heroInView} />
            </span>
            <span className="si-headline-rest">
              <span>That's all you'll remember</span>
              <span>of what you read two weeks ago.</span>
            </span>
          </h2>
          <p className="lede si-sub">
            Hermann Ebbinghaus measured this in 1885. The curve hasn't changed.
            <br />
            <span className="si-sub-strong">Spaced repetition is how you bend it.</span>
          </p>
        </div>
        <div className="si-ring">
          <SiDrainRing inView={heroInView} />
          <div className="si-ring-overlay">
            <div className="si-ring-num">
              <SiStatCounter from={100} to={20} inView={heroInView} />
            </div>
            <div className="si-ring-cap">retained · day 14</div>
          </div>
        </div>
      </div>

      {/* The big interactive chart */}
      <div ref={chartRef} className="si-chart-card">
        <div className="si-chart-head">
          <div>
            <div className="si-chart-eyebrow">Drag to scrub through 30 days</div>
            <div className="si-chart-title">The forgetting curve, live.</div>
          </div>
          <button
            type="button"
            className={`si-toggle ${showReviews ? "is-on" : ""}`}
            onClick={() => setShowReviews((v) => !v)}
            aria-pressed={showReviews}>
            <span className="si-toggle-track">
              <span className="si-toggle-thumb" />
            </span>
            <span>Add spaced reviews</span>
          </button>
        </div>

        <div
          ref={dragRef}
          className="si-chart-svg-wrap"
          onPointerDown={onPointerDown}
          role="slider"
          aria-label="Day in the forgetting curve"
          aria-valuemin={0}
          aria-valuemax={SI_DAYS}
          aria-valuenow={Math.round(day)}>
          <svg viewBox={`0 0 ${SI_W} ${SI_H}`} className="si-svg">
            <defs>
              <linearGradient id="siNoFill" x1="0" y1="0" x2="0" y2="1">
                <stop offset="0" stopColor="var(--muted)" stopOpacity="0.18" />
                <stop offset="1" stopColor="var(--muted)" stopOpacity="0" />
              </linearGradient>
              <linearGradient id="siYesFill" x1="0" y1="0" x2="0" y2="1">
                <stop offset="0" stopColor="var(--blue)" stopOpacity="0.32" />
                <stop offset="1" stopColor="var(--blue)" stopOpacity="0" />
              </linearGradient>
              <filter id="siGlow" x="-50%" y="-50%" width="200%" height="200%">
                <feGaussianBlur stdDeviation="4" />
              </filter>
            </defs>

            {/* Y grid */}
            {yLabels.map(({ y, label }) => (
              <g key={label}>
                <line x1={SI_PAD_L} y1={siYScale(y)} x2={SI_W - SI_PAD_R} y2={siYScale(y)}
                  stroke="var(--line)" strokeWidth="1" strokeDasharray="2 5" />
                <text x={SI_PAD_L - 14} y={siYScale(y) + 4} textAnchor="end"
                  fontSize="13" fill="var(--muted)" fontFamily="-apple-system, sans-serif" fontWeight="500">
                  {label}
                </text>
              </g>
            ))}

            {/* X axis */}
            <line x1={SI_PAD_L} y1={SI_H - SI_PAD_B} x2={SI_W - SI_PAD_R} y2={SI_H - SI_PAD_B}
              stroke="var(--line)" strokeWidth="1" />
            {dayLabels.map((d) => (
              <g key={d}>
                <line x1={siXScale(d)} y1={SI_H - SI_PAD_B} x2={siXScale(d)} y2={SI_H - SI_PAD_B + 6}
                  stroke="var(--muted)" strokeWidth="1" />
                <text x={siXScale(d)} y={SI_H - SI_PAD_B + 26} textAnchor="middle"
                  fontSize="13" fill="var(--muted)" fontFamily="-apple-system, sans-serif" fontWeight="500">
                  Day {d}
                </text>
              </g>
            ))}

            <text x={SI_W - SI_PAD_R} y={SI_H - SI_PAD_B + 56} textAnchor="end" fontSize="11" fill="var(--muted)"
              fontFamily="-apple-system, sans-serif" fontWeight="700" letterSpacing="1.5">
              RETENTION (Y) · DAYS (X)*
            </text>

            {/* Without review curve (always visible) */}
            <path d={fillPath(SI_CURVE_NO)} fill="url(#siNoFill)" />
            <path d={buildPath(SI_CURVE_NO)} fill="none" stroke="var(--muted)"
              strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"
              strokeDasharray="5 5" opacity="0.7" />

            {/* With reviews curve (toggleable) */}
            <g style={{
              opacity: showReviews ? 1 : 0,
              transition: "opacity 0.5s ease"
            }}>
              <path d={fillPath(SI_CURVE_YES)} fill="url(#siYesFill)" />
              <path d={buildPath(SI_CURVE_YES)} fill="none" stroke="var(--blue)"
                strokeWidth="3.5" strokeLinecap="round" strokeLinejoin="round"
                filter="url(#siGlow)" opacity="0.5" />
              <path d={buildPath(SI_CURVE_YES)} fill="none" stroke="var(--blue)"
                strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />

              {SI_REVIEWS.map((r) => visibleX >= r && (
                <g key={r}>
                  <line x1={siXScale(r)} y1={siYScale(1)} x2={siXScale(r)} y2={SI_H - SI_PAD_B}
                    stroke="var(--blue)" strokeWidth="1" strokeDasharray="2 4" opacity="0.35" />
                  <circle cx={siXScale(r)} cy={siYScale(1)} r="7" fill="var(--blue)"
                    stroke="var(--surface)" strokeWidth="2.5" />
                  <text x={siXScale(r)} y={siYScale(1) - 16} textAnchor="middle"
                    fontSize="11" fill="var(--blue-darker)"
                    fontFamily="-apple-system, sans-serif" fontWeight="700">
                    Rep {SI_REVIEWS.indexOf(r) + 1}
                  </text>
                </g>
              ))}
            </g>

            {/* Day handle — vertical line + dots on both curves */}
            {drawT >= 1 && (
              <g style={{ cursor: "ew-resize" }}>
                <line x1={siXScale(day)} y1={SI_PAD_T} x2={siXScale(day)} y2={SI_H - SI_PAD_B}
                  stroke="var(--text)" strokeWidth="1.5" opacity="0.5" />

                {/* Hit-target column for easier grabbing */}
                <rect x={siXScale(day) - 22} y={SI_PAD_T} width="44" height={SI_INNER_H}
                  fill="transparent" />

                {/* No-review intersection */}
                <circle cx={siXScale(day)} cy={siYScale(yNo)} r="9"
                  fill="var(--surface)" stroke="var(--muted)" strokeWidth="2.5" />
                <circle cx={siXScale(day)} cy={siYScale(yNo)} r="3.5" fill="var(--muted)" />

                {/* Yes-review intersection */}
                {showReviews && (
                  <>
                    <circle cx={siXScale(day)} cy={siYScale(yYes)} r="11"
                      fill="var(--blue)" opacity="0.2" />
                    <circle cx={siXScale(day)} cy={siYScale(yYes)} r="9"
                      fill="var(--surface)" stroke="var(--blue)" strokeWidth="3" />
                    <circle cx={siXScale(day)} cy={siYScale(yYes)} r="3.5" fill="var(--blue)" />
                  </>
                )}

                {/* Top-of-line handle indicator */}
                <g transform={`translate(${siXScale(day)}, ${SI_PAD_T - 10})`}>
                  <rect x="-22" y="-14" width="44" height="22" rx="11"
                    fill="var(--text)" />
                  <text x="0" y="2" textAnchor="middle"
                    fontSize="12" fill="var(--bg)"
                    fontFamily="-apple-system, sans-serif" fontWeight="700">
                    Day {Math.round(day)}
                  </text>
                </g>
              </g>
            )}

            {/* Drag hint — fades after first drag */}
            {drawT >= 1 && !draggedOnce && (
              <g style={{ pointerEvents: "none" }} className="si-drag-hint">
                <text x={siXScale(day) + 30} y={siYScale(yNo) + 4}
                  fontSize="13" fill="var(--text-soft)"
                  fontFamily="-apple-system, sans-serif" fontWeight="600">
                  ← drag me
                </text>
              </g>
            )}

            {/* Legend */}
            <g transform={`translate(${SI_W - SI_PAD_R - 240}, ${SI_PAD_T - 4})`}>
              <g>
                <line x1="0" y1="0" x2="22" y2="0" stroke="var(--muted)"
                  strokeWidth="2.5" strokeDasharray="5 5" />
                <text x="30" y="4" fontSize="13" fill="var(--text-soft)"
                  fontFamily="-apple-system, sans-serif" fontWeight="500">
                  Without review
                </text>
              </g>
              <g transform="translate(0, 22)">
                <line x1="0" y1="0" x2="22" y2="0" stroke="var(--blue)" strokeWidth="3" />
                <circle cx="11" cy="0" r="4" fill="var(--blue)" />
                <text x="30" y="4" fontSize="13" fill="var(--text-soft)"
                  fontFamily="-apple-system, sans-serif" fontWeight="500">
                  With spaced reviews
                </text>
              </g>
            </g>
          </svg>
        </div>

        {/* Live readout strip */}
        <div className="si-readout">
          <div className="si-readout-cell">
            <div className="si-readout-cap">After day {Math.round(day)}</div>
            <div className="si-readout-pair">
              <span className="si-readout-num si-readout-muted">
                {Math.round(yNo * 100)}%
              </span>
              <span className="si-readout-sub">without review</span>
            </div>
          </div>
          <div className="si-readout-arrow" aria-hidden="true">→</div>
          <div className="si-readout-cell">
            <div className="si-readout-cap">With Never Forget</div>
            <div className="si-readout-pair">
              <span className="si-readout-num si-readout-blue">
                {Math.round(yYes * 100)}%
              </span>
              <span className="si-readout-sub">retained</span>
            </div>
          </div>
          <div className="si-readout-cell si-readout-delta">
            <div className="si-readout-cap">That's</div>
            <div className="si-readout-pair">
              <span className="si-readout-num si-readout-blue">
                {yNo > 0.001 ? `${(yYes / yNo).toFixed(1)}×` : "—"}
              </span>
              <span className="si-readout-sub">more, on day {Math.round(day)}</span>
            </div>
          </div>
        </div>

        <div className="si-footnote">
          *Numbers are illustrative, derived from the Ebbinghaus forgetting curve and a typical spaced-repetition schedule (reviews on days 1, 3, 7, 16, 30). Real-world retention varies by material and learner.
        </div>
      </div>

      {/* Three explanation cards */}
      <div className="si-pillars">
        <div className="si-pillar">
          <div className="si-pillar-num">01</div>
          <div className="si-pillar-title">Reviews catch decay early</div>
          <div className="si-pillar-body">
            Each rep resets your retention near 100% and slows the decay that
            follows. The curve gets flatter every time.
          </div>
        </div>
        <div className="si-pillar">
          <div className="si-pillar-num">02</div>
          <div className="si-pillar-title">Intervals stretch as you learn</div>
          <div className="si-pillar-body">
            Day 1, day 3, day 7, day 16, day 30. Memories that survive each gap
            earn longer ones — the schedule does the math.
          </div>
        </div>
        <div className="si-pillar">
          <div className="si-pillar-num">03</div>
          <div className="si-pillar-title">A few minutes beats hours of cramming</div>
          <div className="si-pillar-body">
            Five short reviews, spaced over a month, retain more than five hours
            in one sitting. Time on task isn't the metric — timing is.
          </div>
        </div>
      </div>

      <style>{`
        .si-root {
          display: flex;
          flex-direction: column;
          gap: 64px;
        }

        /* === Hero stat block ============================================ */
        .si-hero {
          display: grid;
          grid-template-columns: 1.7fr 1fr;
          gap: 64px;
          align-items: center;
        }
        .si-hero-text { max-width: none; }
        .si-headline {
          display: flex;
          align-items: flex-start;
          gap: 28px;
          margin-bottom: 24px;
          font-weight: 800;
          letter-spacing: -0.045em;
          line-height: 1;
          color: var(--text);
        }
        .si-headline-num {
          font-size: clamp(96px, 14vw, 200px);
          line-height: 0.85;
          background: linear-gradient(135deg, var(--blue) 0%, var(--blue-darker) 100%);
          -webkit-background-clip: text;
          background-clip: text;
          color: transparent;
          font-variant-numeric: tabular-nums;
          letter-spacing: -0.04em;
          padding-right: 0.08em;
          flex-shrink: 0;
        }
        .si-headline-rest {
          display: flex;
          flex-direction: column;
          gap: 8px;
          font-size: clamp(24px, 2.6vw, 36px);
          letter-spacing: -0.025em;
          line-height: 1.15;
          padding-top: 18px;
          font-weight: 700;
        }
        .si-headline-rest > span:first-child { color: var(--text-soft); font-weight: 500; }
        .si-sub { font-size: 18px; line-height: 1.55; max-width: 56ch; }
        .si-sub-strong {
          color: var(--text);
          font-weight: 600;
        }

        .si-ring {
          position: relative;
          aspect-ratio: 1;
          max-width: 360px;
          margin-left: auto;
        }
        .si-ring-overlay {
          position: absolute;
          inset: 0;
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;
          gap: 6px;
        }
        .si-ring-num {
          font-size: clamp(48px, 6vw, 76px);
          font-weight: 800;
          letter-spacing: -0.05em;
          color: var(--text);
          font-variant-numeric: tabular-nums;
          line-height: 1;
        }
        .si-ring-cap {
          font-size: 12px;
          letter-spacing: 0.18em;
          text-transform: uppercase;
          color: var(--muted);
          font-weight: 700;
        }

        /* === Chart card ================================================= */
        .si-chart-card {
          background: var(--surface);
          border: 1px solid var(--line);
          border-radius: 28px;
          padding: 32px 32px 28px;
          box-shadow: 0 30px 80px -40px rgba(15,30,55,0.18);
        }
        [data-theme="dark"] .si-chart-card {
          box-shadow: 0 30px 80px -30px rgba(0,0,0,0.5);
        }
        .si-chart-head {
          display: flex;
          align-items: flex-end;
          justify-content: space-between;
          gap: 24px;
          margin-bottom: 20px;
          flex-wrap: wrap;
        }
        .si-chart-eyebrow {
          font-size: 12px;
          letter-spacing: 0.16em;
          text-transform: uppercase;
          color: var(--muted);
          font-weight: 700;
          margin-bottom: 6px;
        }
        .si-chart-title {
          font-size: clamp(24px, 2.4vw, 32px);
          font-weight: 700;
          letter-spacing: -0.025em;
          color: var(--text);
        }
        .si-toggle {
          display: inline-flex;
          align-items: center;
          gap: 12px;
          padding: 10px 18px 10px 14px;
          border-radius: 999px;
          background: var(--bg-alt);
          border: 1px solid var(--line);
          color: var(--text);
          font-size: 14px;
          font-weight: 600;
          font-family: inherit;
          cursor: pointer;
          transition: background 0.2s, border-color 0.2s;
        }
        .si-toggle:hover { background: var(--blue-soft); }
        .si-toggle.is-on { background: var(--blue-soft); border-color: var(--blue); }
        .si-toggle-track {
          position: relative;
          width: 36px; height: 20px;
          border-radius: 999px;
          background: color-mix(in srgb, var(--muted) 35%, transparent);
          transition: background 0.2s;
        }
        .si-toggle.is-on .si-toggle-track { background: var(--blue); }
        .si-toggle-thumb {
          position: absolute;
          top: 2px; left: 2px;
          width: 16px; height: 16px;
          border-radius: 50%;
          background: white;
          box-shadow: 0 1px 3px rgba(0,0,0,0.2);
          transition: transform 0.2s cubic-bezier(.5,1.6,.4,1);
        }
        .si-toggle.is-on .si-toggle-thumb { transform: translateX(16px); }

        .si-chart-svg-wrap {
          position: relative;
          touch-action: none;
          user-select: none;
          cursor: ew-resize;
        }
        .si-svg { width: 100%; height: auto; display: block; }

        .si-drag-hint {
          animation: si-drag-pulse 1.6s ease-in-out infinite;
        }
        @keyframes si-drag-pulse {
          0%, 100% { opacity: 0.6; transform: translateX(0); }
          50% { opacity: 1; transform: translateX(6px); }
        }
        @media (prefers-reduced-motion: reduce) {
          .si-drag-hint { animation: none; }
        }

        /* === Live readout =============================================== */
        .si-readout {
          display: grid;
          grid-template-columns: 1fr auto 1fr 1fr;
          align-items: center;
          gap: 18px;
          margin-top: 20px;
          padding: 20px 24px;
          background: var(--bg-alt);
          border-radius: 18px;
        }
        .si-readout-cell { display: flex; flex-direction: column; gap: 6px; }
        .si-readout-cap {
          font-size: 11px;
          letter-spacing: 0.14em;
          text-transform: uppercase;
          color: var(--muted);
          font-weight: 700;
        }
        .si-readout-pair {
          display: flex;
          align-items: baseline;
          gap: 10px;
          flex-wrap: wrap;
        }
        .si-readout-num {
          font-size: clamp(28px, 3vw, 40px);
          font-weight: 800;
          letter-spacing: -0.035em;
          font-variant-numeric: tabular-nums;
          line-height: 1;
        }
        .si-readout-muted { color: var(--muted); }
        .si-readout-blue {
          background: linear-gradient(135deg, var(--blue) 0%, var(--blue-darker) 100%);
          -webkit-background-clip: text;
          background-clip: text;
          color: transparent;
        }
        .si-readout-sub {
          font-size: 13.5px;
          color: var(--text-soft);
          font-weight: 500;
        }
        .si-readout-arrow {
          font-size: 22px;
          color: var(--muted);
          font-weight: 300;
        }
        .si-readout-delta {
          padding-left: 18px;
          border-left: 1px solid var(--line);
        }
        .si-footnote {
          margin-top: 14px;
          padding: 0 4px;
          font-size: 12px;
          line-height: 1.5;
          color: var(--muted);
          font-style: italic;
          max-width: 80ch;
        }

        /* === Three pillars ============================================== */
        .si-pillars {
          display: grid;
          grid-template-columns: repeat(3, 1fr);
          gap: 20px;
        }
        .si-pillar {
          background: var(--surface);
          border: 1px solid var(--line);
          border-radius: 20px;
          padding: 28px;
          display: flex;
          flex-direction: column;
          gap: 12px;
          transition: transform 0.3s, border-color 0.3s, box-shadow 0.3s;
        }
        .si-pillar:hover {
          transform: translateY(-2px);
          border-color: color-mix(in srgb, var(--blue) 40%, var(--line));
          box-shadow: 0 20px 40px -20px color-mix(in srgb, var(--blue) 40%, transparent);
        }
        .si-pillar-num {
          font-size: 12px;
          letter-spacing: 0.18em;
          color: var(--blue-darker);
          font-weight: 800;
          font-variant-numeric: tabular-nums;
        }
        [data-theme="dark"] .si-pillar-num { color: var(--blue); }
        .si-pillar-title {
          font-size: 19px;
          font-weight: 700;
          letter-spacing: -0.02em;
          color: var(--text);
          line-height: 1.25;
        }
        .si-pillar-body {
          font-size: 15px;
          line-height: 1.55;
          color: var(--text-soft);
        }

        /* === Responsive ================================================= */
        @media (max-width: 980px) {
          .si-hero { grid-template-columns: 1fr; gap: 40px; }
          .si-ring { max-width: 260px; margin: 0 auto; }
          .si-headline { gap: 18px; }
          .si-pillars { grid-template-columns: 1fr; }
          .si-readout {
            grid-template-columns: 1fr 1fr;
            gap: 16px 20px;
          }
          .si-readout-arrow { display: none; }
          .si-readout-delta { padding-left: 0; border-left: none; border-top: 1px solid var(--line); padding-top: 16px; grid-column: 1 / -1; }
        }
        @media (max-width: 640px) {
          .si-headline { flex-direction: column; gap: 8px; }
          .si-headline-rest { padding-top: 0; }
          .si-chart-card { padding: 24px 20px 20px; border-radius: 22px; }
          .si-chart-head { flex-direction: column; align-items: flex-start; gap: 16px; }
        }
      `}</style>
    </div>
  );
};

window.ScienceInteractive = ScienceInteractive;
