{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"animated-tooltip","type":"registry:ui","title":"AnimatedTooltip","description":"Presence stack of overlapping avatars with a thin, dimmed ring each that lights up on hover or focus while the avatar lifts, its neighbours part to make room, and a glass name card rises, leaning toward the pointer, as one line of light sweeps under the name","author":"fancy-ui <https://fancy-ui.rama.app>","categories":["feedback"],"registryDependencies":["https://fancy-ui.rama.app/r/internals.json","https://fancy-ui.rama.app/r/internals-dom.json","https://fancy-ui.rama.app/r/internals-motion.json","https://fancy-ui.rama.app/r/utils.json"],"files":[{"path":"react/src/components/animated-tooltip/AnimatedTooltip.tsx","content":"\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { CSSProperties, MouseEvent as ReactMouseEvent } from \"react\";\n\nimport { cn } from \"../../utils\";\nimport { useLiveRef } from \"../../internals/dom/use-live-ref\";\nimport { linear } from \"../../internals/motion/easing\";\nimport { useReducedMotion } from \"../../internals/motion/media-query\";\nimport { usePresence } from \"../../internals/motion/presence\";\nimport type { TransitionSpec } from \"../../internals/motion/transitions\";\n\nimport \"./animated-tooltip.css\";\n\nexport interface TooltipItem {\n\tid: number | string;\n\tname: string;\n\tdesignation: string;\n\timage: string;\n}\n\nexport interface AnimatedTooltipProps {\n\t/** Array of items to display */\n\titems: TooltipItem[];\n\t/** Additional CSS classes for the container */\n\tclassName?: string;\n\t/**\n\t * Tint of the presence ring and the light sweep under the name. Any CSS\n\t * colour (`\"#f5a97f\"`, `\"oklch(0.7 0.14 160)\"`, `\"var(--primary)\"`).\n\t * Leave unset for the default soft iridescent pair, which adapts to the\n\t * light and dark themes.\n\t */\n\taccent?: string;\n\t/** Avatar diameter in pixels. */\n\tsize?: number;\n}\n\ntype ItemId = number | string;\n\n/** The id the tooltip is published under, so the wrapper can point\n *  `aria-describedby` at it while it is shown. */\nfunction tooltipId(itemId: ItemId): string {\n\treturn `animated-tooltip-${itemId}`;\n}\n\n/**\n * The inline transform the tooltip positioner is drawn at. `mouseX` is the\n * pointer's offset from the hovered avatar's centre, normalised by half the\n * avatar size to [-1, 1]: the card leans up to 7deg and slides up to 14px\n * toward the pointer; reduced motion keeps it upright.\n */\nfunction transformFor(mouseX: number, size: number, reduced: boolean): string {\n\tconst lean = reduced ? 0 : Math.max(-1, Math.min(1, mouseX / (size / 2)));\n\tconst rotation = lean * 7;\n\tconst translation = lean * 14;\n\treturn `translateX(calc(-50% + ${translation}px)) rotate(${rotation}deg)`;\n}\n\n/** How far item `i` steps aside to make room for the active one. */\nfunction partOffset(i: number, activeIndex: number, reduced: boolean): number {\n\tif (activeIndex < 0 || reduced) return 0;\n\tconst d = i - activeIndex;\n\tconst distance = Math.abs(d);\n\tif (distance === 1) return Math.sign(d) * 6;\n\tif (distance === 2) return Math.sign(d) * 2;\n\treturn 0;\n}\n\ninterface SinkParams {\n\tentering: boolean;\n\tstill: boolean;\n}\n\n/**\n * Exit: a short settle downward (opacity only under reduced motion). The\n * source carries this as an exit-only transition, so the ENTER leg runs at\n * duration 0 — the card appears at rest and its entrance is the CSS\n * `at-rise` / `at-fade` keyframe, exactly as in the source. A reversal\n * mid-exit therefore snaps back to rest rather than replaying anything, which\n * is what an exit-only transition does when its block resumes.\n */\nfunction sink(_node: Element, params?: SinkParams): TransitionSpec {\n\tif (!params || params.entering) {\n\t\treturn { delay: 0, duration: 0, easing: linear, css: () => \"\" };\n\t}\n\tconst still = params.still;\n\treturn {\n\t\tdelay: 0,\n\t\tduration: still ? 120 : 160,\n\t\teasing: (t: number) => t * t,\n\t\tcss: (t: number) =>\n\t\t\tstill\n\t\t\t\t? `opacity: ${t};`\n\t\t\t\t: `opacity: ${t}; transform: translateY(${(1 - t) * 4}px) scale(${0.97 + 0.03 * t});`,\n\t};\n}\n\ninterface AvatarItemProps {\n\titem: TooltipItem;\n\tactive: boolean;\n\tshift: number;\n\treduced: boolean;\n\tonEnter: (itemId: ItemId, event: ReactMouseEvent<HTMLDivElement>) => void;\n\tonMove: (event: ReactMouseEvent<HTMLDivElement>) => void;\n\tonLeave: () => void;\n\tonFocusIn: (itemId: ItemId) => void;\n\tonTip: (itemId: ItemId, node: HTMLElement | null, previous: HTMLElement | null) => void;\n}\n\n/** One avatar and its conditional tooltip. A separate component because each\n *  item owns a presence clock (the mount/unmount timing the source's\n *  transition-aware conditional block owned natively). */\nfunction AvatarItem({\n\titem,\n\tactive,\n\tshift,\n\treduced,\n\tonEnter,\n\tonMove,\n\tonLeave,\n\tonFocusIn,\n\tonTip,\n}: AvatarItemProps) {\n\tconst presence = usePresence(active);\n\t// Rewritten every render, read at the instant a leg starts: the source's\n\t// exit reads the reduced-motion flag when the outro begins.\n\tconst cardRef = presence.register(sink, (entering) => ({ entering, still: reduced }));\n\n\t// The positioner is handed to the row, which alone writes its transform.\n\t// Block body, never a concise arrow: React 19 reads a returned value as a\n\t// cleanup function.\n\tconst tipNode = useRef<HTMLElement | null>(null);\n\tconst itemId = item.id;\n\tconst attachTip = useCallback(\n\t\t(node: HTMLElement | null) => {\n\t\t\tif (node) {\n\t\t\t\ttipNode.current = node;\n\t\t\t\tonTip(itemId, node, null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tonTip(itemId, null, tipNode.current);\n\t\t\ttipNode.current = null;\n\t\t},\n\t\t[itemId, onTip]\n\t);\n\n\treturn (\n\t\t<div\n\t\t\tclassName={cn(\"at-item group relative\", active && \"at-active\", shift !== 0 && \"at-parted\")}\n\t\t\tstyle={{ \"--_at-shift\": `${shift}px` } as CSSProperties}\n\t\t\tdata-active={active ? \"\" : undefined}\n\t\t\tdata-part={shift < 0 ? \"before\" : shift > 0 ? \"after\" : undefined}\n\t\t\tonMouseEnter={(event) => onEnter(item.id, event)}\n\t\t\tonMouseLeave={onLeave}\n\t\t\tonMouseMove={onMove}\n\t\t\tonFocus={() => onFocusIn(item.id)}\n\t\t\tonBlur={onLeave}\n\t\t\ttabIndex={0}\n\t\t\taria-describedby={active ? tooltipId(item.id) : undefined}\n\t\t>\n\t\t\t{/* Tooltip */}\n\t\t\t{presence.mounted && (\n\t\t\t\t<div\n\t\t\t\t\tref={attachTip}\n\t\t\t\t\tid={tooltipId(item.id)}\n\t\t\t\t\trole=\"tooltip\"\n\t\t\t\t\tclassName=\"at-tip pointer-events-none absolute left-1/2 z-50\"\n\t\t\t\t>\n\t\t\t\t\t<div ref={cardRef} className=\"at-card\">\n\t\t\t\t\t\t<span className=\"at-pointer\" aria-hidden=\"true\"></span>\n\t\t\t\t\t\t<div className=\"at-name\">{item.name}</div>\n\t\t\t\t\t\t<span className=\"at-rule\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t<span className=\"at-glint\"></span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<div className=\"at-role\">{item.designation}</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{/* Avatar: presence ring + photo */}\n\t\t\t<div className={cn(\"at-avatar\", active && !reduced && \"at-lifted\")}>\n\t\t\t\t<span className=\"at-glow\" aria-hidden=\"true\"></span>\n\t\t\t\t<span className=\"at-disc\" aria-hidden=\"true\"></span>\n\t\t\t\t<span className=\"at-ring\" aria-hidden=\"true\"></span>\n\t\t\t\t<img\n\t\t\t\t\tsrc={item.image}\n\t\t\t\t\talt={item.name}\n\t\t\t\t\tclassName=\"at-img relative !m-0 rounded-full object-cover object-top !p-0\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport function AnimatedTooltip({ items, className, accent, size = 56 }: AnimatedTooltipProps) {\n\tconst reduced = useReducedMotion();\n\n\tconst [hoveredId, setHoveredId] = useState<ItemId | null>(null);\n\n\t/**\n\t * The pointer offset, the hovered id as the handlers see it, and every\n\t * mounted tooltip positioner. Refs, never state: the source reads the\n\t * shared `mouseX` only INSIDE the active item's conditional block, so one\n\t * pointer sample rewrites one style attribute on one node. Holding it in\n\t * React state would re-render every avatar in the row on every mousemove.\n\t *\n\t * Writing only to the ACTIVE item's positioner is also what reproduces the\n\t * source's freeze: a leaving block is paused, so its tooltip keeps the\n\t * transform it was last drawn at while the pointer moves on — onto the\n\t * overlapping neighbour or off the row, where `mouseX` resets to 0.\n\t */\n\tconst mouseX = useRef(0);\n\tconst hovered = useRef<ItemId | null>(null);\n\tconst tips = useRef(new Map<ItemId, HTMLElement>());\n\tconst reducedRef = useLiveRef(reduced);\n\tconst sizeRef = useLiveRef(size);\n\n\tconst draw = useCallback(\n\t\t(itemId: ItemId): void => {\n\t\t\tconst node = tips.current.get(itemId);\n\t\t\tif (node)\n\t\t\t\tnode.style.transform = transformFor(mouseX.current, sizeRef.current, reducedRef.current);\n\t\t},\n\t\t[reducedRef, sizeRef]\n\t);\n\n\tconst onTip = useCallback(\n\t\t(itemId: ItemId, node: HTMLElement | null, previous: HTMLElement | null): void => {\n\t\t\tif (node) {\n\t\t\t\ttips.current.set(itemId, node);\n\t\t\t\t// A freshly mounted positioner carries no transform yet.\n\t\t\t\tdraw(itemId);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (tips.current.get(itemId) === previous) tips.current.delete(itemId);\n\t\t},\n\t\t[draw]\n\t);\n\n\tconst activate = useCallback(\n\t\t(itemId: ItemId): void => {\n\t\t\thovered.current = itemId;\n\t\t\tsetHoveredId(itemId);\n\t\t\t// A re-entry during the exit finds the positioner still mounted and\n\t\t\t// redraws it; a first entry finds none and `onTip` draws on mount.\n\t\t\tdraw(itemId);\n\t\t},\n\t\t[draw]\n\t);\n\n\tconst handleMouseEnter = useCallback(\n\t\t(itemId: ItemId, event: ReactMouseEvent<HTMLDivElement>): void => {\n\t\t\t// Reset mouseX first to prevent offset from previous item\n\t\t\tconst rect = event.currentTarget.getBoundingClientRect();\n\t\t\tmouseX.current = event.clientX - rect.left - rect.width / 2;\n\t\t\tactivate(itemId);\n\t\t},\n\t\t[activate]\n\t);\n\n\tconst handleMouseMove = useCallback(\n\t\t(event: ReactMouseEvent<HTMLDivElement>): void => {\n\t\t\tif (hovered.current === null) return;\n\t\t\tconst rect = event.currentTarget.getBoundingClientRect();\n\t\t\tmouseX.current = event.clientX - rect.left - rect.width / 2;\n\t\t\tdraw(hovered.current);\n\t\t},\n\t\t[draw]\n\t);\n\n\tconst handleLeave = useCallback((): void => {\n\t\thovered.current = null;\n\t\tmouseX.current = 0;\n\t\tsetHoveredId(null);\n\t}, []);\n\n\tconst handleFocusIn = useCallback(\n\t\t(itemId: ItemId): void => {\n\t\t\tmouseX.current = 0;\n\t\t\tactivate(itemId);\n\t\t},\n\t\t[activate]\n\t);\n\n\t// The source derives the lean from `size` and the reduced-motion flag, so\n\t// either changing redraws the active card.\n\tuseEffect(() => {\n\t\tif (hovered.current !== null) draw(hovered.current);\n\t}, [draw, reduced, size]);\n\n\t/** Index of the active item, or -1 when nothing is hovered/focused. */\n\tconst activeIndex = hoveredId === null ? -1 : items.findIndex((item) => item.id === hoveredId);\n\n\tconst rootStyle = {\n\t\t\"--_at-size\": `${size}px`,\n\t\t...(accent\n\t\t\t? {\n\t\t\t\t\t\"--at-accent\": accent,\n\t\t\t\t\t\"--at-accent-2\": `color-mix(in oklab, ${accent} 62%, white)`,\n\t\t\t\t}\n\t\t\t: {}),\n\t} as CSSProperties;\n\n\treturn (\n\t\t<div className={cn(\"at-root flex flex-row items-center\", className)} style={rootStyle}>\n\t\t\t{items.map((item, i) => (\n\t\t\t\t<AvatarItem\n\t\t\t\t\tkey={item.id}\n\t\t\t\t\titem={item}\n\t\t\t\t\tactive={hoveredId === item.id}\n\t\t\t\t\tshift={partOffset(i, activeIndex, reduced)}\n\t\t\t\t\treduced={reduced}\n\t\t\t\t\tonEnter={handleMouseEnter}\n\t\t\t\t\tonMove={handleMouseMove}\n\t\t\t\t\tonLeave={handleLeave}\n\t\t\t\t\tonFocusIn={handleFocusIn}\n\t\t\t\t\tonTip={onTip}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</div>\n\t);\n}\n","type":"registry:ui","target":"@ui/fancy/components/animated-tooltip/AnimatedTooltip.tsx"},{"path":"react/src/components/animated-tooltip/animated-tooltip.css","content":"/*\n * Every rule below is anchored on `.at-root`, the class the source's own root\n * element carries: the source's compiler scoped these selectors, plain CSS does\n * not, so the anchor keeps the `at-*` hooks from leaking into a consumer page.\n * Class names, custom properties, `@property` and keyframe names are identical\n * to the source's; they are the visual contract.\n */\n\n@property --at-angle {\n\tsyntax: \"<angle>\";\n\tinherits: false;\n\tinitial-value: 0deg;\n}\n\n.at-root {\n\t/* Public, class-themeable knobs with private fallbacks. */\n\t--_at-a: var(--at-accent, oklch(0.62 0.13 295));\n\t--_at-b: var(--at-accent-2, oklch(0.7 0.1 220));\n\t--_at-core: color-mix(in oklab, var(--_at-a) 45%, white);\n\t--_at-sep: var(--at-separator, var(--background, #ffffff));\n\t--_at-ease-out: cubic-bezier(0.16, 1, 0.3, 1);\n\t--_at-ease-rise: cubic-bezier(0.34, 1.32, 0.64, 1);\n\t--_at-ease-inout: cubic-bezier(0.4, 0, 0.2, 1);\n\t--_at-card-bg: rgba(255, 255, 255, 0.85);\n\t--_at-card-line: rgba(0, 0, 0, 0.08);\n\t--_at-card-hi: rgba(255, 255, 255, 0.9);\n\t--_at-card-shadow: 0 10px 28px -12px rgba(0, 0, 0, 0.28), 0 2px 6px -2px rgba(0, 0, 0, 0.1);\n\t--_at-name: #111113;\n\t--_at-role: rgba(0, 0, 0, 0.55);\n}\n\n.dark .at-root {\n\t--_at-a: var(--at-accent, oklch(0.78 0.1 295));\n\t--_at-b: var(--at-accent-2, oklch(0.83 0.08 210));\n\t--_at-card-bg: rgba(20, 20, 22, 0.85);\n\t--_at-card-line: rgba(255, 255, 255, 0.1);\n\t--_at-card-hi: rgba(255, 255, 255, 0.06);\n\t--_at-card-shadow: 0 14px 32px -12px rgba(0, 0, 0, 0.8), 0 2px 6px -2px rgba(0, 0, 0, 0.5);\n\t--_at-name: #f4f4f5;\n\t--_at-role: rgba(255, 255, 255, 0.55);\n}\n\n/* ---------- stack ---------- */\n\n.at-root .at-item {\n\toutline: none;\n\ttransform: translateX(var(--_at-shift, 0px));\n}\n\n.at-root .at-item:not(:last-child) {\n\tmargin-inline-end: calc(var(--_at-size) * -0.285);\n}\n\n.at-root .at-item.at-active {\n\tz-index: 30;\n}\n\n.at-root .at-avatar {\n\tposition: relative;\n\twidth: var(--_at-size);\n\theight: var(--_at-size);\n\tborder-radius: 9999px;\n\tisolation: isolate;\n\ttransform-origin: 50% 60%;\n}\n\n/* The hairline that separates stacked avatars, in the page colour. */\n.at-root .at-disc {\n\tposition: absolute;\n\tinset: 0;\n\tz-index: 1;\n\tborder-radius: 9999px;\n\tbackground: var(--_at-sep);\n\tbox-shadow: 0 0 0 2px var(--_at-sep);\n}\n\n.at-root .at-avatar.at-lifted {\n\ttransform: translateY(-4px) scale(1.08);\n}\n\n.at-root .at-img {\n\tposition: absolute;\n\tinset: 3px;\n\twidth: calc(100% - 6px);\n\theight: calc(100% - 6px);\n\tz-index: 3;\n}\n\n/* The lit ring is the focus indicator; forced-colors mode strips it, so\n   fall back to a system-coloured outline there. */\n@media (forced-colors: active) {\n\t.at-root .at-item:focus-visible .at-avatar {\n\t\toutline: 2px solid Highlight;\n\t\toutline-offset: 2px;\n\t}\n}\n\n/* ---------- presence ring ---------- */\n\n.at-root .at-ring,\n.at-root .at-glow {\n\tposition: absolute;\n\tborder-radius: 9999px;\n\tbackground: conic-gradient(\n\t\tfrom var(--at-angle),\n\t\tvar(--_at-a) 0deg,\n\t\tvar(--_at-b) 150deg,\n\t\tvar(--_at-core) 205deg,\n\t\tvar(--_at-a) 260deg,\n\t\tvar(--_at-a) 360deg\n\t);\n}\n\n.at-root .at-ring {\n\tinset: 0;\n\tz-index: 2;\n\tpadding: 2px;\n\t-webkit-mask:\n\t\tlinear-gradient(#000 0 0) content-box,\n\t\tlinear-gradient(#000 0 0);\n\t-webkit-mask-composite: xor;\n\tmask:\n\t\tlinear-gradient(#000 0 0) content-box exclude,\n\t\tlinear-gradient(#000 0 0);\n\topacity: 0.35;\n\ttransition: opacity 300ms var(--_at-ease-inout);\n}\n\n.at-root .at-glow {\n\tinset: -3px;\n\tz-index: 0;\n\tfilter: blur(7px);\n\topacity: 0;\n\ttransition: opacity 300ms var(--_at-ease-inout);\n}\n\n.at-root .at-active .at-ring {\n\topacity: 1;\n}\n\n.at-root .at-active .at-glow {\n\topacity: 0.55;\n}\n\n/* ---------- glass card ---------- */\n\n.at-root .at-tip {\n\tbottom: calc(100% + 12px);\n\ttransform-origin: 50% 100%;\n}\n\n.at-root .at-card {\n\tposition: relative;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 5px;\n\tpadding: 8px 14px 9px;\n\tborder-radius: 10px;\n\twhite-space: nowrap;\n\tbackground: var(--_at-card-bg);\n\tborder: 1px solid var(--_at-card-line);\n\tbox-shadow:\n\t\tinset 0 1px 0 var(--_at-card-hi),\n\t\tvar(--_at-card-shadow);\n\t-webkit-backdrop-filter: blur(12px) saturate(1.4);\n\tbackdrop-filter: blur(12px) saturate(1.4);\n\ttransform-origin: 50% 100%;\n\tanimation: at-fade 160ms var(--_at-ease-out) both;\n}\n\n.at-root .at-pointer {\n\tposition: absolute;\n\tleft: 50%;\n\tbottom: -5px;\n\twidth: 9px;\n\theight: 9px;\n\tmargin-left: -4.5px;\n\tbackground: var(--_at-card-bg);\n\tborder-right: 1px solid var(--_at-card-line);\n\tborder-bottom: 1px solid var(--_at-card-line);\n\tborder-bottom-right-radius: 2px;\n\ttransform: rotate(45deg);\n\tclip-path: polygon(100% 0, 100% 100%, 0 100%);\n\t-webkit-backdrop-filter: blur(12px);\n\tbackdrop-filter: blur(12px);\n}\n\n.at-root .at-name {\n\tfont-size: 0.8125rem;\n\tline-height: 1.1rem;\n\tfont-weight: 600;\n\tletter-spacing: -0.005em;\n\tcolor: var(--_at-name);\n}\n\n.at-root .at-role {\n\tfont-size: 0.6875rem;\n\tline-height: 0.9rem;\n\tcolor: var(--_at-role);\n}\n\n/* One thin line of light under the name: a faint resting hairline plus a\n   glint that sweeps across it once when the card opens. */\n.at-root .at-rule {\n\tposition: relative;\n\tdisplay: block;\n\twidth: 100%;\n\tmin-width: 64px;\n\theight: 7px;\n\tmargin: -3px 0;\n\toverflow: clip;\n\tbackground: linear-gradient(\n\t\t\t90deg,\n\t\t\ttransparent,\n\t\t\tcolor-mix(in oklab, var(--_at-a) 50%, transparent) 30%,\n\t\t\tcolor-mix(in oklab, var(--_at-b) 50%, transparent) 70%,\n\t\t\ttransparent\n\t\t)\n\t\tcenter / 100% 1px no-repeat;\n}\n\n.at-root .at-glint {\n\tdisplay: none;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n\t.at-root .at-item {\n\t\ttransition: transform 380ms var(--_at-ease-out);\n\t}\n\n\t.at-root .at-avatar {\n\t\ttransition: transform 420ms var(--_at-ease-out);\n\t}\n\n\t.at-root .at-ring,\n\t.at-root .at-glow {\n\t\ttransition:\n\t\t\topacity 300ms var(--_at-ease-inout),\n\t\t\t--at-angle 900ms var(--_at-ease-out);\n\t}\n\n\t.at-root .at-active .at-ring,\n\t.at-root .at-active .at-glow {\n\t\t--at-angle: 140deg;\n\t}\n\n\t.at-root .at-tip {\n\t\ttransition: transform 240ms var(--_at-ease-out);\n\t}\n\n\t.at-root .at-card {\n\t\tanimation: at-rise 260ms var(--_at-ease-rise) both;\n\t}\n\n\t.at-root .at-glint {\n\t\tdisplay: block;\n\t\tposition: absolute;\n\t\ttop: 3px;\n\t\tleft: 0;\n\t\twidth: 60%;\n\t\theight: 1px;\n\t\tborder-radius: 1px;\n\t\tbackground: linear-gradient(\n\t\t\t90deg,\n\t\t\ttransparent,\n\t\t\tvar(--_at-a) 25%,\n\t\t\tvar(--_at-core) 50%,\n\t\t\tvar(--_at-b) 75%,\n\t\t\ttransparent\n\t\t);\n\t\t/* Soft halo so a 1px line still reads as light. */\n\t\tfilter: drop-shadow(0 0 2px var(--_at-a)) drop-shadow(0 0 4px var(--_at-b));\n\t\ttransform: translateX(-110%);\n\t\tanimation: at-sweep 760ms var(--_at-ease-inout) 90ms both;\n\t}\n}\n\n@keyframes at-rise {\n\tfrom {\n\t\topacity: 0;\n\t\ttransform: translateY(6px) scale(0.94);\n\t}\n\tto {\n\t\topacity: 1;\n\t\ttransform: none;\n\t}\n}\n\n@keyframes at-fade {\n\tfrom {\n\t\topacity: 0;\n\t}\n\tto {\n\t\topacity: 1;\n\t}\n}\n\n@keyframes at-sweep {\n\tfrom {\n\t\ttransform: translateX(-110%);\n\t}\n\tto {\n\t\ttransform: translateX(180%);\n\t}\n}\n","type":"registry:ui","target":"@ui/fancy/components/animated-tooltip/animated-tooltip.css"},{"path":"react/src/components/animated-tooltip/index.ts","content":"\"use client\";\n\nexport { AnimatedTooltip } from \"./AnimatedTooltip\";\nexport type { AnimatedTooltipProps, TooltipItem } from \"./AnimatedTooltip\";\n","type":"registry:ui","target":"@ui/fancy/components/animated-tooltip/index.ts"}],"meta":{"license":"MIT","package":"fancy-ui-react","version":"0.7.0","source":"https://fancy-ui.rama.app/docs/components/animated-tooltip?framework=react"}}