# Fine-tune card

> A small inspector in which an agent adjusts a design’s layout, size, radius, opacity and type, and the reader can take over.

An inspector card for one design. While the agent works, its Adjust label shimmers; it picks a layout, counts each number to a new value and chooses a type from a menu, and the header turns to a green Edited. Every control works by hand too: drag a field’s name to scrub it, use the arrow keys or type the number, and each field you change keeps a blue tint so the edits read at a glance.

A React component by Bruno Delorence: https://brunodelorence.com/components/fine-tune-card. MIT License, Copyright (c) 2026 Bruno Delorence.

Category: AI.

## Anatomy

1. **Card**: A 240 px inspector on a soft stage. Its name sits at the left of the header.
2. **Status**: The agent’s words, shimmering every 1.4 s while it works; a green Edited that pops in once anything has changed.
3. **Layout control**: Row, column or grid as a radio group. Arrow keys move the choice.
4. **Thumb**: The raised piece under the choice; it slides by a transform in 300 ms.
5. **Number field**: A number with its name as a handle. Once changed it keeps a blue tint and ring.
6. **Handle**: The field’s name is a slider: drag it sideways to scrub half a unit a pixel, or use the arrow keys, Shift for tens.
7. **Type select**: A button that opens a small menu upwards; it reads the placeholder until a type is picked.

## When to use

- An agent adjusting a design’s properties where the reader can watch each value change and take over at any moment.
- A handful of numbers and choices on one selected thing: layout, size, corner radius, opacity, a type.
- Precise tweaks by feel: dragging a field’s name scrubs its number, arrow keys step it, typing sets it.
- Showing at a glance what was changed: edited fields keep a blue tint and ring, and the header turns to Edited.

## When not to use

- A full inspector with dozens of properties in groups. Use a panel with collapsible sections.
- Values without a sensible range or step, such as names or free text. Use a form.
- An agent that must ask before it changes anything. Use an approval card that waits for an answer.
- Settings that apply to the whole app rather than one selected object. Use a settings page.

## Installation

Install it with the shadcn CLI, into components/fine-tune-card of any React project:

```sh
npx shadcn@latest add https://brunodelorence.com/r/fine-tune-card.json
```

Or copy all the source files below into one folder of your project. Either way it needs React 19 and a bundler with CSS Modules (Next.js, Vite).

It has a dark colour scheme. Its colours are CSS light-dark() pairs, kept inside @supports so that builds which rewrite light-dark() for older browsers leave them alone: it follows your page’s color-scheme (light unless the page sets one) or its colorScheme prop, with nothing to configure. Browsers without light-dark() show it light.

## Usage

```tsx
import { FineTuneCard, type FineTuneValues } from './fine-tune-card';

export function CardInspector({ onEdit }: { onEdit: (values: FineTuneValues) => void }) {
  return (
    <FineTuneCard
      title="Hero banner"
      defaultValues={{ layout: 'row', width: 640, height: 240, radius: 16, opacity: 100 }}
      typeOptions={['Promo', 'Editorial', 'Product']}
      onChange={onEdit}
      autoplay={false}
    />
  );
}
```

## States

- **Untouched**: The starting values, and the agent’s words in the header. Their shimmer keeps running.
- **Agent writing**: The layout is set and the width is counting: its handle turns blue and the field takes the ring.
- **Type menu open**: The four numbers are done; the menu pops up from its corner with the highlight on a row.
- **Edited**: Every change in: the changed fields keep their ring and the header says Edited.
- **Changed by the reader**: One field edited by hand: only it is ringed, and the header turns to Edited at once.
- **Dark**: With `colorScheme="dark"`: a graphite stage, the card a step lighter.

## How it works

- Layout is a radio group drawn as a segmented control: one raised thumb, a third of the well wide, slides under the choice by a transform (300 ms on a soft ease-out) while the icons cross from grey to blue in 200 ms. The arrow keys move the choice.
- Each number is a field whose name is its handle, a slider in its own right. Dragging the name sideways scrubs the value at half a unit per pixel, with the pointer captured so the drag can leave the card; the arrows step by one, Shift by ten, Home and End go to the ends.
- The number itself is a text field that keeps only digits while typing, and commits on Enter or on leaving, clamped to the field’s range; Escape puts the value back.
- A field once changed keeps an accent tint and a 1 px accent ring, crossing in 200 ms, so the card shows what has been edited. The first change turns the header from the shimmering Adjust to a green Edited that pops in (250 ms, from 95 %).
- The shimmer is a gradient, accent to a lighter accent and back, twice the width of the words and clipped to them, running right to left every 1.4 s.
- Type is a listbox that opens upwards from its button, popping from its bottom right corner in 200 ms while the chevron turns over. A soft highlight glides to the row under the pointer or the focus by its top and height (220 ms), and fades when the pointer leaves. It closes on a pick, Escape or a press outside.
- While on screen and untouched, a demo plays the agent: it picks the grid layout, counts width, height, radius and opacity to new values one after another on an ease-in-out curve, the name of the field it writes lit in blue, opens the menu, walks the highlight down and picks a type, then marks the card Edited, waits three seconds and starts over. Its edits never reach onChange; any key or press hands the card to the reader.
- Every colour is a light-dark() token on the root, so the card follows the page’s color-scheme or the colorScheme prop. Shadows are written once with all their layers, those a scheme does not use being transparent: long soft lifts by day, short dark ones at night.

## API reference

