@0xgf/boneyard 1.0.0 → 1.1.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/bin/boneyard.mjs CHANGED
@@ -43,7 +43,7 @@ if (command !== 'build') {
43
43
  const urls = []
44
44
  // Auto-detect: prefer ./src/bones for projects with a src/ directory (Next.js, Vite, etc.)
45
45
  let outDir = existsSync(resolve(process.cwd(), 'src')) ? './src/bones' : './bones'
46
- let breakpoints = [375, 768, 1280]
46
+ let breakpoints = null // null = auto-detect
47
47
  let waitMs = 800
48
48
 
49
49
  for (let i = 1; i < args.length; i++) {
@@ -58,6 +58,57 @@ for (let i = 1; i < args.length; i++) {
58
58
  }
59
59
  }
60
60
 
61
+ // ── Auto-detect breakpoints from Tailwind ────────────────────────────────────
62
+
63
+ /** Tailwind v4 default breakpoints */
64
+ const TAILWIND_DEFAULTS = [640, 768, 1024, 1280, 1536]
65
+
66
+ function detectTailwindBreakpoints() {
67
+ // Check for Tailwind v4 (CSS-based config)
68
+ const cssConfigPaths = [
69
+ 'src/app/globals.css',
70
+ 'src/globals.css',
71
+ 'app/globals.css',
72
+ 'styles/globals.css',
73
+ 'src/index.css',
74
+ 'index.css',
75
+ ]
76
+
77
+ for (const p of cssConfigPaths) {
78
+ const full = resolve(process.cwd(), p)
79
+ if (!existsSync(full)) continue
80
+ try {
81
+ const css = await import('fs').then(m => m.readFileSync(full, 'utf-8'))
82
+ if (css.includes('@import "tailwindcss"') || css.includes("@import 'tailwindcss'") || css.includes('@tailwind')) {
83
+ return TAILWIND_DEFAULTS
84
+ }
85
+ } catch {}
86
+ }
87
+
88
+ // Check for tailwind in package.json dependencies
89
+ try {
90
+ const pkgPath = resolve(process.cwd(), 'package.json')
91
+ if (existsSync(pkgPath)) {
92
+ const pkg = JSON.parse(await import('fs').then(m => m.readFileSync(pkgPath, 'utf-8')))
93
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }
94
+ if (allDeps['tailwindcss']) return TAILWIND_DEFAULTS
95
+ }
96
+ } catch {}
97
+
98
+ return null
99
+ }
100
+
101
+ if (!breakpoints) {
102
+ const tw = await detectTailwindBreakpoints()
103
+ if (tw) {
104
+ // Add mobile (375) as the smallest breakpoint since Tailwind's start at 640
105
+ breakpoints = [375, ...tw]
106
+ process.stdout.write(` boneyard: detected Tailwind — using breakpoints: ${breakpoints.join(', ')}px\n`)
107
+ } else {
108
+ breakpoints = [375, 768, 1280]
109
+ }
110
+ }
111
+
61
112
  // ── Auto-detect dev server ────────────────────────────────────────────────────
62
113
 
