← back to Norma

components/email-analyzer/Thermometer.tsx

162 lines

'use client';

interface ThermometerProps {
  score: number;
  prevScore?: number | null;
  label?: string;
  height?: number;
}

function scoreColor(score: number): string {
  if (score >= 95) return '#10b981';
  if (score >= 75) return '#10b981';
  if (score >= 50) return '#f59e0b';
  if (score >= 25) return '#fb923c';
  return '#f43f5e';
}

/**
 * Live "completion thermometer" — SVG thermometer that fills with mercury
 * proportional to the score. Notches mark 25 / 50 / 75 / 100.
 */
export default function Thermometer({ score, prevScore = null, label, height = 180 }: ThermometerProps) {
  const clamped = Math.max(0, Math.min(100, score));
  const color = scoreColor(clamped);
  const bulbRadius = 18;
  const tubeWidth = 14;
  const tubeX = 30;
  const tubeTop = 14;
  const tubeBottom = height - bulbRadius - 10;
  const tubeHeight = tubeBottom - tubeTop;
  const fillHeight = (clamped / 100) * tubeHeight;
  const fillY = tubeBottom - fillHeight;

  const notches = [0, 25, 50, 75, 100];
  const prevY = prevScore !== null ? tubeBottom - (prevScore / 100) * tubeHeight : null;

  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
      <svg width={78} height={height} viewBox={`0 0 78 ${height}`} style={{ flexShrink: 0 }}>
        {/* Tube outline */}
        <rect
          x={tubeX}
          y={tubeTop}
          width={tubeWidth}
          height={tubeHeight}
          rx={tubeWidth / 2}
          ry={tubeWidth / 2}
          fill="var(--color-surface-el)"
          stroke="var(--color-border)"
          strokeWidth={1.5}
        />

        {/* Mercury fill — tube */}
        <rect
          x={tubeX + 2}
          y={fillY}
          width={tubeWidth - 4}
          height={Math.max(0, tubeBottom - fillY)}
          rx={(tubeWidth - 4) / 2}
          ry={(tubeWidth - 4) / 2}
          fill={color}
          style={{ transition: 'y 500ms ease, height 500ms ease, fill 300ms ease' }}
        />

        {/* Previous score marker */}
        {prevY !== null && Math.abs(prevY - fillY) > 2 && (
          <line
            x1={tubeX - 4}
            x2={tubeX + tubeWidth + 4}
            y1={prevY}
            y2={prevY}
            stroke="var(--color-text-muted)"
            strokeWidth={1.5}
            strokeDasharray="2,2"
          />
        )}

        {/* Bulb — background */}
        <circle
          cx={tubeX + tubeWidth / 2}
          cy={tubeBottom + bulbRadius - 2}
          r={bulbRadius}
          fill={color}
          stroke="var(--color-border)"
          strokeWidth={1.5}
        />
        {/* Bulb highlight */}
        <circle
          cx={tubeX + tubeWidth / 2 - 4}
          cy={tubeBottom + bulbRadius - 5}
          r={3}
          fill="rgba(255,255,255,0.4)"
        />

        {/* Notches */}
        {notches.map((n) => {
          const y = tubeBottom - (n / 100) * tubeHeight;
          return (
            <g key={n}>
              <line
                x1={tubeX + tubeWidth + 2}
                x2={tubeX + tubeWidth + 8}
                y1={y}
                y2={y}
                stroke="var(--color-text-muted)"
                strokeWidth={1}
              />
              <text
                x={tubeX + tubeWidth + 11}
                y={y + 3}
                fontSize={9}
                fill="var(--color-text-muted)"
                fontFamily="system-ui, sans-serif"
              >
                {n}
              </text>
            </g>
          );
        })}
      </svg>

      <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 4 }}>
          <span
            style={{
              fontSize: 30,
              fontWeight: 800,
              color,
              lineHeight: 1,
              fontVariantNumeric: 'tabular-nums',
            }}
          >
            {clamped}
          </span>
          <span style={{ fontSize: 12, color: 'var(--color-text-muted)' }}>/100</span>
        </div>
        {label && (
          <div style={{ fontSize: 11, color: 'var(--color-text-secondary)', fontWeight: 600 }}>
            {label}
          </div>
        )}
        <div style={{ fontSize: 10, color: 'var(--color-text-muted)' }}>
          {clamped === 100
            ? '🏆 Maxed out'
            : `${100 - clamped} pts to 💯`}
        </div>
        {prevScore !== null && prevScore !== clamped && (
          <div
            style={{
              fontSize: 10,
              color: clamped > prevScore ? 'var(--color-success)' : 'var(--color-error)',
              fontWeight: 600,
            }}
          >
            {clamped > prevScore ? '↑' : '↓'} {Math.abs(clamped - prevScore)} from v{prevScore}
          </div>
        )}
      </div>
    </div>
  );
}