- **title** (`string`, default `'Flavor card'`): The name of what is being inspected, in the header.
- **defaultValues** (`Partial<FineTuneValues>`, default `{ layout: 'row', width: 324, height: 96, radius: 28, opacity: 100, type: null }`): Where the card starts, and where the demo returns to: layout (row, column or grid), width, height, radius, opacity and type.
- **defaultState** (`FineTuneState`): Opens the card mid-way: edited (the header reads the edited label), touched (the fields drawn as changed), busy (the field the agent is writing), open (the Type menu) and highlight (its lit row). Setting it turns the demo off.
- **onChange** (`(values: FineTuneValues) => void`): Called with every value after each change the reader makes.
- **typeOptions** (`string[]`, default `['Seasonal', 'Classic', 'Limited']`): The choices in the Type menu.
- **typePlaceholder** (`string`, default `'Select type'`): The Type button’s words before a choice.
- **demoValues** (`Partial<FineTuneValues>`, default `{ layout: 'grid', width: 360, height: 120, radius: 16, opacity: 90, type: 'Limited' }`): What the demo agent sets.
- **agentLabel** (`string`, default `'Adjust'`): The header’s shimmering words while the agent works.
- **editedLabel** (`string`, default `'Edited'`): The header’s words once something has changed.
- **autoplay** (`boolean`, default `true`): While on screen and untouched, a demo agent adjusts the card by itself and starts over.
- **colorScheme** (`'light' | 'dark' | 'system'`): Its colours: light, dark, or the reader’s system setting. Without it the component follows the page’s color-scheme, which is light unless the page sets one.
- **className** (`string`): Extra class for the root.

## Accessibility

- The card is a section named by its heading. Layout is a radiogroup of three radio buttons named Row, Column and Grid, with one tab stop and the arrow keys to move.
- Each field’s name is a slider (aria-valuenow, min and max; Opacity also says its percent) that the arrows, Shift, Home and End move; its number is a text field named “W value” and so on, with the up and down arrows stepping too.
- Type is a button with aria-haspopup="listbox" and aria-expanded; its listbox of options with aria-selected opens on a press or the arrow keys, takes the focus to the chosen row, moves with the arrows, Home and End, and closes on Escape, giving the focus back.
- The first change the reader makes is announced politely (“Flavor card: edited”); the demo’s edits are not. Focus rings are visible on every control, the select ringing in the accent.
- Under reduced motion the shimmer stands still, the thumb, tints, menu and highlight change at once, and the demo never plays.

## Source

### fine-tune-card/fine-tune-card.tsx

