@revyme/runtime 0.0.12 → 0.0.14

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/dist/index.js CHANGED
@@ -159,8 +159,15 @@ function withResponsiveProps(Component) {
159
159
  } catch {}
160
160
  delete mergedProps["data-responsive"];
161
161
  delete mergedProps["__canvasViewportWidth"];
162
+ if (mergedProps.style == null || mergedProps.style.position == null) mergedProps = {
163
+ ...mergedProps,
164
+ style: {
165
+ position: "relative",
166
+ ...mergedProps.style ?? {}
167
+ }
168
+ };
162
169
  const style = mergedProps.style;
163
- let needsWrapper = fwdRef != null;
170
+ let needsWrapper = fwdRef != null || mergedProps["data-size-hug"] != null;
164
171
  if (!needsWrapper && style) {
165
172
  for (const k of Object.keys(style)) if (MOTION_ONLY_KEYS.has(k) || isMotionValue(style[k])) {
166
173
  needsWrapper = true;
@@ -172,8 +179,9 @@ function withResponsiveProps(Component) {
172
179
  const innerStyle = {};
173
180
  for (const [k, v] of Object.entries(style ?? {})) if (MOTION_ONLY_KEYS.has(k) || PLACEMENT_KEYS.has(k) || isMotionValue(v)) wrapperStyle[k] = v;
174
181
  else innerStyle[k] = v;
175
- if ("width" in wrapperStyle) innerStyle.width = "100%";
176
- if ("height" in wrapperStyle) innerStyle.height = "100%";
182
+ const definite = (v) => v != null && v !== "auto";
183
+ if (definite(wrapperStyle.width)) innerStyle.width = "100%";
184
+ if (definite(wrapperStyle.height)) innerStyle.height = "100%";
177
185
  innerStyle.position = "relative";
178
186
  const { style: _split, ...rest } = mergedProps;
179
187
  delete rest.ref;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/withResponsiveProps.tsx","../src/cursor-runtime.tsx","../src/useStaticCanvas.ts","../src/split-text.tsx","../src/sketch-draw.ts","../src/localize-rows.ts"],"sourcesContent":["'use client';\n\nimport { useState, useEffect, useMemo, useRef, forwardRef, type ComponentType } from 'react';\nimport { motion, isMotionValue, MotionConfig } from 'framer-motion';\n\n/**\n * HOC that reads a `data-responsive` JSON prop and merges per-viewport overrides.\n * Usage: `export default withResponsiveProps(MyComponent)`\n *\n * On the canvas, CodeComponentHost injects `__canvasViewportWidth` to simulate viewport size.\n * In production, uses `window.innerWidth`.\n *\n * data-responsive='{\"768\":{\"fontSize\":32},\"375\":{\"fontSize\":24}}'\n * Breakpoints are max-width: if viewport <= 768, the 768 overrides apply.\n *\n * ANIMATED-STYLE SOCKET (Framer parity): the editor expresses instance\n * animation effects (Appear, scroll scrubs, …) as framer MotionValues bound\n * into the instance's `style` prop — `style={{ opacity: <mv>, y: <mv> }}`.\n * Design components consume them natively (their roots are motion.* and\n * spread `...style`), but a CODE component's root is a plain element: a\n * MotionValue arrives as an un-serialisable object (the style is dropped)\n * and motion-only keys like `y` aren't CSS at all — the effect silently\n * dies at the component boundary (live find 2026-07-14: Appear on a\n * Marquee). When animated values or motion-only keys are present, the HOC\n * renders a motion.div WRAPPER that carries them (plus the placement props\n * that describe the instance's slot in its parent), and hands the wrapped\n * component a clean static style. Framer does exactly this — the platform\n * owns an animatable container around every code component, so component\n * authors never deal with animation plumbing. No animated values → no\n * wrapper → byte-identical behaviour to before.\n */\n\n/** Style keys only a motion.* element understands (translated into\n * `transform`) — a plain DOM element ignores them entirely. */\nconst MOTION_ONLY_KEYS = new Set([\n 'x', 'y', 'z',\n 'rotate', 'rotateX', 'rotateY', 'rotateZ',\n 'scale', 'scaleX', 'scaleY',\n 'skew', 'skewX', 'skewY',\n 'originX', 'originY', 'originZ',\n 'transformPerspective',\n]);\n\n/** Placement props describing the instance's slot in ITS PARENT's layout —\n * these must ride on whichever element is outermost (the wrapper, when one\n * exists), mirroring the design-instance wrapper/root split. */\nconst PLACEMENT_KEYS = new Set([\n 'position', 'left', 'top', 'right', 'bottom', 'inset',\n 'width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight',\n 'flex', 'flexGrow', 'flexShrink', 'flexBasis',\n 'order', 'alignSelf', 'justifySelf', 'zIndex',\n 'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft',\n 'gridColumn', 'gridRow', 'gridArea',\n]);\n/** Ascending breakpoint widths out of a `data-responsive` payload. `_bp` carries\n * the full viewport list; without it, fall back to the override keys (an older\n * payload, or one written before every viewport had an override). */\nfunction parseBreakpoints(responsiveStr: unknown): number[] {\n if (!responsiveStr) return [];\n try {\n const overrides = typeof responsiveStr === 'string'\n ? JSON.parse(responsiveStr) : responsiveStr;\n const all = Array.isArray(overrides._bp)\n ? overrides._bp\n : Object.keys(overrides).filter((k) => k !== '_bp').map(Number);\n return [...all].filter((n) => Number.isFinite(n) && n > 0).sort((a, b) => a - b);\n } catch {\n return [];\n }\n}\n\n/** Which breakpoint owns `width`. Each breakpoint's range is `(prev, bp]`, so\n * the buckets don't cascade. `null` = wider than every breakpoint, i.e. the\n * primary/base design with no override. */\nfunction resolveBucket(width: number, sortedBp: number[]): number | null {\n for (let i = 0; i < sortedBp.length; i++) {\n const lower = i > 0 ? sortedBp[i - 1] : 0;\n if (width > lower && width <= sortedBp[i]) return sortedBp[i];\n }\n return null;\n}\n\nexport default function withResponsiveProps<P extends Record<string, any>>(\n Component: ComponentType<P>\n): ComponentType<P & { 'data-responsive'?: string; __canvasViewportWidth?: number }> {\n // forwardRef: scroll effects target the instance with `ref={…}` for\n // `useScroll({ target })` — a plain function component would silently drop\n // it (framer then throws \"Target ref is defined but not hydrated\"). The ref\n // pins to the animated wrapper (a real DOM box = the component's exact\n // footprint), so scroll measurement works with the component untouched.\n return forwardRef(function ResponsiveSpark(props: any, fwdRef: any) {\n const canvasVpWidth = props.__canvasViewportWidth as number | undefined;\n const responsiveStr = props['data-responsive'];\n\n // Breakpoint list, parsed once per distinct `data-responsive` string. The\n // resize listener needs it to bucket a width, and re-parsing JSON on every\n // resize event would be its own waste.\n const sortedBp = useMemo(() => parseBreakpoints(responsiveStr), [responsiveStr]);\n // Read by the resize listener, which is registered once and must not go\n // stale if the breakpoint list changes underneath it.\n const bpRef = useRef(sortedBp);\n bpRef.current = sortedBp;\n\n // STATE IS THE MATCHED BREAKPOINT, NOT THE RAW WIDTH.\n //\n // This used to hold `window.innerWidth` and set it on every resize event,\n // so dragging a window edge re-rendered the whole design component\n // hundreds of times — each commit re-measuring a projection tree that can\n // be 30+ `layout` nodes deep, while the variant's non-tweenable props\n // (position/display) were mid-flight on motion's own loop. Footers came out\n // of a resize with their contents stranded and invisible (user report\n // 2026-08-09). Only the BUCKET can change what renders, so only the bucket\n // belongs in state: one commit per crossing instead of one per mouse move.\n //\n // `prev` rides along so the crossing render can be identified — see\n // `crossing` below. Equal cur/prev = settled.\n const [bp, setBp] = useState(() => {\n const initial = resolveBucket(\n typeof window !== 'undefined' ? window.innerWidth : 1440,\n sortedBp,\n );\n return { cur: initial, prev: initial };\n });\n\n useEffect(() => {\n if (canvasVpWidth !== undefined) return;\n const read = () => {\n const next = resolveBucket(window.innerWidth, bpRef.current);\n // Same bucket → same object → React bails out without re-rendering.\n setBp((s) => (s.cur === next ? s : { cur: next, prev: s.cur }));\n };\n read(); // width may have moved between first render and this effect\n window.addEventListener('resize', read);\n return () => window.removeEventListener('resize', read);\n }, [canvasVpWidth]);\n\n // Release the crossing flag on the commit after it landed, so instant\n // transitions apply to the switch itself and nothing after it.\n useEffect(() => {\n if (bp.cur !== bp.prev) setBp((s) => ({ cur: s.cur, prev: s.cur }));\n }, [bp]);\n\n // A CROSSING must not animate. Everything the variant switch touches —\n // width, gap, flex-direction, position — changes at once, and `layout`\n // FLIP-animates all of it by default: the component visibly morphs from\n // the desktop arrangement to the mobile one instead of just being mobile.\n // A viewport is a state of the world, not a transition between states.\n //\n // MotionConfig sets the DEFAULT transition for every descendant, layout\n // projections included, so this covers a whole design component without\n // codegen touching a single element. Caveat worth knowing: an element with\n // its own `transition` prop overrides the default, so an appear effect's\n // `transition={{duration: 0.6}}` still governs that element's layout\n // animation. Fixing those needs the per-element route (position/display\n // out of the variants object), not this.\n const crossing = bp.cur !== bp.prev;\n\n const vpWidth = canvasVpWidth ?? bp.cur ?? Infinity;\n let mergedProps = { ...props };\n\n if (responsiveStr) {\n try {\n const overrides = typeof responsiveStr === 'string'\n ? JSON.parse(responsiveStr) : responsiveStr;\n // On the canvas the tile width is authoritative and arrives per render;\n // off-canvas `vpWidth` IS the resolved bucket already.\n const matchedBp = canvasVpWidth !== undefined\n ? resolveBucket(canvasVpWidth, sortedBp)\n : bp.cur;\n if (matchedBp !== null && overrides[matchedBp]) {\n const ov = overrides[matchedBp];\n // A Scroll Variant binds `initialVariant={…Sv}` and OWNS it at runtime (it morphs the\n // variant on scroll). The per-viewport `data-responsive` entry must NOT override that\n // here — otherwise it freezes the variant on replicas and the morph never plays. The\n // per-viewport variant CHOICE still drives the canvas + seeds the Sv's resting; only\n // the runtime merge skips `initialVariant`. (No scroll variant → unchanged behaviour.)\n if (props['data-scroll-variant'] && ov && typeof ov === 'object' && 'initialVariant' in ov) {\n const { initialVariant: _skip, ...rest } = ov;\n mergedProps = { ...mergedProps, ...rest };\n } else {\n mergedProps = { ...mergedProps, ...ov };\n }\n }\n } catch {}\n }\n\n delete mergedProps['data-responsive'];\n delete mergedProps['__canvasViewportWidth'];\n\n // ── Animated-style socket (see header comment) ──\n // A forwarded ref ALSO forces the wrapper: it's the scroll-effect target,\n // which must resolve to a real DOM box even when the style carries no\n // animated values yet.\n const style = mergedProps.style as Record<string, any> | undefined;\n let needsWrapper = fwdRef != null;\n if (!needsWrapper && style) {\n for (const k of Object.keys(style)) {\n if (MOTION_ONLY_KEYS.has(k) || isMotionValue(style[k])) { needsWrapper = true; break; }\n }\n }\n if (needsWrapper) {\n const wrapperStyle: Record<string, any> = {};\n const innerStyle: Record<string, any> = {};\n for (const [k, v] of Object.entries(style ?? {})) {\n if (MOTION_ONLY_KEYS.has(k) || PLACEMENT_KEYS.has(k) || isMotionValue(v)) wrapperStyle[k] = v;\n else innerStyle[k] = v;\n }\n // The inner component fills the wrapper — but only along axes the\n // wrapper actually sized (code-component instances always carry\n // definite dims; the guard keeps an unsized axis hugging content).\n if ('width' in wrapperStyle) innerStyle.width = '100%';\n if ('height' in wrapperStyle) innerStyle.height = '100%';\n // The wrapper OWNS the instance's slot (all placement keys moved onto\n // it) — the inner root must re-base INTO the wrapper. Design-component\n // masters bake `position: 'absolute'` on their root (canvas master\n // tiling) and rely on the instance style overriding it via the trailing\n // `...style` spread; with the split the position key never reaches them,\n // so the root absolute-positioned inside a zero-size wrapper and\n // vanished (live find 2026-07-28: Sign Up button instance inside an\n // AnimatePresence popLayout header — popLayout's ref forces the\n // wrapper). Position is a PLACEMENT key, so innerStyle can never carry\n // its own — always re-base.\n innerStyle.position = 'relative';\n const { style: _split, ...rest } = mergedProps;\n delete rest.ref; // React 19 passes `ref` as a prop — never forward it inward\n // The wrapper owns the instance's BOX, so it must also own the instance's\n // IDENTITY. Every per-viewport override the editor writes is a\n // `[data-id=\"…\"] { … !important }` rule in the page's <style> block, and\n // the only styles an instance tag may carry are placement ones\n // (the editor's WRAPPER_ONLY_STYLE_PROPS + width/height) — exactly the\n // keys that just moved onto the wrapper. With the id only on the inner,\n // those rules land on an element this split has already pinned to\n // `width/height: 100%; position: relative`, so they do nothing: the\n // canvas (whose own instance wrapper carries the data-id) painted the\n // override and the published page ignored it. Live find 2026-08-08 —\n // a mobile `width: 100%` on a button instance that had an Appear effect,\n // which is what forces the wrapper in the first place.\n //\n // COPIED, not moved: the inner keeps `rest`'s attributes so nothing that\n // already matches them stops matching. The duplicate is safe precisely\n // because an instance rule can only carry placement, and placement is\n // inert on a lone block child that already fills its parent.\n return withInstantCrossing(crossing,\n <motion.div\n ref={fwdRef}\n data-id={rest['data-id']}\n data-name={rest['data-name']}\n style={wrapperStyle}\n >\n <Component {...rest} style={innerStyle} />\n </motion.div>,\n );\n }\n\n return withInstantCrossing(crossing, <Component {...mergedProps} />);\n }) as any;\n}\n\n/** Wrap the subtree so a breakpoint crossing lands with no animation. Only on\n * the crossing commit — the tree is otherwise handed through untouched, so a\n * component that never crosses renders exactly what it always did. */\nfunction withInstantCrossing(crossing: boolean, tree: React.ReactElement): React.ReactElement {\n if (!crossing) return tree;\n return <MotionConfig transition={{ duration: 0 }}>{tree}</MotionConfig>;\n}\n","'use client';\n\nimport { useEffect, useRef, useSyncExternalStore, type ComponentType } from 'react';\nimport { motion, AnimatePresence, useMotionValue, useSpring } from 'framer-motion';\n\nexport type CursorMode = 'follow' | 'replace';\nexport type CursorSide = 'top' | 'bottom' | 'left' | 'right';\nexport type CursorAlign = 'start' | 'center' | 'end';\n\nexport interface CursorTransition {\n type?: 'spring' | 'tween' | 'instant';\n stiffness?: number;\n damping?: number;\n mass?: number;\n duration?: number;\n ease?: string;\n}\n\nexport interface CursorOpts<P = any> {\n variant?: string;\n mode?: CursorMode;\n /**\n * Which side of the mouse the cursor wrapper anchors to (Follow mode).\n * Replace mode ignores side / align / offset — it auto-centers on the mouse.\n */\n side?: CursorSide;\n /**\n * Alignment along the perpendicular axis to `side`.\n * top/bottom: start = left, center = horizontal center, end = right.\n * left/right: start = top, center = vertical center, end = bottom.\n */\n align?: CursorAlign;\n offsetX?: number;\n offsetY?: number;\n transition?: CursorTransition;\n props?: Partial<P>;\n /**\n * Wrapper width / height applied to the cursor's outer motion.div. Useful\n * for code components (canvases, sparks) that fill their parent — without\n * this they'd render at their intrinsic size, which is often the whole\n * viewport. Plain numbers are interpreted as px; pass a string ('100%',\n * '4rem') to use other CSS units.\n */\n width?: number | string;\n height?: number | string;\n /**\n * When true, fade/scale on enter and exit via AnimatePresence. Default\n * false: appear and disappear instantly. The follow movement is always\n * smoothed by the spring config above — `enterExit` only controls the\n * mount/unmount transition.\n */\n enterExit?: boolean;\n}\n\ninterface ActiveCursor {\n key: number;\n Component: ComponentType<any>;\n opts: CursorOpts;\n}\n\n// ─── Global store (vanilla, no React) ───────────────────────────────────────\nlet _active: ActiveCursor | null = null;\nconst _listeners = new Set<() => void>();\n\n// STABLE key per cursor COMPONENT (not per enter event). The old\n// `key: ++_nextKey` per mouseenter forced React to fully REMOUNT the cursor\n// component on every hover transition. Cursor components are typically design\n// components (LayoutGroup + layout motion nodes + variant background images),\n// so scrolling with the pointer over a stack of cursor-hosting elements fired\n// an enter/leave storm → remount storm → framer-motion projection re-registers\n// + image repaints + forced reflows piled onto the main thread — the page\n// froze for seconds (live find 2026-07-07). With a per-Component key, moving\n// between hosts that share a cursor UPDATES the mounted component in place —\n// the per-instance `variant` lands through `initialVariant`, which the design\n// component's internal sync effect animates. A DIFFERENT component still\n// remounts (key changes).\nconst _componentKeys = new WeakMap<ComponentType<any>, number>();\nlet _nextComponentKey = 0;\nfunction _keyFor(c: ComponentType<any>): number {\n let k = _componentKeys.get(c);\n if (k === undefined) {\n k = ++_nextComponentKey;\n _componentKeys.set(c, k);\n }\n return k;\n}\n\n// Pending deactivate from a mouseleave. Scrolling re-hit-tests the pointer, so\n// leave/enter alternate rapidly while the page moves under the mouse; clearing\n// the cursor synchronously on every leave caused an unmount per row boundary.\n// A short grace window absorbs the churn: a follow-up enter cancels the clear\n// (and, same component, is a pure prop update). A REAL exit clears once, ~90ms\n// later — imperceptible.\nlet _pendingClear: ReturnType<typeof setTimeout> | null = null;\n\nfunction _setActive(next: ActiveCursor | null) {\n _active = next;\n _listeners.forEach((l) => l());\n}\n\nfunction _subscribe(l: () => void) {\n _listeners.add(l);\n return () => { _listeners.delete(l); };\n}\n\nfunction _getActive() {\n return _active;\n}\n\n/**\n * Spread the return value into an element to give it a component cursor.\n * Returns onMouseEnter/onMouseLeave handlers that push/pop the global store.\n *\n * <button {...withCursor(Pointer, { mode: 'follow', transition: { type: 'spring', stiffness: 300 } })}>\n */\nexport function withCursor<P>(Component: ComponentType<P>, opts: CursorOpts<P> = {}) {\n return {\n onMouseEnter: () => {\n if (_pendingClear !== null) {\n clearTimeout(_pendingClear);\n _pendingClear = null;\n }\n _setActive({ key: _keyFor(Component as ComponentType<any>), Component: Component as ComponentType<any>, opts });\n },\n onMouseLeave: () => {\n if (_pendingClear !== null) clearTimeout(_pendingClear);\n _pendingClear = setTimeout(() => {\n _pendingClear = null;\n _setActive(null);\n }, 90);\n },\n };\n}\n\n// ─── Portal (mount once in LayoutClient) ────────────────────────────────────\n\nfunction _springConfig(t?: CursorTransition) {\n if (!t || t.type === 'instant') return { stiffness: 1000, damping: 50, mass: 0.1 };\n if (t.type === 'tween' && t.duration) {\n // Map a tween duration to roughly-equivalent spring values.\n const stiffness = Math.max(50, 400 / Math.max(0.1, t.duration));\n return { stiffness, damping: 30, mass: 1 };\n }\n return {\n stiffness: t.stiffness ?? 300,\n damping: t.damping ?? 30,\n mass: t.mass ?? 1,\n };\n}\n\nexport function CursorPortal() {\n const cursor = useSyncExternalStore(_subscribe, _getActive, _getActive);\n\n const x = useMotionValue(0);\n const y = useMotionValue(0);\n const sx = useSpring(x, _springConfig(cursor?.opts.transition));\n const sy = useSpring(y, _springConfig(cursor?.opts.transition));\n\n // One persistent listener; offsets read through a ref so an active-cursor\n // change never re-registers it (the old `[cursor]` dep re-added the listener\n // on every hover transition).\n const offsetRef = useRef({ x: 0, y: 0 });\n offsetRef.current = { x: cursor?.opts.offsetX ?? 0, y: cursor?.opts.offsetY ?? 0 };\n useEffect(() => {\n const onMove = (e: MouseEvent) => {\n x.set(e.clientX + offsetRef.current.x);\n y.set(e.clientY + offsetRef.current.y);\n };\n window.addEventListener('mousemove', onMove, { passive: true });\n return () => window.removeEventListener('mousemove', onMove);\n }, [x, y]);\n\n // Keyed on the MODE, not the whole active object: writing\n // `document.body.style.cursor` invalidates style for the entire document,\n // and the old `[cursor]` dep re-ran the write on every hover transition —\n // one full-page style recalc per row boundary while scrolling.\n const mode = cursor?.opts.mode;\n useEffect(() => {\n if (mode === 'replace') {\n const prev = document.body.style.cursor;\n document.body.style.cursor = 'none';\n return () => { document.body.style.cursor = prev; };\n }\n }, [mode]);\n\n // Wrapper width/height — numbers become px, strings pass through. Falls\n // back to undefined so intrinsic sizing kicks in if the user hasn't set it.\n const wrapW = typeof cursor?.opts.width === 'number' ? cursor.opts.width + 'px' : cursor?.opts.width;\n const wrapH = typeof cursor?.opts.height === 'number' ? cursor.opts.height + 'px' : cursor?.opts.height;\n\n // The OUTER motion.div carries the spring x/y (mouse position). The INNER\n // div applies a percentage transform for side+align (or auto-center in\n // Replace mode). Splitting them avoids fighting with framer-motion's own\n // transform handling on the x/y motion values.\n const outerStyle = {\n position: 'fixed' as const,\n top: 0,\n left: 0,\n x: sx,\n y: sy,\n pointerEvents: 'none' as const,\n zIndex: 9999,\n };\n const innerTransform = _innerTransform(cursor?.opts);\n const innerStyle = {\n width: wrapW,\n height: wrapH,\n transform: innerTransform,\n };\n\n // Default: instant in/out (no AnimatePresence wrapping). Wrap only when\n // the active cursor opts in via `enterExit: true` — keeps mount/unmount\n // snappy by default and avoids the brief fade-out from the previous cursor\n // when hovering between adjacent elements.\n // `opts.variant` → the design component's `initialVariant` prop. Without\n // this the variant picked in the editor (master call or per-instance\n // `<prop>Opts` override) was stored but NEVER applied — every hover showed\n // the cursor component's default variant (live find 2026-07-06). A fresh\n // `key` per hover means the component mounts with the right variant; its\n // internal `useEffect(() => setVariant(initialVariant), [initialVariant])`\n // covers any same-mount opts change.\n const variantProps = cursor?.opts.variant ? { initialVariant: cursor.opts.variant } : {};\n\n if (!cursor?.opts.enterExit) {\n return cursor ? (\n <motion.div key={cursor.key} style={outerStyle}>\n <div style={innerStyle}>\n <cursor.Component {...(cursor.opts.props ?? {})} {...variantProps} />\n </div>\n </motion.div>\n ) : null;\n }\n\n return (\n <AnimatePresence>\n {cursor && (\n <motion.div\n key={cursor.key}\n style={outerStyle}\n initial={{ opacity: 0, scale: 0.8 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.8 }}\n >\n <div style={innerStyle}>\n <cursor.Component {...(cursor.opts.props ?? {})} {...variantProps} />\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n );\n}\n\n/**\n * Build the inner-wrapper transform from side + align + mode. Pure CSS\n * percentage translates so it works regardless of whether width/height are\n * set explicitly. Replace mode auto-centers; Follow mode anchors a corner /\n * edge / center based on the chosen side and alignment.\n */\nfunction _innerTransform(opts?: CursorOpts) {\n if (!opts || opts.mode === 'replace') return 'translate(-50%, -50%)';\n const side = opts.side ?? 'bottom';\n const align = opts.align ?? 'center';\n let tx = 0;\n let ty = 0;\n if (side === 'top') ty = -100;\n else if (side === 'left') tx = -100;\n // 'bottom' and 'right' default to 0 on the main axis.\n // Align controls the perpendicular axis.\n if (side === 'top' || side === 'bottom') {\n if (align === 'center') tx = -50;\n else if (align === 'end') tx = -100;\n } else {\n if (align === 'center') ty = -50;\n else if (align === 'end') ty = -100;\n }\n return 'translate(' + tx + '%, ' + ty + '%)';\n}\n","'use client';\n\n/**\n * `useStaticCanvas()` — returns `true` when the component is being rendered\n * inside the Revyme canvas editor, `false` in the live preview, published\n * site, or any other consumer environment.\n *\n * Sparks / code components use this to skip GPU-expensive animation work\n * (continuous rAF loops, big CSS blur layers, WebGL frames) on the editor\n * canvas where the user only needs a representative still — paint once,\n * stop. The full animated version still runs in preview and production.\n *\n * Mechanics: this default implementation always returns `false`. The canvas\n * editor's spark loader (`code-component-runtime.ts` MODULE_MAP) overrides\n * the export at compile time so it returns `true` in the canvas iframe and\n * `false` in the spark editor's preview pane (which sets `previewMode`).\n *\n * Mirrors Framer's `useIsStaticRenderer` pattern.\n */\nexport function useStaticCanvas(): boolean {\n return false;\n}\n","'use client';\n\n// split-text.tsx — `<RevymeSplitText>`: the runtime half of Revyme's Text effect.\n//\n// WHY THIS EXISTS (2026-07-31). Text effects used to be split at CODEGEN time: the\n// generator wrote N `<motion.span>` elements — one per character — straight into the\n// user's page source. That cannot work for text that isn't known until render:\n// `{item.title}` from a CMS row, `{t('key')}` from i18n, `{propName}` from a component\n// variable. The generator saw an expression, not a string, and escaped it per character\n// into `&#123;item.title&#125;`, so every CMS row rendered the literal text `{item.title}`.\n//\n// Splitting at RENDER time removes the whole class of problem: by the time this component\n// runs, `children` is already the resolved string. It also collapses ~600 lines of\n// generator (four span builders, scroll-hook injection, canvas dormancy) into one prop.\n//\n// HARD CONSTRAINT — the split must be a PURE function of props, computed DURING RENDER.\n// Published sites are SSR'd; a split deferred to an effect would emit a bare string on the\n// server and spans on the client, i.e. a hydration mismatch. Nothing here measures the DOM\n// or reads `window` outside an effect/lazy-initialiser.\n\nimport * as React from 'react';\nimport { motion, useInView, useScroll, useTransform, type MotionValue } from 'framer-motion';\n\n// ─── Spec ────────────────────────────────────────────────────────────────────\n\nexport type SplitTextScope = { query: string } | { variant: string };\n\n/** Structurally `TextAnimConfig` from the builder (canvas-poc/src/editor/tools/\n * AnimationTool/motion/text-anim-presets.ts). Kept in sync by a type-assignability\n * test in canvas-poc — a field added there without a counterpart here fails the build. */\nexport interface SplitTextSpec {\n /** STRUCTURAL — resolved from the BASE spec only, never from a scope override, so the\n * emitted tree is identical on server and client. */\n animationType?: 'character' | 'word' | 'line' | 'full';\n /** STRUCTURAL — wraps each unit in an overflow-hidden clip (\"cut-off\" reveal). */\n mask?: boolean;\n trigger?: 'view' | 'scroll';\n /** Scroll mode only — viewport position (% from top) where the reveal starts / completes. */\n scrollStart?: number;\n scrollEnd?: number;\n opacity?: number;\n scale?: number;\n blur?: number;\n rotateX?: number;\n rotateY?: number;\n rotateZ?: number;\n skewX?: number;\n skewY?: number;\n /** Strings keep their unit. '100%' resolves against the unit's OWN box, which is what\n * makes a masked reveal correct at every type size — a px offset masks correctly at\n * one breakpoint only. */\n x?: number | string;\n y?: number | string;\n /** Stagger between units, seconds. */\n delay?: number;\n transition?: {\n type?: 'spring' | 'tween';\n stiffness?: number;\n damping?: number;\n mass?: number;\n duration?: number;\n bounce?: number;\n ease?: string | number[];\n /** Initial delay before the first unit, seconds. */\n delay?: number;\n };\n /** Per-viewport / per-variant value overrides. First match wins (matches the builder's\n * `resolveTextAnimForScope`). Structural fields in an override are ignored. */\n responsive?: Array<{ scope: SplitTextScope; config: Partial<SplitTextSpec> }>;\n}\n\nexport interface RevymeSplitTextProps {\n spec?: SplitTextSpec;\n /** Active component variant — only needed when `spec.responsive` has `{variant}` scopes. */\n variant?: string;\n children?: React.ReactNode;\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nconst WRAP_PLAIN: React.CSSProperties = { whiteSpace: 'nowrap' };\n/** paddingBottom/marginBottom cancel out — zero layout cost. The padding keeps descenders\n * (g, y, p) from being shaved by the clip AND leaves the sliver IntersectionObserver needs\n * when a unit is offset a full 100% (otherwise it sits entirely outside the clip). */\nconst WRAP_MASK: React.CSSProperties = {\n whiteSpace: 'nowrap', display: 'inline-block', overflow: 'hidden',\n paddingBottom: '0.14em', marginBottom: '-0.14em',\n};\n\n/** The editor persists a custom cubic-bezier as the STRING \"[0.22, 1, 0.36, 1]\" (that's what\n * the TransitionPanel curve editor writes). framer accepts a named easing or a real array —\n * handed the string it throws `Invalid easing type` and unmounts the tree. Codegen normalises\n * this too; the runtime keeps it so hand-written source still works. */\nfunction normalizeEase(e: string | number[] | undefined): string | number[] | undefined {\n if (e === undefined || Array.isArray(e)) return e;\n const s = String(e).trim();\n if (!s.startsWith('[')) return s;\n const n = s.replace(/[[\\]]/g, '').split(',').map((v) => parseFloat(v.trim()));\n return n.length === 4 && n.every(Number.isFinite) ? n : undefined;\n}\n\n/** Collapse React children to a plain string, or null when they can't be split.\n *\n * `null` (a real element child — a styled `<span>` mark, an icon) means \"render verbatim,\n * unsplit\" rather than mangling rich content. Everything the generator used to normalise at\n * build time — `{item.title}`, `{t('key')}`, `{\"a\\nb\"}`, `a<br/>b` — arrives here already\n * resolved to a string or a `<br />`, so one function covers all of it. */\nfunction flattenToText(node: React.ReactNode): string | null {\n if (node === null || node === undefined || node === false || node === true) return '';\n if (typeof node === 'string') return node;\n if (typeof node === 'number') return String(node);\n if (Array.isArray(node)) {\n let out = '';\n for (const child of node) {\n const part = flattenToText(child);\n if (part === null) return null;\n out += part;\n }\n return out;\n }\n if (React.isValidElement(node)) {\n const type = (node as React.ReactElement).type;\n if (type === 'br') return '\\n';\n if (type === React.Fragment) return flattenToText((node.props as any)?.children);\n return null; // a real element → not splittable\n }\n return null;\n}\n\n/** Active `responsive` entry index, or -1 for the base spec.\n *\n * The `matchMedia` read is a LAZY useState initialiser, not a post-mount effect: framer\n * captures `initial` once at mount, so starting at `false` and correcting later makes the\n * responsive branch permanently lose to the base. Same shape as the builder's generated\n * `useMediaQuery` (canvas-poc/src/code/generation/scoped-expr.ts). */\nfunction useActiveScopeIndex(spec: SplitTextSpec, variant?: string): number {\n const entries = spec.responsive;\n const compute = React.useCallback((): number => {\n if (!entries || entries.length === 0) return -1;\n for (let i = 0; i < entries.length; i++) {\n const scope = entries[i].scope;\n if ('variant' in scope) {\n if (variant !== undefined && scope.variant === variant) return i;\n } else if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {\n if (window.matchMedia(scope.query).matches) return i;\n }\n }\n return -1;\n }, [entries, variant]);\n\n const [idx, setIdx] = React.useState(compute);\n\n React.useEffect(() => {\n setIdx(compute());\n if (!entries || typeof window === 'undefined' || typeof window.matchMedia !== 'function') return;\n const lists = entries\n .map((e) => ('query' in e.scope ? window.matchMedia(e.scope.query) : null))\n .filter(Boolean) as MediaQueryList[];\n if (lists.length === 0) return;\n const onChange = () => setIdx(compute());\n for (const l of lists) l.addEventListener('change', onChange);\n return () => { for (const l of lists) l.removeEventListener('change', onChange); };\n }, [entries, compute]);\n\n return idx;\n}\n\nconst RESTING: Record<string, number> = { opacity: 1, scale: 1, rotateX: 0, rotateY: 0, rotateZ: 0, skewX: 0, skewY: 0, x: 0, y: 0 };\nconst CHANNELS = ['opacity', 'scale', 'rotateX', 'rotateY', 'rotateZ', 'skewX', 'skewY', 'x', 'y'] as const;\n\n/** The animated-from state. Mirrors the builder's `buildHiddenState`: a channel is animated\n * only when the spec sets it to something other than its resting value. */\nfunction hiddenState(s: SplitTextSpec): Record<string, any> {\n const out: Record<string, any> = {};\n for (const k of CHANNELS) {\n const v = s[k];\n if (v !== undefined && v !== RESTING[k]) out[k] = v;\n }\n if (s.blur !== undefined && s.blur !== 0) out.filter = `blur(${s.blur}px)`;\n return out;\n}\n\nfunction visibleState(s: SplitTextSpec): Record<string, any> {\n const out: Record<string, any> = {};\n for (const k of CHANNELS) {\n const v = s[k];\n if (v !== undefined && v !== RESTING[k]) out[k] = RESTING[k];\n }\n if (s.blur !== undefined && s.blur !== 0) out.filter = 'blur(0px)';\n return out;\n}\n\n/** `'100%'` → `'0%'`, `24` → `0`. Keeps the unit so a percentage offset scrubs correctly. */\nfunction restingOf(from: number | string, key: string): number | string {\n if (typeof from === 'number') return RESTING[key] ?? 0;\n const m = String(from).match(/^(-?[\\d.]+)(.*)$/);\n return m ? `${RESTING[key] ?? 0}${m[2]}` : (RESTING[key] ?? 0);\n}\n\ninterface Unit { key: string; inner: string; display: 'inline-block' | 'block'; index: number }\n\ntype Node = { t: 'unit'; u: Unit } | { t: 'br'; k: string } | { t: 'text'; k: string; v: string }\n | { t: 'wrap'; k: string; kids: Node[] };\n\n/** Split `text` into render nodes. Structure mirrors the old codegen splitter exactly, so the\n * emitted DOM is unchanged from the build-time era. Keys are pure functions of index → SSR\n * and CSR agree. */\nfunction buildNodes(text: string, animationType: SplitTextSpec['animationType'], mask: boolean): { nodes: Node[]; count: number } {\n const nodes: Node[] = [];\n let i = 0;\n const lines = text.split('\\n');\n\n if (animationType === 'line') {\n for (let li = 0; li < lines.length; li++) {\n if (li > 0) nodes.push({ t: 'br', k: `br${li}` });\n nodes.push({ t: 'unit', u: { key: `u${i}`, inner: lines[li], display: 'block', index: i } });\n i++;\n }\n return { nodes, count: i };\n }\n if (animationType === 'full') {\n nodes.push({ t: 'unit', u: { key: 'u0', inner: text, display: 'inline-block', index: 0 } });\n return { nodes, count: 1 };\n }\n\n for (let li = 0; li < lines.length; li++) {\n if (li > 0) nodes.push({ t: 'br', k: `br${li}` });\n const words = lines[li].split(' ');\n for (let wi = 0; wi < words.length; wi++) {\n if (wi > 0) nodes.push({ t: 'text', k: `sp${li}-${wi}`, v: ' ' });\n const word = words[wi];\n if (!word) continue;\n if (animationType === 'word') {\n const u: Node = { t: 'unit', u: { key: `u${i}`, inner: word, display: 'inline-block', index: i } };\n i++;\n nodes.push(mask ? { t: 'wrap', k: `w${li}-${wi}`, kids: [u] } : u);\n } else {\n const kids: Node[] = [];\n for (const ch of word) {\n kids.push({ t: 'unit', u: { key: `u${i}`, inner: ch, display: 'inline-block', index: i } });\n i++;\n }\n nodes.push({ t: 'wrap', k: `w${li}-${wi}`, kids });\n }\n }\n }\n return { nodes, count: i };\n}\n\n// ─── Scroll unit ─────────────────────────────────────────────────────────────\n\n/** One scroll-scrubbed unit. Split into its own component because `useTransform` must be\n * called once per animated channel and that count is data-driven — calling them in a loop\n * inside the parent would violate the rules of hooks. The parent keys each instance by a\n * channel fingerprint, so a spec change REMOUNTS rather than reordering hooks. */\nfunction ScrollUnit({ progress, range, channels, display, children }: {\n progress: MotionValue<number>;\n range: [number, number];\n channels: Array<{ key: string; from: number | string; to: number | string }>;\n display: string;\n children: React.ReactNode;\n}) {\n const style: Record<string, any> = { display };\n for (const c of channels) {\n // eslint-disable-next-line react-hooks/rules-of-hooks -- count is fixed per mount; see above\n style[c.key] = useTransform(progress, range, [c.from as never, c.to as never]);\n }\n return <motion.span style={style as React.CSSProperties}>{children}</motion.span>;\n}\n\n// ─── Component ───────────────────────────────────────────────────────────────\n\nexport function RevymeSplitText({ spec, variant, children }: RevymeSplitTextProps): React.ReactElement {\n const base: SplitTextSpec = spec ?? {};\n const hostRef = React.useRef<HTMLSpanElement>(null);\n\n const scopeIdx = useActiveScopeIndex(base, variant);\n const resolved: SplitTextSpec = React.useMemo(() => {\n const r = scopeIdx >= 0 && base.responsive\n ? { ...base, ...base.responsive[scopeIdx].config }\n : base;\n // Structural fields come from the BASE, always — the tree must not depend on a scope,\n // or server and client can disagree about the DOM (not just about style values).\n return { ...r, animationType: base.animationType, mask: base.mask, responsive: base.responsive };\n }, [base, scopeIdx]);\n\n const animationType = base.animationType ?? 'character';\n const mask = !!base.mask;\n const text = flattenToText(children);\n\n // ONE observer for the whole run, not one per character. Besides being N× cheaper, this\n // structurally avoids the deadlock the per-character form had: a masked unit offset out of\n // its own overflow-hidden clip has intersection ratio 0 and never fires.\n const inView = useInView(hostRef, { once: true, amount: 0 });\n\n // Called unconditionally so the hook count never changes when a scope flips view↔scroll.\n const startFrac = Math.min(1, Math.max(0, (resolved.scrollStart ?? 90) / 100));\n const endFrac = Math.min(1, Math.max(0, (resolved.scrollEnd ?? 35) / 100));\n const { scrollYProgress } = useScroll({\n target: hostRef,\n offset: [`start ${startFrac}`, `start ${endFrac}`] as never,\n });\n\n const { nodes, count } = React.useMemo(\n () => buildNodes(text ?? '', animationType, mask),\n [text, animationType, mask],\n );\n\n // Not splittable (a styled span, an icon) → render verbatim rather than mangling it.\n if (text === null) return <span ref={hostRef}>{children}</span>;\n\n const isScroll = resolved.trigger === 'scroll';\n const hidden = hiddenState(resolved);\n const visible = visibleState(resolved);\n const stagger = resolved.delay ?? 0.05;\n const initialDelay = resolved.transition?.delay ?? 0;\n const tr = resolved.transition\n ? { ...resolved.transition, ease: normalizeEase(resolved.transition.ease), delay: undefined } as any\n : { type: 'spring' as const, stiffness: 300, damping: 30 } as any;\n\n const channels = isScroll\n ? Object.entries(hidden).map(([key, from]) => ({\n key,\n from: from as number | string,\n to: key === 'filter' ? 'blur(0px)' : restingOf(from as number | string, key),\n }))\n : [];\n const fingerprint = channels.map((c) => c.key).join('|');\n\n const renderUnit = (u: Unit): React.ReactElement => {\n if (isScroll) {\n const start = count > 1 ? Math.round((u.index / (count - 1)) * 0.6 * 1000) / 1000 : 0;\n const end = Math.min(1, Math.round((start + 0.4) * 1000) / 1000);\n return (\n <ScrollUnit\n key={`${fingerprint}#${u.key}`}\n progress={scrollYProgress}\n range={[start, end]}\n channels={channels}\n display={u.display}\n >{u.inner}</ScrollUnit>\n );\n }\n return (\n <motion.span\n key={u.key}\n style={{ display: u.display }}\n initial={hidden}\n animate={inView ? visible : hidden}\n transition={{ ...tr, delay: Math.round((initialDelay + u.index * stagger) * 1000) / 1000 }}\n >{u.inner}</motion.span>\n );\n };\n\n const render = (n: Node): React.ReactNode => {\n if (n.t === 'br') return <br key={n.k} />;\n if (n.t === 'text') return n.v;\n if (n.t === 'wrap') return <span key={n.k} style={mask ? WRAP_MASK : WRAP_PLAIN}>{n.kids.map(render)}</span>;\n return renderUnit(n.u);\n };\n\n return (\n <span ref={hostRef} style={{ display: animationType === 'line' ? 'block' : 'inline' }}>\n {nodes.map(render)}\n </span>\n );\n}\n\nexport default RevymeSplitText;\n","// sketch-draw.ts — Runtime player for Revyme sketch draw animations.\n//\n// Replays a brush-stroke sketch over time by feeding the original\n// pointer samples (persisted on each `<path>` as a `data-points`\n// attribute) back through perfect-freehand's `getStroke` at\n// progressively-increasing slice lengths. The result is the visible\n// equivalent of watching the user draw the sketch.\n//\n// Why a runtime function instead of an inline useEffect block in the\n// generated source: the orchestrator is ~80 LOC of imperative timing\n// + easing + RAF logic. Inlining it in every page that has a sketch\n// animation buries the page's actual logic. Living in\n// `@revyme/runtime` means the generated source is just one line:\n//\n// useEffect(() => playSketchDraw(el, opts), []);\n//\n// which reads the same way as `withResponsiveProps` / `withCursor`\n// already do for other generated patterns.\n\nimport { getStroke } from 'perfect-freehand';\n\nexport type SketchAnimMode = 'sequential' | 'staggered' | 'simultaneous';\nexport type SketchAnimTrigger = 'mount' | 'inView' | 'hover' | 'tap';\n\nexport interface SketchAnimTransition {\n type: 'tween' | 'spring';\n duration?: number;\n ease?: string;\n stiffness?: number;\n damping?: number;\n mass?: number;\n}\n\nexport interface SketchAnimOpts {\n trigger?: SketchAnimTrigger;\n mode?: SketchAnimMode;\n /** Multiplier on per-stroke duration. Per-stroke duration scales\n * with point count so a long stroke takes longer than a flick;\n * this dials the overall pace. */\n durationScale?: number;\n /** 0–1, only meaningful in staggered mode. 0 = fully sequential,\n * 1 = fully simultaneous. */\n stagger?: number;\n transition?: SketchAnimTransition;\n /** Brush size used for the intermediate-frame outline replay. The\n * final-frame `d` is restored from source so the end state is\n * pixel-exact regardless of this value. */\n brushSize?: number;\n}\n\nconst DEFAULT_OPTS: Required<Omit<SketchAnimOpts, 'transition'>> & { transition: SketchAnimTransition } = {\n trigger: 'inView',\n mode: 'sequential',\n durationScale: 1,\n stagger: 0.5,\n transition: { type: 'tween', duration: 1, ease: 'easeOut' },\n brushSize: 8,\n};\n\nfunction applyEase(t: number, transition: SketchAnimTransition): number {\n if (transition.type === 'spring') {\n const damping = transition.damping ?? 10;\n const stiffness = transition.stiffness ?? 100;\n const dampedT = 1 - Math.exp(-damping * t * 0.1);\n const oscillation = Math.cos(t * Math.sqrt(stiffness) * 0.3);\n return Math.min(1, dampedT * (1 - 0.1 * oscillation * (1 - t)));\n }\n switch (transition.ease ?? 'easeOut') {\n case 'linear': return t;\n case 'easeIn': return t * t;\n case 'easeOut': return 1 - (1 - t) * (1 - t);\n case 'easeInOut': return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;\n case 'circIn': return 1 - Math.sqrt(1 - t * t);\n case 'circOut': return Math.sqrt(1 - Math.pow(t - 1, 2));\n case 'backOut': {\n const c1 = 1.70158;\n const c3 = c1 + 1;\n return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);\n }\n default: return 1 - (1 - t) * (1 - t);\n }\n}\n\nfunction parsePoints(raw: string): number[][] {\n if (!raw) return [];\n return raw.split(/\\s+/).filter(Boolean).map(s => {\n const [x, y, p] = s.split(',');\n return [parseFloat(x) || 0, parseFloat(y) || 0, p != null ? parseFloat(p) : 0.5];\n });\n}\n\nfunction outlineToD(outline: number[][]): string {\n if (outline.length === 0) return '';\n let d = `M ${outline[0][0].toFixed(2)} ${outline[0][1].toFixed(2)}`;\n for (let i = 1; i < outline.length; i++) {\n d += ` L ${outline[i][0].toFixed(2)} ${outline[i][1].toFixed(2)}`;\n }\n return d + ' Z';\n}\n\n/**\n * Play a sketch draw animation on the given wrapper SVG. Pass the\n * options the generator emitted in source.\n *\n * Returns a cleanup function — wire as your useEffect's return value\n * so re-mounts cancel an in-flight animation cleanly:\n *\n * useEffect(() => playSketchDraw(svgEl, opts), []);\n *\n * If `wrapperEl` is null or the wrapper has no path children with\n * `data-points`, this is a no-op and returns a noop cleanup.\n */\nexport function playSketchDraw(\n wrapperEl: SVGSVGElement | null,\n userOpts: SketchAnimOpts = {},\n): () => void {\n const noop = () => {};\n if (!wrapperEl) return noop;\n const opts = { ...DEFAULT_OPTS, ...userOpts, transition: { ...DEFAULT_OPTS.transition, ...userOpts.transition } };\n\n const paths = Array.from(wrapperEl.querySelectorAll('path[data-points]')) as SVGPathElement[];\n if (paths.length === 0) return noop;\n\n // Snapshot the final d so the last frame is pixel-exact regardless\n // of the replay-with-default-brush approximation we use during\n // intermediate frames.\n const finalDs = paths.map(p => p.getAttribute('d') || '');\n const pointsList = paths.map(p => parsePoints(p.getAttribute('data-points') || ''));\n\n // Hide everything up front so the first frame doesn't flash.\n paths.forEach(p => p.setAttribute('d', ''));\n\n // Per-stroke duration — point count drives length so a long stroke\n // takes longer than a flick.\n const baseDur = (opts.transition.duration ?? 1) * 1000 * opts.durationScale;\n const maxPoints = pointsList.reduce((m, p) => Math.max(m, p.length), 1);\n const perStrokeDur = pointsList.map(p => baseDur * (p.length / maxPoints));\n const startMs: number[] = [];\n let cursor = 0;\n for (let i = 0; i < paths.length; i++) {\n if (opts.mode === 'simultaneous') {\n startMs.push(0);\n } else if (opts.mode === 'staggered') {\n const overlap = Math.max(0, Math.min(1, opts.stagger));\n const start = i === 0 ? 0 : startMs[i - 1] + perStrokeDur[i - 1] * (1 - overlap);\n startMs.push(start);\n } else {\n // sequential\n startMs.push(cursor);\n cursor += perStrokeDur[i];\n }\n }\n\n let cancelled = false;\n let rafId = 0;\n let started = false;\n let cleanupTrigger: (() => void) | null = null;\n let startTs = 0;\n\n const tick = (now: number) => {\n if (cancelled) return;\n const elapsed = now - startTs;\n let allDone = true;\n for (let i = 0; i < paths.length; i++) {\n const local = elapsed - startMs[i];\n if (local < 0) { allDone = false; continue; }\n const t = Math.min(1, local / Math.max(1, perStrokeDur[i]));\n if (t < 1) allDone = false;\n let d: string;\n if (t >= 1) {\n d = finalDs[i];\n } else {\n const eased = applyEase(t, opts.transition);\n const sliceCount = Math.max(2, Math.floor(pointsList[i].length * eased));\n const subset = pointsList[i].slice(0, sliceCount);\n if (subset.length < 2) {\n d = '';\n } else {\n const outline = getStroke(subset, {\n size: opts.brushSize, thinning: 0.5, smoothing: 0.5, streamline: 0.5,\n });\n d = outlineToD(outline);\n }\n }\n paths[i].setAttribute('d', d);\n }\n if (!allDone) rafId = requestAnimationFrame(tick);\n };\n\n const start = () => {\n if (started) return;\n started = true;\n startTs = performance.now();\n rafId = requestAnimationFrame(tick);\n };\n\n if (opts.trigger === 'inView') {\n const obs = new IntersectionObserver((entries) => {\n if (entries.some(e => e.isIntersecting)) {\n start();\n obs.disconnect();\n }\n }, { threshold: 0.2 });\n obs.observe(wrapperEl);\n cleanupTrigger = () => obs.disconnect();\n } else if (opts.trigger === 'hover') {\n const onEnter = () => start();\n wrapperEl.addEventListener('mouseenter', onEnter);\n cleanupTrigger = () => wrapperEl.removeEventListener('mouseenter', onEnter);\n } else if (opts.trigger === 'tap') {\n const onTap = () => start();\n wrapperEl.addEventListener('click', onTap);\n cleanupTrigger = () => wrapperEl.removeEventListener('click', onTap);\n } else {\n // mount\n start();\n }\n\n return () => {\n cancelled = true;\n cancelAnimationFrame(rafId);\n cleanupTrigger?.();\n };\n}\n","// localize-rows.ts — per-locale field values for CMS collection rows.\n//\n// A collection item holds ONE row with a translation per field per locale,\n// stored on the item itself:\n//\n// { \"_id\": \"abc\", \"title\": \"Sunset sail\", \"_i18n\": { \"fr\": { \"title\": \"…\" } } }\n//\n// The generated page wraps its collection source with this, so the SOURCE\n// resolves the locale by itself:\n//\n// {localizeRows(programme, __activeLocale).map((item, idx) => (\n// <h3 data-id=\"prog-title\">{item.title}</h3>\n// ))}\n//\n// Why on the row and why here, rather than a build step or an editor-side\n// merge: the page already imports its collection JSON, so the translations\n// ship with the data and the published site needs no extra file, no extra\n// import and no publish-time generation. The bindings stay `{item.title}` —\n// nothing downstream (the builder's parser, the CMS panel, the row preview)\n// has to learn a new shape.\n\n/** Per-locale field values carried on a collection item. */\nexport interface RowI18n {\n [locale: string]: Record<string, unknown> | undefined;\n}\n\ntype Row = Record<string, unknown> & { _i18n?: RowI18n };\n\n/**\n * `rows` with `locale`'s translations merged over each item's base fields.\n *\n * Untranslated fields fall back to the base language rather than blanking — a\n * half-translated collection reads correctly instead of showing holes. An\n * empty string counts as untranslated for the same reason: the editor writes\n * `''` to CLEAR a translation, and honouring it literally would erase the\n * row's text.\n *\n * Returns the SAME array when nothing applies, so React can bail out of the\n * re-render on the default locale (the common case).\n */\nexport function localizeRows<T extends Row>(rows: T[], locale: string | undefined | null): T[] {\n if (!rows || !locale) return rows;\n let changed = false;\n const out = rows.map((row) => {\n const fields = row?._i18n?.[locale];\n if (!fields) return row;\n const usable = Object.entries(fields).filter(([, v]) => typeof v === 'string' && v !== '');\n if (usable.length === 0) return row;\n changed = true;\n return { ...row, ...Object.fromEntries(usable) };\n });\n return changed ? out : rows;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,IAAM,mBAAmB,IAAI,IAAI;CAC/B;CAAK;CAAK;CACV;CAAU;CAAW;CAAW;CAChC;CAAS;CAAU;CACnB;CAAQ;CAAS;CACjB;CAAW;CAAW;CACtB;CACD,CAAC;;;;AAKF,IAAM,iBAAiB,IAAI,IAAI;CAC7B;CAAY;CAAQ;CAAO;CAAS;CAAU;CAC9C;CAAS;CAAU;CAAY;CAAa;CAAY;CACxD;CAAQ;CAAY;CAAc;CAClC;CAAS;CAAa;CAAe;CACrC;CAAU;CAAa;CAAe;CAAgB;CACtD;CAAc;CAAW;CAC1B,CAAC;;;;AAIF,SAAS,iBAAiB,eAAkC;AAC1D,KAAI,CAAC,cAAe,QAAO,EAAE;AAC7B,KAAI;EACF,MAAM,YAAY,OAAO,kBAAkB,WACvC,KAAK,MAAM,cAAc,GAAG;AAIhC,SAAO,CAAC,GAHI,MAAM,QAAQ,UAAU,IAAI,GACpC,UAAU,MACV,OAAO,KAAK,UAAU,CAAC,QAAQ,MAAM,MAAM,MAAM,CAAC,IAAI,OAAO,CAClD,CAAC,QAAQ,MAAM,OAAO,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,MAAM,GAAG,MAAM,IAAI,EAAE;SAC1E;AACN,SAAO,EAAE;;;;;;AAOb,SAAS,cAAc,OAAe,UAAmC;AACvE,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,IAEnC,KAAI,SADU,IAAI,IAAI,SAAS,IAAI,KAAK,MACnB,SAAS,SAAS,GAAI,QAAO,SAAS;AAE7D,QAAO;;AAGT,SAAwB,oBACtB,WACmF;AAMnF,QAAO,WAAW,SAAS,gBAAgB,OAAY,QAAa;EAClE,MAAM,gBAAgB,MAAM;EAC5B,MAAM,gBAAgB,MAAM;EAK5B,MAAM,WAAW,cAAc,iBAAiB,cAAc,EAAE,CAAC,cAAc,CAAC;EAGhF,MAAM,QAAQ,OAAO,SAAS;AAC9B,QAAM,UAAU;EAehB,MAAM,CAAC,IAAI,SAAS,eAAe;GACjC,MAAM,UAAU,cACd,OAAO,WAAW,cAAc,OAAO,aAAa,MACpD,SACD;AACD,UAAO;IAAE,KAAK;IAAS,MAAM;IAAS;IACtC;AAEF,kBAAgB;AACd,OAAI,kBAAkB,KAAA,EAAW;GACjC,MAAM,aAAa;IACjB,MAAM,OAAO,cAAc,OAAO,YAAY,MAAM,QAAQ;AAE5D,WAAO,MAAO,EAAE,QAAQ,OAAO,IAAI;KAAE,KAAK;KAAM,MAAM,EAAE;KAAK,CAAE;;AAEjE,SAAM;AACN,UAAO,iBAAiB,UAAU,KAAK;AACvC,gBAAa,OAAO,oBAAoB,UAAU,KAAK;KACtD,CAAC,cAAc,CAAC;AAInB,kBAAgB;AACd,OAAI,GAAG,QAAQ,GAAG,KAAM,QAAO,OAAO;IAAE,KAAK,EAAE;IAAK,MAAM,EAAE;IAAK,EAAE;KAClE,CAAC,GAAG,CAAC;EAeR,MAAM,WAAW,GAAG,QAAQ,GAAG;AAEf,mBAAiB,GAAG;EACpC,IAAI,cAAc,EAAE,GAAG,OAAO;AAE9B,MAAI,cACF,KAAI;GACF,MAAM,YAAY,OAAO,kBAAkB,WACvC,KAAK,MAAM,cAAc,GAAG;GAGhC,MAAM,YAAY,kBAAkB,KAAA,IAChC,cAAc,eAAe,SAAS,GACtC,GAAG;AACP,OAAI,cAAc,QAAQ,UAAU,YAAY;IAC9C,MAAM,KAAK,UAAU;AAMrB,QAAI,MAAM,0BAA0B,MAAM,OAAO,OAAO,YAAY,oBAAoB,IAAI;KAC1F,MAAM,EAAE,gBAAgB,OAAO,GAAG,SAAS;AAC3C,mBAAc;MAAE,GAAG;MAAa,GAAG;MAAM;UAEzC,eAAc;KAAE,GAAG;KAAa,GAAG;KAAI;;UAGrC;AAGV,SAAO,YAAY;AACnB,SAAO,YAAY;EAMnB,MAAM,QAAQ,YAAY;EAC1B,IAAI,eAAe,UAAU;AAC7B,MAAI,CAAC,gBAAgB;QACd,MAAM,KAAK,OAAO,KAAK,MAAM,CAChC,KAAI,iBAAiB,IAAI,EAAE,IAAI,cAAc,MAAM,GAAG,EAAE;AAAE,mBAAe;AAAM;;;AAGnF,MAAI,cAAc;GAChB,MAAM,eAAoC,EAAE;GAC5C,MAAM,aAAkC,EAAE;AAC1C,QAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,SAAS,EAAE,CAAC,CAC9C,KAAI,iBAAiB,IAAI,EAAE,IAAI,eAAe,IAAI,EAAE,IAAI,cAAc,EAAE,CAAE,cAAa,KAAK;OACvF,YAAW,KAAK;AAKvB,OAAI,WAAW,aAAc,YAAW,QAAQ;AAChD,OAAI,YAAY,aAAc,YAAW,SAAS;AAWlD,cAAW,WAAW;GACtB,MAAM,EAAE,OAAO,QAAQ,GAAG,SAAS;AACnC,UAAO,KAAK;AAkBZ,UAAO,oBAAoB,UACzB,oBAAC,OAAO,KAAR;IACE,KAAK;IACL,WAAS,KAAK;IACd,aAAW,KAAK;IAChB,OAAO;cAEP,oBAAC,WAAD;KAAW,GAAI;KAAM,OAAO;KAAc,CAAA;IAC/B,CAAA,CACd;;AAGH,SAAO,oBAAoB,UAAU,oBAAC,WAAD,EAAW,GAAI,aAAe,CAAA,CAAC;GACpE;;;;;AAMJ,SAAS,oBAAoB,UAAmB,MAA8C;AAC5F,KAAI,CAAC,SAAU,QAAO;AACtB,QAAO,oBAAC,cAAD;EAAc,YAAY,EAAE,UAAU,GAAG;YAAG;EAAoB,CAAA;;;;AC1MzE,IAAI,UAA+B;AACnC,IAAM,6BAAa,IAAI,KAAiB;AAcxC,IAAM,iCAAiB,IAAI,SAAqC;AAChE,IAAI,oBAAoB;AACxB,SAAS,QAAQ,GAA+B;CAC9C,IAAI,IAAI,eAAe,IAAI,EAAE;AAC7B,KAAI,MAAM,KAAA,GAAW;AACnB,MAAI,EAAE;AACN,iBAAe,IAAI,GAAG,EAAE;;AAE1B,QAAO;;AAST,IAAI,gBAAsD;AAE1D,SAAS,WAAW,MAA2B;AAC7C,WAAU;AACV,YAAW,SAAS,MAAM,GAAG,CAAC;;AAGhC,SAAS,WAAW,GAAe;AACjC,YAAW,IAAI,EAAE;AACjB,cAAa;AAAE,aAAW,OAAO,EAAE;;;AAGrC,SAAS,aAAa;AACpB,QAAO;;;;;;;;AAST,SAAgB,WAAc,WAA6B,OAAsB,EAAE,EAAE;AACnF,QAAO;EACL,oBAAoB;AAClB,OAAI,kBAAkB,MAAM;AAC1B,iBAAa,cAAc;AAC3B,oBAAgB;;AAElB,cAAW;IAAE,KAAK,QAAQ,UAAgC;IAAa;IAAiC;IAAM,CAAC;;EAEjH,oBAAoB;AAClB,OAAI,kBAAkB,KAAM,cAAa,cAAc;AACvD,mBAAgB,iBAAiB;AAC/B,oBAAgB;AAChB,eAAW,KAAK;MACf,GAAG;;EAET;;AAKH,SAAS,cAAc,GAAsB;AAC3C,KAAI,CAAC,KAAK,EAAE,SAAS,UAAW,QAAO;EAAE,WAAW;EAAM,SAAS;EAAI,MAAM;EAAK;AAClF,KAAI,EAAE,SAAS,WAAW,EAAE,SAG1B,QAAO;EAAE,WADS,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI,IAAK,EAAE,SAAS,CACrD;EAAW,SAAS;EAAI,MAAM;EAAG;AAE5C,QAAO;EACL,WAAW,EAAE,aAAa;EAC1B,SAAS,EAAE,WAAW;EACtB,MAAM,EAAE,QAAQ;EACjB;;AAGH,SAAgB,eAAe;CAC7B,MAAM,SAAS,qBAAqB,YAAY,YAAY,WAAW;CAEvE,MAAM,IAAI,eAAe,EAAE;CAC3B,MAAM,IAAI,eAAe,EAAE;CAC3B,MAAM,KAAK,UAAU,GAAG,cAAc,QAAQ,KAAK,WAAW,CAAC;CAC/D,MAAM,KAAK,UAAU,GAAG,cAAc,QAAQ,KAAK,WAAW,CAAC;CAK/D,MAAM,YAAY,OAAO;EAAE,GAAG;EAAG,GAAG;EAAG,CAAC;AACxC,WAAU,UAAU;EAAE,GAAG,QAAQ,KAAK,WAAW;EAAG,GAAG,QAAQ,KAAK,WAAW;EAAG;AAClF,iBAAgB;EACd,MAAM,UAAU,MAAkB;AAChC,KAAE,IAAI,EAAE,UAAU,UAAU,QAAQ,EAAE;AACtC,KAAE,IAAI,EAAE,UAAU,UAAU,QAAQ,EAAE;;AAExC,SAAO,iBAAiB,aAAa,QAAQ,EAAE,SAAS,MAAM,CAAC;AAC/D,eAAa,OAAO,oBAAoB,aAAa,OAAO;IAC3D,CAAC,GAAG,EAAE,CAAC;CAMV,MAAM,OAAO,QAAQ,KAAK;AAC1B,iBAAgB;AACd,MAAI,SAAS,WAAW;GACtB,MAAM,OAAO,SAAS,KAAK,MAAM;AACjC,YAAS,KAAK,MAAM,SAAS;AAC7B,gBAAa;AAAE,aAAS,KAAK,MAAM,SAAS;;;IAE7C,CAAC,KAAK,CAAC;CAIV,MAAM,QAAQ,OAAO,QAAQ,KAAK,UAAU,WAAW,OAAO,KAAK,QAAQ,OAAO,QAAQ,KAAK;CAC/F,MAAM,QAAQ,OAAO,QAAQ,KAAK,WAAW,WAAW,OAAO,KAAK,SAAS,OAAO,QAAQ,KAAK;CAMjG,MAAM,aAAa;EACjB,UAAU;EACV,KAAK;EACL,MAAM;EACN,GAAG;EACH,GAAG;EACH,eAAe;EACf,QAAQ;EACT;CAED,MAAM,aAAa;EACjB,OAAO;EACP,QAAQ;EACR,WAJqB,gBAAgB,QAAQ,KAIlC;EACZ;CAaD,MAAM,eAAe,QAAQ,KAAK,UAAU,EAAE,gBAAgB,OAAO,KAAK,SAAS,GAAG,EAAE;AAExF,KAAI,CAAC,QAAQ,KAAK,UAChB,QAAO,SACL,oBAAC,OAAO,KAAR;EAA6B,OAAO;YAClC,oBAAC,OAAD;GAAK,OAAO;aACV,oBAAC,OAAO,WAAR;IAAkB,GAAK,OAAO,KAAK,SAAS,EAAE;IAAG,GAAI;IAAgB,CAAA;GACjE,CAAA;EACK,EAJI,OAAO,IAIX,GACX;AAGN,QACE,oBAAC,iBAAD,EAAA,UACG,UACC,oBAAC,OAAO,KAAR;EAEE,OAAO;EACP,SAAS;GAAE,SAAS;GAAG,OAAO;GAAK;EACnC,SAAS;GAAE,SAAS;GAAG,OAAO;GAAG;EACjC,MAAM;GAAE,SAAS;GAAG,OAAO;GAAK;YAEhC,oBAAC,OAAD;GAAK,OAAO;aACV,oBAAC,OAAO,WAAR;IAAkB,GAAK,OAAO,KAAK,SAAS,EAAE;IAAG,GAAI;IAAgB,CAAA;GACjE,CAAA;EACK,EATN,OAAO,IASD,EAEC,CAAA;;;;;;;;AAUtB,SAAS,gBAAgB,MAAmB;AAC1C,KAAI,CAAC,QAAQ,KAAK,SAAS,UAAW,QAAO;CAC7C,MAAM,OAAO,KAAK,QAAQ;CAC1B,MAAM,QAAQ,KAAK,SAAS;CAC5B,IAAI,KAAK;CACT,IAAI,KAAK;AACT,KAAI,SAAS,MAAO,MAAK;UAChB,SAAS,OAAQ,MAAK;AAG/B,KAAI,SAAS,SAAS,SAAS;MACzB,UAAU,SAAU,MAAK;WACpB,UAAU,MAAO,MAAK;YAE3B,UAAU,SAAU,MAAK;UACpB,UAAU,MAAO,MAAK;AAEjC,QAAO,eAAe,KAAK,QAAQ,KAAK;;;;;;;;;;;;;;;;;;;;;AChQ1C,SAAgB,kBAA2B;AACzC,QAAO;;;;AC4DT,IAAM,aAAkC,EAAE,YAAY,UAAU;;;;AAIhE,IAAM,YAAiC;CACrC,YAAY;CAAU,SAAS;CAAgB,UAAU;CACzD,eAAe;CAAU,cAAc;CACxC;;;;;AAMD,SAAS,cAAc,GAAiE;AACtF,KAAI,MAAM,KAAA,KAAa,MAAM,QAAQ,EAAE,CAAE,QAAO;CAChD,MAAM,IAAI,OAAO,EAAE,CAAC,MAAM;AAC1B,KAAI,CAAC,EAAE,WAAW,IAAI,CAAE,QAAO;CAC/B,MAAM,IAAI,EAAE,QAAQ,UAAU,GAAG,CAAC,MAAM,IAAI,CAAC,KAAK,MAAM,WAAW,EAAE,MAAM,CAAC,CAAC;AAC7E,QAAO,EAAE,WAAW,KAAK,EAAE,MAAM,OAAO,SAAS,GAAG,IAAI,KAAA;;;;;;;;AAS1D,SAAS,cAAc,MAAsC;AAC3D,KAAI,SAAS,QAAQ,SAAS,KAAA,KAAa,SAAS,SAAS,SAAS,KAAM,QAAO;AACnF,KAAI,OAAO,SAAS,SAAU,QAAO;AACrC,KAAI,OAAO,SAAS,SAAU,QAAO,OAAO,KAAK;AACjD,KAAI,MAAM,QAAQ,KAAK,EAAE;EACvB,IAAI,MAAM;AACV,OAAK,MAAM,SAAS,MAAM;GACxB,MAAM,OAAO,cAAc,MAAM;AACjC,OAAI,SAAS,KAAM,QAAO;AAC1B,UAAO;;AAET,SAAO;;AAET,KAAI,MAAM,eAAe,KAAK,EAAE;EAC9B,MAAM,OAAQ,KAA4B;AAC1C,MAAI,SAAS,KAAM,QAAO;AAC1B,MAAI,SAAS,MAAM,SAAU,QAAO,cAAe,KAAK,OAAe,SAAS;AAChF,SAAO;;AAET,QAAO;;;;;;;;AAST,SAAS,oBAAoB,MAAqB,SAA0B;CAC1E,MAAM,UAAU,KAAK;CACrB,MAAM,UAAU,MAAM,kBAA0B;AAC9C,MAAI,CAAC,WAAW,QAAQ,WAAW,EAAG,QAAO;AAC7C,OAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;GACvC,MAAM,QAAQ,QAAQ,GAAG;AACzB,OAAI,aAAa;QACX,YAAY,KAAA,KAAa,MAAM,YAAY,QAAS,QAAO;cACtD,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe;QACnE,OAAO,WAAW,MAAM,MAAM,CAAC,QAAS,QAAO;;;AAGvD,SAAO;IACN,CAAC,SAAS,QAAQ,CAAC;CAEtB,MAAM,CAAC,KAAK,UAAU,MAAM,SAAS,QAAQ;AAE7C,OAAM,gBAAgB;AACpB,SAAO,SAAS,CAAC;AACjB,MAAI,CAAC,WAAW,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,WAAY;EAC1F,MAAM,QAAQ,QACX,KAAK,MAAO,WAAW,EAAE,QAAQ,OAAO,WAAW,EAAE,MAAM,MAAM,GAAG,KAAM,CAC1E,OAAO,QAAQ;AAClB,MAAI,MAAM,WAAW,EAAG;EACxB,MAAM,iBAAiB,OAAO,SAAS,CAAC;AACxC,OAAK,MAAM,KAAK,MAAO,GAAE,iBAAiB,UAAU,SAAS;AAC7D,eAAa;AAAE,QAAK,MAAM,KAAK,MAAO,GAAE,oBAAoB,UAAU,SAAS;;IAC9E,CAAC,SAAS,QAAQ,CAAC;AAEtB,QAAO;;AAGT,IAAM,UAAkC;CAAE,SAAS;CAAG,OAAO;CAAG,SAAS;CAAG,SAAS;CAAG,SAAS;CAAG,OAAO;CAAG,OAAO;CAAG,GAAG;CAAG,GAAG;CAAG;AACpI,IAAM,WAAW;CAAC;CAAW;CAAS;CAAW;CAAW;CAAW;CAAS;CAAS;CAAK;CAAI;;;AAIlG,SAAS,YAAY,GAAuC;CAC1D,MAAM,MAA2B,EAAE;AACnC,MAAK,MAAM,KAAK,UAAU;EACxB,MAAM,IAAI,EAAE;AACZ,MAAI,MAAM,KAAA,KAAa,MAAM,QAAQ,GAAI,KAAI,KAAK;;AAEpD,KAAI,EAAE,SAAS,KAAA,KAAa,EAAE,SAAS,EAAG,KAAI,SAAS,QAAQ,EAAE,KAAK;AACtE,QAAO;;AAGT,SAAS,aAAa,GAAuC;CAC3D,MAAM,MAA2B,EAAE;AACnC,MAAK,MAAM,KAAK,UAAU;EACxB,MAAM,IAAI,EAAE;AACZ,MAAI,MAAM,KAAA,KAAa,MAAM,QAAQ,GAAI,KAAI,KAAK,QAAQ;;AAE5D,KAAI,EAAE,SAAS,KAAA,KAAa,EAAE,SAAS,EAAG,KAAI,SAAS;AACvD,QAAO;;;AAIT,SAAS,UAAU,MAAuB,KAA8B;AACtE,KAAI,OAAO,SAAS,SAAU,QAAO,QAAQ,QAAQ;CACrD,MAAM,IAAI,OAAO,KAAK,CAAC,MAAM,mBAAmB;AAChD,QAAO,IAAI,GAAG,QAAQ,QAAQ,IAAI,EAAE,OAAQ,QAAQ,QAAQ;;;;;AAW9D,SAAS,WAAW,MAAc,eAA+C,MAAiD;CAChI,MAAM,QAAgB,EAAE;CACxB,IAAI,IAAI;CACR,MAAM,QAAQ,KAAK,MAAM,KAAK;AAE9B,KAAI,kBAAkB,QAAQ;AAC5B,OAAK,IAAI,KAAK,GAAG,KAAK,MAAM,QAAQ,MAAM;AACxC,OAAI,KAAK,EAAG,OAAM,KAAK;IAAE,GAAG;IAAM,GAAG,KAAK;IAAM,CAAC;AACjD,SAAM,KAAK;IAAE,GAAG;IAAQ,GAAG;KAAE,KAAK,IAAI;KAAK,OAAO,MAAM;KAAK,SAAS;KAAS,OAAO;KAAG;IAAE,CAAC;AAC5F;;AAEF,SAAO;GAAE;GAAO,OAAO;GAAG;;AAE5B,KAAI,kBAAkB,QAAQ;AAC5B,QAAM,KAAK;GAAE,GAAG;GAAQ,GAAG;IAAE,KAAK;IAAM,OAAO;IAAM,SAAS;IAAgB,OAAO;IAAG;GAAE,CAAC;AAC3F,SAAO;GAAE;GAAO,OAAO;GAAG;;AAG5B,MAAK,IAAI,KAAK,GAAG,KAAK,MAAM,QAAQ,MAAM;AACxC,MAAI,KAAK,EAAG,OAAM,KAAK;GAAE,GAAG;GAAM,GAAG,KAAK;GAAM,CAAC;EACjD,MAAM,QAAQ,MAAM,IAAI,MAAM,IAAI;AAClC,OAAK,IAAI,KAAK,GAAG,KAAK,MAAM,QAAQ,MAAM;AACxC,OAAI,KAAK,EAAG,OAAM,KAAK;IAAE,GAAG;IAAQ,GAAG,KAAK,GAAG,GAAG;IAAM,GAAG;IAAK,CAAC;GACjE,MAAM,OAAO,MAAM;AACnB,OAAI,CAAC,KAAM;AACX,OAAI,kBAAkB,QAAQ;IAC5B,MAAM,IAAU;KAAE,GAAG;KAAQ,GAAG;MAAE,KAAK,IAAI;MAAK,OAAO;MAAM,SAAS;MAAgB,OAAO;MAAG;KAAE;AAClG;AACA,UAAM,KAAK,OAAO;KAAE,GAAG;KAAQ,GAAG,IAAI,GAAG,GAAG;KAAM,MAAM,CAAC,EAAE;KAAE,GAAG,EAAE;UAC7D;IACL,MAAM,OAAe,EAAE;AACvB,SAAK,MAAM,MAAM,MAAM;AACrB,UAAK,KAAK;MAAE,GAAG;MAAQ,GAAG;OAAE,KAAK,IAAI;OAAK,OAAO;OAAI,SAAS;OAAgB,OAAO;OAAG;MAAE,CAAC;AAC3F;;AAEF,UAAM,KAAK;KAAE,GAAG;KAAQ,GAAG,IAAI,GAAG,GAAG;KAAM;KAAM,CAAC;;;;AAIxD,QAAO;EAAE;EAAO,OAAO;EAAG;;;;;;AAS5B,SAAS,WAAW,EAAE,UAAU,OAAO,UAAU,SAAS,YAMvD;CACD,MAAM,QAA6B,EAAE,SAAS;AAC9C,MAAK,MAAM,KAAK,SAEd,OAAM,EAAE,OAAO,aAAa,UAAU,OAAO,CAAC,EAAE,MAAe,EAAE,GAAY,CAAC;AAEhF,QAAO,oBAAC,OAAO,MAAR;EAAoB;EAA+B;EAAuB,CAAA;;AAKnF,SAAgB,gBAAgB,EAAE,MAAM,SAAS,YAAsD;CACrG,MAAM,OAAsB,QAAQ,EAAE;CACtC,MAAM,UAAU,MAAM,OAAwB,KAAK;CAEnD,MAAM,WAAW,oBAAoB,MAAM,QAAQ;CACnD,MAAM,WAA0B,MAAM,cAAc;AAMlD,SAAO;GAAE,GALC,YAAY,KAAK,KAAK,aAC5B;IAAE,GAAG;IAAM,GAAG,KAAK,WAAW,UAAU;IAAQ,GAChD;GAGW,eAAe,KAAK;GAAe,MAAM,KAAK;GAAM,YAAY,KAAK;GAAY;IAC/F,CAAC,MAAM,SAAS,CAAC;CAEpB,MAAM,gBAAgB,KAAK,iBAAiB;CAC5C,MAAM,OAAO,CAAC,CAAC,KAAK;CACpB,MAAM,OAAO,cAAc,SAAS;CAKpC,MAAM,SAAS,UAAU,SAAS;EAAE,MAAM;EAAM,QAAQ;EAAG,CAAC;CAG5D,MAAM,YAAY,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,SAAS,eAAe,MAAM,IAAI,CAAC;CAC9E,MAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,SAAS,aAAa,MAAM,IAAI,CAAC;CAC1E,MAAM,EAAE,oBAAoB,UAAU;EACpC,QAAQ;EACR,QAAQ,CAAC,SAAS,aAAa,SAAS,UAAU;EACnD,CAAC;CAEF,MAAM,EAAE,OAAO,UAAU,MAAM,cACvB,WAAW,QAAQ,IAAI,eAAe,KAAK,EACjD;EAAC;EAAM;EAAe;EAAK,CAC5B;AAGD,KAAI,SAAS,KAAM,QAAO,oBAAC,QAAD;EAAM,KAAK;EAAU;EAAgB,CAAA;CAE/D,MAAM,WAAW,SAAS,YAAY;CACtC,MAAM,SAAS,YAAY,SAAS;CACpC,MAAM,UAAU,aAAa,SAAS;CACtC,MAAM,UAAU,SAAS,SAAS;CAClC,MAAM,eAAe,SAAS,YAAY,SAAS;CACnD,MAAM,KAAK,SAAS,aAChB;EAAE,GAAG,SAAS;EAAY,MAAM,cAAc,SAAS,WAAW,KAAK;EAAE,OAAO,KAAA;EAAW,GAC3F;EAAE,MAAM;EAAmB,WAAW;EAAK,SAAS;EAAI;CAE5D,MAAM,WAAW,WACb,OAAO,QAAQ,OAAO,CAAC,KAAK,CAAC,KAAK,WAAW;EAC3C;EACM;EACN,IAAI,QAAQ,WAAW,cAAc,UAAU,MAAyB,IAAI;EAC7E,EAAE,GACH,EAAE;CACN,MAAM,cAAc,SAAS,KAAK,MAAM,EAAE,IAAI,CAAC,KAAK,IAAI;CAExD,MAAM,cAAc,MAAgC;AAClD,MAAI,UAAU;GACZ,MAAM,QAAQ,QAAQ,IAAI,KAAK,MAAO,EAAE,SAAS,QAAQ,KAAM,KAAM,IAAK,GAAG,MAAO;AAEpF,UACE,oBAAC,YAAD;IAEE,UAAU;IACV,OAAO,CAAC,OALA,KAAK,IAAI,GAAG,KAAK,OAAO,QAAQ,MAAO,IAAK,GAAG,IAKxC,CAAI;IACT;IACV,SAAS,EAAE;cACX,EAAE;IAAmB,EALhB,GAAG,YAAY,GAAG,EAAE,MAKJ;;AAG3B,SACE,oBAAC,OAAO,MAAR;GAEE,OAAO,EAAE,SAAS,EAAE,SAAS;GAC7B,SAAS;GACT,SAAS,SAAS,UAAU;GAC5B,YAAY;IAAE,GAAG;IAAI,OAAO,KAAK,OAAO,eAAe,EAAE,QAAQ,WAAW,IAAK,GAAG;IAAM;aAC1F,EAAE;GAAoB,EALjB,EAAE,IAKe;;CAI5B,MAAM,UAAU,MAA6B;AAC3C,MAAI,EAAE,MAAM,KAAM,QAAO,oBAAC,MAAD,EAAgB,EAAP,EAAE,EAAK;AACzC,MAAI,EAAE,MAAM,OAAQ,QAAO,EAAE;AAC7B,MAAI,EAAE,MAAM,OAAQ,QAAO,oBAAC,QAAD;GAAgB,OAAO,OAAO,YAAY;aAAa,EAAE,KAAK,IAAI,OAAO;GAAQ,EAAtE,EAAE,EAAoE;AAC5G,SAAO,WAAW,EAAE,EAAE;;AAGxB,QACE,oBAAC,QAAD;EAAM,KAAK;EAAS,OAAO,EAAE,SAAS,kBAAkB,SAAS,UAAU,UAAU;YAClF,MAAM,IAAI,OAAO;EACb,CAAA;;;;AC1TX,IAAM,eAAoG;CACxG,SAAS;CACT,MAAM;CACN,eAAe;CACf,SAAS;CACT,YAAY;EAAE,MAAM;EAAS,UAAU;EAAG,MAAM;EAAW;CAC3D,WAAW;CACZ;AAED,SAAS,UAAU,GAAW,YAA0C;AACtE,KAAI,WAAW,SAAS,UAAU;EAChC,MAAM,UAAU,WAAW,WAAW;EACtC,MAAM,YAAY,WAAW,aAAa;EAC1C,MAAM,UAAU,IAAI,KAAK,IAAI,CAAC,UAAU,IAAI,GAAI;EAChD,MAAM,cAAc,KAAK,IAAI,IAAI,KAAK,KAAK,UAAU,GAAG,GAAI;AAC5D,SAAO,KAAK,IAAI,GAAG,WAAW,IAAI,KAAM,eAAe,IAAI,IAAI;;AAEjE,SAAQ,WAAW,QAAQ,WAA3B;EACE,KAAK,SAAU,QAAO;EACtB,KAAK,SAAU,QAAO,IAAI;EAC1B,KAAK,UAAW,QAAO,KAAK,IAAI,MAAM,IAAI;EAC1C,KAAK,YAAa,QAAO,IAAI,KAAM,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,KAAK,IAAI,GAAG,EAAE,GAAG;EAC7E,KAAK,SAAU,QAAO,IAAI,KAAK,KAAK,IAAI,IAAI,EAAE;EAC9C,KAAK,UAAW,QAAO,KAAK,KAAK,IAAI,KAAK,IAAI,IAAI,GAAG,EAAE,CAAC;EACxD,KAAK,WAAW;GACd,MAAM,KAAK;AAEX,UAAO,KADI,KAAK,KACA,KAAK,IAAI,IAAI,GAAG,EAAE,GAAG,KAAK,KAAK,IAAI,IAAI,GAAG,EAAE;;EAE9D,QAAS,QAAO,KAAK,IAAI,MAAM,IAAI;;;AAIvC,SAAS,YAAY,KAAyB;AAC5C,KAAI,CAAC,IAAK,QAAO,EAAE;AACnB,QAAO,IAAI,MAAM,MAAM,CAAC,OAAO,QAAQ,CAAC,KAAI,MAAK;EAC/C,MAAM,CAAC,GAAG,GAAG,KAAK,EAAE,MAAM,IAAI;AAC9B,SAAO;GAAC,WAAW,EAAE,IAAI;GAAG,WAAW,EAAE,IAAI;GAAG,KAAK,OAAO,WAAW,EAAE,GAAG;GAAI;GAChF;;AAGJ,SAAS,WAAW,SAA6B;AAC/C,KAAI,QAAQ,WAAW,EAAG,QAAO;CACjC,IAAI,IAAI,KAAK,QAAQ,GAAG,GAAG,QAAQ,EAAE,CAAC,GAAG,QAAQ,GAAG,GAAG,QAAQ,EAAE;AACjE,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,IAClC,MAAK,MAAM,QAAQ,GAAG,GAAG,QAAQ,EAAE,CAAC,GAAG,QAAQ,GAAG,GAAG,QAAQ,EAAE;AAEjE,QAAO,IAAI;;;;;;;;;;;;;;AAeb,SAAgB,eACd,WACA,WAA2B,EAAE,EACjB;CACZ,MAAM,aAAa;AACnB,KAAI,CAAC,UAAW,QAAO;CACvB,MAAM,OAAO;EAAE,GAAG;EAAc,GAAG;EAAU,YAAY;GAAE,GAAG,aAAa;GAAY,GAAG,SAAS;GAAY;EAAE;CAEjH,MAAM,QAAQ,MAAM,KAAK,UAAU,iBAAiB,oBAAoB,CAAC;AACzE,KAAI,MAAM,WAAW,EAAG,QAAO;CAK/B,MAAM,UAAU,MAAM,KAAI,MAAK,EAAE,aAAa,IAAI,IAAI,GAAG;CACzD,MAAM,aAAa,MAAM,KAAI,MAAK,YAAY,EAAE,aAAa,cAAc,IAAI,GAAG,CAAC;AAGnF,OAAM,SAAQ,MAAK,EAAE,aAAa,KAAK,GAAG,CAAC;CAI3C,MAAM,WAAW,KAAK,WAAW,YAAY,KAAK,MAAO,KAAK;CAC9D,MAAM,YAAY,WAAW,QAAQ,GAAG,MAAM,KAAK,IAAI,GAAG,EAAE,OAAO,EAAE,EAAE;CACvE,MAAM,eAAe,WAAW,KAAI,MAAK,WAAW,EAAE,SAAS,WAAW;CAC1E,MAAM,UAAoB,EAAE;CAC5B,IAAI,SAAS;AACb,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,IAChC,KAAI,KAAK,SAAS,eAChB,SAAQ,KAAK,EAAE;UACN,KAAK,SAAS,aAAa;EACpC,MAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,QAAQ,CAAC;EACtD,MAAM,QAAQ,MAAM,IAAI,IAAI,QAAQ,IAAI,KAAK,aAAa,IAAI,MAAM,IAAI;AACxE,UAAQ,KAAK,MAAM;QACd;AAEL,UAAQ,KAAK,OAAO;AACpB,YAAU,aAAa;;CAI3B,IAAI,YAAY;CAChB,IAAI,QAAQ;CACZ,IAAI,UAAU;CACd,IAAI,iBAAsC;CAC1C,IAAI,UAAU;CAEd,MAAM,QAAQ,QAAgB;AAC5B,MAAI,UAAW;EACf,MAAM,UAAU,MAAM;EACtB,IAAI,UAAU;AACd,OAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;GACrC,MAAM,QAAQ,UAAU,QAAQ;AAChC,OAAI,QAAQ,GAAG;AAAE,cAAU;AAAO;;GAClC,MAAM,IAAI,KAAK,IAAI,GAAG,QAAQ,KAAK,IAAI,GAAG,aAAa,GAAG,CAAC;AAC3D,OAAI,IAAI,EAAG,WAAU;GACrB,IAAI;AACJ,OAAI,KAAK,EACP,KAAI,QAAQ;QACP;IACL,MAAM,QAAQ,UAAU,GAAG,KAAK,WAAW;IAC3C,MAAM,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,WAAW,GAAG,SAAS,MAAM,CAAC;IACxE,MAAM,SAAS,WAAW,GAAG,MAAM,GAAG,WAAW;AACjD,QAAI,OAAO,SAAS,EAClB,KAAI;QAKJ,KAAI,WAHY,UAAU,QAAQ;KAChC,MAAM,KAAK;KAAW,UAAU;KAAK,WAAW;KAAK,YAAY;KAClE,CACc,CAAQ;;AAG3B,SAAM,GAAG,aAAa,KAAK,EAAE;;AAE/B,MAAI,CAAC,QAAS,SAAQ,sBAAsB,KAAK;;CAGnD,MAAM,cAAc;AAClB,MAAI,QAAS;AACb,YAAU;AACV,YAAU,YAAY,KAAK;AAC3B,UAAQ,sBAAsB,KAAK;;AAGrC,KAAI,KAAK,YAAY,UAAU;EAC7B,MAAM,MAAM,IAAI,sBAAsB,YAAY;AAChD,OAAI,QAAQ,MAAK,MAAK,EAAE,eAAe,EAAE;AACvC,WAAO;AACP,QAAI,YAAY;;KAEjB,EAAE,WAAW,IAAK,CAAC;AACtB,MAAI,QAAQ,UAAU;AACtB,yBAAuB,IAAI,YAAY;YAC9B,KAAK,YAAY,SAAS;EACnC,MAAM,gBAAgB,OAAO;AAC7B,YAAU,iBAAiB,cAAc,QAAQ;AACjD,yBAAuB,UAAU,oBAAoB,cAAc,QAAQ;YAClE,KAAK,YAAY,OAAO;EACjC,MAAM,cAAc,OAAO;AAC3B,YAAU,iBAAiB,SAAS,MAAM;AAC1C,yBAAuB,UAAU,oBAAoB,SAAS,MAAM;OAGpE,QAAO;AAGT,cAAa;AACX,cAAY;AACZ,uBAAqB,MAAM;AAC3B,oBAAkB;;;;;;;;;;;;;;;;;ACrLtB,SAAgB,aAA4B,MAAW,QAAwC;AAC7F,KAAI,CAAC,QAAQ,CAAC,OAAQ,QAAO;CAC7B,IAAI,UAAU;CACd,MAAM,MAAM,KAAK,KAAK,QAAQ;EAC5B,MAAM,SAAS,KAAK,QAAQ;AAC5B,MAAI,CAAC,OAAQ,QAAO;EACpB,MAAM,SAAS,OAAO,QAAQ,OAAO,CAAC,QAAQ,GAAG,OAAO,OAAO,MAAM,YAAY,MAAM,GAAG;AAC1F,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,YAAU;AACV,SAAO;GAAE,GAAG;GAAK,GAAG,OAAO,YAAY,OAAO;GAAE;GAChD;AACF,QAAO,UAAU,MAAM"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/withResponsiveProps.tsx","../src/cursor-runtime.tsx","../src/useStaticCanvas.ts","../src/split-text.tsx","../src/sketch-draw.ts","../src/localize-rows.ts"],"sourcesContent":["'use client';\n\nimport { useState, useEffect, useMemo, useRef, forwardRef, type ComponentType } from 'react';\nimport { motion, isMotionValue, MotionConfig } from 'framer-motion';\n\n/**\n * HOC that reads a `data-responsive` JSON prop and merges per-viewport overrides.\n * Usage: `export default withResponsiveProps(MyComponent)`\n *\n * On the canvas, CodeComponentHost injects `__canvasViewportWidth` to simulate viewport size.\n * In production, uses `window.innerWidth`.\n *\n * data-responsive='{\"768\":{\"fontSize\":32},\"375\":{\"fontSize\":24}}'\n * Breakpoints are max-width: if viewport <= 768, the 768 overrides apply.\n *\n * ANIMATED-STYLE SOCKET (Framer parity): the editor expresses instance\n * animation effects (Appear, scroll scrubs, …) as framer MotionValues bound\n * into the instance's `style` prop — `style={{ opacity: <mv>, y: <mv> }}`.\n * Design components consume them natively (their roots are motion.* and\n * spread `...style`), but a CODE component's root is a plain element: a\n * MotionValue arrives as an un-serialisable object (the style is dropped)\n * and motion-only keys like `y` aren't CSS at all — the effect silently\n * dies at the component boundary (live find 2026-07-14: Appear on a\n * Marquee). When animated values or motion-only keys are present, the HOC\n * renders a motion.div WRAPPER that carries them (plus the placement props\n * that describe the instance's slot in its parent), and hands the wrapped\n * component a clean static style. Framer does exactly this — the platform\n * owns an animatable container around every code component, so component\n * authors never deal with animation plumbing. No animated values → no\n * wrapper → byte-identical behaviour to before.\n */\n\n/** Style keys only a motion.* element understands (translated into\n * `transform`) — a plain DOM element ignores them entirely. */\nconst MOTION_ONLY_KEYS = new Set([\n 'x', 'y', 'z',\n 'rotate', 'rotateX', 'rotateY', 'rotateZ',\n 'scale', 'scaleX', 'scaleY',\n 'skew', 'skewX', 'skewY',\n 'originX', 'originY', 'originZ',\n 'transformPerspective',\n]);\n\n/** Placement props describing the instance's slot in ITS PARENT's layout —\n * these must ride on whichever element is outermost (the wrapper, when one\n * exists), mirroring the design-instance wrapper/root split. */\nconst PLACEMENT_KEYS = new Set([\n 'position', 'left', 'top', 'right', 'bottom', 'inset',\n 'width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight',\n 'flex', 'flexGrow', 'flexShrink', 'flexBasis',\n 'order', 'alignSelf', 'justifySelf', 'zIndex',\n 'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft',\n 'gridColumn', 'gridRow', 'gridArea',\n]);\n/** Ascending breakpoint widths out of a `data-responsive` payload. `_bp` carries\n * the full viewport list; without it, fall back to the override keys (an older\n * payload, or one written before every viewport had an override). */\nfunction parseBreakpoints(responsiveStr: unknown): number[] {\n if (!responsiveStr) return [];\n try {\n const overrides = typeof responsiveStr === 'string'\n ? JSON.parse(responsiveStr) : responsiveStr;\n const all = Array.isArray(overrides._bp)\n ? overrides._bp\n : Object.keys(overrides).filter((k) => k !== '_bp').map(Number);\n return [...all].filter((n) => Number.isFinite(n) && n > 0).sort((a, b) => a - b);\n } catch {\n return [];\n }\n}\n\n/** Which breakpoint owns `width`. Each breakpoint's range is `(prev, bp]`, so\n * the buckets don't cascade. `null` = wider than every breakpoint, i.e. the\n * primary/base design with no override. */\nfunction resolveBucket(width: number, sortedBp: number[]): number | null {\n for (let i = 0; i < sortedBp.length; i++) {\n const lower = i > 0 ? sortedBp[i - 1] : 0;\n if (width > lower && width <= sortedBp[i]) return sortedBp[i];\n }\n return null;\n}\n\nexport default function withResponsiveProps<P extends Record<string, any>>(\n Component: ComponentType<P>\n): ComponentType<P & { 'data-responsive'?: string; __canvasViewportWidth?: number }> {\n // forwardRef: scroll effects target the instance with `ref={…}` for\n // `useScroll({ target })` — a plain function component would silently drop\n // it (framer then throws \"Target ref is defined but not hydrated\"). The ref\n // pins to the animated wrapper (a real DOM box = the component's exact\n // footprint), so scroll measurement works with the component untouched.\n return forwardRef(function ResponsiveSpark(props: any, fwdRef: any) {\n const canvasVpWidth = props.__canvasViewportWidth as number | undefined;\n const responsiveStr = props['data-responsive'];\n\n // Breakpoint list, parsed once per distinct `data-responsive` string. The\n // resize listener needs it to bucket a width, and re-parsing JSON on every\n // resize event would be its own waste.\n const sortedBp = useMemo(() => parseBreakpoints(responsiveStr), [responsiveStr]);\n // Read by the resize listener, which is registered once and must not go\n // stale if the breakpoint list changes underneath it.\n const bpRef = useRef(sortedBp);\n bpRef.current = sortedBp;\n\n // STATE IS THE MATCHED BREAKPOINT, NOT THE RAW WIDTH.\n //\n // This used to hold `window.innerWidth` and set it on every resize event,\n // so dragging a window edge re-rendered the whole design component\n // hundreds of times — each commit re-measuring a projection tree that can\n // be 30+ `layout` nodes deep, while the variant's non-tweenable props\n // (position/display) were mid-flight on motion's own loop. Footers came out\n // of a resize with their contents stranded and invisible (user report\n // 2026-08-09). Only the BUCKET can change what renders, so only the bucket\n // belongs in state: one commit per crossing instead of one per mouse move.\n //\n // `prev` rides along so the crossing render can be identified — see\n // `crossing` below. Equal cur/prev = settled.\n const [bp, setBp] = useState(() => {\n const initial = resolveBucket(\n typeof window !== 'undefined' ? window.innerWidth : 1440,\n sortedBp,\n );\n return { cur: initial, prev: initial };\n });\n\n useEffect(() => {\n if (canvasVpWidth !== undefined) return;\n const read = () => {\n const next = resolveBucket(window.innerWidth, bpRef.current);\n // Same bucket → same object → React bails out without re-rendering.\n setBp((s) => (s.cur === next ? s : { cur: next, prev: s.cur }));\n };\n read(); // width may have moved between first render and this effect\n window.addEventListener('resize', read);\n return () => window.removeEventListener('resize', read);\n }, [canvasVpWidth]);\n\n // Release the crossing flag on the commit after it landed, so instant\n // transitions apply to the switch itself and nothing after it.\n useEffect(() => {\n if (bp.cur !== bp.prev) setBp((s) => ({ cur: s.cur, prev: s.cur }));\n }, [bp]);\n\n // A CROSSING must not animate. Everything the variant switch touches —\n // width, gap, flex-direction, position — changes at once, and `layout`\n // FLIP-animates all of it by default: the component visibly morphs from\n // the desktop arrangement to the mobile one instead of just being mobile.\n // A viewport is a state of the world, not a transition between states.\n //\n // MotionConfig sets the DEFAULT transition for every descendant, layout\n // projections included, so this covers a whole design component without\n // codegen touching a single element. Caveat worth knowing: an element with\n // its own `transition` prop overrides the default, so an appear effect's\n // `transition={{duration: 0.6}}` still governs that element's layout\n // animation. Fixing those needs the per-element route (position/display\n // out of the variants object), not this.\n const crossing = bp.cur !== bp.prev;\n\n const vpWidth = canvasVpWidth ?? bp.cur ?? Infinity;\n let mergedProps = { ...props };\n\n if (responsiveStr) {\n try {\n const overrides = typeof responsiveStr === 'string'\n ? JSON.parse(responsiveStr) : responsiveStr;\n // On the canvas the tile width is authoritative and arrives per render;\n // off-canvas `vpWidth` IS the resolved bucket already.\n const matchedBp = canvasVpWidth !== undefined\n ? resolveBucket(canvasVpWidth, sortedBp)\n : bp.cur;\n if (matchedBp !== null && overrides[matchedBp]) {\n const ov = overrides[matchedBp];\n // A Scroll Variant binds `initialVariant={…Sv}` and OWNS it at runtime (it morphs the\n // variant on scroll). The per-viewport `data-responsive` entry must NOT override that\n // here — otherwise it freezes the variant on replicas and the morph never plays. The\n // per-viewport variant CHOICE still drives the canvas + seeds the Sv's resting; only\n // the runtime merge skips `initialVariant`. (No scroll variant → unchanged behaviour.)\n if (props['data-scroll-variant'] && ov && typeof ov === 'object' && 'initialVariant' in ov) {\n const { initialVariant: _skip, ...rest } = ov;\n mergedProps = { ...mergedProps, ...rest };\n } else {\n mergedProps = { ...mergedProps, ...ov };\n }\n }\n } catch {}\n }\n\n delete mergedProps['data-responsive'];\n delete mergedProps['__canvasViewportWidth'];\n\n // ── Flow-position safety net ──\n // Design-component masters bake `position: 'absolute'` on their root\n // (canvas master tiling; no left/top — variant x/y live in variantConfig)\n // and rely on the INSTANCE style overriding it via the trailing\n // `...style` spread. An instance authored WITHOUT a `position` key\n // (Make Component's transfer gap, pre-oracle AI writes, hand-written\n // code) lets the master's absolute leak on the live site: absolute with\n // no offsets takes its static position, and since every such sibling is\n // out of flow they all compute the SAME static position — repeated\n // instances stack on one spot (the collapsed-footer bug, 2026-08-12).\n // The editor canvas neutralizes master absolutes during instance\n // expansion, so the violation is invisible there; this net restores\n // canvas/live parity. Intentional out-of-flow instances always carry\n // their own `position` (+ offsets) and are untouched.\n if (mergedProps.style == null || (mergedProps.style as Record<string, any>).position == null) {\n mergedProps = {\n ...mergedProps,\n style: { position: 'relative', ...((mergedProps.style as Record<string, any>) ?? {}) },\n };\n }\n\n // ── Animated-style socket (see header comment) ──\n // A forwarded ref ALSO forces the wrapper: it's the scroll-effect target,\n // which must resolve to a real DOM box even when the style carries no\n // animated values yet.\n //\n // `data-size-hug` (instance-auto-size, 2026-08-15) forces it too: the\n // editor writes a per-variant hug as `height: 'auto'` in the instance's\n // dim ternary, and on a NON-animated design instance the style would\n // spread straight into the root — `auto` on a leaf div collapses it to\n // its (empty) content. The wrapper restores the design-instance split:\n // placement (incl. the auto dim) rides the wrapper, the root keeps its\n // own baked master dim, and CSS auto wraps it at exactly the master's\n // size — tracking master edits with zero codegen at publish time.\n const style = mergedProps.style as Record<string, any> | undefined;\n let needsWrapper = fwdRef != null || mergedProps['data-size-hug'] != null;\n if (!needsWrapper && style) {\n for (const k of Object.keys(style)) {\n if (MOTION_ONLY_KEYS.has(k) || isMotionValue(style[k])) { needsWrapper = true; break; }\n }\n }\n if (needsWrapper) {\n const wrapperStyle: Record<string, any> = {};\n const innerStyle: Record<string, any> = {};\n for (const [k, v] of Object.entries(style ?? {})) {\n if (MOTION_ONLY_KEYS.has(k) || PLACEMENT_KEYS.has(k) || isMotionValue(v)) wrapperStyle[k] = v;\n else innerStyle[k] = v;\n }\n // The inner component fills the wrapper — but only along axes the\n // wrapper sized to a DEFINITE value (code-component instances always\n // carry definite dims; the guard keeps an unsized axis hugging\n // content). A hug axis (`'auto'`) must NOT fill: 100% of an auto box\n // resolves to nothing — the root's own baked dim is the size the\n // wrapper hugs.\n const definite = (v: unknown) => v != null && v !== 'auto';\n if (definite(wrapperStyle.width)) innerStyle.width = '100%';\n if (definite(wrapperStyle.height)) innerStyle.height = '100%';\n // The wrapper OWNS the instance's slot (all placement keys moved onto\n // it) — the inner root must re-base INTO the wrapper. Design-component\n // masters bake `position: 'absolute'` on their root (canvas master\n // tiling) and rely on the instance style overriding it via the trailing\n // `...style` spread; with the split the position key never reaches them,\n // so the root absolute-positioned inside a zero-size wrapper and\n // vanished (live find 2026-07-28: Sign Up button instance inside an\n // AnimatePresence popLayout header — popLayout's ref forces the\n // wrapper). Position is a PLACEMENT key, so innerStyle can never carry\n // its own — always re-base.\n innerStyle.position = 'relative';\n const { style: _split, ...rest } = mergedProps;\n delete rest.ref; // React 19 passes `ref` as a prop — never forward it inward\n // The wrapper owns the instance's BOX, so it must also own the instance's\n // IDENTITY. Every per-viewport override the editor writes is a\n // `[data-id=\"…\"] { … !important }` rule in the page's <style> block, and\n // the only styles an instance tag may carry are placement ones\n // (the editor's WRAPPER_ONLY_STYLE_PROPS + width/height) — exactly the\n // keys that just moved onto the wrapper. With the id only on the inner,\n // those rules land on an element this split has already pinned to\n // `width/height: 100%; position: relative`, so they do nothing: the\n // canvas (whose own instance wrapper carries the data-id) painted the\n // override and the published page ignored it. Live find 2026-08-08 —\n // a mobile `width: 100%` on a button instance that had an Appear effect,\n // which is what forces the wrapper in the first place.\n //\n // COPIED, not moved: the inner keeps `rest`'s attributes so nothing that\n // already matches them stops matching. The duplicate is safe precisely\n // because an instance rule can only carry placement, and placement is\n // inert on a lone block child that already fills its parent.\n return withInstantCrossing(crossing,\n <motion.div\n ref={fwdRef}\n data-id={rest['data-id']}\n data-name={rest['data-name']}\n style={wrapperStyle}\n >\n <Component {...rest} style={innerStyle} />\n </motion.div>,\n );\n }\n\n return withInstantCrossing(crossing, <Component {...mergedProps} />);\n }) as any;\n}\n\n/** Wrap the subtree so a breakpoint crossing lands with no animation. Only on\n * the crossing commit — the tree is otherwise handed through untouched, so a\n * component that never crosses renders exactly what it always did. */\nfunction withInstantCrossing(crossing: boolean, tree: React.ReactElement): React.ReactElement {\n if (!crossing) return tree;\n return <MotionConfig transition={{ duration: 0 }}>{tree}</MotionConfig>;\n}\n","'use client';\n\nimport { useEffect, useRef, useSyncExternalStore, type ComponentType } from 'react';\nimport { motion, AnimatePresence, useMotionValue, useSpring } from 'framer-motion';\n\nexport type CursorMode = 'follow' | 'replace';\nexport type CursorSide = 'top' | 'bottom' | 'left' | 'right';\nexport type CursorAlign = 'start' | 'center' | 'end';\n\nexport interface CursorTransition {\n type?: 'spring' | 'tween' | 'instant';\n stiffness?: number;\n damping?: number;\n mass?: number;\n duration?: number;\n ease?: string;\n}\n\nexport interface CursorOpts<P = any> {\n variant?: string;\n mode?: CursorMode;\n /**\n * Which side of the mouse the cursor wrapper anchors to (Follow mode).\n * Replace mode ignores side / align / offset — it auto-centers on the mouse.\n */\n side?: CursorSide;\n /**\n * Alignment along the perpendicular axis to `side`.\n * top/bottom: start = left, center = horizontal center, end = right.\n * left/right: start = top, center = vertical center, end = bottom.\n */\n align?: CursorAlign;\n offsetX?: number;\n offsetY?: number;\n transition?: CursorTransition;\n props?: Partial<P>;\n /**\n * Wrapper width / height applied to the cursor's outer motion.div. Useful\n * for code components (canvases, sparks) that fill their parent — without\n * this they'd render at their intrinsic size, which is often the whole\n * viewport. Plain numbers are interpreted as px; pass a string ('100%',\n * '4rem') to use other CSS units.\n */\n width?: number | string;\n height?: number | string;\n /**\n * When true, fade/scale on enter and exit via AnimatePresence. Default\n * false: appear and disappear instantly. The follow movement is always\n * smoothed by the spring config above — `enterExit` only controls the\n * mount/unmount transition.\n */\n enterExit?: boolean;\n}\n\ninterface ActiveCursor {\n key: number;\n Component: ComponentType<any>;\n opts: CursorOpts;\n}\n\n// ─── Global store (vanilla, no React) ───────────────────────────────────────\nlet _active: ActiveCursor | null = null;\nconst _listeners = new Set<() => void>();\n\n// STABLE key per cursor COMPONENT (not per enter event). The old\n// `key: ++_nextKey` per mouseenter forced React to fully REMOUNT the cursor\n// component on every hover transition. Cursor components are typically design\n// components (LayoutGroup + layout motion nodes + variant background images),\n// so scrolling with the pointer over a stack of cursor-hosting elements fired\n// an enter/leave storm → remount storm → framer-motion projection re-registers\n// + image repaints + forced reflows piled onto the main thread — the page\n// froze for seconds (live find 2026-07-07). With a per-Component key, moving\n// between hosts that share a cursor UPDATES the mounted component in place —\n// the per-instance `variant` lands through `initialVariant`, which the design\n// component's internal sync effect animates. A DIFFERENT component still\n// remounts (key changes).\nconst _componentKeys = new WeakMap<ComponentType<any>, number>();\nlet _nextComponentKey = 0;\nfunction _keyFor(c: ComponentType<any>): number {\n let k = _componentKeys.get(c);\n if (k === undefined) {\n k = ++_nextComponentKey;\n _componentKeys.set(c, k);\n }\n return k;\n}\n\n// Pending deactivate from a mouseleave. Scrolling re-hit-tests the pointer, so\n// leave/enter alternate rapidly while the page moves under the mouse; clearing\n// the cursor synchronously on every leave caused an unmount per row boundary.\n// A short grace window absorbs the churn: a follow-up enter cancels the clear\n// (and, same component, is a pure prop update). A REAL exit clears once, ~90ms\n// later — imperceptible.\nlet _pendingClear: ReturnType<typeof setTimeout> | null = null;\n\nfunction _setActive(next: ActiveCursor | null) {\n _active = next;\n _listeners.forEach((l) => l());\n}\n\nfunction _subscribe(l: () => void) {\n _listeners.add(l);\n return () => { _listeners.delete(l); };\n}\n\nfunction _getActive() {\n return _active;\n}\n\n/**\n * Spread the return value into an element to give it a component cursor.\n * Returns onMouseEnter/onMouseLeave handlers that push/pop the global store.\n *\n * <button {...withCursor(Pointer, { mode: 'follow', transition: { type: 'spring', stiffness: 300 } })}>\n */\nexport function withCursor<P>(Component: ComponentType<P>, opts: CursorOpts<P> = {}) {\n return {\n onMouseEnter: () => {\n if (_pendingClear !== null) {\n clearTimeout(_pendingClear);\n _pendingClear = null;\n }\n _setActive({ key: _keyFor(Component as ComponentType<any>), Component: Component as ComponentType<any>, opts });\n },\n onMouseLeave: () => {\n if (_pendingClear !== null) clearTimeout(_pendingClear);\n _pendingClear = setTimeout(() => {\n _pendingClear = null;\n _setActive(null);\n }, 90);\n },\n };\n}\n\n// ─── Portal (mount once in LayoutClient) ────────────────────────────────────\n\nfunction _springConfig(t?: CursorTransition) {\n if (!t || t.type === 'instant') return { stiffness: 1000, damping: 50, mass: 0.1 };\n if (t.type === 'tween' && t.duration) {\n // Map a tween duration to roughly-equivalent spring values.\n const stiffness = Math.max(50, 400 / Math.max(0.1, t.duration));\n return { stiffness, damping: 30, mass: 1 };\n }\n return {\n stiffness: t.stiffness ?? 300,\n damping: t.damping ?? 30,\n mass: t.mass ?? 1,\n };\n}\n\nexport function CursorPortal() {\n const cursor = useSyncExternalStore(_subscribe, _getActive, _getActive);\n\n const x = useMotionValue(0);\n const y = useMotionValue(0);\n const sx = useSpring(x, _springConfig(cursor?.opts.transition));\n const sy = useSpring(y, _springConfig(cursor?.opts.transition));\n\n // One persistent listener; offsets read through a ref so an active-cursor\n // change never re-registers it (the old `[cursor]` dep re-added the listener\n // on every hover transition).\n const offsetRef = useRef({ x: 0, y: 0 });\n offsetRef.current = { x: cursor?.opts.offsetX ?? 0, y: cursor?.opts.offsetY ?? 0 };\n useEffect(() => {\n const onMove = (e: MouseEvent) => {\n x.set(e.clientX + offsetRef.current.x);\n y.set(e.clientY + offsetRef.current.y);\n };\n window.addEventListener('mousemove', onMove, { passive: true });\n return () => window.removeEventListener('mousemove', onMove);\n }, [x, y]);\n\n // Keyed on the MODE, not the whole active object: writing\n // `document.body.style.cursor` invalidates style for the entire document,\n // and the old `[cursor]` dep re-ran the write on every hover transition —\n // one full-page style recalc per row boundary while scrolling.\n const mode = cursor?.opts.mode;\n useEffect(() => {\n if (mode === 'replace') {\n const prev = document.body.style.cursor;\n document.body.style.cursor = 'none';\n return () => { document.body.style.cursor = prev; };\n }\n }, [mode]);\n\n // Wrapper width/height — numbers become px, strings pass through. Falls\n // back to undefined so intrinsic sizing kicks in if the user hasn't set it.\n const wrapW = typeof cursor?.opts.width === 'number' ? cursor.opts.width + 'px' : cursor?.opts.width;\n const wrapH = typeof cursor?.opts.height === 'number' ? cursor.opts.height + 'px' : cursor?.opts.height;\n\n // The OUTER motion.div carries the spring x/y (mouse position). The INNER\n // div applies a percentage transform for side+align (or auto-center in\n // Replace mode). Splitting them avoids fighting with framer-motion's own\n // transform handling on the x/y motion values.\n const outerStyle = {\n position: 'fixed' as const,\n top: 0,\n left: 0,\n x: sx,\n y: sy,\n pointerEvents: 'none' as const,\n zIndex: 9999,\n };\n const innerTransform = _innerTransform(cursor?.opts);\n const innerStyle = {\n width: wrapW,\n height: wrapH,\n transform: innerTransform,\n };\n\n // Default: instant in/out (no AnimatePresence wrapping). Wrap only when\n // the active cursor opts in via `enterExit: true` — keeps mount/unmount\n // snappy by default and avoids the brief fade-out from the previous cursor\n // when hovering between adjacent elements.\n // `opts.variant` → the design component's `initialVariant` prop. Without\n // this the variant picked in the editor (master call or per-instance\n // `<prop>Opts` override) was stored but NEVER applied — every hover showed\n // the cursor component's default variant (live find 2026-07-06). A fresh\n // `key` per hover means the component mounts with the right variant; its\n // internal `useEffect(() => setVariant(initialVariant), [initialVariant])`\n // covers any same-mount opts change.\n const variantProps = cursor?.opts.variant ? { initialVariant: cursor.opts.variant } : {};\n\n if (!cursor?.opts.enterExit) {\n return cursor ? (\n <motion.div key={cursor.key} style={outerStyle}>\n <div style={innerStyle}>\n <cursor.Component {...(cursor.opts.props ?? {})} {...variantProps} />\n </div>\n </motion.div>\n ) : null;\n }\n\n return (\n <AnimatePresence>\n {cursor && (\n <motion.div\n key={cursor.key}\n style={outerStyle}\n initial={{ opacity: 0, scale: 0.8 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.8 }}\n >\n <div style={innerStyle}>\n <cursor.Component {...(cursor.opts.props ?? {})} {...variantProps} />\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n );\n}\n\n/**\n * Build the inner-wrapper transform from side + align + mode. Pure CSS\n * percentage translates so it works regardless of whether width/height are\n * set explicitly. Replace mode auto-centers; Follow mode anchors a corner /\n * edge / center based on the chosen side and alignment.\n */\nfunction _innerTransform(opts?: CursorOpts) {\n if (!opts || opts.mode === 'replace') return 'translate(-50%, -50%)';\n const side = opts.side ?? 'bottom';\n const align = opts.align ?? 'center';\n let tx = 0;\n let ty = 0;\n if (side === 'top') ty = -100;\n else if (side === 'left') tx = -100;\n // 'bottom' and 'right' default to 0 on the main axis.\n // Align controls the perpendicular axis.\n if (side === 'top' || side === 'bottom') {\n if (align === 'center') tx = -50;\n else if (align === 'end') tx = -100;\n } else {\n if (align === 'center') ty = -50;\n else if (align === 'end') ty = -100;\n }\n return 'translate(' + tx + '%, ' + ty + '%)';\n}\n","'use client';\n\n/**\n * `useStaticCanvas()` — returns `true` when the component is being rendered\n * inside the Revyme canvas editor, `false` in the live preview, published\n * site, or any other consumer environment.\n *\n * Sparks / code components use this to skip GPU-expensive animation work\n * (continuous rAF loops, big CSS blur layers, WebGL frames) on the editor\n * canvas where the user only needs a representative still — paint once,\n * stop. The full animated version still runs in preview and production.\n *\n * Mechanics: this default implementation always returns `false`. The canvas\n * editor's spark loader (`code-component-runtime.ts` MODULE_MAP) overrides\n * the export at compile time so it returns `true` in the canvas iframe and\n * `false` in the spark editor's preview pane (which sets `previewMode`).\n *\n * Mirrors Framer's `useIsStaticRenderer` pattern.\n */\nexport function useStaticCanvas(): boolean {\n return false;\n}\n","'use client';\n\n// split-text.tsx — `<RevymeSplitText>`: the runtime half of Revyme's Text effect.\n//\n// WHY THIS EXISTS (2026-07-31). Text effects used to be split at CODEGEN time: the\n// generator wrote N `<motion.span>` elements — one per character — straight into the\n// user's page source. That cannot work for text that isn't known until render:\n// `{item.title}` from a CMS row, `{t('key')}` from i18n, `{propName}` from a component\n// variable. The generator saw an expression, not a string, and escaped it per character\n// into `&#123;item.title&#125;`, so every CMS row rendered the literal text `{item.title}`.\n//\n// Splitting at RENDER time removes the whole class of problem: by the time this component\n// runs, `children` is already the resolved string. It also collapses ~600 lines of\n// generator (four span builders, scroll-hook injection, canvas dormancy) into one prop.\n//\n// HARD CONSTRAINT — the split must be a PURE function of props, computed DURING RENDER.\n// Published sites are SSR'd; a split deferred to an effect would emit a bare string on the\n// server and spans on the client, i.e. a hydration mismatch. Nothing here measures the DOM\n// or reads `window` outside an effect/lazy-initialiser.\n\nimport * as React from 'react';\nimport { motion, useInView, useScroll, useTransform, type MotionValue } from 'framer-motion';\n\n// ─── Spec ────────────────────────────────────────────────────────────────────\n\nexport type SplitTextScope = { query: string } | { variant: string };\n\n/** Structurally `TextAnimConfig` from the builder (canvas-poc/src/editor/tools/\n * AnimationTool/motion/text-anim-presets.ts). Kept in sync by a type-assignability\n * test in canvas-poc — a field added there without a counterpart here fails the build. */\nexport interface SplitTextSpec {\n /** STRUCTURAL — resolved from the BASE spec only, never from a scope override, so the\n * emitted tree is identical on server and client. */\n animationType?: 'character' | 'word' | 'line' | 'full';\n /** STRUCTURAL — wraps each unit in an overflow-hidden clip (\"cut-off\" reveal). */\n mask?: boolean;\n trigger?: 'view' | 'scroll';\n /** Scroll mode only — viewport position (% from top) where the reveal starts / completes. */\n scrollStart?: number;\n scrollEnd?: number;\n opacity?: number;\n scale?: number;\n blur?: number;\n rotateX?: number;\n rotateY?: number;\n rotateZ?: number;\n skewX?: number;\n skewY?: number;\n /** Strings keep their unit. '100%' resolves against the unit's OWN box, which is what\n * makes a masked reveal correct at every type size — a px offset masks correctly at\n * one breakpoint only. */\n x?: number | string;\n y?: number | string;\n /** Stagger between units, seconds. */\n delay?: number;\n transition?: {\n type?: 'spring' | 'tween';\n stiffness?: number;\n damping?: number;\n mass?: number;\n duration?: number;\n bounce?: number;\n ease?: string | number[];\n /** Initial delay before the first unit, seconds. */\n delay?: number;\n };\n /** Per-viewport / per-variant value overrides. First match wins (matches the builder's\n * `resolveTextAnimForScope`). Structural fields in an override are ignored. */\n responsive?: Array<{ scope: SplitTextScope; config: Partial<SplitTextSpec> }>;\n}\n\nexport interface RevymeSplitTextProps {\n spec?: SplitTextSpec;\n /** Active component variant — only needed when `spec.responsive` has `{variant}` scopes. */\n variant?: string;\n children?: React.ReactNode;\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nconst WRAP_PLAIN: React.CSSProperties = { whiteSpace: 'nowrap' };\n/** paddingBottom/marginBottom cancel out — zero layout cost. The padding keeps descenders\n * (g, y, p) from being shaved by the clip AND leaves the sliver IntersectionObserver needs\n * when a unit is offset a full 100% (otherwise it sits entirely outside the clip). */\nconst WRAP_MASK: React.CSSProperties = {\n whiteSpace: 'nowrap', display: 'inline-block', overflow: 'hidden',\n paddingBottom: '0.14em', marginBottom: '-0.14em',\n};\n\n/** The editor persists a custom cubic-bezier as the STRING \"[0.22, 1, 0.36, 1]\" (that's what\n * the TransitionPanel curve editor writes). framer accepts a named easing or a real array —\n * handed the string it throws `Invalid easing type` and unmounts the tree. Codegen normalises\n * this too; the runtime keeps it so hand-written source still works. */\nfunction normalizeEase(e: string | number[] | undefined): string | number[] | undefined {\n if (e === undefined || Array.isArray(e)) return e;\n const s = String(e).trim();\n if (!s.startsWith('[')) return s;\n const n = s.replace(/[[\\]]/g, '').split(',').map((v) => parseFloat(v.trim()));\n return n.length === 4 && n.every(Number.isFinite) ? n : undefined;\n}\n\n/** Collapse React children to a plain string, or null when they can't be split.\n *\n * `null` (a real element child — a styled `<span>` mark, an icon) means \"render verbatim,\n * unsplit\" rather than mangling rich content. Everything the generator used to normalise at\n * build time — `{item.title}`, `{t('key')}`, `{\"a\\nb\"}`, `a<br/>b` — arrives here already\n * resolved to a string or a `<br />`, so one function covers all of it. */\nfunction flattenToText(node: React.ReactNode): string | null {\n if (node === null || node === undefined || node === false || node === true) return '';\n if (typeof node === 'string') return node;\n if (typeof node === 'number') return String(node);\n if (Array.isArray(node)) {\n let out = '';\n for (const child of node) {\n const part = flattenToText(child);\n if (part === null) return null;\n out += part;\n }\n return out;\n }\n if (React.isValidElement(node)) {\n const type = (node as React.ReactElement).type;\n if (type === 'br') return '\\n';\n if (type === React.Fragment) return flattenToText((node.props as any)?.children);\n return null; // a real element → not splittable\n }\n return null;\n}\n\n/** Active `responsive` entry index, or -1 for the base spec.\n *\n * The `matchMedia` read is a LAZY useState initialiser, not a post-mount effect: framer\n * captures `initial` once at mount, so starting at `false` and correcting later makes the\n * responsive branch permanently lose to the base. Same shape as the builder's generated\n * `useMediaQuery` (canvas-poc/src/code/generation/scoped-expr.ts). */\nfunction useActiveScopeIndex(spec: SplitTextSpec, variant?: string): number {\n const entries = spec.responsive;\n const compute = React.useCallback((): number => {\n if (!entries || entries.length === 0) return -1;\n for (let i = 0; i < entries.length; i++) {\n const scope = entries[i].scope;\n if ('variant' in scope) {\n if (variant !== undefined && scope.variant === variant) return i;\n } else if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {\n if (window.matchMedia(scope.query).matches) return i;\n }\n }\n return -1;\n }, [entries, variant]);\n\n const [idx, setIdx] = React.useState(compute);\n\n React.useEffect(() => {\n setIdx(compute());\n if (!entries || typeof window === 'undefined' || typeof window.matchMedia !== 'function') return;\n const lists = entries\n .map((e) => ('query' in e.scope ? window.matchMedia(e.scope.query) : null))\n .filter(Boolean) as MediaQueryList[];\n if (lists.length === 0) return;\n const onChange = () => setIdx(compute());\n for (const l of lists) l.addEventListener('change', onChange);\n return () => { for (const l of lists) l.removeEventListener('change', onChange); };\n }, [entries, compute]);\n\n return idx;\n}\n\nconst RESTING: Record<string, number> = { opacity: 1, scale: 1, rotateX: 0, rotateY: 0, rotateZ: 0, skewX: 0, skewY: 0, x: 0, y: 0 };\nconst CHANNELS = ['opacity', 'scale', 'rotateX', 'rotateY', 'rotateZ', 'skewX', 'skewY', 'x', 'y'] as const;\n\n/** The animated-from state. Mirrors the builder's `buildHiddenState`: a channel is animated\n * only when the spec sets it to something other than its resting value. */\nfunction hiddenState(s: SplitTextSpec): Record<string, any> {\n const out: Record<string, any> = {};\n for (const k of CHANNELS) {\n const v = s[k];\n if (v !== undefined && v !== RESTING[k]) out[k] = v;\n }\n if (s.blur !== undefined && s.blur !== 0) out.filter = `blur(${s.blur}px)`;\n return out;\n}\n\nfunction visibleState(s: SplitTextSpec): Record<string, any> {\n const out: Record<string, any> = {};\n for (const k of CHANNELS) {\n const v = s[k];\n if (v !== undefined && v !== RESTING[k]) out[k] = RESTING[k];\n }\n if (s.blur !== undefined && s.blur !== 0) out.filter = 'blur(0px)';\n return out;\n}\n\n/** `'100%'` → `'0%'`, `24` → `0`. Keeps the unit so a percentage offset scrubs correctly. */\nfunction restingOf(from: number | string, key: string): number | string {\n if (typeof from === 'number') return RESTING[key] ?? 0;\n const m = String(from).match(/^(-?[\\d.]+)(.*)$/);\n return m ? `${RESTING[key] ?? 0}${m[2]}` : (RESTING[key] ?? 0);\n}\n\ninterface Unit { key: string; inner: string; display: 'inline-block' | 'block'; index: number }\n\ntype Node = { t: 'unit'; u: Unit } | { t: 'br'; k: string } | { t: 'text'; k: string; v: string }\n | { t: 'wrap'; k: string; kids: Node[] };\n\n/** Split `text` into render nodes. Structure mirrors the old codegen splitter exactly, so the\n * emitted DOM is unchanged from the build-time era. Keys are pure functions of index → SSR\n * and CSR agree. */\nfunction buildNodes(text: string, animationType: SplitTextSpec['animationType'], mask: boolean): { nodes: Node[]; count: number } {\n const nodes: Node[] = [];\n let i = 0;\n const lines = text.split('\\n');\n\n if (animationType === 'line') {\n for (let li = 0; li < lines.length; li++) {\n if (li > 0) nodes.push({ t: 'br', k: `br${li}` });\n nodes.push({ t: 'unit', u: { key: `u${i}`, inner: lines[li], display: 'block', index: i } });\n i++;\n }\n return { nodes, count: i };\n }\n if (animationType === 'full') {\n nodes.push({ t: 'unit', u: { key: 'u0', inner: text, display: 'inline-block', index: 0 } });\n return { nodes, count: 1 };\n }\n\n for (let li = 0; li < lines.length; li++) {\n if (li > 0) nodes.push({ t: 'br', k: `br${li}` });\n const words = lines[li].split(' ');\n for (let wi = 0; wi < words.length; wi++) {\n if (wi > 0) nodes.push({ t: 'text', k: `sp${li}-${wi}`, v: ' ' });\n const word = words[wi];\n if (!word) continue;\n if (animationType === 'word') {\n const u: Node = { t: 'unit', u: { key: `u${i}`, inner: word, display: 'inline-block', index: i } };\n i++;\n nodes.push(mask ? { t: 'wrap', k: `w${li}-${wi}`, kids: [u] } : u);\n } else {\n const kids: Node[] = [];\n for (const ch of word) {\n kids.push({ t: 'unit', u: { key: `u${i}`, inner: ch, display: 'inline-block', index: i } });\n i++;\n }\n nodes.push({ t: 'wrap', k: `w${li}-${wi}`, kids });\n }\n }\n }\n return { nodes, count: i };\n}\n\n// ─── Scroll unit ─────────────────────────────────────────────────────────────\n\n/** One scroll-scrubbed unit. Split into its own component because `useTransform` must be\n * called once per animated channel and that count is data-driven — calling them in a loop\n * inside the parent would violate the rules of hooks. The parent keys each instance by a\n * channel fingerprint, so a spec change REMOUNTS rather than reordering hooks. */\nfunction ScrollUnit({ progress, range, channels, display, children }: {\n progress: MotionValue<number>;\n range: [number, number];\n channels: Array<{ key: string; from: number | string; to: number | string }>;\n display: string;\n children: React.ReactNode;\n}) {\n const style: Record<string, any> = { display };\n for (const c of channels) {\n // eslint-disable-next-line react-hooks/rules-of-hooks -- count is fixed per mount; see above\n style[c.key] = useTransform(progress, range, [c.from as never, c.to as never]);\n }\n return <motion.span style={style as React.CSSProperties}>{children}</motion.span>;\n}\n\n// ─── Component ───────────────────────────────────────────────────────────────\n\nexport function RevymeSplitText({ spec, variant, children }: RevymeSplitTextProps): React.ReactElement {\n const base: SplitTextSpec = spec ?? {};\n const hostRef = React.useRef<HTMLSpanElement>(null);\n\n const scopeIdx = useActiveScopeIndex(base, variant);\n const resolved: SplitTextSpec = React.useMemo(() => {\n const r = scopeIdx >= 0 && base.responsive\n ? { ...base, ...base.responsive[scopeIdx].config }\n : base;\n // Structural fields come from the BASE, always — the tree must not depend on a scope,\n // or server and client can disagree about the DOM (not just about style values).\n return { ...r, animationType: base.animationType, mask: base.mask, responsive: base.responsive };\n }, [base, scopeIdx]);\n\n const animationType = base.animationType ?? 'character';\n const mask = !!base.mask;\n const text = flattenToText(children);\n\n // ONE observer for the whole run, not one per character. Besides being N× cheaper, this\n // structurally avoids the deadlock the per-character form had: a masked unit offset out of\n // its own overflow-hidden clip has intersection ratio 0 and never fires.\n const inView = useInView(hostRef, { once: true, amount: 0 });\n\n // Called unconditionally so the hook count never changes when a scope flips view↔scroll.\n const startFrac = Math.min(1, Math.max(0, (resolved.scrollStart ?? 90) / 100));\n const endFrac = Math.min(1, Math.max(0, (resolved.scrollEnd ?? 35) / 100));\n const { scrollYProgress } = useScroll({\n target: hostRef,\n offset: [`start ${startFrac}`, `start ${endFrac}`] as never,\n });\n\n const { nodes, count } = React.useMemo(\n () => buildNodes(text ?? '', animationType, mask),\n [text, animationType, mask],\n );\n\n // Not splittable (a styled span, an icon) → render verbatim rather than mangling it.\n if (text === null) return <span ref={hostRef}>{children}</span>;\n\n const isScroll = resolved.trigger === 'scroll';\n const hidden = hiddenState(resolved);\n const visible = visibleState(resolved);\n const stagger = resolved.delay ?? 0.05;\n const initialDelay = resolved.transition?.delay ?? 0;\n const tr = resolved.transition\n ? { ...resolved.transition, ease: normalizeEase(resolved.transition.ease), delay: undefined } as any\n : { type: 'spring' as const, stiffness: 300, damping: 30 } as any;\n\n const channels = isScroll\n ? Object.entries(hidden).map(([key, from]) => ({\n key,\n from: from as number | string,\n to: key === 'filter' ? 'blur(0px)' : restingOf(from as number | string, key),\n }))\n : [];\n const fingerprint = channels.map((c) => c.key).join('|');\n\n const renderUnit = (u: Unit): React.ReactElement => {\n if (isScroll) {\n const start = count > 1 ? Math.round((u.index / (count - 1)) * 0.6 * 1000) / 1000 : 0;\n const end = Math.min(1, Math.round((start + 0.4) * 1000) / 1000);\n return (\n <ScrollUnit\n key={`${fingerprint}#${u.key}`}\n progress={scrollYProgress}\n range={[start, end]}\n channels={channels}\n display={u.display}\n >{u.inner}</ScrollUnit>\n );\n }\n return (\n <motion.span\n key={u.key}\n style={{ display: u.display }}\n initial={hidden}\n animate={inView ? visible : hidden}\n transition={{ ...tr, delay: Math.round((initialDelay + u.index * stagger) * 1000) / 1000 }}\n >{u.inner}</motion.span>\n );\n };\n\n const render = (n: Node): React.ReactNode => {\n if (n.t === 'br') return <br key={n.k} />;\n if (n.t === 'text') return n.v;\n if (n.t === 'wrap') return <span key={n.k} style={mask ? WRAP_MASK : WRAP_PLAIN}>{n.kids.map(render)}</span>;\n return renderUnit(n.u);\n };\n\n return (\n <span ref={hostRef} style={{ display: animationType === 'line' ? 'block' : 'inline' }}>\n {nodes.map(render)}\n </span>\n );\n}\n\nexport default RevymeSplitText;\n","// sketch-draw.ts — Runtime player for Revyme sketch draw animations.\n//\n// Replays a brush-stroke sketch over time by feeding the original\n// pointer samples (persisted on each `<path>` as a `data-points`\n// attribute) back through perfect-freehand's `getStroke` at\n// progressively-increasing slice lengths. The result is the visible\n// equivalent of watching the user draw the sketch.\n//\n// Why a runtime function instead of an inline useEffect block in the\n// generated source: the orchestrator is ~80 LOC of imperative timing\n// + easing + RAF logic. Inlining it in every page that has a sketch\n// animation buries the page's actual logic. Living in\n// `@revyme/runtime` means the generated source is just one line:\n//\n// useEffect(() => playSketchDraw(el, opts), []);\n//\n// which reads the same way as `withResponsiveProps` / `withCursor`\n// already do for other generated patterns.\n\nimport { getStroke } from 'perfect-freehand';\n\nexport type SketchAnimMode = 'sequential' | 'staggered' | 'simultaneous';\nexport type SketchAnimTrigger = 'mount' | 'inView' | 'hover' | 'tap';\n\nexport interface SketchAnimTransition {\n type: 'tween' | 'spring';\n duration?: number;\n ease?: string;\n stiffness?: number;\n damping?: number;\n mass?: number;\n}\n\nexport interface SketchAnimOpts {\n trigger?: SketchAnimTrigger;\n mode?: SketchAnimMode;\n /** Multiplier on per-stroke duration. Per-stroke duration scales\n * with point count so a long stroke takes longer than a flick;\n * this dials the overall pace. */\n durationScale?: number;\n /** 0–1, only meaningful in staggered mode. 0 = fully sequential,\n * 1 = fully simultaneous. */\n stagger?: number;\n transition?: SketchAnimTransition;\n /** Brush size used for the intermediate-frame outline replay. The\n * final-frame `d` is restored from source so the end state is\n * pixel-exact regardless of this value. */\n brushSize?: number;\n}\n\nconst DEFAULT_OPTS: Required<Omit<SketchAnimOpts, 'transition'>> & { transition: SketchAnimTransition } = {\n trigger: 'inView',\n mode: 'sequential',\n durationScale: 1,\n stagger: 0.5,\n transition: { type: 'tween', duration: 1, ease: 'easeOut' },\n brushSize: 8,\n};\n\nfunction applyEase(t: number, transition: SketchAnimTransition): number {\n if (transition.type === 'spring') {\n const damping = transition.damping ?? 10;\n const stiffness = transition.stiffness ?? 100;\n const dampedT = 1 - Math.exp(-damping * t * 0.1);\n const oscillation = Math.cos(t * Math.sqrt(stiffness) * 0.3);\n return Math.min(1, dampedT * (1 - 0.1 * oscillation * (1 - t)));\n }\n switch (transition.ease ?? 'easeOut') {\n case 'linear': return t;\n case 'easeIn': return t * t;\n case 'easeOut': return 1 - (1 - t) * (1 - t);\n case 'easeInOut': return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;\n case 'circIn': return 1 - Math.sqrt(1 - t * t);\n case 'circOut': return Math.sqrt(1 - Math.pow(t - 1, 2));\n case 'backOut': {\n const c1 = 1.70158;\n const c3 = c1 + 1;\n return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);\n }\n default: return 1 - (1 - t) * (1 - t);\n }\n}\n\nfunction parsePoints(raw: string): number[][] {\n if (!raw) return [];\n return raw.split(/\\s+/).filter(Boolean).map(s => {\n const [x, y, p] = s.split(',');\n return [parseFloat(x) || 0, parseFloat(y) || 0, p != null ? parseFloat(p) : 0.5];\n });\n}\n\nfunction outlineToD(outline: number[][]): string {\n if (outline.length === 0) return '';\n let d = `M ${outline[0][0].toFixed(2)} ${outline[0][1].toFixed(2)}`;\n for (let i = 1; i < outline.length; i++) {\n d += ` L ${outline[i][0].toFixed(2)} ${outline[i][1].toFixed(2)}`;\n }\n return d + ' Z';\n}\n\n/**\n * Play a sketch draw animation on the given wrapper SVG. Pass the\n * options the generator emitted in source.\n *\n * Returns a cleanup function — wire as your useEffect's return value\n * so re-mounts cancel an in-flight animation cleanly:\n *\n * useEffect(() => playSketchDraw(svgEl, opts), []);\n *\n * If `wrapperEl` is null or the wrapper has no path children with\n * `data-points`, this is a no-op and returns a noop cleanup.\n */\nexport function playSketchDraw(\n wrapperEl: SVGSVGElement | null,\n userOpts: SketchAnimOpts = {},\n): () => void {\n const noop = () => {};\n if (!wrapperEl) return noop;\n const opts = { ...DEFAULT_OPTS, ...userOpts, transition: { ...DEFAULT_OPTS.transition, ...userOpts.transition } };\n\n const paths = Array.from(wrapperEl.querySelectorAll('path[data-points]')) as SVGPathElement[];\n if (paths.length === 0) return noop;\n\n // Snapshot the final d so the last frame is pixel-exact regardless\n // of the replay-with-default-brush approximation we use during\n // intermediate frames.\n const finalDs = paths.map(p => p.getAttribute('d') || '');\n const pointsList = paths.map(p => parsePoints(p.getAttribute('data-points') || ''));\n\n // Hide everything up front so the first frame doesn't flash.\n paths.forEach(p => p.setAttribute('d', ''));\n\n // Per-stroke duration — point count drives length so a long stroke\n // takes longer than a flick.\n const baseDur = (opts.transition.duration ?? 1) * 1000 * opts.durationScale;\n const maxPoints = pointsList.reduce((m, p) => Math.max(m, p.length), 1);\n const perStrokeDur = pointsList.map(p => baseDur * (p.length / maxPoints));\n const startMs: number[] = [];\n let cursor = 0;\n for (let i = 0; i < paths.length; i++) {\n if (opts.mode === 'simultaneous') {\n startMs.push(0);\n } else if (opts.mode === 'staggered') {\n const overlap = Math.max(0, Math.min(1, opts.stagger));\n const start = i === 0 ? 0 : startMs[i - 1] + perStrokeDur[i - 1] * (1 - overlap);\n startMs.push(start);\n } else {\n // sequential\n startMs.push(cursor);\n cursor += perStrokeDur[i];\n }\n }\n\n let cancelled = false;\n let rafId = 0;\n let started = false;\n let cleanupTrigger: (() => void) | null = null;\n let startTs = 0;\n\n const tick = (now: number) => {\n if (cancelled) return;\n const elapsed = now - startTs;\n let allDone = true;\n for (let i = 0; i < paths.length; i++) {\n const local = elapsed - startMs[i];\n if (local < 0) { allDone = false; continue; }\n const t = Math.min(1, local / Math.max(1, perStrokeDur[i]));\n if (t < 1) allDone = false;\n let d: string;\n if (t >= 1) {\n d = finalDs[i];\n } else {\n const eased = applyEase(t, opts.transition);\n const sliceCount = Math.max(2, Math.floor(pointsList[i].length * eased));\n const subset = pointsList[i].slice(0, sliceCount);\n if (subset.length < 2) {\n d = '';\n } else {\n const outline = getStroke(subset, {\n size: opts.brushSize, thinning: 0.5, smoothing: 0.5, streamline: 0.5,\n });\n d = outlineToD(outline);\n }\n }\n paths[i].setAttribute('d', d);\n }\n if (!allDone) rafId = requestAnimationFrame(tick);\n };\n\n const start = () => {\n if (started) return;\n started = true;\n startTs = performance.now();\n rafId = requestAnimationFrame(tick);\n };\n\n if (opts.trigger === 'inView') {\n const obs = new IntersectionObserver((entries) => {\n if (entries.some(e => e.isIntersecting)) {\n start();\n obs.disconnect();\n }\n }, { threshold: 0.2 });\n obs.observe(wrapperEl);\n cleanupTrigger = () => obs.disconnect();\n } else if (opts.trigger === 'hover') {\n const onEnter = () => start();\n wrapperEl.addEventListener('mouseenter', onEnter);\n cleanupTrigger = () => wrapperEl.removeEventListener('mouseenter', onEnter);\n } else if (opts.trigger === 'tap') {\n const onTap = () => start();\n wrapperEl.addEventListener('click', onTap);\n cleanupTrigger = () => wrapperEl.removeEventListener('click', onTap);\n } else {\n // mount\n start();\n }\n\n return () => {\n cancelled = true;\n cancelAnimationFrame(rafId);\n cleanupTrigger?.();\n };\n}\n","// localize-rows.ts — per-locale field values for CMS collection rows.\n//\n// A collection item holds ONE row with a translation per field per locale,\n// stored on the item itself:\n//\n// { \"_id\": \"abc\", \"title\": \"Sunset sail\", \"_i18n\": { \"fr\": { \"title\": \"…\" } } }\n//\n// The generated page wraps its collection source with this, so the SOURCE\n// resolves the locale by itself:\n//\n// {localizeRows(programme, __activeLocale).map((item, idx) => (\n// <h3 data-id=\"prog-title\">{item.title}</h3>\n// ))}\n//\n// Why on the row and why here, rather than a build step or an editor-side\n// merge: the page already imports its collection JSON, so the translations\n// ship with the data and the published site needs no extra file, no extra\n// import and no publish-time generation. The bindings stay `{item.title}` —\n// nothing downstream (the builder's parser, the CMS panel, the row preview)\n// has to learn a new shape.\n\n/** Per-locale field values carried on a collection item. */\nexport interface RowI18n {\n [locale: string]: Record<string, unknown> | undefined;\n}\n\ntype Row = Record<string, unknown> & { _i18n?: RowI18n };\n\n/**\n * `rows` with `locale`'s translations merged over each item's base fields.\n *\n * Untranslated fields fall back to the base language rather than blanking — a\n * half-translated collection reads correctly instead of showing holes. An\n * empty string counts as untranslated for the same reason: the editor writes\n * `''` to CLEAR a translation, and honouring it literally would erase the\n * row's text.\n *\n * Returns the SAME array when nothing applies, so React can bail out of the\n * re-render on the default locale (the common case).\n */\nexport function localizeRows<T extends Row>(rows: T[], locale: string | undefined | null): T[] {\n if (!rows || !locale) return rows;\n let changed = false;\n const out = rows.map((row) => {\n const fields = row?._i18n?.[locale];\n if (!fields) return row;\n const usable = Object.entries(fields).filter(([, v]) => typeof v === 'string' && v !== '');\n if (usable.length === 0) return row;\n changed = true;\n return { ...row, ...Object.fromEntries(usable) };\n });\n return changed ? out : rows;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revyme/runtime",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -187,12 +187,42 @@ export default function withResponsiveProps<P extends Record<string, any>>(
187
187
  delete mergedProps['data-responsive'];
188
188
  delete mergedProps['__canvasViewportWidth'];
189
189
 
190
+ // ── Flow-position safety net ──
191
+ // Design-component masters bake `position: 'absolute'` on their root
192
+ // (canvas master tiling; no left/top — variant x/y live in variantConfig)
193
+ // and rely on the INSTANCE style overriding it via the trailing
194
+ // `...style` spread. An instance authored WITHOUT a `position` key
195
+ // (Make Component's transfer gap, pre-oracle AI writes, hand-written
196
+ // code) lets the master's absolute leak on the live site: absolute with
197
+ // no offsets takes its static position, and since every such sibling is
198
+ // out of flow they all compute the SAME static position — repeated
199
+ // instances stack on one spot (the collapsed-footer bug, 2026-08-12).
200
+ // The editor canvas neutralizes master absolutes during instance
201
+ // expansion, so the violation is invisible there; this net restores
202
+ // canvas/live parity. Intentional out-of-flow instances always carry
203
+ // their own `position` (+ offsets) and are untouched.
204
+ if (mergedProps.style == null || (mergedProps.style as Record<string, any>).position == null) {
205
+ mergedProps = {
206
+ ...mergedProps,
207
+ style: { position: 'relative', ...((mergedProps.style as Record<string, any>) ?? {}) },
208
+ };
209
+ }
210
+
190
211
  // ── Animated-style socket (see header comment) ──
191
212
  // A forwarded ref ALSO forces the wrapper: it's the scroll-effect target,
192
213
  // which must resolve to a real DOM box even when the style carries no
193
214
  // animated values yet.
215
+ //
216
+ // `data-size-hug` (instance-auto-size, 2026-08-15) forces it too: the
217
+ // editor writes a per-variant hug as `height: 'auto'` in the instance's
218
+ // dim ternary, and on a NON-animated design instance the style would
219
+ // spread straight into the root — `auto` on a leaf div collapses it to
220
+ // its (empty) content. The wrapper restores the design-instance split:
221
+ // placement (incl. the auto dim) rides the wrapper, the root keeps its
222
+ // own baked master dim, and CSS auto wraps it at exactly the master's
223
+ // size — tracking master edits with zero codegen at publish time.
194
224
  const style = mergedProps.style as Record<string, any> | undefined;
195
- let needsWrapper = fwdRef != null;
225
+ let needsWrapper = fwdRef != null || mergedProps['data-size-hug'] != null;
196
226
  if (!needsWrapper && style) {
197
227
  for (const k of Object.keys(style)) {
198
228
  if (MOTION_ONLY_KEYS.has(k) || isMotionValue(style[k])) { needsWrapper = true; break; }
@@ -206,10 +236,14 @@ export default function withResponsiveProps<P extends Record<string, any>>(
206
236
  else innerStyle[k] = v;
207
237
  }
208
238
  // The inner component fills the wrapper — but only along axes the
209
- // wrapper actually sized (code-component instances always carry
210
- // definite dims; the guard keeps an unsized axis hugging content).
211
- if ('width' in wrapperStyle) innerStyle.width = '100%';
212
- if ('height' in wrapperStyle) innerStyle.height = '100%';
239
+ // wrapper sized to a DEFINITE value (code-component instances always
240
+ // carry definite dims; the guard keeps an unsized axis hugging
241
+ // content). A hug axis (`'auto'`) must NOT fill: 100% of an auto box
242
+ // resolves to nothing — the root's own baked dim is the size the
243
+ // wrapper hugs.
244
+ const definite = (v: unknown) => v != null && v !== 'auto';
245
+ if (definite(wrapperStyle.width)) innerStyle.width = '100%';
246
+ if (definite(wrapperStyle.height)) innerStyle.height = '100%';
213
247
  // The wrapper OWNS the instance's slot (all placement keys moved onto
214
248
  // it) — the inner root must re-base INTO the wrapper. Design-component
215
249
  // masters bake `position: 'absolute'` on their root (canvas master