AI CSS Animations

design-grill

102 lines

Interviews the user to settle every design system decision, asking in dependency order and writing an HTML preview for each round so the user chooses by looking instead of reading numbers. Produces .design/decisions.json. Use as phase 2 of design-system, after design-extract.

skills/design-grill/SKILL.md

Design Grill

Settle every design decision with the user, one round of questions at a time. Some decisions depend on others (you can't choose spacing sizes before the spacing grid), so ask them in order.

Each round is an HTML file the user opens in a browser. Nobody can judge a type scale, spacing or color from numbers in a terminal.

Before you start

test -f .design/drift.json || echo "MISSING"

If it's missing, stop and run design-extract first. Its counts are what let the user check each recommendation.

How each round works

Read references/design-tree.md. It lists every decision, what each one depends on, and its default.

Each round follows the same loop:

find ready questions -> answer what the data already answers -> render the preview -> ask -> wait -> record -> repeat

1. Find the questions that are ready

A question is ready when every decision it depends on is already made. Ask all the ready questions in one round. Round 1 is always just product character, because everything else depends on it.

2. Answer what the data already answers

For each ready question, check drift.json. If one value already covers 90% or more of usage in that category, don't ask. Show it in the round as already settled, with the percentage, and move on.

Look up anything you can measure. Only ask the user about things the code can't answer.

3. Render the preview

Write .design/grill/round-N.html: a single file with no build step that opens directly in a browser. Start from assets/round-shell.html.

The preview must:

  • Use the project's own fonts and colors, read from the codebase.
  • Show the options side by side, labelled A, B, C.
  • Use real text from the app. No lorem ipsum, because placeholder text hides problems like lines that run too long.
  • Mark the recommended option and show how many current values it fits.
  • Show light and dark versions if the project has both.
  • Fit a 375px-wide screen without scrolling sideways.

4. Ask in the terminal

Print the file path first, then the questions:

Round 2 preview: .design/grill/round-2.html

SETTLED **Radius**: rounded-lg on 94% of surfaces. Using it as the base.

---

**Q1. Spacing grid**: 4px or 8px. Options A and B in the preview show the same card on each grid.

> **A, 4px.** Fits 24 of your 31 current spacing values. 8px fits 13 and would mean rewriting 11.

---

**Q2. Type scale**: each font size 1.200× or 1.250× the one below it, shown in Inter with your text.

> **B, 1.250.** Fits 11 of your 14 current sizes. Your largest heading is already about 1.24× body text.

Number every question. Back every recommendation with a number from drift.json. Separate questions with ---.

5. Wait for answers

Wait for the user to answer before starting the next round. Don't move on if they go quiet, and don't ask the next round's questions early.

If the user picks something other than your recommendation, accept it. Then work out which later questions it affects and tell the user what changed. For example: "With an 8px grid, 6px and 10px are no longer options, so round 3 will offer 4 spacing sizes instead of 6."

Record each answer

After each round, add the answers to .design/decisions.json:

{
  "character": { "value": "dense-productive", "source": "user", "round": 1 },
  "radius.base": { "value": "0.5rem", "source": "pre-settled", "evidence": "94% of surfaces" },
  "spacing.base": { "value": 4, "source": "user", "recommended": 4, "round": 2 }
}

source says where each answer came from: user if the user chose it, pre-settled if the data answered it, default if the default was used. design-ratify reads this field to decide which decisions get a written note.

Finish

The phase is done when no questions are left, not when you feel you know enough.

Print every decision and wait for the user to confirm before handing off to design-ratify.

Reference references/design-tree.md 99 lines

The Design Tree

Design decisions depend on each other in the same order for every project. Follow this order. Don't try to work it out again.

Each arrow means the decision below can't be made until the one above it is settled. A question is ready when everything above it is settled. Ask all the ready questions in one round.

                        +-------------+
                        |   PRODUCT   |  R1
                        |  CHARACTER  |
                        +------+------+
          +------------+-------+--------+------------+
          v            v       v        v            v
      +-------+  +---------+ +------+ +------+  +--------+
      |SPACING|  |  TYPE   | |COLOR | |RADIUS|  | EASING |   R2
      | grid  |  |  scale  | |format| | base |  |        |
      +---+---+  +----+----+ +--+---+ +--+---+  +---+----+
          |           |         |        |          |
          v           v         v        |          v
      +-------+  +---------+ +--------+  |     +--------+
      |spacing|  |  font   | | color  |  |     |duration|   R3
      | sizes |  |  sizes  | | roles  |  |     | values |
      +---+---+  +----+----+ +---+----+  |     +---+----+
          +-----------+----------+-------+---------+
                             v
                    +-----------------+
                    |   COMPONENTS    |  R4
                    | height, density |
                    +--------+--------+
                             v
                    +-----------------+
                    |    PATTERNS     |  R5
                    | empty, loading, |
                    | error, shadows  |
                    +-----------------+

R1 to R5 are the rounds. Each round asks everything on its row.

The decisions

R1. Product character

Decides: what kind of product this is. One of: dense and productive, calm and editorial, playful and consumer, or premium and restrained. Affects: the defaults for spacing density, animation speed and corner radius. Default: guess from the codebase and ask the user to confirm. Tables and data-heavy screens suggest dense and productive. Large images and long text suggest calm and editorial. Note: this is the only decision the code can't answer.

R2. Spacing grid

Decides: whether spacing is built on multiples of 4px or 8px. Default: 4px. It fits more existing values and still allows 8px steps. Only recommend 8px when drift.json shows the code already uses mostly multiples of 8.

R2. Type scale

Decides: how much bigger each font size is than the one below it: 1.125×, 1.200×, 1.250× or 1.333×. Default: 1.200 for dense apps, 1.250 for marketing pages. Quote how many current sizes each option fits, from drift.json.

R2. Color format

Decides: whether color tokens are written in HSL or OKLCH. Default: OKLCH on Tailwind v4. HSL on v3, and always HSL when components.json exists, to match the hsl(var(--x)) format shadcn/ui uses.

R2. Corner radius

Decides: one base radius. The small and large sizes are 2px either side of it. Default: the radius the code already uses most.

R2. Easing

Decides: the easing curves for things entering and leaving the screen. Default: ease-out for entering, ease-in for leaving.

R3. Spacing sizes

Decides: which multiples of the grid are allowed. Needs the spacing grid. Default: 6 to 8 sizes.

R3. Font sizes and line height

Decides: how many font sizes there are, and how line height changes with size. Needs the type scale. Default: 6 sizes. Line height gets tighter as text gets bigger.

R3. Color roles

Decides: what each named color role means in this product: primary, muted, accent, destructive. Needs the color format. Default: when components.json exists, use shadcn/ui's role names as they are. Note: this decision is what makes color maintainable. A Tailwind color like text-blue-600 has to be changed in every file that uses it. A role like text-primary is changed in one place.

R3. Durations

Decides: two or three animation durations, each tied to a kind of interaction. Needs the easing. Default: 150ms for hover and press, 250ms for dropdowns and tooltips, 400ms for modals and page changes.

R4. Components

Decides: control heights, how dense components are, and which variants exist (like primary, secondary and ghost buttons). Needs every R2 and R3 decision. Default: take control heights from the spacing sizes. Don't create a second set of sizes.

R5. Patterns

Decides: how empty, loading and error states look, and what shadows mean. Needs the components decision. Default: shadows show how far something sits above the page. Three levels.

Rules

  1. Don't ask a question until everything it depends on is settled.
  2. After each answer, check which later questions it affects. An answer can remove options from a later round.
  3. When drift.json shows one value covering 90% or more of a category, treat that question as settled and show it for the user to confirm instead of asking.
  4. The phase ends when every question is answered, not when you have enough to get started.

Asset assets/round-shell.html 55 lines

Show source
<!doctype html>
<!-- Frame for a rendered grill round. Fill: TITLE, ROUND_N, OPTIONS.
     Keep it single-file. Substitute the project's real font stack and palette. -->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Round ROUND_N: TITLE</title>
<style>
  :root{
    --bg:#fafafa; --fg:#0a0a0a; --muted:#71717a; --line:#e4e4e7;
    --card:#fff; --pick:#2563eb; --pick-bg:#eff6ff;
    /* SUBSTITUTE the project's real font stack here */
    --font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  }
  :root:not([data-theme=light]) @media (prefers-color-scheme:dark){}
  @media (prefers-color-scheme:dark){ :root:not([data-theme=light]){
    --bg:#09090b; --fg:#fafafa; --muted:#a1a1aa; --line:#27272a;
    --card:#131316; --pick:#60a5fa; --pick-bg:#172554;
  }}
  :root[data-theme=dark]{ --bg:#09090b; --fg:#fafafa; --muted:#a1a1aa;
    --line:#27272a; --card:#131316; --pick:#60a5fa; --pick-bg:#172554; }
  *{box-sizing:border-box}
  body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--font);
       padding:48px 16px 96px;line-height:1.5;-webkit-font-smoothing:antialiased}
  .wrap{max-width:1100px;margin:0 auto}
  .eyebrow{font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin:0 0 8px}
  h1{font-size:clamp(24px,4vw,34px);margin:0 0 6px;letter-spacing:-.02em}
  .sub{color:var(--muted);margin:0 0 40px;max-width:62ch}
  .grid{display:grid;gap:20px;grid-template-columns:repeat(auto-fit,minmax(300px,1fr))}
  .opt{background:var(--card);border:1px solid var(--line);border-radius:12px;
       padding:24px;position:relative;overflow:hidden}
  .opt[data-pick]{border-color:var(--pick);box-shadow:0 0 0 3px var(--pick-bg)}
  .tag{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:600;
       letter-spacing:.06em;text-transform:uppercase;color:var(--muted);margin-bottom:16px}
  .opt[data-pick] .tag{color:var(--pick)}
  .why{margin-top:20px;padding-top:16px;border-top:1px solid var(--line);
       font-size:13px;color:var(--muted)}
  .opt[data-pick] .why{color:var(--pick)}
  /* specimen area, replace per round */
  .specimen{display:flex;flex-direction:column;gap:10px}
</style>
</head>
<body>
<div class="wrap">
  <p class="eyebrow">Round ROUND_N</p>
  <h1>TITLE</h1>
  <p class="sub">SUBTITLE. State what is being decided and what it unblocks.</p>
  <div class="grid">
    <!-- OPTIONS: one .opt per choice; add data-pick to the recommendation -->
  </div>
</div>
</body>
</html>