63
114
  /**
@@ -133,6 +184,11 @@ console.log(` Output: ${outDir}\n`)
133
184
  const browser = await chromium.launch()
134
185
  const page = await browser.newPage()
135
186
 
187
+ // Set build mode flag before any page loads so <Skeleton fixture={...}> renders mock content
188
+ await page.addInitScript(() => {
189
+ window.__BONEYARD_BUILD = true
190
+ })
191
+
136
192
  // { [skeletonName]: { breakpoints: { [width]: SkeletonResult } } }
137
193
  const collected = {}
138
194
 
@@ -148,19 +204,44 @@ for (const url of urls) {
148
204
  // networkidle can timeout on heavy pages — still try to capture
149
205
  }
150
206
 
151
- // Wait for React to render and boneyard to snapshot
207
+ // Wait for React to render
152
208
  if (waitMs > 0) await page.waitForTimeout(waitMs)
153
209
 
154
- // Read the global registry that <Skeleton name="..."> populates
210
+ // Find [data-boneyard] elements and extract bones using the real snapshotBones function
155
211
  const bones = await page.evaluate(() => {
156
- return window.__BONEYARD_BONES ?? {}
212
+ const fn = window.__BONEYARD_SNAPSHOT
213
+ if (!fn) return {}
214
+
215
+ const elements = document.querySelectorAll('[data-boneyard]')
216
+ const results = {}
217
+
218
+ for (const el of elements) {
219
+ const name = el.getAttribute('data-boneyard')
220
+ if (!name) continue
221
+
222
+ // Read snapshotConfig from data attribute
223
+ const configStr = el.getAttribute('data-boneyard-config')
224
+ const config = configStr ? JSON.parse(configStr) : undefined
225
+
226
+ // Target the first rendered child (fixture content)
227
+ const target = el.firstElementChild?.firstElementChild
228
+ if (!target) continue
229
+
230
+ try {
231
+ results[name] = fn(target, name, config)
232
+ } catch {
233
+ // skip on error
234
+ }
235
+ }
236
+
237
+ return results
157
238
  })
158
239
 
159
240
  const names = Object.keys(bones)
160
241
 
161
242
  if (names.length === 0) {
162
- console.warn(` ⚠ No named <Skeleton name="..."> found at ${width}px`)
163
- console.warn(` Make sure your <Skeleton> has a name prop and loading=false`)
243
+ console.warn(` ⚠ No <Skeleton name="..."> found at ${width}px`)
244
+ console.warn(` Make sure your <Skeleton> has a name prop and a fixture prop`)
164
245
  continue
165
246
  }
166
247
 
package/dist/extract.js CHANGED
@@ -10,7 +10,7 @@ const DEFAULT_LEAF_TAGS = new Set(['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li'
10
10
  export function snapshotBones(el, name = 'component', config) {
11
11
  const rootRect = el.getBoundingClientRect();
12
12
  const bones = [];
13
- const leafTags = config?.leafTags ? new Set(config.leafTags) : DEFAULT_LEAF_TAGS;
13
+ const leafTags = config?.leafTags ? new Set([...DEFAULT_LEAF_TAGS, ...config.leafTags]) : DEFAULT_LEAF_TAGS;
14
14
  const captureRoundedBorders = config?.captureRoundedBorders ?? true;
15
15
  const excludeTags = config?.excludeTags ? new Set(config.excludeTags) : null;
16
16
  const excludeSelectors = config?.excludeSelectors ?? null;
@@ -41,17 +41,18 @@ export function snapshotBones(el, name = 'component', config) {
41
41
  const hasBorder = captureRoundedBorders && borderTopWidth > 0 && style.borderTopColor !== 'rgba(0, 0, 0, 0)' && style.borderTopColor !== 'transparent';
42
42
  const hasBorderRadius = (parseFloat(style.borderTopLeftRadius) || 0) > 0;
43
43
  const hasVisualSurface = hasBg || hasBgImage || (hasBorder && hasBorderRadius);
44
+ const isTableNode = tag === 'tr' || tag === 'td' || tag === 'th' || tag === 'thead' || tag === 'tbody' || tag === 'table';
44
45
  if (isLeaf) {
45
46
  const rect = node.getBoundingClientRect();
46
47
  if (rect.width < 1 || rect.height < 1)
47
48
  return;
48
- const br = parseBorderRadius(style, node);
49
+ const br = isTableNode ? 0 : (parseBorderRadius(style, node) ?? 8);
49
50
  bones.push({
50
51
  x: Math.round(rect.left - rootRect.left),
51
52
  y: Math.round(rect.top - rootRect.top),
52
53
  w: Math.round(rect.width),
53
54
  h: Math.round(rect.height),
54
- r: br ?? 8,
55
+ r: br,
55
56
  });
56
57
  return;
57
58
  }
@@ -60,13 +61,13 @@ export function snapshotBones(el, name = 'component', config) {
60
61
  if (hasVisualSurface) {
61
62
  const rect = node.getBoundingClientRect();
62
63
  if (rect.width >= 1 && rect.height >= 1) {
63
- const br = parseBorderRadius(style, node);
64
+ const br = isTableNode ? 0 : (parseBorderRadius(style, node) ?? 8);
64
65
  bones.push({
65
66
  x: Math.round(rect.left - rootRect.left),
66
67
  y: Math.round(rect.top - rootRect.top),
67
68
  w: Math.round(rect.width),
68
69
  h: Math.round(rect.height),
69
- r: br ?? 8,
70
+ r: br,
70
71
  c: true, // container bone — rendered at reduced opacity
71
72
  });
72
73
  }
@@ -133,10 +134,14 @@ function extractNode(el) {
133
134
  const mar = extractSides(style, 'margin');
134
135
  if (mar)
135
136
  desc.margin = mar;
136
- // Border radius
137
- const br = parseBorderRadius(style, el);
138
- if (br !== undefined)
139
- desc.borderRadius = br;
137
+ // Border radius (skip table elements — they inherit from overflow:hidden parents)
138
+ const elTag = el.tagName.toLowerCase();
139
+ const isTableEl = elTag === 'tr' || elTag === 'td' || elTag === 'th' || elTag === 'thead' || elTag === 'tbody' || elTag === 'table';
140
+ if (!isTableEl) {
141
+ const br = parseBorderRadius(style, el);
142
+ if (br !== undefined)
143
+ desc.borderRadius = br;
144
+ }
140
145
  // Max width
141
146
  const maxW = parseFloat(style.maxWidth);
142
147
  if (maxW > 0 && isFinite(maxW))
@@ -345,10 +350,14 @@ function snapshotAsLeaf(el, style, desc) {
345
350
  const rect = el.getBoundingClientRect();
346
351
  desc.width = Math.round(rect.width);
347
352
  desc.height = Math.round(rect.height);
348
- // Border radius
349
- const br = parseBorderRadius(style, el);
350
- if (br !== undefined)
351
- desc.borderRadius = br;
353
+ // Border radius (skip table elements)
354
+ const leafTag = el.tagName.toLowerCase();
355
+ const isTableLeaf = leafTag === 'tr' || leafTag === 'td' || leafTag === 'th' || leafTag === 'thead' || leafTag === 'tbody' || leafTag === 'table';
356
+ if (!isTableLeaf) {
357
+ const br = parseBorderRadius(style, el);
358
+ if (br !== undefined)
359
+ desc.borderRadius = br;
360
+ }
352
361
  // If it has children, try to extract them — we just fix this node's own dimensions
353
362
  // so the layout engine treats it as a fixed-size container
354
363
  const children = [];
package/dist/react.d.ts CHANGED
@@ -9,44 +9,20 @@ import type { SkeletonResult, ResponsiveBones, SnapshotConfig } from './types.js
9
9
  * ```ts
10
10
  * import './bones/registry'
11
11
  * ```
12
- *
13
- * Then every `<Skeleton name="blog-card">` automatically gets its bones — no
14
- * `initialBones` prop needed.
15
12
  */
