Adds the Manifold app (manifold/) — the convertible-mode React front-end on the real NISPS ML+audio engine, served live at meml.lnfinitemonkeys.org/next/. Console chrome trimmed per UI pass: - drop mode label + subtitle from the top-left overlay (keep MEMLNaut wordmark) - remove the composite split preset/ratio readout (top-centre) - remove the OUTPUT corner tag above the bars - remove the A/B compare toggle from the verdict cluster - remove the follow button + input/noise readout (bottom-left) - remove AltitudeNav focus switcher (bottom-right)
107 lines
2.6 KiB
TypeScript
107 lines
2.6 KiB
TypeScript
import type { CSSProperties } from 'react';
|
|
|
|
export interface SliderProps {
|
|
label?: string;
|
|
value?: number;
|
|
min?: number;
|
|
max?: number;
|
|
step?: number;
|
|
unit?: string;
|
|
onChange?: (value: number) => void;
|
|
disabled?: boolean;
|
|
/** Custom formatter for the value readout. */
|
|
format?: (value: number) => string;
|
|
style?: CSSProperties;
|
|
}
|
|
|
|
/**
|
|
* Manifold Slider — labeled horizontal range with a glowing orange thumb and
|
|
* a tabular value readout. Controlled via value/onChange (0..max).
|
|
*
|
|
* Relies on the `.mf-slider-input` rules in `styles/primitives.css` for the
|
|
* track gradient and glowing thumb. The fill percentage is passed via the
|
|
* inline `--mf-pct` custom property.
|
|
*/
|
|
export function Slider({
|
|
label,
|
|
value = 0,
|
|
min = 0,
|
|
max = 1,
|
|
step = 0.01,
|
|
unit = '',
|
|
onChange,
|
|
disabled = false,
|
|
format,
|
|
style,
|
|
}: SliderProps) {
|
|
const pct = max > min ? (value - min) / (max - min) : 0;
|
|
const display = format
|
|
? format(value)
|
|
: Number.isInteger(step)
|
|
? String(value)
|
|
: value.toFixed(2);
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: 'var(--sp-1)',
|
|
fontFamily: 'var(--font-mono)',
|
|
userSelect: 'none',
|
|
opacity: disabled ? 0.5 : 1,
|
|
pointerEvents: disabled ? 'none' : 'auto',
|
|
...style,
|
|
}}
|
|
>
|
|
{label && (
|
|
<span
|
|
style={{
|
|
color: 'var(--fg-mute)',
|
|
fontSize: 'var(--fs-xs)',
|
|
textTransform: 'uppercase',
|
|
letterSpacing: '0.06em',
|
|
}}
|
|
>
|
|
{label}
|
|
</span>
|
|
)}
|
|
<div style={{ display: 'flex', gap: 'var(--sp-3)', alignItems: 'center' }}>
|
|
<input
|
|
type="range"
|
|
min={min}
|
|
max={max}
|
|
step={step}
|
|
value={value}
|
|
disabled={disabled}
|
|
onChange={(e) => onChange?.(parseFloat(e.target.value))}
|
|
className="mf-slider-input"
|
|
style={
|
|
{
|
|
flex: 1,
|
|
WebkitAppearance: 'none',
|
|
appearance: 'none',
|
|
background: 'transparent',
|
|
height: 24,
|
|
margin: 0,
|
|
cursor: 'pointer',
|
|
'--mf-pct': `${pct}`,
|
|
} as CSSProperties
|
|
}
|
|
/>
|
|
<span
|
|
style={{
|
|
fontVariantNumeric: 'tabular-nums',
|
|
fontSize: 'var(--fs-xs)',
|
|
color: 'var(--fg-mute)',
|
|
minWidth: '4ch',
|
|
textAlign: 'right',
|
|
}}
|
|
>
|
|
{display}
|
|
{unit && <span style={{ color: 'var(--fg-dim)', marginLeft: 2 }}>{unit}</span>}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|