furin-ui 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +230 -2
  3. package/dist/analog-gauge/AnalogGauge.css +119 -0
  4. package/dist/analog-gauge/AnalogGauge.d.ts +34 -0
  5. package/dist/analog-gauge/AnalogGauge.js +155 -0
  6. package/dist/ascii-text/AsciiText.css +8 -0
  7. package/dist/ascii-text/AsciiText.d.ts +28 -0
  8. package/dist/ascii-text/AsciiText.js +251 -0
  9. package/dist/aurora/Aurora.css +49 -0
  10. package/dist/aurora/Aurora.d.ts +26 -0
  11. package/dist/aurora/Aurora.js +310 -0
  12. package/dist/bento-light/BentoLight.css +105 -0
  13. package/dist/bento-light/BentoLight.d.ts +32 -0
  14. package/dist/bento-light/BentoLight.js +155 -0
  15. package/dist/blur-number/BlurNumber.css +43 -0
  16. package/dist/blur-number/BlurNumber.d.ts +34 -0
  17. package/dist/blur-number/BlurNumber.js +188 -0
  18. package/dist/border-beam/BorderBeam.css +32 -0
  19. package/dist/border-beam/BorderBeam.d.ts +36 -0
  20. package/dist/border-beam/BorderBeam.js +230 -0
  21. package/dist/campana/Campana.css +288 -0
  22. package/dist/campana/Campana.d.ts +29 -0
  23. package/dist/campana/Campana.js +450 -0
  24. package/dist/card-stack/CardStack.css +92 -0
  25. package/dist/card-stack/CardStack.d.ts +32 -0
  26. package/dist/card-stack/CardStack.js +276 -0
  27. package/dist/chrome-text/ChromeText.css +28 -0
  28. package/dist/chrome-text/ChromeText.d.ts +24 -0
  29. package/dist/chrome-text/ChromeText.js +454 -0
  30. package/dist/click-burst/ClickBurst.css +20 -0
  31. package/dist/click-burst/ClickBurst.d.ts +30 -0
  32. package/dist/click-burst/ClickBurst.js +210 -0
  33. package/dist/code-rain/CodeRain.css +25 -0
  34. package/dist/code-rain/CodeRain.d.ts +26 -0
  35. package/dist/code-rain/CodeRain.js +348 -0
  36. package/dist/confetti-button/ConfettiButton.css +35 -0
  37. package/dist/confetti-button/ConfettiButton.d.ts +28 -0
  38. package/dist/confetti-button/ConfettiButton.js +157 -0
  39. package/dist/copy-morph/CopyMorph.css +58 -0
  40. package/dist/copy-morph/CopyMorph.d.ts +26 -0
  41. package/dist/copy-morph/CopyMorph.js +150 -0
  42. package/dist/coverflow/Coverflow.css +145 -0
  43. package/dist/coverflow/Coverflow.d.ts +28 -0
  44. package/dist/coverflow/Coverflow.js +448 -0
  45. package/dist/crosshair/Crosshair.css +107 -0
  46. package/dist/crosshair/Crosshair.d.ts +28 -0
  47. package/dist/crosshair/Crosshair.js +125 -0
  48. package/dist/descifrar/Descifrar.css +45 -0
  49. package/dist/descifrar/Descifrar.d.ts +27 -0
  50. package/dist/descifrar/Descifrar.js +104 -0
  51. package/dist/dither-wave/DitherWave.css +14 -0
  52. package/dist/dither-wave/DitherWave.d.ts +24 -0
  53. package/dist/dither-wave/DitherWave.js +265 -0
  54. package/dist/dock/Dock.css +118 -0
  55. package/dist/dock/Dock.d.ts +37 -0
  56. package/dist/dock/Dock.js +242 -0
  57. package/dist/download-button/DownloadButton.css +65 -0
  58. package/dist/download-button/DownloadButton.d.ts +34 -0
  59. package/dist/download-button/DownloadButton.js +113 -0
  60. package/dist/elastic-slider/ElasticSlider.css +67 -0
  61. package/dist/elastic-slider/ElasticSlider.d.ts +38 -0
  62. package/dist/elastic-slider/ElasticSlider.js +237 -0
  63. package/dist/electric-border/ElectricBorder.css +34 -0
  64. package/dist/electric-border/ElectricBorder.d.ts +34 -0
  65. package/dist/electric-border/ElectricBorder.js +300 -0
  66. package/dist/estanque/Estanque.css +63 -0
  67. package/dist/estanque/Estanque.d.ts +26 -0
  68. package/dist/estanque/Estanque.js +569 -0
  69. package/dist/estilos.css +6068 -0
  70. package/dist/fireflies/Fireflies.css +23 -0
  71. package/dist/fireflies/Fireflies.d.ts +22 -0
  72. package/dist/fireflies/Fireflies.js +256 -0
  73. package/dist/folder-peek/FolderPeek.css +85 -0
  74. package/dist/folder-peek/FolderPeek.d.ts +24 -0
  75. package/dist/folder-peek/FolderPeek.js +18 -0
  76. package/dist/galaxy/Galaxy.css +20 -0
  77. package/dist/galaxy/Galaxy.d.ts +28 -0
  78. package/dist/galaxy/Galaxy.js +341 -0
  79. package/dist/glass-blobs/GlassBlobs.css +91 -0
  80. package/dist/glass-blobs/GlassBlobs.d.ts +28 -0
  81. package/dist/glass-blobs/GlassBlobs.js +50 -0
  82. package/dist/glass-button/GlassButton.css +102 -0
  83. package/dist/glass-button/GlassButton.d.ts +30 -0
  84. package/dist/glass-button/GlassButton.js +133 -0
  85. package/dist/glass-card/GlassCard.css +88 -0
  86. package/dist/glass-card/GlassCard.d.ts +30 -0
  87. package/dist/glass-card/GlassCard.js +174 -0
  88. package/dist/glass-segmented/GlassSegmented.css +93 -0
  89. package/dist/glass-segmented/GlassSegmented.d.ts +32 -0
  90. package/dist/glass-segmented/GlassSegmented.js +202 -0
  91. package/dist/glint-button/GlintButton.css +92 -0
  92. package/dist/glint-button/GlintButton.d.ts +26 -0
  93. package/dist/glint-button/GlintButton.js +52 -0
  94. package/dist/gold-foil-text/GoldFoilText.css +28 -0
  95. package/dist/gold-foil-text/GoldFoilText.d.ts +24 -0
  96. package/dist/gold-foil-text/GoldFoilText.js +463 -0
  97. package/dist/goo-cursor/GooCursor.css +15 -0
  98. package/dist/goo-cursor/GooCursor.d.ts +24 -0
  99. package/dist/goo-cursor/GooCursor.js +343 -0
  100. package/dist/gooey-tabs/GooeyTabs.css +38 -0
  101. package/dist/gooey-tabs/GooeyTabs.d.ts +34 -0
  102. package/dist/gooey-tabs/GooeyTabs.js +205 -0
  103. package/dist/grain-mesh/GrainMesh.css +18 -0
  104. package/dist/grain-mesh/GrainMesh.d.ts +22 -0
  105. package/dist/grain-mesh/GrainMesh.js +258 -0
  106. package/dist/halftone-field/HalftoneField.css +13 -0
  107. package/dist/halftone-field/HalftoneField.d.ts +26 -0
  108. package/dist/halftone-field/HalftoneField.js +172 -0
  109. package/dist/hilo-rojo/HiloRojo.css +32 -0
  110. package/dist/hilo-rojo/HiloRojo.d.ts +34 -0
  111. package/dist/hilo-rojo/HiloRojo.js +324 -0
  112. package/dist/hold-to-confirm/HoldToConfirm.css +111 -0
  113. package/dist/hold-to-confirm/HoldToConfirm.d.ts +34 -0
  114. package/dist/hold-to-confirm/HoldToConfirm.js +151 -0
  115. package/dist/holo-foil/HoloFoil.css +85 -0
  116. package/dist/holo-foil/HoloFoil.d.ts +24 -0
  117. package/dist/holo-foil/HoloFoil.js +88 -0
  118. package/dist/holograma/Holograma.css +276 -0
  119. package/dist/holograma/Holograma.d.ts +35 -0
  120. package/dist/holograma/Holograma.js +141 -0
  121. package/dist/iman/Iman.css +9 -0
  122. package/dist/iman/Iman.d.ts +21 -0
  123. package/dist/iman/Iman.js +112 -0
  124. package/dist/index.d.ts +184 -0
  125. package/dist/index.js +95 -0
  126. package/dist/ink-bloom/InkBloom.css +12 -0
  127. package/dist/ink-bloom/InkBloom.d.ts +20 -0
  128. package/dist/ink-bloom/InkBloom.js +353 -0
  129. package/dist/ink-trail/InkTrail.css +11 -0
  130. package/dist/ink-trail/InkTrail.d.ts +22 -0
  131. package/dist/ink-trail/InkTrail.js +322 -0
  132. package/dist/lava-lamp/LavaLamp.css +12 -0
  133. package/dist/lava-lamp/LavaLamp.d.ts +22 -0
  134. package/dist/lava-lamp/LavaLamp.js +334 -0
  135. package/dist/lens-cursor/LensCursor.css +95 -0
  136. package/dist/lens-cursor/LensCursor.d.ts +26 -0
  137. package/dist/lens-cursor/LensCursor.js +207 -0
  138. package/dist/liquid-chrome/LiquidChrome.css +30 -0
  139. package/dist/liquid-chrome/LiquidChrome.d.ts +22 -0
  140. package/dist/liquid-chrome/LiquidChrome.js +313 -0
  141. package/dist/liquid-fill/LiquidFill.css +73 -0
  142. package/dist/liquid-fill/LiquidFill.d.ts +22 -0
  143. package/dist/liquid-fill/LiquidFill.js +382 -0
  144. package/dist/magnetic-letters/MagneticLetters.css +18 -0
  145. package/dist/magnetic-letters/MagneticLetters.d.ts +26 -0
  146. package/dist/magnetic-letters/MagneticLetters.js +195 -0
  147. package/dist/marquee/Marquee.css +40 -0
  148. package/dist/marquee/Marquee.d.ts +28 -0
  149. package/dist/marquee/Marquee.js +131 -0
  150. package/dist/mask-reveal/MaskReveal.css +55 -0
  151. package/dist/mask-reveal/MaskReveal.d.ts +32 -0
  152. package/dist/mask-reveal/MaskReveal.js +56 -0
  153. package/dist/masonry-reveal/MasonryReveal.css +85 -0
  154. package/dist/masonry-reveal/MasonryReveal.d.ts +32 -0
  155. package/dist/masonry-reveal/MasonryReveal.js +297 -0
  156. package/dist/metal-button/MetalButton.css +177 -0
  157. package/dist/metal-button/MetalButton.d.ts +18 -0
  158. package/dist/metal-button/MetalButton.js +25 -0
  159. package/dist/metal-card/MetalCard.css +40 -0
  160. package/dist/metal-card/MetalCard.d.ts +30 -0
  161. package/dist/metal-card/MetalCard.js +574 -0
  162. package/dist/metal-knob/MetalKnob.css +174 -0
  163. package/dist/metal-knob/MetalKnob.d.ts +34 -0
  164. package/dist/metal-knob/MetalKnob.js +110 -0
  165. package/dist/morph-loader/MorphLoader.css +29 -0
  166. package/dist/morph-loader/MorphLoader.d.ts +27 -0
  167. package/dist/morph-loader/MorphLoader.js +105 -0
  168. package/dist/morph-text/MorphText.css +33 -0
  169. package/dist/morph-text/MorphText.d.ts +24 -0
  170. package/dist/morph-text/MorphText.js +129 -0
  171. package/dist/odometro/Odometro.css +67 -0
  172. package/dist/odometro/Odometro.d.ts +33 -0
  173. package/dist/odometro/Odometro.js +63 -0
  174. package/dist/orbit-gallery/OrbitGallery.css +110 -0
  175. package/dist/orbit-gallery/OrbitGallery.d.ts +28 -0
  176. package/dist/orbit-gallery/OrbitGallery.js +392 -0
  177. package/dist/orbit-text/OrbitText.css +19 -0
  178. package/dist/orbit-text/OrbitText.d.ts +26 -0
  179. package/dist/orbit-text/OrbitText.js +131 -0
  180. package/dist/outline-fill/OutlineFill.css +14 -0
  181. package/dist/outline-fill/OutlineFill.d.ts +26 -0
  182. package/dist/outline-fill/OutlineFill.js +428 -0
  183. package/dist/parallax-card/ParallaxCard.css +117 -0
  184. package/dist/parallax-card/ParallaxCard.d.ts +28 -0
  185. package/dist/parallax-card/ParallaxCard.js +105 -0
  186. package/dist/photo-stack/PhotoStack.css +52 -0
  187. package/dist/photo-stack/PhotoStack.d.ts +20 -0
  188. package/dist/photo-stack/PhotoStack.js +213 -0
  189. package/dist/pill-nav/PillNav.css +86 -0
  190. package/dist/pill-nav/PillNav.d.ts +40 -0
  191. package/dist/pill-nav/PillNav.js +155 -0
  192. package/dist/pixel-assemble/PixelAssemble.css +10 -0
  193. package/dist/pixel-assemble/PixelAssemble.d.ts +28 -0
  194. package/dist/pixel-assemble/PixelAssemble.js +312 -0
  195. package/dist/prism-light/PrismLight.css +63 -0
  196. package/dist/prism-light/PrismLight.d.ts +26 -0
  197. package/dist/prism-light/PrismLight.js +1006 -0
  198. package/dist/profile-foil/ProfileFoil.css +133 -0
  199. package/dist/profile-foil/ProfileFoil.d.ts +32 -0
  200. package/dist/profile-foil/ProfileFoil.js +94 -0
  201. package/dist/rafaga/Rafaga.css +46 -0
  202. package/dist/rafaga/Rafaga.d.ts +26 -0
  203. package/dist/rafaga/Rafaga.js +225 -0
  204. package/dist/reflector/Reflector.css +136 -0
  205. package/dist/reflector/Reflector.d.ts +35 -0
  206. package/dist/reflector/Reflector.js +118 -0
  207. package/dist/rejilla-viva/RejillaViva.css +34 -0
  208. package/dist/rejilla-viva/RejillaViva.d.ts +31 -0
  209. package/dist/rejilla-viva/RejillaViva.js +360 -0
  210. package/dist/retro-grid/RetroGrid.css +20 -0
  211. package/dist/retro-grid/RetroGrid.d.ts +26 -0
  212. package/dist/retro-grid/RetroGrid.js +249 -0
  213. package/dist/rgb-glitch-text/RGBGlitchText.css +29 -0
  214. package/dist/rgb-glitch-text/RGBGlitchText.d.ts +22 -0
  215. package/dist/rgb-glitch-text/RGBGlitchText.js +196 -0
  216. package/dist/ring-cursor/RingCursor.css +59 -0
  217. package/dist/ring-cursor/RingCursor.d.ts +41 -0
  218. package/dist/ring-cursor/RingCursor.js +197 -0
  219. package/dist/ripple-button/RippleButton.css +67 -0
  220. package/dist/ripple-button/RippleButton.d.ts +26 -0
  221. package/dist/ripple-button/RippleButton.js +65 -0
  222. package/dist/shimmer-sweep/ShimmerSweep.css +69 -0
  223. package/dist/shimmer-sweep/ShimmerSweep.d.ts +26 -0
  224. package/dist/shimmer-sweep/ShimmerSweep.js +46 -0
  225. package/dist/shine-card/ShineCard.css +199 -0
  226. package/dist/shine-card/ShineCard.d.ts +30 -0
  227. package/dist/shine-card/ShineCard.js +68 -0
  228. package/dist/silk-flow/SilkFlow.css +9 -0
  229. package/dist/silk-flow/SilkFlow.d.ts +22 -0
  230. package/dist/silk-flow/SilkFlow.js +211 -0
  231. package/dist/slide-to-confirm/SlideToConfirm.css +95 -0
  232. package/dist/slide-to-confirm/SlideToConfirm.d.ts +32 -0
  233. package/dist/slide-to-confirm/SlideToConfirm.js +208 -0
  234. package/dist/smoke-field/SmokeField.css +12 -0
  235. package/dist/smoke-field/SmokeField.d.ts +22 -0
  236. package/dist/smoke-field/SmokeField.js +330 -0
  237. package/dist/snowfall/Snowfall.css +23 -0
  238. package/dist/snowfall/Snowfall.d.ts +22 -0
  239. package/dist/snowfall/Snowfall.js +219 -0
  240. package/dist/spark-trail/SparkTrail.css +20 -0
  241. package/dist/spark-trail/SparkTrail.d.ts +28 -0
  242. package/dist/spark-trail/SparkTrail.js +250 -0
  243. package/dist/spring-accordion/SpringAccordion.css +55 -0
  244. package/dist/spring-accordion/SpringAccordion.d.ts +32 -0
  245. package/dist/spring-accordion/SpringAccordion.js +117 -0
  246. package/dist/spring-tooltip/SpringTooltip.css +47 -0
  247. package/dist/spring-tooltip/SpringTooltip.d.ts +29 -0
  248. package/dist/spring-tooltip/SpringTooltip.js +239 -0
  249. package/dist/stepper/Stepper.css +102 -0
  250. package/dist/stepper/Stepper.d.ts +44 -0
  251. package/dist/stepper/Stepper.js +48 -0
  252. package/dist/sticker-peel/StickerPeel.css +9 -0
  253. package/dist/sticker-peel/StickerPeel.d.ts +26 -0
  254. package/dist/sticker-peel/StickerPeel.js +336 -0
  255. package/dist/tablero/Tablero.css +135 -0
  256. package/dist/tablero/Tablero.d.ts +28 -0
  257. package/dist/tablero/Tablero.js +304 -0
  258. package/dist/ticket-tear/TicketTear.css +43 -0
  259. package/dist/ticket-tear/TicketTear.d.ts +38 -0
  260. package/dist/ticket-tear/TicketTear.js +258 -0
  261. package/dist/toast-stack/ToastStack.css +81 -0
  262. package/dist/toast-stack/ToastStack.d.ts +60 -0
  263. package/dist/toast-stack/ToastStack.js +141 -0
  264. package/dist/topo-lines/TopoLines.css +20 -0
  265. package/dist/topo-lines/TopoLines.d.ts +28 -0
  266. package/dist/topo-lines/TopoLines.js +372 -0
  267. package/dist/typewriter/Typewriter.css +49 -0
  268. package/dist/typewriter/Typewriter.d.ts +34 -0
  269. package/dist/typewriter/Typewriter.js +133 -0
  270. package/dist/typing-indicator/TypingIndicator.css +103 -0
  271. package/dist/typing-indicator/TypingIndicator.d.ts +24 -0
  272. package/dist/typing-indicator/TypingIndicator.js +47 -0
  273. package/dist/warp-stars/WarpStars.css +21 -0
  274. package/dist/warp-stars/WarpStars.d.ts +24 -0
  275. package/dist/warp-stars/WarpStars.js +242 -0
  276. package/dist/weight-wave/WeightWave.css +20 -0
  277. package/dist/weight-wave/WeightWave.d.ts +26 -0
  278. package/dist/weight-wave/WeightWave.js +175 -0
  279. package/dist/word-roller/WordRoller.css +46 -0
  280. package/dist/word-roller/WordRoller.d.ts +30 -0
  281. package/dist/word-roller/WordRoller.js +148 -0
  282. package/package.json +422 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marcelo Treviño Velázquez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,231 @@
