129 lines
8.0 KiB
Markdown
129 lines
8.0 KiB
Markdown
---
|
||
name: css-to-tailwind
|
||
description: Rules for converting resolved computed CSS from the capture into Tailwind v3 classes on Next.js components. Decides when to use arbitrary values vs extending tailwind.config.ts, how to handle gradients / shadows / filters / clamp / variable fonts / container queries, and when to bail to globals.css. Load this in every Stage 1 or Stage 2 generation run.
|
||
user-invocable: false
|
||
---
|
||
|
||
# CSS → Tailwind
|
||
|
||
The generate agent receives a DOM fragment with computed styles already resolved per node. Your job is to turn those styles into Tailwind classes on the JSX output — no cascade reasoning required.
|
||
|
||
## Core decision: utility vs arbitrary vs config vs CSS file
|
||
|
||
| Situation | Approach |
|
||
| ---------------------------------------------------------------------------------------------- | ----------------------------------------------- |
|
||
| Value matches a default Tailwind scale step (e.g. `p-4`, `gap-8`) | Use the utility |
|
||
| Value is used 1-2 times site-wide and does not match scale | Arbitrary: `w-[347px]` |
|
||
| Value is used 3+ times site-wide | Extend `tailwind.config.ts`, then use the token |
|
||
| Cannot be expressed in Tailwind (complex `filter`, `@property`, `@supports`, dynamic `attr()`) | Write to `globals.css` |
|
||
|
||
The manifest's `design_tokens` object is already deduplicated by frequency — use it as the source of truth for what goes into `tailwind.config.ts`.
|
||
|
||
## Specific rules
|
||
|
||
### Heights and widths — use intent over resolved px
|
||
|
||
The capture's computed styles resolve `vh`/`vw`/`%`/`calc()` to literal pixel values at the capture viewport (1280×720). **Do not blindly transcribe these.** The manifest gives you better signals:
|
||
|
||
1. **`section.vh_relative === true`**: the section's height is viewport-height-derived. Map `section.vh_value` to Tailwind:
|
||
- `vh_value` ≈ 100 → `min-h-screen` (or `h-screen` if the source had `height` not `min-height`)
|
||
- `vh_value` between 50 and 99 → `min-h-[<vh>vh]` arbitrary, e.g. `min-h-[88vh]`
|
||
- `vh_value` < 50 → still `min-h-[<vh>vh]` but consider whether content alone determines the height (if so, omit the height entirely and let content size it)
|
||
2. **`section.full_width === true`**: use `w-full` (or `w-screen` for true full-bleed elements that escape parent padding). Never write `w-[1280px]`.
|
||
3. **`capture/css-rules/<vp>.json`** has the source intent verbatim. When in doubt for a specific element, read the rule that matched its selector. Look for `100vh`, `100%`, `calc(...)`, `clamp(...)`, `aspect-ratio` — these are signals to NOT use the literal px value.
|
||
|
||
Heuristic for elements not in `vh_relative_elements` but suspected vh-relative: if `bounding_box.height` is close to `viewport_height * N` for `N ∈ {0.5, 0.75, 1.0}` and the element is a top-level section with no other content driving height, prefer the vh form. Cheaper to over-flag here than to leave a brittle px height.
|
||
|
||
### Colors
|
||
|
||
- Every color in `manifest.design_tokens.colors` gets promoted into `theme.extend.colors` under its semantic name.
|
||
- Reference as `bg-primary`, `text-primary`, `border-accent-1`, etc. in class output.
|
||
- Unseen one-off colors: arbitrary, e.g. `text-[#3a3a3a]`.
|
||
|
||
### Typography
|
||
|
||
- Font families: promote to `theme.extend.fontFamily`. Use `next/font/local` in `layout.tsx` with the downloaded woff2 files and expose via CSS variable, then reference as `['var(--font-display)']` etc.
|
||
- Font sizes: promote clusters to `theme.extend.fontSize` as named keys (`display`, `h1`, `body`, `small`). Value is `[fontSize, { lineHeight, letterSpacing, fontWeight }]`.
|
||
- One-off sizes: `text-[32px]/[1.2]` arbitrary.
|
||
|
||
### Spacing
|
||
|
||
- Infer the scale from `manifest.design_tokens.spacing[]`. Often 4px / 8px / 16px / 24px ... but match what the site actually uses.
|
||
- Extend Tailwind's spacing scale only for values not already close to a default step.
|
||
- Use `gap-*`, `p-*`, `m-*`, `space-y-*` utilities.
|
||
|
||
### Gradients
|
||
|
||
- Linear: `bg-gradient-to-r from-[#...] via-[#...] to-[#...]` for simple 2-3 stops. For more stops or angled variants, use arbitrary `bg-[linear-gradient(135deg,#a_0%,#b_50%,#c_100%)]`.
|
||
- Radial: `bg-[radial-gradient(...)]` arbitrary.
|
||
- Conic: `bg-[conic-gradient(...)]` arbitrary.
|
||
- Mesh / multi-layer: wrap in `bg-[image:...]` syntax with escaped commas.
|
||
|
||
### Shadows
|
||
|
||
- Single-layer shadows that match scale: `shadow-md`, `shadow-xl`.
|
||
- Multi-layer shadows: arbitrary with escaped commas: `shadow-[0_1px_2px_rgba(0,0,0,0.05),_0_4px_8px_rgba(0,0,0,0.08)]`.
|
||
- Promote 3+ occurrences of the same shadow to `theme.extend.boxShadow`.
|
||
|
||
### Filters and backdrop-filter
|
||
|
||
- Built-in: `blur-*`, `brightness-*`, `contrast-*`, `grayscale-*`, `backdrop-blur-*`, `backdrop-saturate-*`.
|
||
- Arbitrary: `filter-[blur(16px)_saturate(140%)]`, `backdrop-filter-[blur(20px)_saturate(140%)]`.
|
||
- Complex filter chains (4+ functions): promote to a CSS utility class in `globals.css` under `@layer utilities`.
|
||
|
||
### clamp() / min() / max()
|
||
|
||
- Preserve verbatim in arbitrary: `text-[clamp(24px,4vw,48px)]`, `w-[min(100%,1200px)]`.
|
||
- Do not try to approximate with `md:` breakpoints — the whole point of `clamp()` is continuous scaling.
|
||
|
||
### Variable fonts
|
||
|
||
- `font-variation-settings: "wght" 420, "ital" 0` → arbitrary `[font-variation-settings:'wght'_420,'ital'_0]`.
|
||
- If the site uses variable weights fluidly (e.g. on hover), pair with `transition-[font-variation-settings]`.
|
||
|
||
### Container queries
|
||
|
||
- Use the `@tailwindcss/container-queries` plugin. Add to `tailwind.config.ts` plugins.
|
||
- Mark the container: `@container`.
|
||
- Query: `@md:grid-cols-2`, `@lg:text-lg`, etc.
|
||
|
||
### clip-path and mask
|
||
|
||
- Built-in utilities cover basic cases (`rounded-full`). Anything custom: arbitrary.
|
||
- `clip-path: polygon(...)` → `[clip-path:polygon(0_0,100%_0,100%_80%,0_100%)]`.
|
||
- `mask-image` → `[mask-image:linear-gradient(...)]` + matching `[-webkit-mask-image:...]` if Safari support needed.
|
||
|
||
### Transforms
|
||
|
||
- Scale / rotate / translate cover common cases. Chained transforms: Tailwind's `transform` + individual utilities, or arbitrary `[transform:perspective(800px)_rotateY(12deg)]`.
|
||
|
||
### Transitions
|
||
|
||
- Simple: `transition-colors`, `duration-200`, `ease-in-out`.
|
||
- Multi-property transitions: arbitrary `[transition:transform_400ms_cubic-bezier(0.22,1,0.36,1),_opacity_300ms]`.
|
||
|
||
### Keyframes / `@keyframes`
|
||
|
||
- Always bail to `globals.css` under `@layer utilities` or a named keyframe in `tailwind.config.ts` `theme.extend.keyframes` + `animation`.
|
||
- Then reference in JSX: `animate-fade-up`.
|
||
|
||
## When to bail to `globals.css`
|
||
|
||
Write rules to `src/app/globals.css` (not to the component) when:
|
||
|
||
- The rule is `@property` — Tailwind cannot express it.
|
||
- The rule is `@supports` — conditional CSS outside Tailwind's reach.
|
||
- The rule is `@font-face` — prefer `next/font/local`, but if the font is served from a remote URL that cannot be downloaded, `@font-face` in `globals.css` is acceptable.
|
||
- A selector you cannot express as a class (e.g. `:has()`, `:nth-child(odd) > :first-of-type`).
|
||
- Complex multi-rule animations (keyframes + state variants + JS hooks).
|
||
|
||
Keep `globals.css` under 200 lines. If it grows beyond, group related rules into separate files imported from `globals.css`.
|
||
|
||
## Do not
|
||
|
||
- Do not re-derive cascade from the source stylesheets. The computed styles on each node already resolve cascade. Copy those.
|
||
- Do not use `!important`. If a style is not applying, you have a selector specificity problem — fix it by structuring the JSX, not by nuking the cascade.
|
||
- Do not add classes that are not represented in the captured computed styles. If the source does not have `box-shadow`, do not add one "for polish".
|
||
- Do not add `@apply` in component files. `@apply` only belongs in `globals.css` when consolidating a repeated utility cluster.
|
||
- Do not leave TODO comments in component files — the cloner is meant to be done work, not a scaffold.
|