fancy-ui-react 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +56 -20
  2. package/dist/components/animated-beam/AnimatedBeam.d.ts +47 -11
  3. package/dist/components/animated-beam/AnimatedBeam.js +293 -118
  4. package/dist/components/animated-tooltip/AnimatedTooltip.d.ts +10 -1
  5. package/dist/components/animated-tooltip/AnimatedTooltip.js +170 -101
  6. package/dist/components/bento-grid/BentoFrame.d.ts +16 -0
  7. package/dist/components/bento-grid/BentoFrame.js +28 -0
  8. package/dist/components/bento-grid/BentoGrid.d.ts +5 -1
  9. package/dist/components/bento-grid/BentoGrid.js +71 -2
  10. package/dist/components/bento-grid/BentoGridCard.js +34 -26
  11. package/dist/components/bento-grid/BentoGridItem.js +10 -10
  12. package/dist/components/border-beam/BorderBeam.d.ts +55 -10
  13. package/dist/components/border-beam/BorderBeam.js +93 -16
  14. package/dist/components/button/Button.js +5 -1
  15. package/dist/components/compare/Compare.d.ts +32 -8
  16. package/dist/components/compare/Compare.js +213 -95
  17. package/dist/components/container-scroll/ContainerScroll.d.ts +26 -4
  18. package/dist/components/container-scroll/ContainerScroll.js +88 -49
  19. package/dist/components/datamosh-transition/DatamoshTransition.d.ts +76 -0
  20. package/dist/components/datamosh-transition/DatamoshTransition.js +368 -0
  21. package/dist/components/datamosh-transition/datamosh-transition-core.d.ts +180 -0
  22. package/dist/components/datamosh-transition/datamosh-transition-core.js +370 -0
  23. package/dist/components/datamosh-transition/index.d.ts +3 -0
  24. package/dist/components/dock/Dock.d.ts +10 -7
  25. package/dist/components/dock/Dock.js +76 -31
  26. package/dist/components/dock/DockIcon.d.ts +14 -3
  27. package/dist/components/dock/DockIcon.js +23 -19
  28. package/dist/components/dock/DockSeparator.d.ts +4 -1
  29. package/dist/components/dock/DockSeparator.js +5 -2
  30. package/dist/components/dock/types.d.ts +13 -6
  31. package/dist/components/dropdown-menu/menu-shared.d.ts +11 -8
  32. package/dist/components/editorial-engine/engine.js +1 -14
  33. package/dist/components/fireworks-hdr/webgl2-renderer.js +0 -19
  34. package/dist/components/fireworks-hdr/webgpu-renderer.js +3 -22
  35. package/dist/components/flip-card/FlipCard.d.ts +42 -1
  36. package/dist/components/flip-card/FlipCard.js +178 -25
  37. package/dist/components/flip-card/index.d.ts +1 -1
  38. package/dist/components/fluid-cursor/FluidCursor.js +5 -4
  39. package/dist/components/fluid-cursor/dev.d.ts +1 -0
  40. package/dist/components/fluid-cursor/dev.js +8 -0
  41. package/dist/components/fluid-cursor/fluid-shared.d.ts +0 -2
  42. package/dist/components/fluid-cursor/fluid-shared.js +0 -2
  43. package/dist/components/fluid-cursor/webgpu-engine.js +10 -24
  44. package/dist/components/glow-border/GlowBorder.d.ts +41 -1
  45. package/dist/components/glow-border/GlowBorder.js +96 -16
  46. package/dist/components/glow-border/index.d.ts +2 -2
  47. package/dist/components/gradient-button/GradientButton.d.ts +10 -5
  48. package/dist/components/gradient-button/GradientButton.js +25 -18
  49. package/dist/components/image-trail-cursor/ImageTrailCursor.js +1 -0
  50. package/dist/components/image-trail-cursor/trail-variants.d.ts +37 -1
  51. package/dist/components/image-trail-cursor/trail-variants.js +331 -8
  52. package/dist/components/interactive-hover-button/InteractiveHoverButton.js +30 -35
  53. package/dist/components/marquee/Marquee.d.ts +7 -3
  54. package/dist/components/marquee/Marquee.js +134 -2
  55. package/dist/components/marquee/ReviewCard.d.ts +2 -1
  56. package/dist/components/marquee/ReviewCard.js +20 -9
  57. package/dist/components/meteors/Meteors.d.ts +32 -10
  58. package/dist/components/meteors/Meteors.js +53 -20
  59. package/dist/components/mosaic-glow/mosaic-glow-core.d.ts +98 -10
  60. package/dist/components/neon-border/NeonBorder.d.ts +24 -5
  61. package/dist/components/neon-border/NeonBorder.js +37 -7
  62. package/dist/components/pagination/Pagination.js +220 -82
  63. package/dist/components/rainbow-button/RainbowButton.js +13 -5
  64. package/dist/components/ripple-button/RippleButton.d.ts +20 -1
  65. package/dist/components/ripple-button/RippleButton.js +44 -21
  66. package/dist/components/shimmer-button/ShimmerButton.d.ts +5 -5
  67. package/dist/components/shimmer-button/ShimmerButton.js +17 -19
  68. package/dist/components/smooth-cursor/SmoothCursor.d.ts +13 -9
  69. package/dist/components/smooth-cursor/SmoothCursor.js +114 -163
  70. package/dist/components/smooth-cursor/smooth-cursor-core.d.ts +85 -0
  71. package/dist/components/smooth-cursor/smooth-cursor-core.js +327 -0
  72. package/dist/components/stepper/Step.js +32 -19
  73. package/dist/components/stepper/Stepper.js +18 -2
  74. package/dist/components/stepper/types.d.ts +13 -0
  75. package/dist/components/timeline/Timeline.d.ts +8 -1
  76. package/dist/components/timeline/Timeline.js +165 -76
  77. package/dist/index.d.ts +1 -0
  78. package/dist/index.js +6 -1
  79. package/dist/internals/markdown.d.ts +5 -0
  80. package/dist/internals/motion/haptics.d.ts +0 -4
  81. package/dist/internals/motion/presence-core.d.ts +59 -0
  82. package/dist/internals/motion/presence-core.js +162 -0
  83. package/dist/internals/motion/presence.d.ts +2 -4
  84. package/dist/internals/motion/presence.js +1 -156
  85. package/dist/internals/motion/raf.d.ts +1 -1
  86. package/dist/internals/motion/stagger.d.ts +3 -4
  87. package/dist/internals/motion/tokens.js +4 -0
  88. package/dist/internals/motion/transitions.d.ts +17 -17
  89. package/dist/internals/motion/types.d.ts +6 -7
  90. package/dist/internals/relative-time.d.ts +1 -1
  91. package/dist/styles.css +3545 -204
  92. package/package.json +18 -2
  93. package/dist/components/fireworks-hdr/webgpu-types.d.ts +0 -159
