// instruments.jsx — analytical widgets (gauges, dials, sparklines, eval bar) // extracted from the Codex direction so every surface (home, dashboard, play, // exercises) can use them. All take their colour from CSS vars; work in dark // or light context automatically. const { useState: useStateI, useEffect: useEffectI, useRef: useRefI } = React; // ────────────────────────────────────────────────────────────────────── // 1. BRASS GAUGE — labelled horizontal bar with glowing fill // // ────────────────────────────────────────────────────────────────────── function BrassGauge({ label, value, color = 'var(--gold)', delta, dark = false }){ const labelColor = dark ? 'rgba(255,247,230,.55)' : 'var(--ink-muted)'; const trackBg = dark ? 'rgba(255,247,230,.12)' : 'rgba(25,23,20,.08)'; return (
{label} {value}% {delta != null && ( = 0 ? 'var(--olive)' : 'var(--rose)', fontSize: 9, letterSpacing: 0, }}>{delta >= 0 ? '↑' : '↓'} {Math.abs(delta)} )}
{/* subtle tick marks */} {[25, 50, 75].map(p => ( ))}
); } // ────────────────────────────────────────────────────────────────────── // 2. RATING DIAL — half-circle needle gauge for Maia rating // // ────────────────────────────────────────────────────────────────────── function RatingDial({ value = 1500, min = 200, max = 2000, dark = false, label = 'Maia · sparring' }){ const pct = (value - min) / (max - min); const angle = -180 + pct * 180; const fg = dark ? '#fff7e6' : 'var(--ink)'; return (
{label} ● {value}
{/* tick marks */} {Array.from({ length: 9 }, (_, i) => { const a = (-180 + i * 22.5) * Math.PI / 180; const r1 = 95, r2 = i % 2 === 0 ? 86 : 90; return ( ); })} {/* needle */}
{value}
); } // ────────────────────────────────────────────────────────────────────── // 3. SPARKLINE — animated draw of a path // // ────────────────────────────────────────────────────────────────────── function Sparkline({ points = [], color = 'var(--gold)', height = 80, fill = true, label }){ const max = Math.max(...points), min = Math.min(...points); const range = max - min || 1; const w = 320; const xs = points.map((_, i) => (i / (points.length - 1)) * w); const ys = points.map(p => height - 10 - ((p - min) / range) * (height - 20)); const path = xs.map((x, i) => `${i === 0 ? 'M' : 'L'} ${x.toFixed(1)} ${ys[i].toFixed(1)}`).join(' '); const area = `${path} L ${w} ${height} L 0 ${height} Z`; const id = `spark-${color.replace(/[^a-z]/gi,'')}-${Math.random().toString(36).slice(2,7)}`; return ( {fill && } {/* endpoint */} ); } // ────────────────────────────────────────────────────────────────────── // 4. EVAL BAR — vertical Stockfish eval indicator // // ────────────────────────────────────────────────────────────────────── function EvalBar({ value = 0, max = 5, height = 300, depth = 22 }){ // value range: -max to +max; positive = white advantage const pct = Math.max(0, Math.min(1, (value + max) / (2 * max))); const whiteHeight = pct * 100; return (
{/* center line */}
{/* value badge */} = 0 ? '#1a1612' : '#fffaf2', color: value >= 0 ? 'var(--gold)' : 'var(--ink)', border: `1px solid ${value >= 0 ? 'rgba(255,247,230,.3)' : 'var(--line-strong)'}`, whiteSpace:'nowrap', }}>{value > 0 ? '+' : ''}{value.toFixed(1)} {/* depth */} D · {depth}
); } // ────────────────────────────────────────────────────────────────────── // 5. MOVE LIST — algebraic notation column // ────────────────────────────────────────────────────────────────────── function MoveList({ moves = [], current = -1, dark = false }){ const bg = dark ? 'rgba(255,247,230,.04)' : 'var(--paper-200)'; const border = dark ? 'rgba(255,247,230,.10)' : 'var(--line)'; const muted = dark ? 'rgba(255,247,230,.55)' : 'var(--ink-muted)'; const text = dark ? '#fff7e6' : 'var(--ink)'; return (
{moves.map((pair, i) => ( {i + 1}. {pair[0]} {pair[1] || ''} ))}
); } // ────────────────────────────────────────────────────────────────────── // 6. INSTRUMENT PANEL — composite card wrapper for instruments // ────────────────────────────────────────────────────────────────────── function InstrumentCard({ title, hint, children, dark = false, style = {} }){ const bg = dark ? 'linear-gradient(160deg, #181519 0%, #2a1f24 100%)' : 'var(--paper-card)'; const border = dark ? 'rgba(255,247,230,.16)' : 'var(--line-strong)'; const text = dark ? '#fff7e6' : 'var(--ink)'; const muted = dark ? 'rgba(255,247,230,.55)' : 'var(--ink-muted)'; return (
{title} {hint && {hint}}
{children}
); } // ────────────────────────────────────────────────────────────────────── // 7. STREAK FLAME — animated flame icon with day count // ────────────────────────────────────────────────────────────────────── function StreakFlame({ days = 7, dark = false }){ const fg = dark ? '#fff7e6' : 'var(--ink)'; return (
{days}d
); } // keyframe for flame if (typeof document !== 'undefined' && !document.getElementById('pw-instruments-kf')){ const s = document.createElement('style'); s.id = 'pw-instruments-kf'; s.textContent = ` @keyframes pwFlicker { 0%,100% { transform: scaleY(1) rotate(-1deg); } 35% { transform: scaleY(1.08) rotate(2deg); } 65% { transform: scaleY(.95) rotate(-2deg); } } `; document.head.appendChild(s); } Object.assign(window, { BrassGauge, RatingDial, Sparkline, EvalBar, MoveList, InstrumentCard, StreakFlame, });