```tsx
// Copyright (c) 2026 Bruno Delorence. MIT License.

'use client';

import {
  useEffect,
  useId,
  useLayoutEffect,
  useRef,
  useState,
  type KeyboardEvent as ReactKeyboardEvent,
  type PointerEvent as ReactPointerEvent,
} from 'react';
import { useAutoplay } from './use-autoplay';
import styles from './fine-tune-card.module.css';

/*
 * Fine-tune card: a small inspector in which an agent adjusts a design's properties (its layout,
 * size, corner radius, opacity and type), and the reader can take over at any time.
 *
 * - The header says what is going on: "Adjust", its words shimmering, while the agent works; a
 *   green "Edited" that pops in once something has changed.
 * - Layout is a three-way segmented control; the raised thumb slides under the choice by a
 *   transform (300 ms, a soft ease-out) while the icons cross their colours.
 * - Each number is a field with its name as a handle: drag the name sideways to scrub (half a
 *   unit per pixel), use the arrow keys on it (Shift for tens), or type in the number. A field
 *   once changed keeps a blue tint and ring, so the edits read at a glance.
 * - Type is a small menu that opens upwards; a soft highlight glides between its rows.
 * - While on screen and untouched, a demo plays the agent: it picks a layout, counts each number
 *   to a new value, opens the menu, hovers and picks a type, then marks the card edited, waits and
 *   starts over. Any real input hands the card to the reader.
 * - defaultState opens it mid-way (fields changed, one being written, the menu open on a row,
 *   the header edited), for documentation and tests; it turns the demo off.
 * - Light or dark: every colour is a light-dark() token in the CSS, so the scheme is the page's
 *   color-scheme, or the one the colorScheme prop sets.
 */

export type FineTuneLayout = 'row' | 'column' | 'grid';

export type FineTuneValues = {
  layout: FineTuneLayout;
  width: number;
  height: number;
  radius: number;
  opacity: number;
  /** The chosen type, or null before one is chosen. */
  type: string | null;
};

export type FineTuneField = 'width' | 'height' | 'radius' | 'opacity';
type NumberKey = FineTuneField;

/** Where the card opens (defaultState): for documentation and tests. */
export type FineTuneState = {
  /** The header says the edited label instead of the agent's. */
  edited?: boolean;
  /** The fields drawn as changed: a blue tint and ring. */
  touched?: FineTuneField[];
  /** The field the agent is writing: its handle in the accent. */
  busy?: FineTuneField | null;
  /** The Type menu open. */
  open?: boolean;
  /** The menu row under the highlight. */
  highlight?: number | null;
};

const DEFAULTS: FineTuneValues = {
  layout: 'row',
  width: 324,
  height: 96,
  radius: 28,
  opacity: 100,
  type: null,
};

/** What the demo agent sets, in this order. */
const DEMO: FineTuneValues = {
  layout: 'grid',
  width: 360,
  height: 120,
  radius: 16,
  opacity: 90,
  type: 'Limited',
};

const TYPES = ['Seasonal', 'Classic', 'Limited'];

const FIELDS: { key: NumberKey; label: string; min: number; max: number; unit?: string }[] = [
  { key: 'width', label: 'W', min: 40, max: 999 },
  { key: 'height', label: 'H', min: 24, max: 999 },
  { key: 'radius', label: 'Radius', min: 0, max: 64 },
  { key: 'opacity', label: 'Opacity', min: 0, max: 100, unit: '%' },
];

const LAYOUTS: { value: FineTuneLayout; label: string }[] = [
  { value: 'row', label: 'Row' },
  { value: 'column', label: 'Column' },
  { value: 'grid', label: 'Grid' },
];

/** Each layout's icon. */
const ICONS: Record<FineTuneLayout, string> = {
  row: styles.iconRow,
  column: styles.iconColumn,
  grid: styles.iconGrid,
};

/** Scrubbing: units per pixel the handle is dragged. */
const SCRUB = 0.5;

const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));
/** The demo's counting curve: slow out, fast through the middle, slow in. */
const easeInOut = (t: number) => (t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2);

/** Thrown into the demo's script when it is stopped. */
const STOP = Symbol('stop');

export function FineTuneCard({
  title = 'Flavor card',
  defaultValues,
  demoValues,
  typeOptions = TYPES,
  typePlaceholder = 'Select type',
  agentLabel = 'Adjust',
  editedLabel = 'Edited',
  onChange,
  autoplay = true,
  defaultState,
  colorScheme,
  className = '',
}: {
  /** The name of what is being inspected, in the header. */
  title?: string;
  /** The values the card starts from (and the demo returns to). */
  defaultValues?: Partial<FineTuneValues>;
  /** The values the demo agent sets. */
  demoValues?: Partial<FineTuneValues>;
  /** The Type menu's choices. */
  typeOptions?: string[];
  typePlaceholder?: string;
  /** The header's words while the agent works. */
  agentLabel?: string;
  /** The header's words once something has changed. */
  editedLabel?: string;
  /** Called with every value after each change the reader makes. */
  onChange?: (values: FineTuneValues) => void;
  /** While on screen and untouched, a demo agent adjusts the card by itself. */
  autoplay?: boolean;
  /** Opens the card in a given state, with the demo off. */
  defaultState?: FineTuneState;
  /** Light or dark colours, or the reader's ('system'); without it, the page's color-scheme. */
  colorScheme?: 'light' | 'dark' | 'system';
  className?: string;
}) {
  const start: FineTuneValues = { ...DEFAULTS, ...defaultValues };
  const target: FineTuneValues = { ...DEMO, ...demoValues };
  const { ref: root, playing, take } = useAutoplay<HTMLDivElement>(6000);
  const [values, setValues] = useState(start);
  const [touched, setTouched] = useState<Partial<Record<NumberKey, boolean>>>(() =>
    Object.fromEntries((defaultState?.touched ?? []).map((key) => [key, true])),
  );
  const [edited, setEdited] = useState(defaultState?.edited ?? false);
  /** Bumped when the header returns to the agent's words, so they pop in again. */
  const [round, setRound] = useState(0);
  /** The field the demo agent is writing. */
  const [busy, setBusy] = useState<NumberKey | null>(defaultState?.busy ?? null);
  const [drafts, setDrafts] = useState<Partial<Record<NumberKey, string>>>({});
  const [open, setOpen] = useState(defaultState?.open ?? false);
  /** The menu row under the highlight, or null when it is hidden. */
  const [lit, setLit] = useState<number | null>(defaultState?.highlight ?? null);
  const [glide, setGlide] = useState({ top: 0, height: 0 });
  const [message, setMessage] = useState('');
  const rows = useRef<(HTMLButtonElement | null)[]>([]);
  const selectRef = useRef<HTMLDivElement>(null);
  const buttonRef = useRef<HTMLButtonElement>(null);
  const valuesRef = useRef(values);
  valuesRef.current = values;
  const editedRef = useRef(edited);
  editedRef.current = edited;
  const id = useId();

  const enabled = autoplay && playing && !defaultState;
  const starting = useRef(start);
  starting.current = start;
  const aim = useRef(target);
  aim.current = target;
  const options = useRef(typeOptions);
  options.current = typeOptions;

  /** A change the reader made: it marks the card edited and is reported. */
  const commit = (next: FineTuneValues, field?: NumberKey) => {
    setValues(next);
    if (field) setTouched((was) => ({ ...was, [field]: true }));
    if (!edited) setMessage(`${title}: ${editedLabel.toLowerCase()}`);
    setEdited(true);
    onChange?.(next);
  };
  const setNumber = (key: NumberKey, value: number) => {
    const field = FIELDS.find((f) => f.key === key)!;
    const next = clamp(Math.round(value), field.min, field.max);
    if (next === valuesRef.current[key]) return;
    commit({ ...valuesRef.current, [key]: next }, key);
  };

  // The demo agent: one pass of edits, a pause, and back to the start, for as long as it plays.
  useEffect(() => {
    if (!enabled) return;
    let stopped = false;
    let frame = 0;
    const timers: number[] = [];
    const wait = (ms: number) =>
      new Promise<void>((resolve, reject) => {
        timers.push(window.setTimeout(() => (stopped ? reject(STOP) : resolve()), ms));
      });
    const count = (key: NumberKey, to: number, duration: number) =>
      new Promise<void>((resolve, reject) => {
        const from = valuesRef.current[key];
        const began = performance.now();
        setBusy(key);
        setTouched((was) => ({ ...was, [key]: true }));
        const tick = (now: number) => {
          if (stopped) return reject(STOP);
          const t = Math.min(1, (now - began) / duration);
          setValues((was) => ({ ...was, [key]: Math.round(from + (to - from) * easeInOut(t)) }));
          if (t < 1) frame = requestAnimationFrame(tick);
          else {
            setBusy(null);
            resolve();
          }
        };
        frame = requestAnimationFrame(tick);
      });
    const reset = () => {
      setValues(starting.current);
      setTouched({});
      setDrafts({});
      setOpen(false);
      setLit(null);
      setBusy(null);
      if (editedRef.current) setRound((n) => n + 1);
      setEdited(false);
    };
    const script = async () => {
      const goal = aim.current;
      const list = options.current;
      for (;;) {
        reset();
        await wait(1200);
        setValues((was) => ({ ...was, layout: goal.layout }));
        await wait(700);
        await count('width', goal.width, 700);
        await wait(300);
        await count('height', goal.height, 500);
        await wait(300);
        await count('radius', goal.radius, 600);
        await wait(300);
        await count('opacity', goal.opacity, 500);
        await wait(500);
        const pick = Math.max(0, list.indexOf(goal.type ?? ''));
        setOpen(true);
        await wait(400);
        for (let row = 0; row <= pick; row++) {
          setLit(row);
          await wait(row === pick ? 450 : 380);
        }
        setValues((was) => ({ ...was, type: list[pick] ?? null }));
        setOpen(false);
        setLit(null);
        await wait(400);
        setEdited(true);
        await wait(3000);
      }
    };
    script().catch((error) => {
      if (error !== STOP) throw error;
    });
    return () => {
      stopped = true;
      cancelAnimationFrame(frame);
      timers.forEach(window.clearTimeout);
      setBusy(null);
      setOpen(false);
      setLit(null);
    };
  }, [enabled]);

  // The highlight follows its row: offsets are read from the layout.
  useLayoutEffect(() => {
    if (lit === null) return;
    const row = rows.current[lit];
    if (row) setGlide({ top: row.offsetTop, height: row.offsetHeight });
  }, [lit, open]);

  // An open menu closes on a press outside it.
  useEffect(() => {
    if (!open) return;
    const close = (event: PointerEvent) => {
      if (!selectRef.current?.contains(event.target as Node)) setOpen(false);
    };
    document.addEventListener('pointerdown', close);
    return () => document.removeEventListener('pointerdown', close);
  }, [open]);

  const openMenu = (focusRow: boolean) => {
    const chosen = values.type ? typeOptions.indexOf(values.type) : -1;
    setOpen(true);
    setLit(chosen >= 0 ? chosen : focusRow ? 0 : null);
    if (focusRow) requestAnimationFrame(() => rows.current[Math.max(0, chosen)]?.focus());
  };
  const closeMenu = (refocus: boolean) => {
    setOpen(false);
    setLit(null);
    if (refocus) buttonRef.current?.focus();
  };
  const choose = (index: number) => {
    const type = typeOptions[index];
    closeMenu(true);
    if (type !== values.type) commit({ ...values, type });
  };

  const onButtonKey = (event: ReactKeyboardEvent<HTMLButtonElement>) => {
    if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
      event.preventDefault();
      openMenu(true);
    } else if (event.key === 'Escape' && open) {
      event.preventDefault();
      closeMenu(false);
    }
  };
  const onMenuKey = (event: ReactKeyboardEvent<HTMLDivElement>) => {
    const last = typeOptions.length - 1;
    const now = lit ?? 0;
    const go = (row: number) => {
      event.preventDefault();
      setLit(row);
      rows.current[row]?.focus();
    };
    if (event.key === 'ArrowDown') go(now >= last ? 0 : now + 1);
    else if (event.key === 'ArrowUp') go(now <= 0 ? last : now - 1);
    else if (event.key === 'Home') go(0);
    else if (event.key === 'End') go(last);
    else if (event.key === 'Enter' || event.key === ' ') {
      event.preventDefault();
      choose(now);
    } else if (event.key === 'Escape' || event.key === 'Tab') {
      if (event.key === 'Escape') event.preventDefault();
      closeMenu(event.key === 'Escape');
    }
  };

  const onLayoutKey = (event: ReactKeyboardEvent<HTMLDivElement>) => {
    const index = LAYOUTS.findIndex((l) => l.value === values.layout);
    const step =
      event.key === 'ArrowRight' || event.key === 'ArrowDown'
        ? 1
        : event.key === 'ArrowLeft' || event.key === 'ArrowUp'
          ? -1
          : 0;
    if (!step) return;
    event.preventDefault();
    const next = (index + step + LAYOUTS.length) % LAYOUTS.length;
    commit({ ...values, layout: LAYOUTS[next].value });
    (
      event.currentTarget.querySelectorAll('button')[next] as HTMLButtonElement | undefined
    )?.focus();
  };

  return (
    <div
      ref={root}
      className={`${styles.root} ${className}`}
      data-color-scheme={colorScheme}
      onPointerDownCapture={take}
      onKeyDownCapture={take}
    >
      <div className={styles.column}>
        <section className={styles.card} aria-labelledby={`${id}-title`} data-part="card">
          <div className={styles.bar} data-part="header">
            <h3 id={`${id}-title`} className={styles.title}>
              {title}
            </h3>
            {edited ? (
              <span className={styles.edited} data-part="status">
                <svg width="10" height="10" viewBox="0 0 10 10" aria-hidden="true">
                  <path d="M1.9 5.3 4.1 7.4 8.2 2.6" />
                </svg>
                {editedLabel}
              </span>
            ) : (
              <span
                key={round}
                className={`${styles.agent} ${round ? styles.pop : ''}`}
                data-part="status"
              >
                <span className={styles.spark} aria-hidden="true">
                  <svg width="9" height="9" viewBox="0 0 10 10">
                    <path d="M5 .55 6.05 3.95 9.45 5 6.05 6.05 5 9.45 3.95 6.05.55 5 3.95 3.95Z" />
                  </svg>
                </span>
                <span className={styles.adjust}>{agentLabel}</span>
              </span>
            )}
          </div>

          <div className={styles.pad}>
            <p className={styles.section} id={`${id}-layout`}>
              Layout
            </p>
            <div
              className={styles.segmented}
              role="radiogroup"
              aria-labelledby={`${id}-layout`}
              data-part="layout"
              onKeyDown={onLayoutKey}
            >
              <span
                className={styles.thumb}
                aria-hidden="true"
                data-part="thumb"
                style={{
                  transform: `translateX(${LAYOUTS.findIndex((l) => l.value === values.layout) * 100}%)`,
                }}
              />
              {LAYOUTS.map((layout) => {
                const on = layout.value === values.layout;
                return (
                  <button
                    key={layout.value}
                    type="button"
                    role="radio"
                    aria-checked={on}
                    aria-label={layout.label}
                    tabIndex={on ? 0 : -1}
                    className={`${styles.segment} ${on ? styles.on : ''}`}
                    onClick={() => !on && commit({ ...values, layout: layout.value })}
                  >
                    <span className={`${styles.icon} ${ICONS[layout.value]}`} aria-hidden="true">
                      {Array.from(
                        { length: layout.value === 'row' ? 3 : layout.value === 'column' ? 2 : 4 },
                        (_, i) => (
                          <span key={i} />
                        ),
                      )}
                    </span>
                  </button>
                );
              })}
            </div>
            {[FIELDS.slice(0, 2), FIELDS.slice(2)].map((pair, line) => (
              <div className={styles.pair} key={line}>
                {pair.map((field) => (
                  <NumberField
                    key={field.key}
                    label={field.label}
                    min={field.min}
                    max={field.max}
                    unit={field.unit}
                    value={values[field.key]}
                    draft={drafts[field.key]}
                    touched={!!touched[field.key]}
                    busy={busy === field.key}
                    onDraft={(text) => setDrafts((was) => ({ ...was, [field.key]: text }))}
                    onValue={(value) => setNumber(field.key, value)}
                  />
                ))}
              </div>
            ))}
          </div>

          <div className={styles.footer}>
            <span className={styles.typeLabel} id={`${id}-type`}>
              Type
            </span>
            <div className={styles.select} ref={selectRef}>
              <button
                ref={buttonRef}
                type="button"
                className={`${styles.selectButton} ${open ? styles.open : ''}`}
                data-part="select"
                aria-haspopup="listbox"
                aria-expanded={open}
                aria-controls={open ? `${id}-menu` : undefined}
                aria-labelledby={`${id}-type ${id}-value`}
                // Opened from the keyboard (Enter or Space: a click of no detail), the menu takes focus.
                onClick={(event) => (open ? closeMenu(false) : openMenu(event.detail === 0))}
                onKeyDown={onButtonKey}
              >
                <span
                  id={`${id}-value`}
                  className={values.type ? styles.value : styles.placeholder}
                >
                  {values.type ?? typePlaceholder}
                </span>
                <svg
                  className={styles.chevron}
                  width="11"
                  height="11"
                  viewBox="0 0 11 11"
                  aria-hidden="true"
                >
                  <path d="M3 4.3 5.5 6.8 8 4.3" />
                </svg>
              </button>
              {open && (
                <div className={styles.menu} data-part="menu">
                  <div
                    id={`${id}-menu`}
                    className={styles.rows}
                    role="listbox"
                    aria-labelledby={`${id}-type`}
                    onKeyDown={onMenuKey}
                    onPointerLeave={() => setLit(null)}
                  >
                    <span
                      className={styles.glide}
                      aria-hidden="true"
                      data-part="highlight"
                      style={{
                        top: glide.top,
                        height: glide.height,
                        opacity: lit === null ? 0 : 1,
                      }}
                    />
                    {typeOptions.map((type, index) => (
                      <button
                        key={type}
                        ref={(element) => {
                          rows.current[index] = element;
                        }}
                        type="button"
                        role="option"
                        aria-selected={type === values.type}
                        tabIndex={-1}
                        className={styles.option}
                        onPointerEnter={() => setLit(index)}
                        onFocus={() => setLit(index)}
                        onClick={() => choose(index)}
                      >
                        {type}
                      </button>
                    ))}
                  </div>
                </div>
              )}
            </div>
          </div>
        </section>
      </div>
      <p className={styles.hidden} aria-live="polite">
        {message}
      </p>
    </div>
  );
}

/** A number with its name as a handle: drag the name to scrub, arrow keys on it, or type. */
function NumberField({
  label,
  min,
  max,
  unit,
  value,
  draft,
  touched,
  busy,
  onDraft,
  onValue,
}: {
  label: string;
  min: number;
  max: number;
  unit?: string;
  value: number;
  draft?: string;
  touched: boolean;
  busy: boolean;
  onDraft: (text: string | undefined) => void;
  onValue: (value: number) => void;
}) {
  const drag = useRef<{ x: number; from: number } | null>(null);

  const onPointerDown = (event: ReactPointerEvent<HTMLSpanElement>) => {
    if (event.button !== 0) return;
    event.preventDefault();
    event.currentTarget.focus();
    event.currentTarget.setPointerCapture(event.pointerId);
    drag.current = { x: event.clientX, from: value };
  };
  const onPointerMove = (event: ReactPointerEvent<HTMLSpanElement>) => {
    if (!drag.current) return;
    onValue(drag.current.from + (event.clientX - drag.current.x) * SCRUB);
  };
  const onPointerUp = () => {
    drag.current = null;
  };
  const onHandleKey = (event: ReactKeyboardEvent<HTMLSpanElement>) => {
    const step = event.shiftKey ? 10 : 1;
    const moves: Record<string, number> = {
      ArrowRight: value + step,
      ArrowUp: value + step,
      ArrowLeft: value - step,
      ArrowDown: value - step,
      PageUp: value + 10,
      PageDown: value - 10,
      Home: min,
      End: max,
    };
    if (!(event.key in moves)) return;
    event.preventDefault();
    onValue(moves[event.key]);
  };

  const settle = (text: string) => {
    onDraft(undefined);
    if (text.trim() !== '') onValue(Number(text));
  };

  return (
    <label
      className={`${styles.field} ${touched ? styles.touched : ''}`}
      data-part="field"
      data-touched={touched || undefined}
    >
      <span
        role="slider"
        aria-label={label}
        aria-valuenow={value}
        aria-valuemin={min}
        aria-valuemax={max}
        aria-valuetext={unit ? `${value}${unit}` : undefined}
        tabIndex={0}
        className={`${styles.handle} ${busy ? styles.busy : ''}`}
        data-part="handle"
        onPointerDown={onPointerDown}
        onPointerMove={onPointerMove}
        onPointerUp={onPointerUp}
        onPointerCancel={onPointerUp}
        onClick={(event) => event.preventDefault()}
        onKeyDown={onHandleKey}
      >
        {label}
      </span>
      <input
        className={styles.input}
        inputMode="numeric"
        aria-label={`${label} value`}
        value={draft ?? String(value)}
        onChange={(event) => onDraft(event.target.value.replace(/\D/g, '').slice(0, 3))}
        onBlur={(event) => settle(event.target.value)}
        onKeyDown={(event) => {
          if (event.key === 'Enter') settle(event.currentTarget.value);
          else if (event.key === 'Escape') onDraft(undefined);
          else if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
            event.preventDefault();
            onDraft(undefined);
            onValue(value + (event.key === 'ArrowUp' ? 1 : -1) * (event.shiftKey ? 10 : 1));
          }
        }}
      />
      {unit && (
        <span className={styles.unit} aria-hidden="true">
          {unit}
        </span>
      )}
    </label>
  );
}
```