package/README.md CHANGED
@@ -4,7 +4,7 @@ React counterpart of [`fancy-ui-svelte`](https://www.npmjs.com/package/fancy-ui-
4
4
  same components, same visual contract, ported from the Svelte 5 reference
5
5
  implementation that lives at the root of this repo.
6
6
 
7
- Status: at parity. All 144 components of `fancy-ui-svelte` are ported and
7
+ Status: at parity. All 145 components of `fancy-ui-svelte` are ported and
8
8
  exported from the package root, alongside the `sound` family; the cameleon
9
9
  skin engine (FancyProvider + primitives + skins) ships on the
10
10
  `fancy-ui-react/cameleon` subpath. The Svelte package remains the reference;
@@ -183,22 +183,17 @@ Deliberate, small, and documented — everything else is a faithful transpose:
183
183
  README example inherits that bug. Ported as-is per the fidelity law; fix it
184
184
  upstream first if it bothers you.
185
185
  - Compiler-scoped selectors with no root anchor in the source gained one
186
- (`fancy-marquee`, `ripple-button`) so their CSS does not leak into consumer
186
+ (`line-reveal`) so their CSS does not leak into consumer
187
187
  pages — see PORTING.md, styling rule 2.
188
- - **`Meteors` takes a `seed`** the Svelte side has no equivalent for. Svelte
189
- runs its randomiser once, in the browser; React runs the same initializer on
190
- the server AND again during hydration, and `Math.random()` disagrees with
191
- itself across the two — a hydration mismatch React may settle by keeping the
192
- server values. A seeded PRNG makes both renders agree while leaving the
193
- shower in the server HTML. The default seed is shared, so two unseeded
194
- showers fall the same way; pass different seeds to separate them.
195
188
 
196
189
  - **agent-plan**: Svelte's `item` prop is a Snippet<[PlanStepData, number]>; ported as a render-prop function `(step, index) => ReactNode` per PORTING.md's Snippet->ReactNode mapping.
197
190
  - **agent-plan**: Svelte's `ref = $bindable(null)` became a standard forwardRef<HTMLDivElement, AgentPlanProps> on the root div; no functional difference.
198
191
  - **ai-data-table**: The `cell` snippet prop became a render-function prop `cell?: (value, at) => ReactNode` instead of a ReactNode, since the Svelte Snippet<[unknown,{row,key}]> takes arguments — same call-site shape, a JS function instead of a snippet.
199
192
  - **animated-beam**: containerRef/fromRef/toRef additionally accept a React ref object ({ current: HTMLElement | null }) alongside a plain HTMLElement, resolved at effect/observer time (exported helper type AnimatedBeamTarget), and are typed nullable since a ref may not be populated yet
200
- - **animated-beam**: gradient id comes from useFancyId("beam") (SSR-stable) instead of the Svelte side's Math.random() id
201
- - **animated-beam**: new seed prop (default 1): the default duration (Svelte: Math.random() * 3 + 4) is derived from a deterministic mulberry32 PRNG so server render and hydration agree
193
+ - **animated-beam**: the svg root carries a port-added `animated-beam` anchor class (first token of the root cn() call) because the source's styles relied on compiler scoping; every rule in animated-beam.css (fibre glass, packet-core, packet-layer, bloom, reduced-motion) is anchored on it, and the keyframes beam-packet / beam-bloom / beam-ring ship under those exact names instead of compiler-hashed ones
194
+ - **animated-tooltip**: The tooltip positioner's lean transform is written imperatively to the active item's node from refs (pointer offset, size, reduced-motion flag) instead of flowing through render state, so a pointer sample rewrites one style attribute without re-rendering the row; writing only to the active item's positioner is what reproduces the source's frozen pose on a leaving tooltip.
195
+ - **animated-tooltip**: The exit-only `sink` transition on `.at-card` runs on the shared presence clock (`usePresence`) with a duration-0 enter leg, so the card still enters through the CSS `at-rise` / `at-fade` keyframe and a re-hover mid-exit snaps back to rest; the tooltip node mounts one extra pre-paint render pass after the hover.
196
+ - **animated-tooltip**: The colocated stylesheet anchors every rule under `.at-root` (dark theme on `.dark .at-root`) because plain CSS has no compiler scoping; class hooks, custom property names, `@property --at-angle` and the `at-rise` / `at-fade` / `at-sweep` keyframes are identical to the source.
202
197
  - **apple-card-carousel**: AppleCard's props interface is now exported as AppleCardProps (the Svelte side kept it a local, unexported interface) to satisfy the <Name>Props tooling contract.
203
198
  - **apple-card-carousel**: AppleCardData.content is a ReactNode instead of a Svelte Snippet (rendered directly rather than invoked).
204
199
  - **apple-card-carousel**: Svelte's await tick() + inline rAF chain after expand became two effects keyed on overlayVisible: a layout effect that focuses the close button (and commits the reduced-motion still frame), then a passive effect whose double rAF flips fullyExpanded; the rAFs are cancelled on cleanup (Svelte had no cancellation, but the timing/behaviour is identical). The reduced-motion branch is read from a ref so it is decided once per expand, like the source handler.
@@ -209,12 +204,18 @@ Deliberate, small, and documented — everything else is a faithful transpose:
209
204
  - **approval-card**: children (Snippet) becomes ReactNode children as usual for the detail region.
210
205
  - **bento-grid**: BentoGridItemProps uses Omit<HTMLAttributes<HTMLDivElement>, 'title'> to avoid a TS conflict between the DOM 'title' attribute and the Svelte 'title' snippet prop (same intersection pattern PORTING.md prescribes for RainbowButton).
211
206
  - **bento-grid**: BentoGridItemProps and BentoGridCardProps extend HTMLAttributes<HTMLDivElement>, so they accept every div attribute — including a `children` the components render fixed JSX in place of and therefore silently drop; the Svelte parts declare a closed prop list.
207
+ - **bento-grid**: BentoFrame's compiler-scoped `.bento-panel`, `.bento-glow` and `.bento-edge` rules are anchored as descendants of `.bento-tile` in bento-frame.css (port-added anchor, since plain CSS is global); cascade outcomes match the Svelte source, including the reduced-motion glow fade.
208
+ - **bento-grid**: BentoGrid spreads rest props onto the grid root like the rest of the React port; a consumer `style` is merged with the `accent` prop, with `--bento-accent` from `accent` taking precedence as the Svelte `style:` directive does, and a consumer `onFocus` is chained after the focus-reveal handler.
209
+ - **bento-grid**: The focus-in reveal uses React's `onFocus`, which React delivers as a bubbling focusin, in place of the Svelte `onfocusin` attribute.
212
210
  - **bg-falling-stars**: Added optional `seed?: number` (default 1) + mulberry32 PRNG replacing every `Math.random()` (star layout and respawn), per PORTING.md's randomisation rule; consequence: two `<FallingStarsBg />` with no seed show an identical sky, where the Svelte side gave each instance a different one.
213
211
  - **bg-falling-stars**: `count` re-initialises the starfield in React (it is a dependency of the canvas effect); the Svelte side reads `count` only inside `onMount`, so a later `count` change there is silently ignored. `color` keeps Svelte's behaviour exactly (live, next-frame, no re-seed) via a ref rather than an effect dependency.
214
212
  - **bg-stars**: Added a `seed` prop (default 1) with a deterministic mulberry32 PRNG replacing the Svelte side's Math.random() star scatter — required by the SSR/hydration rule (PORTING.md, C-7); same seed = same sky.
215
213
  - **bg-stars**: Per-frame spring transform is written imperatively to the parallax element's style in the rAF loop (per group guidance) instead of re-rendering through state; a stiffness/damping change restarts the loop but spring position/velocity persist in refs, so motion resumes from the in-flight position.
216
214
  - **bg-stars**: Port-added root anchor class `fancy-bg-stars` for the colocated CSS (Svelte compiler-scoped .stars-parallax/.star-layer/.star-field). The bare slug `bg-stars` was avoided deliberately: tailwind-merge classifies `bg-*` tokens as background utilities and would drop the anchor against any consumer bg-* override.
217
215
  - **bg-stars**: Star box-shadows are derived in render (useMemo on starColor+seed) instead of a client-only effect, so stars appear in server-rendered HTML; the Svelte side renders empty shadows until the effect runs.
216
+ - **border-beam**: The compiler-scoped `bb-*` rules are anchored under the root class `.border-beam` in plain CSS (for example `.border-beam .bb-ride`), and the dark theme reads `.dark .border-beam`. Specificity keeps the same relative order, so the render is identical.
217
+ - **border-beam**: The keyframes `bb-travel` and `bb-ignite` ship under those exact names as global keyframes, where the Svelte compiler hashes them. They are only referenced from inside `.border-beam` rules.
218
+ - **border-beam**: The tail and bloom piece lists (`tailSegments(18)` / `tailSegments(8)`) are computed once at module load instead of once per instance. The values are the same and nothing visible changes.
218
219
  - **box-reveal**: Rest props: React BoxRevealProps extends HTMLAttributes<HTMLDivElement> and spreads rest onto the root div; the Svelte BoxRevealProps declares only color/duration/delay/class and spreads nothing, so the React component accepts id/onClick/data-* attributes the Svelte one silently drops. Kept per PORTING.md's general 'rest props spread onto the root element' rule, recorded here because it is consumer-visible.
219
220
  - **breadcrumb**: `class` becomes `className` (standard rename).
220
221
  - **breadcrumb**: `item` snippet prop (`Snippet<[BreadcrumbItem, number]>`) becomes a render-prop function: `item?: (item: BreadcrumbItem, index: number) => ReactNode`.
@@ -240,6 +241,12 @@ Deliberate, small, and documented — everything else is a faithful transpose:
240
241
  - **confetti**: No behavioural divergence in `ConfettiButton`: no className, no rest-prop spread, no `type` attribute — the Svelte source declares only `options`, `children` and `sound` on a bare `<button>`.
241
242
  - **confetti**: `Confetti` gains one extra file over the Svelte tree (`context.ts`), mirroring the `button-group` precedent; one file per Svelte file is otherwise preserved.
242
243
  - **confetti**: `fire()` reaches consumers through the ref channel, not a component instance. Svelte declares `export function fire(opts)` (callable via `bind:this`); React has no instance, so `Confetti` is a `forwardRef<ConfettiHandle, ConfettiProps>` whose ref carries `{ fire }` rather than the DOM node. This is the one forwardRef in the port not backed by a `ref = $bindable` declaration (PORTING.md C-4); the alternative was deleting a public method. `ConfettiHandle` is exported from `Confetti.tsx` but deliberately NOT from `index.ts`, since the mandated export list is exactly Confetti/ConfettiButton + Props types.
244
+ - **container-scroll**: The first measurement runs in an isomorphic layout effect rather than a passive effect. This matches the Svelte effect, which measures before paint, so a card that is already in view never paints one shut frame.
245
+ - **container-scroll**: Scoped selectors from the Svelte `<style>` block are anchored under the component's own `.cs-root` class in `container-scroll.css` (for example `.cs-root .cs-title`). The Svelte `:global(.dark)` and `:global(html:not(.dark))` wrappers become plain `.dark .cs-root` and `html:not(.dark) .cs-root`. Class names, custom property names and values are identical.
246
+ - **container-scroll**: Reduced motion is read through `useReducedMotion` (useSyncExternalStore). The server render and the hydration render therefore always report motion allowed (progress 0, no `data-reduced-motion`), and the still frame applies on the next commit. The CSS `@media (prefers-reduced-motion: reduce)` block still shows the open still frame from the first paint.
247
+ - **datamosh-transition**: `cover()`, `reveal()` and `play()` reach consumers through the ref channel, not a component instance. Svelte declares them with `export function` (callable via `bind:this`) and also binds the overlay element through `ref = $bindable(null)`; React has no instance, so `DatamoshTransition` is a `forwardRef<DatamoshTransitionHandle, DatamoshTransitionProps>` whose ref carries `{ cover, reveal, play, element }` — the three methods plus the overlay element that the Svelte `ref` published.
248
+ - **datamosh-transition**: The read-only bindable `phase` becomes an `onPhaseChange(phase)` callback, called on every change (`covering`, `covered`, `revealing`, `idle`); `data-state` on the overlay carries the same value on both sides.
249
+ - **datamosh-transition**: SSR markup: React serialises the overlay's inline style as `style="z-index:9999"` (no space after the colon) where Svelte emits `z-index: 9999`. Same declaration, different byte string.
243
250
  - **direction-aware-hover**: fade transitions on the overlay and caption are reproduced via usePresence + a local fade300 TransitionSpec (WAAPI mechanism per internals-api §5) instead of a framework transition directive — same 300ms linear opacity curve
244
251
  - **direction-aware-hover**: isMobile lives in a ref rather than state — it is only read in event handlers and never drives markup, so resize events no longer trigger re-renders (no observable difference)
245
252
  - **direction-aware-hover**: non-null assertion added on event.touches[0] to satisfy the React tsconfig's indexed-access checking (no runtime change)
@@ -261,7 +268,6 @@ Deliberate, small, and documented — everything else is a faithful transpose:
261
268
  - **flip-words**: The flip cycle is keyed on `words.length`, not on the `words` array identity the Svelte $effect tracks, so a parent handing down a new array reference (or a same-length content swap) no longer clears the pending timer and re-arms a full `duration` wait.
262
269
  - **flip-words**: The next-index computation guards an empty `words` list: if the array empties while the 600 ms exit is in flight the React component falls back to index 0 and resumes when the list is repopulated, where the Svelte source computes `(index + 1) % 0` = NaN and stays blank forever.
263
270
  - **fluid-cursor**: Dev-only console diagnostics (singleton warning, WebGPU fallback/tone-mapping/device-lost logs, HDR-level info) are gated on a `typeof`-guarded `process.env.NODE_ENV !== "production"` instead of `import.meta.env.DEV` — the react package's tsconfig does not load Vite client types. In an unbundled browser load where `process` is undefined the diagnostics are silent. No non-diagnostic behavior differs; `hexToRgb`'s invalid-hex `console.warn` is NOT gated on either side (the test asserts it).
264
- - **fluid-cursor**: WebGPU is typed by module-scoped minimal interfaces instead of `@webgpu/types` (not declared in react/package.json; it is only reachable here by accident, transitively through `@types/three`). Type-level only — no runtime difference — but it also keeps the emitted `.d.ts` free of an external type reference consumers would have to install.
265
271
  - **fluid-cursor**: `FluidCursorAdvancedProps` is a new exported type (empty interface extending `FluidCursorProps`). The Svelte index exports the deprecated `FluidCursorAdvanced` alias with no Props type of its own; PORTING.md's `<Name>Props` tooling contract requires one.
266
272
  - **fluid-cursor**: `interactive` and `contained` are captured at mount and used for BOTH listener registration and removal. The Svelte source re-reads them (reactive getters) at cleanup time, so flipping either prop mid-life leaks or mis-pairs listeners there; here add/remove always pair. Unobservable unless a consumer mutates those props on a live instance, which the props are documented not to support.
267
273
  - **frosted-glass**: Filter id generator: the Svelte `onMount` mints `fg-dist-${Math.random().toString(36).slice(2,8)}`; the port mints `uid("fg-dist")` from `src/internals/use-id.ts` inside the equivalent mount effect. Consumer-visible ids are now `fg-dist-1`, `fg-dist-2`, ... instead of a random base-36 suffix. Timing is unchanged (still client-only, still after mount, so the filter layer and its <defs> are still absent from the server HTML), and no seed prop was added — the existing `seed` prop stays the feTurbulence seed it is on the Svelte side.
@@ -271,7 +277,7 @@ Deliberate, small, and documented — everything else is a faithful transpose:
271
277
  - **image-trail-cursor**: Root `touch-action: none` is emitted through React's inline-style object instead of Svelte's literal style string. Identical in browsers (style="touch-action: none;"), but jsdom's cssstyle does not implement `touch-action` and silently drops any CSSOM write, so the transposed assertion reads the server markup (renderToStaticMarkup contains style="touch-action:none") rather than the live node's style attribute. Same fact, jsdom-observable form.
272
278
  - **image-trail-cursor**: `ImageItem.defaultStyle` is `private` here where the Svelte class declares it `public`: a public TweenVars member emits a bare animation-library namespace reference into the shipped .d.ts that a deep-importing consumer cannot resolve. The member is read only by `initEvents()`, and `ImageItem` is not re-exported from index.ts on either side.
273
279
  - **interactive-grid-pattern**: Root SVG carries an extra class token `interactive-grid-pattern` that the Svelte source does not emit: the Svelte <style> rule `.interactive-grid-square:not(:hover){transition-duration:1000ms}` was compiler-scoped, so the port adds the slug as an anchor per PORTING.md styling rule 2. This was already present in the ported component before this fix and is commented as port-added in interactive-grid-pattern.css.
274
- - **line-reveal**: Port-added root anchor class `line-reveal` (first token of the root `cn()`), so the compiler-scoped `.line-mask` / `.line` / `.revealed` rules do not leak globally — same treatment as `fancy-marquee` and `ripple-button`; README's 'Compiler-scoped selectors ... gained one' bullet should list `line-reveal` too.
280
+ - **line-reveal**: Port-added root anchor class `line-reveal` (first token of the root `cn()`), so the compiler-scoped `.line-mask` / `.line` / `.revealed` rules do not leak globally; README's 'Compiler-scoped selectors ... gained one' bullet lists `line-reveal` too.
275
281
  - **line-reveal**: `class` -> `className` (standard PORTING.md rename); no ref/forwardRef and no rest-prop spread, matching the Svelte source which declares neither.
276
282
  - **line-reveal**: LineReveal creates one ResizeObserver per mounted instance where the Svelte `bind:clientWidth` routes through the framework's process-wide observer singleton; each disconnects on unmount and nothing leaks.
277
283
  - **link**: Native click handler is `onClick` (React convention/rest-props spread) rather than Svelte's `onclick` prop name.
@@ -294,12 +300,13 @@ Deliberate, small, and documented — everything else is a faithful transpose:
294
300
  - **mosaic-glow**: `MosaicGlowProps` extends `Omit<HTMLAttributes<HTMLDivElement>, "color" | "children">` rather than omitting every `keyof BaseProps` — those two are the only names that actually collide in `@types/react`, so the surviving attribute surface is one entry wider than the Svelte type (e.g. `title`, `role` were never omitted on either side anyway). No runtime effect.
295
301
  - **navbar**: NavbarLink's onclick prop is renamed onClick (React event-prop casing convention, consistent with other ports)
296
302
  - **navbar**: Svelte source had two <style> blocks (one per .svelte file); ported as two colocated CSS files, navbar.css and navbar-link.css, each imported by its own component
297
- - **neon-border**: `neon-border.css` carries the Svelte `<style>` rules unanchored (`.neon-layer-one`, `.neon-layer-two`, `.neon-animated`) instead of nesting them under the root class, so they are global where the Svelte compiler scoped them. Known deviation from PORTING.md styling rule 2, shared with `meteors.css`.
298
303
  - **neon-border**: NeonBorderProps extends HTMLAttributes<HTMLDivElement> and spreads rest onto the root, so id/role/onClick/data-* reach the element; the Svelte component declares a closed six-prop list and spreads nothing.
299
304
  - **noise-reveal**: `class` prop renamed to `className` (package-wide PORTING.md convention); every other prop name, type and default is identical.
300
305
  - **noise-reveal**: The viewport-trigger effect lists `delay` in its dependency array, which the Svelte `$effect` does not track, so changing `delay` after the element has intersected cancels a reveal already waiting out its delay and re-observes; `delay` is a mount-time setting.
301
306
  - **pagination**: Pagination: Svelte's `bind:page` becomes the standard controlled/uncontrolled split — uncontrolled by default (internal page state starting at 1), controlled when the `page` prop is passed; `onPageChange` fires with the same value either way.
302
307
  - **pagination**: Pagination: `previousLabel`/`nextLabel` snippets become `ReactNode` props; the nav element is exposed via `forwardRef` instead of `bind:ref`.
308
+ - **pagination**: Svelte's `animate:flip` + `in:fade` on the page items become a hand-rolled FLIP in a layout effect (Web Animations API, 300ms with the library's out curve; new items fade in over 150ms after a 60ms delay; skipped on first commit, under reduced motion, or where `Element.animate` is missing). An interrupted glide continues from the item's live position.
309
+ - **pagination**: Arming the slide and placing the pill run in one isomorphic layout effect (not an `$effect` plus `tick()`); same visible behaviour.
303
310
  - **prompt-suggestions**: Svelte $effect.pre (pre-flush) mapped to useLayoutEffect (closest React equivalent for pre-paint DOM updates); no functional difference observed in tests.