1
- # Temporary Holding Version
1
+ # furin
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Animated components for React. Text that decrypts itself, rolling numbers, living backgrounds, cards with 3D tilt, buttons, cursors and loaders. Zero dependencies (just React), TypeScript included, production ready.
4
+
5
+ <!-- gen:resumen -->
6
+ **92 free components** in 10 categories: Text (19), Backgrounds (21), Cards (12), Buttons (9), Cursors (7), Navigation (11), Data (1), Galleries (4), Loaders (3), Interaction (5).
7
+ <!-- /gen:resumen -->
8
+
9
+ - Zero dependencies besides React.
10
+ - TypeScript types included (`.d.ts`).
11
+ - SSR-safe: they work in Next.js (App Router), Vite, Remix, Astro and any React project.
12
+ - They respect `prefers-reduced-motion` and clean everything up on unmount.
13
+ - MIT license: use them anywhere.
14
+
15
+ Catalog with live previews: **https://furin.dev/components/** · Documentación en español: **https://furin.dev/es/**
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm i furin-ui
21
+ ```
22
+
23
+ Also works with `pnpm add furin-ui`, `yarn add furin-ui` or `bun add furin-ui`.
24
+
25
+ ## Usage
26
+
27
+ ```tsx
28
+ import { Descifrar, Odometro, Aurora } from "furin-ui";
29
+
30
+ export default function Hero() {
31
+ return (
32
+ <Aurora style={{ minHeight: 360 }}>
33
+ <h1 style={{ fontSize: 48, color: "white" }}>
34
+ <Descifrar text="Hello, furin" />
35
+ </h1>
36
+ <Odometro value={1250} prefix="$" suffix=" USD" style={{ fontSize: 32, color: "white" }} />
37
+ </Aurora>
38
+ );
39
+ }
40
+ ```
41
+
42
+ Each component ships its own CSS and imports it for you; you don't need to add any stylesheet.
43
+
44
+ If you'd rather load only what you use, import per component:
45
+
46
+ ```tsx
47
+ import Descifrar from "furin-ui/descifrar";
48
+ import { Aurora } from "furin-ui/aurora";
49
+ ```
50
+
51
+ If you'd rather import all the CSS once (in your layout, for example), use:
52
+
53
+ ```tsx
54
+ import "furin-ui/estilos.css";
55
+ ```
56
+
57
+ ## Requirements
58
+
59
+ - React 18 or newer (and `react-dom`).
60
+ - Any bundler that can import `.css` files from `node_modules`: Next.js, Vite, Remix, Astro, webpack, Parcel, esbuild or Bun.
61
+ - ESM-only package. In the Next.js App Router the components already carry the `"use client"` directive.
62
+
63
+ ## Free components
64
+
65
+ Each link goes to the live preview, the live controls and the props table.
66
+
67
+ <!-- gen:componentes -->
68
+ ### Text (19)
69
+
70
+ | Component | What it does |
71
+ |---|---|
72
+ | [ASCII Text](https://furin.dev/components/ascii-text/) · `AsciiText` | Text drawn with ASCII characters. A lens around the cursor magnifies, rotates and scrambles them. |
73
+ | [Blur Number](https://furin.dev/components/blur-number/) · `BlurNumber` | A number whose digits roll to the new value with vertical motion blur, one after another. |
74
+ | [Chrome Text](https://furin.dev/components/chrome-text/) · `ChromeText` | Liquid chrome letters whose reflection follows the cursor. |
75
+ | [Decipher Text](https://furin.dev/components/descifrar/) · `Descifrar` | Text that appears by deciphering itself: random characters settle into place letter by letter. |
76
+ | [Gold Foil Text](https://furin.dev/components/gold-foil-text/) · `GoldFoilText` | Gold foil text with relief and a specular glint as you move the cursor. |
77
+ | [Gust](https://furin.dev/components/rafaga/) · `Rafaga` | A headline whose letters get swept away by the cursor's wind like leaves, then spring back into place. |
78
+ | [Magnetic Letters](https://furin.dev/components/magnetic-letters/) · `MagneticLetters` | Every letter is pulled toward the cursor by a bouncy spring, tinting and growing as it gets close. |
79
+ | [Mask Reveal](https://furin.dev/components/mask-reveal/) · `MaskReveal` | Staggered lines that slide up from behind a mask, like a product launch intro. |
80
+ | [Morph Text](https://furin.dev/components/morph-text/) · `MorphText` | Words melt into one another with a viscous blur-and-threshold transition and a liquid sheen. |
81
+ | [Odometer](https://furin.dev/components/odometro/) · `Odometro` | Numbers that roll digit by digit up to their value, like a mechanical odometer. |
82
+ | [Orbit Text](https://furin.dev/components/orbit-text/) · `OrbitText` | Text on a circle that turns slowly and speeds up, with inertia, when the cursor gets close. |
83
+ | [Outline Fill](https://furin.dev/components/outline-fill/) · `OutlineFill` | Outlined text that fills with liquid through a wave that starts where the cursor enters, and drains when it leaves. |
84
+ | [Pixel Assemble](https://furin.dev/components/pixel-assemble/) · `PixelAssemble` | Text assembles itself from a cloud of spring-driven particles; the cursor scatters them and they snap back on their own. |
85
+ | [RGB Glitch Text](https://furin.dev/components/rgb-glitch-text/) · `RGBGlitchText` | Clean text that occasionally breaks into short bursts: split RGB channels and shifted slices. |
86
+ | [Shimmer Sweep](https://furin.dev/components/shimmer-sweep/) · `ShimmerSweep` | A satin sheen sweeps across the text like light over aluminum. |
87
+ | [Split-Flap Board](https://furin.dev/components/tablero/) · `Tablero` | A board of flaps that flip in 3D, character by character, until the phrase forms, like at a train station. |
88
+ | [Typewriter](https://furin.dev/components/typewriter/) · `Typewriter` | Types, deletes and retypes phrases with a human rhythm, pauses on punctuation and a blinking cursor. |
89
+ | [Weight Wave](https://furin.dev/components/weight-wave/) · `WeightWave` | The weight of a variable font ripples with the cursor's proximity, letter by letter. |
90
+ | [Word Roller](https://furin.dev/components/word-roller/) · `WordRoller` | A word rolls vertically between options on a spring-loaded drum, and its pill changes width along with it. |
91
+
92
+ ### Backgrounds (21)
93
+
94
+ | Component | What it does |
95
+ |---|---|
96
+ | [Aurora](https://furin.dev/components/aurora/) · `Aurora` | A WebGL background of slowly rippling aurora curtains, in your own colors. |
97
+ | [Code Rain](https://furin.dev/components/code-rain/) · `CodeRain` | Columns of real JSX and TypeScript snippets falling across three depth planes, with a glowing head and syntax highlighting. |
98
+ | [Dither Wave](https://furin.dev/components/dither-wave/) · `DitherWave` | A relief of waves drawn with one-bit Bayer ordered dithering. The cursor leaves expanding rings. |
99
+ | [Fireflies](https://furin.dev/components/fireflies/) · `Fireflies` | Fireflies that drift at three depths and blink with a warm glow; they curiously approach the cursor and circle it. |
100
+ | [Galaxy](https://furin.dev/components/galaxy/) · `Galaxy` | A spiral galaxy of tens of thousands of particles with a glowing core that rotates slowly; the cursor tilts the view. |
101
+ | [Glass Blobs](https://furin.dev/components/glass-blobs/) · `GlassBlobs` | Blobs of color floating behind a sheet of frosted glass. |
102
+ | [Grain Mesh](https://furin.dev/components/grain-mesh/) · `GrainMesh` | A mesh gradient that floats and blends, with fine film grain; the cursor pulls on one of the blobs. |
103
+ | [Halftone Field](https://furin.dev/components/halftone-field/) · `HalftoneField` | A print-style halftone with two overprinted inks: the dots grow and shrink with a flowing field and with the cursor. |
104
+ | [Holo Foil](https://furin.dev/components/holo-foil/) · `HoloFoil` | A holographic background: an iridescent film, a fine diffraction grating and a sheen that shift with the cursor's angle. |
105
+ | [Ink Bloom](https://furin.dev/components/ink-bloom/) · `InkBloom` | Drops of ink that bloom in dark water with swirls and tendrils, one after another in a cycle. Tap to drop one; move the cursor to stir the water. |
106
+ | [Lava Lamp](https://furin.dev/components/lava-lamp/) · `LavaLamp` | A lava lamp with glass and warm light: the wax rises, stretches, merges with other blobs and splits apart. Your cursor warms it. |
107
+ | [Liquid Chrome](https://furin.dev/components/liquid-chrome/) · `LiquidChrome` | A chrome surface that ripples and reflects a luminous environment. |
108
+ | [Live Grid](https://furin.dev/components/rejilla-viva/) · `RejillaViva` | A grid of dots that part and light up around the cursor, on a spring. |
109
+ | [Prism Light](https://furin.dev/components/prism-light/) · `PrismLight` | A real 3D scene in WebGL2: a glass prism (beveled mesh with fresnel and refraction) on a reflective vinyl floor. A light orb follows your cursor and fires a volumetric beam; the spectrum fans out by Snell's law, one ray per color, and lands on the wall and floor with its caustic. The camera parallaxes with the cursor. |
110
+ | [Retro Grid](https://furin.dev/components/retro-grid/) · `RetroGrid` | A perspective grid that scrolls endlessly toward a horizon with a muted sun; the cursor shifts the horizon and holding down speeds it up. |
111
+ | [Ripple Pond](https://furin.dev/components/estanque/) · `Estanque` | A WebGL water surface: the cursor and raindrops leave ripples that cross, catch the light and warp a grid beneath. |
112
+ | [Silk Flow](https://furin.dev/components/silk-flow/) · `SilkFlow` | A silk fabric that ripples slowly, with an anisotropic thread sheen that shifts with the light and follows the cursor. |
113
+ | [Smoke Field](https://furin.dev/components/smoke-field/) · `SmokeField` | Volumetric smoke with studio lighting that rises endlessly; your cursor parts and swirls it, and the smoke fills the gap back in. |
114
+ | [Snowfall](https://furin.dev/components/snowfall/) · `Snowfall` | Snow in three layers of depth (far, mid and close bokeh) with a wind that blows toward wherever the cursor is. |
115
+ | [Topo Lines](https://furin.dev/components/topo-lines/) · `TopoLines` | A living topographic map: contour lines of a slowly deforming terrain, with labeled index contours; the cursor raises a hill. |
116
+ | [Warp Stars](https://furin.dev/components/warp-stars/) · `WarpStars` | Stars at warp speed with trails and depth; the cursor steers the vanishing point and holding down triggers the jump. |
117
+
118
+ ### Cards (12)
119
+
120
+ | Component | What it does |
121
+ |---|---|
122
+ | [Bento Light](https://furin.dev/components/bento-light/) · `BentoLight` | A six-cell bento grid with a light that jumps between cells, following the cursor (or on its own), and spills onto its neighbors. |
123
+ | [Border Beam](https://furin.dev/components/border-beam/) · `BorderBeam` | A beam of light with a tail, a bright head and a halo that travels around the outline of any content at constant speed. |
124
+ | [Card Stack](https://furin.dev/components/card-stack/) · `CardStack` | A deck of cards you shuffle by dragging the top one: inertia, rotation and a spring back to the bottom. Also works with click and keyboard. |
125
+ | [Electric Border](https://furin.dev/components/electric-border/) · `ElectricBorder` | Arcs of electricity that snake around the border, with local discharges, sparks and a flickering glow. |
126
+ | [Folder Peek](https://furin.dev/components/folder-peek/) · `FolderPeek` | A cardboard folder that opens on hover and lets its documents peek out with a soft bounce. |
127
+ | [Glass Card](https://furin.dev/components/glass-card/) · `GlassCard` | Liquid glass with refraction, a specular edge and a reflection that follows the cursor. |
128
+ | [Parallax Card](https://furin.dev/components/parallax-card/) · `ParallaxCard` | Real 3D layers (sky, stars, planet, ring, dunes, text and shine) that separate at different depths as the card tilts. |
129
+ | [Profile Foil](https://furin.dev/components/profile-foil/) · `ProfileFoil` | A profile card with a holographic foil: it tilts in 3D with the cursor and the iridescence shifts with the angle. |
130
+ | [Shine Card](https://furin.dev/components/shine-card/) · `ShineCard` | A satin sheen sweeps across the card as the cursor passes over it. |
131
+ | [Sticker Peel](https://furin.dev/components/sticker-peel/) · `StickerPeel` | A glossy die-cut sticker that peels from the corner following the cursor, with curvature, a paper back and a shadow. |
132
+ | [Ticket Tear](https://furin.dev/components/ticket-tear/) · `TicketTear` | A paper ticket with a perforation: drag the stub and it tears from top to bottom, with a fibrous edge and a shadow that grows as it peels away. |
133
+ | [Titanium Card](https://furin.dev/components/metal-card/) · `MetalCard` | A brushed-titanium card whose reflection shifts as it tilts. |
134
+
135
+ ### Buttons (9)
136
+
137
+ | Component | What it does |
138
+ |---|---|
139
+ | [Confetti Button](https://furin.dev/components/confetti-button/) · `ConfettiButton` | A button that fires physical confetti: paper pieces that spin in 3D, fall with air drag and flutter. |
140
+ | [Copy Morph](https://furin.dev/components/copy-morph/) · `CopyMorph` | A copy button where the icon outline morphs into a checkmark with a stroke and spring morph. It really copies the text with navigator.clipboard (with a fallback). |
141
+ | [Download Button](https://furin.dev/components/download-button/) · `DownloadButton` | A button that narrows into a progress bar, advances at an uneven pace like a real network, and ends in a checkmark. With href, it really downloads the file. |
142
+ | [Glass Button](https://furin.dev/components/glass-button/) · `GlassButton` | A liquid-glass button that refracts the background behind it. |
143
+ | [Glint Button](https://furin.dev/components/glint-button/) · `GlintButton` | A dark button with a glint that sweeps across it every so often and on hover, lighting up the fill, the border and the text at once. |
144
+ | [Hold to Confirm](https://furin.dev/components/hold-to-confirm/) · `HoldToConfirm` | Press and hold to confirm: a ring fills, the button shakes more and more, and it vibrates on completion. Works with mouse, touch or keyboard. |
145
+ | [Metal Button](https://furin.dev/components/metal-button/) · `MetalButton` | A polished metal button with a beveled edge and a reflection that follows the cursor. |
146
+ | [Ripple Button](https://furin.dev/components/ripple-button/) · `RippleButton` | A button whose ripple is born exactly where you click: a luminous wavefront with rings that sweeps out to the farthest corner. |
147
+ | [Slide to Confirm](https://furin.dev/components/slide-to-confirm/) · `SlideToConfirm` | A physics-driven handle you drag to the end to confirm; release early and it springs back with a bounce. Accessible as a slider. |
148
+
149
+ ### Cursors (7)
150
+
151
+ | Component | What it does |
152
+ |---|---|
153
+ | [Click Burst](https://furin.dev/components/click-burst/) · `ClickBurst` | Fine rays that burst out where you click: they travel outward, detach from their origin and leave a ring and tiny sparks. |
154
+ | [Crosshair](https://furin.dev/components/crosshair/) · `Crosshair` | A precision crosshair: edge-to-edge lines with a center gap, rulers with tick marks along the edges and live monospace coordinates. |
155
+ | [Goo Cursor](https://furin.dev/components/goo-cursor/) · `GooCursor` | A translucent jelly cursor with a specular highlight: it drags droplets behind it that stretch into a trail and melt back into it when you stop. |
156
+ | [Ink Trail](https://furin.dev/components/ink-trail/) · `InkTrail` | A glossy ink trail that follows the cursor, widens as if bleeding into paper, and frays into strands until it fades away. |
157
+ | [Lens Cursor](https://furin.dev/components/lens-cursor/) · `LensCursor` | A glass magnifier that enlarges whatever is underneath it. |
158
+ | [Ring Cursor](https://furin.dev/components/ring-cursor/) · `RingCursor` | A ring that follows the cursor with an elastic lag, stretches with speed and, over anything clickable, morphs into its outline like a soft magnet. |
159
+ | [Spark Trail](https://furin.dev/components/spark-trail/) · `SparkTrail` | The cursor sheds sparks with real physics: they inherit its momentum, fall under gravity, bounce on the floor and cool from white to ember. |
160
+
161
+ ### Navigation (11)
162
+
163
+ | Component | What it does |
164
+ |---|---|
165
+ | [Dock](https://furin.dev/components/dock/) · `Dock` | A bar of icons that magnify under the cursor, on glass. |
166
+ | [Elastic Slider](https://furin.dev/components/elastic-slider/) · `ElasticSlider` | A slider (role slider, full keyboard support) that stretches like rubber when dragged past its limits and snaps back with a bounce; the track squashes to preserve volume. |
167
+ | [Glass Segmented Control](https://furin.dev/components/glass-segmented/) · `GlassSegmented` | A glass segmented control with a lens that slides between options. |
168
+ | [Gooey Tabs](https://furin.dev/components/gooey-tabs/) · `GooeyTabs` | Accessible tabs whose selection is a blob of liquid: it stretches toward the new tab, thins at the neck and merges when it arrives. |
169
+ | [Marquee](https://furin.dev/components/marquee/) · `Marquee` | A seamless infinite marquee: the cursor slows it down and nudges it with inertia, rows run in opposite directions, and keyboard focus pauses it. |
170
+ | [Metal Knob](https://furin.dev/components/metal-knob/) · `MetalKnob` | A knurled metal knob you turn by dragging. |
171
+ | [Pill Nav](https://furin.dev/components/pill-nav/) · `PillNav` | Accessible tabs (tablist, arrow keys, Home and End) with a pill that slides on a spring: the leading edge arrives first and stretches the pill as it crosses. |
172
+ | [Spring Accordion](https://furin.dev/components/spring-accordion/) · `SpringAccordion` | An accessible accordion (aria-expanded, regions, arrow keys, Home and End) whose regions open on a real spring: the height bounces as it lands and the content comes in from blurry to sharp. |
173
+ | [Spring Tooltip](https://furin.dev/components/spring-tooltip/) · `SpringTooltip` | A tooltip (role tooltip) that follows the cursor on a spring and hangs from it like a tag: it tilts with speed and stretches a little as it moves. It also appears on keyboard focus. |
174
+ | [Stepper](https://furin.dev/components/stepper/) · `Stepper` | Steps with a progress line that fills from one node to the next; the new step lights up as the stroke arrives and the checkmark draws itself on completion. |
175
+ | [Toast Stack](https://furin.dev/components/toast-stack/) · `ToastStack` | Notifications stacked like cards: they fan out on hover or focus, pause while you read them, close on their own and are announced with aria-live. |
176
+
177
+ ### Data (1)
178
+
179
+ | Component | What it does |
180
+ |---|---|
181
+ | [Analog Gauge](https://furin.dev/components/analog-gauge/) · `AnalogGauge` | An analog gauge whose needle swings and settles on its value, with a printed dial and a red zone. |
182
+
183
+ ### Galleries (4)
184
+
185
+ | Component | What it does |
186
+ |---|---|
187
+ | [Coverflow](https://furin.dev/components/coverflow/) · `Coverflow` | Album covers in perspective with reflections: the center one faces you and the rest fan out. Arrows, drag with inertia, click and keyboard. |
188
+ | [Masonry Reveal](https://furin.dev/components/masonry-reveal/) · `MasonryReveal` | A column masonry grid that appears piece by piece as it scrolls into view, with a soft bounce and a flash; hovering a piece zooms it in and shows its title. |
189
+ | [Orbit Gallery](https://furin.dev/components/orbit-gallery/) · `OrbitGallery` | Cards with generated scenes on a 3D circular carousel: it spins on its own, drags with inertia, and a click brings a card to the front. |
190
+ | [Photo Stack](https://furin.dev/components/photo-stack/) · `PhotoStack` | A stack of printed photos that fans out when you hover; a click sends the top one to the back. |
191
+
192
+ ### Loaders (3)
193
+
194
+ | Component | What it does |
195
+ |---|---|
196
+ | [Liquid Fill](https://furin.dev/components/liquid-fill/) · `LiquidFill` | A glass flask that fills with liquid. |
197
+ | [Morph Loader](https://furin.dev/components/morph-loader/) · `MorphLoader` | A glossy gradient shape that smoothly morphs between circle, square and triangle, spinning and squashing slightly on each change. |
198
+ | [Typing Indicator](https://furin.dev/components/typing-indicator/) · `TypingIndicator` | A "typing…" bubble with three elastically bouncing dots that, in a loop, turns into the reply with a little spring. |
199
+
200
+ ### Interaction (5)
201
+
202
+ | Component | What it does |
203
+ |---|---|
204
+ | [Holo Card](https://furin.dev/components/holograma/) · `Holograma` | A card with 3D tilt and an iridescent holographic foil that follows the cursor. |
205
+ | [Magnet](https://furin.dev/components/iman/) · `Iman` | Wrap any element and it gets pulled toward the cursor with spring physics. |
206
+ | [Red Thread](https://furin.dev/components/hilo-rojo/) · `HiloRojo` | The red thread of fate: a thread with Verlet physics that follows the cursor, pulls taut against its anchor and swings. |
207
+ | [Spotlight Cards](https://furin.dev/components/reflector/) · `Reflector` | A grid of cards whose borders light up wherever the cursor passes, driven by a single shared light. |
208
+ | [Wind Chime](https://furin.dev/components/campana/) · `Campana` | A glass furin wind chime that sways with the cursor's breeze and rings when touched. It doubles as a notification bell with a counter. |
209
+ <!-- /gen:componentes -->
210
+
211
+ ## Alternative: shadcn
212
+
213
+ If you'd rather have the code inside your project so you can edit it, every free component also installs with the shadcn CLI:
214
+
215
+ ```bash
216
+ npx shadcn@latest add https://furin.dev/r/descifrar.json
217
+ ```
218
+
219
+ It copies the `.tsx` and its `.css` to `components/furin/descifrar/`.
220
+
221
+ ## Furin Pro
222
+
223
+ <!-- gen:pro -->
224
+ The other 136 components in the catalog are original Furin designs and are sold separately: **Furin Pro** ($150 USD, one-time payment) includes the ones that exist on the day you buy, and **Lifetime** ($199 USD) adds every new component. Both come with a commercial license, unlimited projects and a Claude Code kit. See them running (the preview and live controls are free): **https://furin.dev/components/?plan=pro**. Pricing: **https://furin.dev/pricing/**
225
+
226
+ Pro components aren't in this package; they're delivered with your license.
227
+ <!-- /gen:pro -->
228
+
229
+ ## License
230
+
231
+ MIT © 2026 Marcelo Treviño Velázquez
@@ -0,0 +1,119 @@
1
+ @property --fag-x { syntax: "<number>"; inherits: true; initial-value: 0.5; }
2
+ @property --fag-y { syntax: "<number>"; inherits: true; initial-value: 0.5; }
3
+
4
+ .furin-analog-gauge {
5
+ --fag-size: 320px;
6
+ --fag-accent: #ff6a2a;
7
+ position: relative;
8
+ width: var(--fag-size);
9
+ height: var(--fag-size);
10
+ border-radius: 50%;
11
+ isolation: isolate;
12
+ transition: --fag-x 0.5s cubic-bezier(0.2, 0.75, 0.25, 1), --fag-y 0.5s cubic-bezier(0.2, 0.75, 0.25, 1);
13
+ }
14
+
15
+ /* Bisel cromado: reflejos de estudio en un cono y cepillado radial muy fino. */
16
+ .furin-analog-gauge__bezel {
17
+ position: absolute;
18
+ inset: 0;
19
+ border-radius: 50%;
20
+ background: conic-gradient(from 205deg, #70767f 0%, #f5f7f9 7%, #454b53 16%, #bcc2ca 26%, #fcfdfd 35%, #5a6068 46%, #9ca3ad 57%, #f6f8fa 67%, #4a5058 79%, #adb3bc 90%, #70767f 100%);
21
+ box-shadow:
22
+ 0 0 0 1px rgba(0, 0, 0, 0.75),
23
+ 0 6px 10px rgba(0, 0, 0, 0.55),
24
+ 0 26px 36px -8px rgba(0, 0, 0, 0.85),
25
+ 0 40px 60px -26px rgba(190, 205, 225, 0.22);
26
+ }
27
+ .furin-analog-gauge__bezel::after {
28
+ content: "";
29
+ position: absolute;
30
+ inset: 0;
31
+ border-radius: 50%;
32
+ background: repeating-conic-gradient(from 0deg, rgba(255, 255, 255, 0.07) 0deg 0.45deg, rgba(0, 0, 0, 0.08) 0.45deg 0.9deg);
33
+ mix-blend-mode: overlay;
34
+ }
35
+ /* Chaflán interior: el mismo cromo con la luz invertida. */
36
+ .furin-analog-gauge__chamfer {
37
+ position: absolute;
38
+ inset: 3.6%;
39
+ border-radius: 50%;
40
+ background: conic-gradient(from 25deg, #2c3037 0%, #a9b0b9 9%, #20242a 20%, #7b828c 31%, #d9dee4 40%, #2a2e34 52%, #8d949e 63%, #cdd2d9 73%, #23272d 85%, #8a919b 94%, #2c3037 100%);
41
+ box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.55), inset 0 2px 3px rgba(255, 255, 255, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.18);
42
+ }
43
+ .furin-analog-gauge__dial {
44
+ position: absolute;
45
+ inset: 7.2%;
46
+ border-radius: 50%;
47
+ background:
48
+ repeating-conic-gradient(from 0deg, rgba(255, 255, 255, 0.018) 0deg 1deg, rgba(0, 0, 0, 0) 1deg 2deg),
49
+ radial-gradient(circle at 50% 34%, #2a2e36 0%, #14161a 58%, #08090b 100%);
50
+ box-shadow:
51
+ inset 0 7px 16px rgba(0, 0, 0, 0.9),
52
+ inset 0 -2px 8px rgba(255, 255, 255, 0.04),
53
+ 0 0 0 1px rgba(0, 0, 0, 0.8);
54
+ }
55
+
56
+ .furin-analog-gauge__svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
57
+ .furin-analog-gauge__tick { stroke: #c9ced6; stroke-width: 1.7; stroke-linecap: butt; }
58
+ .furin-analog-gauge__tick.is-major { stroke: #f1f3f6; stroke-width: 3.4; }
59
+ .furin-analog-gauge__tick.is-red { stroke: #ee4b50; }
60
+ .furin-analog-gauge__zone { fill: none; stroke: #d93a3f; stroke-width: 6; opacity: 0.92; filter: drop-shadow(0 0 5px rgba(238, 75, 80, 0.55)); }
61
+ .furin-analog-gauge__num { font: 600 18px / 1 "Geist", system-ui, -apple-system, "Segoe UI", sans-serif; fill: #eef0f3; font-variant-numeric: tabular-nums; }
62
+ .furin-analog-gauge__num.is-red { fill: #ff7a7e; }
63
+ .furin-analog-gauge__label { font: 600 12px / 1 ui-monospace, "JetBrains Mono", "SF Mono", Menlo, monospace; letter-spacing: 0.28em; text-transform: uppercase; fill: #8b929c; }
64
+ .furin-analog-gauge__unit { font: 700 20px / 1 "Geist", system-ui, sans-serif; letter-spacing: 0.06em; fill: #eef0f3; }
65
+ .furin-analog-gauge__tipL { fill: var(--fag-accent); }
66
+ .furin-analog-gauge__tipR { fill: color-mix(in srgb, var(--fag-accent) 52%, #000); }
67
+
68
+ /* Eje: casquillo de acero con la aguja pasando por debajo. */
69
+ .furin-analog-gauge__hub {
70
+ position: absolute;
71
+ left: 50%;
72
+ top: 50%;
73
+ width: 11.5%;
74
+ height: 11.5%;
75
+ transform: translate(-50%, -50%);
76
+ border-radius: 50%;
77
+ background:
78
+ radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0) 0 28%, rgba(0, 0, 0, 0.35) 30%, rgba(255, 255, 255, 0.35) 33%, rgba(255, 255, 255, 0) 38%),
79
+ radial-gradient(circle at 34% 28%, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0) 42%),
80
+ conic-gradient(from 40deg, #f1f4f7, #7d848e, #fafbfc, #5b626b, #eef1f4, #8a919b, #f1f4f7);
81
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.6), 0 5px 7px rgba(0, 0, 0, 0.6), inset 0 -3px 4px rgba(0, 0, 0, 0.3);
82
+ }
83
+
84
+ /* Cristal: reflejo curvo arriba, filo de luz abajo y una veta que se desplaza con el cursor. */
85
+ .furin-analog-gauge__glass {
86
+ position: absolute;
87
+ inset: 7.2%;
88
+ border-radius: 50%;
89
+ overflow: hidden;
90
+ pointer-events: none;
91
+ background:
92
+ radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0) 70%, rgba(255, 255, 255, 0.07) 96%, rgba(255, 255, 255, 0.2) 100%),
93
+ radial-gradient(circle at 78% 104%, rgba(255, 255, 255, 0.14) 0%, rgba(255, 255, 255, 0) 42%);
94
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
95
+ }
96
+ .furin-analog-gauge__glare {
97
+ position: absolute;
98
+ left: 9%;
99
+ right: 9%;
100
+ top: 3%;
101
+ height: 46%;
102
+ border-radius: 50% 50% 46% 46% / 62% 62% 38% 38%;
103
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.3) 0%, rgba(255, 255, 255, 0.1) 55%, rgba(255, 255, 255, 0.015) 100%);
104
+ transform: translate(calc((var(--fag-x) - 0.5) * -16px), calc((var(--fag-y) - 0.5) * -10px));
105
+ }
106
+ .furin-analog-gauge__streak {
107
+ position: absolute;
108
+ top: -10%;
109
+ bottom: -10%;
110
+ width: 14%;
111
+ left: calc(8% + var(--fag-x) * 74%);
112
+ background: linear-gradient(90deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));
113
+ transform: skewX(-18deg);
114
+ filter: blur(2px);
115
+ }
116
+
117
+ @media (prefers-reduced-motion: reduce) {
118
+ .furin-analog-gauge { transition: none; }
119
+ }
@@ -0,0 +1,34 @@
1
+ import type { CSSProperties } from "react";
2
+ export interface AnalogGaugeProps {
3
+ /** Valor que marca la aguja. */
4
+ value?: number;
5
+ /** Valor al inicio de la escala. */
6
+ min?: number;
7
+ /** Valor al final de la escala. */
8
+ max?: number;
9
+ /** Rótulo impreso en la carátula. */
10
+ label?: string;
11
+ /** Unidad impresa en la carátula. */
12
+ unit?: string;
13
+ /** Marcas grandes con número. */
14
+ ticks?: number;
15
+ /** Valor donde empieza la zona roja (usa el máximo para quitarla). */
16
+ redline?: number;
17
+ /** Diámetro del medidor, en px. */
18
+ size?: number;
19
+ /** Cuánto se pasa y oscila la aguja antes de asentarse (0 a 1). */
20
+ bounce?: number;
21
+ /** Color pintado en la punta de la aguja. */
22
+ accent?: string;
23
+ /** Cambia el valor solo cada pocos segundos (útil en demos). */
24
+ wander?: boolean;
25
+ /** Segundos entre cambios cuando wander está activo. */
26
+ interval?: number;
27
+ /** Clase extra para el contenedor. */
28
+ className?: string;
29
+ /** Estilos extra para el contenedor. */
30
+ style?: CSSProperties;
31
+ }
32
+ /** Medidor analógico: bisel cromado, carátula con marcas y números, aguja de acero con física de resorte (se pasa y se asienta) y cristal con reflejo. */
33
+ export declare function AnalogGauge({ value, min, max, label, unit, ticks, redline, size, bounce, accent, wander, interval, className, style, }: AnalogGaugeProps): import("react").JSX.Element;
34
+ export default AnalogGauge;
@@ -0,0 +1,155 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useId, useRef, useState } from "react";
4
+ import "./AnalogGauge.css";
5
+ const BARRIDO = 240; // grados que recorre la aguja
6
+ const C = 200; // centro del lienzo SVG (viewBox 400)
7
+ const SUBDIV = 5;
8
+ const limitar = (v, a, b) => Math.min(b, Math.max(a, v));
9
+ const rad = (g) => (g * Math.PI) / 180;
10
+ const polar = (r, grados) => ({ x: C + r * Math.sin(rad(grados)), y: C - r * Math.cos(rad(grados)) });
11
+ /** Medidor analógico: bisel cromado, carátula con marcas y números, aguja de acero con física de resorte (se pasa y se asienta) y cristal con reflejo. */
12
+ export function AnalogGauge({ value = 72, min = 0, max = 100, label = "Pressure", unit = "PSI", ticks = 11, redline = 85, size = 320, bounce = 0.55, accent = "#ff6a2a", wander = false, interval = 2.8, className = "", style, }) {
13
+ const uid = useId().replace(/[^a-zA-Z0-9]/g, "");
14
+ const [auto, setAuto] = useState(null);
15
+ const actual = limitar(auto ?? value, min, max);
16
+ const rango = max - min || 1;
17
+ const fraccion = (v) => limitar((v - min) / rango, 0, 1);
18
+ const raiz = useRef(null);
19
+ const agujaRef = useRef(null);
20
+ const sombraRef = useRef(null);
21
+ const fis = useRef({ f: 0, v: 0, meta: 0, raf: 0, t: 0, visible: true, quieto: false });
22
+ const zeta = useRef(0.5);
23
+ zeta.current = 0.9 - 0.7 * limitar(bounce, 0, 1);
24
+ const pintar = (f) => {
25
+ // f puede pasarse un poco de 0..1 por el rebote: la aguja gira libre, sin tope.
26
+ const g = (-BARRIDO / 2 + f * BARRIDO).toFixed(3);
27
+ agujaRef.current?.setAttribute("transform", `rotate(${g} ${C} ${C})`);
28
+ sombraRef.current?.setAttribute("transform", `rotate(${g} ${C} ${C})`);
29
+ };
30
+ const correr = () => {
31
+ const s = fis.current;
32
+ if (s.raf || !s.visible)
33
+ return;
34
+ s.t = performance.now();
35
+ const paso = (ahora) => {
36
+ let dt = Math.min(0.04, Math.max(0, (ahora - s.t) / 1000));
37
+ s.t = ahora;
38
+ const w = 2 * Math.PI * 1.15;
39
+ while (dt > 0) {
40
+ const h = Math.min(dt, 1 / 240);
41
+ dt -= h;
42
+ s.v += (w * w * (s.meta - s.f) - 2 * zeta.current * w * s.v) * h;
43
+ s.f += s.v * h;
44
+ }
45
+ pintar(s.f);
46
+ if (Math.abs(s.f - s.meta) < 0.0004 && Math.abs(s.v) < 0.004) {
47
+ s.f = s.meta;
48
+ s.v = 0;
49
+ s.raf = 0;
50
+ pintar(s.f);
51
+ return;
52
+ }
53
+ if (!s.visible) {
54
+ s.raf = 0;
55
+ return;
56
+ }
57
+ s.raf = requestAnimationFrame(paso);
58
+ };
59
+ s.raf = requestAnimationFrame(paso);
60
+ };
61
+ // Entrada: la aguja parte del mínimo y se asienta en su valor. Con movimiento reducido va directo.
62
+ useEffect(() => {
63
+ const s = fis.current;
64
+ s.quieto = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
65
+ s.meta = fraccion(actual);
66
+ if (s.quieto) {
67
+ cancelAnimationFrame(s.raf);
68
+ s.raf = 0;
69
+ s.f = s.meta;
70
+ s.v = 0;
71
+ pintar(s.f);
72
+ return;
73
+ }
74
+ correr();
75
+ // eslint-disable-next-line react-hooks/exhaustive-deps
76
+ }, [actual, min, max]);
77
+ // Duerme fuera de pantalla.
78
+ useEffect(() => {
79
+ const el = raiz.current;
80
+ if (!el)
81
+ return;
82
+ const io = new IntersectionObserver(([e]) => {
83
+ fis.current.visible = e.isIntersecting;
84
+ if (e.isIntersecting && Math.abs(fis.current.f - fis.current.meta) > 0.0004)
85
+ correr();
86
+ });
87
+ io.observe(el);
88
+ return () => { io.disconnect(); cancelAnimationFrame(fis.current.raf); fis.current.raf = 0; };
89
+ // eslint-disable-next-line react-hooks/exhaustive-deps
90
+ }, []);
91
+ // Modo demo: nuevos valores al azar cada cierto tiempo, solo si se ve.
92
+ useEffect(() => {
93
+ if (!wander || window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
94
+ setAuto(null);
95
+ return;
96
+ }
97
+ let id = 0;
98
+ let previo = fraccion(actual);
99
+ const siguiente = () => {
100
+ if (fis.current.visible) {
101
+ let n = previo;
102
+ for (let i = 0; i < 6 && Math.abs(n - previo) < 0.28; i++)
103
+ n = 0.08 + Math.random() * 0.88;
104
+ previo = n;
105
+ setAuto(min + n * rango);
106
+ }
107
+ id = window.setTimeout(siguiente, Math.max(0.8, interval) * 1000);
108
+ };
109
+ id = window.setTimeout(siguiente, 1800);
110
+ return () => window.clearTimeout(id);
111
+ // eslint-disable-next-line react-hooks/exhaustive-deps
112
+ }, [wander, interval, min, max]);
113
+ const alMover = (e) => {
114
+ const r = e.currentTarget.getBoundingClientRect();
115
+ e.currentTarget.style.setProperty("--fag-x", limitar((e.clientX - r.left) / r.width, 0, 1).toFixed(3));
116
+ e.currentTarget.style.setProperty("--fag-y", limitar((e.clientY - r.top) / r.height, 0, 1).toFixed(3));
117
+ };
118
+ const alSalir = (e) => {
119
+ e.currentTarget.style.removeProperty("--fag-x");
120
+ e.currentTarget.style.removeProperty("--fag-y");
121
+ };
122
+ // Marcas: grandes con número y pequeñas entre ellas; las de la zona roja se pintan de rojo.
123
+ const segmentos = Math.max(2, ticks - 1);
124
+ const total = segmentos * SUBDIV;
125
+ const zonaRoja = redline < max;
126
+ const fRoja = fraccion(redline);
127
+ const marcas = Array.from({ length: total + 1 }, (_, m) => {
128
+ const f = m / total;
129
+ const grande = m % SUBDIV === 0;
130
+ const a = -BARRIDO / 2 + f * BARRIDO;
131
+ const p1 = polar(160, a);
132
+ const p2 = polar(grande ? 140 : 150, a);
133
+ return { ...p1, x2: p2.x, y2: p2.y, grande, roja: zonaRoja && f >= fRoja - 1e-6 };
134
+ });
135
+ const numeros = Array.from({ length: segmentos + 1 }, (_, i) => {
136
+ const f = i / segmentos;
137
+ const p = polar(116, -BARRIDO / 2 + f * BARRIDO);
138
+ const v = min + f * rango;
139
+ return { ...p, texto: String(Number(v.toFixed(1))), roja: zonaRoja && f >= fRoja - 1e-6 };
140
+ });
141
+ const arco = (() => {
142
+ if (!zonaRoja)
143
+ return "";
144
+ const a0 = -BARRIDO / 2 + fRoja * BARRIDO;
145
+ const a1 = BARRIDO / 2;
146
+ const p0 = polar(166, a0);
147
+ const p1 = polar(166, a1);
148
+ return `M ${p0.x.toFixed(2)} ${p0.y.toFixed(2)} A 166 166 0 ${a1 - a0 > 180 ? 1 : 0} 1 ${p1.x.toFixed(2)} ${p1.y.toFixed(2)}`;
149
+ })();
150
+ const f0 = -BARRIDO / 2;
151
+ const aguja = "M198.7 52 L192.6 200 L195.4 246 L204.6 246 L207.4 200 L201.3 52 Z";
152
+ const vars = { "--fag-size": `${size}px`, "--fag-accent": accent };
153
+ return (_jsxs("div", { ref: raiz, className: `furin-analog-gauge ${className}`, style: { ...vars, ...style }, role: "meter", "aria-label": label, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": Number(actual.toFixed(2)), "aria-valuetext": `${Number(actual.toFixed(1))} ${unit}`, onPointerMove: alMover, onPointerLeave: alSalir, children: [_jsx("div", { className: "furin-analog-gauge__bezel" }), _jsx("div", { className: "furin-analog-gauge__chamfer" }), _jsx("div", { className: "furin-analog-gauge__dial" }), _jsxs("svg", { className: "furin-analog-gauge__svg", viewBox: "0 0 400 400", "aria-hidden": "true", children: [_jsxs("defs", { children: [_jsx("filter", { id: `${uid}b`, x: "-60%", y: "-30%", width: "220%", height: "160%", children: _jsx("feGaussianBlur", { stdDeviation: "3.4" }) }), _jsxs("linearGradient", { id: `${uid}l`, x1: "0", x2: "1", y1: "0", y2: "0", children: [_jsx("stop", { offset: "0", stopColor: "#ffffff" }), _jsx("stop", { offset: "0.55", stopColor: "#d3d8df" }), _jsx("stop", { offset: "1", stopColor: "#9aa1ab" })] }), _jsxs("linearGradient", { id: `${uid}r`, x1: "0", x2: "1", y1: "0", y2: "0", children: [_jsx("stop", { offset: "0", stopColor: "#8c939d" }), _jsx("stop", { offset: "0.6", stopColor: "#525861" }), _jsx("stop", { offset: "1", stopColor: "#2b2f36" })] }), _jsx("clipPath", { id: `${uid}t`, children: _jsx("rect", { x: "150", y: "40", width: "100", height: "66" }) })] }), arco ? _jsx("path", { d: arco, className: "furin-analog-gauge__zone" }) : null, marcas.map((m, i) => (_jsx("line", { x1: m.x, y1: m.y, x2: m.x2, y2: m.y2, className: `furin-analog-gauge__tick${m.grande ? " is-major" : ""}${m.roja ? " is-red" : ""}` }, i))), numeros.map((n, i) => (_jsx("text", { x: n.x, y: n.y, textAnchor: "middle", dominantBaseline: "central", className: `furin-analog-gauge__num${n.roja ? " is-red" : ""}`, children: n.texto }, i))), _jsx("text", { x: C, y: "262", textAnchor: "middle", className: "furin-analog-gauge__label", children: label }), _jsx("text", { x: C, y: "292", textAnchor: "middle", className: "furin-analog-gauge__unit", children: unit }), _jsx("g", { transform: "translate(7 11)", opacity: "0.65", children: _jsx("g", { ref: sombraRef, transform: `rotate(${f0} ${C} ${C})`, filter: `url(#${uid}b)`, children: _jsx("path", { d: aguja, fill: "#000" }) }) }), _jsxs("g", { ref: agujaRef, transform: `rotate(${f0} ${C} ${C})`, children: [_jsx("path", { d: "M200 52 L198.7 52 L192.6 200 L195.4 246 L200 246 Z", fill: `url(#${uid}l)` }), _jsx("path", { d: "M200 52 L201.3 52 L207.4 200 L204.6 246 L200 246 Z", fill: `url(#${uid}r)` }), _jsxs("g", { clipPath: `url(#${uid}t)`, children: [_jsx("path", { d: "M200 52 L198.7 52 L192.6 200 L200 200 Z", className: "furin-analog-gauge__tipL" }), _jsx("path", { d: "M200 52 L201.3 52 L207.4 200 L200 200 Z", className: "furin-analog-gauge__tipR" })] }), _jsx("path", { d: "M200 56 L200 244", stroke: "rgba(255,255,255,0.6)", strokeWidth: "0.8" })] })] }), _jsx("div", { className: "furin-analog-gauge__hub" }), _jsxs("div", { className: "furin-analog-gauge__glass", children: [_jsx("span", { className: "furin-analog-gauge__glare" }), _jsx("span", { className: "furin-analog-gauge__streak" })] })] }));
154
+ }
155
+ export default AnalogGauge;
@@ -0,0 +1,8 @@
1
+ .furin-ascii-text {
2
+ position: relative;
3
+ display: block;
4
+ width: min(100%, 520px);
5
+ user-select: none;
6
+ }
7
+ .furin-ascii-text__lienzo { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
8
+ .furin-ascii-text__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
@@ -0,0 +1,28 @@
1
+ import type { CSSProperties } from "react";
2
+ export interface AsciiTextProps {
3
+ /** El texto que se dibuja con caracteres (acepta saltos de línea). */
4
+ children?: string;
5
+ /** Tamaño máximo de la letra del texto, en px (se reduce solo si no cabe). */
6
+ size?: number;
7
+ /** Tamaño de cada carácter ASCII, en px: más chico, más detalle. */
8
+ cell?: number;
9
+ /** Alto del lienzo, en px. */
10
+ height?: number;
11
+ /** Radio de la lente del cursor, en px. */
12
+ radius?: number;
13
+ /** Caracteres de menor a mayor densidad. */
14
+ charset?: string;
15
+ /** Cuando no hay cursor, una lente pasea sola por el texto. */
16
+ idle?: boolean;
17
+ /** Color de los caracteres. */
18
+ color?: string;
19
+ /** Color de los caracteres dentro de la lente. */
20
+ accent?: string;
21
+ /** Clase extra para el contenedor. */
22
+ className?: string;
23
+ /** Estilos extra para el contenedor. */
24
+ style?: CSSProperties;
25
+ }
26
+ /** El texto se dibuja con caracteres ASCII en un lienzo; una lente alrededor del cursor los reacomoda y los revuelve. */
27
+ export declare function AsciiText({ children, size, cell, height, radius, charset, idle, color, accent, className, style, }: AsciiTextProps): import("react").JSX.Element;
28
+ export default AsciiText;