tw-shimmer 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +251 -11
  2. package/package.json +16 -2
  3. package/src/index.css +132 -30
package/README.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  Tailwind CSS v4 plugin for shimmer effects.
4
4
 
5
+ ## Features
6
+
7
+ - Zero-dependency, CSS-only shimmer effects
8
+ - **Sine-eased gradients** for buttery-smooth highlights with no banding
9
+ - OKLCH color space for perceptually uniform color mixing
10
+ - Text shimmer and skeleton/background shimmer variants
11
+ - Fully customizable speed, spread, angle, and colors
12
+
13
+ ## Why Sine-Eased Gradients?
14
+
15
+ Most shimmer effects use simple linear gradients that create visible "banding" - harsh edges where the highlight meets the background. We use [eased gradients](https://www.joshwcomeau.com/css/make-beautiful-gradients/) with **13-17 carefully calculated stops** following a sine ease-in-out curve.
16
+
17
+ This produces gradual transitions at both the center (peak brightness) and edges (fade to transparent), eliminating the banding that plagues typical shimmer implementations. The result is a shimmer that feels organic and polished.
18
+
19
+ Any performance impact is negligible given the gradient is rendered and accelerated as a texture on the GPU.
20
+
5
21
  ## Installation
6
22
 
7
23
  ```bash
@@ -39,31 +55,84 @@ Base utility. Apply to any element with a text color.
39
55
 
40
56
  ### `shimmer-speed-{value}`
41
57
 
42
- Animation speed in pixels per second. Default: `100`px/s.
58
+ Animation speed in pixels per second. Default: `150`px/s for text, `1000`px/s for background.
59
+
60
+ `--shimmer-speed` is inheritable:
61
+
62
+ - If you set `--shimmer-speed` (or use `shimmer-speed-{value}`) on a parent container, both `shimmer` and `shimmer-bg` children will use that value unless they override it.
63
+ - If no value is set anywhere, text shimmer falls back to `150` and background shimmer falls back to `1000`.
64
+
65
+ > **How speed is calculated:** For text shimmer, duration is `(2 × width) / speed`, where width is the shimmer track width in pixels (from `--shimmer-width`). For background shimmer we use the same idea but include an angle-aware overshoot so the stripe starts and ends fully off-screen at shallow angles. At 90deg this reduces to the same `(2 × width) / speed` formula; at more extreme angles the pass time is slightly longer to account for the extra travel. Spread does not affect timing; it only controls how thick the highlight appears.
66
+
67
+ When `shimmer-bg` is used inside a `shimmer-container`, the plugin derives `--shimmer-speed` automatically from the container width so that each shimmer pass takes roughly 1.1–1.6 seconds, depending on container size. Smaller containers run slightly faster, larger containers slightly slower, which feels more consistent than a perfectly flat duration. Text shimmer in containers uses a slightly slower multiplier (~2.2–3.2 second passes) for a more subtle effect. You can still override this by setting `--shimmer-speed` (or using `shimmer-speed-{value}`) on any ancestor or the element itself.
43
68
 
44
69
  ```html
45
70
  <span class="shimmer shimmer-speed-200 text-foreground/40">Fast (200px/s)</span>
46
71
  ```
47
72
 
48
- ### `--shimmer-width-x`
73
+ ### `shimmer-width-{value}`
74
+
75
+ Container width in pixels for animation timing. Default: `200`px for text shimmer, `800`px for background shimmer.
49
76
 
50
- CSS variable for container width in pixels used in speed calculations. Default: `200`px.
77
+ `--shimmer-width` is inheritable:
51
78
 
52
- Set this at runtime to match your actual container width for accurate speed.
79
+ - If you set `--shimmer-width` (or use `shimmer-width-{value}`) on a parent container, both `shimmer` and `shimmer-bg` children will use that value unless they override it.
80
+ - If no value is set anywhere, text shimmer falls back to `200` and background shimmer falls back to `800`.
81
+
82
+ Set this to match your container width for consistent animation speed across different element sizes.
83
+
84
+ When used inside `shimmer-container`, the plugin sets an auto track width based on the container's width; this auto value is used only if you haven't explicitly set `--shimmer-width` yourself. Any explicit `--shimmer-width` (or `shimmer-width-{value}`) still wins and will be used to compute timing.
53
85
 
54
86
  ```tsx
55
- <span class="shimmer" style={{ ["--shimmer-width-x" as string]: "300" }}>
87
+ <span class="shimmer shimmer-width-300 text-foreground/40">Wide container</span>
88
+ ```
89
+
90
+ Or set via CSS variable at runtime:
91
+
92
+ ```tsx
93
+ <span class="shimmer" style={{ ["--shimmer-width" as string]: "300" }}>
56
94
  Wide container
57
95
  </span>
58
96
  ```
59
97
 
60
- Duration formula: `(width * 2) / speed`
98
+ #### CSS-only Auto-Width with `shimmer-container`
99
+
100
+ In modern browsers that support CSS container queries and container units (Chrome 111+, Safari 16.4+, Firefox 110+), you can use the `shimmer-container` helper class to automatically set `--shimmer-width` based on the container's width:
101
+
102
+ ```html
103
+ <div class="shimmer-container flex gap-3">
104
+ <div class="shimmer-bg bg-muted size-10 rounded-full" />
105
+ <div class="flex-1 space-y-2">
106
+ <div class="shimmer-bg bg-muted h-4 w-24 rounded" />
107
+ <div class="shimmer-bg bg-muted h-4 w-full rounded" />
108
+ </div>
109
+ </div>
110
+ ```
111
+
112
+ Inside `shimmer-container`, tw-shimmer automatically:
113
+
114
+ - Sets the shimmer track width based on the container width (`--shimmer-width` auto).
115
+ - Derives the animation speed from the container width so that one shimmer pass takes roughly 1.1–1.6 seconds, depending on container size. Small containers feel snappier, larger containers a bit slower, while keeping the motion perceptually consistent.
116
+ - For `shimmer-bg`, adjusts the highlight spread based on the container width, clamping it between a sensible minimum (about 200px, or the track width if smaller) and maximum (about 300px) so it looks good at any size.
117
+
118
+ These container-based values act as smart defaults. Any explicit `--shimmer-width`, `--shimmer-speed`, or `--shimmer-bg-spread` that you set (for example via `shimmer-width-*`, `shimmer-speed-*`, or `shimmer-bg-spread-*`) will override them, even inside `shimmer-container`.
119
+
120
+ This is primarily useful for **skeleton/background shimmer layouts** where the container already has a stable width defined by its parent or layout context.
121
+
122
+ > **Note:** `shimmer-container` sets `container-type: inline-size`, which prevents the container from sizing based on its contents. This means it's **not recommended for text-only containers** that rely on shrink-to-fit behavior. For those cases, continue using JS or the `shimmer-width-*` utility.
123
+
124
+ In older browsers, `shimmer-container` has no effect and shimmers fall back to their default widths or manually configured values.
61
125
 
62
126
  ### `shimmer-color-{color}`
63
127
 
64
- Shimmer highlight color. Default: `currentColor`.
128
+ Shimmer highlight color. Default: `black` for text (white in dark mode), `white` for bg.
65
129
 
66
- Uses Tailwind color palette.
130
+ `--shimmer-color` is shared and inheritable:
131
+
132
+ - If you set `--shimmer-color` (or use `shimmer-color-{color}`) on a parent container, any `shimmer` or `shimmer-bg` elements inside will use that color unless they define their own.
133
+ - If no value is set anywhere, text shimmer falls back to black in light mode (and white in dark mode), and background shimmer falls back to white (with a subtler default in dark mode).
134
+
135
+ Uses the Tailwind color palette.
67
136
 
68
137
  ```html
69
138
  <span class="shimmer shimmer-color-blue-500 text-blue-500/40"
@@ -73,9 +142,14 @@ Uses Tailwind color palette.
73
142
 
74
143
  ### `shimmer-spread-{spacing}`
75
144
 
76
- Width of the shimmer highlight. Default: `6`ch.
145
+ Width of the shimmer highlight for text shimmer. Default: `6`ch.
146
+
147
+ `--shimmer-spread` is inheritable:
148
+
149
+ - If you set `--shimmer-spread` (or use `shimmer-spread-{spacing}`) on a parent container, any `shimmer` elements inside will use that value unless they override it.
150
+ - If no value is set anywhere, text shimmer falls back to `6ch`.
77
151
 
78
- Uses Tailwind spacing scale.
152
+ Uses the Tailwind spacing scale.
79
153
 
80
154
  ```html
81
155
  <span class="shimmer shimmer-spread-24 text-foreground/40">Wide highlight</span>
@@ -83,7 +157,14 @@ Uses Tailwind spacing scale.
83
157
 
84
158
  ### `shimmer-angle-{degrees}`
85
159
 
86
- Shimmer direction. Default: `90`deg.
160
+ Shimmer direction. Default: `90`deg. Shared with `shimmer-bg`.
161
+
162
+ `--shimmer-angle` is inheritable:
163
+
164
+ - If you set `--shimmer-angle` (or use `shimmer-angle-{degrees}`) on a parent container, any `shimmer` or `shimmer-bg` elements inside will use that angle unless they define their own.
165
+ - If no value is set anywhere, both text and background shimmer fall back to `90deg`.
166
+
167
+ > **Note on angle values:** Avoid exactly `0deg` and `180deg`, as these create extreme values in the animation delay formula (which uses tangent). For diagonal sweeps, use angles in a "safe" range such as 15–75° or 105–165°. Extreme angles can cause very large delays and may visually desync the animation.
87
168
 
88
169
  ```html
89
170
  <span class="shimmer shimmer-angle-45 text-foreground/40"
@@ -91,6 +172,165 @@ Shimmer direction. Default: `90`deg.
91
172
  >
92
173
  ```
93
174
 
175
+ ## Background Shimmer (Skeletons)
176
+
177
+ For skeleton loaders and non-text elements, use `shimmer-bg` instead:
178
+
179
+ ### `shimmer-bg`
180
+
181
+ Background shimmer for skeleton loaders and non-text elements. Use standard Tailwind `bg-*` for base color. Standalone default: 800px width, 1000px/s speed.
182
+
183
+ ```html
184
+ <div class="shimmer-bg bg-muted h-4 w-48 rounded" />
185
+ ```
186
+
187
+ ### Skeleton Example
188
+
189
+ To sync shimmer timing across all skeleton children, you have two options:
190
+
191
+ **Option 1: CSS-only with `shimmer-container` (recommended for modern browsers)**
192
+
193
+ Wrap skeleton elements in `shimmer-container` for consistent timing. The container auto-derives speed so each pass takes roughly 1.1–1.6 seconds depending on width, and clamps the highlight spread to a width-aware band (roughly 200–300px, or the track width if smaller). All `shimmer-bg` children sync to the same animation. Older browsers fall back to standalone defaults.
194
+
195
+ ```html
196
+ <div class="shimmer-container flex gap-3">
197
+ <div class="shimmer-bg bg-muted size-10 rounded-full" />
198
+ <div class="flex-1 space-y-2">
199
+ <div class="shimmer-bg bg-muted h-4 w-24 rounded" />
200
+ <div class="shimmer-bg bg-muted h-4 w-full rounded" />
201
+ <div class="shimmer-bg bg-muted h-4 w-4/5 rounded" />
202
+ </div>
203
+ </div>
204
+ ```
205
+
206
+ **Option 2: Manual width with CSS variable or JS**
207
+
208
+ Set `--shimmer-width` on the container manually. Any `shimmer-bg` or `shimmer` elements inside will inherit this width unless they define their own `shimmer-width-{value}`:
209
+
210
+ ```tsx
211
+ <div class="flex gap-3" style={{ ["--shimmer-width" as string]: "600" }}>
212
+ <div class="shimmer-bg bg-muted size-10 rounded-full" />
213
+ <div class="flex-1 space-y-2">
214
+ <div class="shimmer-bg bg-muted h-4 w-24 rounded" />
215
+ <div class="shimmer-bg bg-muted h-4 w-full rounded" />
216
+ <div class="shimmer-bg bg-muted h-4 w-4/5 rounded" />
217
+ </div>
218
+ </div>
219
+ ```
220
+
221
+ You can also set `--shimmer-speed`, `--shimmer-angle`, and `--shimmer-color` on the same container to keep both text shimmer and background shimmer moving in the same direction, at the same speed, and with the same highlight color. You can also use `shimmer-bg-spread-*` or set `--shimmer-bg-spread` on the container (or individual skeleton elements) to override the container's auto spread and manually control the background highlight band width.
222
+
223
+ ### `shimmer-color-{color}` (with shimmer-bg)
224
+
225
+ The same `shimmer-color-*` utility works for both text and bg shimmer:
226
+
227
+ ```html
228
+ <div class="shimmer-bg shimmer-color-blue-100 h-4 w-48 rounded bg-blue-300" />
229
+ ```
230
+
231
+ ### Angled Skeleton Shimmer
232
+
233
+ Use `shimmer-angle-{degrees}` (shared with text shimmer) for diagonal sweeps. This works with both `shimmer-container` and manual width configuration:
234
+
235
+ ```html
236
+ <div class="shimmer-container shimmer-angle-15 flex gap-3">
237
+ <div class="shimmer-bg bg-muted size-10 rounded-full" />
238
+ <div class="shimmer-bg bg-muted h-4 w-full rounded" />
239
+ </div>
240
+ ```
241
+
242
+ ## Advanced: Position-Based Sync
243
+
244
+ > **Note:** These utilities are **optional** and only relevant for angled shimmers (`shimmer-angle-*` ≠ 90°). Most users can skip this section.
245
+
246
+ ### `shimmer-x-{value}` / `shimmer-y-{value}`
247
+
248
+ Manual position hints for syncing angled shimmer animations across multiple elements. When using diagonal shimmers on layouts with multiple skeleton elements (e.g., avatar + text lines), the highlights may appear slightly out of sync because each element animates independently.
249
+
250
+ These utilities let you specify each element's approximate position (in pixels) relative to a shared container. The plugin uses these values to calculate animation delays, aligning the diagonal sweep across elements.
251
+
252
+ - `shimmer-x-*`: Horizontal offset from container left (unitless, interpreted as pixels)
253
+ - `shimmer-y-*`: Vertical offset from container top (unitless, interpreted as pixels)
254
+
255
+ **How it works:** The x/y values feed into an animation-delay formula that accounts for the shimmer angle. This creates the illusion of a single diagonal highlight passing through all elements.
256
+
257
+ > **Tip:** For larger or rounded elements (like avatars), use the element's approximate center rather than its top-left corner. The sync math treats each element as a single reference point, so using the center better matches where the shimmer visually "passes through" the element. Finding good offsets may still require some trial and error.
258
+ >
259
+ > For example, a 40×40 avatar at the left edge of the container would often look better with `shimmer-x-20 shimmer-y-20` than `shimmer-x-0 shimmer-y-0`.
260
+
261
+ > **Angle caveat:** Position sync works best with moderate angles (15–75° or 105–165°). Avoid exactly 0° and 180° as these cause extreme delay values. See the `shimmer-angle-*` section for details.
262
+
263
+ ```html
264
+ <div class="shimmer-container shimmer-angle-15">
265
+ <div
266
+ class="shimmer-bg shimmer-x-20 shimmer-y-20 bg-muted size-10 rounded-full"
267
+ />
268
+ <div class="shimmer-bg shimmer-x-52 shimmer-y-0 bg-muted h-4 w-24 rounded" />
269
+ <div
270
+ class="shimmer-bg shimmer-x-52 shimmer-y-24 bg-muted h-4 w-full rounded"
271
+ />
272
+ </div>
273
+ ```
274
+
275
+ ### Advanced: Offscreen Overshoot (background shimmer)
276
+
277
+ For angled background shimmers (`shimmer-bg` with `shimmer-angle-*`), the plugin automatically adjusts how far the shimmer stripe starts and ends off-screen based on the angle. This helps avoid the highlight being visible at the left edge at the very start of the animation, especially at shallow angles (e.g. 15–30°).
278
+
279
+ Internally, this uses an angle-aware overshoot multiplier:
280
+
281
+ - Steep angles (~90°) → multiplier ≈ 1 (no extra overshoot)
282
+ - Moderate angles (e.g. 45°) → multiplier ≈ 1.3
283
+ - Shallow angles (e.g. 15° or 165°) → multiplier up to ≈ 3 (clamped)
284
+
285
+ You can override this behavior per element or per container using the CSS variable:
286
+
287
+ ```css
288
+ --shimmer-offscreen-multiplier: 1.5; /* or any positive value */
289
+ ```
290
+
291
+ For example:
292
+
293
+ ```html
294
+ <div
295
+ class="shimmer-container"
296
+ style="--shimmer-angle: 20deg; --shimmer-offscreen-multiplier: 2;"
297
+ >
298
+ <div class="shimmer-bg bg-muted h-4 w-full rounded"></div>
299
+ </div>
300
+ ```
301
+
302
+ Most users never need to set this manually; it exists for power users who are tuning very shallow angles or unusual aspect ratios.
303
+
304
+ ## Browser Support & Accessibility
305
+
306
+ ### Modern CSS Requirements
307
+
308
+ `tw-shimmer` uses modern CSS features for the best visual quality:
309
+
310
+ - `oklch` and `color-mix` for perceptually uniform color mixing
311
+ - `translate` as an independent transform property
312
+ - `overflow: clip` for precise clipping (with `overflow: hidden` fallback)
313
+
314
+ These features are supported in all modern browsers (Chrome 111+, Firefox 113+, Safari 16.4+). Older browsers will degrade gracefully but may not render the full effect.
315
+
316
+ ## Limitations
317
+
318
+ `tw-shimmer` is intentionally **zero-dependency and CSS-only**. This keeps it lightweight and framework-agnostic, but it comes with trade-offs:
319
+
320
+ - **`shimmer-container` auto-width/auto-speed/auto-spread are progressive enhancements:** The `shimmer-container` helper uses modern CSS container units (`cqw`) and is gated by `@supports (width: 1cqw)`. In unsupported browsers, `shimmer-container` has no effect and shimmer elements fall back to their default widths or manually configured values.
321
+
322
+ - **`shimmer-container` prevents shrink-to-fit sizing:** Because it sets `container-type: inline-size`, the container cannot size itself based on its contents. This makes it unsuitable for text-only containers that rely on intrinsic sizing.
323
+
324
+ - **No automatic layout detection:** CSS cannot access runtime element positions, so perfectly unified diagonal shimmer across arbitrarily positioned elements cannot be fully automated.
325
+
326
+ - **Vertical shimmers just work:** For `shimmer-angle-90` (the default), all elements naturally sync without any extra configuration.
327
+
328
+ - **Angled shimmers are best-effort:** The `shimmer-x-*` / `shimmer-y-*` utilities enable manual sync tuning, but some minor desync or visual artifacts may still occur—especially at shallow angles or with large rounded shapes.
329
+
330
+ - **Complex layouts may need JavaScript:** If your application requires truly "physically correct" shimmer alignment across a complex layout, that would require measuring element positions at runtime and setting CSS variables via JavaScript. `tw-shimmer` intentionally does not do this.
331
+
332
+ For most skeleton loaders and text shimmer use cases, the defaults work well. The advanced position utilities are there for power users who want fine-grained control over angled animations.
333
+
94
334
  ## License
95
335
 
96
336
  MIT
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tw-shimmer",
3
3
  "description": "Tailwind CSS v4 plugin for shimmer effects",
4
- "version": "0.2.2",
4
+ "version": "0.4.0",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "exports": {
@@ -14,7 +14,18 @@
14
14
  "src",
15
15
  "README.md"
16
16
  ],
17
- "sideEffects": false,
17
+ "sideEffects": [
18
+ "./src/index.css"
19
+ ],
20
+ "keywords": [
21
+ "tailwindcss",
22
+ "tailwind",
23
+ "shimmer",
24
+ "skeleton",
25
+ "loading",
26
+ "animation",
27
+ "css"
28
+ ],
18
29
  "publishConfig": {
19
30
  "access": "public",
20
31
  "provenance": true
@@ -26,5 +37,8 @@
26
37
  },
27
38
  "bugs": {
28
39
  "url": "https://github.com/assistant-ui/assistant-ui/issues"
40
+ },
41
+ "peerDependencies": {
42
+ "tailwindcss": ">=4.0.0-0"
29
43
  }
30
44
  }
package/src/index.css CHANGED
@@ -1,53 +1,155 @@
1
+ @property --shimmer-track-height {
2
+ syntax: '<length>';
3
+ inherits: true;
4
+ initial-value: 200px;
5
+ }
6
+
7
+ @property --shimmer-angle {
8
+ syntax: '<angle>';
9
+ inherits: true;
10
+ initial-value: 15deg;
11
+ }
12
+
1
13
  @theme inline {
2
- @keyframes shimmer {
3
- 0% {
4
- background-position: 150% 0;
5
- }
6
- 100% {
7
- background-position: -50% 0;
14
+ @keyframes tw-shimmer {
15
+ from {
16
+ background-position: 100% 0;
8
17
  }
9
18
  }
10
19
  }
11
20
 
12
21
  @utility shimmer {
13
- --shimmer-speed: 100;
14
- --shimmer-width-x: 200;
15
- --shimmer-color: black;
16
- --shimmer-spread: 6ch;
17
- --shimmer-angle: 90deg;
18
- background:
19
- linear-gradient(
20
- var(--shimmer-angle),
21
- transparent calc(50% - var(--shimmer-spread) * 0.5),
22
- color-mix(in oklch, currentColor, var(--shimmer-color) 90%) 50%,
23
- transparent calc(50% + var(--shimmer-spread) * 0.5)
24
- )
25
- 0 0 / 200% 100% no-repeat,
26
- linear-gradient(currentColor, currentColor);
27
- background-clip: text;
28
- -webkit-background-clip: text;
29
- -webkit-text-fill-color: transparent;
30
- animation: shimmer
31
- calc((var(--shimmer-width-x) * 2) / var(--shimmer-speed) * 1s) infinite
32
- linear;
22
+ --_gradient-width: calc(var(--_spread) + var(--shimmer-track-height) * tan(var(--shimmer-angle)));
23
+ --_active-distance: calc(var(--shimmer-track-width, 200px) + var(--_gradient-width));
24
+ --_duration: var(--shimmer-duration, calc(var(--_active-distance) / var(--_speed) / 1px * 1000));
25
+ --_repeat-delay: var(--shimmer-repeat-delay, calc(20000 / var(--_speed)));
26
+ --_repeat-delay-px: calc(var(--_repeat-delay) * var(--_active-distance) / var(--_duration));
27
+ --_xy-offset-px: calc((var(--shimmer-x, 0) + var(--shimmer-y, 0) * tan(var(--shimmer-angle))) * 1px);
28
+
29
+ --_bg-width: calc(
30
+ 100% +
31
+ var(--shimmer-track-width, 100%) +
32
+ var(--_gradient-width) +
33
+ var(--_repeat-delay-px)
34
+ );
35
+
36
+ --_position: calc(
37
+ var(--shimmer-track-width, (100% - var(--_gradient-width) - var(--_repeat-delay-px)) / 2)
38
+ + var(--_gradient-width) / 2
39
+ + var(--_repeat-delay-px)
40
+ - var(--_xy-offset-px)
41
+ );
42
+
43
+ &:not(.shimmer-bg) {
44
+ --_speed: var(--shimmer-speed, 200);
45
+ --_spread: var(--shimmer-spread, calc(4ch + 80px));
46
+ --_bg: currentColor;
47
+ --_fg: var(--shimmer-color, oklch(from currentColor l c h / calc(alpha * 0.2)));
48
+
49
+ background-clip: text;
50
+ -webkit-text-fill-color: transparent;
51
+ }
52
+
53
+ &.shimmer-bg {
54
+ --_speed: var(--shimmer-speed, 1000);
55
+ --_spread: var(--shimmer-spread, 480px);
56
+ --_bg: transparent;
57
+ --_fg: var(--shimmer-color, oklch(from currentColor 0 c h / 0.06));
58
+ }
33
59
 
34
60
  @variant dark {
35
- --shimmer-color: white;
61
+ &:not(.shimmer-bg) {
62
+ --_fg: var(--shimmer-color, oklch(from currentColor max(0.8, calc(l + 0.4)) c h / calc(alpha + 0.4)));
63
+ }
64
+ &.shimmer-bg {
65
+ --_fg: var(--shimmer-color, oklch(from currentColor 0 c h / 0.30));
66
+ }
67
+ }
68
+
69
+ @-moz-document url-prefix() {
70
+ & {
71
+ --_duration: var(--shimmer-duration, calc(375000 / var(--_speed)));
72
+ }
36
73
  }
74
+
75
+ /* Use a sine ease-in-out curve (17 stops) for smooth falloff */
76
+ --_mix-96: color-mix(in oklch, var(--_fg), var(--_bg) 96%);
77
+ --_mix-83: color-mix(in oklch, var(--_fg), var(--_bg) 83%);
78
+ --_mix-67: color-mix(in oklch, var(--_fg), var(--_bg) 67%);
79
+ --_mix-50: color-mix(in oklch, var(--_fg), var(--_bg) 50%);
80
+ --_mix-33: color-mix(in oklch, var(--_fg), var(--_bg) 33%);
81
+ --_mix-17: color-mix(in oklch, var(--_fg), var(--_bg) 17%);
82
+ --_mix-4: color-mix(in oklch, var(--_fg), var(--_bg) 4%);
83
+
84
+ background:
85
+ linear-gradient(
86
+ calc(90deg + var(--shimmer-angle)),
87
+ var(--_bg) calc(var(--_position) - var(--_spread) * 0.5),
88
+ var(--_mix-96) calc(var(--_position) - var(--_spread) * 0.44),
89
+ var(--_mix-83) calc(var(--_position) - var(--_spread) * 0.37),
90
+ var(--_mix-67) calc(var(--_position) - var(--_spread) * 0.31),
91
+ var(--_mix-50) calc(var(--_position) - var(--_spread) * 0.25),
92
+ var(--_mix-33) calc(var(--_position) - var(--_spread) * 0.19),
93
+ var(--_mix-17) calc(var(--_position) - var(--_spread) * 0.12),
94
+ var(--_mix-4) calc(var(--_position) - var(--_spread) * 0.06),
95
+ var(--_fg) var(--_position),
96
+ var(--_mix-4) calc(var(--_position) + var(--_spread) * 0.06),
97
+ var(--_mix-17) calc(var(--_position) + var(--_spread) * 0.12),
98
+ var(--_mix-33) calc(var(--_position) + var(--_spread) * 0.19),
99
+ var(--_mix-50) calc(var(--_position) + var(--_spread) * 0.25),
100
+ var(--_mix-67) calc(var(--_position) + var(--_spread) * 0.31),
101
+ var(--_mix-83) calc(var(--_position) + var(--_spread) * 0.37),
102
+ var(--_mix-96) calc(var(--_position) + var(--_spread) * 0.44),
103
+ var(--_bg) calc(var(--_position) + var(--_spread) * 0.5)
104
+ )
105
+ 0 0 / var(--_bg-width) 100%
106
+ no-repeat;
107
+
108
+ animation: tw-shimmer 1s linear 0s infinite backwards;
109
+ animation-duration: calc((var(--_duration) + var(--_repeat-delay)) * 1ms);
37
110
  }
38
111
 
39
112
  @utility shimmer-speed-* {
40
113
  --shimmer-speed: --value(number);
41
114
  }
42
115
 
116
+ @utility shimmer-duration-* {
117
+ --shimmer-duration: --value(integer);
118
+ }
119
+
120
+ @utility shimmer-repeat-delay-* {
121
+ --shimmer-repeat-delay: --value(integer);
122
+ }
123
+
124
+ @utility shimmer-invert {
125
+ &:not(.shimmer-bg) {
126
+ --shimmer-color: oklch(from currentColor min(calc(l - 0.4), 0.2) c h / calc(alpha + 0.4));
127
+
128
+ @variant dark {
129
+ --shimmer-color: oklch(from currentColor l c h / calc(alpha * 0.2));
130
+ }
131
+ }
132
+ }
133
+
43
134
  @utility shimmer-color-* {
44
- --shimmer-color: --value(--color-*);
135
+ --alpha: 100%;
136
+ --alpha: calc(--modifier(integer) * 1%);
137
+ --shimmer-color: --alpha(--value(--color, [color]) / var(--alpha, 100%));
45
138
  }
46
139
 
47
140
  @utility shimmer-spread-* {
48
- --shimmer-spread: --spacing(--value(integer));
141
+ --shimmer-spread: calc(--value(integer) * 1px);
49
142
  }
50
143
 
51
144
  @utility shimmer-angle-* {
52
- --shimmer-angle: calc(--value(number) * 1deg);
145
+ --shimmer-angle: calc(--value(integer) * 1deg);
146
+ }
147
+
148
+ @utility shimmer-container {
149
+ container-type: inline-size;
150
+
151
+ & .shimmer {
152
+ --shimmer-track-width: 100cqw;
153
+ --shimmer-track-height: max(100cqh, 50cqw, 200px);
154
+ }
53
155
  }