### fine-tune-card/fine-tune-card.module.css

```css
/* Fine-tune card (fine-tune-card.tsx): a soft grey stage with a 240 px inspector card in its
   middle, or at night a graphite stage with the card a step lighter.

   Colours are the tokens below, light-dark(light, dark): the component takes the color-scheme it
   inherits from the page (light unless the page says otherwise), or the one its colorScheme prop
   sets (data-color-scheme). The shadows are one list for both schemes, each layer coloured for
   one of them and clear in the other.

   Motion is transforms, opacity and colours: the layout thumb slides, the fields cross their
   tint, the menu pops up from its corner and its highlight glides by top and height. */

.root {
  /* The stage, the card, the fields and the select's well. */
  --canvas: oklch(96.1% 0.002 247.84);
  --surface: oklch(100% 0 0);
  --field: oklch(96.1% 0.001 286.375);
  --inset: oklch(97.9% 0.002 247.839);
  /* Words: strong, the handles under the pointer, and quiet. */
  --ink: oklch(24.7% 0.006 258.361);
  --ink-2: oklch(50.6% 0.01 264.477);
  --ink-3: oklch(69.5% 0.009 264.505);
  --line: oklch(94.6% 0.003 264.542);
  /* The agent's blue: icons, rings, the tint of a changed field, its shimmer's light. */
  --accent: oklch(62.6% 0.205 254.947);
  --accent-ink: oklch(55.6% 0.187 255.617);
  --accent-tint: oklch(96% 0.019 252.878);
  --accent-line: oklch(62.6% 0.205 254.947 / 0.3);
  --green: oklch(60.3% 0.155 150.883);
  /* The card's and the thumb's rings, and their lifts: long soft shadows by day, short dark
     ones at night. */
  --card-ring: oklch(94.6% 0.003 264.542);
  --btn-ring: oklch(91.2% 0.005 258.326);
  --day-4: rgb(0 0 0 / 0.04);
  --day-3: rgb(0 0 0 / 0.03);
  --day-2: rgb(0 0 0 / 0.02);
  --day-1: rgb(0 0 0 / 0.012);
  --night: transparent;
  --night-btn: transparent;
  position: relative;
  container-type: inline-size;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 100%;
  min-height: 272px;
  padding: 12px;
  overflow: hidden;
  overflow: clip;
  border-radius: 14px;
  background: var(--canvas);
  box-shadow: 0 0 0 1px var(--line);
  color: var(--ink);
  font-family: Inter, ui-sans-serif, system-ui, sans-serif;
  font-size: 14px;
  line-height: 1.5;
  letter-spacing: -0.14px;
  font-feature-settings: 'cv11', 'ss01';
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}
@supports (color: light-dark(#fff, #000)) {
  .root {
    --canvas: light-dark(oklch(96.1% 0.002 247.84), oklch(23.1% 0.004 264.487));
    --surface: light-dark(oklch(100% 0 0), oklch(26% 0.006 271.191));
    --field: light-dark(oklch(96.1% 0.001 286.375), oklch(29.3% 0.006 271.223));
    --inset: light-dark(oklch(97.9% 0.002 247.839), oklch(24.3% 0.004 264.492));
    --ink: light-dark(oklch(24.7% 0.006 258.361), oklch(96.4% 0.002 247.839));
    --ink-2: light-dark(oklch(50.6% 0.01 264.477), oklch(73.1% 0.008 260.731));
    --ink-3: light-dark(oklch(69.5% 0.009 264.505), oklch(54.1% 0.01 264.484));
    --line: light-dark(oklch(94.6% 0.003 264.542), oklch(30.8% 0.006 258.354));
    --accent: light-dark(oklch(62.6% 0.205 254.947), oklch(68% 0.173 253.301));
    --accent-ink: light-dark(oklch(55.6% 0.187 255.617), oklch(78.8% 0.113 248.33));
    --accent-tint: light-dark(oklch(96% 0.019 252.878), oklch(68% 0.173 253.301 / 0.16));
    --accent-line: light-dark(oklch(62.6% 0.205 254.947 / 0.3), oklch(68% 0.173 253.301 / 0.3));
    --green: light-dark(oklch(60.3% 0.155 150.883), oklch(70.5% 0.154 153.814));
    --card-ring: light-dark(oklch(94.6% 0.003 264.542), oklch(100% 0 0 / 0.13));
    --btn-ring: light-dark(oklch(91.2% 0.005 258.326), oklch(100% 0 0 / 0.1));
    --day-4: light-dark(rgb(0 0 0 / 0.04), transparent);
    --day-3: light-dark(rgb(0 0 0 / 0.03), transparent);
    --day-2: light-dark(rgb(0 0 0 / 0.02), transparent);
    --day-1: light-dark(rgb(0 0 0 / 0.012), transparent);
    --night: light-dark(transparent, oklch(0% 0 0 / 0.22));
    --night-btn: light-dark(transparent, oklch(0% 0 0 / 0.3));
  }
}
.root[data-color-scheme='light'] {
  color-scheme: light;
}
.root[data-color-scheme='dark'] {
  color-scheme: dark;
}
.root[data-color-scheme='system'] {
  color-scheme: light dark;
}

:where(.root) :where(button, input, h3, p) {
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
}
:where(.root) :where(button) {
  cursor: pointer;
}
.root button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* The card, centred in a 480 px column. */
.column {
  display: flex;
  justify-content: center;
  width: 100%;
  max-width: 480px;
}
.card {
  position: relative;
  width: 100%;
  max-width: 240px;
  border-radius: 10px;
  background: var(--surface);
  box-shadow:
    0 0 0 1px var(--card-ring),
    0 2px 10px var(--night),
    0 17.5px 23.4px var(--day-4),
    0 9.4px 12.5px var(--day-3),
    0 5.25px 7px var(--day-2),
    0 2.8px 3.7px -2px var(--day-1),
    0 1.16px 1.5px var(--day-1);
}

/* The header: the card's name, and what the agent is doing. */
.bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
}
.title {
  font-size: 13px;
  font-weight: 500;
  line-height: 1.5;
}
.agent {
  display: flex;
  align-items: center;
  gap: 6px;
}
.spark {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 18px;
  height: 18px;
  border: 1px solid var(--accent-line);
  border-radius: 5px;
  background: var(--accent-tint);
}
.spark svg {
  display: block;
  width: 9px;
  height: 9px;
  fill: var(--accent);
}
/* The agent's words: a light band runs across them, right to left, every 1.4 s. */
.adjust {
  background-image: linear-gradient(
    90deg,
    var(--accent) 35%,
    var(--accent-ink) 50%,
    var(--accent) 65%
  );
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  font-size: 12px;
  font-weight: 500;
  line-height: 18px;
  animation: shimmer 1.4s linear infinite;
}
@keyframes shimmer {
  from {
    background-position: 150% center;
  }
  to {
    background-position: -50% center;
  }
}
.edited {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--green);
  font-size: 12px;
  font-weight: 500;
  line-height: 18px;
  animation: pop 250ms cubic-bezier(0.23, 1, 0.32, 1) both;
}
.edited svg {
  display: block;
  width: 10px;
  height: 10px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.25;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.pop {
  animation: pop 250ms cubic-bezier(0.23, 1, 0.32, 1) both;
}
@keyframes pop {
  from {
    opacity: 0;
    transform: scale(0.95);
  }
}

/* Layout and the four numbers. */
.pad {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  border-bottom: 1px solid var(--line);
}
.section {
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.5;
}

/* The segmented control: a well with a raised thumb that slides under the choice. */
.segmented {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  padding: 2px;
  border-radius: 8px;
  background: var(--field);
}
.thumb {
  position: absolute;
  top: 2px;
  bottom: 2px;
  left: 2px;
  width: calc((100% - 4px) / 3);
  border-radius: 6px;
  background: var(--surface);
  box-shadow:
    0 0 0 1px var(--btn-ring),
    0 0 4px var(--day-4),
    0 1px 2px var(--night-btn);
  transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.segment {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 24px;
  color: var(--ink-3);
  transition: color 200ms cubic-bezier(0.4, 0, 0.2, 1);
}
.segment.on {
  color: var(--accent);
}
.root .segment:focus-visible {
  border-radius: 6px;
}
/* The icons: three in a row, two in a column, four in a grid, of small rounded squares. */
.icon {
  display: grid;
  gap: 2px;
}
.icon > span {
  box-sizing: border-box;
  width: 6px;
  height: 6px;
  border: 1.2px solid currentColor;
  border-radius: 2px;
}
.iconRow {
  grid-auto-flow: column;
}
.iconColumn {
  grid-auto-flow: row;
}
.iconGrid {
  grid-template-columns: repeat(2, 6px);
}

.pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  min-width: 0;
}
/* A number field; once changed it keeps a blue tint and ring. */
.field {
  display: flex;
  align-items: center;
  gap: 4px;
  box-sizing: border-box;
  min-width: 0;
  height: 26px;
  padding: 4px 4px 4px 2px;
  border-radius: 6px;
  background: var(--field);
  box-shadow: 0 0 0 0 transparent;
  transition:
    background-color 200ms cubic-bezier(0.4, 0, 0.2, 1),
    box-shadow 200ms cubic-bezier(0.4, 0, 0.2, 1);
}
.field.touched {
  background: var(--accent-tint);
  box-shadow: 0 0 0 1px var(--accent);
}
/* The field's name, a handle to scrub it. */
.handle {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  height: 100%;
  padding: 0 2px;
  border-radius: 4px;
  color: var(--ink-3);
  font-size: 12px;
  line-height: 18px;
  cursor: ew-resize;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
@media (hover: hover) {
  .handle:hover {
    color: var(--ink-2);
  }
}
.handle:focus-visible,
.handle.busy {
  color: var(--accent-ink);
}
.handle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.input {
  flex: 1;
  min-width: 0;
  height: 18px;
  outline: none;
  font-size: 12px;
  line-height: 18px;
  font-variant-numeric: tabular-nums;
}
.unit {
  flex-shrink: 0;
  padding-right: 2px;
  color: var(--ink-3);
  font-size: 11.5px;
  line-height: 1.5;
}

/* The footer: Type and its menu. */
.footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px;
}
.typeLabel {
  color: var(--ink-3);
  font-size: 12px;
  line-height: 18px;
}
.select {
  position: relative;
  width: 120px;
  margin-right: -2px;
}
.selectButton {
  display: flex;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  width: 100%;
  height: 26px;
  padding: 4px 4px 4px 8px;
  border-radius: 6px;
  background: var(--inset);
  box-shadow: 0 0 0 1px var(--line);
  transition: box-shadow 200ms cubic-bezier(0.4, 0, 0.2, 1);
}
.root .selectButton:focus-visible {
  outline: none;
}
.selectButton.open,
.selectButton:focus-visible {
  box-shadow: 0 0 0 1px var(--accent);
}
.placeholder,
.value {
  overflow: hidden;
  font-size: 12px;
  line-height: 18px;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.placeholder {
  color: var(--ink-3);
}
.chevron {
  display: block;
  width: 11px;
  height: 11px;
  flex-shrink: 0;
  fill: none;
  stroke: var(--ink-3);
  stroke-width: 1.15;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 200ms cubic-bezier(0.4, 0, 0.2, 1);
}
.open .chevron {
  transform: rotate(180deg);
}
/* The menu opens upwards, popping from its bottom right corner. */
.menu {
  position: absolute;
  right: 0;
  bottom: 32px;
  z-index: 10;
  box-sizing: border-box;
  width: 120px;
  padding: 4px;
  border-radius: 10px;
  background: var(--surface);
  box-shadow:
    0 0 0 1px var(--card-ring),
    0 2px 10px var(--night),
    0 17.5px 23.4px var(--day-4),
    0 9.4px 12.5px var(--day-3),
    0 5.25px 7px var(--day-2),
    0 2.8px 3.7px -2px var(--day-1),
    0 1.16px 1.5px var(--day-1);
  transform-origin: right bottom;
  animation: pop 200ms cubic-bezier(0.23, 1, 0.32, 1) both;
}
.rows {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.glide {
  position: absolute;
  right: 0;
  left: 0;
  border-radius: 6px;
  background: var(--field);
  pointer-events: none;
  transition:
    top 220ms cubic-bezier(0.23, 1, 0.32, 1),
    height 220ms cubic-bezier(0.23, 1, 0.32, 1),
    opacity 150ms;
}
.option {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  box-sizing: border-box;
  width: 100%;
  height: 26px;
  padding: 0 8px;
  border-radius: 6px;
  font-size: 12.5px;
  line-height: 1.5;
  text-align: left;
}
.root .option:focus-visible {
  outline: none;
}

.hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  .adjust {
    animation: none;
    background-position: 50% center;
  }
  .edited,
  .pop,
  .menu {
    animation: none;
  }
  .thumb,
  .segment,
  .field,
  .handle,
  .selectButton,
  .chevron,
  .glide {
    transition: none;
  }
}
```

