From ee27bd254cb210c2e6878e95d85f2095782748b8 Mon Sep 17 00:00:00 2001 From: Sudha Shrestha Date: Thu, 11 Jun 2026 17:34:04 +0545 Subject: [PATCH 1/2] fix(skeleton): restore category glyph hover animations Move cg keyframes to globals.css so animate-cg-* utilities compile. Keep group-hover/cg in TSX for most glyphs; CSS transform rules only for SVG scale-x/y edge cases (text, list, graphs, widget). Co-authored-by: Cursor --- animata/skeleton/category-glyphs.css | 112 +++++++++++++++------------ animata/skeleton/category-glyphs.tsx | 56 +++++--------- styles/globals.css | 45 +++++++++++ 3 files changed, 130 insertions(+), 83 deletions(-) diff --git a/animata/skeleton/category-glyphs.css b/animata/skeleton/category-glyphs.css index 3fae5c9c..5bd52e46 100644 --- a/animata/skeleton/category-glyphs.css +++ b/animata/skeleton/category-glyphs.css @@ -1,52 +1,5 @@ @reference "../../styles/globals.css"; -@theme inline { - @keyframes cg-ping { - 0%, - 100% { - transform: scale(1); - } - 50% { - transform: scale(1.12); - } - } - - @keyframes cg-bounce { - 0%, - 100% { - transform: translateY(0); - } - 50% { - transform: translateY(2.5px); - } - } - - @keyframes cg-shimmer { - 0%, - 100% { - opacity: 0.45; - } - 50% { - opacity: 1; - } - } - - @keyframes cg-twinkle { - 0%, - 100% { - transform: scale(1) rotate(0deg); - } - 50% { - transform: scale(1.25) rotate(20deg); - } - } - - --animate-cg-ping: cg-ping 1.4s cubic-bezier(0.22, 1, 0.36, 1) infinite; - --animate-cg-bounce: cg-bounce 1s ease-in-out infinite; - --animate-cg-shimmer: cg-shimmer 1.2s ease-in-out infinite; - --animate-cg-twinkle: cg-twinkle 0.9s ease-in-out infinite; -} - /** Category glyph SVG — shared motion base (pair with group/cg + group-hover/cg:* on parts). */ .cg-motion { transform-box: fill-box; @@ -54,11 +7,13 @@ } @media (prefers-reduced-motion: no-preference) { - /* TW v4 utilities animate translate/scale/rotate — not transform alone */ + /* TW v4 utilities use individual transform properties — transition each one */ .cg-motion { transition: translate 0.5s cubic-bezier(0.22, 1, 0.36, 1), scale 0.5s cubic-bezier(0.22, 1, 0.36, 1), + scale-x 0.5s cubic-bezier(0.22, 1, 0.36, 1), + scale-y 0.5s cubic-bezier(0.22, 1, 0.36, 1), rotate 0.5s cubic-bezier(0.22, 1, 0.36, 1), transform 0.5s cubic-bezier(0.22, 1, 0.36, 1), fill 0.5s ease, @@ -109,3 +64,64 @@ transition: none; } } + +/** + * SVG scale-x / scale-y exceptions — TW v4 longhands (scale-x, scale-y) fail on stroke + * paths and bottom-anchored rects; unified transform here. Animations use Tailwind + * (animate-cg-* in globals.css). Everything else: group-hover/cg:* in TSX. + */ +@media (prefers-reduced-motion: no-preference) { + .cg-text-line { + transform-origin: left center; + } + .group\/cg:hover .cg-text-line-1 { + transform: scaleX(1.25); + } + .group\/cg:hover .cg-text-line-2 { + transform: scaleX(1.5); + transition-delay: 60ms; + } + .group\/cg:hover .cg-text-line-3 { + transform: scaleX(1.8); + transition-delay: 120ms; + } + + .cg-list-bar { + transform-origin: left center; + } + .group\/cg:hover .cg-list-grow { + transform: scaleX(1.3); + } + .group\/cg:hover .cg-list-shrink { + transform: scaleX(0.7); + } + + .cg-graph-bar { + transform-origin: center bottom; + } + .group\/cg:hover .cg-graph-bar { + transform: scaleY(1.18); + } + .group\/cg:hover .cg-graph-bar-2 { + transition-delay: 50ms; + } + .group\/cg:hover .cg-graph-bar-3 { + transition-delay: 100ms; + } + .group\/cg:hover .cg-graph-bar-4 { + transition-delay: 150ms; + } + + .cg-widget-hour { + transform-origin: center bottom; + } + .cg-widget-minute { + transform-origin: 0% 0%; + } + .group\/cg:hover .cg-widget-hour { + transform: rotate(30deg); + } + .group\/cg:hover .cg-widget-minute { + transform: rotate(60deg); + } +} diff --git a/animata/skeleton/category-glyphs.tsx b/animata/skeleton/category-glyphs.tsx index 5e7b9d5d..d8564523 100644 --- a/animata/skeleton/category-glyphs.tsx +++ b/animata/skeleton/category-glyphs.tsx @@ -71,27 +71,21 @@ const GLYPHS: Record = { stroke={SOFT} strokeWidth="2.2" strokeLinecap={CAP} - className={cn("cg-motion", "origin-left motion-safe:group-hover/cg:scale-x-125")} + className={cn("cg-motion", "cg-text-line", "cg-text-line-1")} /> ), @@ -196,13 +190,7 @@ const GLYPHS: Record = { height="3.5" rx="1.75" fill={String(shade)} - className={cn( - "cg-motion", - "origin-left", - i < 2 - ? "motion-safe:group-hover/cg:scale-x-[1.3]" - : "motion-safe:group-hover/cg:scale-x-[0.7]", - )} + className={cn("cg-motion", "cg-list-bar", i < 2 ? "cg-list-grow" : "cg-list-shrink")} /> ))} @@ -406,7 +394,8 @@ const GLYPHS: Record = { strokeWidth="1.6" className={cn( "cg-motion-frame", - "motion-safe:group-hover/cg:animate-cg-ping motion-safe:group-hover/cg:[animation-delay:0.3s]", + "motion-safe:group-hover/cg:animate-cg-ping", + "motion-safe:group-hover/cg:[animation-delay:0.3s]", )} /> = { strokeWidth="2" className={cn( "cg-motion-frame", - "motion-safe:group-hover/cg:animate-cg-ping motion-safe:group-hover/cg:[animation-delay:0.15s]", + "motion-safe:group-hover/cg:animate-cg-ping", + "motion-safe:group-hover/cg:[animation-delay:0.15s]", )} /> = { height={Number(h)} rx="2" fill={String(shade)} - className={cn( - "cg-motion", - "origin-[center_bottom] motion-safe:group-hover/cg:scale-y-[1.18]", - i === 1 && "motion-safe:group-hover/cg:delay-[50ms]", - i === 2 && "motion-safe:group-hover/cg:delay-100", - i === 3 && "motion-safe:group-hover/cg:delay-150", - )} + className={cn("cg-motion", "cg-graph-bar", `cg-graph-bar-${i + 1}`)} /> ))} @@ -626,17 +610,14 @@ const GLYPHS: Record = { stroke={INK} strokeWidth="2.2" strokeLinecap={CAP} - className={cn( - "cg-motion", - "origin-[center_bottom] motion-safe:group-hover/cg:rotate-[30deg]", - )} + className={cn("cg-motion", "cg-widget-hour")} /> @@ -841,7 +822,8 @@ const GLYPHS: Record = { fill={SOFT} className={cn( "cg-motion-frame", - "motion-safe:group-hover/cg:animate-cg-shimmer motion-safe:group-hover/cg:[animation-delay:0.1s]", + "motion-safe:group-hover/cg:animate-cg-shimmer", + "motion-safe:group-hover/cg:[animation-delay:0.1s]", )} /> = { fill={FAINT} className={cn( "cg-motion-frame", - "motion-safe:group-hover/cg:animate-cg-shimmer motion-safe:group-hover/cg:[animation-delay:0.2s]", + "motion-safe:group-hover/cg:animate-cg-shimmer", + "motion-safe:group-hover/cg:[animation-delay:0.2s]", )} /> = { fill={SOFT} className={cn( "cg-motion-frame", - "motion-safe:group-hover/cg:animate-cg-shimmer motion-safe:group-hover/cg:[animation-delay:0.3s]", + "motion-safe:group-hover/cg:animate-cg-shimmer", + "motion-safe:group-hover/cg:[animation-delay:0.3s]", )} /> = { fill={SOFT} className={cn( "cg-motion-frame", - "motion-safe:group-hover/cg:animate-cg-shimmer motion-safe:group-hover/cg:[animation-delay:0.4s]", + "motion-safe:group-hover/cg:animate-cg-shimmer", + "motion-safe:group-hover/cg:[animation-delay:0.4s]", )} /> = { fill={FAINT} className={cn( "cg-motion-frame", - "motion-safe:group-hover/cg:animate-cg-shimmer motion-safe:group-hover/cg:[animation-delay:0.5s]", + "motion-safe:group-hover/cg:animate-cg-shimmer", + "motion-safe:group-hover/cg:[animation-delay:0.5s]", )} /> diff --git a/styles/globals.css b/styles/globals.css index 94d9a27b..d9c6dbb7 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -110,6 +110,11 @@ --animate-float: float 3s ease-in-out infinite; --animate-rotate-full: rotate-full 45s linear infinite; + --animate-cg-ping: cg-ping 1.4s cubic-bezier(0.22, 1, 0.36, 1) infinite; + --animate-cg-bounce: cg-bounce 1s ease-in-out infinite; + --animate-cg-shimmer: cg-shimmer 1.2s ease-in-out infinite; + --animate-cg-twinkle: cg-twinkle 0.9s ease-in-out infinite; + @keyframes fill { 0% { height: 0%; @@ -402,6 +407,46 @@ transform: translateY(0); } } + + @keyframes cg-ping { + 0%, + 100% { + transform: scale(1); + } + 50% { + transform: scale(1.12); + } + } + + @keyframes cg-bounce { + 0%, + 100% { + transform: translateY(0); + } + 50% { + transform: translateY(2.5px); + } + } + + @keyframes cg-shimmer { + 0%, + 100% { + opacity: 0.45; + } + 50% { + opacity: 1; + } + } + + @keyframes cg-twinkle { + 0%, + 100% { + transform: scale(1) rotate(0deg); + } + 50% { + transform: scale(1.25) rotate(20deg); + } + } } @layer base { From 32e7ec5c596a8bd10dee26efa4d4e515184e7ee8 Mon Sep 17 00:00:00 2001 From: Sudha Shrestha Date: Thu, 11 Jun 2026 18:38:45 +0545 Subject: [PATCH 2/2] docs(skeleton): document category glyph hover patterns Document the three hover patterns (Tailwind, keyframe loops, CSS scale exceptions), why cg keyframes live in globals.css, and add Storybook stories for glyphs with non-default hover behavior. Co-authored-by: Cursor --- CLAUDE.md | 2 +- animata/skeleton/category-glyphs.css | 6 +- animata/skeleton/category-glyphs.tsx | 4 +- .../skeleton/category-skeleton.stories.tsx | 16 +++++ .../docs/contributing/category-glyphs-spec.md | 61 ++++++++++++++----- content/docs/contributing/category-glyphs.mdx | 40 +++++++++--- styles/globals.css | 1 + 7 files changed, 103 insertions(+), 27 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ed555bcf..1f6f6f07 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -76,7 +76,7 @@ Add to the relevant `YYYY-MM.mdx` file. No need to touch the index unless the su animata/ # Component source (copy-paste friendly) skeleton/ category-glyphs.tsx # /components tile SVG pictograms (GLYPHS map) - category-glyphs.css # cg-motion base + keyframes (hover transforms live in TSX) + category-glyphs.css # cg-motion base + SVG scale/rotate hover exceptions category-skeleton.tsx # Tile frame + shade tokens container/ # Layout wrappers (marquee, dock, ribbon…) text/ # Text animation effects diff --git a/animata/skeleton/category-glyphs.css b/animata/skeleton/category-glyphs.css index 5bd52e46..e28ca179 100644 --- a/animata/skeleton/category-glyphs.css +++ b/animata/skeleton/category-glyphs.css @@ -66,9 +66,9 @@ } /** - * SVG scale-x / scale-y exceptions — TW v4 longhands (scale-x, scale-y) fail on stroke - * paths and bottom-anchored rects; unified transform here. Animations use Tailwind - * (animate-cg-* in globals.css). Everything else: group-hover/cg:* in TSX. + * SVG scale-x / scale-y exceptions — TW v4 longhands fail on stroke paths and + * bottom-anchored rects; unified transform here. animate-cg-* tokens + keyframes + * live in globals.css (@theme inline in this file does not emit TW utilities). */ @media (prefers-reduced-motion: no-preference) { .cg-text-line { diff --git a/animata/skeleton/category-glyphs.tsx b/animata/skeleton/category-glyphs.tsx index d8564523..285b2cf3 100644 --- a/animata/skeleton/category-glyphs.tsx +++ b/animata/skeleton/category-glyphs.tsx @@ -4,7 +4,9 @@ * category so users can recognize it at a glance. * * Shading uses three tokens on the parent SVG (--cg-ink / --cg-soft / --cg-faint). - * Hover micro-interactions use Tailwind on each part; ancestor must be `group/cg`. + * Hover micro-interactions: default is Tailwind `group-hover/cg:*` on each part + * (ancestor must be `group/cg`). Four glyphs use CSS scale/rotate exceptions in + * category-glyphs.css; keyframe tokens live in styles/globals.css @theme. */ import type { ReactNode } from "react"; diff --git a/animata/skeleton/category-skeleton.stories.tsx b/animata/skeleton/category-skeleton.stories.tsx index 46bb9071..f06b4ff6 100644 --- a/animata/skeleton/category-skeleton.stories.tsx +++ b/animata/skeleton/category-skeleton.stories.tsx @@ -23,6 +23,22 @@ export const Text: Story = { args: { variant: "text", className: "w-80" }, }; +export const List: Story = { + args: { variant: "list", className: "w-80" }, +}; + +export const Icon: Story = { + args: { variant: "icon", className: "w-80" }, +}; + +export const Graphs: Story = { + args: { variant: "graphs", className: "w-80" }, +}; + +export const Skeleton: Story = { + args: { variant: "skeleton", className: "w-80" }, +}; + export const Background: Story = { args: { variant: "background", className: "w-80" }, }; diff --git a/content/docs/contributing/category-glyphs-spec.md b/content/docs/contributing/category-glyphs-spec.md index c3b8dabd..60db3241 100644 --- a/content/docs/contributing/category-glyphs-spec.md +++ b/content/docs/contributing/category-glyphs-spec.md @@ -7,7 +7,8 @@ Guidelines for the SVG pictograms on `/components` — one tile per docs categor | File | Role | |------|------| | `animata/skeleton/category-glyphs.tsx` | Glyph SVG markup, `GLYPHS` map, hover Tailwind on animated parts | -| `animata/skeleton/category-glyphs.css` | Motion base classes, keyframe definitions, transition defaults | +| `animata/skeleton/category-glyphs.css` | Motion base classes, SVG scale/rotate hover exceptions, transition defaults | +| `styles/globals.css` (`@theme`) | `--animate-cg-*` tokens and `@keyframes cg-*` for glyph keyframe loops | | `animata/skeleton/category-skeleton.tsx` | Card frame; shade tokens on the tile SVG; scales glyph into the tile | | `animata/skeleton/category-skeleton.stories.tsx` | Storybook preview per variant (`group/cg` decorator) | @@ -16,7 +17,7 @@ Guidelines for the SVG pictograms on `/components` — one tile per docs categor 1. Add the category to `config/docs.ts` (`sidebarNav` + `href: "/docs/"`). 2. Add a category index MDX at `content/docs//index.mdx`. 3. Add an entry to `GLYPHS` in `category-glyphs.tsx` keyed by the **slug** (e.g. `"bento-grid"`, not `"Bento grid"`). -4. Compose hover motion on animated parts in `category-glyphs.tsx` with `cn("cg-motion", …)` plus Tailwind `motion-safe:group-hover/cg:*` utilities (see Hover micro-interactions). +4. Compose hover motion on animated parts in `category-glyphs.tsx` — Tailwind `motion-safe:group-hover/cg:*` by default (see Hover micro-interactions for CSS exceptions and keyframe loops). 5. Add a Storybook story in `category-skeleton.stories.tsx`. 6. Confirm `/components` lists the category (via `lib/component-categories.ts` — automatic when published count > 0). @@ -102,23 +103,55 @@ Do not mix `rx={3}`, `rx={3.5}`, `rx={6}` on the same object class. ## Hover micro-interactions -Hover transforms live in **TSX**, not CSS. Tag animated parts with `className={cn("cg-motion", …)}` and compose Tailwind utilities on the same element: +Hover behavior uses **three patterns**. Pick the one that matches the SVG part. + +### 1. Tailwind (default) + +Most glyphs: `className={cn("cg-motion", …)}` plus `motion-safe:group-hover/cg:*` on the same element. Works for translate, uniform scale, rotate on rects/groups, opacity, and fill crossfades. + +```tsx +className={cn("cg-motion", "motion-safe:group-hover/cg:scale-[1.06]")} +``` + +The tile ancestor must be **`group/cg`** (`Link` on `/components`, Storybook decorator) so `group-hover/cg:*` variants fire. + +### 2. Tailwind keyframe loops + +Ripple, shimmer, bounce, twinkle: `cg-motion-frame` plus `motion-safe:group-hover/cg:animate-cg-*` and optional `[animation-delay:…]`. ```tsx className={cn( - "cg-motion", - "origin-left motion-safe:group-hover/cg:scale-x-125", + "cg-motion-frame", + "motion-safe:group-hover/cg:animate-cg-ping", + "motion-safe:group-hover/cg:[animation-delay:0.15s]", )} ``` -- The tile ancestor must be **`group/cg`** (`Link` on `/components`, Storybook decorator) so `group-hover/cg:*` variants fire. -- `category-glyphs.css` does **not** define hover transforms. It only sets shared motion bases: - - **`.cg-motion`** — `transform-box`, `transform-origin`, and transition properties (gated by `prefers-reduced-motion`). - - **`.cg-motion-frame`** — same box/origin, no transform transition (for keyframe-driven parts so CSS animation is not fighting transitions). - - **`.cg-progress-arc`** — `stroke-dasharray` transition only (no `transform-box`; conflicts with SVG rotate). - - **`.cg-feature-tilt`** — resting `-8deg` tilt; pair with `motion-safe:group-hover/cg:rotate-0`. -- Keyframe loops (`animate-cg-ping`, `animate-cg-bounce`, etc.) are defined in `category-glyphs.css` `@theme` and applied via Tailwind on `cg-motion-frame` parts. -- Animation should **mimic the component category** (button swells, scroll rows translate, accordion content darkens to INK, container marquee slides). +**`--animate-cg-ping`**, **`--animate-cg-bounce`**, **`--animate-cg-shimmer`**, **`--animate-cg-twinkle`** and their `@keyframes` live in **`styles/globals.css`** inside `@theme`. Do not move them into `category-glyphs.css` — that file is `@reference`-only and `@theme inline` there does not emit `animate-cg-*` utilities. + +Used on: `icon`, `skeleton`, `hero`, `feature-cards`. + +### 3. CSS scale/rotate exceptions + +Tailwind v4 **`scale-x-*` / `scale-y-*`** use individual transform longhands that fail on SVG stroke paths and bottom-anchored rects. Four categories use semantic classes + `.group\/cg:hover` rules in **`category-glyphs.css`** (unified `transform: scaleX()` / `scaleY()` / `rotate()`): + +| Category | TSX classes | Hover effect | +|----------|-------------|--------------| +| `text` | `cg-text-line`, `cg-text-line-1` … `3` | trail lines extend, staggered | +| `list` | `cg-list-bar`, `cg-list-grow` / `cg-list-shrink` | top rows grow, bottom shrink | +| `graphs` | `cg-graph-bar`, `cg-graph-bar-1` … `4` | bars grow from baseline | +| `widget` | `cg-widget-hour`, `cg-widget-minute` | clock hands tick forward | + +Do not use Tailwind `scale-x-*` / `scale-y-*` on stroke ``s or graph bars — use this pattern instead. + +### CSS helpers (bases only, except §3) + +- **`.cg-motion`** — `transform-box: fill-box`, `transform-origin`, transitions (gated by `prefers-reduced-motion`). +- **`.cg-motion-frame`** — same box/origin, no transform transition (keyframe loops only). +- **`.cg-progress-arc`** — `stroke-dasharray` transition only (no `transform-box`; conflicts with SVG rotate). +- **`.cg-feature-tilt`** — resting `-8deg` tilt; pair with `motion-safe:group-hover/cg:rotate-0`. + +Animation should **mimic the component category** (button swells, scroll rows translate, accordion content darkens to INK, container marquee slides). --- @@ -130,7 +163,7 @@ className={cn( - [ ] Stroke tier matches role (2.2 / 2 / 1.6) - [ ] Card frames use `rx={4}`; pills fully rounded - [ ] Layer story is clear (which element is focal?) -- [ ] Hover behavior matches category semantics (optional but preferred) +- [ ] Hover behavior matches category semantics; correct pattern (Tailwind / keyframe / CSS exception) - [ ] Decorative SVGs in stories: include `` if Biome `noSvgWithoutTitle` fires - [ ] Story added; spot-check light + dark on `/components` diff --git a/content/docs/contributing/category-glyphs.mdx b/content/docs/contributing/category-glyphs.mdx index 5ff25162..43a496ef 100644 --- a/content/docs/contributing/category-glyphs.mdx +++ b/content/docs/contributing/category-glyphs.mdx @@ -13,7 +13,7 @@ You only need this when you add a **new component category** (a new sidebar sect 1. Register the category in `config/docs.ts` with `href: "/docs/<slug>"`. 2. Add `content/docs/<slug>/index.mdx`. 3. Add `GLYPHS["<slug>"]` in `animata/skeleton/category-glyphs.tsx`. -4. Wire hover motion on any animated parts in that same file: `cn("cg-motion", …)` plus Tailwind classes like `motion-safe:group-hover/cg:scale-x-125` (see Hover animations). +4. Wire hover motion on animated parts (Tailwind `group-hover/cg:*` by default; see [Hover animations](#hover-animations) for CSS exceptions and keyframe loops). 5. Add a story in `category-skeleton.stories.tsx`. The decorator already wraps stories in `group/cg`. Once the category has at least one published component, `/components` picks it up automatically via `lib/component-categories.ts`. @@ -56,19 +56,43 @@ Round caps and joins on all strokes. ## Hover animations -Put `cg-motion` on any part that moves, then add Tailwind hover utilities on the same element: +**Default:** put `cg-motion` on any part that moves, then Tailwind hover utilities on the same element: ```tsx -className={cn("cg-motion", "origin-left motion-safe:group-hover/cg:scale-x-125")} +className={cn("cg-motion", "motion-safe:group-hover/cg:scale-[1.06]")} ``` -The parent tile must be `group/cg` (the `Link` on `/components`, or the Storybook decorator) or the hover classes never run. +The parent tile must be `group/cg` (the `Link` on `/components`, or the Storybook decorator) or `group-hover/cg:*` never runs. -`category-glyphs.css` does not define the hover transforms. It only sets `transform-box`, `transform-origin`, and transitions on `.cg-motion`, with `prefers-reduced-motion` respected. Other helpers when you need them: +**Keyframe loops** (icon ripple, skeleton shimmer, hero bounce, feature-cards twinkle): use `cg-motion-frame` plus Tailwind animation utilities: -- `cg-motion-frame` for keyframe loops (no transform transition, so animation does not fight CSS transitions) -- `cg-progress-arc` for stroke-dasharray only -- `cg-feature-tilt` for a resting tilt that straightens on hover +```tsx +className={cn( + "cg-motion-frame", + "motion-safe:group-hover/cg:animate-cg-ping", + "motion-safe:group-hover/cg:[animation-delay:0.15s]", +)} +``` + +`--animate-cg-*` tokens and `@keyframes cg-*` live in `styles/globals.css` inside `@theme`. They cannot live in `category-glyphs.css` — that file uses `@reference` to globals, and `@theme inline` there does not emit `animate-cg-*` utilities. + +**SVG scale exceptions** — Tailwind v4 `scale-x-*` / `scale-y-*` longhands fail on stroke paths and bottom-anchored rects. Four categories use semantic classes + CSS hover rules in `category-glyphs.css` instead: + +| Category | Classes | Effect | +|----------|---------|--------| +| `text` | `cg-text-line`, `cg-text-line-1` … `3` | motion trail lines scale from left | +| `list` | `cg-list-bar`, `cg-list-grow` / `cg-list-shrink` | top rows expand, bottom rows shrink | +| `graphs` | `cg-graph-bar`, `cg-graph-bar-1` … `4` | bars grow from baseline | +| `widget` | `cg-widget-hour`, `cg-widget-minute` | clock hands rotate | + +Everything else stays Tailwind on the element. + +**CSS helpers** (no hover rules except the four rows above): + +- `cg-motion` — `transform-box`, transitions (`prefers-reduced-motion` respected) +- `cg-motion-frame` — same box/origin, no transform transition (for keyframe loops) +- `cg-progress-arc` — `stroke-dasharray` only +- `cg-feature-tilt` — resting `-8deg` tilt; pair with `motion-safe:group-hover/cg:rotate-0` Pick motion that fits the category: scroll rows slide, button pills swell, container content marquees. diff --git a/styles/globals.css b/styles/globals.css index d9c6dbb7..9b62e123 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -110,6 +110,7 @@ --animate-float: float 3s ease-in-out infinite; --animate-rotate-full: rotate-full 45s linear infinite; + /* Category glyphs — must live here; @theme inline in category-glyphs.css (@reference) does not emit animate-cg-* */ --animate-cg-ping: cg-ping 1.4s cubic-bezier(0.22, 1, 0.36, 1) infinite; --animate-cg-bounce: cg-bounce 1s ease-in-out infinite; --animate-cg-shimmer: cg-shimmer 1.2s ease-in-out infinite;