304
311
  - **prompt-suggestions**: Svelte 'item' prop was a Snippet<[string,number]>; ported as item?: (suggestion, index) => ReactNode, per PORTING.md snippet->ReactNode rule.
305
312
  - **prompt-suggestions**: class prop renamed to className per PORTING.md convention.
@@ -312,6 +319,11 @@ Deliberate, small, and documented — everything else is a faithful transpose:
312
319
  - **sidebar**: `ref` props become forwardRef on Sidebar, SidebarItem, SidebarSeparator and SidebarFooter (the four declaring `ref = $bindable(null)`); SidebarGroup declares none and gets none. The `ref` field is therefore absent from every Props interface.
313
320
  - **smooth-cursor**: `class` prop becomes `className` (standard mapping).
314
321
  - **smooth-cursor**: `cursor` prop is a ReactNode instead of a Svelte Snippet (standard snippet->ReactNode mapping); truthiness check preserved, so a falsy node falls back to the default arrow exactly like the Svelte {#if}.
322
+ - **smooth-cursor**: the wrapper now drives the shared framework-free engine (`smooth-cursor-core.ts`, a byte-identical copy of the Svelte core) instead of re-implementing the spring loop inline; the core's own test file is copied byte-for-byte as well.
323
+ - **smooth-cursor**: the Svelte scoped `<style>` block ships as the colocated `smooth-cursor.css`, with every rule anchored under the existing root class `.smooth-cursor`. Class names (`sc-pointer`, `sc-pointer--rotate`, `sc-arrow`, `sc-arrow-rim`, `sc-stop-*`, `sc-label`, `sc-label-text`, `is-idle`) and custom properties (`--_sc-color`, `--smooth-cursor-color`, `--smooth-cursor-label-color`) are identical to the Svelte source.
324
+ - **smooth-cursor**: the arrow's gradient id comes from React's `useId()` with every character outside `[A-Za-z0-9_-]` stripped (the counterpart of `$props.id()`), keeping the `url(#smooth-cursor-fill-…)` paint reference ASCII-clean while staying SSR-stable.
325
+ - **smooth-cursor**: reduced motion is read with the shared `useReducedMotion()` hook rather than a raw `matchMedia` call; the mount value is passed to the engine as a creation option, and only a later change is pushed through `setOptions({ reducedMotion })`. The hook reports `false` during the hydration render, so on a machine that asks for reduced motion the preference reaches a hydrated cursor as a change right after hydration — one snap to the origin while the cursor is still hidden.
326
+ - **smooth-cursor**: the name pill is handed to the engine through a callback ref (`setLabel(node)` on mount, `setLabel(null)` on unmount), replacing the Svelte `bind:this` + `$effect`; the hotspot `translate` is set as a single React style key, so re-renders never touch the engine-written `transform`.
315
327
  - **sound**: Svelte's `use:soundFeedback` action has no React counterpart: it ships as `attachSoundFeedback(element, options)` (the imperative attach/detach core, callable from a ref callback) plus the `useSoundFeedback(options)` hook that returns a ref callback for the common case. The action name `soundFeedback` is therefore absent from the package root.
316
328
  - **sound**: The React engine clamps a custom theme's layer duration to `SOUND_LIMITS.MAX_LAYER_MS` before scheduling; the Svelte engine schedules the declared duration. Only `validateSoundTheme()` enforces the limit on the Svelte side, so a theme passed straight to `engine.setTheme()` with a layer longer than 400 ms plays in full under Svelte and truncated under React.
317
329
  - **sound**: The sound family adds `useSound`, `useSoundCue`, `useSoundEnabled` and `useSoundStatus` — the React counterpart of reading the `sound` controller's runes directly.
@@ -322,7 +334,10 @@ Deliberate, small, and documented — everything else is a faithful transpose:
322
334
  - **sparkles-text**: Adds a `seed` prop (default 1) with a deterministic mulberry32 PRNG replacing the source's Math.random() — required for identical server render and hydration; same seed yields the same initial field.
323
335
  - **sparkles-text**: Post-mount star regeneration draws from a second seeded stream (mulberry32(seed+1)) instead of Math.random(), so the animation timeline is also deterministic per seed.
324
336
  - **sparkles-text**: Sparkle ids drop the source's Date.now()+Math.random() composition in favor of an extra PRNG draw (`${x}-${y}-${random()}`) — ids stay unique per field but are deterministic.
325
- - **stepper**: Step.svelte's scoped `<style>` block ships as colocated `step.css` imported by `Step.tsx`. Every rule was already anchored on a class the component owns (`.ft-step`, `.ft-step-bullet`, `.ft-step-connector`, `.ft-step-trigger`), so no port-added anchor class was needed; keyframe-free, and the deliberate no-`prefers-reduced-motion` decision is preserved verbatim with its rationale.
337
+ - **stepper**: Step.svelte's scoped `<style>` block ships as colocated `step.css` imported by `Step.tsx`, copied verbatim. That includes the lit-rail layers, the halo, the glyph flip and check draw, every `ft-step-*` keyframe and the `prefers-reduced-motion: no-preference` gate. Every rule is already anchored on a class the component owns (`.ft-step`, `.ft-step-bullet*`, `.ft-step-connector*`, `.ft-step-glyph`, `.ft-step-check`, `.ft-step-trigger`, `.ft-step-animate`), so the port adds no anchor class.
338
+ - **stepper**: The Svelte `createReducedMotion()` + `reduced.start()` + `asked = true` sequence becomes `useReducedMotion()` (useSyncExternalStore, `false` on the server and during hydration) plus an `asked` state flag set in a mount `useEffect`. `animate = asked && !reduced`, so neither the server render nor the hydration pass ever carries `ft-step-animate`, and `data-motion` reads `reduced` until mount, as on the Svelte side.
339
+ - **stepper**: The Svelte `$effect.pre` that writes `origin` (the active index before the latest change) becomes React's adjust-state-while-rendering pattern: a `{ settled, origin }` state pair that is updated during render whenever the active index differs from `settled`. React re-renders before committing, so no committed frame pairs the new index with a stale origin. `origin` starts at 0, so the first paint plays the arrival run.
340
+ - **stepper**: The Svelte `style:--ft-step-order` / `style:--ft-step-arrival` directives become inline `style` objects cast to `CSSProperties`. An `undefined` arrival passes `style={undefined}`, so the bullet carries no style attribute, as the directive omits it.
326
341
  - **stepper**: The Svelte `untrack` guard around register/unregister becomes two React rules: `register` is `useCallback(..., [])` over a ref-held registry (identity never changes), and a Step's registration effect depends on `[register, id]`, never on the context object — depending on the context (rebuilt whenever the registry changes) would re-run the effect from its own call and loop forever, the same bug in React shape.
327
342
  - **stepper**: `STEPPER_KEY` is a `React.Context<StepperContext | undefined>` object, not a `Symbol` context key — read with `useContext(STEPPER_KEY)`. Same exported name, same optional-outside-the-root semantics (button-group's `BUTTON_GROUP_CONTEXT_KEY` precedent).
328
343
  - **stepper**: `Step`'s custom-bullet branch is `children ?? default` (null/undefined only) rather than Svelte's snippet-existence test, so a falsy-but-present child such as `0` or `false` overrides the glyph where Svelte would have rendered the default.
@@ -338,6 +353,11 @@ Deliberate, small, and documented — everything else is a faithful transpose:
338
353
  - **text-reveal-card**: touchmove is attached natively via useEffect with { passive: false } instead of React's onTouchMove: the handler calls preventDefault() and React registers its touchmove root listener as passive. Same listener semantics as the Svelte attribute; behavior identical.
339
354
  - **timeline**: `class` prop becomes `className` (standard port convention)
340
355
  - **timeline**: `content` snippet becomes a `content?: (item: TimelineItem) => ReactNode` render prop
356
+ - **timeline**: The Svelte `<style>` block ships as the colocated `timeline.css`. Every selector is anchored under the source's own root class `.tl-root`, so no anchor class was added, and `:global(.dark) .tl-root` becomes `.dark .tl-root`. The custom property names, the `tl-ignite` keyframes, the `prefers-reduced-motion` block and the reduced-motion trail are all copied verbatim.
357
+ - **timeline**: The head position is not React state. `place()` writes `--tl-progress` straight to the root node each rAF frame, and the render repeats the latest value from a ref, so scrolling re-renders nothing until the head crosses a dot. `activeIndex` is state that updates only on a crossing, and `ready` and the measured track height are state too.
358
+ - **timeline**: Row content renders through a memoised `TimelineContent` keyed on (`content`, `item`). When a dot lights, the rows re-render to flip `data-state` and `data-lit`, but the consumer's `content` render prop is not called again for the whole list.
359
+ - **timeline**: The first measurement and placement run in a layout effect (`useIsomorphicLayoutEffect`) instead of `onMount`, so the rail height and head position are written before the first paint.
360
+ - **timeline**: Reduced motion reads `useReducedMotion()` (useSyncExternalStore) in place of `createReducedMotion()`. On the server and during hydration it reports full motion, so the head is rendered and `data-motion="full"` until the client snapshot resolves.
341
361
  - **toggle**: ToggleHarness.test.svelte (Svelte bind: rig) became ToggleHarness.tsx (not .test.tsx, so vitest's src/**/*.test.{ts,tsx} include does not collect it as its own empty suite); its assertions live in Toggle.test.tsx as before.
342
362
  - **toggle**: pressed is React state seeded once from the `pressed` prop (mirrors Svelte's pressed = $bindable(false)), not a controlled/uncontrolled toggle via `pressed ?? state` — re-passing a different `pressed` value after mount does not resync it, matching the Svelte bindable-prop semantics exactly.
343
363
  - **toggle-group**: Test transposition: the two `bind:value` round-trip tests become "round-trips a single/multiple selection through the value channel", asserting the same values via the harness's `onValueChange`; "binds the root element" becomes "exposes the root element through the ref" using `createRef` + `forwardRef`. `ToggleGroupHarness.test.svelte` is now an inline `Harness` component inside `ToggleGroup.test.tsx`. Every `await tick()` flush is dropped as Svelte-scheduler mechanics (RTL's `fireEvent` already acts).
@@ -420,10 +440,12 @@ Deliberate, small, and documented — everything else is a faithful transpose:
420
440
  - **date-picker**: `ref = $bindable` on the trigger button becomes `forwardRef` exposing the `<button>` element
421
441
  - **date-picker**: `viewDate`/`focusedDate` start as `null` and are seeded on first open rather than with `dayOnly(new Date())` at construction (C-7: no `new Date()` in a render path); not observable — nothing reads either before `openPanel` seeds them and the live region is empty while closed on both sides
422
442
  - **dock**: Context key: Svelte's `DOCK_CONTEXT_KEY = Symbol("dock-context")` becomes a React context object of the same name in types.ts. Neither barrel exports it, so the public API surface is unchanged.
423
- - **dock**: Dock tracks and cancels any rAF still queued at unmount; the Svelte source relies on its scheduler tearing the component down first. No observable behaviour change — a cancelled frame had nothing left to paint into.
424
443
  - **dock**: Media queries go through `useMediaQuery`/`useReducedMotion` (useSyncExternalStore) instead of `createMediaQuery().start()` in an `$effect` — internals-contract divergence D-4. SSR and the hydration render still answer `false`/`false` (identical to the Svelte fallback), but on a client-only first render `magnify` is already correct instead of settling one effect pass later.
425
444
  - **dock**: `DockContext.mouseX`/`mouseY` keep the exact `{ current: number }` shape, but the box is REBUILT by Dock on every pointer frame instead of being mutated in place — a mutated object is invisible to React and the new identity is what re-renders the icons.
426
445
  - **dock**: index.ts re-exports the three components plus their Props types AND the `DataOrientation`, `Direction`, `DockContext` types, mirroring the Svelte index.ts exactly (a superset of the three components + Props types requested in the brief; DockProps references `Direction`/`DataOrientation`, so consumers need them).
446
+ - **dock**: One frame writes the pointer, the shelf-local spot (`--dock-x`/`--dock-y`) and the hover flag as a single state object, because an updater must stay pure. The shelf rect is read in the rAF callback before the state update, not inside it, so there is still one `getBoundingClientRect()` per frame and the output is identical.
447
+ - **dock**: The shelf's decorative CSS lives in colocated `dock.css`, with every rule anchored on the source's own `.dock-shelf` root class (`.dock-shelf .dock-spot`, `.dark .dock-shelf .dock-frame`, …). The icon rules live in `dock-icon.css`, anchored on `.dock-icon`. `:global(.dark)` becomes a plain `.dark` ancestor selector. The class names, custom property names and media blocks are verbatim.
448
+ - **dock**: `dockIconSize` and `DOCK_BASE_SIZE` are exported from DockIcon.tsx, the counterpart of the Svelte `<script module>`. Like the Svelte side, they are not re-exported from the folder index.ts.
427
449
  - **drawer**: No portal-before-focus-trap ordering ceremony (contract divergence D-10): createPortal commits before effects, so the trap always focuses a connected node
428
450
  - **drawer**: Per-frame drag offset lives in a ref with direct element.style writes instead of reactive state (group guidance); the spring-back class is applied via flushSync so the CSS transition and the transform reset land in the same frame, matching Svelte's batched flush
429
451
  - **drawer**: Scroll lock is `useScrollLock(presence.mounted)` rather than an outro-delayed action destroy — identical release timing (page unlocks when the slide-out finishes)
@@ -592,15 +614,14 @@ Deliberate, small, and documented — everything else is a faithful transpose:
592
614
 
593
615
  - **approval-card**: onApprove/onDeny receive the committed decision as an argument — React batches the onStateChange parent update, so the argument is the only synchronously-committed signal (Svelte writes through bind:state and needs none).
594
616
  - **code-diff**: inert is written to the collapsed views imperatively via internals/dom/use-inert-attribute (React 18 drops the boolean JSX prop); absent from server HTML where Svelte SSRs it. A local DiffBody sub-component hosts the hook — markup unchanged.
595
- - **compare**: new ariaLabel prop (default "Image comparison slider") and full keyboard operation (Arrow ±1, PageUp/PageDown ±10, Home/End) on the role=slider root; the Svelte source exposes a focusable slider with no name and no key handling.
617
+ - **compare**: `label` (default "Comparison slider") is the accessible name, as on the Svelte side; the older React-only `ariaLabel` prop still works as a deprecated alias, and `label` wins when both are given.
618
+ - **compare**: A burst of pointer moves within one frame commits once (and a pending move frame is dropped on unmount); the Svelte side schedules and commits each move.
596
619
  - **compare**: The autoplay loop is started once: the Svelte source starts it twice (onMount plus the $effect tracking hover and drag), so two rAF loops run there and `onpercentagechange` fires twice per frame. The divider position is identical on both sides, but a consumer counting or debouncing that callback sees half the invocations here.
597
- - **dock**: pointer tracked in viewport coordinates (clientX/clientY); the Svelte source reads pageX/pageY against getBoundingClientRect() and mis-magnifies on a scrolled page.
598
- - **dock**: React adds an `ariaLabel` prop (rendered as `aria-label` on the toolbar) and emits `aria-orientation` matching `orientation`; the Svelte source has neither, so this is a temporary React-only addition until the Svelte side mirrors it.
599
620
  - **dock**: While the pointer is inside the dock every animation frame produces a new context value, so the Dock root and every DockSeparator re-render and re-run their `cn()` calls where Svelte mutates two `{ current }` boxes and re-renders neither; visual output is identical.
600
621
  - **dropdown-menu**: Tab/Shift+Tab return focus to the trigger before the browser continues traversal; the source passes returnFocus: false and leaves focus in a portalled panel, wrapping traversal to the body.
601
622
  - **dropdown-menu**: Post-open focus is moved in a passive effect where the source moves it in a `tick()` microtask, so the panel paints one frame before the focus ring lands on the first or last item; the anchored entrance is still animating at that point.
602
623
  - **file-upload**: The native file input is reset on picker-open (not on change) so `name`/`required` see a real FileList; `removeFile` clears it again when a removal invalidates it. A cancelled picker leaves the input empty while the row list keeps the earlier pick; dropped files never feed the native input, as in the Svelte source.
603
- - **glow-border**: style custom properties are built as a React style object during render (present in server HTML); the Svelte source emits one raw CSS string — byte order differs, rendered result identical.
624
+ - **glow-border**: the style custom properties, including the --gb-metal-* and --gb-glint-* gradients, are built as a React style object during render, so they are in the server HTML. The Svelte source emits one raw CSS string, so the byte order differs but the rendered result is the same. `:global(.dark) .glow-border` becomes `.dark .glow-border`, and the compiler-scoped `.animate-glow` rule and its reduced-motion override are anchored as `.glow-border.animate-glow`.
604
625
  - **inline-citation**: a scheme-less host like docs.example.dev/guide is promoted to https:// before sanitizing, matching SourceCard and WebSearch; genuine relative paths pass through.
605
626
  - **link**: The new-tab announcement and the safe-`rel` tokens are gated on the RESOLVED target, not on the bare `external` flag, so an `external` link whose `target` is overridden to `_self`/`_parent`/`_top` stays silent and keeps the caller's own `rel`; the Svelte source announces a new tab whenever `external` is set.
606
627
  - **logo-cloud**: the four cloned marquee tracks carry aria-hidden and empty alt, so assistive tech hears the brand list once; the Svelte source announces all five copies.
@@ -627,15 +648,30 @@ Deliberate, small, and documented — everything else is a faithful transpose:
627
648
  - **animated-testimonials**: Autoplay pauses on keyboard focus inside the region as well as on pointer hover, and does not start at all under `prefers-reduced-motion` — temporarily ahead of the Svelte source, which mirrors it next.
628
649
  - **button**: `onclick` is renamed `onClick` and typed with React's synthetic MouseEvent, matching the event-prop casing NavbarLink, Sidebar, ChatMessageAction, Toggle and ToggleGroupItem already use.
629
650
  - **chat-panel**: ChatEmptyState's `icon` fallback uses `??`, so a nullish `icon` falls back to the default sparkle but a falsy-but-defined node such as `icon={false}` or `icon={""}` renders an empty decorative span where the Svelte `{#if icon}` branch still draws the sparkle; pass `undefined` to get the default mark.
651
+ - **compare**: The beam/autoplay/glide loops read live values from refs that mirror the state; the observable behaviour is the same as the reference's closures reading $state.
652
+ - **compare**: compare.css anchors every component rule under the root `.compare` class, because plain CSS is global where the reference's rules were compiler-scoped. Class names, keyframes (cmp-pulse, cmp-spin), @property --cmp-angle and custom properties are unchanged.
653
+ - **compare**: Compare no longer renders StarField, matching the reference. StarField is still exported on its own, as the Svelte index does.
630
654
  - **container-text-flip**: ContainerTextFlipProps extends HTMLAttributes<HTMLParagraphElement> and spreads rest onto the root, so id/onClick/data-*/aria-* reach the element where the Svelte component drops them; the inherited `children` is type-accepted and dropped, since the component renders its own.
655
+ - **flip-card**: `bind:flipped` becomes a seed-and-resync prop: `flipped` sets the starting face on mount, and a later change to it turns the card the rest of the way, calling `onFlip` as Svelte does; internal flips report only through `onFlip` (pass both for two-way behaviour).
656
+ - **flip-card**: Svelte's `onflip` callback is named `onFlip`, following the package-wide camelCase convention for callbacks; same signature, fires at the same times.
657
+ - **flip-card**: The `inert` attribute on the hidden face is written by `useInertAttribute` at commit, not rendered inline, because no JSX spelling works on both React 18 and 19; it is therefore missing from server HTML until hydration.
658
+ - **flip-card**: React's onBlur bubbles (it listens to focusout), so the handler ignores blurs whose target is not the card root. This keeps the Svelte behaviour, where the non-bubbling root blur listener does not fire when focus leaves from a control inside a face, so the card stays on its back in that case.
631
659
  - **gradient-button**: GradientButton forwards a ref to the underlying `<button>` even though the Svelte source declares no bindable ref; additive only, recorded rather than removed.
660
+ - **interactive-hover-button**: forwardRef to the <button> kept although the Svelte source exposes no bindable ref (pre-existing React API, same as the other button ports).
661
+ - **interactive-hover-button**: The React tests keep four sound tests (press cue on click, silent by default, silent while disabled, silent on hover) that the Svelte test file does not have; the sound behaviour is identical.
632
662
  - **letter-pullup**: LetterPullupProps extends HTMLAttributes<HTMLDivElement> and spreads rest onto the root div, so id/style/onClick/role/aria-*/data-* reach the element; the Svelte component's surface is exactly {words, delay, class} and it spreads nothing.
633
663
  - **line-hover-link**: Props extend AnchorHTMLAttributes minus className and spread ...rest onto the `<a>`, per PORTING.md's rest-props-spread rule; the Svelte side exposed a fixed prop list and spread nothing.
634
664
  - **marquee**: `ReviewCardProps` is exported as a named type from the folder barrel; the Svelte README states the prop shape is deliberately not re-exported, so a consumer writing their own card need not match it.
665
+ - **marquee**: The per-instance conveyor state (engaged flag, eased factor, live animations, rAF id, resume point) lives in one lazily created ref object instead of loose component-scoped variables. `speed` and `pauseOnHover` are mirrored into it from effects, so an enter or leave event reads the value from the last commit. The easing maths are unchanged: time constant 140 ms, settle epsilon 0.002, dt capped at 64 ms.
666
+ - **marquee**: The Svelte `onfocusin`/`onfocusout` handlers map to React `onFocus`/`onBlur`, which React attaches as focusin/focusout. The relatedTarget containment check is kept as-is.
667
+ - **marquee**: The compiler-scoped rules are anchored on the source's own root class `.marquee`, as `.marquee > .animate-marquee` and `.marquee.marquee-fade`, and the ReviewCard rules on `.review-card`. The `.marquee.marquee-upgraded > …` override is kept more specific than the base keyframe rule, so the cascade result matches the Svelte scoped output.
668
+ - **marquee**: ReviewCard's style block ships as its own colocated `review-card.css`, imported by ReviewCard.tsx, so the card is styled even when it is used without a Marquee. `:global(.dark) .review-card` becomes `.dark .review-card`.
635
669
  - **number-ticker**: Duration and delay are captured when the animation starts; the Svelte source re-reads both props each frame, so a mid-animation duration change re-times the count there and not here.
636
670
  - **number-ticker**: The re-animate effect depends on the animation target only; the Svelte $effect also re-runs on displayValue and on delay, so a delay-only change restarts the count in Svelte and not in React.
637
671
  - **pixel-loader**: PixelLoaderProps extends HTMLAttributes<HTMLDivElement> and spreads rest onto the root, so a consumer's `style` can override the component's own `--ft-pixel-*` custom properties; the Svelte component accepts exactly nine props and spreads nothing.
638
672
  - **ripple-button**: Port-added `ref`: RippleButton is a forwardRef publishing the root `<button>`, which the Svelte source does not expose; the consumer's ref is composed through `useComposedRefs`, so it attaches once and is never churned by the ripple state.
673
+ - **ripple-button**: inner `.ripple-hover` / `.ripple-animation` rules are anchored under the source's root class `.ripple-button`, since plain CSS is global.
674
+ - **shimmer-button**: React keeps forwardRef<HTMLButtonElement> (pre-existing public API); the Svelte source exposes no bindable ref.
639
675
  - **toast**: The per-toast entrance and exit are owned by `<Toaster>`'s per-item presence clock rather than by `<Toast>` itself, so a `<Toast>` rendered directly outside a `<Toaster>` is static where the Svelte one still plays its 300 ms rise in and 200 ms sink out.
640
676
  - **toast**: A dismissed toast is `inert` for the length of its exit (the presence clock's default), so its Retry and Dismiss buttons stop answering clicks and focus inside it is released; the Svelte toast root carries no `inert` and stays interactive until the outro destroys it.
641
677
  - **tracing-beam**: Per-frame spring values are written imperatively to the gradient node in the rAF loop (setAttribute "y1"/"y2") instead of round-tripping through state, so the component and its children never re-render during the animation; the rendered JSX keeps y1/y2 at their 0 seed, which is also what the server markup emits.
@@ -11,29 +11,65 @@ export interface AnimatedBeamProps {
11
11
  containerRef: AnimatedBeamTarget | null;
12
12
  fromRef: AnimatedBeamTarget | null;
13
13
  toRef: AnimatedBeamTarget | null;
14
+ /** Vertical bend of the fibre in px (the quadratic control point sits this far above the start). */
14
15
  curvature?: number;
16
+ /** Send the light from `toRef` back to `fromRef` (the bloom then lands on `fromRef`). */
15
17
  reverse?: boolean;
18
+ /** Fibre colour. Unset = theme-aware glass (dark on light, pale on dark). */
16
19
  pathColor?: string;
20
+ /** Width of the fibre core and of the light packet, in px. */
17
21
  pathWidth?: number;
22
+ /** Opacity of the fibre. Unset = 1 for the theme-aware glass, 0.2 when `pathColor` is set. */
18
23
  pathOpacity?: number;
24
+ /** Colour of the packet's leading edge (its white-hot head is this colour mixed with white). */
19
25
  gradientStartColor?: string;
26
+ /** Colour of the far end of the dispersing tail, and of the arrival bloom. */
20
27
  gradientStopColor?: string;
21
- /** Seconds the beam waits before its first travel. Feeds the gradient
22
- * animations' `begin`, so a group of beams can be staggered. */
28
+ /** Seconds before the first packet leaves. */
23
29
  delay?: number;
30
+ /** Seconds per packet cycle. Unset = derived from `seed` (between 4 and 7). */
24
31
  duration?: number;
25
32
  startXOffset?: number;
26
33
  startYOffset?: number;
27
34
  endXOffset?: number;
28
35
  endYOffset?: number;
29
- /**
30
- * Seed for the default animation duration. The Svelte side randomises the
31
- * default (`Math.random() * 3 + 4`); a render-path `Math.random()` would
32
- * make the server render and its hydration disagree, so the default is
33
- * derived deterministically from this seed instead. Two beams with no
34
- * `seed` and no `duration` share the same pace — vary the seed (or pass
35
- * `duration`) to desynchronise them.
36
- */
36
+ /** Packets in flight at once, spaced evenly over one cycle. */
37
+ pulses?: number;
38
+ /** Length of the packet's tail as a fraction of the fibre (0.05–0.9). */
39
+ tail?: number;
40
+ /** Strength of the soft glow under the packet and the arrival bloom (0–1). */
41
+ glow?: number;
42
+ /** Seed for the default `duration`, so server and browser agree. */
37
43
  seed?: number;
38
44
  }
39
- export declare function AnimatedBeam({ className, containerRef, fromRef, toRef, curvature, reverse, pathColor, pathWidth, pathOpacity, gradientStartColor, gradientStopColor, delay, duration, startXOffset, startYOffset, endXOffset, endYOffset, seed, }: AnimatedBeamProps): import("react").JSX.Element;
45
+ /** The default cycle length for a seed: somewhere between 4 and 7 seconds. */
46
+ export declare function beamDuration(seed?: number): number;
47
+ /** Share of the cycle the head spends travelling; the rest drains the tail into the node. */
48
+ export declare const ARRIVAL = 0.78;
49
+ export interface PacketLayer {
50
+ key: "stop" | "mid" | "start" | "head";
51
+ color: string;
52
+ /** Dash length, in PATH_LENGTH units. */
53
+ len: number;
54
+ /** How far this layer has fallen behind the head by the moment of arrival. */
55
+ lag: number;
56
+ opacity: number;
57
+ /** Stroke width as a multiple of `pathWidth`. */
58
+ width: number;
59
+ /** The white-hot core: on light surfaces it keeps more of its colour so it never vanishes. */
60
+ core?: boolean;
61
+ }
62
+ /**
63
+ * The packet: a white-hot head followed by three chromatic sub-pulses,
64
+ * each longer, dimmer and further behind than the one before, so the tail
65
+ * spreads out as it travels — light dispersing through glass. Listed bottom
66
+ * to top (the order they are painted).
67
+ *
68
+ * Each chromatic band is drawn as three stacked dashes sharing one leading
69
+ * edge (full, two thirds, one third of its length), so its brightness ramps
70
+ * up toward the head instead of ending in a hard block.
71
+ */
72
+ export declare function packetLayers(tail: number, start: string, stop: string): PacketLayer[];
73
+ /** The blurred copies under the packet: one per band plus a wide head. */
74
+ export declare function glowLayers(tail: number, start: string, stop: string): PacketLayer[];
75
+ export declare function AnimatedBeam({ className, containerRef, fromRef, toRef, curvature, reverse, pathColor, pathWidth, pathOpacity, gradientStartColor, gradientStopColor, delay, duration, startXOffset, startYOffset, endXOffset, endYOffset, pulses, tail, glow, seed, }: AnimatedBeamProps): import("react").JSX.Element;