{
  "mappings": "AASA,cAAc,uBAAgD;AAmJ9D,OAAO,iBAAS,iBAAiB,kBAAkB,YAAY,IAAI,gBAAgB",
  "names": [],
  "sources": [
    "src/createAnimations.tsx"
  ],
  "version": 3,
  "sourcesContent": [
    "import {\n  normalizeTransition,\n  getAnimatedProperties,\n  hasAnimation as hasNormalizedAnimation,\n  getEffectiveAnimation,\n  getAnimationConfigsForKeys,\n} from '@tamagui/animation-helpers'\nimport { useIsomorphicLayoutEffect } from '@tamagui/constants'\nimport { ResetPresence, usePresence } from '@tamagui/use-presence'\nimport type { AnimationDriver, UniversalAnimatedNumber } from '@tamagui/web'\nimport { transformsToString } from '@tamagui/web'\nimport React, { useState } from 'react' // import { animate } from '@tamagui/cubic-bezier-animator'\n\nconst EXTRACT_MS_REGEX = /(\\d+(?:\\.\\d+)?)\\s*ms/\nconst EXTRACT_S_REGEX = /(\\d+(?:\\.\\d+)?)\\s*s/\n\n/**\n * Helper function to extract duration from CSS animation string\n * Examples: \"ease-in 200ms\" -> 200, \"cubic-bezier(0.215, 0.610, 0.355, 1.000) 400ms\" -> 400\n * \"ease-in 0.5s\" -> 500, \"slow 2s\" -> 2000\n */\nfunction extractDuration(animation: string): number {\n  // Try to match milliseconds first\n  const msMatch = animation.match(EXTRACT_MS_REGEX)\n  if (msMatch) {\n    return Number.parseInt(msMatch[1], 10)\n  }\n\n  // Try to match seconds and convert to milliseconds\n  const sMatch = animation.match(EXTRACT_S_REGEX)\n  if (sMatch) {\n    return Math.round(Number.parseFloat(sMatch[1]) * 1000)\n  }\n\n  // Default to 300ms if no duration found\n  return 300\n}\n\nconst MS_DURATION_REGEX = /(\\d+(?:\\.\\d+)?)\\s*ms/\nconst S_DURATION_REGEX = /(\\d+(?:\\.\\d+)?)\\s*s(?!tiffness)/\n\n/**\n * Apply duration override to a CSS animation string\n * Replaces the existing duration with the override value\n */\nfunction applyDurationOverride(animation: string, durationMs: number): string {\n  // Replace ms duration\n  const msReplaced = animation.replace(MS_DURATION_REGEX, `${durationMs}ms`)\n  if (msReplaced !== animation) {\n    return msReplaced\n  }\n\n  // Replace seconds duration\n  const sReplaced = animation.replace(S_DURATION_REGEX, `${durationMs}ms`)\n  if (sReplaced !== animation) {\n    return sReplaced\n  }\n\n  // No duration found, prepend the duration\n  return `${durationMs}ms ${animation}`\n}\n\n// transform keys that need special handling\nconst TRANSFORM_KEYS = [\n  'x',\n  'y',\n  'scale',\n  'scaleX',\n  'scaleY',\n  'rotate',\n  'rotateX',\n  'rotateY',\n  'rotateZ',\n  'skewX',\n  'skewY',\n] as const\n\n/**\n * Build a CSS transform string from a style object containing transform properties\n */\nfunction buildTransformString(style: Record<string, unknown> | undefined): string {\n  if (!style) return ''\n\n  const parts: string[] = []\n\n  if (style.x !== undefined || style.y !== undefined) {\n    const x = style.x ?? 0\n    const y = style.y ?? 0\n    parts.push(`translate(${x}px, ${y}px)`)\n  }\n  if (style.scale !== undefined) {\n    parts.push(`scale(${style.scale})`)\n  }\n  if (style.scaleX !== undefined) {\n    parts.push(`scaleX(${style.scaleX})`)\n  }\n  if (style.scaleY !== undefined) {\n    parts.push(`scaleY(${style.scaleY})`)\n  }\n  if (style.rotate !== undefined) {\n    const val = style.rotate\n    const unit = typeof val === 'string' && val.includes('deg') ? '' : 'deg'\n    parts.push(`rotate(${val}${unit})`)\n  }\n  if (style.rotateX !== undefined) {\n    parts.push(`rotateX(${style.rotateX}deg)`)\n  }\n  if (style.rotateY !== undefined) {\n    parts.push(`rotateY(${style.rotateY}deg)`)\n  }\n  if (style.rotateZ !== undefined) {\n    parts.push(`rotateZ(${style.rotateZ}deg)`)\n  }\n  if (style.skewX !== undefined) {\n    parts.push(`skewX(${style.skewX}deg)`)\n  }\n  if (style.skewY !== undefined) {\n    parts.push(`skewY(${style.skewY}deg)`)\n  }\n\n  return parts.join(' ')\n}\n\n/**\n * Apply a style object to a DOM node, handling transform keys specially\n */\nfunction applyStylesToNode(\n  node: HTMLElement,\n  style: Record<string, unknown> | undefined\n): void {\n  if (!style) return\n\n  // collect transform values\n  const transformStr = buildTransformString(style)\n  if (transformStr) {\n    node.style.transform = transformStr\n  }\n\n  // apply non-transform properties\n  for (const [key, value] of Object.entries(style)) {\n    if (TRANSFORM_KEYS.includes(key as any)) continue\n    if (value === undefined) continue\n\n    if (key === 'opacity') {\n      node.style.opacity = String(value)\n    } else if (key === 'backgroundColor') {\n      node.style.backgroundColor = String(value)\n    } else if (key === 'color') {\n      node.style.color = String(value)\n    } else {\n      // generic fallback\n      node.style[key as any] = typeof value === 'number' ? `${value}px` : String(value)\n    }\n  }\n}\n\nexport function createAnimations<A extends object>(animations: A): AnimationDriver<A> {\n  const reactionListeners = new WeakMap<any, Set<Function>>()\n\n  return {\n    animations,\n    usePresence,\n    ResetPresence,\n    inputStyle: 'css',\n    outputStyle: 'css',\n\n    useAnimatedNumber(initial): UniversalAnimatedNumber<Function> {\n      const [val, setVal] = React.useState(initial)\n      const finishTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n\n      return {\n        getInstance() {\n          return setVal\n        },\n        getValue() {\n          return val\n        },\n        setValue(next, config, onFinish) {\n          setVal(next)\n\n          // clear any pending finish callback from a previous setValue\n          if (finishTimerRef.current) {\n            clearTimeout(finishTimerRef.current)\n            finishTimerRef.current = null\n          }\n\n          if (onFinish) {\n            if (\n              !config ||\n              config.type === 'direct' ||\n              (config.type === 'timing' && config.duration === 0)\n            ) {\n              onFinish()\n            } else {\n              // estimate duration: use explicit duration, or fall back to\n              // default CSS transition duration for spring-type configs\n              const duration = config.type === 'timing' ? config.duration : 300\n              finishTimerRef.current = setTimeout(onFinish, duration)\n            }\n          }\n\n          // call reaction listeners with the new value\n          const listeners = reactionListeners.get(setVal)\n          if (listeners) {\n            listeners.forEach((listener) => listener(next))\n          }\n        },\n        stop() {\n          if (finishTimerRef.current) {\n            clearTimeout(finishTimerRef.current)\n            finishTimerRef.current = null\n          }\n        },\n      }\n    },\n\n    useAnimatedNumberReaction({ value }, onValue) {\n      React.useEffect(() => {\n        const instance = value.getInstance()\n        let queue = reactionListeners.get(instance)\n        if (!queue) {\n          const next = new Set<Function>()\n          reactionListeners.set(instance, next)\n          queue = next!\n        }\n        queue.add(onValue)\n        return () => {\n          queue?.delete(onValue)\n        }\n      }, [])\n    },\n\n    useAnimatedNumberStyle(val, getStyle) {\n      return getStyle(val.getValue())\n    },\n\n    useAnimatedNumbersStyle(vals, getStyle) {\n      return getStyle(...vals.map((v) => v.getValue()))\n    },\n\n    // @ts-ignore - styleState is added by createComponent\n    useAnimations: ({\n      props,\n      presence,\n      style,\n      componentState,\n      stateRef,\n      styleState,\n    }: any) => {\n      const isHydrating = componentState.unmounted === true\n      const isEntering = !!componentState.unmounted\n      const isExiting = presence?.[0] === false\n      const sendExitComplete = presence?.[1]\n\n      // Track if we just finished entering (transition from entering to not entering)\n      // This is needed because the CSS transition happens on the render AFTER t_unmounted is removed\n      const wasEnteringRef = React.useRef(isEntering)\n      const justFinishedEntering = wasEnteringRef.current && !isEntering\n      React.useEffect(() => {\n        wasEnteringRef.current = isEntering\n      })\n\n      // exit cycle guards to prevent stale/duplicate completion\n      const exitCycleIdRef = React.useRef(0)\n      const exitCompletedRef = React.useRef(false)\n      const wasExitingRef = React.useRef(false)\n      const exitInterruptedRef = React.useRef(false)\n      const sendExitCompleteRef = React.useRef(sendExitComplete)\n      const lastNonExitingStyleRef = React.useRef<Record<string, string>>({})\n      sendExitCompleteRef.current = sendExitComplete\n\n      // detect transition into/out of exiting state\n      const justStartedExiting = isExiting && !wasExitingRef.current\n      const justStoppedExiting = !isExiting && wasExitingRef.current\n\n      // start new exit cycle only on transition INTO exiting\n      if (justStartedExiting) {\n        exitCycleIdRef.current++\n        exitCompletedRef.current = false\n      }\n      // track interruptions so we know to force-restart transitions\n      if (justStoppedExiting) {\n        exitCycleIdRef.current++\n        exitInterruptedRef.current = true\n      }\n\n      // track previous exiting state\n      React.useEffect(() => {\n        wasExitingRef.current = isExiting\n      })\n\n      useIsomorphicLayoutEffect(() => {\n        const host = stateRef.current.host\n        if (isExiting || !host) return\n        const computedStyle = getComputedStyle(host as HTMLElement)\n        lastNonExitingStyleRef.current = {\n          opacity: computedStyle.opacity,\n        }\n      })\n\n      // use effectiveTransition computed by createComponent (single source of truth)\n      const effectiveTransition = styleState?.effectiveTransition ?? props.transition\n\n      // Normalize the transition prop to a consistent format\n      const normalized = normalizeTransition(effectiveTransition)\n\n      // Determine animation state and get effective animation\n      // Use 'enter' if we're entering OR if we just finished entering (transition is happening)\n      const animationState = isExiting\n        ? 'exit'\n        : isEntering || justFinishedEntering\n          ? 'enter'\n          : 'default'\n      const effectiveAnimationKey = getEffectiveAnimation(normalized, animationState)\n      const defaultAnimation = effectiveAnimationKey\n        ? animations[effectiveAnimationKey]\n        : null\n      const animatedProperties = getAnimatedProperties(normalized)\n\n      // Determine which properties to animate\n      // - animateOnly prop is an exclusive filter (only animate those properties)\n      // - per-property configs WITHOUT a default = only animate those specific properties\n      // - per-property configs WITH a default = per-property overrides + default for rest\n      const hasDefault =\n        normalized.default !== null ||\n        normalized.enter !== null ||\n        normalized.exit !== null\n      const hasPerPropertyConfigs = animatedProperties.length > 0\n\n      let keys: string[]\n      if (props.animateOnly) {\n        // animateOnly is explicit filter\n        keys = props.animateOnly\n      } else if (hasPerPropertyConfigs && !hasDefault) {\n        // object format without default: { opacity: '200ms' } = only animate opacity\n        keys = animatedProperties\n      } else if (hasPerPropertyConfigs && hasDefault) {\n        // array format or object with default: 'all' first, then per-property overrides\n        // CSS transition specificity: later declarations override earlier ones for the same property\n        keys = ['all', ...animatedProperties]\n      } else {\n        // simple string format: 'quick' = animate all\n        keys = ['all']\n      }\n\n      useIsomorphicLayoutEffect(() => {\n        const host = stateRef.current.host\n        if (!sendExitComplete || !isExiting || !host) return\n        const node = host as HTMLElement\n\n        // capture current cycle id for this effect\n        const cycleId = exitCycleIdRef.current\n\n        // helper to complete exit with guards\n        const completeExit = () => {\n          if (cycleId !== exitCycleIdRef.current) return\n          if (exitCompletedRef.current) return\n          exitCompletedRef.current = true\n          sendExitCompleteRef.current?.()\n        }\n\n        // if no properties to animate (animateOnly=[]), complete immediately\n        if (keys.length === 0) {\n          completeExit()\n          return\n        }\n\n        // Force transition restart for interrupted exits\n        // When an exit is interrupted and restarted, the element may already be at\n        // the exit style, so no CSS transition fires. We need to:\n        // 1. Reset to non-exit state\n        // 2. Force reflow\n        // 3. Re-apply exit state to trigger transition\n        let rafId: number | undefined\n        const wasInterrupted = exitInterruptedRef.current\n        // flag to ignore transitioncancel during reset (we intentionally cancel the old transition)\n        let ignoreCancelEvents = wasInterrupted\n        // get enter/exit styles for potential restart\n        const enterStyle = props.enterStyle as Record<string, unknown> | undefined\n        const exitStyle = props.exitStyle as Record<string, unknown> | undefined\n\n        // Build the exit transition string - needed for both normal and interrupted exits\n        const delayStr = normalized.delay ? ` ${normalized.delay}ms` : ''\n        const durationOverride = normalized.config?.duration\n        const exitTransitionString = keys\n          .map((key) => {\n            const propAnimation = normalized.properties[key]\n            let animationValue: string | null = null\n            if (typeof propAnimation === 'string') {\n              animationValue = animations[propAnimation]\n            } else if (\n              propAnimation &&\n              typeof propAnimation === 'object' &&\n              propAnimation.type\n            ) {\n              animationValue = animations[propAnimation.type]\n            } else if (defaultAnimation) {\n              animationValue = defaultAnimation\n            }\n            if (animationValue && durationOverride) {\n              animationValue = applyDurationOverride(animationValue, durationOverride)\n            }\n            return animationValue ? `${key} ${animationValue}${delayStr}` : null\n          })\n          .filter(Boolean)\n          .join(', ')\n\n        const getResetValue = (key: string) => {\n          if (key === 'opacity') {\n            return (\n              style?.opacity ??\n              props.opacity ??\n              lastNonExitingStyleRef.current.opacity ??\n              1\n            )\n          }\n          if (TRANSFORM_KEYS.includes(key as any)) {\n            return key === 'scale' || key === 'scaleX' || key === 'scaleY' ? 1 : 0\n          }\n          return enterStyle?.[key]\n        }\n\n        if (wasInterrupted) {\n          exitInterruptedRef.current = false\n          // disable transition, reset to enter state\n          node.style.transition = 'none'\n\n          // reset: apply active/open state for each exit property (not enterStyle,\n          // which may equal exitStyle — see comment in the normal exit path below)\n          if (exitStyle) {\n            const resetStyle: Record<string, unknown> = {}\n            for (const key of Object.keys(exitStyle)) {\n              const resetValue = getResetValue(key)\n              if (resetValue !== undefined) {\n                resetStyle[key] = resetValue\n              }\n            }\n            applyStylesToNode(node, resetStyle)\n          } else {\n            // fallback if no exitStyle defined\n            node.style.opacity = '1'\n            node.style.transform = 'none'\n          }\n\n          // force reflow\n          void node.offsetHeight\n        } else if (exitStyle) {\n          // For normal (non-interrupted) exits, we need to ensure the CSS transition is\n          // processed by the browser BEFORE the exitStyle takes effect. The issue is that\n          // React may have already applied exitStyle in the same render batch. To fix this:\n          // 1. Disable transition and reset to non-exit state\n          // 2. Force reflow so browser processes the reset\n          // 3. Use RAF to ensure we're in a new frame\n          // 4. Re-enable transition and apply exitStyle\n          // This mirrors the interrupted exit handling approach (which also uses RAF).\n          ignoreCancelEvents = true\n          node.style.transition = 'none'\n\n          // Reset to the active/open state (not enterStyle, which may equal exitStyle).\n          // enterStyle is the \"unmounted\" initial state and can share values with exitStyle\n          // (e.g., both have opacity: 0). resetting to enterStyle would mean no value change\n          // when exitStyle is applied, so the CSS transition wouldn't fire.\n          const resetStyle: Record<string, unknown> = {}\n          for (const key of Object.keys(exitStyle)) {\n            const resetValue = getResetValue(key)\n            if (resetValue !== undefined) {\n              resetStyle[key] = resetValue\n            }\n          }\n          applyStylesToNode(node, resetStyle)\n\n          // Force reflow\n          void node.offsetHeight\n\n          // Use RAF to ensure transition is applied in a new frame\n          rafId = requestAnimationFrame(() => {\n            if (cycleId !== exitCycleIdRef.current) return\n            // Re-enable transition\n            node.style.transition = exitTransitionString\n            // Force reflow to ensure transition is active\n            void node.offsetHeight\n            // Apply exit styles - this triggers the animation\n            applyStylesToNode(node, exitStyle)\n            // Re-enable cancel event handling\n            ignoreCancelEvents = false\n          })\n        }\n\n        /**\n         * Exit animation handling for Dialog/Modal components\n         *\n         * The Challenge: When users close dialogs (via Escape key or clicking outside),\n         * the element can disappear from the DOM before CSS transitions finish, which causes:\n         * 1. Dialogs to stick around on screen\n         * 2. Event handlers to stop working\n         *\n         * Fix: Calculate the MAXIMUM duration across all animated properties, not just\n         * the default. With animateOnly and per-property configs, different properties\n         * can have different durations, and we need to wait for the LONGEST one.\n         */\n\n        // calculate max duration across all animated properties\n        let maxDuration = defaultAnimation ? extractDuration(defaultAnimation) : 200\n\n        // check per-property animation durations using shared helper\n        const animationConfigs = getAnimationConfigsForKeys(\n          normalized,\n          animations as Record<string, string>,\n          keys,\n          defaultAnimation\n        )\n        for (const animationValue of animationConfigs.values()) {\n          if (animationValue) {\n            const duration = extractDuration(animationValue)\n            if (duration > maxDuration) {\n              maxDuration = duration\n            }\n          }\n        }\n\n        const delay = normalized.delay ?? 0\n        const fallbackTimeout = maxDuration + delay\n\n        const timeoutId = setTimeout(() => {\n          completeExit()\n        }, fallbackTimeout)\n\n        // track number of transitioning properties to wait for all to finish\n        // (each property fires its own transitionend event)\n        const transitioningProps = new Set(keys)\n        let completedCount = 0\n\n        const onFinishAnimation = (event: TransitionEvent) => {\n          // only count transitions on THIS element, not bubbled from children\n          if (event.target !== node) return\n\n          // map CSS property names to our key names\n          // e.g., transitionend fires with propertyName 'transform' for scale/x/y\n          const eventProp = event.propertyName\n          if (transitioningProps.has(eventProp) || eventProp === 'all') {\n            completedCount++\n            // wait for all properties to finish\n            if (completedCount >= transitioningProps.size) {\n              clearTimeout(timeoutId)\n              completeExit()\n            }\n          }\n        }\n\n        // on cancel, still complete (element is exiting and animation was interrupted)\n        // the guards prevent duplicate completion if this is a stale cycle\n        const onCancelAnimation = () => {\n          // ignore cancel events during reset phase (we intentionally cancel the old transition)\n          if (ignoreCancelEvents) return\n          clearTimeout(timeoutId)\n          completeExit()\n        }\n\n        node.addEventListener('transitionend', onFinishAnimation)\n        node.addEventListener('transitioncancel', onCancelAnimation)\n\n        // For interrupted exits, re-enable transition and re-apply exit styles\n        // This must happen AFTER listeners are set up so we catch the transitionend\n        if (wasInterrupted) {\n          rafId = requestAnimationFrame(() => {\n            if (cycleId !== exitCycleIdRef.current) return\n            // re-enable transition using the pre-built string\n            node.style.transition = exitTransitionString\n            // force reflow again\n            void node.offsetHeight\n            // now apply exit styles - this triggers the transition\n            applyStylesToNode(node, exitStyle)\n            // re-enable cancel event handling now that reset is complete\n            ignoreCancelEvents = false\n          })\n        }\n\n        return () => {\n          clearTimeout(timeoutId)\n          if (rafId !== undefined) cancelAnimationFrame(rafId)\n          node.removeEventListener('transitionend', onFinishAnimation)\n          node.removeEventListener('transitioncancel', onCancelAnimation)\n          // restore transition: the exit handling sets node.style.transition='none'\n          // directly on the DOM (bypassing React). if exit is interrupted (e.g. same-key\n          // re-entry in AnimatePresence), React won't re-apply its managed transition\n          // value because it hasn't changed in the virtual DOM. clearing the inline\n          // override lets React's value take effect again.\n          node.style.transition = ''\n        }\n      }, [isExiting])\n\n      // tamagui doesnt even use animation output during hydration\n      if (isHydrating) {\n        return null\n      }\n\n      // Check if we have any animation to apply\n      if (!hasNormalizedAnimation(normalized)) {\n        return null\n      }\n\n      if (Array.isArray(style.transform)) {\n        style.transform = transformsToString(style.transform)\n      }\n\n      // Build CSS transition string\n      // TODO: we disabled the transform transition, because it will create issue for inverse function and animate function\n      // for non layout transform properties either use animate function or find a workaround to do it with css\n      const delayStr = normalized.delay ? ` ${normalized.delay}ms` : ''\n      const durationOverride = normalized.config?.duration\n      style.transition = keys\n        .map((key) => {\n          // Check for property-specific animation, fall back to default\n          const propAnimation = normalized.properties[key]\n          let animationValue: string | null = null\n\n          if (typeof propAnimation === 'string') {\n            animationValue = animations[propAnimation]\n          } else if (\n            propAnimation &&\n            typeof propAnimation === 'object' &&\n            propAnimation.type\n          ) {\n            animationValue = animations[propAnimation.type]\n          } else if (defaultAnimation) {\n            animationValue = defaultAnimation\n          }\n\n          // Apply global duration override if specified\n          if (animationValue && durationOverride) {\n            animationValue = applyDurationOverride(animationValue, durationOverride)\n          }\n\n          return animationValue ? `${key} ${animationValue}${delayStr}` : null\n        })\n        .filter(Boolean)\n        .join(', ')\n\n      if (process.env.NODE_ENV === 'development' && props['debug'] === 'verbose') {\n        console.info('CSS animation', {\n          props,\n          animations,\n          normalized,\n          defaultAnimation,\n          style,\n          isEntering,\n          isExiting,\n        })\n      }\n\n      return { style, className: isEntering ? 't_unmounted' : '' }\n    },\n  }\n}\n\n// layout animations\n// useIsomorphicLayoutEffect(() => {\n//   if (!host || !props.layout) {\n//     return\n//   }\n//   // @ts-ignore\n//   const boundingBox = host?.getBoundingClientRect()\n//   if (isChanged(initialPositionRef.current, boundingBox)) {\n//     const transform = invert(\n//       host,\n//       boundingBox,\n//       initialPositionRef.current\n//     )\n\n//     animate({\n//       from: transform,\n//       to: { x: 0, y: 0, scaleX: 1, scaleY: 1 },\n//       duration: 1000,\n//       onUpdate: ({ x, y, scaleX, scaleY }) => {\n//         // @ts-ignore\n//         host.style.transform = `translate(${x}px, ${y}px) scaleX(${scaleX}) scaleY(${scaleY})`\n//         // TODO: handle childRef inverse scale\n//         //   childRef.current.style.transform = `scaleX(${1 / scaleX}) scaleY(${\n//         //     1 / scaleY\n//         //   })`\n//       },\n//       // TODO: extract ease-in from string and convert/map it to a cubicBezier array\n//       cubicBezier: [0, 1.38, 1, -0.41],\n//     })\n//   }\n//   initialPositionRef.current = boundingBox\n// })\n\n// style.transition = `${keys} ${animation}${\n//   props.layout ? ',width 0s, height 0s, margin 0s, padding 0s, transform' : ''\n// }`\n\n// const isChanged = (initialBox: any, finalBox: any) => {\n//   // we just mounted, so we don't have complete data yet\n//   if (!initialBox || !finalBox) return false\n\n//   // deep compare the two boxes\n//   return JSON.stringify(initialBox) !== JSON.stringify(finalBox)\n// }\n\n// const invert = (el, from, to) => {\n//   const { x: fromX, y: fromY, width: fromWidth, height: fromHeight } = from\n//   const { x, y, width, height } = to\n\n//   const transform = {\n//     x: x - fromX - (fromWidth - width) / 2,\n//     y: y - fromY - (fromHeight - height) / 2,\n//     scaleX: width / fromWidth,\n//     scaleY: height / fromHeight,\n//   }\n\n//   el.style.transform = `\n"
  ]
}