# Fine-tune Card

> Compact inspector the agent adjusts, then hands back to you.

- **Slug:** `fine-tune-card`
- **Kind:** component · **Category:** Agent
- **Source:** ported from [Beautiful UI](https://github.com/slev12397/beautiful-ui/blob/main/components/primitives/FineTuneCard.tsx) (MIT)
- **Docs page:** https://viewership.ai/ui/components/fine-tune-card
- **All files in one bundle:** https://viewership.ai/ui/components/fine-tune-card.bundle.txt
- **Structured JSON:** https://viewership.ai/ui/components/fine-tune-card.json

## Install

1. Import the foundation tokens once (download: https://viewership.ai/ui/raw/foundation.css.txt):

```astro
---
// src/layouts/Layout.astro
import "../styles/foundation.css";
---
```

2. Copy this file into `src/components/viewership/fine-tune-card/`:

   - `FineTuneCard.astro` (raw: https://viewership.ai/ui/raw/fine-tune-card/FineTuneCard.astro.txt)

3. Copy its registry dependencies, each into its own sibling folder under `src/components/viewership/`:

   - `glide-menu`: `GlideMenu.astro`, `glide-menu.ts` (docs: https://viewership.ai/ui/components/glide-menu.md)

npm packages: none.

## Usage

```astro
---
import FineTuneCard from "../components/viewership/fine-tune-card/FineTuneCard.astro";
---

<FineTuneCard />
```

## Example

The gallery demo, verbatim. Use the import path from Usage instead of `./`.

```astro
---
import FineTuneCard from "./FineTuneCard.astro";
import type { FineTuneEdit, FineTuneField } from "./FineTuneCard.astro";

const fields: FineTuneField[] = [
  { key: "width", label: "W", name: "Width", value: 324, min: 40, max: 999 },
  { key: "height", label: "H", name: "Height", value: 96, min: 24, max: 999 },
  { key: "radius", label: "Radius", value: 28, min: 0, max: 64 },
  { key: "opacity", label: "Opacity", value: 100, min: 0, max: 100, suffix: "%" },
];

// What the agent changes when the card scrolls into view.
const edits: FineTuneEdit[] = [
  { field: "radius", value: 14 },
  { field: "height", value: 120 },
  { layout: 2 },
  { type: "Launch" },
];
---

<div class="stage">
  <FineTuneCard
    fields={fields}
    options={["Launch", "Standard", "Teaser"]}
    edits={edits}
    labels={{ title: "Post card" }}
  />
</div>

<style>
  .stage {
    display: flex;
    width: 100%;
    justify-content: center;
    /* room for the type menu, which opens upward */
    padding-top: 24px;
  }
</style>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `fields` | `{ key; label; name?; value; min; max; step?; suffix? }[]` |  | Scrub-able number fields in pairs. `name` is the full accessible name when label is short. |
| `options` | `string[]` |  | Type menu options. |
| `layout` | `0 \| 1 \| 2` | `0` | Initial layout: row, column, grid. |
| `type` | `string` |  | Initial type. Empty shows the placeholder. |
| `edits` | `{ field?: string; value?: number; layout?: number; type?: string }[]` | `[]` | Changes the agent makes, in order, when scrolled into view. Interaction takes over. |
| `autoplay` | `boolean` | `true` | Play edits on scroll into view. |
| `loop` | `boolean` | `false` | Replay after a 4s hold until interaction. |
| `labels` | `Partial<FineTuneCardLabels>` |  | Text overrides: title, layout, type, placeholder, adjust, edited, layouts. |
| `vs-fine-tune-change` | `CustomEvent<{ layout; layoutName; values; type; source: "user" \| "agent" }>` |  | Fires on every change, by you or the agent. |

## Source

### `src/components/viewership/fine-tune-card/FineTuneCard.astro`

```astro
---
/**
 * FineTuneCard: a compact inspector the agent (and you) can adjust.
 *
 * Adapted from Beautiful UI FineTuneCard (MIT, © 2026 Shane Levine).
 *
 * A layout switch (row / column / grid), scrub-able number fields and a type
 * menu. Drag a field's label sideways, use up/down arrows (Shift for x10), or
 * type. Changed fields turn accent and the header flips from "Adjust" to
 * "Edited".
 *
 * Pass `edits` to have the agent make changes when the card scrolls into
 * view. Any interaction hands control back to you. Without JS or with
 * reduced motion, the edits render already applied.
 *
 * Number fields are labelled text inputs with spinbutton semantics, the
 * layout switch is a radio group, and the type menu is a listbox. Each has a
 * `name` so the card can sit inside a form.
 *
 * Events (bubbling, on the root):
 *   vs-fine-tune-change  detail: { layout, layoutName, values, type, source: "user" | "agent" }
 *
 * @example
 * <FineTuneCard fields={[{ key: "radius", label: "Radius", value: 12, min: 0, max: 48 }]} options={["Standard", "Launch"]} />
 */
import type { HTMLAttributes } from "astro/types";
import GlideMenu from "../glide-menu/GlideMenu.astro";

export interface FineTuneField {
  key: string;
  /** Visible label (the scrub handle), e.g. "W". */
  label: string;
  /** Full accessible name when the label is abbreviated, e.g. "Width". */
  name?: string;
  /** Starting value; the field turns accent once it differs. */
  value: number;
  min: number;
  max: number;
  step?: number;
  suffix?: string;
}

/** One agent step: set a field, the layout, or the type. */
export interface FineTuneEdit {
  /** Field key to change (with `value`). */
  field?: string;
  value?: number;
  /** Layout index: 0 row, 1 column, 2 grid. */
  layout?: number;
  /** Type option to pick. */
  type?: string;
}

export interface FineTuneCardLabels {
  title: string;
  layout: string;
  type: string;
  placeholder: string;
  adjust: string;
  edited: string;
  /** Names of the three layout options. */
  layouts: [string, string, string];
}

type Props = HTMLAttributes<"div"> & {
  fields: FineTuneField[];
  /** Options in the Type menu. */
  options: string[];
  labels?: Partial<FineTuneCardLabels>;
  /** Initial layout index. */
  layout?: number;
  /** Initial type (defaults to the placeholder). */
  type?: string;
  /** Changes the agent makes on its own. */
  edits?: FineTuneEdit[];
  /** Play `edits` when scrolled into view. */
  autoplay?: boolean;
  /** Replay the edits after a hold, until someone interacts. */
  loop?: boolean;
};

const {
  fields = [],
  options = [],
  labels,
  layout = 0,
  type,
  edits = [],
  autoplay = true,
  loop = false,
  class: className,
  ...rest
} = Astro.props;

const l: FineTuneCardLabels = {
  title: "Card",
  layout: "Layout",
  type: "Type",
  placeholder: "Select type",
  adjust: "Adjust",
  edited: "Edited",
  layouts: ["Row", "Column", "Grid"],
  ...labels,
};

const uid = `vs-ftc-${Math.random().toString(36).slice(2, 8)}`;

/* The no-JS / reduced-motion render shows the agent's edits already applied. */
const initial = {
  layout,
  type: type ?? "",
  values: Object.fromEntries(fields.map((f) => [f.key, f.value])) as Record<string, number>,
};
const final = { layout: initial.layout, type: initial.type, values: { ...initial.values } };
for (const e of edits) {
  if (e.field !== undefined && e.value !== undefined && e.field in final.values) final.values[e.field] = e.value;
  if (e.layout !== undefined) final.layout = e.layout;
  if (e.type !== undefined) final.type = e.type;
}
const changed =
  final.layout !== layout || final.type !== initial.type || fields.some((f) => final.values[f.key] !== f.value);
const pairs: FineTuneField[][] = [];
for (let i = 0; i < fields.length; i += 2) pairs.push(fields.slice(i, i + 2));
const config = { initial, edits, layouts: l.layouts, placeholder: l.placeholder };
---

<div
  class:list={["vs-fine-tune-card", className]}
  data-autoplay={autoplay && edits.length ? "" : undefined}
  data-loop={loop ? "" : undefined}
  data-changed={changed ? "" : undefined}
  {...rest}
>
  <script is:inline type="application/json" data-vs-config set:html={JSON.stringify(config).replace(/</g, "\\u003c")} />

  <div class="bar">
    <span class="title">{l.title}</span>
    <span class="status" aria-live="polite">
      <span class="adjust">
        <span class="spark" aria-hidden="true">
          <svg width="9" height="9" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l2.4 7.2L22 12l-7.6 2.8L12 22l-2.4-7.2L2 12l7.6-2.8z"></path></svg>
        </span>
        <span class="adjust-text">{l.adjust}</span>
      </span>
      <span class="edited">
        <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6L9 17l-5-5"></path></svg>
        {l.edited}
      </span>
    </span>
  </div>

  <div class="section">
    <p class="section-title" id={`${uid}-layout`}>{l.layout}</p>
    <div class="seg" role="radiogroup" aria-labelledby={`${uid}-layout`} style={`--vs-ftc-seg: ${final.layout}`} data-seg>
      <span class="seg-thumb" aria-hidden="true"></span>
      {[0, 1, 2].map((i) => (
        <button
          type="button"
          role="radio"
          class="seg-btn"
          aria-checked={i === final.layout ? "true" : "false"}
          aria-label={l.layouts[i]}
          tabindex={i === final.layout ? 0 : -1}
          data-seg-option={i}
        >
          {i === 0 && <span class="glyph glyph--row" aria-hidden="true"><span></span><span></span><span></span></span>}
          {i === 1 && <span class="glyph glyph--col" aria-hidden="true"><span></span><span></span></span>}
          {i === 2 && <span class="glyph glyph--grid" aria-hidden="true"><span></span><span></span><span></span><span></span></span>}
        </button>
      ))}
      <input type="hidden" name="layout" value={l.layouts[final.layout]} data-seg-input />
    </div>

    {pairs.map((pair) => (
      <div class="pair">
        {pair.map((f) => {
          const id = `${uid}-${f.key}`;
          const v = final.values[f.key] ?? f.value;
          return (
            <div
              class="field"
              data-field={f.key}
              data-default={f.value}
              data-min={f.min}
              data-max={f.max}
              data-step={f.step ?? 1}
              data-suffix={f.suffix ?? ""}
              data-active={v !== f.value ? "" : undefined}
            >
              <label class="handle" for={id} data-handle>
                {f.label}
                {f.name && f.name !== f.label && <span class="vh">, {f.name}</span>}
              </label>
              <input
                class="num"
                id={id}
                name={f.key}
                type="text"
                inputmode="numeric"
                role="spinbutton"
                autocomplete="off"
                aria-valuemin={f.min}
                aria-valuemax={f.max}
                aria-valuenow={v}
                aria-valuetext={f.suffix ? `${v}${f.suffix}` : undefined}
                value={String(v)}
              />
              {f.suffix && <span class="suffix" aria-hidden="true">{f.suffix}</span>}
            </div>
          );
        })}
      </div>
    ))}
  </div>

  <div class="footer">
    <span class="footer-label" id={`${uid}-type`}>{l.type}</span>
    <div class="select" data-select>
      <button
        type="button"
        class="trigger"
        aria-haspopup="listbox"
        aria-expanded="false"
        aria-controls={`${uid}-menu`}
        aria-labelledby={`${uid}-type ${uid}-value`}
        data-trigger
      >
        <span class="value" id={`${uid}-value`} data-placeholder={final.type ? undefined : ""} data-value>{final.type || l.placeholder}</span>
        <svg class="chevron" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9l6 6 6-6"></path></svg>
      </button>
      <input type="hidden" name="type" value={final.type} data-type-input />
      <div class="menu" hidden data-menu>
        <GlideMenu class="menu-list" role="listbox" id={`${uid}-menu`} aria-labelledby={`${uid}-type`} highlightBg="var(--vs-field)" highlightRadius="6px" arrowKeys>
          {options.map((o) => (
            <div class="opt" role="option" tabindex="-1" aria-selected={o === final.type ? "true" : "false"} data-menu-row data-option={o}>
              {o}
            </div>
          ))}
        </GlideMenu>
      </div>
    </div>
  </div>
</div>

<script>
  interface Edit {
    field?: string;
    value?: number;
    layout?: number;
    type?: string;
  }
  interface Config {
    initial: { layout: number; type: string; values: Record<string, number> };
    edits: Edit[];
    layouts: string[];
    placeholder: string;
  }

  const START_DELAY = 600;
  const STEP_GAP = 650;
  const TWEEN_MS = 450;
  const LOOP_HOLD = 4000;

  function init(root: HTMLElement) {
    const config = JSON.parse(root.querySelector("[data-vs-config]")?.textContent || "{}") as Config;
    const fields = [...root.querySelectorAll<HTMLElement>("[data-field]")];
    const seg = root.querySelector<HTMLElement>("[data-seg]")!;
    const segOptions = [...root.querySelectorAll<HTMLButtonElement>("[data-seg-option]")];
    const segInput = root.querySelector<HTMLInputElement>("[data-seg-input]")!;
    const trigger = root.querySelector<HTMLButtonElement>("[data-trigger]")!;
    const valueEl = root.querySelector<HTMLElement>("[data-value]")!;
    const typeInput = root.querySelector<HTMLInputElement>("[data-type-input]")!;
    const menu = root.querySelector<HTMLElement>("[data-menu]")!;
    const opts = [...root.querySelectorAll<HTMLElement>("[data-option]")];
    const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    const loop = root.hasAttribute("data-loop");

    const num = (el: HTMLElement, k: string) => Number(el.dataset[k]);
    const inputOf = (f: HTMLElement) => f.querySelector<HTMLInputElement>(".num")!;
    const fieldByKey = (key: string) => fields.find((f) => f.dataset.field === key);

    let layout = Number(seg.style.getPropertyValue("--vs-ftc-seg")) || 0;
    let type = typeInput.value;
    const values: Record<string, number> = Object.fromEntries(fields.map((f) => [f.dataset.field!, Number(inputOf(f).value)]));

    /* ── state → DOM ── */
    const refreshChanged = () => {
      const changed =
        layout !== config.initial.layout ||
        type !== config.initial.type ||
        fields.some((f) => values[f.dataset.field!] !== num(f, "default"));
      root.toggleAttribute("data-changed", changed);
    };

    const emit = (source: "user" | "agent") => {
      refreshChanged();
      root.dispatchEvent(
        new CustomEvent("vs-fine-tune-change", {
          bubbles: true,
          detail: { layout, layoutName: config.layouts[layout], values: { ...values }, type, source },
        }),
      );
    };

    const clamp = (f: HTMLElement, v: number) => Math.min(num(f, "max"), Math.max(num(f, "min"), Math.round(v)));

    const paintField = (f: HTMLElement, v: number) => {
      const input = inputOf(f);
      if (document.activeElement !== input || input.value !== String(v)) input.value = String(v);
      input.setAttribute("aria-valuenow", String(v));
      if (f.dataset.suffix) input.setAttribute("aria-valuetext", `${v}${f.dataset.suffix}`);
      f.toggleAttribute("data-active", v !== num(f, "default"));
    };

    const setField = (f: HTMLElement, v: number, source: "user" | "agent") => {
      const next = clamp(f, v);
      values[f.dataset.field!] = next;
      paintField(f, next);
      emit(source);
    };

    const paintLayout = (i: number) => {
      layout = Math.min(2, Math.max(0, i));
      seg.style.setProperty("--vs-ftc-seg", String(layout));
      segOptions.forEach((o, j) => {
        o.setAttribute("aria-checked", String(j === layout));
        o.tabIndex = j === layout ? 0 : -1;
      });
      segInput.value = config.layouts[layout] ?? "";
    };

    const paintType = (value: string) => {
      type = value;
      valueEl.textContent = value || config.placeholder;
      valueEl.toggleAttribute("data-placeholder", !value);
      typeInput.value = value;
      opts.forEach((o) => o.setAttribute("aria-selected", String(o.dataset.option === value)));
    };

    const setLayout = (i: number, source: "user" | "agent", focus = false) => {
      paintLayout(i);
      if (focus) segOptions[layout]!.focus();
      emit(source);
    };

    const setType = (value: string, source: "user" | "agent") => {
      paintType(value);
      emit(source);
    };

    /* type menu */
    const onOutside = (event: PointerEvent) => {
      if (!root.querySelector("[data-select]")!.contains(event.target as Node)) closeMenu(false);
    };
    const openMenu = (focus: boolean) => {
      if (!menu.hidden) return;
      menu.hidden = false;
      trigger.setAttribute("aria-expanded", "true");
      document.addEventListener("pointerdown", onOutside);
      if (focus) {
        const target = opts.find((o) => o.getAttribute("aria-selected") === "true") ?? opts[0];
        opts.forEach((o) => (o.tabIndex = o === target ? 0 : -1));
        target?.focus();
      }
    };
    function closeMenu(focusTrigger: boolean) {
      if (menu.hidden) return;
      menu.hidden = true;
      trigger.setAttribute("aria-expanded", "false");
      document.removeEventListener("pointerdown", onOutside);
      if (focusTrigger) trigger.focus();
    }

    trigger.addEventListener("click", () => (menu.hidden ? openMenu(true) : closeMenu(false)));
    trigger.addEventListener("keydown", (event) => {
      if (event.key === "ArrowDown" || event.key === "ArrowUp") {
        event.preventDefault();
        openMenu(true);
      }
    });
    menu.addEventListener("click", (event) => {
      const opt = (event.target as Element).closest<HTMLElement>("[data-option]");
      if (!opt) return;
      setType(opt.dataset.option ?? "", "user");
      closeMenu(true);
    });
    menu.addEventListener("keydown", (event) => {
      const opt = (event.target as Element).closest<HTMLElement>("[data-option]");
      if (event.key === "Escape") {
        event.preventDefault();
        closeMenu(true);
      } else if ((event.key === "Enter" || event.key === " ") && opt) {
        event.preventDefault();
        setType(opt.dataset.option ?? "", "user");
        closeMenu(true);
      } else if (event.key === "Tab") closeMenu(false);
    });

    /* ── layout radio group ── */
    seg.addEventListener("click", (event) => {
      const o = (event.target as Element).closest<HTMLButtonElement>("[data-seg-option]");
      if (o) setLayout(Number(o.dataset.segOption), "user");
    });
    seg.addEventListener("keydown", (event) => {
      let next: number | null = null;
      if (event.key === "ArrowRight" || event.key === "ArrowDown") next = (layout + 1) % 3;
      else if (event.key === "ArrowLeft" || event.key === "ArrowUp") next = (layout + 2) % 3;
      else if (event.key === "Home") next = 0;
      else if (event.key === "End") next = 2;
      if (next === null) return;
      event.preventDefault();
      setLayout(next, "user", true);
    });

    /* ── number fields: scrub, arrows, typing ── */
    fields.forEach((f) => {
      const input = inputOf(f);
      const handle = f.querySelector<HTMLElement>("[data-handle]")!;
      const step = num(f, "step") || 1;
      let drag: { x: number; v: number; moved: boolean } | null = null;

      handle.addEventListener("pointerdown", (event) => {
        if (event.button !== 0) return;
        handle.setPointerCapture(event.pointerId);
        drag = { x: event.clientX, v: values[f.dataset.field!] ?? 0, moved: false };
      });
      handle.addEventListener("pointermove", (event) => {
        if (!drag) return;
        const dx = event.clientX - drag.x;
        if (Math.abs(dx) > 2) drag.moved = true;
        if (drag.moved) setField(f, drag.v + (dx / 2) * step, "user");
      });
      const endDrag = () => {
        if (drag?.moved) handle.dataset.dragged = "";
        drag = null;
      };
      handle.addEventListener("pointerup", endDrag);
      handle.addEventListener("pointercancel", endDrag);
      // A drag should not also focus the input via the label.
      handle.addEventListener("click", (event) => {
        if (handle.hasAttribute("data-dragged")) {
          event.preventDefault();
          delete handle.dataset.dragged;
        }
      });

      input.addEventListener("keydown", (event) => {
        const mult = event.shiftKey ? 10 : 1;
        const v = values[f.dataset.field!] ?? 0;
        if (event.key === "ArrowUp") {
          event.preventDefault();
          setField(f, v + step * mult, "user");
        } else if (event.key === "ArrowDown") {
          event.preventDefault();
          setField(f, v - step * mult, "user");
        } else if (event.key === "PageUp") {
          event.preventDefault();
          setField(f, v + step * 10, "user");
        } else if (event.key === "PageDown") {
          event.preventDefault();
          setField(f, v - step * 10, "user");
        } else if (event.key === "Enter") {
          input.blur();
        }
      });
      input.addEventListener("input", () => {
        const clean = input.value.replace(/[^\d-]/g, "");
        if (clean !== input.value) input.value = clean;
        const n = Number(clean);
        // Live-apply while the typed value is in range; clamp on commit.
        if (clean !== "" && clean !== "-" && n >= num(f, "min") && n <= num(f, "max")) {
          values[f.dataset.field!] = n;
          input.setAttribute("aria-valuenow", String(n));
          f.toggleAttribute("data-active", n !== num(f, "default"));
          emit("user");
        }
      });
      input.addEventListener("change", () => {
        const n = Number(input.value);
        setField(f, Number.isFinite(n) && input.value !== "" ? n : values[f.dataset.field!] ?? 0, "user");
        input.value = String(values[f.dataset.field!]);
      });
      // Clicking the chip's padding focuses its input.
      f.addEventListener("click", (event) => {
        if (event.target === f) input.focus();
      });
    });

    /* ── the agent ── */
    let timers: number[] = [];
    let raf = 0;
    let touched = false;
    let visible = false;
    let running = false;
    const later = (ms: number, fn: () => void) => timers.push(window.setTimeout(fn, ms));
    const stop = () => {
      timers.forEach((t) => window.clearTimeout(t));
      timers = [];
      cancelAnimationFrame(raf);
      running = false;
      root.removeAttribute("data-agent");
    };

    const tween = (f: HTMLElement, to: number) => {
      const from = values[f.dataset.field!] ?? 0;
      const t0 = performance.now();
      const frame = (now: number) => {
        const t = Math.min(1, (now - t0) / TWEEN_MS);
        const eased = 1 - Math.pow(1 - t, 3);
        values[f.dataset.field!] = clamp(f, from + (to - from) * eased);
        paintField(f, values[f.dataset.field!]!);
        if (t < 1) raf = requestAnimationFrame(frame);
        else emit("agent");
      };
      raf = requestAnimationFrame(frame);
    };

    const reset = () => {
      fields.forEach((f) => {
        values[f.dataset.field!] = config.initial.values[f.dataset.field!] ?? num(f, "default");
        paintField(f, values[f.dataset.field!]!);
      });
      paintLayout(config.initial.layout);
      paintType(config.initial.type);
      refreshChanged();
    };

    const play = () => {
      stop();
      reset();
      running = true;
      root.setAttribute("data-agent", "");
      let at = START_DELAY;
      for (const edit of config.edits) {
        if (edit.field !== undefined && edit.value !== undefined) {
          const f = fieldByKey(edit.field);
          if (f) later(at, () => tween(f, edit.value!));
          at += TWEEN_MS + STEP_GAP;
        }
        if (edit.layout !== undefined) {
          const i = edit.layout;
          later(at, () => setLayout(i, "agent"));
          at += 300 + STEP_GAP;
        }
        if (edit.type !== undefined) {
          const value = edit.type;
          const opt = opts.find((o) => o.dataset.option === value);
          later(at, () => openMenu(false));
          if (opt) later(at + 350, () => opt.dispatchEvent(new MouseEvent("mouseover", { bubbles: true })));
          later(at + 800, () => {
            setType(value, "agent");
            closeMenu(false);
          });
          at += 800 + STEP_GAP;
        }
      }
      later(at, () => {
        running = false;
        root.removeAttribute("data-agent");
        if (loop) later(LOOP_HOLD, again);
      });
    };

    function again() {
      if (touched || !visible) return;
      if (root.matches(":hover") || root.contains(document.activeElement)) return later(LOOP_HOLD, again);
      play();
    }

    // Any hands-on interaction takes over from the agent, keeping what it has done so far.
    const takeOver = () => {
      if (touched) return;
      touched = true;
      stop();
      refreshChanged();
    };
    root.addEventListener("pointerdown", (event) => event.isTrusted && takeOver());
    root.addEventListener("keydown", (event) => event.isTrusted && takeOver());

    refreshChanged();
    if (reduce || !root.hasAttribute("data-autoplay")) return;

    // Start from the untouched card; the agent works once it is on screen.
    reset();
    let started = false;
    const io = new IntersectionObserver(
      ([entry]) => {
        if (!root.isConnected) return teardown();
        visible = Boolean(entry?.isIntersecting);
        if (visible && !started && !touched) {
          started = true;
          play();
        } else if (visible && started && !running && loop && !touched) {
          later(LOOP_HOLD, again);
        }
      },
      { threshold: 0.4 },
    );
    io.observe(root);

    function teardown() {
      stop();
      io.disconnect();
      document.removeEventListener("pointerdown", onOutside);
    }
    document.addEventListener("astro:before-swap", teardown, { once: true });
  }

  const boot = () =>
    document.querySelectorAll<HTMLElement>(".vs-fine-tune-card:not([data-vs-ready])").forEach((root) => {
      root.dataset.vsReady = "";
      init(root);
    });
  boot();
  document.addEventListener("astro:page-load", boot);
</script>

<style>
  .vs-fine-tune-card {
    position: relative;
    width: 100%;
    max-width: 240px;
    border-radius: var(--vs-radius-card);
    background: var(--vs-surface);
    box-shadow: var(--vs-shadow-raised);
  }
  .vh {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }

  /* ── header ── */
  .bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 12px;
    border-bottom: 1px solid var(--vs-line);
  }
  .title {
    font-size: 13px;
    font-weight: 500;
    color: var(--vs-ink);
  }
  .status {
    display: flex;
  }
  .adjust,
  .edited {
    display: flex;
    align-items: center;
    gap: 6px;
  }
  .edited {
    display: none;
    font-size: 12px;
    font-weight: 500;
    color: var(--vs-green);
    animation: vs-pop-in 250ms var(--vs-ease-out) both;
  }
  [data-changed]:not([data-agent]) .adjust {
    display: none;
  }
  [data-changed]:not([data-agent]) .edited {
    display: flex;
  }
  .spark {
    display: flex;
    width: 18px;
    height: 18px;
    align-items: center;
    justify-content: center;
    border: 1px solid color-mix(in srgb, var(--vs-accent) 30%, transparent);
    border-radius: 5px;
    background: var(--vs-accent-tint);
    color: var(--vs-accent-ink);
    box-sizing: border-box;
  }
  .adjust-text {
    background-image: linear-gradient(90deg, var(--vs-accent) 35%, var(--vs-accent-ink) 50%, var(--vs-accent) 65%);
    background-size: 200% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    font-size: 12px;
    font-weight: 500;
    animation: vs-shimmer-text 1.4s linear infinite;
  }

  /* ── layout section ── */
  .section {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px;
    border-bottom: 1px solid var(--vs-line);
  }
  .section-title {
    margin: 0;
    font-size: 12.5px;
    font-weight: 500;
    color: var(--vs-ink);
  }
  .seg {
    position: relative;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    padding: 2px;
    border-radius: var(--vs-radius-control);
    background: var(--vs-field);
  }
  .seg-thumb {
    position: absolute;
    top: 2px;
    bottom: 2px;
    left: 2px;
    width: calc((100% - 4px) / 3);
    border-radius: 6px;
    background: var(--vs-surface);
    box-shadow: var(--vs-shadow-btn);
    transform: translateX(calc(var(--vs-ftc-seg, 0) * 100%));
    transition: transform 300ms var(--vs-ease-out);
  }
  .seg-btn {
    position: relative;
    z-index: 1;
    display: flex;
    height: 24px;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--vs-ink-3);
    cursor: pointer;
    transition: color 200ms ease-out;
  }
  .seg-btn[aria-checked="true"] {
    color: var(--vs-accent-ink);
  }
  .glyph {
    display: flex;
    gap: 2px;
  }
  .glyph--col {
    flex-direction: column;
  }
  .glyph--grid {
    display: grid;
    grid-template-columns: repeat(2, auto);
  }
  .glyph span {
    width: 6px;
    height: 6px;
    box-sizing: border-box;
    border: 1.2px solid currentColor;
    border-radius: 2px;
  }

  .pair {
    display: grid;
    min-width: 0;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
  }
  .field {
    display: flex;
    min-width: 0;
    height: 26px;
    box-sizing: border-box;
    align-items: center;
    gap: 4px;
    padding: 4px 4px 4px 2px;
    border-radius: var(--vs-radius-chip);
    background: var(--vs-field);
    cursor: text;
    transition:
      background-color 200ms ease-out,
      box-shadow 200ms ease-out;
  }
  .field[data-active] {
    background: var(--vs-accent-tint);
    box-shadow: 0 0 0 1px var(--vs-accent);
  }
  .field:has(.num:focus-visible) {
    box-shadow: var(--vs-focus-ring);
  }
  .handle {
    display: flex;
    height: 100%;
    flex-shrink: 0;
    align-items: center;
    padding: 0 2px;
    border-radius: 4px;
    color: var(--vs-ink-3);
    font-size: 12px;
    cursor: ew-resize;
    touch-action: none;
    user-select: none;
    transition: color var(--vs-duration-fast) ease-out;
  }
  .handle:hover {
    color: var(--vs-ink-2);
  }
  .field:has(.num:focus-visible) .handle {
    color: var(--vs-accent-ink);
  }
  .num {
    min-width: 0;
    flex: 1;
    padding: 0;
    border: none;
    background: transparent;
    color: var(--vs-ink);
    font: inherit;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    outline: none;
  }
  .suffix {
    flex-shrink: 0;
    padding-right: 2px;
    font-size: 11.5px;
    color: var(--vs-ink-3);
  }

  /* type menu */
  .footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px;
  }
  .footer-label {
    font-size: 12px;
    color: var(--vs-ink-3);
  }
  .select {
    position: relative;
    width: 120px;
    margin-right: -2px;
  }
  .trigger {
    display: flex;
    width: 100%;
    height: 26px;
    align-items: center;
    justify-content: space-between;
    padding: 4px 4px 4px 8px;
    border: none;
    border-radius: var(--vs-radius-chip);
    background: var(--vs-inset);
    box-shadow: var(--vs-shadow-hairline);
    font: inherit;
    cursor: pointer;
    transition: box-shadow 200ms ease-out;
  }
  .trigger[aria-expanded="true"] {
    box-shadow: 0 0 0 1px var(--vs-accent);
  }
  .trigger:focus-visible {
    outline: none;
    box-shadow: var(--vs-focus-ring);
  }
  .value {
    font-size: 12px;
    color: var(--vs-ink);
  }
  .value[data-placeholder] {
    color: var(--vs-ink-3);
  }
  .chevron {
    color: var(--vs-ink-3);
    transition: transform 200ms ease-out;
  }
  .trigger[aria-expanded="true"] .chevron {
    transform: rotate(180deg);
  }
  .menu {
    position: absolute;
    right: 0;
    bottom: 32px;
    z-index: 10;
    width: 120px;
    box-sizing: border-box;
    padding: 4px;
    border-radius: 10px;
    background: var(--vs-surface);
    box-shadow: var(--vs-shadow-raised);
    transform-origin: bottom right;
    animation: vs-pop-in 200ms var(--vs-ease-out) both;
  }
  .menu-list {
    display: flex;
    flex-direction: column;
    gap: 1px;
  }
  .opt {
    position: relative;
    z-index: 1;
    display: flex;
    height: 26px;
    align-items: center;
    padding: 0 8px;
    border-radius: 6px;
    color: var(--vs-ink);
    font-size: 12.5px;
    cursor: pointer;
    outline: none;
  }
  .opt[aria-selected="true"] {
    background: var(--vs-field);
  }
  .menu-list[data-glide-active] .opt[aria-selected="true"]:not([data-glide-current]) {
    background: transparent;
  }
  .opt:focus-visible {
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--vs-accent) 60%, transparent);
  }

  @media (prefers-reduced-motion: reduce) {
    .adjust-text {
      animation: none;
      background: none;
      color: var(--vs-accent-ink);
    }
    .edited,
    .menu {
      animation: none;
    }
    .seg-thumb,
    .chevron,
    .field {
      transition: none;
    }
  }
  @media (forced-colors: active) {
    .seg-thumb {
      outline: 2px solid Highlight;
    }
    .field[data-active] {
      outline: 1px solid Highlight;
    }
  }
</style>
```
