From ed370dbbe4c52bb95ff5b6ad5460f673b1f5f4f3 Mon Sep 17 00:00:00 2001 From: Gab Date: Fri, 26 Jun 2026 14:35:44 -0500 Subject: [PATCH 1/2] [NO-ISSUE] feat(theme): add shimmer animation preset + keyframes --- packages/theme/dist/v3/globals.css | 1 + packages/theme/dist/v3/globals.scss | 1 + packages/theme/src/tokens/primitives/animations/animate.js | 1 + packages/theme/src/tokens/semantic/animations.js | 4 ++++ 4 files changed, 7 insertions(+) diff --git a/packages/theme/dist/v3/globals.css b/packages/theme/dist/v3/globals.css index 2535a422..2832e8e8 100644 --- a/packages/theme/dist/v3/globals.css +++ b/packages/theme/dist/v3/globals.css @@ -446,6 +446,7 @@ --animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; --animate-bounce: bounce 1s infinite; + --animate-shimmer: shimmer 1.6s linear infinite; --animate-popup-scale-in: popupScaleIn 150ms cubic-bezier(0.39, 0.57, 0.56, 1); --animate-popup-scale-out: popupScaleOut 110ms cubic-bezier(0.55, 0.09, 0.68, 0.53); --ring-offset: 0.5px; diff --git a/packages/theme/dist/v3/globals.scss b/packages/theme/dist/v3/globals.scss index 2535a422..2832e8e8 100644 --- a/packages/theme/dist/v3/globals.scss +++ b/packages/theme/dist/v3/globals.scss @@ -446,6 +446,7 @@ --animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; --animate-bounce: bounce 1s infinite; + --animate-shimmer: shimmer 1.6s linear infinite; --animate-popup-scale-in: popupScaleIn 150ms cubic-bezier(0.39, 0.57, 0.56, 1); --animate-popup-scale-out: popupScaleOut 110ms cubic-bezier(0.55, 0.09, 0.68, 0.53); --ring-offset: 0.5px; diff --git a/packages/theme/src/tokens/primitives/animations/animate.js b/packages/theme/src/tokens/primitives/animations/animate.js index f40f7c7a..8d65852b 100644 --- a/packages/theme/src/tokens/primitives/animations/animate.js +++ b/packages/theme/src/tokens/primitives/animations/animate.js @@ -19,6 +19,7 @@ export const animate = { ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite', pulse: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite', bounce: 'bounce 1s infinite', + shimmer: 'shimmer 1.6s linear infinite', 'popup-scale-in': `popupScaleIn ${duration['moderate-01']} ${curve['productive-entrance']}`, 'popup-scale-out': `popupScaleOut ${duration['fast-02']} ${curve['productive-exit']}`, }; diff --git a/packages/theme/src/tokens/semantic/animations.js b/packages/theme/src/tokens/semantic/animations.js index 8afa5e27..7d61b20b 100644 --- a/packages/theme/src/tokens/semantic/animations.js +++ b/packages/theme/src/tokens/semantic/animations.js @@ -61,6 +61,10 @@ export const animations = () => { '0%': { backgroundColor: 'var(--surface-hover)', fontWeight: '500' }, '100%': { backgroundColor: 'var(--surface-hover)', fontWeight: '500' }, }, + '@keyframes shimmer': { + '0%': { backgroundPosition: '200% 0' }, + '100%': { backgroundPosition: '-200% 0' }, + }, '@keyframes popupScaleIn': { '0%': { opacity: '0', transform: 'scale(0.95)' }, '100%': { opacity: '1', transform: 'scale(1)' }, From a76ce1a4f67c3ab107ba7d8ac1edbb34bf3b68a5 Mon Sep 17 00:00:00 2001 From: Gab Date: Fri, 26 Jun 2026 14:36:00 -0500 Subject: [PATCH 2/2] [NO-ISSUE] feat(webkit): premium linear shimmer for Skeleton --- .specs/skeleton.md | 16 ++++++++-------- .../components/feedback/skeleton/skeleton.vue | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.specs/skeleton.md b/.specs/skeleton.md index 7c0e265c..3cd5c74c 100644 --- a/.specs/skeleton.md +++ b/.specs/skeleton.md @@ -7,9 +7,9 @@ spec_version: 1 figma: url: https://www.figma.com/design/t97pXRs7xME3SJDs5iZ5RF/Webkit?node-id=479-881 node_id: 479:881 -checksum: a3ba11ff0d67cde98427376c7f4f5e8d958933bcaced577819fc26e721e03fe5 +checksum: 5ebe624103f06c4dcdc3fac32789b74cc7fd48142bd277515151e8c6d44e5423 created: 2026-06-23 -last_updated: 2026-06-24 +last_updated: 2026-06-26 --- # Skeleton — Component Spec @@ -38,7 +38,7 @@ import Skeleton from '@aziontech/webkit/skeleton' | `kind` | `'shape' \| 'circle'` | `'shape'` | no | Geometry: a rounded rectangular block (`shape`) or a circle. | | `width` | `string` | `'100%'` | no | CSS width (any length). | | `height` | `string` | `'1rem'` | no | CSS height (any length); for a circle, set equal to `width`. | -| `animated` | `boolean` | `true` | no | Pulse while loading; suppressed under reduced motion. | +| `animated` | `boolean` | `true` | no | Shimmer while loading; suppressed under reduced motion. | ## Events @@ -52,13 +52,13 @@ import Skeleton from '@aziontech/webkit/skeleton' - Visual states: `default` (a single decorative placeholder; no hover/focus/active — it is not interactive) - `data-kind` mirrors the `kind` prop (`shape` | `circle`) -- `data-animated` is present when `animated` is true (drives the pulse) +- `data-animated` is present when `animated` is true (drives the shimmer) ## Motion & Animations | Trigger | Animation / Transition | Token (see `.claude/docs/DESIGN.md` § Animations) | Reduced-motion fallback | |---|---|---|---| -| while loading (`animated`) | `animate-pulse` | theme gap (see Theme gaps) — closest looping opacity primitive | `motion-reduce:animate-none` (static) | +| while loading (`animated`) | `animate-shimmer` (linear gradient sweep over the fill) | `--animate-shimmer` (`animate.js`) + `@keyframes shimmer` (`semantic/animations.js`) | gated behind `motion-safe:` (no sweep) + `motion-reduce:animate-none` (static) | ## Tokens @@ -71,13 +71,13 @@ import Skeleton from '@aziontech/webkit/skeleton' | Figma variable | Temporary primitive | Follow-up | |---|---|---| | `--bg-surface-overlay` (skeleton fill, #4D4D4D dark / #FAFAFA light) | `bg-[var(--bg-surface-overlay)]` (real token, not yet in DESIGN.md) | `TODO: document --bg-surface-overlay in DESIGN.md` | -| pulse animation | `animate-pulse` (Tailwind primitive, present in the compiled theme) | `TODO: add a semantic skeleton/pulse animation to semantic/animations.js` | +| shimmer animation | `animate-shimmer` (`--animate-shimmer` preset in `animate.js` + `@keyframes shimmer` in `semantic/animations.js`) | `TODO: document --animate-shimmer in DESIGN.md § Animations` | ## Accessibility (WCAG 2.1 AA) - The skeleton is decorative: `aria-hidden="true"` so assistive tech skips it. The loading status is conveyed by the surrounding region (e.g. `aria-busy="true"` on the container the consumer owns), not by the placeholder itself. - Not focusable, not interactive: no keyboard map, no focus ring. -- `motion-reduce:animate-none` suppresses the pulse for users who prefer reduced motion. +- The shimmer is gated behind `motion-safe:` and `motion-reduce:animate-none` suppresses it for users who prefer reduced motion (static flat fill). ## Stories (Storybook) @@ -85,7 +85,7 @@ This component has no `size` axis, so the canonical Sizes story does not apply. - Default - Types — composite story rendering both `kind` values (`shape`, `circle`) side-by-side. -- Static — `animated: false`; demonstrates the non-pulsing placeholder. Justified because `animated` is a distinct state of the component and the pulse cannot be seen in a static screenshot. +- Static — `animated: false`; demonstrates the non-shimmering placeholder. Justified because `animated` is a distinct state of the component and the shimmer cannot be seen in a static screenshot. ## Constraints — DO NOT diff --git a/packages/webkit/src/components/feedback/skeleton/skeleton.vue b/packages/webkit/src/components/feedback/skeleton/skeleton.vue index 8c3c873f..b0c6ad2f 100644 --- a/packages/webkit/src/components/feedback/skeleton/skeleton.vue +++ b/packages/webkit/src/components/feedback/skeleton/skeleton.vue @@ -15,7 +15,7 @@ width?: string /** CSS height (any length); for a circle, set equal to `width`. */ height?: string - /** Pulse while loading; suppressed under reduced motion. */ + /** Shimmer while loading; suppressed under reduced motion. */ animated?: boolean } @@ -40,6 +40,6 @@ :data-kind="kind" :data-animated="animated || null" :style="{ width, height }" - class="block bg-[var(--bg-surface-overlay)] data-[kind=shape]:rounded-[var(--shape-elements)] data-[kind=circle]:rounded-full data-[animated]:animate-pulse motion-reduce:animate-none" + class="block bg-[var(--bg-surface-overlay)] data-[kind=shape]:rounded-[var(--shape-elements)] data-[kind=circle]:rounded-full data-[animated]:motion-safe:bg-[linear-gradient(90deg,var(--bg-surface-raised)_0%,var(--bg-surface)_35%,var(--bg-surface-raised)_50%,var(--bg-surface-raised)_65%,var(--bg-surface-raised)_100%)] data-[animated]:motion-safe:bg-[length:200%_100%] data-[animated]:motion-safe:animate-[var(--animate-shimmer)] motion-reduce:animate-none" />