16
13
  export declare function registerBones(map: Record<string, SkeletonResult | ResponsiveBones>): void;
17
14
  export interface SkeletonProps {
18
- /** When true, shows the skeleton. When false, shows children and extracts layout. */
15
+ /** When true, shows the skeleton. When false, shows children. */
19
16
  loading: boolean;
20
- /** Your component — rendered when not loading. The skeleton is extracted from it. */
17
+ /** Your component — rendered when not loading. */
21
18
  children: ReactNode;
22
19
  /**
23
- * Name this skeleton. When provided:
24
- * - After each snapshot, bones are registered to `window.__PRESKEL_BONES[name]`
25
- * - The `boneyard build` CLI reads this registry to generate bones JSON files
26
- *
27
- * @example
28
- * <Skeleton name="blog-card" loading={isLoading}>
29
- * <BlogCard />
30
- * </Skeleton>
31
- *
32
- * Then run: npx boneyard capture http://localhost:3000 --out ./src/bones
33
- * Which writes: ./src/bones/blog-card.bones.json
20
+ * Name this skeleton. Used by `npx boneyard build` to identify and capture bones.
21
+ * Also used to auto-resolve pre-generated bones from the registry.
34
22
  */
35
23
  name?: string;
36
24
  /**
37
- * Pre-generated bones for zero first-load flash.
38
- * Accepts a single `SkeletonResult` or a `ResponsiveBones` map (from `boneyard build`).
39
- *
40
- * - Single: used regardless of viewport width
41
- * - Responsive: boneyard picks the nearest breakpoint match for the current container width
42
- *
43
- * After the first real render, live `snapshotBones` measurements replace the initial bones.
44
- *
45
- * @example
46
- * import blogBones from './src/bones/blog-card.bones.json'
47
- * <Skeleton loading={isLoading} initialBones={blogBones}>
48
- * <BlogCard />
49
- * </Skeleton>
25
+ * Pre-generated bones. Accepts a single `SkeletonResult` or a `ResponsiveBones` map.
50
26
  */
51
27
  initialBones?: SkeletonResult | ResponsiveBones;
52
28
  /** Bone color (default: '#e0e0e0') */
@@ -56,52 +32,26 @@ export interface SkeletonProps {
56
32
  /** Additional className for the container */
57
33
  className?: string;
58
34
  /**
59
- * Shown on the very first load if no cached bones and no initialBones.
60
- * Unnecessary when initialBones is provided.
35
+ * Shown when loading is true and no bones are available.
61
36
  */
62
37
  fallback?: ReactNode;
63
38
  /**
64
- * Controls how boneyard extracts bones from your component's DOM.
65
- * Override the default extraction rules to match your design system.
66
- *
67
- * @example
68
- * // Treat <Card> root divs as leaves, skip icons and footers
69
- * snapshotConfig={{
70
- * leafTags: ['p', 'h1', 'h2', 'h3', 'li'],
71
- * excludeSelectors: ['.icon', '[data-no-skeleton]', 'footer'],
72
- * }}
39
+ * Mock content rendered during `npx boneyard build` so the CLI can capture
40
+ * bone positions even when real data isn't available.
41
+ * Only rendered when the CLI sets `window.__BONEYARD_BUILD = true`.
42
+ */
43
+ fixture?: ReactNode;
44
+ /**
45
+ * Controls how `npx boneyard build` extracts bones from the fixture.
46
+ * Stored as a data attribute — the CLI reads it during capture.
73
47
  */
74
48
  snapshotConfig?: SnapshotConfig;
75
49
  }
76
50
  /**
77
51
  * Wrap any component to get automatic skeleton loading screens.
78
52
  *
79
- * How it works:
80
- * 1. When loading=false, your children render normally.
81
- * After paint, boneyard snapshots the exact bone positions from the DOM.
82
- * 2. When loading=true, the cached bones are replayed as a skeleton overlay.
83
- * 3. On the very first load (no cache yet):
84
- * - With `initialBones`: shows pre-generated bones immediately, no flash
85
- * - Without: shows the `fallback` prop
86
- *
87
- * For zero first-load flash, run `npx boneyard capture` to generate initialBones.
88
- *
89
- * @example Basic
90
- * ```tsx
91
- * import { Skeleton } from '@0xgf/boneyard/react'
92
- *
93
- * <Skeleton name="blog-card" loading={isLoading}>
94
- * <BlogCard data={data} />
95
- * </Skeleton>
96
- * ```
97
- *
98
- * @example With pre-generated responsive bones (zero flash)
99
- * ```tsx
100
- * import blogBones from './src/bones/blog-card.bones.json'
101
- *
102
- * <Skeleton name="blog-card" loading={isLoading} initialBones={blogBones}>
103
- * <BlogCard data={data} />
104
- * </Skeleton>
105
- * ```
53
+ * 1. Run `npx boneyard build` — captures bone positions from your rendered UI
54
+ * 2. Import the generated registry in your app entry
55
+ * 3. `<Skeleton name="..." loading={isLoading}>` auto-resolves bones by name
106
56
  */
107
- export declare function Skeleton({ loading, children, name, initialBones, color, animate, className, fallback, snapshotConfig, }: SkeletonProps): import("react/jsx-runtime").JSX.Element;
57
+ export declare function Skeleton({ loading, children, name, initialBones, color, animate, className, fallback, fixture, snapshotConfig, }: SkeletonProps): import("react/jsx-runtime").JSX.Element;
package/dist/react.js CHANGED
@@ -2,8 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useRef, useState, useEffect } from 'react';
3
3
  import { snapshotBones } from './extract.js';
