diff --git a/docs/app/components/Header/HeaderSidePanel.tsx b/docs/app/components/Header/HeaderSidePanel.tsx
index c1b2fc224a..6b50d99325 100644
--- a/docs/app/components/Header/HeaderSidePanel.tsx
+++ b/docs/app/components/Header/HeaderSidePanel.tsx
@@ -10,7 +10,6 @@ import { MenuDocs } from '../Menu/MenuDocs'
 
 import { NavigationSchema } from '../../../scripts/docs/navigation'
 import { SiteThemePicker } from '../Site/SiteThemePicker'
-import { forwardRef } from 'react'
 import {
   mainNavigation,
   mobileDialogHeader,
@@ -26,80 +25,82 @@ interface HeaderSidePanelProps {
   isOpen: boolean
   submenu?: NavigationSchema
   onNavigationClick?: () => void
+  ref?: React.RefObject<HTMLDivElement | null>
 }
 
-export const HeaderSidePanel = forwardRef<HTMLDivElement, HeaderSidePanelProps>(
-  ({ isOpen, submenu, onNavigationClick }, ref) => {
-    const location = useLocation()
+export const HeaderSidePanel = ({
+  isOpen,
+  submenu,
+  onNavigationClick,
+  ref,
+}: HeaderSidePanelProps) => {
+  const location = useLocation()
 
-    const isDocs = location.pathname.includes('/docs')
+  const isDocs = location.pathname.includes('/docs')
 
-    const transitions = useTransition(isOpen, {
-      from: {
-        x: '100%',
-        opacity: 0,
-      },
-      enter: {
-        x: '0',
-        opacity: 1,
-      },
-      leave: {
-        x: '100%',
-        opacity: 0,
-      },
-      config: {
-        tension: 210,
-        friction: 30,
-        mass: 1,
-      },
-    })
+  const transitions = useTransition(isOpen, {
+    from: {
+      x: '100%',
+      opacity: 0,
+    },
+    enter: {
+      x: '0',
+      opacity: 1,
+    },
+    leave: {
+      x: '100%',
+      opacity: 0,
+    },
+    config: {
+      tension: 210,
+      friction: 30,
+      mass: 1,
+    },
+  })
 
-    const handleNavClick = () => {
-      if (onNavigationClick) {
-        onNavigationClick()
-      }
+  const handleNavClick = () => {
+    if (onNavigationClick) {
+      onNavigationClick()
     }
-
-    return transitions(({ opacity, x }, item) =>
-      item ? (
-        <>
-          <Dialog.Overlay forceMount asChild>
-            <animated.div className={mobileMenuOverlay} style={{ opacity }} />
-          </Dialog.Overlay>
-          {/* @ts-ignore */}
-          <Dialog.Content trapFocus={false} forceMount asChild>
-            <animated.div className={mobileMenu} ref={ref} style={{ x }}>
-              <div>
-                <header className={mobileDialogHeader}>
-                  <Dialog.Close className={mobileMenuClose}>
-                    <X />
-                  </Dialog.Close>
-                  <Toolbar.Root className={mobileThemePicker}>
-                    <SiteThemePicker />
-                  </Toolbar.Root>
-                </header>
-                <Dialog.Title className={visuallyHidden}>
-                  Main Menu
-                </Dialog.Title>
-                <HeaderNavigation
-                  className={mainNavigation({ isDocsSection: isDocs })}
-                  showSubNav={false}
-                  showThemePicker={false}
-                  showLabels={!isDocs}
-                />
-              </div>
-              <MenuDocs submenu={submenu} onNavClick={handleNavClick} />
-              <Toolbar.Root
-                className={subNavContainer({
-                  isDocsSection: isDocs,
-                })}
-              >
-                <HeaderSubNavigation showLabels={!isDocs} />
-              </Toolbar.Root>
-            </animated.div>
-          </Dialog.Content>
-        </>
-      ) : null
-    )
   }
-)
+
+  return transitions(({ opacity, x }, item) =>
+    item ? (
+      <>
+        <Dialog.Overlay forceMount asChild>
+          <animated.div className={mobileMenuOverlay} style={{ opacity }} />
+        </Dialog.Overlay>
+        {/* @ts-ignore */}
+        <Dialog.Content trapFocus={false} forceMount asChild>
+          <animated.div className={mobileMenu} ref={ref} style={{ x }}>
+            <div>
+              <header className={mobileDialogHeader}>
+                <Dialog.Close className={mobileMenuClose}>
+                  <X />
+                </Dialog.Close>
+                <Toolbar.Root className={mobileThemePicker}>
+                  <SiteThemePicker />
+                </Toolbar.Root>
+              </header>
+              <Dialog.Title className={visuallyHidden}>Main Menu</Dialog.Title>
+              <HeaderNavigation
+                className={mainNavigation({ isDocsSection: isDocs })}
+                showSubNav={false}
+                showThemePicker={false}
+                showLabels={!isDocs}
+              />
+            </div>
+            <MenuDocs submenu={submenu} onNavClick={handleNavClick} />
+            <Toolbar.Root
+              className={subNavContainer({
+                isDocsSection: isDocs,
+              })}
+            >
+              <HeaderSubNavigation showLabels={!isDocs} />
+            </Toolbar.Root>
+          </animated.div>
+        </Dialog.Content>
+      </>
+    ) : null
+  )
+}
diff --git a/docs/app/components/Text/Copy.tsx b/docs/app/components/Text/Copy.tsx
index 44c697fe9f..01d09f6f00 100644
--- a/docs/app/components/Text/Copy.tsx
+++ b/docs/app/components/Text/Copy.tsx
@@ -1,5 +1,5 @@
 import clsx from 'clsx'
-import { forwardRef, ReactNode } from 'react'
+import { ReactNode } from 'react'
 
 import { copy } from './Copy.css'
 import * as FontSizes from '../../styles/fontStyles.css'
@@ -9,25 +9,21 @@ export interface CopyProps {
   className?: string
   children?: ReactNode
   tag?: keyof Pick<JSX.IntrinsicElements, 'p' | 'blockquote' | 'div' | 'label'>
+  ref?: React.RefObject<any>
 }
 
-export const Copy = forwardRef<
-  | HTMLHeadingElement
-  | HTMLQuoteElement
-  | HTMLDivElement
-  | HTMLLabelElement
-  | HTMLParagraphElement,
-  CopyProps
->(({ fontStyle = 'XS', className, children, tag = 'p' }, ref) => {
+export const Copy = ({
+  fontStyle = 'XS',
+  className,
+  children,
+  tag = 'p',
+  ref,
+}: CopyProps) => {
   const Element = tag
 
   return (
-    <Element
-      className={clsx(FontSizes[fontStyle], copy, className)}
-      // @ts-expect-error – TODO: fix this
-      ref={ref}
-    >
+    <Element className={clsx(FontSizes[fontStyle], copy, className)} ref={ref}>
       {children}
     </Element>
   )
-})
+}
diff --git a/docs/app/components/Text/Heading.tsx b/docs/app/components/Text/Heading.tsx
index 1527dc7fae..437aad68db 100644
--- a/docs/app/components/Text/Heading.tsx
+++ b/docs/app/components/Text/Heading.tsx
@@ -1,4 +1,4 @@
-import { CSSProperties, forwardRef, ReactNode } from 'react'
+import { CSSProperties, ReactNode } from 'react'
 
 import { Link } from 'phosphor-react'
 
@@ -17,37 +17,34 @@ export interface HeadingProps {
   isLink?: boolean
   weight?: keyof FontSizes.FontWeights
   style?: CSSProperties
+  ref?: React.RefObject<any>
 }
 
-export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(
-  (
-    {
-      tag = 'h1',
-      fontStyle = 'S',
-      weight = 'default',
-      className,
-      children,
-      isLink = false,
-      ...restProps
-    },
-    ref
-  ) => {
-    const Element = tag
+export const Heading = ({
+  tag = 'h1',
+  fontStyle = 'S',
+  weight = 'default',
+  className,
+  children,
+  isLink = false,
+  ref,
+  ...restProps
+}: HeadingProps) => {
+  const Element = tag
 
-    return (
-      <Element
-        className={clsx(
-          FontSizes[fontStyle],
-          FontSizes.WEIGHTS[weight],
-          heading,
-          className
-        )}
-        ref={ref}
-        {...restProps}
-      >
-        {children}
-        {isLink ? <Link className={linkIcon} size={16} /> : null}
-      </Element>
-    )
-  }
-)
+  return (
+    <Element
+      className={clsx(
+        FontSizes[fontStyle],
+        FontSizes.WEIGHTS[weight],
+        heading,
+        className
+      )}
+      ref={ref}
+      {...restProps}
+    >
+      {children}
+      {isLink ? <Link className={linkIcon} size={16} /> : null}
+    </Element>
+  )
+}
diff --git a/docs/app/components/Text/List.tsx b/docs/app/components/Text/List.tsx
index ecef793db6..7fd38be89f 100644
--- a/docs/app/components/Text/List.tsx
+++ b/docs/app/components/Text/List.tsx
@@ -1,5 +1,5 @@
 import clsx from 'clsx'
-import { forwardRef, ReactNode } from 'react'
+import { ReactNode } from 'react'
 import * as FontSizes from '../../styles/fontStyles.css'
 import { descriptiveList, list } from './List.css'
 
@@ -8,23 +8,24 @@ export interface ListProps {
   fontStyle?: keyof FontSizes.FontSizes
   className?: string
   children?: ReactNode
+  ref?: React.RefObject<any>
 }
 
-export const List = forwardRef<HTMLUListElement | HTMLOListElement, ListProps>(
-  ({ tag = 'ul', fontStyle = 'XS', className, children }, ref) => {
-    const Element = tag
+export const List = ({
+  tag = 'ul',
+  fontStyle = 'XS',
+  className,
+  children,
+  ref,
+}: ListProps) => {
+  const Element = tag
 
-    return (
-      <Element
-        className={clsx(FontSizes[fontStyle], list, className)}
-        // @ts-expect-error - TODO: polymorphic refs, woo.
-        ref={ref}
-      >
-        {children}
-      </Element>
-    )
-  }
-)
+  return (
+    <Element className={clsx(FontSizes[fontStyle], list, className)} ref={ref}>
+      {children}
+    </Element>
+  )
+}
 
 interface DescriptiveListProps {
   data: [title: string, item: ReactNode][]
diff --git a/docs/package.json b/docs/package.json
index 032793171e..3ced67a027 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -32,6 +32,7 @@
     "@remix-run/serve": "2.15.2",
     "@remix-run/server-runtime": "2.15.2",
     "@supabase/supabase-js": "2.47.10",
+    "@use-gesture/react": "^10.3.1",
     "@vanilla-extract/css": "1.17.0",
     "@vanilla-extract/dynamic": "2.1.2",
     "@vanilla-extract/recipes": "0.5.5",
@@ -44,6 +45,7 @@
     "react": "18.3.1",
     "react-dom": "18.3.1",
     "react-select": "5.9.0",
+    "react-use-measure": "^2.1.1",
     "zod": "3.24.1"
   },
   "devDependencies": {
diff --git a/package.json b/package.json
index 6429c5e7f2..c1826114c5 100644
--- a/package.json
+++ b/package.json
@@ -66,7 +66,7 @@
     "@changesets/cli": "2.27.11",
     "@commitlint/cli": "19.6.1",
     "@commitlint/config-conventional": "19.6.0",
-    "@react-three/fiber": "8.17.10",
+    "@react-three/fiber": "^9.1.0",
     "@remix-run/dev": "2.15.2",
     "@simonsmith/cypress-image-snapshot": "9.1.0",
     "@swc/core": "1.10.4",
@@ -79,8 +79,8 @@
     "@types/jest": "29.5.14",
     "@types/lodash.clamp": "4.0.9",
     "@types/lodash.shuffle": "4.2.9",
-    "@types/react": "18.3.18",
-    "@types/react-dom": "18.3.5",
+    "@types/react": "19.0.0",
+    "@types/react-dom": "19.0.0",
     "@types/react-lazyload": "3.2.3",
     "@types/react-native": "0.73.0",
     "@types/styled-components": "5.1.34",
@@ -95,8 +95,8 @@
     "mock-raf": "npm:@react-spring/mock-raf@1.1.1",
     "prettier": "3.4.2",
     "pretty-quick": "4.0.0",
-    "react": "18.3.1",
-    "react-dom": "18.3.1",
+    "react": "19.0.0",
+    "react-dom": "19.0.0",
     "react-konva": "18.2.10",
     "react-native": "0.76.5",
     "react-zdog": "1.2.2",
diff --git a/packages/animated/src/withAnimated.tsx b/packages/animated/src/withAnimated.tsx
index ba715d8dde..071717c4f1 100644
--- a/packages/animated/src/withAnimated.tsx
+++ b/packages/animated/src/withAnimated.tsx
@@ -1,5 +1,5 @@
 import * as React from 'react'
-import { forwardRef, useRef, Ref, useCallback, useEffect } from 'react'
+import { useRef, Ref, useCallback, useEffect } from 'react'
 import {
   is,
   each,
@@ -27,7 +27,7 @@ export const withAnimated = (Component: any, host: HostConfig) => {
     !is.fun(Component) ||
     (Component.prototype && Component.prototype.isReactComponent)
 
-  return forwardRef((givenProps: any, givenRef: Ref<any>) => {
+  return (givenProps: any, givenRef: Ref<any>) => {
     const instanceRef = useRef<any>(null)
 
     // The `hasInstance` value is constant, so we can safely avoid
@@ -66,7 +66,7 @@ export const withAnimated = (Component: any, host: HostConfig) => {
 
     const observer = new PropsObserver(callback, deps)
 
-    const observerRef = useRef<PropsObserver>()
+    const observerRef = useRef<PropsObserver>(null)
     useIsomorphicLayoutEffect(() => {
       observerRef.current = observer
 
@@ -94,7 +94,7 @@ export const withAnimated = (Component: any, host: HostConfig) => {
 
     const usedProps = host.getComponentProps(props.getValue())
     return <Component {...usedProps} ref={ref} />
-  })
+  }
 }
 
 class PropsObserver {
diff --git a/packages/core/src/SpringContext.test.tsx b/packages/core/src/SpringContext.test.tsx
index 5b1f3f47a0..7f9de3addf 100644
--- a/packages/core/src/SpringContext.test.tsx
+++ b/packages/core/src/SpringContext.test.tsx
@@ -1,6 +1,6 @@
 import * as React from 'react'
 import { render, RenderResult } from '@testing-library/react'
-import { SpringContext } from './SpringContext'
+import { SpringContextProvider, SpringContext } from './SpringContext'
 import { SpringValue } from './SpringValue'
 import { useSpring } from './hooks'
 
@@ -13,9 +13,9 @@ describe('SpringContext', () => {
   }
 
   const update = createUpdater(props => (
-    <SpringContext {...props}>
+    <SpringContextProvider {...props}>
       <Child />
-    </SpringContext>
+    </SpringContextProvider>
   ))
 
   it('only merges when changed', () => {
@@ -27,9 +27,9 @@ describe('SpringContext', () => {
     }
 
     const getRoot = () => (
-      <SpringContext {...context}>
+      <SpringContextProvider {...context}>
         <Test />
-      </SpringContext>
+      </SpringContextProvider>
     )
 
     const expectUpdates = (updates: any[]) => {
diff --git a/packages/core/src/SpringContext.tsx b/packages/core/src/SpringContext.tsx
index 8e0a48d6fe..c01fdf23d6 100644
--- a/packages/core/src/SpringContext.tsx
+++ b/packages/core/src/SpringContext.tsx
@@ -1,6 +1,5 @@
 import * as React from 'react'
 import { useContext, PropsWithChildren } from 'react'
-import { useMemoOne } from '@react-spring/shared'
 
 /**
  * This context affects all new and existing `SpringValue` objects
@@ -13,33 +12,29 @@ export interface SpringContext {
   immediate?: boolean
 }
 
-export const SpringContext = ({
+export const SpringContext = React.createContext<SpringContext>({
+  pause: false,
+  immediate: false,
+})
+
+export const SpringContextProvider = ({
   children,
   ...props
 }: PropsWithChildren<SpringContext>) => {
-  const inherited = useContext(ctx)
+  const inherited = useContext(SpringContext)
 
   // Inherited values are dominant when truthy.
-  const pause = props.pause || !!inherited.pause,
-    immediate = props.immediate || !!inherited.immediate
+  const pause = props.pause ?? inherited.pause ?? false
+  const immediate = props.immediate ?? inherited.immediate ?? false
 
   // Memoize the context to avoid unwanted renders.
-  props = useMemoOne(() => ({ pause, immediate }), [pause, immediate])
-
-  const { Provider } = ctx
-  return <Provider value={props}>{children}</Provider>
-}
-
-const ctx = makeContext(SpringContext, {} as SpringContext)
-
-// Allow `useContext(SpringContext)` in TypeScript.
-SpringContext.Provider = ctx.Provider
-SpringContext.Consumer = ctx.Consumer
-
-/** Make the `target` compatible with `useContext` */
-function makeContext<T>(target: any, init: T): React.Context<T> {
-  Object.assign(target, React.createContext(init))
-  target.Provider._context = target
-  target.Consumer._context = target
-  return target
+  const contextValue = React.useMemo(
+    () => ({ pause, immediate }),
+    [pause, immediate]
+  )
+  return (
+    <SpringContext.Provider value={contextValue}>
+      {children}
+    </SpringContext.Provider>
+  )
 }
diff --git a/packages/core/src/hooks/useInView.ts b/packages/core/src/hooks/useInView.ts
index 7d26d2c3d2..90dc6c9067 100644
--- a/packages/core/src/hooks/useInView.ts
+++ b/packages/core/src/hooks/useInView.ts
@@ -35,7 +35,7 @@ export function useInView<TElement extends HTMLElement>(
   args?: IntersectionArgs
 ) {
   const [isInView, setIsInView] = useState(false)
-  const ref = useRef<TElement>()
+  const ref = useRef<TElement>(null)
 
   const propsFn = is.fun(props) && props
 
diff --git a/packages/core/src/hooks/useSpring.test.tsx b/packages/core/src/hooks/useSpring.test.tsx
index c5bd8892c8..dbbd19e110 100644
--- a/packages/core/src/hooks/useSpring.test.tsx
+++ b/packages/core/src/hooks/useSpring.test.tsx
@@ -2,7 +2,7 @@ import * as React from 'react'
 import { render, RenderResult } from '@testing-library/react'
 import { is } from '@react-spring/shared'
 import { Lookup } from '@react-spring/types'
-import { SpringContext } from '../SpringContext'
+import { SpringContextProvider, SpringContext } from '../SpringContext'
 import { SpringValue } from '../SpringValue'
 import { SpringRef } from '../SpringRef'
 import { useSpring } from './useSpring'
@@ -140,7 +140,9 @@ function createUpdater(Component: React.ComponentType<{ args: [any, any?] }>) {
   })
 
   function renderWithContext(elem: JSX.Element) {
-    const wrapped = <SpringContext {...context}>{elem}</SpringContext>
+    const wrapped = (
+      <SpringContextProvider {...context}>{elem}</SpringContextProvider>
+    )
     if (result) result.rerender(wrapped)
     else result = render(wrapped)
     return result
diff --git a/packages/core/src/hooks/useSprings.ts b/packages/core/src/hooks/useSprings.ts
index a1729fda9c..ddb8d2f3e4 100644
--- a/packages/core/src/hooks/useSprings.ts
+++ b/packages/core/src/hooks/useSprings.ts
@@ -128,7 +128,8 @@ export function useSprings(
   )
 
   const ctrls = useRef([...state.ctrls])
-  const updates: any[] = []
+  const updates = useRef<any[]>(null!)
+  updates.current ??= []
 
   // Cache old controllers to dispose in the commit phase.
   const prevLength = usePrev(length) || 0
@@ -164,7 +165,7 @@ export function useSprings(
         : (props as any)[i]
 
       if (update) {
-        updates[i] = declareUpdate(update)
+        updates.current[i] = declareUpdate(update)
       }
     }
   }
@@ -172,7 +173,9 @@ export function useSprings(
   // New springs are created during render so users can pass them to
   // their animated components, but new springs aren't cached until the
   // commit phase (see the `useIsomorphicLayoutEffect` callback below).
-  const springs = ctrls.current.map((ctrl, i) => getSprings(ctrl, updates[i]))
+  const springs = ctrls.current.map((ctrl, i) =>
+    getSprings(ctrl, updates.current[i])
+  )
 
   const context = useContext(SpringContext)
   const prevContext = usePrev(context)
@@ -202,7 +205,7 @@ export function useSprings(
       }
 
       // Apply updates created during render.
-      const update = updates[i]
+      const update = updates.current[i]
       if (update) {
         // Update the injected ref if needed.
         replaceRef(ctrl, update.ref)
@@ -214,6 +217,8 @@ export function useSprings(
         } else {
           ctrl.start(update)
         }
+
+        updates.current[i] = null
       }
     })
   })
diff --git a/packages/parallax/src/index.tsx b/packages/parallax/src/index.tsx
index 74e05932a5..f5bf397016 100644
--- a/packages/parallax/src/index.tsx
+++ b/packages/parallax/src/index.tsx
@@ -73,135 +73,139 @@ export interface ParallaxLayerProps extends ViewProps {
   speed?: number
   /** Layer will be sticky between these two offsets, all other props are ignored */
   sticky?: StickyConfig
+  ref?: React.RefObject<IParallaxLayer>
 }
 
 export const ParallaxLayer = React.memo(
-  React.forwardRef<IParallaxLayer, ParallaxLayerProps>(
-    (
-      { horizontal, factor = 1, offset = 0, speed = 0, sticky, ...rest },
-      ref
-    ) => {
-      // Our parent controls our height and position.
-      const parent = useContext<IParallax>(ParentContext)
-
-      // This is how we animate.
-      const ctrl = useMemoOne(() => {
-        let translate
-        if (sticky) {
-          const start = sticky.start || 0
-          translate = start * parent.space
-        } else {
-          const targetScroll = Math.floor(offset) * parent.space
-          const distance = parent.space * offset + targetScroll * speed
-          translate = -(parent.current * speed) + distance
-        }
-        type Animated = { space: number; translate: number }
-        return new Controller<Animated>({
-          space: sticky ? parent.space : parent.space * factor,
-          translate,
-        })
-      }, [])
-
-      // Create the layer.
-      const layer = useMemoOne<IParallaxLayer>(
-        () => ({
-          horizontal:
-            horizontal === undefined || sticky ? parent.horizontal : horizontal,
-          sticky: undefined,
-          isSticky: false,
-          setPosition(height, scrollTop, immediate = false) {
-            if (sticky) {
-              setSticky(height, scrollTop)
-            } else {
-              const targetScroll = Math.floor(offset) * height
-              const distance = height * offset + targetScroll * speed
-              ctrl.start({
-                translate: -(scrollTop * speed) + distance,
-                config: parent.config,
-                immediate,
-              })
-            }
-          },
-          setHeight(height, immediate = false) {
+  ({
+    horizontal,
+    factor = 1,
+    offset = 0,
+    speed = 0,
+    sticky,
+    ref,
+    ...rest
+  }: ParallaxLayerProps) => {
+    // Our parent controls our height and position.
+    const parent = useContext<IParallax>(ParentContext)
+
+    // This is how we animate.
+    const ctrl = useMemoOne(() => {
+      let translate
+      if (sticky) {
+        const start = sticky.start || 0
+        translate = start * parent.space
+      } else {
+        const targetScroll = Math.floor(offset) * parent.space
+        const distance = parent.space * offset + targetScroll * speed
+        translate = -(parent.current * speed) + distance
+      }
+      type Animated = { space: number; translate: number }
+      return new Controller<Animated>({
+        space: sticky ? parent.space : parent.space * factor,
+        translate,
+      })
+    }, [])
+
+    // Create the layer.
+    const layer = useMemoOne<IParallaxLayer>(
+      () => ({
+        horizontal:
+          horizontal === undefined || sticky ? parent.horizontal : horizontal,
+        sticky: undefined,
+        isSticky: false,
+        setPosition(height, scrollTop, immediate = false) {
+          if (sticky) {
+            setSticky(height, scrollTop)
+          } else {
+            const targetScroll = Math.floor(offset) * height
+            const distance = height * offset + targetScroll * speed
             ctrl.start({
-              space: sticky ? height : height * factor,
+              translate: -(scrollTop * speed) + distance,
               config: parent.config,
               immediate,
             })
-          },
-        }),
-        []
-      )
-
-      useOnce(() => {
-        if (sticky) {
-          const start = sticky.start || 0
-          const end = sticky.end || start + 1
-          layer.sticky = { start, end }
-        }
-      })
+          }
+        },
+        setHeight(height, immediate = false) {
+          ctrl.start({
+            space: sticky ? height : height * factor,
+            config: parent.config,
+            immediate,
+          })
+        },
+      }),
+      []
+    )
+
+    useOnce(() => {
+      if (sticky) {
+        const start = sticky.start || 0
+        const end = sticky.end || start + 1
+        layer.sticky = { start, end }
+      }
+    })
 
-      React.useImperativeHandle(ref, () => layer)
+    React.useImperativeHandle(ref, () => layer)
 
-      const layerRef = useRef<any>()
+    const layerRef = useRef<any>(null)
 
-      const setSticky = (height: number, scrollTop: number) => {
-        const start = layer.sticky!.start! * height
-        const end = layer.sticky!.end! * height
-        const isSticky = scrollTop >= start && scrollTop <= end
+    const setSticky = (height: number, scrollTop: number) => {
+      const start = layer.sticky!.start! * height
+      const end = layer.sticky!.end! * height
+      const isSticky = scrollTop >= start && scrollTop <= end
 
-        if (isSticky === layer.isSticky) return
-        layer.isSticky = isSticky
+      if (isSticky === layer.isSticky) return
+      layer.isSticky = isSticky
 
-        const ref = layerRef.current
-        ref.style.position = isSticky ? 'sticky' : 'absolute'
-        ctrl.set({
-          translate: isSticky ? 0 : scrollTop < start ? start : end,
-        })
-      }
+      const ref = layerRef.current
+      ref.style.position = isSticky ? 'sticky' : 'absolute'
+      ctrl.set({
+        translate: isSticky ? 0 : scrollTop < start ? start : end,
+      })
+    }
 
-      // Register the layer with our parent.
-      useOnce(() => {
-        if (parent) {
-          parent.layers.add(layer)
+    // Register the layer with our parent.
+    useOnce(() => {
+      if (parent) {
+        parent.layers.add(layer)
+        parent.update()
+        return () => {
+          parent.layers.delete(layer)
           parent.update()
-          return () => {
-            parent.layers.delete(layer)
-            parent.update()
-          }
         }
-      })
+      }
+    })
 
-      const translate3d = ctrl.springs.translate.to(
-        layer.horizontal
-          ? x => `translate3d(${x}px,0,0)`
-          : y => `translate3d(0,${y}px,0)`
-      )
-
-      return (
-        <a.div
-          {...rest}
-          ref={layerRef}
-          style={{
-            position: 'absolute',
-            top: 0,
-            bottom: 0,
-            left: 0,
-            right: 0,
-            backgroundSize: 'auto',
-            backgroundRepeat: 'no-repeat',
-            willChange: 'transform',
-            [layer.horizontal ? 'height' : 'width']: '100%',
-            [layer.horizontal ? 'width' : 'height']: ctrl.springs.space,
-            WebkitTransform: translate3d,
-            msTransform: translate3d,
-            transform: translate3d,
-            ...rest.style,
-          }}
-        />
-      )
-    }
-  )
+    const translate3d = ctrl.springs.translate.to(
+      layer.horizontal
+        ? x => `translate3d(${x}px,0,0)`
+        : y => `translate3d(0,${y}px,0)`
+    )
+
+    return (
+      <a.div
+        {...rest}
+        ref={layerRef}
+        style={{
+          position: 'absolute',
+          top: 0,
+          bottom: 0,
+          left: 0,
+          right: 0,
+          backgroundSize: 'auto',
+          backgroundRepeat: 'no-repeat',
+          willChange: 'transform',
+          [layer.horizontal ? 'height' : 'width']: '100%',
+          [layer.horizontal ? 'width' : 'height']: ctrl.springs.space,
+          WebkitTransform: translate3d,
+          msTransform: translate3d,
+          transform: translate3d,
+          ...rest.style,
+        }}
+      />
+    )
+  }
 )
 
 type ConfigProp = SpringConfig | ((key: string) => SpringConfig)
@@ -214,179 +218,179 @@ export interface ParallaxProps extends ViewProps {
   horizontal?: boolean
   innerStyle?: CSSProperties
   children: React.ReactNode
+  ref?: React.RefObject<IParallax>
 }
 
-export const Parallax = React.memo(
-  React.forwardRef<IParallax, ParallaxProps>((props, ref) => {
-    const [ready, setReady] = useState(false)
-    const {
-      pages,
-      innerStyle: _innerStyle,
-      config = configs.slow,
-      enabled = true,
-      horizontal = false,
-      children,
-      ...rest
-    } = props
-
-    const containerRef = useRef<any>()
-    const contentRef = useRef<any>()
-
-    const state: IParallax = useMemoOne(
-      () => ({
-        config,
-        horizontal,
-        busy: false,
-        space: 0,
-        current: 0,
-        offset: 0,
-        controller: new Controller({ scroll: 0 }),
-        layers: new Set<IParallaxLayer>(),
-        container: containerRef,
-        content: contentRef,
-        update: () => update(),
-        scrollTo: offset => scrollTo(offset),
-        stop: () => state.controller.stop(),
-      }),
-      []
-    )
-
-    useEffect(() => {
-      state.config = config
-      // eslint-disable-next-line react-hooks/exhaustive-deps
-    }, [config])
+export const Parallax = React.memo((props: ParallaxProps) => {
+  const [ready, setReady] = useState(false)
+  const {
+    pages,
+    innerStyle: _innerStyle,
+    config = configs.slow,
+    enabled = true,
+    horizontal = false,
+    children,
+    ref,
+    ...rest
+  } = props
+
+  const containerRef = useRef<any>(null)
+  const contentRef = useRef<any>(null)
+
+  const state: IParallax = useMemoOne(
+    () => ({
+      config,
+      horizontal,
+      busy: false,
+      space: 0,
+      current: 0,
+      offset: 0,
+      controller: new Controller({ scroll: 0 }),
+      layers: new Set<IParallaxLayer>(),
+      container: containerRef,
+      content: contentRef,
+      update: () => update(),
+      scrollTo: offset => scrollTo(offset),
+      stop: () => state.controller.stop(),
+    }),
+    []
+  )
 
-    React.useImperativeHandle(ref, () => state)
+  useEffect(() => {
+    state.config = config
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [config])
 
-    const update = () => {
-      const container = containerRef.current
-      if (!container) return
+  React.useImperativeHandle(ref, () => state)
 
-      const spaceProp = horizontal ? 'clientWidth' : 'clientHeight'
-      state.space = container[spaceProp]
+  const update = () => {
+    const container = containerRef.current
+    if (!container) return
 
-      const scrollType = getScrollType(horizontal)
-      if (enabled) {
-        state.current = container[scrollType]
-      } else {
-        container[scrollType] = state.current = state.offset * state.space
-      }
+    const spaceProp = horizontal ? 'clientWidth' : 'clientHeight'
+    state.space = container[spaceProp]
 
-      const content = contentRef.current
-      if (content) {
-        const sizeProp = horizontal ? 'width' : 'height'
-        content.style[sizeProp] = `${state.space * pages}px`
-      }
+    const scrollType = getScrollType(horizontal)
+    if (enabled) {
+      state.current = container[scrollType]
+    } else {
+      container[scrollType] = state.current = state.offset * state.space
+    }
 
-      state.layers.forEach(layer => {
-        layer.setHeight(state.space, true)
-        layer.setPosition(state.space, state.current, true)
-      })
+    const content = contentRef.current
+    if (content) {
+      const sizeProp = horizontal ? 'width' : 'height'
+      content.style[sizeProp] = `${state.space * pages}px`
     }
 
-    const scrollTo = (offset: number) => {
-      const container = containerRef.current
-      const scrollType = getScrollType(horizontal)
+    state.layers.forEach(layer => {
+      layer.setHeight(state.space, true)
+      layer.setPosition(state.space, state.current, true)
+    })
+  }
 
-      state.offset = offset
+  const scrollTo = (offset: number) => {
+    const container = containerRef.current
+    const scrollType = getScrollType(horizontal)
 
-      state.controller.set({ scroll: state.current })
-      state.controller.stop().start({
-        scroll: offset * state.space,
-        config,
-        onChange({ value: { scroll } }: any) {
-          container[scrollType] = scroll
-        },
+    state.offset = offset
+
+    state.controller.set({ scroll: state.current })
+    state.controller.stop().start({
+      scroll: offset * state.space,
+      config,
+      onChange({ value: { scroll } }: any) {
+        container[scrollType] = scroll
+      },
+    })
+  }
+
+  const onScroll = (event: any) => {
+    if (!state.busy) {
+      state.busy = true
+      state.current = event.target[getScrollType(horizontal)]
+      raf.onStart(() => {
+        state.layers.forEach(layer =>
+          layer.setPosition(state.space, state.current)
+        )
+        state.busy = false
       })
     }
+  }
 
-    const onScroll = (event: any) => {
-      if (!state.busy) {
-        state.busy = true
-        state.current = event.target[getScrollType(horizontal)]
-        raf.onStart(() => {
-          state.layers.forEach(layer =>
-            layer.setPosition(state.space, state.current)
-          )
-          state.busy = false
-        })
-      }
+  useEffect(() => state.update())
+  useOnce(() => {
+    setReady(true)
+
+    const onResize = () => {
+      const update = () => state.update()
+      raf.onFrame(update)
+      setTimeout(update, 150) // Some browsers don't fire on maximize!
     }
 
-    useEffect(() => state.update())
-    useOnce(() => {
-      setReady(true)
+    window.addEventListener('resize', onResize, false)
+    return () => window.removeEventListener('resize', onResize, false)
+  })
 
-      const onResize = () => {
-        const update = () => state.update()
-        raf.onFrame(update)
-        setTimeout(update, 150) // Some browsers don't fire on maximize!
+  const overflow: React.CSSProperties = enabled
+    ? {
+        overflowY: horizontal ? 'hidden' : 'scroll',
+        overflowX: horizontal ? 'scroll' : 'hidden',
+      }
+    : {
+        overflowY: 'hidden',
+        overflowX: 'hidden',
       }
 
-      window.addEventListener('resize', onResize, false)
-      return () => window.removeEventListener('resize', onResize, false)
-    })
-
-    const overflow: React.CSSProperties = enabled
-      ? {
-          overflowY: horizontal ? 'hidden' : 'scroll',
-          overflowX: horizontal ? 'scroll' : 'hidden',
-        }
-      : {
-          overflowY: 'hidden',
-          overflowX: 'hidden',
-        }
-
-    return (
-      <a.div
-        {...rest}
-        ref={containerRef}
-        onScroll={onScroll}
-        onWheel={enabled ? state.stop : undefined}
-        onTouchStart={enabled ? state.stop : undefined}
-        style={{
-          position: 'absolute',
-          width: '100%',
-          height: '100%',
-          ...overflow,
-          WebkitOverflowScrolling: 'touch',
-          WebkitTransform: START_TRANSLATE,
-          msTransform: START_TRANSLATE,
-          transform: START_TRANSLATE_3D,
-          ...rest.style,
-        }}
-      >
-        {ready && (
-          <>
-            <a.div
-              ref={contentRef}
-              style={{
-                overflow: 'hidden',
-                position: 'absolute',
-                [horizontal ? 'height' : 'width']: '100%',
-                [horizontal ? 'width' : 'height']: state.space * pages,
-                WebkitTransform: START_TRANSLATE,
-                msTransform: START_TRANSLATE,
-                transform: START_TRANSLATE_3D,
-                ...props.innerStyle,
-              }}
-            >
-              <ParentContext.Provider value={state}>
-                {mapChildrenRecursive(
-                  children,
-                  (child: any) => !child.props.sticky && child
-                )}
-              </ParentContext.Provider>
-            </a.div>
+  return (
+    <a.div
+      {...rest}
+      ref={containerRef}
+      onScroll={onScroll}
+      onWheel={enabled ? state.stop : undefined}
+      onTouchStart={enabled ? state.stop : undefined}
+      style={{
+        position: 'absolute',
+        width: '100%',
+        height: '100%',
+        ...overflow,
+        WebkitOverflowScrolling: 'touch',
+        WebkitTransform: START_TRANSLATE,
+        msTransform: START_TRANSLATE,
+        transform: START_TRANSLATE_3D,
+        ...rest.style,
+      }}
+    >
+      {ready && (
+        <>
+          <a.div
+            ref={contentRef}
+            style={{
+              overflow: 'hidden',
+              position: 'absolute',
+              [horizontal ? 'height' : 'width']: '100%',
+              [horizontal ? 'width' : 'height']: state.space * pages,
+              WebkitTransform: START_TRANSLATE,
+              msTransform: START_TRANSLATE,
+              transform: START_TRANSLATE_3D,
+              ...props.innerStyle,
+            }}
+          >
             <ParentContext.Provider value={state}>
               {mapChildrenRecursive(
                 children,
-                (child: any) => child.props.sticky && child
+                (child: any) => !child.props.sticky && child
               )}
             </ParentContext.Provider>
-          </>
-        )}
-      </a.div>
-    )
-  })
-)
+          </a.div>
+          <ParentContext.Provider value={state}>
+            {mapChildrenRecursive(
+              children,
+              (child: any) => child.props.sticky && child
+            )}
+          </ParentContext.Provider>
+        </>
+      )}
+    </a.div>
+  )
+})
diff --git a/packages/shared/src/hooks/useMemoOne.ts b/packages/shared/src/hooks/useMemoOne.ts
index 4d02743845..a06b84d052 100644
--- a/packages/shared/src/hooks/useMemoOne.ts
+++ b/packages/shared/src/hooks/useMemoOne.ts
@@ -14,7 +14,7 @@ export function useMemoOne<T>(getResult: () => T, inputs?: any[]): T {
     })
   )
 
-  const committed = useRef<Cache<T>>()
+  const committed = useRef<Cache<T>>(null)
   const prevCache = committed.current
 
   let cache = prevCache
diff --git a/packages/shared/src/hooks/usePrev.ts b/packages/shared/src/hooks/usePrev.ts
index 9c97e064c8..bf44a12d3a 100644
--- a/packages/shared/src/hooks/usePrev.ts
+++ b/packages/shared/src/hooks/usePrev.ts
@@ -2,7 +2,7 @@ import { useEffect, useRef } from 'react'
 
 /** Use a value from the previous render */
 export function usePrev<T>(value: T): T | undefined {
-  const prevRef = useRef<any>()
+  const prevRef = useRef<any>(null)
   useEffect(() => {
     prevRef.current = value
   })
diff --git a/targets/three/src/index.ts b/targets/three/src/index.ts
index 7840ae51ad..450a68c960 100644
--- a/targets/three/src/index.ts
+++ b/targets/three/src/index.ts
@@ -19,7 +19,6 @@ addEffect(() => {
 })
 
 const host = createHost(primitives, {
-  // @ts-expect-error r3f related
   applyAnimatedValues: applyProps,
 })
 
diff --git a/targets/web/src/animated.test.tsx b/targets/web/src/animated.test.tsx
index 6c7cf2c5dc..82301972a5 100644
--- a/targets/web/src/animated.test.tsx
+++ b/targets/web/src/animated.test.tsx
@@ -1,5 +1,4 @@
 import * as React from 'react'
-import { forwardRef } from 'react'
 import { render } from '@testing-library/react'
 import createMockRaf, { MockRaf } from 'mock-raf'
 import { Globals } from '@react-spring/shared'
@@ -27,14 +26,16 @@ describe('animated component', () => {
     expect(queryByTitle('Foo')).toBeTruthy()
   })
   it('wraps a component', () => {
-    const Name = forwardRef<
-      HTMLHeadingElement,
-      { name: string; other: string; children: React.ReactNode }
-    >((props, ref) => (
-      <h2 title={props.name} ref={ref}>
+    const Name = (props: {
+      name: string
+      other: string
+      children: React.ReactNode
+      ref?: React.RefObject<HTMLHeadingElement>
+    }) => (
+      <h2 title={props.name} ref={props.ref}>
         {props.children}
       </h2>
-    ))
+    )
     const AnimatedName = a(Name)
     const child = spring('Animated Text')
     const name = spring('name')
@@ -60,14 +61,15 @@ describe('animated component', () => {
     expect(div.style.opacity).toBe('1')
   })
   it('accepts Animated values in custom style prop', () => {
-    const Name = forwardRef<
-      HTMLHeadingElement,
-      { style: { color: string; opacity?: number }; children: React.ReactNode }
-    >((props, ref) => (
-      <h2 ref={ref} style={props.style}>
+    const Name = (props: {
+      style: { color: string; opacity?: number }
+      children: React.ReactNode
+      ref?: React.RefObject<HTMLHeadingElement>
+    }) => (
+      <h2 ref={props.ref} style={props.style}>
         {props.children}
       </h2>
-    ))
+    )
     const AnimatedName = a(Name)
     const opacity = spring(0.5)
     const { queryByText } = render(
diff --git a/yarn.lock b/yarn.lock
index c0fdc04aab..fa45ca266a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4682,6 +4682,7 @@ __metadata:
     "@remix-run/serve": 2.15.2
     "@remix-run/server-runtime": 2.15.2
     "@supabase/supabase-js": 2.47.10
+    "@use-gesture/react": ^10.3.1
     "@vanilla-extract/css": 1.17.0
     "@vanilla-extract/dynamic": 2.1.2
     "@vanilla-extract/recipes": 0.5.5
@@ -4702,6 +4703,7 @@ __metadata:
     react: 18.3.1
     react-dom: 18.3.1
     react-select: 5.9.0
+    react-use-measure: ^2.1.1
     refractor: 4.8.1
     rehype-autolink-headings: 7.1.0
     rehype-parse: 9.0.1
@@ -4908,6 +4910,48 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@react-three/fiber@npm:^9.1.0":
+  version: 9.1.0
+  resolution: "@react-three/fiber@npm:9.1.0"
+  dependencies:
+    "@babel/runtime": ^7.17.8
+    "@types/react-reconciler": ^0.28.9
+    "@types/webxr": "*"
+    base64-js: ^1.5.1
+    buffer: ^6.0.3
+    its-fine: ^2.0.0
+    react-reconciler: ^0.31.0
+    react-use-measure: ^2.1.7
+    scheduler: ^0.25.0
+    suspend-react: ^0.1.3
+    use-sync-external-store: ^1.4.0
+    zustand: ^5.0.3
+  peerDependencies:
+    expo: ">=43.0"
+    expo-asset: ">=8.4"
+    expo-file-system: ">=11.0"
+    expo-gl: ">=11.0"
+    react: ^19.0.0
+    react-dom: ^19.0.0
+    react-native: ">=0.78"
+    three: ">=0.156"
+  peerDependenciesMeta:
+    expo:
+      optional: true
+    expo-asset:
+      optional: true
+    expo-file-system:
+      optional: true
+    expo-gl:
+      optional: true
+    react-dom:
+      optional: true
+    react-native:
+      optional: true
+  checksum: b1ace61e7002a3d73213be94f5dcd2ecbfd486c1e84dd051dfd851b3fcaa7cbe1d55c8b8bc7f7c6fb9f1cd733f608f57343e10fa3bc683e4aced566026791775
+  languageName: node
+  linkType: hard
+
 "@remix-run/dev@npm:2.15.2":
   version: 2.15.2
   resolution: "@remix-run/dev@npm:2.15.2"
@@ -6047,12 +6091,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/react-dom@npm:18.3.5":
-  version: 18.3.5
-  resolution: "@types/react-dom@npm:18.3.5"
-  peerDependencies:
-    "@types/react": ^18.0.0
-  checksum: 95c757684f71e761515c5a11299e5feec550c72bb52975487f360e6f0d359b26454c26eaf2ce45dd22748205aa9b2c2fe0abe7005ebcbd233a7615283ac39a7d
+"@types/react-dom@npm:19.0.0":
+  version: 19.0.0
+  resolution: "@types/react-dom@npm:19.0.0"
+  dependencies:
+    "@types/react": "*"
+  checksum: 86945c4d4c4cd82e993acdd380c3d9d8e8ca297228aa72c0fa6af4620abb145e7b12235c9165d569f1b25b5f72d1dbe4b4e3f2419432248de9838b22e94295a2
   languageName: node
   linkType: hard
 
@@ -6092,6 +6136,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/react-reconciler@npm:^0.28.9":
+  version: 0.28.9
+  resolution: "@types/react-reconciler@npm:0.28.9"
+  peerDependencies:
+    "@types/react": "*"
+  checksum: 06257f693c7b148a4258c0d0a958288116100014e7b3c21ceaea2d55a668c71718f79b4105a9a0f35b480f3729e46960b40026d685719f9386b4ed63108dda09
+  languageName: node
+  linkType: hard
+
 "@types/react-transition-group@npm:^4.4.0":
   version: 4.4.10
   resolution: "@types/react-transition-group@npm:4.4.10"
@@ -6101,7 +6154,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/react@npm:*, @types/react@npm:18.3.18":
+"@types/react@npm:*":
   version: 18.3.18
   resolution: "@types/react@npm:18.3.18"
   dependencies:
@@ -6111,6 +6164,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/react@npm:19.0.0":
+  version: 19.0.0
+  resolution: "@types/react@npm:19.0.0"
+  dependencies:
+    csstype: ^3.0.2
+  checksum: dd7d7388b28fdf78cdf28c88490fe99413a0e1fab33e92afdf862620edc77dfe605ffe48dd3aeffb1de29107ea958a12f6d667236b2ead1affdf609db7152fad
+  languageName: node
+  linkType: hard
+
 "@types/semver@npm:^7.5.0":
   version: 7.5.6
   resolution: "@types/semver@npm:7.5.6"
@@ -12519,6 +12581,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"its-fine@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "its-fine@npm:2.0.0"
+  dependencies:
+    "@types/react-reconciler": ^0.28.9
+  peerDependencies:
+    react: ^19.0.0
+  checksum: 887ff10d8dfe8558683d5f68ad963c72a28c6df027c5039de7ec57978e5071c564ef4b00b14ef41e7706e5839a5584cbd480a79a3880f78d7ff826931e5dc22a
+  languageName: node
+  linkType: hard
+
 "jackspeak@npm:^3.1.2":
   version: 3.4.3
   resolution: "jackspeak@npm:3.4.3"
@@ -16886,6 +16959,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"react-dom@npm:19.0.0":
+  version: 19.0.0
+  resolution: "react-dom@npm:19.0.0"
+  dependencies:
+    scheduler: ^0.25.0
+  peerDependencies:
+    react: ^19.0.0
+  checksum: 009cc6e575263a0d1906f9dd4aa6532d2d3d0d71e4c2b7777c8fe4de585fa06b5b77cdc2e0fbaa2f3a4a5e5d3305c189ba152153f358ee7da4d9d9ba5d3a8975
+  languageName: node
+  linkType: hard
+
 "react-dropzone@npm:^12.0.0":
   version: 12.1.0
   resolution: "react-dropzone@npm:12.1.0"
@@ -17013,6 +17097,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"react-reconciler@npm:^0.31.0":
+  version: 0.31.0
+  resolution: "react-reconciler@npm:0.31.0"
+  dependencies:
+    scheduler: ^0.25.0
+  peerDependencies:
+    react: ^19.0.0
+  checksum: 820c4e4003c5615849bf0cda97d8a55b99af2bb59cc0825882b727f0ad0c4bf4581bb3d25e00beca1164203dbc172f0a8c4725e7aa2fb85e025938722384a84e
+  languageName: node
+  linkType: hard
+
 "react-reconciler@npm:~0.29.0":
   version: 0.29.0
   resolution: "react-reconciler@npm:0.29.0"
@@ -17125,7 +17220,7 @@ __metadata:
     "@changesets/cli": 2.27.11
     "@commitlint/cli": 19.6.1
     "@commitlint/config-conventional": 19.6.0
-    "@react-three/fiber": 8.17.10
+    "@react-three/fiber": ^9.1.0
     "@remix-run/dev": 2.15.2
     "@simonsmith/cypress-image-snapshot": 9.1.0
     "@swc/core": 1.10.4
@@ -17138,8 +17233,8 @@ __metadata:
     "@types/jest": 29.5.14
     "@types/lodash.clamp": 4.0.9
     "@types/lodash.shuffle": 4.2.9
-    "@types/react": 18.3.18
-    "@types/react-dom": 18.3.5
+    "@types/react": 19.0.0
+    "@types/react-dom": 19.0.0
     "@types/react-lazyload": 3.2.3
     "@types/react-native": 0.73.0
     "@types/styled-components": 5.1.34
@@ -17154,8 +17249,8 @@ __metadata:
     mock-raf: "npm:@react-spring/mock-raf@1.1.1"
     prettier: 3.4.2
     pretty-quick: 4.0.0
-    react: 18.3.1
-    react-dom: 18.3.1
+    react: 19.0.0
+    react-dom: 19.0.0
     react-konva: 18.2.10
     react-native: 0.76.5
     react-zdog: 1.2.2
@@ -17238,6 +17333,19 @@ __metadata:
   languageName: node
   linkType: hard
 
+"react-use-measure@npm:^2.1.1, react-use-measure@npm:^2.1.7":
+  version: 2.1.7
+  resolution: "react-use-measure@npm:2.1.7"
+  peerDependencies:
+    react: ">=16.13"
+    react-dom: ">=16.13"
+  peerDependenciesMeta:
+    react-dom:
+      optional: true
+  checksum: 5f00c14cf50b0710cdbd27b63a005be20283099d2fa2723a97f3a1cf0b2daedddd67249520c21e49e95348f56428689f3229c343dcb9ed37da58f9c227d29bee
+  languageName: node
+  linkType: hard
+
 "react-zdog@npm:1.2.2":
   version: 1.2.2
   resolution: "react-zdog@npm:1.2.2"
@@ -17269,6 +17377,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"react@npm:19.0.0":
+  version: 19.0.0
+  resolution: "react@npm:19.0.0"
+  checksum: 86de15d85b2465feb40297a90319c325cb07cf27191a361d47bcfe8c6126c973d660125aa67b8f4cbbe39f15a2f32efd0c814e98196d8e5b68c567ba40a399c6
+  languageName: node
+  linkType: hard
+
 "read-yaml-file@npm:^1.1.0":
   version: 1.1.0
   resolution: "read-yaml-file@npm:1.1.0"
@@ -18053,6 +18168,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"scheduler@npm:^0.25.0":
+  version: 0.25.0
+  resolution: "scheduler@npm:0.25.0"
+  checksum: b7bb9fddbf743e521e9aaa5198a03ae823f5e104ebee0cb9ec625392bb7da0baa1c28ab29cee4b1e407a94e76acc6eee91eeb749614f91f853efda2613531566
+  languageName: node
+  linkType: hard
+
 "section-matter@npm:^1.0.0":
   version: 1.0.0
   resolution: "section-matter@npm:1.0.0"
@@ -20138,6 +20260,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"use-sync-external-store@npm:^1.4.0":
+  version: 1.4.0
+  resolution: "use-sync-external-store@npm:1.4.0"
+  peerDependencies:
+    react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+  checksum: dc3843a1b59ac8bd01417bd79498d4c688d5df8bf4801be50008ef4bfaacb349058c0b1605b5b43c828e0a2d62722d7e861573b3f31cea77a7f23e8b0fc2f7e3
+  languageName: node
+  linkType: hard
+
 "util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1":
   version: 1.0.2
   resolution: "util-deprecate@npm:1.0.2"
@@ -21075,6 +21206,27 @@ __metadata:
   languageName: node
   linkType: hard
 
+"zustand@npm:^5.0.3":
+  version: 5.0.3
+  resolution: "zustand@npm:5.0.3"
+  peerDependencies:
+    "@types/react": ">=18.0.0"
+    immer: ">=9.0.6"
+    react: ">=18.0.0"
+    use-sync-external-store: ">=1.2.0"
+  peerDependenciesMeta:
+    "@types/react":
+      optional: true
+    immer:
+      optional: true
+    react:
+      optional: true
+    use-sync-external-store:
+      optional: true
+  checksum: 72da39ac3017726c3562c615a0f76cee0c9ea678d664f82ee7669f8cb5e153ee81059363473094e4154d73a2935ee3459f6792d1ec9d08d2e72ebe641a16a6ba
+  languageName: node
+  linkType: hard
+
 "zwitch@npm:^2.0.0, zwitch@npm:^2.0.4":
   version: 2.0.4
   resolution: "zwitch@npm:2.0.4"