// Scene 2: Why Springs Snap on Cold Mornings
// Duration: 38s
// Story: Cold morning → brittle steel → hidden micro-cracks → Paris' Law → SNAP

function SceneColdWeather() {
  return (
    <>
      <Backdrop />

      {/* ── Beat 1 (0–4.5s): Hook — the bang ─────────────── */}
      <Sprite start={0} end={9.0}>
        <LabelChip text="Chapter 02 / Spring Fatigue × Cold Weather" x={80} y={64} accent={COLORS.danger}/>
      </Sprite>

      {/* Garage door silhouette on the right — anchors the opening */}
      <Sprite start={0} end={9.0}>
        {({ localTime }) => {
          // Idle until BANG (localTime >= 7.3), then shake + spring flashes red
          const bangT = Math.max(0, localTime - 7.3);
          const inBang = bangT > 0 && bangT < 1.6;
          const shake = inBang ? Math.sin(bangT * 55) * 8 * Math.max(0, 1 - bangT / 1.6) : 0;
          const shakeY = inBang ? Math.sin(bangT * 50) * 5 * Math.max(0, 1 - bangT / 1.6) : 0;
          // flash intensity: quick spike at 0, decays
          const flash = inBang ? Math.max(0, 1 - bangT * 2) : 0;

          return (
            <div style={{
              position: 'absolute',
              left: 1060 + shake, top: 180 + shakeY,
            }}>
              {/* red flash halo */}
              <div style={{
                position: 'absolute',
                left: -60, top: -20, width: 820, height: 240,
                borderRadius: '50%',
                background: `radial-gradient(ellipse, ${COLORS.danger}55 0%, transparent 70%)`,
                opacity: flash,
                pointerEvents: 'none',
              }}/>
              <DoorSchematic
                x={0} y={0}
                width={720} height={760}
                lift={0}
                showSpring={true}
                springWind={0}
                accentPanel={inBang && bangT > 0.1 ? 0 : null}
              />
              {/* Red spring flash overlay */}
              {inBang && (
                <svg width="720" height="60" style={{
                  position: 'absolute', left: 0, top: 0, pointerEvents: 'none',
                  opacity: flash,
                }}>
                  <rect x="0" y="0" width="720" height="60" fill={COLORS.danger} opacity="0.35"/>
                </svg>
              )}
            </div>
          );
        }}
      </Sprite>

      <Sprite start={0.4} end={9.0}>
        {({ localTime }) => (
          <div style={{
            position: 'absolute', left: 80, top: 260,
            fontFamily: FONTS.mono, fontSize: 22, color: COLORS.inkDim,
            letterSpacing: '0.1em', textTransform: 'uppercase',
          }}>
            06:47 AM · Monday · {Math.round(28 + Math.sin(localTime * 2) * 0.3)}°F
          </div>
        )}
      </Sprite>

      <Sprite start={1.4} end={9.0}>
        <Caption
          headline="You press the opener."
          x={80} y={340} maxWidth={700}/>
      </Sprite>

      <Sprite start={7.3} end={9.0}>
        {({ localTime }) => {
          // Shake effect for "BANG" — fires just before the VO says "bang" at ~7.7s
          const shake = localTime > 0.3 ? Math.sin(localTime * 40) * 5 * Math.max(0, 1 - (localTime - 0.3) * 2) : 0;
          return (
            <div style={{
              position: 'absolute', left: 80 + shake, top: 500,
              fontFamily: FONTS.serif, fontSize: 320, color: COLORS.danger,
              lineHeight: 1, letterSpacing: '-0.03em',
              textShadow: `0 0 ${Math.max(0, 40 - localTime * 20)}px ${COLORS.danger}`,
            }}>
              BANG.
            </div>
          );
        }}
      </Sprite>

      <Sprite start={9.5} end={10.5}>
        <Caption sub="One torsion spring, gone. It was fine yesterday." x={80} y={840} maxWidth={800}/>
      </Sprite>

      {/* ── Beat 2 (4.8–12.8s): Cold makes steel brittle ─────────────── */}
      <Sprite start={9.3} end={17.3}>
        <Backdrop />
        <LabelChip text="Phenomenon 01 / Ductile→brittle transition" x={80} y={64} accent={COLORS.cold}/>
      </Sprite>

      {/* Thermometer animating from 70 → 28°F */}
      <Sprite start={9.3} end={17.3}>
        {({ localTime }) => {
          const t = Easing.easeInOutCubic(clamp((localTime - 0.5) / 3.5, 0, 1));
          const temp = 70 - t * 42; // 70 → 28
          return <Thermometer x={280} y={200} temp={temp} minTemp={0} maxTemp={90} criticalTemp={40} height={420} label="Outside air"/>;
        }}
      </Sprite>

      {/* Spring cross-section that shifts color cold */}
      <Sprite start={10.7} end={17.3}>
        {({ localTime }) => {
          const t = Easing.easeInOutCubic(clamp((localTime - 1) / 3, 0, 1));
          const color = interpolateColor(COLORS.warm, COLORS.cold, t);
          return (
            <svg width="1000" height="340" viewBox="0 0 1000 340" style={{
              position: 'absolute', left: 560, top: 520,
            }}>
              <text x="0" y="24" fontFamily={FONTS.mono} fontSize="15"
                fill={COLORS.inkDim} style={{ letterSpacing: '0.08em', textTransform: 'uppercase' }}>
                Torsion spring · cross-section
              </text>
              {/* shaft */}
              <line x1="20" y1="170" x2="980" y2="170" stroke={COLORS.inkDim} strokeWidth="2.5"/>
              {/* coils */}
              <g transform="translate(70, 170)">
                {[...Array(24)].map((_, i) => (
                  <ellipse key={i} cx={i * 36 + 18} cy="0" rx="19" ry="58"
                    fill="none" stroke={color} strokeWidth="2.6"/>
                ))}
              </g>
              {/* end caps */}
              <circle cx="20" cy="170" r="8" fill={COLORS.ink}/>
              <circle cx="980" cy="170" r="8" fill={COLORS.ink}/>
            </svg>
          );
        }}
      </Sprite>

      <Sprite start={11.3} end={17.3}>
        <Caption
          headline="Below 40°F, steel changes."
          sub="It stops bending. It starts cracking."
          x={560} y={200} maxWidth={640}/>
      </Sprite>

      <Sprite start={14.5} end={17.3}>
        <div style={{
          position: 'absolute', left: 560, top: 900,
          fontFamily: FONTS.mono, fontSize: 14, color: COLORS.cold,
          letterSpacing: '0.08em', textTransform: 'uppercase',
        }}>
          Ductile ——→ brittle transition threshold
        </div>
      </Sprite>

      {/* ── Beat 3 (13.1–21s): Hidden micro-cracks ─────────────── */}
      <Sprite start={17.6} end={25.5}>
        <Backdrop />
        <LabelChip text="Phenomenon 02 / Invisible damage" x={80} y={64} accent={COLORS.warning}/>
      </Sprite>

      {/* Zoom into a single coil surface — show micro-cracks appearing */}
      <Sprite start={17.6} end={25.5}>
        {({ localTime }) => {
          const zoomT = Easing.easeOutCubic(clamp(localTime / 1.6, 0, 1));
          const crackCount = Math.floor(clamp((localTime - 1.8) / 3, 0, 1) * 16);
          // Deterministic crack positions
          const cracks = Array.from({ length: 16 }, (_, i) => {
            const rand1 = Math.sin(i * 12.9898) * 43758.5453;
            const rand2 = Math.sin(i * 78.233) * 43758.5453;
            return {
              x: 100 + (rand1 - Math.floor(rand1)) * 780,
              y: 100 + (rand2 - Math.floor(rand2)) * 320,
              len: 14 + (rand1 - Math.floor(rand1)) * 42,
              angle: (rand2 - Math.floor(rand2)) * 360,
            };
          });
          return (
            <svg width="1000" height="560" viewBox="0 0 1000 560" style={{
              position: 'absolute', left: 680, top: 240,
              transform: `scale(${zoomT})`, transformOrigin: 'center center',
              opacity: zoomT,
            }}>
              {/* Steel surface texture */}
              <defs>
                <pattern id="steel" width="10" height="10" patternUnits="userSpaceOnUse">
                  <rect width="10" height="10" fill="#3a4550"/>
                  <line x1="0" y1="0" x2="10" y2="10" stroke="#2a3540" strokeWidth="0.6"/>
                </pattern>
              </defs>
              <rect x="50" y="80" width="900" height="400" fill="url(#steel)" rx="16"
                stroke={COLORS.line} strokeWidth="1.8"/>

              {/* Cracks appearing one-by-one */}
              {cracks.slice(0, crackCount).map((c, i) => (
                <g key={i} transform={`translate(${c.x}, ${c.y}) rotate(${c.angle})`}>
                  <line x1="0" y1="0" x2={c.len} y2="0" stroke={COLORS.danger} strokeWidth="1.6" opacity="0.9"/>
                  <line x1={c.len * 0.4} y1="0" x2={c.len * 0.4 + 6} y2="-4" stroke={COLORS.danger} strokeWidth="1.3" opacity="0.75"/>
                </g>
              ))}

              {/* Scale ruler */}
              <line x1="800" y1="450" x2="880" y2="450" stroke={COLORS.inkDim} strokeWidth="1.8"/>
              <text x="840" y="470" textAnchor="middle"
                fontFamily={FONTS.mono} fontSize="12" fill={COLORS.inkDim}>100 µm</text>
            </svg>
          );
        }}
      </Sprite>

      <Sprite start={18.3} end={25.5}>
        <Caption
          headline="Every cycle leaves a mark."
          sub="Micro-cracks, far too small to see. They accumulate."
          x={80} y={280} maxWidth={520}/>
      </Sprite>

      {/* Cycle counter */}
      <Sprite start={20.5} end={25.5}>
        {({ localTime }) => {
          const t = Easing.easeOutCubic(clamp((localTime - 0.3) / 3.4, 0, 1));
          const cycles = Math.round(t * 8247);
          return (
            <div style={{ position: 'absolute', left: 80, top: 560 }}>
              <div style={{
                fontFamily: FONTS.mono, fontSize: 13,
                letterSpacing: '0.1em', textTransform: 'uppercase', color: COLORS.inkDim,
                marginBottom: 10,
              }}>
                Cycle counter
              </div>
              <div style={{
                fontFamily: FONTS.mono, fontSize: 88, fontWeight: 500,
                color: COLORS.warning, lineHeight: 1, fontVariantNumeric: 'tabular-nums',
                letterSpacing: '-0.02em',
                whiteSpace: 'nowrap',
                display: 'flex', alignItems: 'baseline', gap: 16,
              }}>
                <span>{cycles.toLocaleString()}</span>
                <span style={{ color: COLORS.inkDim, fontSize: 42 }}>/ 10,000</span>
              </div>
              <div style={{
                marginTop: 16, fontFamily: FONTS.sans, fontSize: 18, color: COLORS.inkDim,
              }}>82% of rated cycle life.</div>
            </div>
          );
        }}
      </Sprite>

      {/* ── Beat 4 (21.3–28.5s): Paris' Law graph ─────────────── */}
      <Sprite start={25.8} end={33}>
        <Backdrop />
        <LabelChip text="Paris' Law / Crack growth rate" x={80} y={64} accent={COLORS.orange}/>
      </Sprite>

      <Sprite start={25.8} end={33}>
        {({ localTime }) => {
          const t = Easing.easeOutCubic(clamp((localTime - 0.8) / 4.5, 0, 1));
          // Graph: x = cycles (0..10000), y = crack length. Curve is ~exponential tail.
          const W = 1080, H = 560;
          const pad = { l: 70, r: 40, t: 40, b: 60 };
          const xScale = (n) => pad.l + (n / 10000) * (W - pad.l - pad.r);
          const yScale = (v) => H - pad.b - v * (H - pad.t - pad.b);
          // crack length fn: slow then asymptotic near 1
          const crackLen = (n) => {
            const nn = n / 10000;
            return Math.pow(nn, 3.5); // 0..1
          };
          // Build path up to progress
          const maxN = t * 10000;
          const points = [];
          for (let n = 0; n <= maxN; n += 100) {
            points.push(`${xScale(n)},${yScale(crackLen(n))}`);
          }

          return (
            <svg width={W} height={H} style={{
              position: 'absolute', left: 680, top: 240,
            }}>
              {/* axes */}
              <line x1={pad.l} y1={pad.t} x2={pad.l} y2={H - pad.b} stroke={COLORS.line} strokeWidth="1.4"/>
              <line x1={pad.l} y1={H - pad.b} x2={W - pad.r} y2={H - pad.b} stroke={COLORS.line} strokeWidth="1.4"/>

              {/* gridlines */}
              {[0.25, 0.5, 0.75].map(g => (
                <line key={g} x1={pad.l} y1={yScale(g)} x2={W - pad.r} y2={yScale(g)}
                  stroke={COLORS.line} strokeWidth="0.6" strokeDasharray="2 4"/>
              ))}

              {/* curve */}
              <polyline points={points.join(' ')}
                fill="none" stroke={COLORS.orange} strokeWidth="3.5" strokeLinejoin="round"/>

              {/* danger zone after 80% */}
              <rect x={xScale(8000)} y={pad.t}
                width={xScale(10000) - xScale(8000)} height={H - pad.t - pad.b}
                fill={COLORS.danger} opacity="0.08"/>
              <line x1={xScale(8000)} y1={pad.t} x2={xScale(8000)} y2={H - pad.b}
                stroke={COLORS.danger} strokeWidth="1" strokeDasharray="4 4" opacity="0.5"/>
              <text x={xScale(8000) + 12} y={pad.t + 18}
                fontFamily={FONTS.mono} fontSize="13"
                fill={COLORS.danger} style={{ letterSpacing: '0.08em', textTransform: 'uppercase' }}>
                Critical region
              </text>

              {/* axis labels */}
              <text x={W/2} y={H - 12} textAnchor="middle"
                fontFamily={FONTS.mono} fontSize="13" fill={COLORS.inkDim}
                style={{ letterSpacing: '0.1em', textTransform: 'uppercase' }}>
                Cycles completed
              </text>
              <text x={22} y={H/2} textAnchor="middle"
                fontFamily={FONTS.mono} fontSize="13" fill={COLORS.inkDim}
                transform={`rotate(-90, 22, ${H/2})`}
                style={{ letterSpacing: '0.1em', textTransform: 'uppercase' }}>
                Crack length
              </text>

              {/* tick labels */}
              {[0, 2500, 5000, 7500, 10000].map(n => (
                <text key={n} x={xScale(n)} y={H - pad.b + 22} textAnchor="middle"
                  fontFamily={FONTS.mono} fontSize="12" fill={COLORS.inkDim}>
                  {(n/1000).toFixed(n % 1000 === 0 ? 0 : 1)}k
                </text>
              ))}

              {/* Moving dot */}
              {t > 0 && (() => {
                const n = maxN;
                const cx = xScale(n), cy = yScale(crackLen(n));
                return (
                  <g>
                    <circle cx={cx} cy={cy} r="9" fill={COLORS.orange}/>
                    <circle cx={cx} cy={cy} r="18" fill="none" stroke={COLORS.orange} strokeWidth="1.2" opacity="0.4"/>
                  </g>
                );
              })()}
            </svg>
          );
        }}
      </Sprite>

      <Sprite start={26.5} end={33}>
        <Caption
          headline="Then the last few cycles."
          sub="Paris' Law: cracks grow faster as they get longer. The curve goes vertical."
          x={80} y={300} maxWidth={520}/>
      </Sprite>

      {/* ── Beat 5 (28.8–34.5s): SNAP ─────────────── */}
      <Sprite start={33.3} end={39}>
        <Backdrop />
        <LabelChip text="Failure mode / First cycle, cold morning" x={80} y={64} accent={COLORS.danger}/>
      </Sprite>

      <Sprite start={33.3} end={39}>
        {({ localTime }) => {
          // Coils vibrate; at t ≈ 1.8s a break propagates across
          const breakStart = 1.8;
          const breakT = clamp((localTime - breakStart) / 0.4, 0, 1);
          const shake = localTime < breakStart ? Math.sin(localTime * 50) * 0.9 : 0;
          const shakeY = localTime < breakStart ? Math.sin(localTime * 47) * 0.9 : 0;

          const brokenIdx = Math.floor(breakT * 24);
          const separation = Easing.easeOutCubic(clamp((localTime - 2.1) / 0.6, 0, 1)) * 50;

          return (
            <svg width="1100" height="400" viewBox="0 0 1100 400" style={{
              position: 'absolute', left: 680, top: 360,
              transform: `translate(${shake}px, ${shakeY}px)`,
            }}>
              <text x="0" y="24" fontFamily={FONTS.mono} fontSize="15"
                fill={COLORS.inkDim} style={{ letterSpacing: '0.08em', textTransform: 'uppercase' }}>
                Torsion spring · 28°F · cycle 8,248
              </text>
              <line x1="20" y1="200" x2="1080" y2="200" stroke={COLORS.inkDim} strokeWidth="2.5"/>
              {/* coils */}
              {[...Array(24)].map((_, i) => {
                const isBroken = i === brokenIdx && breakT < 1;
                const past = i < brokenIdx && breakT > 0;
                const dx = past ? -separation * 0.3 : (i > brokenIdx ? separation * 0.3 : 0);
                return (
                  <g key={i} transform={`translate(${60 + i * 42 + dx}, 200)`}>
                    <ellipse cx="0" cy="0" rx="20" ry="70"
                      fill="none"
                      stroke={isBroken ? COLORS.danger : (past ? COLORS.inkFaint : COLORS.cold)}
                      strokeWidth={isBroken ? 4 : 2.6}
                      strokeDasharray={past ? "3 3" : "none"}
                      opacity={past ? 0.5 : 1}
                    />
                    {isBroken && (
                      <g>
                        <line x1="-22" y1="-50" x2="22" y2="50" stroke={COLORS.danger} strokeWidth="3"/>
                        <circle cx="0" cy="0" r="10" fill={COLORS.danger} opacity="0.6"/>
                      </g>
                    )}
                  </g>
                );
              })}
              {/* shock particles at the break */}
              {breakT > 0 && breakT < 1 && [...Array(8)].map((_, i) => {
                const angle = (i / 8) * Math.PI * 2;
                const r = breakT * 80;
                const bx = 60 + brokenIdx * 42;
                return <circle key={i} cx={bx + Math.cos(angle) * r} cy={200 + Math.sin(angle) * r}
                  r={4 * (1 - breakT)} fill={COLORS.danger}/>;
              })}
            </svg>
          );
        }}
      </Sprite>

      <Sprite start={35.7} end={39}>
        <Caption
          headline="Snap."
          sub="Fracture propagates across the coil in ~40 milliseconds."
          x={80} y={360} maxWidth={500}/>
      </Sprite>

      {/* ── Beat 6 (34.8–38s): CTA outro ─────────────── */}
      <Sprite start={39.3} end={48.5}>
        <Backdrop />
        <div style={{
          position: 'absolute', left: 80, top: 260, maxWidth: 1700,
        }}>
          <div style={{
            fontFamily: FONTS.serif, fontSize: 56, lineHeight: 1.08,
            color: COLORS.ink, letterSpacing: '-0.01em',
            whiteSpace: 'nowrap',
          }}>
            Replace at 80% of rated cycles.
          </div>
          <div style={{
            marginTop: 14, fontFamily: FONTS.sans, fontSize: 20, lineHeight: 1.45,
            color: COLORS.inkDim, fontWeight: 400,
          }}>
            Not after the bang — before it.
          </div>
        </div>
      </Sprite>
      <Sprite start={40.6} end={48.5}>
        <BrandMark x={80} y={540}/>
      </Sprite>
      <Sprite start={41} end={48.5}>
        <div style={{
          position: 'absolute', left: 80, top: 870,
          fontFamily: FONTS.mono, fontSize: 15, color: COLORS.inkDim,
          letterSpacing: '0.12em', textTransform: 'uppercase',
        }}>
          Lab: Spring Fatigue × Cold Weather →
        </div>
      </Sprite>

      {/* Cycle maintenance timeline — right side of outro */}
      <Sprite start={40.0} end={48.5}>
        {({ localTime }) => {
          // Animate the progress dot from 0 → 8,000 (the 80% mark)
          const t = Easing.easeOutCubic(clamp((localTime - 0.8) / 3.5, 0, 1));
          const progress = t * 8000;

          const W = 960, H = 560;
          const pad = { l: 60, r: 60, t: 160, b: 180 };
          const xScale = (n) => pad.l + (n / 10000) * (W - pad.l - pad.r);
          const railY = H / 2;

          return (
            <svg width={W} height={H} style={{
              position: 'absolute', left: 960, top: 260,
            }}>
              <text x={pad.l} y={pad.t - 60}
                fontFamily={FONTS.mono} fontSize="14" fill={COLORS.inkDim}
                style={{ letterSpacing: '0.12em', textTransform: 'uppercase' }}>
                Maintenance schedule / rated 10,000 cycles
              </text>

              {/* Rail */}
              <line x1={xScale(0)} y1={railY} x2={xScale(10000)} y2={railY}
                stroke={COLORS.line} strokeWidth="2"/>

              {/* Tick marks every 2,000 */}
              {[0, 2000, 4000, 6000, 8000, 10000].map(n => {
                const atService = n === 8000;
                const atFailure = n === 10000;
                return (
                  <g key={n} transform={`translate(${xScale(n)}, ${railY})`}>
                    <line x1="0" y1={-10} x2="0" y2={10}
                      stroke={atFailure ? COLORS.danger : (atService ? COLORS.orange : COLORS.inkDim)}
                      strokeWidth={atService || atFailure ? 2 : 1}/>
                    <text x="0" y={38} textAnchor="middle"
                      fontFamily={FONTS.mono} fontSize="13"
                      fill={atFailure ? COLORS.danger : (atService ? COLORS.orange : COLORS.inkDim)}
                      fontWeight={atService || atFailure ? 500 : 400}>
                      {(n / 1000).toFixed(0)}k
                    </text>
                  </g>
                );
              })}

              {/* Safe zone band (0–8,000) */}
              <rect x={xScale(0)} y={railY - 5}
                width={xScale(progress) - xScale(0)} height={10}
                fill={COLORS.success} opacity="0.5" rx="2"/>

              {/* Danger zone (8,000–10,000) */}
              <rect x={xScale(8000)} y={railY - 5}
                width={xScale(10000) - xScale(8000)} height={10}
                fill={COLORS.danger} opacity="0.2" rx="2"/>

              {/* Service marker at 8000 */}
              {t > 0.85 && (
                <g transform={`translate(${xScale(8000)}, ${railY - 90})`} opacity={clamp((t - 0.85) * 8, 0, 1)}>
                  <rect x="-90" y="-20" width="180" height="36" rx="6"
                    fill={COLORS.orange} opacity="0.15" stroke={COLORS.orange}/>
                  <text x="0" y="4" textAnchor="middle"
                    fontFamily={FONTS.mono} fontSize="13" fontWeight="500" fill={COLORS.orange}
                    style={{ letterSpacing: '0.1em', textTransform: 'uppercase' }}>
                    Replace here
                  </text>
                  <line x1="0" y1="16" x2="0" y2="65" stroke={COLORS.orange} strokeWidth="1.5" strokeDasharray="3 3"/>
                </g>
              )}

              {/* Failure marker at 10,000 */}
              {t > 0.95 && (
                <g transform={`translate(${xScale(10000)}, ${railY + 90})`} opacity={clamp((t - 0.95) * 20, 0, 1)}>
                  <line x1="0" y1="-65" x2="0" y2="-16" stroke={COLORS.danger} strokeWidth="1.5" strokeDasharray="3 3"/>
                  <rect x="-60" y="-15" width="120" height="32" rx="6"
                    fill={COLORS.danger} opacity="0.15" stroke={COLORS.danger}/>
                  <text x="0" y="6" textAnchor="middle"
                    fontFamily={FONTS.mono} fontSize="13" fontWeight="500" fill={COLORS.danger}
                    style={{ letterSpacing: '0.1em', textTransform: 'uppercase' }}>
                    Failure
                  </text>
                </g>
              )}

              {/* Broken spring icon above the failure point */}
              {t > 0.95 && (
                <g transform={`translate(${xScale(10000) - 50}, ${railY - 140})`}
                  opacity={clamp((t - 0.95) * 20, 0, 1)}>
                  {[...Array(4)].map((_, i) => (
                    <ellipse key={i} cx={i * 18 + 10} cy="20" rx="9" ry="26"
                      fill="none" stroke={COLORS.danger} strokeWidth="1.8"
                      strokeDasharray={i >= 2 ? "2 3" : "none"}
                      opacity={i >= 2 ? 0.4 : 1}/>
                  ))}
                  <line x1="40" y1="-10" x2="60" y2="50"
                    stroke={COLORS.danger} strokeWidth="2.5"/>
                </g>
              )}

              {/* Progress dot */}
              <g transform={`translate(${xScale(progress)}, ${railY})`}>
                <circle cx="0" cy="0" r="9" fill={progress < 8000 ? COLORS.success : COLORS.orange}/>
                <circle cx="0" cy="0" r="18" fill="none"
                  stroke={progress < 8000 ? COLORS.success : COLORS.orange}
                  strokeWidth="1" opacity="0.4"/>
              </g>

              {/* Current cycle count */}
              <text x={xScale(progress)} y={railY - 30} textAnchor="middle"
                fontFamily={FONTS.mono} fontSize="16" fontWeight="500"
                fill={progress < 8000 ? COLORS.success : COLORS.orange}>
                {Math.round(progress).toLocaleString()}
              </text>
            </svg>
          );
        }}
      </Sprite>
    </>
  );
}

// Small color interpolation helper (hex to hex)
function interpolateColor(a, b, t) {
  const ah = parseInt(a.slice(1), 16), bh = parseInt(b.slice(1), 16);
  const ar = (ah >> 16) & 0xff, ag = (ah >> 8) & 0xff, ab_ = ah & 0xff;
  const br = (bh >> 16) & 0xff, bg = (bh >> 8) & 0xff, bb = bh & 0xff;
  const rr = Math.round(ar + (br - ar) * t);
  const rg = Math.round(ag + (bg - ag) * t);
  const rb = Math.round(ab_ + (bb - ab_) * t);
  return `#${((rr << 16) | (rg << 8) | rb).toString(16).padStart(6, '0')}`;
}

window.SceneColdWeather = SceneColdWeather;
window.interpolateColor = interpolateColor;