### fine-tune-card/use-autoplay.ts

```tsx
'use client';

import { useCallback, useEffect, useRef, useState } from 'react';

/*
 * When a demo may play on its own: while part of it is on screen and the tab is visible, never
 * under reduced motion, and not while the reader is using it. `take()` hands it to the reader; it
 * takes back over `idle` ms after their last input.
 */
export function useAutoplay<T extends HTMLElement>(idle = 6000) {
  const ref = useRef<T>(null);
  const [visible, setVisible] = useState(false);
  const [reduced, setReduced] = useState(true);
  const [taken, setTaken] = useState(false);
  const timer = useRef(0);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;
    let onScreen = false;
    const update = () => setVisible(onScreen && !document.hidden);
    const observer = new IntersectionObserver(
      ([entry]) => {
        onScreen = entry.isIntersecting;
        update();
      },
      { threshold: 0.3 },
    );
    observer.observe(element);
    document.addEventListener('visibilitychange', update);
    const motion = window.matchMedia('(prefers-reduced-motion: reduce)');
    const readMotion = () => setReduced(motion.matches);
    readMotion();
    motion.addEventListener('change', readMotion);
    return () => {
      observer.disconnect();
      document.removeEventListener('visibilitychange', update);
      motion.removeEventListener('change', readMotion);
    };
  }, []);

  const take = useCallback(() => {
    setTaken(true);
    window.clearTimeout(timer.current);
    timer.current = window.setTimeout(() => setTaken(false), idle);
  }, [idle]);
  useEffect(() => () => window.clearTimeout(timer.current), []);

  return { ref, take, playing: visible && !reduced && !taken };
}
```