@lukas_holdings/castdom 1.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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +707 -0
  3. package/bin/castdom.js +2 -0
  4. package/dist/astro.cjs +86 -0
  5. package/dist/astro.cjs.map +1 -0
  6. package/dist/astro.d.cts +88 -0
  7. package/dist/astro.d.ts +88 -0
  8. package/dist/astro.js +80 -0
  9. package/dist/astro.js.map +1 -0
  10. package/dist/chunk-COLESJ66.js +57 -0
  11. package/dist/chunk-COLESJ66.js.map +1 -0
  12. package/dist/chunk-EJRNKHL5.js +31 -0
  13. package/dist/chunk-EJRNKHL5.js.map +1 -0
  14. package/dist/chunk-JRQ6EVQP.cjs +35 -0
  15. package/dist/chunk-JRQ6EVQP.cjs.map +1 -0
  16. package/dist/chunk-KGLTVTHU.js +73 -0
  17. package/dist/chunk-KGLTVTHU.js.map +1 -0
  18. package/dist/chunk-O4OOMGGM.cjs +198 -0
  19. package/dist/chunk-O4OOMGGM.cjs.map +1 -0
  20. package/dist/chunk-ONS533CQ.js +104 -0
  21. package/dist/chunk-ONS533CQ.js.map +1 -0
  22. package/dist/chunk-ORY4OMZ5.cjs +110 -0
  23. package/dist/chunk-ORY4OMZ5.cjs.map +1 -0
  24. package/dist/chunk-QLEBTZIB.cjs +64 -0
  25. package/dist/chunk-QLEBTZIB.cjs.map +1 -0
  26. package/dist/chunk-XS5HAU5E.cjs +109 -0
  27. package/dist/chunk-XS5HAU5E.cjs.map +1 -0
  28. package/dist/chunk-YDT4TPB7.cjs +84 -0
  29. package/dist/chunk-YDT4TPB7.cjs.map +1 -0
  30. package/dist/chunk-ZBJB7WVV.js +193 -0
  31. package/dist/chunk-ZBJB7WVV.js.map +1 -0
  32. package/dist/chunk-ZWZ5ZLJE.js +103 -0
  33. package/dist/chunk-ZWZ5ZLJE.js.map +1 -0
  34. package/dist/cli.js +135 -0
  35. package/dist/index.cjs +540 -0
  36. package/dist/index.cjs.map +1 -0
  37. package/dist/index.d.cts +176 -0
  38. package/dist/index.d.ts +176 -0
  39. package/dist/index.js +440 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/next.cjs +65 -0
  42. package/dist/next.cjs.map +1 -0
  43. package/dist/next.d.cts +72 -0
  44. package/dist/next.d.ts +72 -0
  45. package/dist/next.js +48 -0
  46. package/dist/next.js.map +1 -0
  47. package/dist/react.cjs +30 -0
  48. package/dist/react.cjs.map +1 -0
  49. package/dist/react.d.cts +70 -0
  50. package/dist/react.d.ts +70 -0
  51. package/dist/react.js +7 -0
  52. package/dist/react.js.map +1 -0
  53. package/dist/renderer-B1R7u2wm.d.ts +30 -0
  54. package/dist/renderer-Bfzjr6l9.d.cts +30 -0
  55. package/dist/ssr.cjs +46 -0
  56. package/dist/ssr.cjs.map +1 -0
  57. package/dist/ssr.d.cts +83 -0
  58. package/dist/ssr.d.ts +83 -0
  59. package/dist/ssr.js +5 -0
  60. package/dist/ssr.js.map +1 -0
  61. package/dist/types-ChD5jENU.d.cts +105 -0
  62. package/dist/types-ChD5jENU.d.ts +105 -0
  63. package/dist/vite.cjs +83 -0
  64. package/dist/vite.cjs.map +1 -0
  65. package/dist/vite.d.cts +81 -0
  66. package/dist/vite.d.ts +81 -0
  67. package/dist/vite.js +77 -0
  68. package/dist/vite.js.map +1 -0
  69. package/package.json +130 -0
