# Structure

> Organizing a panel: folders, tabs, buttons, button groups and separators, live.

The page: https://ryankiley.github.io/tweakit/structure.html. Every page as Markdown: https://ryankiley.github.io/tweakit/llms.txt

Past a handful of controls, a flat panel stops scanning. Folders and tabs group; buttons and separators punctuate. Folders, buttons, button groups and separators are built in; tabs load lazily.

## Folder

Any nested plain object becomes a collapsible folder, and its children land on `params` as a nested object: here `type` and `box` are two folders over one tag, read as `params.type.size` and `params.box.radius`. Folders nest as deep as you'd ever want.

```js
const tag = target.querySelector(".fld-tag");
const panel = tweaks("Folder", {
  label: "Folder",
  type: {                        // nested object → folder
    size: { type: "slider", value: 15, min: 10, max: 40, step: 1, unit: "px" },
    tracking: { type: "slider", value: 0, min: -2, max: 8, step: 1, unit: "px" },
    caps: false,
  },
  box: {                         // a second folder beside the first
    padding: { type: "slider", value: 12, min: 4, max: 40, step: 1, unit: "px" },
    radius: { type: "slider", value: 10, min: 0, max: 40, step: 1, unit: "px" },
  },
});
mount.append(panel.el);

const apply = (p) => {
  tag.textContent = p.label;
  tag.style.fontSize = `${p.type.size}px`;
  tag.style.letterSpacing = `${p.type.tracking}px`;
  tag.style.textTransform = p.type.caps ? "uppercase" : "none";
  tag.style.padding = `${p.box.padding}px ${Math.round(p.box.padding * 1.6)}px`;
  tag.style.borderRadius = `${p.box.radius}px`;
};
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)`.

## Tabs

`{ type: "tabs", pages: { … } }` splits a panel into pages; each page is just another schema. Params nest by page: `params.look.fill.color`, `params.look.stroke.width`.

```js
const shape = target.querySelector(".tab-shape path");
const panel = tweaks("Tabs", {
  look: { type: "tabs", pages: {
    Fill: { color: "#7C5CFF", opacity: [1, 0, 1, 0.01] },
    Stroke: { color: "#888888", width: [2, 0, 14, 1], dashed: false },
  } },
});
mount.append(panel.el);

const apply = (p) => {
  shape.setAttribute("fill", p.look.fill.color);
  shape.setAttribute("fill-opacity", p.look.fill.opacity);
  shape.setAttribute("stroke", p.look.stroke.color);
  shape.setAttribute("stroke-width", p.look.stroke.width);
  shape.setAttribute("stroke-dasharray", p.look.stroke.dashed ? "10 8" : "0");
};
panel.on(apply);
panel.ready.then(() => apply(panel.params)); // tabs (and color) load lazily
```

## Button, button group & separator

A bare `{ action: fn }` is a button; `buttongroup` packs several into one row; `{ type: "separator" }` draws the line between concerns. Buttons don't produce params; they just fire.

```js
const orbit = target.querySelector(".act-orbit");
const planet = target.querySelector(".act-planet");
const panel = tweaks("Actions", {
  pulse: { type: "button", label: "Pulse", action: () => {
    planet.classList.remove("act-pulse");
    requestAnimationFrame(() => planet.classList.add("act-pulse"));
  } },
  playback: { type: "buttongroup", buttons: {
    Play: () => { orbit.style.animationPlayState = "running"; },
    Pause: () => { orbit.style.animationPlayState = "paused"; },
  } },
  line: { type: "separator" },
  seconds: [6, 1, 12, 0.5],
});
mount.append(panel.el);

const apply = (p) => { orbit.style.animationDuration = `${p.seconds}s`; };
panel.on(apply);
apply(panel.params);
```