4
4
  // ── Bones registry ──────────────────────────────────────────────────────────
5
- // Module-level registry populated by registerBones() from the generated registry file.
6
- // This lets <Skeleton name="x"> auto-resolve bones without an explicit initialBones prop.
7
5
  const bonesRegistry = new Map();
8
6
  /**
9
7
  * Register pre-generated bones so `<Skeleton name="...">` can auto-resolve them.
@@ -14,15 +12,16 @@ const bonesRegistry = new Map();
14
12
  * ```ts
15
13
  * import './bones/registry'
16
14
  * ```
17
- *
18
- * Then every `<Skeleton name="blog-card">` automatically gets its bones — no
19
- * `initialBones` prop needed.
20
15
  */
21
16
  export function registerBones(map) {
22
17
  for (const [name, bones] of Object.entries(map)) {
23
18
  bonesRegistry.set(name, bones);
24
19
  }
25
20
  }
21
+ // ── Expose snapshotBones for CLI build mode (module-level, no useEffect) ────
22
+ if (typeof window !== 'undefined' && window.__BONEYARD_BUILD) {
23
+ window.__BONEYARD_SNAPSHOT = snapshotBones;
24
+ }
26
25
  /** Pick the right SkeletonResult from a responsive set for the current width */
27
26
  function resolveResponsive(bones, width) {
28
27
  if (!('breakpoints' in bones))
@@ -30,7 +29,6 @@ function resolveResponsive(bones, width) {
30
29
  const bps = Object.keys(bones.breakpoints).map(Number).sort((a, b) => a - b);
31
30
  if (bps.length === 0)
32
31
  return null;
33
- // Largest breakpoint that fits (same logic as CSS min-width media queries)
34
32
  const match = [...bps].reverse().find(bp => width >= bp) ?? bps[0];
35
33
  return bones.breakpoints[match] ?? null;
36
34
  }
@@ -47,39 +45,15 @@ function lightenHex(hex, amount) {
47
45
  /**
48
46
  * Wrap any component to get automatic skeleton loading screens.
49
47
  *
50
- * How it works:
51
- * 1. When loading=false, your children render normally.
52
- * After paint, boneyard snapshots the exact bone positions from the DOM.
53
- * 2. When loading=true, the cached bones are replayed as a skeleton overlay.
54
- * 3. On the very first load (no cache yet):
55
- * - With `initialBones`: shows pre-generated bones immediately, no flash
56
- * - Without: shows the `fallback` prop
57
- *
58
- * For zero first-load flash, run `npx boneyard capture` to generate initialBones.
59
- *
60
- * @example Basic
61
- * ```tsx
62
- * import { Skeleton } from '@0xgf/boneyard/react'
63
- *
64
- * <Skeleton name="blog-card" loading={isLoading}>
65
- * <BlogCard data={data} />
66
- * </Skeleton>
67
- * ```
68
- *
69
- * @example With pre-generated responsive bones (zero flash)
70
- * ```tsx
71
- * import blogBones from './src/bones/blog-card.bones.json'
72
- *
73
- * <Skeleton name="blog-card" loading={isLoading} initialBones={blogBones}>
74
- * <BlogCard data={data} />
75
- * </Skeleton>
76
- * ```
48
+ * 1. Run `npx boneyard build` — captures bone positions from your rendered UI
49
+ * 2. Import the generated registry in your app entry
50
+ * 3. `<Skeleton name="..." loading={isLoading}>` auto-resolves bones by name
77
51
  */
78
- export function Skeleton({ loading, children, name, initialBones, color = '#e0e0e0', animate = true, className, fallback, snapshotConfig, }) {
52
+ export function Skeleton({ loading, children, name, initialBones, color = '#e0e0e0', animate = true, className, fallback, fixture, snapshotConfig, }) {
79
53
  const containerRef = useRef(null);
80
- const [cachedBones, setCachedBones] = useState(null);
81
54
  const [containerWidth, setContainerWidth] = useState(0);
82
- // Track container width so responsive initialBones picks the right breakpoint
55
+ const isBuildMode = typeof window !== 'undefined' && window.__BONEYARD_BUILD === true;
56
+ // Track container width for responsive breakpoint selection
83
57
  useEffect(() => {
84
58
  const el = containerRef.current;
85
59
  if (!el)
@@ -91,55 +65,32 @@ export function Skeleton({ loading, children, name, initialBones, color = '#e0e0
91
65
  setContainerWidth(Math.round(el.getBoundingClientRect().width));
92
66
  return () => ro.disconnect();
93
67
  }, []);
94
- // After every non-loading render, snapshot the DOM and update the cache
95
- useEffect(() => {
96
- if (loading || !containerRef.current)
97
- return;
98
- let raf1, raf2;
99
- raf1 = requestAnimationFrame(() => {
100
- raf2 = requestAnimationFrame(() => {
101
- const el = containerRef.current;
102
- if (!el)
103
- return;
104
- const firstChild = el.firstElementChild;
105
- if (!firstChild)
106
- return;
107
- try {
108
- const result = snapshotBones(firstChild, name ?? 'component', snapshotConfig);
109
- setCachedBones(result);
110
- // Register to global so `boneyard build` CLI can read it
111
- if (name && typeof window !== 'undefined') {
112
- const w = window;
113
- w.__BONEYARD_BONES = w.__BONEYARD_BONES ?? {};
114
- w.__BONEYARD_BONES[name] = result;
115
- }
116
- }
117
- catch {
118
- // keep existing cache on error
119
- }
120
- });
121
- });
122
- return () => {
123
- cancelAnimationFrame(raf1);
124
- cancelAnimationFrame(raf2);
125
- };
126
- }, [loading, name]);
127
- // Active bones: live cache > explicit initialBones > registry lookup
128
- const effectiveInitialBones = initialBones ?? (name ? bonesRegistry.get(name) : undefined);
129
- const resolved = !cachedBones && effectiveInitialBones && containerWidth > 0
130
- ? resolveResponsive(effectiveInitialBones, containerWidth)
68
+ // Data attributes for CLI discovery
69
+ const dataAttrs = {};
70
+ if (name) {
71
+ dataAttrs['data-boneyard'] = name;
72
+ if (snapshotConfig) {
73
+ dataAttrs['data-boneyard-config'] = JSON.stringify(snapshotConfig);
74
+ }
75
+ }
76
+ // Build mode: render fixture so CLI can capture bones from it
77
+ if (isBuildMode && fixture) {
78
+ return (_jsx("div", { ref: containerRef, className: className, style: { position: 'relative' }, ...dataAttrs, children: _jsx("div", { children: fixture }) }));
79
+ }
80
+ // Resolve bones: explicit initialBones > registry lookup
81
+ const effectiveBones = initialBones ?? (name ? bonesRegistry.get(name) : undefined);
82
+ const activeBones = effectiveBones && containerWidth > 0
83
+ ? resolveResponsive(effectiveBones, containerWidth)
131
84
  : null;
132
- const activeBones = cachedBones ?? resolved;
133
85
  const showSkeleton = loading && activeBones;
134
86
  const showFallback = loading && !activeBones;
135
- return (_jsxs("div", { ref: containerRef, className: className, style: { position: 'relative' }, children: [_jsx("div", { style: showSkeleton ? { visibility: 'hidden' } : undefined, children: showFallback ? fallback : children }), showSkeleton && (_jsx("div", { style: { position: 'absolute', inset: 0 }, children: _jsxs("div", { style: { position: 'relative', width: '100%', height: activeBones.height }, children: [activeBones.bones.map((b, i) => (_jsx("div", { style: {
87
+ return (_jsxs("div", { ref: containerRef, className: className, style: { position: 'relative' }, ...dataAttrs, children: [_jsx("div", { style: showSkeleton ? { visibility: 'hidden' } : undefined, children: showFallback ? fallback : children }), showSkeleton && (_jsx("div", { style: { position: 'absolute', inset: 0 }, children: _jsxs("div", { style: { position: 'relative', width: '100%', height: activeBones.height }, children: [activeBones.bones.map((b, i) => (_jsx("div", { style: {
136
88
  position: 'absolute',
137
89
  left: b.x,
138
90
  top: b.y,
139
91
  width: b.w,
140
92
  height: b.h,
141
93
  borderRadius: typeof b.r === 'string' ? b.r : `${b.r}px`,
142
- // Container bones are rendered lighter so children stand out on top
143
94
  backgroundColor: b.c ? lightenHex(color, 0.45) : color,
144
95
  animation: animate ? 'boneyard-pulse 1.8s ease-in-out infinite' : undefined,
145
96
  } }, i))), animate && (_jsx("style", { children: `@keyframes boneyard-pulse{0%,100%{background-color:${color}}50%{background-color:${lightenHex(color, 0.3)}}}` }))] }) }))] }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xgf/boneyard",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Pixel-perfect skeleton loading screens. Wrap your component in <Skeleton> and boneyard snapshots the real DOM layout — no manual descriptors, no configuration.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",