File size: 3,547 Bytes
f2f99a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import type { PresetName } from '@core/types.js';

const PRESET_MARKS: Array<{ value: number; label: string; emoji: string; preset: PresetName }> = [
  { value: 0.00, label: 'Light', emoji: '🌤️', preset: 'light' },
  { value: 0.33, label: 'Moderate', emoji: '⚡', preset: 'moderate' },
  { value: 0.67, label: 'Strong', emoji: '🛡️', preset: 'strong' },
  { value: 1.00, label: 'Fortress', emoji: '🏰', preset: 'fortress' },
];

interface StrengthSliderProps {
  value: number;
  onChange: (value: number, preset: PresetName) => void;
  disabled?: boolean;
}

export default function StrengthSlider({ value, onChange, disabled }: StrengthSliderProps) {
  const nearestPreset = PRESET_MARKS.reduce((best, mark) =>
    Math.abs(mark.value - value) < Math.abs(best.value - value) ? mark : best
  );

  return (
    <div className="space-y-3">
      <div className="flex items-center justify-between">
        <label className="text-sm font-medium text-zinc-300">Strength</label>
        <span className="text-xs tabular-nums text-zinc-500">
          {nearestPreset.emoji} {nearestPreset.label}
        </span>
      </div>

      <div className="relative">
        <input
          type="range"
          min="0"
          max="1"
          step="0.01"
          value={value}
          disabled={disabled}
          onChange={(e) => {
            const v = parseFloat(e.target.value);
            const snap = PRESET_MARKS.find((m) => Math.abs(m.value - v) < 0.04);
            if (snap) {
              onChange(snap.value, snap.preset);
            } else {
              // Snap to nearest preset (no custom mode)
              const nearest = PRESET_MARKS.reduce((best, mark) =>
                Math.abs(mark.value - v) < Math.abs(best.value - v) ? mark : best
              );
              onChange(nearest.value, nearest.preset);
            }
          }}
          className="w-full h-1.5 rounded-full appearance-none cursor-pointer
            bg-zinc-700 accent-blue-500
            disabled:opacity-50 disabled:cursor-not-allowed
            [&::-webkit-slider-thumb]:appearance-none
            [&::-webkit-slider-thumb]:w-4
            [&::-webkit-slider-thumb]:h-4
            [&::-webkit-slider-thumb]:rounded-full
            [&::-webkit-slider-thumb]:bg-blue-500
            [&::-webkit-slider-thumb]:shadow-lg
            [&::-webkit-slider-thumb]:shadow-blue-500/20
            [&::-webkit-slider-thumb]:transition-transform
            [&::-webkit-slider-thumb]:hover:scale-110"
        />

        <div className="relative mt-2 h-5">
          {PRESET_MARKS.map((mark, i) => {
            const pct = mark.value * 100;
            const isFirst = i === 0;
            const isLast = i === PRESET_MARKS.length - 1;
            const align = isFirst ? 'left-0' : isLast ? 'right-0' : '-translate-x-1/2';
            return (
              <button
                key={mark.preset}
                onClick={() => onChange(mark.value, mark.preset)}
                disabled={disabled}
                style={isFirst || isLast ? undefined : { left: `${pct}%` }}
                className={`absolute whitespace-nowrap text-[10px] transition-colors ${align} ${
                  nearestPreset.preset === mark.preset
                    ? 'text-blue-400 font-medium'
                    : 'text-zinc-600 hover:text-zinc-400'
                } disabled:opacity-50`}
              >
                {mark.emoji} {mark.label}
              </button>
            );
          })}
        </div>
      </div>
    </div>
  );
}