IDKHowToCodeFr commited on
Commit
be61f37
·
verified ·
1 Parent(s): 8b66798

Upload folder using huggingface_hub

Browse files
frontend/src/app/architecture/page.tsx CHANGED
@@ -118,9 +118,9 @@ const fadeUp = {
118
 
119
  export default function Architecture() {
120
  return (
121
- <main className="min-h-[100dvh] bg-canvas-cream flex flex-col font-mono selection:bg-hazard selection:text-white pb-16 overflow-x-hidden">
122
 
123
- <div className="max-w-[1400px] mx-auto w-full px-4 md:px-8 py-16 flex-1 flex flex-col">
124
 
125
  {/* HEADER BLOCK */}
126
  <motion.header
@@ -136,7 +136,7 @@ export default function Architecture() {
136
  PIPELINE<br />TOPOLOGY
137
  </h1>
138
  </div>
139
- <div className="text-[12px] text-slate font-bold tracking-[0.1em] uppercase text-right max-w-xs">
140
  REF: HFL-OS-ARCH-01<br/>
141
  REV: 2.6.4<br/>
142
  STATUS: ACTIVE
@@ -146,7 +146,7 @@ export default function Architecture() {
146
  {/* BLUEPRINT GRID */}
147
  <motion.div
148
  initial="hidden" animate="show" variants={fadeUp}
149
- className="w-full bg-ink grid grid-cols-1 md:grid-cols-3 gap-[2px] p-[2px] mb-16 shadow-[16px_16px_0px_0px_rgba(230,25,25,1)]"
150
  >
151
  {/* COLUMN 1 */}
152
  <div className="bg-canvas-cream flex flex-col justify-between p-8 relative group">
@@ -219,11 +219,11 @@ export default function Architecture() {
219
  <div className="text-[10px] text-hazard font-bold tracking-[0.15em] mb-2 uppercase">SPEC_01</div>
220
  <h2 className="text-[24px] font-sans font-black tracking-[-0.02em] uppercase text-ink leading-tight">DATA SANITIZATION & FEATURES</h2>
221
  </div>
222
- <div className="md:col-span-3 bg-ink p-[2px]">
223
- <div className="bg-canvas-cream grid grid-cols-1 md:grid-cols-2 h-full gap-[2px] bg-ink">
224
 
225
  <div className="bg-canvas-cream p-8">
226
- <h3 className="text-[12px] font-bold text-ink tracking-[0.1em] mb-4 uppercase border-b-2 border-ink pb-2">/// Pipeline_Execution</h3>
227
  <p className="text-[13px] text-slate leading-relaxed uppercase mb-6">
228
  Raw telemetry is hostile. The ingestion script aggressively drops redundant alert flags to prevent target-leakage. Broken unicode encodings from edge sensors are patched on the fly.
229
  </p>
@@ -235,7 +235,7 @@ export default function Architecture() {
235
  </div>
236
 
237
  <div className="bg-canvas-cream p-8">
238
- <h3 className="text-[12px] font-bold text-ink tracking-[0.1em] mb-4 uppercase border-b-2 border-ink pb-2">/// Feature_Synthesis</h3>
239
  <p className="text-[13px] text-slate leading-relaxed uppercase mb-6">
240
  Missing sensor drops are repaired using a fitted <code className="bg-ink text-white px-1">SimpleImputer</code>. We inject a non-linear hint directly into the AST: <code className="bg-ink text-white px-1">Risk_Severity</code>. All floats are strictly normalized.
241
  </p>
@@ -264,7 +264,7 @@ export default function Architecture() {
264
  When a clinician uploads a new batch dataset, the API spawns a background thread. It retrains all 5 models, calculates the weighted F1-Score against a 20% holdout, and checks <code className="bg-ink text-white px-1">registry.json</code>. If the new score degrades, the batch is destroyed. If it improves, the active `.pkl` weights are hot-swapped globally without dropping WebSocket streams.
265
  </p>
266
 
267
- <div className="inline-flex flex-col md:flex-row border-2 border-ink bg-ink gap-[2px] relative z-10">
268
  <div className="bg-canvas-cream text-ink px-6 py-4 font-bold text-[12px] flex items-center justify-center">
269
  F1 SCORE &gt; ACTIVE_V1
270
  </div>
 
118
 
119
  export default function Architecture() {
120
  return (
121
+ <main className="min-h-dvh bg-canvas-cream flex flex-col font-mono selection:bg-hazard selection:text-white pb-16 overflow-x-hidden">
122
 
123
+ <div className="max-w-350 mx-auto w-full px-4 md:px-8 py-16 flex-1 flex flex-col">
124
 
125
  {/* HEADER BLOCK */}
126
  <motion.header
 
136
  PIPELINE<br />TOPOLOGY
137
  </h1>
138
  </div>
139
+ <div className="text-[12px] text-slate font-bold tracking-widest uppercase text-right max-w-xs">
140
  REF: HFL-OS-ARCH-01<br/>
141
  REV: 2.6.4<br/>
142
  STATUS: ACTIVE
 
146
  {/* BLUEPRINT GRID */}
147
  <motion.div
148
  initial="hidden" animate="show" variants={fadeUp}
149
+ className="w-full bg-ink grid grid-cols-1 md:grid-cols-3 gap-0.5 p-0.5 mb-16 shadow-[16px_16px_0px_0px_rgba(230,25,25,1)]"
150
  >
151
  {/* COLUMN 1 */}
152
  <div className="bg-canvas-cream flex flex-col justify-between p-8 relative group">
 
219
  <div className="text-[10px] text-hazard font-bold tracking-[0.15em] mb-2 uppercase">SPEC_01</div>
220
  <h2 className="text-[24px] font-sans font-black tracking-[-0.02em] uppercase text-ink leading-tight">DATA SANITIZATION & FEATURES</h2>
221
  </div>
222
+ <div className="md:col-span-3 bg-ink p-0.5">
223
+ <div className="grid grid-cols-1 md:grid-cols-2 h-full gap-0.5 bg-ink">
224
 
225
  <div className="bg-canvas-cream p-8">
226
+ <h3 className="text-[12px] font-bold text-ink tracking-widest mb-4 uppercase border-b-2 border-ink pb-2">/// Pipeline_Execution</h3>
227
  <p className="text-[13px] text-slate leading-relaxed uppercase mb-6">
228
  Raw telemetry is hostile. The ingestion script aggressively drops redundant alert flags to prevent target-leakage. Broken unicode encodings from edge sensors are patched on the fly.
229
  </p>
 
235
  </div>
236
 
237
  <div className="bg-canvas-cream p-8">
238
+ <h3 className="text-[12px] font-bold text-ink tracking-widest mb-4 uppercase border-b-2 border-ink pb-2">/// Feature_Synthesis</h3>
239
  <p className="text-[13px] text-slate leading-relaxed uppercase mb-6">
240
  Missing sensor drops are repaired using a fitted <code className="bg-ink text-white px-1">SimpleImputer</code>. We inject a non-linear hint directly into the AST: <code className="bg-ink text-white px-1">Risk_Severity</code>. All floats are strictly normalized.
241
  </p>
 
264
  When a clinician uploads a new batch dataset, the API spawns a background thread. It retrains all 5 models, calculates the weighted F1-Score against a 20% holdout, and checks <code className="bg-ink text-white px-1">registry.json</code>. If the new score degrades, the batch is destroyed. If it improves, the active `.pkl` weights are hot-swapped globally without dropping WebSocket streams.
265
  </p>
266
 
267
+ <div className="inline-flex flex-col md:flex-row border-2 border-ink bg-ink gap-0.5 relative z-10">
268
  <div className="bg-canvas-cream text-ink px-6 py-4 font-bold text-[12px] flex items-center justify-center">
269
  F1 SCORE &gt; ACTIVE_V1
270
  </div>
frontend/src/app/dashboard/page.tsx CHANGED
@@ -23,7 +23,7 @@ export default function Dashboard() {
23
  const { data, stream, status } = useTelemetry();
24
 
25
  return (
26
- <main className="max-h-dvh bg-canvas-cream flex flex-col font-mono selection:bg-hazard selection:text-white pb-16">
27
  {/* Top Nav (Mechanical) */}
28
 
29
  <motion.div
@@ -40,7 +40,7 @@ export default function Dashboard() {
40
  transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
41
  className="w-2 h-2 rounded-full bg-hazard"
42
  />
43
- /// TELEMETRY_STREAM_ACTIVE
44
  </div>
45
  <h1 className="text-[clamp(3rem,6vw,6rem)] leading-[0.9]">
46
  PATIENT<br />TELEMETRY
 
23
  const { data, stream, status } = useTelemetry();
24
 
25
  return (
26
+ <main className="w-full flex-1 bg-canvas-cream flex flex-col font-mono selection:bg-hazard selection:text-white pb-16">
27
  {/* Top Nav (Mechanical) */}
28
 
29
  <motion.div
 
40
  transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
41
  className="w-2 h-2 rounded-full bg-hazard"
42
  />
43
+ {"/// TELEMETRY_STREAM_ACTIVE"}
44
  </div>
45
  <h1 className="text-[clamp(3rem,6vw,6rem)] leading-[0.9]">
46
  PATIENT<br />TELEMETRY
frontend/src/app/simulator/page.tsx CHANGED
@@ -47,7 +47,7 @@ export default function Analysis() {
47
  setLoading(true);
48
  setPrediction(null);
49
  setSubmittedData(null);
50
-
51
  try {
52
  const res = await fetch((process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000") + "/predict", {
53
  method: "POST",
@@ -65,10 +65,10 @@ export default function Analysis() {
65
  };
66
 
67
  return (
68
- <main className="max-h-dvh bg-canvas-cream flex flex-col font-mono selection:bg-hazard selection:text-white pb-16">
69
  {/* Top Nav (Mechanical) */}
70
 
71
- <motion.div
72
  variants={staggerContainer}
73
  initial="hidden"
74
  animate="show"
@@ -77,7 +77,7 @@ export default function Analysis() {
77
  <motion.header variants={fadeUp} className="mb-12 border-b-4 border-ink pb-8 flex justify-between items-end">
78
  <div>
79
  <div className="text-[12px] text-hazard font-bold tracking-widest mb-4">
80
- /// EXPLAINABLE_AI_MODULE
81
  </div>
82
  <h1 className="text-[clamp(3rem,6vw,6rem)]">
83
  MODEL<br />SIMULATOR
@@ -91,21 +91,21 @@ export default function Analysis() {
91
  </motion.header>
92
 
93
  <motion.div variants={staggerContainer} className="grid grid-cols-1 lg:grid-cols-12 gap-px bg-ink border-2 border-ink">
94
-
95
  {/* Input Panel */}
96
  <motion.div variants={fadeUp} className="lg:col-span-5 bg-canvas-cream flex flex-col">
97
  <div className="border-b-2 border-ink p-4 bg-white flex justify-between items-center">
98
  <h2 className="text-[14px] font-bold tracking-wider">{'< MANUAL_INPUT_MATRIX >'}</h2>
99
  </div>
100
-
101
  <div className="p-8 flex flex-col gap-8 grow">
102
- <InputControl label="HEART_RATE (BPM)" value={formData.Heart_Rate} min={40} max={200} onChange={(v: number) => setFormData({...formData, Heart_Rate: v})} />
103
- <InputControl label="SPO2_LEVEL (%)" value={formData.SpO2_Level} min={70} max={100} onChange={(v: number) => setFormData({...formData, SpO2_Level: v})} />
104
- <InputControl label="SYSTOLIC_BP (MMHG)" value={formData.Systolic_BP} min={80} max={220} onChange={(v: number) => setFormData({...formData, Systolic_BP: v})} />
105
- <InputControl label="DIASTOLIC_BP (MMHG)" value={formData.Diastolic_BP} min={40} max={140} onChange={(v: number) => setFormData({...formData, Diastolic_BP: v})} />
106
- <InputControl label="BODY_TEMP (°C)" value={formData.Body_Temp} min={30} max={42} step={0.1} onChange={(v: number) => setFormData({...formData, Body_Temp: v})} />
107
-
108
- <motion.button
109
  onClick={handlePredict}
110
  disabled={loading}
111
  whileHover={{ y: 2, x: 2, boxShadow: "4px 4px 0px 0px rgba(0,0,0,1)" }}
@@ -117,14 +117,15 @@ export default function Analysis() {
117
  </motion.button>
118
 
119
  {/* Terminal Readout */}
120
- <div className="relative inset-0 pointer-events-none opacity-10 bg-[repeating-linear-gradient(0deg,transparent,transparent_2px,rgba(255,255,255,0.1)_2px,rgba(255,255,255,0.1)_4px)] z-10" />
 
121
  <div className="text-[10px] font-bold tracking-[0.2em] text-ink bg-white border-b-[3px] border-ink px-4 py-3 flex justify-between uppercase">
122
  <span>RAW_PAYLOAD_PREVIEW</span>
123
- <span>{JSON.stringify(formData).length} BYTES // SYS_READY</span>
124
  </div>
125
  <div className="p-6 grow flex flex-col relative z-0">
126
  <pre className="text-[12px] font-mono whitespace-pre-wrap grow overflow-auto leading-relaxed">
127
- {`{
128
  "Heart_Rate": ${formData.Heart_Rate},
129
  "SpO2_Level": ${formData.SpO2_Level},
130
  "Systolic_BP": ${formData.Systolic_BP},
@@ -135,6 +136,7 @@ export default function Analysis() {
135
  </pre>
136
  </div>
137
  </div>
 
138
  </motion.div>
139
 
140
  {/* Results Panel */}
@@ -142,20 +144,20 @@ export default function Analysis() {
142
  <div className="border-b-2 border-ink p-4 bg-white flex justify-between items-center">
143
  <h2 className="text-[14px] font-bold tracking-wider">{'< INFERENCE_RESULTS_&_XAI >'}</h2>
144
  </div>
145
-
146
  <div className="grow flex flex-col relative bg-white">
147
  <div className="absolute inset-0 z-0 opacity-10 pointer-events-none" style={{ backgroundImage: 'linear-gradient(var(--ink-black) 1px, transparent 1px), linear-gradient(90deg, var(--ink-black) 1px, transparent 1px)', backgroundSize: '40px 40px' }}></div>
148
-
149
  <div className="z-10 p-8 flex flex-col h-full">
150
  {prediction ? (
151
- <motion.div
152
  initial={{ opacity: 0, scale: 0.95 }}
153
  animate={{ opacity: 1, scale: 1 }}
154
  transition={{ duration: 0.6, ease: easeFluid }}
155
  >
156
  <div className="mb-12">
157
  <div className="text-[11px] font-bold text-slate tracking-widest mb-4">MODEL_OUTPUT</div>
158
-
159
  {(() => {
160
  const getStatusStyles = (label: string) => {
161
  const normalized = (label || "").toLowerCase();
@@ -164,9 +166,9 @@ export default function Analysis() {
164
  if (normalized.includes('asthma')) return "bg-[#e0f2fe] text-[#075985] border-[#075985]";
165
  if (normalized.includes('hypertension')) return "bg-[#f3e8ff] text-[#6b21a8] border-[#6b21a8]";
166
  if (normalized.includes('diabetes')) return "bg-[#ffedd5] text-[#9a3412] border-[#9a3412]";
167
- return "bg-hazard text-white border-hazard";
168
  };
169
-
170
  return (
171
  <div className={`border-2 p-6 flex flex-col mb-4 ${getStatusStyles(prediction.prediction ? prediction.prediction_label : 'Healthy')}`}>
172
  <div className="font-sans text-[clamp(28px,3vw,56px)] font-black tracking-tighter leading-none mb-2 uppercase wrap-break-words">
@@ -188,7 +190,7 @@ export default function Analysis() {
188
  </div>
189
  <div className="divide-y-2 divide-ink">
190
  {Object.keys(prediction.model_outputs).map((modelName, index) => (
191
- <motion.div
192
  key={modelName}
193
  initial={{ opacity: 0, x: 20 }}
194
  animate={{ opacity: 1, x: 0 }}
@@ -229,7 +231,7 @@ export default function Analysis() {
229
  </div>
230
  </div>
231
  </motion.div>
232
-
233
  </motion.div>
234
  </motion.div>
235
  </main>
@@ -241,7 +243,7 @@ function InputControl({ label, value, min, max, step = 1, onChange }: any) {
241
  <div className="flex flex-col gap-3 group">
242
  <div className="flex justify-between items-end">
243
  <label className="text-[13px] font-bold text-slate tracking-widest group-hover:text-ink transition-colors">{label}</label>
244
- <motion.span
245
  key={value}
246
  initial={{ opacity: 0.5, y: -5 }}
247
  animate={{ opacity: 1, y: 0 }}
@@ -251,12 +253,12 @@ function InputControl({ label, value, min, max, step = 1, onChange }: any) {
251
  </motion.span>
252
  </div>
253
  <div className="relative border-2 border-ink p-1 bg-white">
254
- <input
255
- type="range"
256
- min={min}
257
- max={max}
258
  step={step}
259
- value={value}
260
  onChange={(e) => onChange(Number(e.target.value))}
261
  className="w-full appearance-none h-4 bg-ink/10 outline-none [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-6 [&::-webkit-slider-thumb]:h-8 [&::-webkit-slider-thumb]:bg-ink [&::-webkit-slider-thumb]:cursor-pointer relative z-10 block"
262
  />
 
47
  setLoading(true);
48
  setPrediction(null);
49
  setSubmittedData(null);
50
+
51
  try {
52
  const res = await fetch((process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000") + "/predict", {
53
  method: "POST",
 
65
  };
66
 
67
  return (
68
+ <main className="w-full flex-1 bg-canvas-cream flex flex-col font-mono selection:bg-hazard selection:text-white pb-16">
69
  {/* Top Nav (Mechanical) */}
70
 
71
+ <motion.div
72
  variants={staggerContainer}
73
  initial="hidden"
74
  animate="show"
 
77
  <motion.header variants={fadeUp} className="mb-12 border-b-4 border-ink pb-8 flex justify-between items-end">
78
  <div>
79
  <div className="text-[12px] text-hazard font-bold tracking-widest mb-4">
80
+ {"/// EXPLAINABLE_AI_MODULE"}
81
  </div>
82
  <h1 className="text-[clamp(3rem,6vw,6rem)]">
83
  MODEL<br />SIMULATOR
 
91
  </motion.header>
92
 
93
  <motion.div variants={staggerContainer} className="grid grid-cols-1 lg:grid-cols-12 gap-px bg-ink border-2 border-ink">
94
+
95
  {/* Input Panel */}
96
  <motion.div variants={fadeUp} className="lg:col-span-5 bg-canvas-cream flex flex-col">
97
  <div className="border-b-2 border-ink p-4 bg-white flex justify-between items-center">
98
  <h2 className="text-[14px] font-bold tracking-wider">{'< MANUAL_INPUT_MATRIX >'}</h2>
99
  </div>
100
+
101
  <div className="p-8 flex flex-col gap-8 grow">
102
+ <InputControl label="HEART_RATE (BPM)" value={formData.Heart_Rate} min={40} max={200} onChange={(v: number) => setFormData({ ...formData, Heart_Rate: v })} />
103
+ <InputControl label="SPO2_LEVEL (%)" value={formData.SpO2_Level} min={70} max={100} onChange={(v: number) => setFormData({ ...formData, SpO2_Level: v })} />
104
+ <InputControl label="SYSTOLIC_BP (MMHG)" value={formData.Systolic_BP} min={80} max={220} onChange={(v: number) => setFormData({ ...formData, Systolic_BP: v })} />
105
+ <InputControl label="DIASTOLIC_BP (MMHG)" value={formData.Diastolic_BP} min={40} max={140} onChange={(v: number) => setFormData({ ...formData, Diastolic_BP: v })} />
106
+ <InputControl label="BODY_TEMP (°C)" value={formData.Body_Temp} min={30} max={42} step={0.1} onChange={(v: number) => setFormData({ ...formData, Body_Temp: v })} />
107
+
108
+ <motion.button
109
  onClick={handlePredict}
110
  disabled={loading}
111
  whileHover={{ y: 2, x: 2, boxShadow: "4px 4px 0px 0px rgba(0,0,0,1)" }}
 
117
  </motion.button>
118
 
119
  {/* Terminal Readout */}
120
+ <div className="mt-8 grow border-[3px] border-ink bg-ink text-[#4AF626] p-0 flex flex-col min-h-37.5 shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] relative overflow-hidden group">
121
+ <div className="absolute inset-0 pointer-events-none opacity-10 bg-[repeating-linear-gradient(0deg,transparent,transparent_2px,rgba(255,255,255,0.1)_2px,rgba(255,255,255,0.1)_4px)] z-10" />
122
  <div className="text-[10px] font-bold tracking-[0.2em] text-ink bg-white border-b-[3px] border-ink px-4 py-3 flex justify-between uppercase">
123
  <span>RAW_PAYLOAD_PREVIEW</span>
124
+ <span>{JSON.stringify(formData).length} BYTES {"// SYS_READY"}</span>
125
  </div>
126
  <div className="p-6 grow flex flex-col relative z-0">
127
  <pre className="text-[12px] font-mono whitespace-pre-wrap grow overflow-auto leading-relaxed">
128
+ {`{
129
  "Heart_Rate": ${formData.Heart_Rate},
130
  "SpO2_Level": ${formData.SpO2_Level},
131
  "Systolic_BP": ${formData.Systolic_BP},
 
136
  </pre>
137
  </div>
138
  </div>
139
+ </div>
140
  </motion.div>
141
 
142
  {/* Results Panel */}
 
144
  <div className="border-b-2 border-ink p-4 bg-white flex justify-between items-center">
145
  <h2 className="text-[14px] font-bold tracking-wider">{'< INFERENCE_RESULTS_&_XAI >'}</h2>
146
  </div>
147
+
148
  <div className="grow flex flex-col relative bg-white">
149
  <div className="absolute inset-0 z-0 opacity-10 pointer-events-none" style={{ backgroundImage: 'linear-gradient(var(--ink-black) 1px, transparent 1px), linear-gradient(90deg, var(--ink-black) 1px, transparent 1px)', backgroundSize: '40px 40px' }}></div>
150
+
151
  <div className="z-10 p-8 flex flex-col h-full">
152
  {prediction ? (
153
+ <motion.div
154
  initial={{ opacity: 0, scale: 0.95 }}
155
  animate={{ opacity: 1, scale: 1 }}
156
  transition={{ duration: 0.6, ease: easeFluid }}
157
  >
158
  <div className="mb-12">
159
  <div className="text-[11px] font-bold text-slate tracking-widest mb-4">MODEL_OUTPUT</div>
160
+
161
  {(() => {
162
  const getStatusStyles = (label: string) => {
163
  const normalized = (label || "").toLowerCase();
 
166
  if (normalized.includes('asthma')) return "bg-[#e0f2fe] text-[#075985] border-[#075985]";
167
  if (normalized.includes('hypertension')) return "bg-[#f3e8ff] text-[#6b21a8] border-[#6b21a8]";
168
  if (normalized.includes('diabetes')) return "bg-[#ffedd5] text-[#9a3412] border-[#9a3412]";
169
+ return "bg-hazard text-white border-hazard";
170
  };
171
+
172
  return (
173
  <div className={`border-2 p-6 flex flex-col mb-4 ${getStatusStyles(prediction.prediction ? prediction.prediction_label : 'Healthy')}`}>
174
  <div className="font-sans text-[clamp(28px,3vw,56px)] font-black tracking-tighter leading-none mb-2 uppercase wrap-break-words">
 
190
  </div>
191
  <div className="divide-y-2 divide-ink">
192
  {Object.keys(prediction.model_outputs).map((modelName, index) => (
193
+ <motion.div
194
  key={modelName}
195
  initial={{ opacity: 0, x: 20 }}
196
  animate={{ opacity: 1, x: 0 }}
 
231
  </div>
232
  </div>
233
  </motion.div>
234
+
235
  </motion.div>
236
  </motion.div>
237
  </main>
 
243
  <div className="flex flex-col gap-3 group">
244
  <div className="flex justify-between items-end">
245
  <label className="text-[13px] font-bold text-slate tracking-widest group-hover:text-ink transition-colors">{label}</label>
246
+ <motion.span
247
  key={value}
248
  initial={{ opacity: 0.5, y: -5 }}
249
  animate={{ opacity: 1, y: 0 }}
 
253
  </motion.span>
254
  </div>
255
  <div className="relative border-2 border-ink p-1 bg-white">
256
+ <input
257
+ type="range"
258
+ min={min}
259
+ max={max}
260
  step={step}
261
+ value={value}
262
  onChange={(e) => onChange(Number(e.target.value))}
263
  className="w-full appearance-none h-4 bg-ink/10 outline-none [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-6 [&::-webkit-slider-thumb]:h-8 [&::-webkit-slider-thumb]:bg-ink [&::-webkit-slider-thumb]:cursor-pointer relative z-10 block"
264
  />