package/README.md ADDED
@@ -0,0 +1,707 @@
1
+ <p align="center">
2
+ <img src="https://img.shields.io/npm/v/castdom?style=flat-square&color=000" alt="npm version" />
3
+ <img src="https://img.shields.io/badge/license-MIT-000?style=flat-square" alt="license" />
4
+ <img src="https://img.shields.io/badge/runtime-CSS%20only-000?style=flat-square" alt="CSS only runtime" />
5
+ <img src="https://img.shields.io/badge/bundle-~14KB-000?style=flat-square" alt="bundle size" />
6
+ <img src="https://img.shields.io/badge/tests-71%20passed-000?style=flat-square" alt="tests" />
7
+ </p>
8
+
9
+ <h1 align="center">CastDOM</h1>
10
+
11
+ <p align="center">
12
+ <strong>Pixel-perfect skeleton loading screens, extracted from your real DOM.</strong><br/>
13
+ No manual measurement. No hand-tuned placeholders. Zero-config, SSR-first, CSS-only runtime.
14
+ </p>
15
+
16
+ <p align="center">
17
+ <a href="#quick-start">Quick Start</a> &bull;
18
+ <a href="#how-it-works">How It Works</a> &bull;
19
+ <a href="#react">React</a> &bull;
20
+ <a href="#nextjs">Next.js</a> &bull;
21
+ <a href="#astro">Astro</a> &bull;
22
+ <a href="#vite">Vite</a> &bull;
23
+ <a href="#ssr--seo">SSR & SEO</a> &bull;
24
+ <a href="#api-reference">API</a>
25
+ </p>
26
+
27
+ ---
28
+
29
+ ## Why CastDOM?
30
+
31
+ Every skeleton screen you've ever built was a lie. You eyeballed the widths, guessed the heights, and prayed the layout didn't shift. CastDOM fixes that.
32
+
33
+ It reads `getBoundingClientRect()` on every visible element in your component, stores the positions as a flat array of bones, and renders them as CSS-only rectangles that match your real layout **exactly** — at every breakpoint.
34
+
35
+ ```
36
+ Your Component CastDOM Skeleton
37
+ +----------------------------------+ +----------------------------------+
38
+ | [====] [O] | | [####] [O] |
39
+ | | | |
40
+ | Some heading text | | [################] |
41
+ | A longer paragraph that wraps | | [##########################] |
42
+ | across multiple lines of text | | [########################] |
43
+ | | | |
44
+ | [ Button ] | | [##########] |
45
+ +----------------------------------+ +----------------------------------+
46
+ Real DOM Extracted Skeleton
47
+ ```
48
+
49
+ ### Key Advantages
50
+
51
+ - **Pixel-perfect** — bones are extracted from your actual rendered DOM, not approximated
52
+ - **SSR-first** — skeletons render server-side as real HTML. Crawlers see them. Zero CLS
53
+ - **CSS-only runtime** — no JavaScript needed to display skeletons. Just CSS animations
54
+ - **Content-aware** — detects text, headings, images, avatars, buttons, and inputs
55
+ - **Responsive** — captures at multiple breakpoints (375px, 768px, 1280px by default)
56
+ - **Compressed** — delta-encoded bone data is 60-70% smaller than raw JSON
57
+ - **Accessible** — `role="status"`, `aria-busy`, `aria-label`, `prefers-reduced-motion`
58
+ - **Framework-native** — adapters for Next.js, Astro, Vite, and vanilla JS
59
+ - **Zero runtime dependencies** — the client bundle is pure CSS + tiny registry
60
+
61
+ ---
62
+
63
+ ## Quick Start
64
+
65
+ ### 1. Install
66
+
67
+ ```bash
68
+ npm install castdom
69
+ ```
70
+
71
+ ### 2. Mark your components
72
+
73
+ Add `data-castdom` attributes to the components you want skeletons for:
74
+
75
+ ```html
76
+ <div data-castdom="user-card">
77
+ <img class="avatar" src="..." />
78
+ <h2>Jane Doe</h2>
79
+ <p>Software Engineer at Acme Corp</p>
80
+ <button>Follow</button>
81
+ </div>
82
+ ```
83
+
84
+ ### 3. Configure
85
+
86
+ ```bash
87
+ npx castdom init
88
+ ```
89
+
90
+ This creates `castdom.config.json`:
91
+
92
+ ```json
93
+ {
94
+ "devServer": "http://localhost:3000",
95
+ "outDir": ".castdom",
96
+ "breakpoints": [375, 768, 1280],
97
+ "color": "#e0e0e0",
98
+ "shimmerColor": "#f0f0f0",
99
+ "animationDuration": 1500,
100
+ "contentAware": true,
101
+ "targets": [
102
+ {
103
+ "name": "user-card",
104
+ "selector": "[data-castdom=\"user-card\"]",
105
+ "route": "/"
106
+ }
107
+ ]
108
+ }
109
+ ```
110
+
111
+ ### 4. Extract
112
+
113
+ Start your dev server, then:
114
+
115
+ ```bash
116
+ npx castdom build
117
+ ```
118
+
119
+ ```
120
+ CastDOM Build
121
+ Server: http://localhost:3000
122
+ Breakpoints: 375, 768, 1280px
123
+ Targets: 1
124
+ Output: .castdom
125
+
126
+ Skeletons: 1
127
+ Bones: 12
128
+ Raw size: 2.1 KB
129
+ Compressed: 0.8 KB (62% smaller)
130
+ Files: 8
131
+ Total time: 1240ms
132
+ ```
133
+
134
+ ### 5. Use
135
+
136
+ ```tsx
137
+ // Import the loader once at your app entry
138
+ import ".castdom/loader.js";
139
+
140
+ // Use anywhere
141
+ import { CastDOM } from "castdom/react";
142
+
143
+ function UserProfile({ userId }) {
144
+ const { data, isLoading } = useFetch(`/api/users/${userId}`);
145
+
146
+ return (
147
+ <CastDOM name="user-card" loading={isLoading}>
148
+ <UserCard data={data} />
149
+ </CastDOM>
150
+ );
151
+ }
152
+ ```
153
+
154
+ That's it. The skeleton matches your real layout perfectly, at every screen size.
155
+
156
+ ---
157
+
158
+ ## How It Works
159
+
160
+ ### Extraction Pipeline
161
+
162
+ ```
163
+ Dev Server Playwright Extractor Compiler
164
+ | | | |
165
+ | load page | | |
166
+ |<------------------| | |
167
+ | set viewport | | |
168
+ |<------------------| | |
169
+ | | inject script | |
170
+ | |------------------->| |
171
+ | | | walk DOM tree |
172
+ | | | getBoundingRect |
173
+ | | | detect kinds |
174
+ | | bone data | |
175
+ | |<-------------------| |
176
+ | | | |
177
+ | | compile, compress, codegen |
178
+ | |-------------------------------------->|
179
+ | | | |
180
+ | | | .castdom/
181
+ | | | manifest.json
182
+ | | | castdom.css
183
+ | | | loader.js
184
+ | | | index.d.ts
185
+ ```
186
+
187
+ ### Bone Data Model
188
+
189
+ Each element becomes a **bone** — a minimal rectangle descriptor:
190
+
191
+ ```typescript
192
+ interface Bone {
193
+ x: number; // X position relative to container
194
+ y: number; // Y position relative to container
195
+ w: number; // Width
196
+ h: number; // Height
197
+ r: number; // Border radius (9999 = circle)
198
+ kind?: BoneKind; // "text" | "heading" | "image" | "avatar" | "button" | ...
199
+ }
200
+ ```
201
+
202
+ ### Content-Aware Detection
203
+
204
+ CastDOM doesn't just make rectangles. It detects what each element **is**:
205
+
206
+ | Element | Detection | Skeleton Shape |
207
+ |---------|-----------|----------------|
208
+ | `<h1>`-`<h6>` | Tag name | Rounded rectangle, heading height |
209
+ | `<p>`, text nodes | Text content check | Multiple line rectangles |
210
+ | `<img>` | Tag name + aspect ratio | Rectangle with image proportions |
211
+ | Avatar | Small + `border-radius: 50%` | Circle |
212
+ | `<button>` | Tag or `role="button"` | Pill shape |
213
+ | `<input>` | Tag name | Bordered rectangle |
214
+ | `<hr>` | Tag name | Thin divider line |
215
+
216
+ ### Compression
217
+
218
+ Bone data is compressed using delta encoding and zigzag varint packing:
219
+
220
+ ```
221
+ Raw JSON: 2,100 bytes
222
+ Compressed: 800 bytes (62% smaller)
223
+ Base64: 540 bytes (74% smaller)
224
+ ```
225
+
226
+ The compression pipeline:
227
+ 1. Sort bones top-to-bottom, left-to-right
228
+ 2. Delta-encode X/Y positions
229
+ 3. Quantize to half-pixels
230
+ 4. Zigzag encode signed integers
231
+ 5. Variable-length integer packing
232
+
233
+ ---
234
+
235
+ ## React
236
+
237
+ ### `<CastDOM>` Component
238
+
239
+ ```tsx
240
+ import { CastDOM } from "castdom/react";
241
+
242
+ <CastDOM
243
+ name="user-card" // Registered skeleton name
244
+ loading={isLoading} // Show skeleton when true
245
+ animation="shimmer" // "shimmer" | "pulse" | "wave" | "none"
246
+ color="#e0e0e0" // Base bone color
247
+ shimmerColor="#f0f0f0" // Shimmer highlight color
248
+ duration={1500} // Animation duration (ms)
249
+ className="my-skeleton" // Additional CSS class
250
+ onSkeletonShow={() => console.log("Skeleton visible")}
251
+ onContentShow={() => console.log("Content rendered")}
252
+ ariaLabel="Loading user profile"
253
+ >
254
+ <UserCard data={data} />
255
+ </CastDOM>
256
+ ```
257
+
258
+ ### `<CastDOMStyle>` Component
259
+
260
+ Add to your `<head>` for critical CSS:
261
+
262
+ ```tsx
263
+ import { CastDOMStyle } from "castdom/react";
264
+
265
+ function Layout({ children }) {
266
+ return (
267
+ <html>
268
+ <head>
269
+ <CastDOMStyle />
270
+ </head>
271
+ <body>{children}</body>
272
+ </html>
273
+ );
274
+ }
275
+ ```
276
+
277
+ ### `useCastDOM` Hook
278
+
279
+ ```tsx
280
+ import { useCastDOM } from "castdom/react";
281
+
282
+ function CustomSkeleton() {
283
+ const { exists, data, breakpoint, css, html } = useCastDOM("user-card");
284
+
285
+ if (!exists) return <FallbackSkeleton />;
286
+
287
+ return <div dangerouslySetInnerHTML={{ __html: html }} />;
288
+ }
289
+ ```
290
+
291
+ ---
292
+
293
+ ## Next.js
294
+
295
+ ### App Router
296
+
297
+ ```tsx
298
+ // app/layout.tsx
299
+ import { initCastDOM } from "castdom/next";
300
+ import manifest from "../.castdom/manifest.json";
301
+
302
+ initCastDOM(manifest);
303
+
304
+ export default function RootLayout({ children }) {
305
+ return (
306
+ <html>
307
+ <body>{children}</body>
308
+ </html>
309
+ );
310
+ }
311
+ ```
312
+
313
+ ```tsx
314
+ // app/dashboard/page.tsx
315
+ import { CastDOM } from "castdom/next";
316
+
317
+ export default async function Dashboard() {
318
+ const data = await fetchDashboard();
319
+
320
+ return (
321
+ <CastDOM name="dashboard" loading={!data}>
322
+ <DashboardContent data={data} />
323
+ </CastDOM>
324
+ );
325
+ }
326
+ ```
327
+
328
+ ### Pages Router
329
+
330
+ ```tsx
331
+ // pages/_app.tsx
332
+ import { initCastDOM } from "castdom/next";
333
+ import manifest from "../.castdom/manifest.json";
334
+
335
+ initCastDOM(manifest);
336
+
337
+ export default function App({ Component, pageProps }) {
338
+ return <Component {...pageProps} />;
339
+ }
340
+ ```
341
+
342
+ ### Server-Side Props
343
+
344
+ ```tsx
345
+ import { getSkeletonProps } from "castdom/next";
346
+
347
+ export async function getServerSideProps() {
348
+ const { skeletonHTML, skeletonCSS } = getSkeletonProps(["user-card"]);
349
+ return { props: { skeletonHTML, skeletonCSS } };
350
+ }
351
+ ```
352
+
353
+ ### Auto-Generated `loading.tsx`
354
+
355
+ After `npx castdom build`, a ready-to-use `loading.tsx` is generated at `.castdom/nextjs-loading.tsx`. Copy it to your route:
356
+
357
+ ```bash
358
+ cp .castdom/nextjs-loading.tsx app/dashboard/loading.tsx
359
+ ```
360
+
361
+ ---
362
+
363
+ ## Astro
364
+
365
+ ### Integration
366
+
367
+ ```js
368
+ // astro.config.mjs
369
+ import { defineConfig } from "astro/config";
370
+ import { castdomIntegration } from "castdom/astro";
371
+
372
+ export default defineConfig({
373
+ integrations: [castdomIntegration()],
374
+ });
375
+ ```
376
+
377
+ ### Component Usage
378
+
379
+ ```astro
380
+ ---
381
+ import { skeleton } from "castdom/astro";
382
+ const { html, css } = skeleton("user-card");
383
+ ---
384
+
385
+ <style set:html={css}></style>
386
+ <div data-castdom="user-card">
387
+ <Fragment set:html={html} />
388
+ <slot />
389
+ </div>
390
+ ```
391
+
392
+ ### View Transitions
393
+
394
+ ```astro
395
+ ---
396
+ import { skeleton, viewTransitionProps } from "castdom/astro";
397
+ const { html } = skeleton("sidebar");
398
+ const transitionProps = viewTransitionProps("sidebar");
399
+ ---
400
+
401
+ <div {...transitionProps}>
402
+ <Fragment set:html={html} />
403
+ </div>
404
+ ```
405
+
406
+ ---
407
+
408
+ ## Vite
409
+
410
+ ```ts
411
+ // vite.config.ts
412
+ import { defineConfig } from "vite";
413
+ import { castdom } from "castdom/vite";
414
+
415
+ export default defineConfig({
416
+ plugins: [castdom()],
417
+ });
418
+ ```
419
+
420
+ Then import the virtual module:
421
+
422
+ ```ts
423
+ import "virtual:castdom";
424
+ ```
425
+
426
+ The plugin provides:
427
+ - **HMR** — hot-reload when skeleton data changes
428
+ - **Virtual module** — `virtual:castdom` auto-loads the manifest
429
+ - **Dev middleware** — `/__castdom/extract` endpoint
430
+
431
+ ---
432
+
433
+ ## Vanilla JS
434
+
435
+ For non-framework usage:
436
+
437
+ ```js
438
+ import { createCastDOM } from "castdom";
439
+ import manifest from ".castdom/manifest.json";
440
+
441
+ const castdom = createCastDOM();
442
+ castdom.loadManifest(manifest);
443
+
444
+ // Show skeleton
445
+ const container = document.getElementById("user-card");
446
+ castdom.show("user-card", container);
447
+
448
+ // Later, hide and show real content
449
+ castdom.hide("user-card", container);
450
+ ```
451
+
452
+ ---
453
+
454
+ ## SSR & SEO
455
+
456
+ CastDOM is SSR-first. Skeletons render as server-side HTML with zero JavaScript.
457
+
458
+ ### Why This Matters for SEO
459
+
460
+ | Metric | Without CastDOM | With CastDOM |
461
+ |--------|-----------------|--------------|
462
+ | **CLS** | Layout shifts when content loads | Zero — skeletons match exact dimensions |
463
+ | **FCP** | Blank until JS loads | Instant — CSS-only skeletons |
464
+ | **LCP** | Delayed by data fetching | Skeleton paints immediately |
465
+ | **Accessibility** | No loading indication | `role="status"`, `aria-busy`, screen reader labels |
466
+ | **Crawler visibility** | Empty containers | Structured placeholders with ARIA |
467
+
468
+ ### Server-Side Rendering
469
+
470
+ ```ts
471
+ import { renderSkeleton, renderSSRFragment } from "castdom/ssr";
472
+
473
+ // Single skeleton
474
+ const html = renderSkeleton(skeletonData);
475
+ // Includes: <style> + skeleton HTML + hydration data attributes
476
+
477
+ // Multiple skeletons (shared CSS)
478
+ const { head, body } = renderSSRFragment([skeleton1, skeleton2]);
479
+ // head: <style> + <script> hydration
480
+ // body: { "user-card": "...", "feed-item": "..." }
481
+ ```
482
+
483
+ ### Hydration
484
+
485
+ SSR skeletons include `data-castdom-ssr` attributes. A tiny (~200 byte) inline hydration script automatically removes them once real content renders:
486
+
487
+ ```ts
488
+ import { renderHydrationScript } from "castdom/ssr";
489
+
490
+ const script = renderHydrationScript();
491
+ // <script data-castdom="hydration">(...)</script>
492
+ ```
493
+
494
+ ### Critical CSS
495
+
496
+ ```ts
497
+ import { renderCriticalStyleTag } from "castdom/ssr";
498
+
499
+ const styleTag = renderCriticalStyleTag([skeleton1, skeleton2]);
500
+ // <style data-castdom="critical">@keyframes castdom-shimmer{...}...</style>
501
+ ```
502
+
503
+ ---
504
+
505
+ ## CLI Reference
506
+
507
+ ```bash
508
+ castdom build [options] # Extract skeletons and generate files
509
+ castdom init # Create castdom.config.json template
510
+ castdom list # List skeletons in the manifest
511
+ castdom clean # Remove generated output directory
512
+ ```
513
+
514
+ ### Build Options
515
+
516
+ | Flag | Default | Description |
517
+ |------|---------|-------------|
518
+ | `--url <url>` | `http://localhost:3000` | Dev server URL |
519
+ | `--config <path>` | `castdom.config.json` | Config file path |
520
+ | `--out <dir>` | `.castdom` | Output directory |
521
+ | `--breakpoints <list>` | `375,768,1280` | Comma-separated breakpoints |
522
+ | `--no-headless` | `false` | Show browser UI (for debugging) |
523
+ | `--verbose` | `false` | Detailed progress output |
524
+
525
+ ---
526
+
527
+ ## Configuration
528
+
529
+ ### `castdom.config.json`
530
+
531
+ ```json
532
+ {
533
+ "devServer": "http://localhost:3000",
534
+ "outDir": ".castdom",
535
+ "breakpoints": [375, 768, 1280],
536
+ "color": "#e0e0e0",
537
+ "shimmerColor": "#f0f0f0",
538
+ "animationDuration": 1500,
539
+ "contentAware": true,
540
+ "minBoneSize": 4,
541
+ "targets": [
542
+ {
543
+ "name": "user-card",
544
+ "selector": "[data-castdom=\"user-card\"]",
545
+ "route": "/"
546
+ },
547
+ {
548
+ "name": "feed-item",
549
+ "selector": "[data-castdom=\"feed-item\"]",
550
+ "route": "/feed"
551
+ }
552
+ ]
553
+ }
554
+ ```
555
+
556
+ | Option | Type | Default | Description |
557
+ |--------|------|---------|-------------|
558
+ | `devServer` | `string` | `http://localhost:3000` | URL of your dev server |
559
+ | `outDir` | `string` | `.castdom` | Where to write generated files |
560
+ | `breakpoints` | `number[]` | `[375, 768, 1280]` | Viewport widths to capture |
561
+ | `color` | `string` | `#e0e0e0` | Base bone color |
562
+ | `shimmerColor` | `string` | `#f0f0f0` | Shimmer highlight color |
563
+ | `animationDuration` | `number` | `1500` | Animation cycle duration (ms) |
564
+ | `contentAware` | `boolean` | `true` | Detect element types for shaped bones |
565
+ | `minBoneSize` | `number` | `4` | Skip elements smaller than this (px) |
566
+
567
+ ### Targets
568
+
569
+ Each target defines a component to extract:
570
+
571
+ | Field | Required | Description |
572
+ |-------|----------|-------------|
573
+ | `name` | Yes | Unique skeleton identifier |
574
+ | `selector` | Yes | CSS selector for the container element |
575
+ | `route` | No | Page route to navigate to before extracting |
576
+
577
+ ---
578
+
579
+ ## Animation Types
580
+
581
+ CastDOM supports four animation types, all CSS-only:
582
+
583
+ ### Shimmer (default)
584
+
585
+ A gradient sweep from left to right. Classic skeleton animation.
586
+
587
+ ```tsx
588
+ <CastDOM name="card" animation="shimmer" />
589
+ ```
590
+
591
+ ### Pulse
592
+
593
+ Opacity fade between 100% and 40%.
594
+
595
+ ```tsx
596
+ <CastDOM name="card" animation="pulse" />
597
+ ```
598
+
599
+ ### Wave
600
+
601
+ Staggered opacity animation — each bone animates with a 50ms delay.
602
+
603
+ ```tsx
604
+ <CastDOM name="card" animation="wave" />
605
+ ```
606
+
607
+ ### None
608
+
609
+ Static gray rectangles. Also used automatically when `prefers-reduced-motion: reduce` is active.
610
+
611
+ ```tsx
612
+ <CastDOM name="card" animation="none" />
613
+ ```
614
+
615
+ ---
616
+
617
+ ## API Reference
618
+
619
+ ### Core
620
+
621
+ ```typescript
622
+ import {
623
+ loadManifest, // Register all skeletons from generated manifest
624
+ register, // Register a single skeleton
625
+ get, // Get skeleton entry by name
626
+ has, // Check if skeleton exists
627
+ names, // List all registered skeleton names
628
+ configure, // Set global config (colors, animation, etc.)
629
+ getAllCSS, // Get combined CSS for all registered skeletons
630
+ extractBones, // Extract bones from a DOM element (browser-side)
631
+ compressBones, // Compress bone data for storage
632
+ decompressBones, // Decompress bone data
633
+ } from "castdom";
634
+ ```
635
+
636
+ ### React
637
+
638
+ ```typescript
639
+ import {
640
+ CastDOM, // Wrapper component
641
+ CastDOMStyle, // Critical CSS component for <head>
642
+ useCastDOM, // Hook for programmatic access
643
+ } from "castdom/react";
644
+ ```
645
+
646
+ ### SSR
647
+
648
+ ```typescript
649
+ import {
650
+ renderSkeleton, // Render skeleton to HTML string
651
+ renderSkeletons, // Render multiple with shared CSS
652
+ renderCriticalStyleTag, // Generate <style> tag
653
+ renderHydrationScript, // Generate hydration <script>
654
+ renderSSRFragment, // Complete head + body fragments
655
+ } from "castdom/ssr";
656
+ ```
657
+
658
+ ---
659
+
660
+ ## Generated Output
661
+
662
+ After running `npx castdom build`, the `.castdom/` directory contains:
663
+
664
+ ```
665
+ .castdom/
666
+ manifest.json # All skeleton data (register with loadManifest)
667
+ castdom.css # Critical CSS for all skeletons
668
+ index.js # ESM module with tree-shakeable exports
669
+ index.d.ts # TypeScript declarations
670
+ loader.js # Auto-registers all skeletons on import
671
+ nextjs-loading.tsx # Ready-to-use Next.js loading component
672
+ skeletons/
673
+ user-card.json # Individual skeleton data
674
+ user-card.js # Individual ESM export
675
+ feed-item.json
676
+ feed-item.js
677
+ ```
678
+
679
+ ---
680
+
681
+ ## TypeScript
682
+
683
+ CastDOM is written in TypeScript and ships full type declarations. After extraction, skeleton names are typed:
684
+
685
+ ```typescript
686
+ // .castdom/index.d.ts (auto-generated)
687
+ export type SkeletonName = "user-card" | "feed-item";
688
+ ```
689
+
690
+ ---
691
+
692
+ ## Browser Support
693
+
694
+ CastDOM's runtime is pure CSS — it works everywhere CSS animations work:
695
+
696
+ - Chrome 60+
697
+ - Firefox 60+
698
+ - Safari 12+
699
+ - Edge 79+
700
+
701
+ The extraction tool (CLI) requires Node.js 18+ and Playwright.
702
+
703
+ ---
704
+
705
+ ## License
706
+
707
+ MIT
package/bin/castdom.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../dist/cli.js";