Tweakit

Numbers

Four ways to edit a number — or two, or four of them at once. Sliders and number fields are built in; interval and point are heavy controls that load on first use.

Slider

The workhorse. [value, min, max, step] as shorthand, or { type: "slider" } for options. Drag anywhere on the track, scrub with arrow keys (⇧ for coarse steps), or hover the value and click to type (double-click resets). With six or fewer stops the track snaps and shows rule lines.

const tile = target.querySelector(".num-tile");
const panel = tweaks("Slider", {
  radius: [16, 0, 66, 1],       // continuous: smooth square → circle
  size: [4, 2, 6, 1],           // ≤6 stops: snaps, shows rule lines
});
mount.append(panel.el);

const apply = (p) => {
  tile.style.borderRadius = `${p.radius}px`;
  tile.style.transform = `scale(${0.55 + p.size * 0.11})`;
};
panel.on(apply);
apply(panel.params);

mount is the panel's slot inside the stage; target is the demo surface it controls. In your own page you'd just document.body.append(panel.el).

Number

{ type: "number" } is a plain numeric field with a drag-to-scrub grab handle — for values where a track makes no sense. Drag the handle to scrub it, click the field to type. min/max clamp; omit them for unbounded.

12°
const card = target.querySelector(".num-rotor-card");
const panel = tweaks("Number", {
  angle: { type: "number", value: 12, min: -180, max: 180, step: 1 },
});
mount.append(panel.el);

const apply = (p) => {
  card.style.transform = `rotate(${p.angle}deg)`;
  card.textContent = `${p.angle}°`;
};
panel.on(apply);
apply(panel.params);

Interval

A dual-handle range. Shorthand [[lo, hi], min, max, step?] — the first entry being a 2-tuple is what marks it as an interval. The value on params is a [lo, hi] pair; here it gates which bars count as “in range”.

const strip = target.querySelector(".num-bars");
const heights = Array.from({ length: 28 }, (_, i) => 12 + 84 * Math.abs(Math.sin(i * 0.6 + 1)));
const bars = heights.map((h) => {
  const b = document.createElement("div");
  b.className = "num-bar";
  b.style.height = `${h}%`;
  strip.append(b);
  return b;
});
const panel = tweaks("Interval", {
  band: [[30, 70], 0, 100, 1],   // [[lo, hi], min, max, step]
});
mount.append(panel.el);

const apply = (p) => bars.forEach((b, i) =>
  b.classList.toggle("num-bar-hot", heights[i] >= p.band[0] && heights[i] <= p.band[1]));
panel.on(apply);
panel.ready.then(() => apply(panel.params)); // interval loads lazily

Point

An n-dimensional vector — one scrubbable field per component, plus a draggable 2D pad for the first two (on by default; pad: false opts out). The value is a plain map of component keys. Add a third component for 3D, a fourth for 4D; the pad stays 2D, the fields stack.

const dot = target.querySelector(".num-dot");
const panel = tweaks("Point", {
  offset: { type: "point", pad: true, components: [
    { key: "x", label: "X", value: 0, min: -1, max: 1, step: 0.01 },
    { key: "y", label: "Y", value: 0, min: -1, max: 1, step: 0.01 },
  ] },
});
mount.append(panel.el);

const apply = (p) => {
  dot.style.left = `${50 + p.offset.x * 45}%`;
  dot.style.top = `${50 + p.offset.y * 45}%`;
};
panel.on(apply);
panel.ready.then(() => apply(panel.params)); // point loads lazily