@alikhalilll/a-smart-skeleton 1.0.0 → 2.0.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.
package/README.md CHANGED
@@ -1,34 +1,35 @@
1
- # @alikhalilll/a-smart-skeleton
1
+ # `@alikhalilll/a-smart-skeleton`
2
2
 
3
- > Smart, dynamic skeleton loader for Vue 3, Nuxt 3+, and vanilla Web Components. Lays content out unseen, measures typography and geometry down to exact canvas font metrics and clip regions, and reveals real content with smooth animations (smart, sequence, sweep, together).
3
+ > Measured skeleton loaders for Vue 3 and Nuxt 3+. Captures your real component's layout — background, border, radius, box-shadow, text lines, and media boxes — and replays it as a pixel-faithful placeholder. One template to maintain. Zero cumulative layout shift.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/@alikhalilll/a-smart-skeleton.svg?color=444)](https://www.npmjs.com/package/@alikhalilll/a-smart-skeleton)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-444.svg)](./LICENSE)
6
+ [![license](https://img.shields.io/npm/l/@alikhalilll/a-smart-skeleton.svg?color=444)](https://github.com/alikhalilll/ali-nuxt-toolkit/blob/main/LICENSE)
7
+ [![docs](https://img.shields.io/badge/docs-live%20demo-5c6ac4)](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton)
7
8
 
8
- ---
9
+ **[Live demo and full documentation →](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton)**
9
10
 
10
- ## Why `@alikhalilll/a-smart-skeleton`?
11
+ Hand-authored skeletons are the second template you have to maintain. Padding drifts, font sizes bump, images start loading later — the skeleton guesses wrong and the reveal jumps. `ASmartSkeleton` lays your real content out invisibly, measures the live geometry, and reconstructs a skeleton from what it saw. One template — the real one — stays the source of truth.
11
12
 
12
- Handcrafted skeleton placeholders easily drift out of sync with real content, resulting in jarring layout shifts when data arrives.
13
+ ---
13
14
 
14
- `@alikhalilll/a-smart-skeleton` solves this by:
15
+ ## Table of contents
15
16
 
16
- 1. **Zero Layout Shift (CLS = 0)**: Content (or a sample schema) is rendered unseen in an off-screen ghost tree. Its geometry is measured, generating matching skeleton bones that occupy the exact layout footprint before first paint.
17
- 2. **Typographic Accuracy**: Using 2D canvas font metrics, text is measured along font lines (`ascender`, `capHeight`, `xHeight`, `baseline`, `descender`). Visual bars follow your actual typography, not generic rectangular gray blocks.
18
- 3. **Clip-Aware Geometry**: Elements nested inside rounded containers or clipped bounds (`overflow: hidden`) compute intersecting bounding boxes (`clipWithin`) so bones mirror real borders and clips.
19
- 4. **Rich Animation Suite**:
20
- - **Bones animations**: `shimmer` (page-wide band), `group-shimmer` (per-skeleton), `pulse` (soft opacity wave), or `none`.
21
- - **Reveal modes**: `smart` (intelligent contextual reveal), `together` (fade together), `sequence` (staggered cascade from top to bottom), `sweep` (glossy gradient sweep across the reveal with automatic stall detection), and `instant`.
22
- - **Height easing**: When content height differs slightly from the skeleton guess, the container smoothly eases to the new height (`280ms`) rather than snapping.
23
- 5. **Stand-in Bones for Images**: Slow images don't block text from revealing. Content reveals on schedule while pending images remain covered by individual stand-in bones until decoded.
24
- 6. **Shape Memory**: Saves layouts under an `id` or auto-key to `localStorage` across user visits, ensuring second visits load instant skeleton shapes before network data resolves.
25
- 7. **SSR Pre-Paint Script**: Bundles a minimal inline script (`ResizeObserver`-driven) that measures server-rendered skeletons after layout and before the very first paint.
17
+ - [Install](#install)
18
+ - [Minimal example](#minimal-example)
19
+ - [Full example — social feed card](#full-example--social-feed-card)
20
+ - [States: `loading`, `content`, `empty`, `error`](#states-loading-content-empty-error)
21
+ - [Hints — `ask-block`, `ask-ignore`, `ask-own`](#hints--ask-block-ask-ignore-ask-own)
22
+ - [Chrome capture](#chrome-capture)
23
+ - [Reveal transitions](#reveal-transitions)
24
+ - [Theming](#theming)
25
+ - [API reference](#api-reference)
26
+ - [Changelog highlights](#changelog-highlights)
26
27
 
27
28
  ---
28
29
 
29
- ## Installation
30
+ ## Install
30
31
 
31
- ```bash
32
+ ```sh
32
33
  pnpm add @alikhalilll/a-smart-skeleton
33
34
  # or
34
35
  npm install @alikhalilll/a-smart-skeleton
@@ -36,260 +37,321 @@ npm install @alikhalilll/a-smart-skeleton
36
37
  yarn add @alikhalilll/a-smart-skeleton
37
38
  ```
38
39
 
39
- Import styles in your main entry (if not using the Nuxt module):
40
+ **Nuxt 3 / 4** — add the module, four components auto-import:
40
41
 
41
42
  ```ts
42
- import '@alikhalilll/a-smart-skeleton/styles.css';
43
+ // nuxt.config.ts
44
+ export default defineNuxtConfig({
45
+ modules: ['@alikhalilll/a-smart-skeleton/nuxt'],
46
+ });
43
47
  ```
44
48
 
45
- ---
46
-
47
- ## Nuxt 3 / 4 Setup
48
-
49
- Add the module to `nuxt.config.ts`:
49
+ **Vue 3 + Vite** — import the stylesheet and optionally register the auto-import resolver:
50
50
 
51
51
  ```ts
52
- export default defineNuxtConfig({
53
- modules: ['@alikhalilll/a-smart-skeleton/nuxt'],
54
- aSmartSkeleton: {
55
- prefix: 'A', // Optional prefix
56
- styles: true, // Automatically injects styles.css
57
- },
58
- });
52
+ // main.ts
53
+ import '@alikhalilll/a-smart-skeleton/styles.css';
59
54
  ```
60
55
 
61
- Components auto-imported in Nuxt:
56
+ ```ts
57
+ // vite.config.ts (optional, for unplugin-vue-components)
58
+ import Components from 'unplugin-vue-components/vite';
59
+ import ASmartSkeletonResolver from '@alikhalilll/a-smart-skeleton/resolver';
60
+
61
+ export default {
62
+ plugins: [Components({ resolvers: [ASmartSkeletonResolver()] })],
63
+ };
64
+ ```
62
65
 
63
- - `<ASmartSkeleton>`
64
- - `<ASmartSkeletonBox>`
65
- - `<ASmartSkeletonSuspense>`
66
- - `<ASmartSkeletonProvider>`
66
+ Full install guide with troubleshooting → [docs/install](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#install).
67
67
 
68
68
  ---
69
69
 
70
- ## Vue 3 Usage
71
-
72
- ### Basic Data-Driven Skeleton
70
+ ## Minimal example
73
71
 
74
- When `data` is `null` or `undefined`, the skeleton measures the `sample` data. Once `data` resolves, it smoothly reveals the real content.
72
+ The simplest useful pattern — bind `data`, pass a `sample`, give it an `id`, done:
75
73
 
76
74
  ```vue
77
75
  <script setup lang="ts">
78
- import { ref } from 'vue';
79
- import { ASmartSkeleton } from '@alikhalilll/a-smart-skeleton';
76
+ import { ref, onMounted } from 'vue';
80
77
 
81
- interface User {
82
- name: string;
83
- role: string;
84
- avatar: string;
85
- }
78
+ const user = ref<User | null>(null);
86
79
 
87
- const SAMPLE_USER: User = {
88
- name: 'Alex Johnson',
89
- role: 'Senior Software Engineer',
90
- avatar: '',
80
+ const SAMPLE_USER = {
81
+ name: 'Sarah Connor',
82
+ role: 'Chief Mitigation Officer',
83
+ avatar: 'https://example.com/avatar.jpg',
91
84
  };
92
85
 
93
- const user = ref<User | null>(null);
94
-
95
- // Simulating fetch
96
- setTimeout(() => {
97
- user.value = {
98
- name: 'Sarah Connor',
99
- role: 'Cyberdyne Systems Lead',
100
- avatar: 'https://example.com/avatar.jpg',
101
- };
102
- }, 1500);
86
+ onMounted(async () => {
87
+ user.value = await $fetch('/api/me');
88
+ });
103
89
  </script>
104
90
 
105
91
  <template>
106
- <ASmartSkeleton :data="user" :sample="SAMPLE_USER" id="user-profile" label="Loading user profile">
92
+ <ASmartSkeleton :data="user" :sample="SAMPLE_USER" id="user-card">
107
93
  <template #default="{ data }">
108
- <div class="user-card">
109
- <img :src="data.avatar" alt="Avatar" class="avatar" />
110
- <div class="meta">
111
- <h3>{{ data.name }}</h3>
112
- <p>{{ data.role }}</p>
113
- </div>
114
- </div>
94
+ <UserCard :user="data" />
115
95
  </template>
116
96
  </ASmartSkeleton>
117
97
  </template>
118
98
  ```
119
99
 
100
+ **Three things to know:**
101
+
102
+ 1. **Always pass a `sample`.** It's what the skeleton measures before real data arrives.
103
+ 2. **Give the skeleton a stable `id`.** Shape memory is keyed by `id` across visits.
104
+ 3. **Give images explicit `width` / `height`** (or `aspect-ratio`) so the skeleton can reserve their space.
105
+
106
+ Live on the site → [Quick start demo](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#quick-start).
107
+
120
108
  ---
121
109
 
122
- ### Reveal Modes
110
+ ## Full example — social feed card
111
+
112
+ A real social feed: `<article>` wrapper with `border` + `bg-white` + `rounded-[12px]`, split sub-components for `Header` (avatar + author + actions menu), body text, `MediaGrid`, and a `footer` divider holding the reactions and comment buttons.
123
113
 
124
- Select how content replaces its skeleton using the `reveal` prop:
114
+ Interactive version with reveal-mode / animation / theme controls → [Customize demo](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#customize).
125
115
 
126
116
  ```vue
127
- <!-- Smart contextual reveal (default) -->
128
- <ASmartSkeleton :data="user" :sample="SAMPLE_USER" reveal="smart">
129
- ...
130
- </ASmartSkeleton>
117
+ <!-- PostList.vue (parent) -->
118
+ <script setup lang="ts">
119
+ import { ref, computed } from 'vue';
120
+ import PostCard from './PostCard.vue';
121
+
122
+ interface Post {
123
+ id: string;
124
+ author: { id: string; name: string; avatar?: string | null; verified?: boolean };
125
+ created_at_relative: string;
126
+ body: string;
127
+ media?: { type: 'image' | 'video'; url: string }[];
128
+ reactions_count: number;
129
+ comments_count: number;
130
+ is_reacted?: boolean;
131
+ }
131
132
 
132
- <!-- Glossy sweep animation across the element -->
133
- <ASmartSkeleton :data="user" :sample="SAMPLE_USER" reveal="sweep">
134
- ...
135
- </ASmartSkeleton>
133
+ // Sample bodies match the real-feed replacements 1:1 in length so the
134
+ // reveal doesn't trigger the plugin's dev-only "layout jumped" warning.
135
+ const SAMPLE_POSTS: Post[] = [
136
+ /* ... three sample shapes ... */
137
+ ];
136
138
 
137
- <!-- Top-to-bottom sequence cascade -->
138
- <ASmartSkeleton :data="user" :sample="SAMPLE_USER" reveal="sequence">
139
- ...
140
- </ASmartSkeleton>
139
+ const feed = ref<Post[] | null>(null);
140
+ async function loadFeed() {
141
+ feed.value = null;
142
+ feed.value = await $fetch<Post[]>('/api/community/posts');
143
+ }
141
144
 
142
- <!-- Simple concurrent crossfade -->
143
- <ASmartSkeleton :data="user" :sample="SAMPLE_USER" reveal="together">
144
- ...
145
- </ASmartSkeleton>
146
- ```
145
+ const theme = computed(() => ({
146
+ light: { base: '#d1d4dc', highlight: '#ffffff' },
147
+ dark: { base: '#31363f', highlight: 'rgba(255,255,255,0.08)' },
148
+ animation: 'shimmer' as const,
149
+ speed: 'slow' as const,
150
+ }));
151
+ </script>
147
152
 
148
- Listen for when the reveal animation completes via `@reveal`:
153
+ <template>
154
+ <ASmartSkeletonProvider :theme="theme" mode="system" reveal="smart">
155
+ <ASmartSkeleton id="community-feed" :data="feed" :sample="SAMPLE_POSTS">
156
+ <template #default="{ data }">
157
+ <div class="flex flex-col gap-4">
158
+ <PostCard v-for="post in data as Post[]" :key="post.id" :post="post" />
159
+ </div>
160
+ </template>
161
+ </ASmartSkeleton>
162
+ </ASmartSkeletonProvider>
163
+ </template>
164
+ ```
149
165
 
150
166
  ```vue
151
- <ASmartSkeleton :data="user" :sample="SAMPLE_USER" @reveal="onRevealed">
152
- ...
153
- </ASmartSkeleton>
167
+ <!-- PostCard.vue (child) -->
168
+ <script setup lang="ts">
169
+ import Header from './Header.vue';
170
+ import MediaGrid from './MediaGrid.vue';
171
+ import Reactions from './Reactions.vue';
172
+
173
+ defineProps<{ post: Post }>();
174
+ </script>
175
+
176
+ <template>
177
+ <article
178
+ :id="`post-${post.id}`"
179
+ class="border border-[#EDF0F3] rounded-[12px] bg-white flex flex-col w-full"
180
+ >
181
+ <Header :post="post" class="px-5 pb-4 pt-4" />
182
+
183
+ <div class="px-5 pb-4 flex flex-col gap-3">
184
+ <p class="text-sm leading-relaxed whitespace-pre-wrap break-words">{{ post.body }}</p>
185
+ <MediaGrid :media="post.media" />
186
+ </div>
187
+
188
+ <footer class="px-5 py-4 border-t border-[#EDF0F3] flex gap-4 items-center">
189
+ <Reactions :count="post.reactions_count" :is-reacted="post.is_reacted" />
190
+ <!--
191
+ `data-ask-block` collapses the icon + count into a single pill bone
192
+ during loading — otherwise the walker recurses and emits a tiny
193
+ media bone for the icon and a separate text bar for the number.
194
+ -->
195
+ <button
196
+ type="button"
197
+ data-ask-block
198
+ class="text-sm text-gray-600 rounded-lg flex gap-1.5 items-center hover:text-primary"
199
+ >
200
+ <svg class="size-4" aria-hidden="true"><!-- comment icon --></svg>
201
+ <span>{{ post.comments_count }}</span>
202
+ </button>
203
+ </footer>
204
+ </article>
205
+ </template>
154
206
  ```
155
207
 
208
+ **What this example exercises** (and what the demo on the docs site shows live):
209
+
210
+ - **Chrome capture** on the painted `<article>` — the skeleton reproduces the background, border, and radius verbatim from the real element's computed style. See [Chrome capture section](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#chrome-capture).
211
+ - **Divider capture** on the `<footer>`'s `border-t` — one-sided borders survive as hairlines in the skeleton.
212
+ - **`data-ask-block`** on action buttons for one-bone pills instead of SVG + text fragments. See [Hints](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#hints--ask-block-ask-ignore-ask-own).
213
+ - **Split sub-component walker** — the plugin follows DOM nodes, not Vue VNodes, so SFC boundaries are transparent.
214
+ - **Provider-driven theming** — switch `mode`, `theme.light`, `theme.dark`, `animation`, `speed`, `reveal` in one place. See [Theming](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#theming).
215
+
156
216
  ---
157
217
 
158
- ### States: Empty & Error
218
+ ## States: `loading`, `content`, `empty`, `error`
159
219
 
160
- Handle error and empty states directly inside the skeleton boundary:
220
+ One component handles all four states. Resolution order is `loading` → `error` → `empty` → `content`:
161
221
 
162
222
  ```vue
163
- <ASmartSkeleton :data="items" :empty="items?.length === 0" :error="error">
223
+ <ASmartSkeleton
224
+ :data="posts"
225
+ :sample="SAMPLE_POSTS"
226
+ :error="fetchError"
227
+ :empty="posts?.length === 0"
228
+ @reveal="onContentRevealed"
229
+ >
164
230
  <template #default="{ data }">
165
- <ItemList :items="data" />
231
+ <PostList :posts="data" />
166
232
  </template>
167
-
168
233
  <template #empty>
169
- <p>No items found in this collection.</p>
234
+ <EmptyIllustration message="No posts yet" />
170
235
  </template>
171
-
172
236
  <template #error="{ error }">
173
- <p class="error">Failed to load items: {{ error }}</p>
237
+ <ErrorBanner :error="error" @retry="reload" />
174
238
  </template>
175
239
  </ASmartSkeleton>
176
240
  ```
177
241
 
242
+ `@reveal` fires once per `skeleton → content` transition. Full behaviour → [States demo](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#states-loading-content-empty-error).
243
+
178
244
  ---
179
245
 
180
- ### Standalone Skeleton Box
246
+ ## Hints — `ask-block`, `ask-ignore`, `ask-own`
181
247
 
182
- For manual placeholder boxes, use `<ASmartSkeletonBox>`:
248
+ Three attributes let you override the walker where it matters:
183
249
 
184
- ```vue
185
- <template>
186
- <div class="card">
187
- <ASmartSkeletonBox width="100%" height="200px" radius="8px" />
188
- <ASmartSkeletonBox width="70%" height="1.2em" radius="4px" />
189
- <ASmartSkeletonBox width="40%" height="1em" radius="4px" />
190
- </div>
191
- </template>
192
- ```
250
+ | Hint | Alias | What it does |
251
+ | ------------ | ---------------------- | -------------------------------------------------------------------------------------------- |
252
+ | `ask-block` | `data-skeleton-block` | Draws the element as **one** bone sized to its box + border-radius. Walker does not recurse. |
253
+ | `ask-ignore` | `data-skeleton-ignore` | Element keeps its layout footprint but emits **zero** bones. |
254
+ | `ask-own` | `data-skeleton-own` | Subtree opts out — you're drawing its skeleton yourself. |
255
+
256
+ See it in action → [Hints demo](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#hints--ask-block-ask-ignore-ask-own).
193
257
 
194
258
  ---
195
259
 
196
- ### Suspense Integration
260
+ ## Chrome capture
197
261
 
198
- `<ASmartSkeletonSuspense>` handles Vue's `<Suspense>` boundary lifecycle, keeping an exact clone of the fallback skeleton in place to crossfade smoothly once async setup finishes:
262
+ When the walker crosses a **painted container** (a background, a border on any side, or a box-shadow) it emits an extra `type: 'chrome'` bone that carries the captured `background`, per-side `border-*`, `border-radius`, and `box-shadow` inline. The bone skips the shimmer animation so it reads as an outline, not a placeholder fill.
199
263
 
200
- ```vue
201
- <template>
202
- <ASmartSkeletonSuspense id="async-quote" reveal="smart">
203
- <AsyncQuoteComponent />
204
- <template #skeleton>
205
- <QuoteCard :quote="SAMPLE_QUOTE" />
206
- </template>
207
- </ASmartSkeletonSuspense>
208
- </template>
209
- ```
264
+ - `backgroundColor` and gradient `backgroundImage` layers are kept. `url(…)` layers are stripped — hero bitmaps don't re-download in the skeleton.
265
+ - Each border side is captured separately, so a `border-t` footer divider survives as a hairline.
266
+ - `border-radius` is captured per-corner where the parent's overflow clips it.
267
+ - Chrome bones ignore `--ask-base` by design — they're reproductions, not placeholders.
268
+
269
+ Full details and before/after shots → [Chrome capture section](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#chrome-capture).
210
270
 
211
271
  ---
212
272
 
213
- ### Global Provider & Theming
273
+ ## Reveal transitions
214
274
 
215
- Configure colors, speed, animations, font metric edges, and CSP nonce across your whole app:
275
+ | Mode | Behaviour |
276
+ | ---------- | ----------------------------------------------------------------------------- |
277
+ | `smart` | Instant if the content lands under ~150 ms, otherwise crossfade. The default. |
278
+ | `together` | Simple crossfade. |
279
+ | `sequence` | Staggered top-to-bottom cascade. |
280
+ | `sweep` | A highlight beam sweeps across the transition edge. |
281
+ | `instant` | Snap, no transition. |
216
282
 
217
- ```vue
218
- <template>
219
- <ASmartSkeletonProvider
220
- mode="system"
221
- :theme="{
222
- light: { base: 'rgb(0 0 0 / 0.07)', highlight: 'rgb(255 255 255 / 0.6)' },
223
- dark: { base: 'rgb(255 255 255 / 0.08)', highlight: 'rgb(255 255 255 / 0.1)' },
224
- radius: '6px',
225
- animation: 'shimmer',
226
- speed: 'slow',
227
- text: { top: 'ascender', bottom: 'descender' },
228
- }"
229
- reveal="smart"
230
- :debug="false"
231
- >
232
- <App />
233
- </ASmartSkeletonProvider>
234
- </template>
235
- ```
283
+ `prefers-reduced-motion: reduce` forces `instant` automatically. Compare them side-by-side → [Reveal transitions](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#reveal-transitions).
236
284
 
237
285
  ---
238
286
 
239
- ## Web Components / Custom Element
287
+ ## Theming
240
288
 
241
- Use the `<a-smart-skeleton-reveal>` custom element in standard HTML without Vue:
289
+ Every surface is driven by CSS custom properties. Set them on any ancestor:
242
290
 
243
- ```html
244
- <script type="module" src="@alikhalilll/a-smart-skeleton/element"></script>
291
+ ```css
292
+ .my-feed {
293
+ --ask-base: #d1d4dc;
294
+ --ask-highlight: #ffffff;
295
+ }
296
+ ```
245
297
 
246
- <a-smart-skeleton-reveal loading id="article-preview" reveal="smart">
247
- <article>
248
- <h2>Article Title Headline</h2>
249
- <p>Detailed body paragraph explaining the context of the article.</p>
250
- </article>
251
- </a-smart-skeleton-reveal>
252
-
253
- <script>
254
- const el = document.querySelector('a-smart-skeleton-reveal');
255
- setTimeout(() => {
256
- el.show(`
257
- <article>
258
- <h2>Real Fetched Title</h2>
259
- <p>This is the real body text loaded over the wire.</p>
260
- </article>
261
- `);
262
- }, 2000);
263
- </script>
298
+ For app-wide defaults, wrap your app in `<ASmartSkeletonProvider>`:
299
+
300
+ ```vue
301
+ <ASmartSkeletonProvider
302
+ mode="system"
303
+ :theme="{
304
+ light: { base: '#d1d4dc', highlight: '#ffffff' },
305
+ dark: { base: '#31363f', highlight: 'rgba(255,255,255,0.08)' },
306
+ animation: 'shimmer',
307
+ speed: 'slow',
308
+ }"
309
+ reveal="smart"
310
+ >
311
+ <App />
312
+ </ASmartSkeletonProvider>
264
313
  ```
265
314
 
315
+ Bones ship as `<div [ask-rect="text|media|box|chrome"]>` in the **light DOM** (no shadow root), so your stylesheet reaches them without `:deep()`. A 4-px minimum radius is applied to every non-chrome bone so nothing reads as a sharp glyph.
316
+
317
+ ```css
318
+ /* Handcrafted ASkeletonBlock-style look on placeholder bones. */
319
+ [ask-rect='text'],
320
+ [ask-rect='box'] {
321
+ box-shadow: inset 0 0 0 1px rgb(169 175 188 / 0.35);
322
+ }
323
+ ```
324
+
325
+ Full CSS variable table + per-type targeting → [Theming](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#theming).
326
+
266
327
  ---
267
328
 
268
- ## Author Hints & Attributes
329
+ ## API reference
269
330
 
270
- Customize measuring behavior with attributes:
331
+ **`<ASmartSkeleton>`** — `data`, `sample`, `loading`, `id`, `label`, `empty`, `error`, `reveal` · emits `@reveal` · slots `default` / `empty` / `error` / `skeleton`.
271
332
 
272
- - `ask-ignore` / `[ask-ignore]`: Skip an element and its children during measurement.
273
- - `ask-block` / `[ask-block]`: Force an element to be measured as a solid rectangle block rather than text lines.
274
- - `ask-own` / `[ask-own]`: Subtree provides its own custom skeleton bones; measuring stops at this boundary.
275
- - `ask-xray` / `[ask-xray]`: Debug mode that exposes the ghost tree through translucent bones.
333
+ **`<ASmartSkeletonProvider>`** — `theme`, `mode`, `reveal`, `debug`, `nonce`.
276
334
 
277
- ---
335
+ **`<ASmartSkeletonBox>`** — standalone shimmer bar · `width`, `height`, `radius`.
278
336
 
279
- ## CSS Variables
337
+ **`<ASmartSkeletonSuspense>`** — Suspense-boundary wrapper that measures a fallback skeleton.
280
338
 
281
- | Variable | Default | Description |
282
- | ------------------- | ------------------------- | --------------------------------------------------------------- |
283
- | `--ask-base` | `rgb(128 128 128 / 0.2)` | Base bone background color |
284
- | `--ask-highlight` | `rgb(255 255 255 / 0.12)` | Shimmer sweep highlight color |
285
- | `--ask-radius` | `0.25em` | Default border-radius for text lines and standalone boxes |
286
- | `--ask-animation` | `shimmer` | Animation: `shimmer`, `group-shimmer`, `pulse`, `none` |
287
- | `--ask-speed` | `slow` | Animation speed: `slow`, `fast` |
288
- | `--ask-text-top` | `ascender` | Top typographic boundary (`ascender`, `cap-height`, `x-height`) |
289
- | `--ask-text-bottom` | `descender` | Bottom typographic boundary (`baseline`, `descender`) |
339
+ **Author hints (HTML attrs)** — `ask-block`, `ask-ignore`, `ask-own` (each with a `data-skeleton-*` alias).
340
+
341
+ Full props / slots / events reference → [API reference](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#api-reference).
290
342
 
291
343
  ---
292
344
 
293
- ## License
345
+ ## Changelog highlights
346
+
347
+ - **Chrome capture** (`type: 'chrome'`) — reproduces card outlines pixel-for-pixel from the consumer's own paint. Partial borders (`border-t` dividers) are preserved. `url(…)` background layers are filtered out so hero bitmaps don't re-download.
348
+ - **Light-DOM bones** — placeholders now live in the light DOM with a `[ask-rect="text|media|box|chrome"]` attribute, so consumer stylesheets can style them per type without `:deep()` on a shadow root.
349
+ - **4-px minimum radius** — every non-chrome bone is floored at 4 px so sharp-cornered placeholders don't read as glyphs of the real content.
350
+ - **Fixed `reveal="instant"`** — previously threw `Cannot read properties of undefined (reading 'hold')`. Unknown reveal strings now fall through to `smart` instead of crashing.
351
+ - **Cleaned stylesheet** — the published `dist/styles.css` is now a hand-authored ~4 KB file. No Tailwind utilities, `@property` globals, or theme tokens leak into the consumer's `:root`.
352
+
353
+ See the full version history → [CHANGELOG](https://github.com/alikhalilll/ali-nuxt-toolkit/blob/main/packages/ui-components/ASmartSkeleton/CHANGELOG.md) · browse source → [GitHub](https://github.com/alikhalilll/ali-nuxt-toolkit/tree/main/packages/ui-components/ASmartSkeleton) · open an issue → [Issues](https://github.com/alikhalilll/ali-nuxt-toolkit/issues).
354
+
355
+ ---
294
356
 
295
357
  MIT © [alikhalilll](https://github.com/alikhalilll)