trama-ui 0.1.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 (285) hide show
  1. package/CATALOG.md +1237 -0
  2. package/LICENSE +21 -0
  3. package/README.md +78 -0
  4. package/THIRD-PARTY.md +4 -0
  5. package/assets/pixel/kenney-pixel-ui/9-slice/Ancient/brown.png +0 -0
  6. package/assets/pixel/kenney-pixel-ui/9-slice/Ancient/brown_inlay.png +0 -0
  7. package/assets/pixel/kenney-pixel-ui/9-slice/Ancient/brown_pressed.png +0 -0
  8. package/assets/pixel/kenney-pixel-ui/9-slice/Ancient/grey.png +0 -0
  9. package/assets/pixel/kenney-pixel-ui/9-slice/Ancient/grey_inlay.png +0 -0
  10. package/assets/pixel/kenney-pixel-ui/9-slice/Ancient/grey_pressed.png +0 -0
  11. package/assets/pixel/kenney-pixel-ui/9-slice/Ancient/tan.png +0 -0
  12. package/assets/pixel/kenney-pixel-ui/9-slice/Ancient/tan_inlay.png +0 -0
  13. package/assets/pixel/kenney-pixel-ui/9-slice/Ancient/tan_pressed.png +0 -0
  14. package/assets/pixel/kenney-pixel-ui/9-slice/Ancient/white.png +0 -0
  15. package/assets/pixel/kenney-pixel-ui/9-slice/Ancient/white_inlay.png +0 -0
  16. package/assets/pixel/kenney-pixel-ui/9-slice/Ancient/white_pressed.png +0 -0
  17. package/assets/pixel/kenney-pixel-ui/9-slice/Colored/blue.png +0 -0
  18. package/assets/pixel/kenney-pixel-ui/9-slice/Colored/blue_pressed.png +0 -0
  19. package/assets/pixel/kenney-pixel-ui/9-slice/Colored/green.png +0 -0
  20. package/assets/pixel/kenney-pixel-ui/9-slice/Colored/green_pressed.png +0 -0
  21. package/assets/pixel/kenney-pixel-ui/9-slice/Colored/grey.png +0 -0
  22. package/assets/pixel/kenney-pixel-ui/9-slice/Colored/grey_pressed.png +0 -0
  23. package/assets/pixel/kenney-pixel-ui/9-slice/Colored/red.png +0 -0
  24. package/assets/pixel/kenney-pixel-ui/9-slice/Colored/red_pressed.png +0 -0
  25. package/assets/pixel/kenney-pixel-ui/9-slice/Colored/yellow.png +0 -0
  26. package/assets/pixel/kenney-pixel-ui/9-slice/Colored/yellow_pressed.png +0 -0
  27. package/assets/pixel/kenney-pixel-ui/9-slice/Outline/blue.png +0 -0
  28. package/assets/pixel/kenney-pixel-ui/9-slice/Outline/blue_pressed.png +0 -0
  29. package/assets/pixel/kenney-pixel-ui/9-slice/Outline/green.png +0 -0
  30. package/assets/pixel/kenney-pixel-ui/9-slice/Outline/green_pressed.png +0 -0
  31. package/assets/pixel/kenney-pixel-ui/9-slice/Outline/red.png +0 -0
  32. package/assets/pixel/kenney-pixel-ui/9-slice/Outline/red_pressed.png +0 -0
  33. package/assets/pixel/kenney-pixel-ui/9-slice/Outline/yellow.png +0 -0
  34. package/assets/pixel/kenney-pixel-ui/9-slice/Outline/yellow_pressed.png +0 -0
  35. package/assets/pixel/kenney-pixel-ui/9-slice/list.png +0 -0
  36. package/assets/pixel/kenney-pixel-ui/9-slice/space.png +0 -0
  37. package/assets/pixel/kenney-pixel-ui/9-slice/space_inlay.png +0 -0
  38. package/assets/pixel/kenney-pixel-ui/License.txt +23 -0
  39. package/assets/pixel/kenney-pixel-ui/spritesheet/sheet.png +0 -0
  40. package/bin/trama-ui.mjs +27 -0
  41. package/dist/components/ui/Accordion.d.ts +12 -0
  42. package/dist/components/ui/Accordion.js +12 -0
  43. package/dist/components/ui/Alert.d.ts +15 -0
  44. package/dist/components/ui/Alert.js +15 -0
  45. package/dist/components/ui/ArticlesSection.d.ts +30 -0
  46. package/dist/components/ui/ArticlesSection.js +18 -0
  47. package/dist/components/ui/AsciiBackground.d.ts +58 -0
  48. package/dist/components/ui/AsciiBackground.js +173 -0
  49. package/dist/components/ui/AsciiCard.d.ts +78 -0
  50. package/dist/components/ui/AsciiCard.js +253 -0
  51. package/dist/components/ui/AsciiChart.d.ts +18 -0
  52. package/dist/components/ui/AsciiChart.js +47 -0
  53. package/dist/components/ui/Badge.d.ts +15 -0
  54. package/dist/components/ui/Badge.js +8 -0
  55. package/dist/components/ui/BitmapText.d.ts +25 -0
  56. package/dist/components/ui/BitmapText.js +21 -0
  57. package/dist/components/ui/BlogCard.d.ts +33 -0
  58. package/dist/components/ui/BlogCard.js +13 -0
  59. package/dist/components/ui/Breadcrumbs.d.ts +9 -0
  60. package/dist/components/ui/Breadcrumbs.js +7 -0
  61. package/dist/components/ui/Button.d.ts +74 -0
  62. package/dist/components/ui/Button.js +48 -0
  63. package/dist/components/ui/CTASection.d.ts +13 -0
  64. package/dist/components/ui/CTASection.js +7 -0
  65. package/dist/components/ui/Carousel.d.ts +30 -0
  66. package/dist/components/ui/Carousel.js +80 -0
  67. package/dist/components/ui/CheckboxGroup.d.ts +11 -0
  68. package/dist/components/ui/CheckboxGroup.js +20 -0
  69. package/dist/components/ui/CodeBlock.d.ts +11 -0
  70. package/dist/components/ui/CodeBlock.js +20 -0
  71. package/dist/components/ui/ContactForm.d.ts +25 -0
  72. package/dist/components/ui/ContactForm.js +26 -0
  73. package/dist/components/ui/CrtOverlay.d.ts +10 -0
  74. package/dist/components/ui/CrtOverlay.js +5 -0
  75. package/dist/components/ui/Divider.d.ts +12 -0
  76. package/dist/components/ui/Divider.js +7 -0
  77. package/dist/components/ui/Drawer.d.ts +16 -0
  78. package/dist/components/ui/Drawer.js +25 -0
  79. package/dist/components/ui/Dropdown.d.ts +10 -0
  80. package/dist/components/ui/Dropdown.js +21 -0
  81. package/dist/components/ui/FAQSection.d.ts +14 -0
  82. package/dist/components/ui/FAQSection.js +7 -0
  83. package/dist/components/ui/FeatureGrid.d.ts +18 -0
  84. package/dist/components/ui/FeatureGrid.js +14 -0
  85. package/dist/components/ui/Footer.d.ts +14 -0
  86. package/dist/components/ui/Footer.js +16 -0
  87. package/dist/components/ui/GlyphCursor.d.ts +22 -0
  88. package/dist/components/ui/GlyphCursor.js +50 -0
  89. package/dist/components/ui/GridBackground.d.ts +15 -0
  90. package/dist/components/ui/GridBackground.js +6 -0
  91. package/dist/components/ui/Hero.d.ts +21 -0
  92. package/dist/components/ui/Hero.js +12 -0
  93. package/dist/components/ui/HoverFX.d.ts +29 -0
  94. package/dist/components/ui/HoverFX.js +75 -0
  95. package/dist/components/ui/Icon.d.ts +33 -0
  96. package/dist/components/ui/Icon.js +29 -0
  97. package/dist/components/ui/ImageGallery.d.ts +31 -0
  98. package/dist/components/ui/ImageGallery.js +79 -0
  99. package/dist/components/ui/LogoCloud.d.ts +12 -0
  100. package/dist/components/ui/LogoCloud.js +9 -0
  101. package/dist/components/ui/MagneticButton.d.ts +12 -0
  102. package/dist/components/ui/MagneticButton.js +49 -0
  103. package/dist/components/ui/Marquee.d.ts +29 -0
  104. package/dist/components/ui/Marquee.js +15 -0
  105. package/dist/components/ui/Modal.d.ts +40 -0
  106. package/dist/components/ui/Modal.js +51 -0
  107. package/dist/components/ui/NavBar.d.ts +14 -0
  108. package/dist/components/ui/NavBar.js +12 -0
  109. package/dist/components/ui/NeonSign.d.ts +14 -0
  110. package/dist/components/ui/NeonSign.js +7 -0
  111. package/dist/components/ui/OtpInput.d.ts +9 -0
  112. package/dist/components/ui/OtpInput.js +27 -0
  113. package/dist/components/ui/Pagination.d.ts +10 -0
  114. package/dist/components/ui/Pagination.js +24 -0
  115. package/dist/components/ui/Panel.d.ts +21 -0
  116. package/dist/components/ui/Panel.js +8 -0
  117. package/dist/components/ui/PixelFrame.d.ts +61 -0
  118. package/dist/components/ui/PixelFrame.js +29 -0
  119. package/dist/components/ui/Presence.d.ts +51 -0
  120. package/dist/components/ui/Presence.js +343 -0
  121. package/dist/components/ui/PricingCard.d.ts +30 -0
  122. package/dist/components/ui/PricingCard.js +21 -0
  123. package/dist/components/ui/PricingSection.d.ts +35 -0
  124. package/dist/components/ui/PricingSection.js +21 -0
  125. package/dist/components/ui/Progress.d.ts +17 -0
  126. package/dist/components/ui/Progress.js +9 -0
  127. package/dist/components/ui/RadioGroup.d.ts +10 -0
  128. package/dist/components/ui/RadioGroup.js +11 -0
  129. package/dist/components/ui/RangeSlider.d.ts +14 -0
  130. package/dist/components/ui/RangeSlider.js +11 -0
  131. package/dist/components/ui/Reveal.d.ts +48 -0
  132. package/dist/components/ui/Reveal.js +181 -0
  133. package/dist/components/ui/SceneFlash.d.ts +14 -0
  134. package/dist/components/ui/SceneFlash.js +51 -0
  135. package/dist/components/ui/ScrambleText.d.ts +31 -0
  136. package/dist/components/ui/ScrambleText.js +51 -0
  137. package/dist/components/ui/ScrollArea.d.ts +15 -0
  138. package/dist/components/ui/ScrollArea.js +18 -0
  139. package/dist/components/ui/SectionHeader.d.ts +12 -0
  140. package/dist/components/ui/SectionHeader.js +6 -0
  141. package/dist/components/ui/Select.d.ts +17 -0
  142. package/dist/components/ui/Select.js +31 -0
  143. package/dist/components/ui/Skeleton.d.ts +12 -0
  144. package/dist/components/ui/Skeleton.js +18 -0
  145. package/dist/components/ui/Spinner.d.ts +15 -0
  146. package/dist/components/ui/Spinner.js +24 -0
  147. package/dist/components/ui/Spotlight.d.ts +16 -0
  148. package/dist/components/ui/Spotlight.js +16 -0
  149. package/dist/components/ui/Sprite.d.ts +27 -0
  150. package/dist/components/ui/Sprite.js +19 -0
  151. package/dist/components/ui/StatCounter.d.ts +24 -0
  152. package/dist/components/ui/StatCounter.js +70 -0
  153. package/dist/components/ui/StatsSection.d.ts +14 -0
  154. package/dist/components/ui/StatsSection.js +13 -0
  155. package/dist/components/ui/Stepper.d.ts +10 -0
  156. package/dist/components/ui/Stepper.js +7 -0
  157. package/dist/components/ui/Table.d.ts +10 -0
  158. package/dist/components/ui/Table.js +8 -0
  159. package/dist/components/ui/Tabs.d.ts +12 -0
  160. package/dist/components/ui/Tabs.js +18 -0
  161. package/dist/components/ui/TeamSection.d.ts +18 -0
  162. package/dist/components/ui/TeamSection.js +19 -0
  163. package/dist/components/ui/TerminalTyper.d.ts +20 -0
  164. package/dist/components/ui/TerminalTyper.js +45 -0
  165. package/dist/components/ui/Testimonial.d.ts +19 -0
  166. package/dist/components/ui/Testimonial.js +9 -0
  167. package/dist/components/ui/TestimonialSection.d.ts +20 -0
  168. package/dist/components/ui/TestimonialSection.js +15 -0
  169. package/dist/components/ui/TextField.d.ts +26 -0
  170. package/dist/components/ui/TextField.js +13 -0
  171. package/dist/components/ui/TextmodeBackground.d.ts +52 -0
  172. package/dist/components/ui/TextmodeBackground.js +83 -0
  173. package/dist/components/ui/Timeline.d.ts +13 -0
  174. package/dist/components/ui/Timeline.js +8 -0
  175. package/dist/components/ui/Toast.d.ts +30 -0
  176. package/dist/components/ui/Toast.js +47 -0
  177. package/dist/components/ui/Toggle.d.ts +13 -0
  178. package/dist/components/ui/Toggle.js +10 -0
  179. package/dist/components/ui/Tooltip.d.ts +20 -0
  180. package/dist/components/ui/Tooltip.js +20 -0
  181. package/dist/components/ui/Typewriter.d.ts +20 -0
  182. package/dist/components/ui/Typewriter.js +46 -0
  183. package/dist/components/ui/VideoPlayer.d.ts +81 -0
  184. package/dist/components/ui/VideoPlayer.js +379 -0
  185. package/dist/components/ui/fill.d.ts +32 -0
  186. package/dist/components/ui/fill.js +27 -0
  187. package/dist/components/ui/intent.d.ts +20 -0
  188. package/dist/components/ui/intent.js +23 -0
  189. package/dist/components/ui/styles/kit.css +31 -0
  190. package/dist/components/ui/styles/ui-carousel.css +113 -0
  191. package/dist/components/ui/styles/ui-kit.css +1135 -0
  192. package/dist/components/ui/styles/ui-video.css +75 -0
  193. package/dist/components/ui/swiperCss.d.ts +15 -0
  194. package/dist/components/ui/swiperCss.js +15 -0
  195. package/dist/components/ui/variants.d.ts +12 -0
  196. package/dist/components/ui/variants.js +10 -0
  197. package/dist/index.d.ts +77 -0
  198. package/dist/index.js +77 -0
  199. package/dist/lib/asciify/painters.d.ts +25 -0
  200. package/dist/lib/asciify/painters.js +604 -0
  201. package/dist/lib/asciify/pointer.d.ts +12 -0
  202. package/dist/lib/asciify/pointer.js +54 -0
  203. package/dist/lib/asciify/shape-painters.d.ts +44 -0
  204. package/dist/lib/asciify/shape-painters.js +335 -0
  205. package/dist/lib/asciify/theme-painters.d.ts +8 -0
  206. package/dist/lib/asciify/theme-painters.js +437 -0
  207. package/dist/lib/cells.d.ts +46 -0
  208. package/dist/lib/cells.js +114 -0
  209. package/dist/lib/scroll/store.d.ts +21 -0
  210. package/dist/lib/scroll/store.js +25 -0
  211. package/dist/lib/sketches/aurora.d.ts +3 -0
  212. package/dist/lib/sketches/aurora.js +42 -0
  213. package/dist/lib/sketches/fire.d.ts +3 -0
  214. package/dist/lib/sketches/fire.js +80 -0
  215. package/dist/lib/sketches/flow.d.ts +3 -0
  216. package/dist/lib/sketches/flow.js +70 -0
  217. package/dist/lib/sketches/life.d.ts +3 -0
  218. package/dist/lib/sketches/life.js +107 -0
  219. package/dist/lib/sketches/matrix.d.ts +3 -0
  220. package/dist/lib/sketches/matrix.js +64 -0
  221. package/dist/lib/sketches/ocean.d.ts +3 -0
  222. package/dist/lib/sketches/ocean.js +56 -0
  223. package/dist/lib/sketches/plasma.d.ts +3 -0
  224. package/dist/lib/sketches/plasma.js +29 -0
  225. package/dist/lib/sketches/registry.d.ts +6 -0
  226. package/dist/lib/sketches/registry.js +60 -0
  227. package/dist/lib/sketches/ripples.d.ts +3 -0
  228. package/dist/lib/sketches/ripples.js +84 -0
  229. package/dist/lib/sketches/scroll-assemble.d.ts +8 -0
  230. package/dist/lib/sketches/scroll-assemble.js +90 -0
  231. package/dist/lib/sketches/scroll-parallax.d.ts +6 -0
  232. package/dist/lib/sketches/scroll-parallax.js +76 -0
  233. package/dist/lib/sketches/scroll-rain.d.ts +6 -0
  234. package/dist/lib/sketches/scroll-rain.js +82 -0
  235. package/dist/lib/sketches/scroll-stats.d.ts +7 -0
  236. package/dist/lib/sketches/scroll-stats.js +81 -0
  237. package/dist/lib/sketches/scroll-terminal.d.ts +6 -0
  238. package/dist/lib/sketches/scroll-terminal.js +87 -0
  239. package/dist/lib/sketches/scroll-zoom.d.ts +7 -0
  240. package/dist/lib/sketches/scroll-zoom.js +49 -0
  241. package/dist/lib/sketches/starfield.d.ts +3 -0
  242. package/dist/lib/sketches/starfield.js +54 -0
  243. package/dist/lib/sketches/text-3d.d.ts +6 -0
  244. package/dist/lib/sketches/text-3d.js +62 -0
  245. package/dist/lib/sketches/text-combo-figlet.d.ts +6 -0
  246. package/dist/lib/sketches/text-combo-figlet.js +91 -0
  247. package/dist/lib/sketches/text-glitch.d.ts +3 -0
  248. package/dist/lib/sketches/text-glitch.js +57 -0
  249. package/dist/lib/sketches/text-marquee.d.ts +3 -0
  250. package/dist/lib/sketches/text-marquee.js +35 -0
  251. package/dist/lib/sketches/text-particles.d.ts +9 -0
  252. package/dist/lib/sketches/text-particles.js +91 -0
  253. package/dist/lib/sketches/text-rain.d.ts +3 -0
  254. package/dist/lib/sketches/text-rain.js +73 -0
  255. package/dist/lib/sketches/text-scramble.d.ts +6 -0
  256. package/dist/lib/sketches/text-scramble.js +49 -0
  257. package/dist/lib/sketches/text-wave.d.ts +3 -0
  258. package/dist/lib/sketches/text-wave.js +43 -0
  259. package/dist/lib/sketches/theme-orbit.d.ts +6 -0
  260. package/dist/lib/sketches/theme-orbit.js +119 -0
  261. package/dist/lib/sketches/theme-pulse.d.ts +7 -0
  262. package/dist/lib/sketches/theme-pulse.js +84 -0
  263. package/dist/lib/sketches/tunnel.d.ts +3 -0
  264. package/dist/lib/sketches/tunnel.js +28 -0
  265. package/dist/lib/text/banner.d.ts +5 -0
  266. package/dist/lib/text/banner.js +35 -0
  267. package/dist/lib/text/mask.d.ts +40 -0
  268. package/dist/lib/text/mask.js +143 -0
  269. package/dist/lib/text/store.d.ts +23 -0
  270. package/dist/lib/text/store.js +86 -0
  271. package/dist/lib/ui/card-fields.d.ts +52 -0
  272. package/dist/lib/ui/card-fields.js +193 -0
  273. package/dist/lib/ui/fonts.d.ts +56 -0
  274. package/dist/lib/ui/fonts.js +94 -0
  275. package/dist/lib/ui/fx.d.ts +42 -0
  276. package/dist/lib/ui/fx.js +162 -0
  277. package/dist/lib/ui/icons.d.ts +437 -0
  278. package/dist/lib/ui/icons.js +314 -0
  279. package/dist/lib/ui/placeholder.d.ts +11 -0
  280. package/dist/lib/ui/placeholder.js +65 -0
  281. package/dist/lib/ui/tokens.d.ts +49 -0
  282. package/dist/lib/ui/tokens.js +129 -0
  283. package/dist/lib/ui/useTokens.d.ts +10 -0
  284. package/dist/lib/ui/useTokens.js +32 -0
  285. package/package.json +80 -0
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /** Fondo de rejilla en CSS puro (sin canvas): la opción más ligera para fondos discretos. Rellena su contenedor. */
3
+ export default function GridBackground({ kind = "dots", cellSize, size: legacySize, opacity = 0.6, fade = true, drift = false, className = "" }) {
4
+ const size = cellSize ?? legacySize ?? 28;
5
+ return (_jsx("div", { className: `ui-grid ui-grid--${kind} ${fade ? "ui-grid--fade" : ""} ${drift ? "ui-grid--drift" : ""} ${className}`, style: { "--gs": `${size}px`, "--gop": opacity }, "aria-hidden": true, children: _jsx("div", { className: "ui-grid__layer" }) }));
6
+ }
@@ -0,0 +1,21 @@
1
+ import type { Variant } from "./variants.js";
2
+ export type HeroProps = {
3
+ kicker?: string;
4
+ title?: string;
5
+ subtitle?: string;
6
+ primaryCta?: string;
7
+ secondaryCta?: string;
8
+ /** insignias de confianza separadas por comas; vacío = ninguna */
9
+ badges?: string;
10
+ align?: "left" | "center";
11
+ /** patrón decorativo de fondo, solo CSS (sin canvas) */
12
+ backdrop?: "none" | "grid" | "dots" | "glow";
13
+ variant?: Variant;
14
+ className?: string;
15
+ };
16
+ /**
17
+ * Cabecera de landing: sobretítulo, titular grande, subtítulo, una o dos llamadas a la acción y una
18
+ * fila de insignias de confianza. El fondo decorativo es CSS puro (sin motor de render); para una escena
19
+ * ASCII de verdad, combínalo con un componente de "Fondos" por detrás (ver guía §7).
20
+ */
21
+ export default function Hero({ kicker, title, subtitle, primaryCta, secondaryCta, badges, align, backdrop, variant, className, }: HeroProps): import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Badge from "./Badge.js";
3
+ import Button from "./Button.js";
4
+ /**
5
+ * Cabecera de landing: sobretítulo, titular grande, subtítulo, una o dos llamadas a la acción y una
6
+ * fila de insignias de confianza. El fondo decorativo es CSS puro (sin motor de render); para una escena
7
+ * ASCII de verdad, combínalo con un componente de "Fondos" por detrás (ver guía §7).
8
+ */
9
+ export default function Hero({ kicker = "ESCUELA DE SURF · CANTABRIA", title = "Aprende a leer el mar", subtitle = "Del primer remo a tu primera ola verde. Neopreno, tabla y monitores titulados incluidos.", primaryCta = "Reservar clase", secondaryCta = "Ver horarios", badges = "+500 alumnos, 4.9 ★ valoración, Grupos de 6", align = "center", backdrop = "grid", variant = "minimal", className = "", }) {
10
+ const chips = badges.split(",").map((s) => s.trim()).filter(Boolean);
11
+ return (_jsx("section", { className: `ui-hero ui-hero--${align} ui-hero--bd-${backdrop} ${className}`, children: _jsxs("div", { className: "ui-hero__inner", children: [kicker && _jsx("span", { className: "ui-hero__kicker", children: kicker }), _jsx("h1", { children: title }), subtitle && _jsx("p", { children: subtitle }), (primaryCta || secondaryCta) && (_jsxs("div", { className: "ui-hero__cta", children: [primaryCta && _jsx(Button, { label: primaryCta, variant: variant, emphasis: "primary", glyph: "\u2192" }), secondaryCta && _jsx(Button, { label: secondaryCta, variant: variant, emphasis: "secondary" })] })), chips.length > 0 && (_jsx("div", { className: "ui-hero__trust", children: chips.map((c) => (_jsx(Badge, { text: c, variant: variant, intent: "neutral" }, c))) }))] }) }));
12
+ }
@@ -0,0 +1,29 @@
1
+ import { type ReactNode } from "react";
2
+ import { type Tone } from "./variants.js";
3
+ export type HoverEffectKind = "none" | "lift" | "scale" | "tilt" | "glow" | "shine" | "scanline" | "jitter" | "brackets" | "underline";
4
+ export declare const HOVERFX_EFFECTS: {
5
+ id: HoverEffectKind;
6
+ label: string;
7
+ }[];
8
+ export type HoverFXProps = {
9
+ effect?: HoverEffectKind;
10
+ /** 0–1: fuerza del desplazamiento, la escala, el glifo o el brillo */
11
+ strength?: number;
12
+ tone?: Tone;
13
+ /** radio del brillo, en px (solo "glow") */
14
+ radius?: number;
15
+ /** inclinación máxima en grados (solo "tilt") */
16
+ tiltMax?: number;
17
+ /** ocupa el 100% del contenedor en vez de ajustarse al contenido (para usarlo sobre fondos) */
18
+ fill?: boolean;
19
+ disabled?: boolean;
20
+ children: ReactNode;
21
+ className?: string;
22
+ };
23
+ /**
24
+ * Envoltorio de efectos de hover reutilizable en cualquier contenido: elevación, escala,
25
+ * inclinación 3D, brillo que sigue al puntero, destello diagonal, barrido de escaneo,
26
+ * micro-glitch, corchetes de objetivo o subrayado. La mayoría son CSS puro; "tilt" y "glow"
27
+ * siguen la posición del puntero con GSAP. Sin efecto en táctil ni con movimiento reducido.
28
+ */
29
+ export default function HoverFX({ effect, strength, tone, radius, tiltMax, fill, disabled, children, className, }: HoverFXProps): import("react").JSX.Element;
@@ -0,0 +1,75 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef } from "react";
4
+ import { tcls } from "./variants.js";
5
+ export const HOVERFX_EFFECTS = [
6
+ { id: "lift", label: "Elevación" },
7
+ { id: "scale", label: "Escala" },
8
+ { id: "tilt", label: "Inclinación 3D" },
9
+ { id: "glow", label: "Brillo que sigue al puntero" },
10
+ { id: "shine", label: "Destello diagonal" },
11
+ { id: "scanline", label: "Barrido de escaneo" },
12
+ { id: "jitter", label: "Micro-glitch" },
13
+ { id: "brackets", label: "Corchetes de objetivo" },
14
+ { id: "underline", label: "Subrayado animado" },
15
+ { id: "none", label: "Ninguno" },
16
+ ];
17
+ /**
18
+ * Envoltorio de efectos de hover reutilizable en cualquier contenido: elevación, escala,
19
+ * inclinación 3D, brillo que sigue al puntero, destello diagonal, barrido de escaneo,
20
+ * micro-glitch, corchetes de objetivo o subrayado. La mayoría son CSS puro; "tilt" y "glow"
21
+ * siguen la posición del puntero con GSAP. Sin efecto en táctil ni con movimiento reducido.
22
+ */
23
+ export default function HoverFX({ effect = "lift", strength = 0.6, tone = "acc", radius = 220, tiltMax = 10, fill = false, disabled = false, children, className = "", }) {
24
+ const root = useRef(null);
25
+ const inner = useRef(null);
26
+ const fx = disabled ? "none" : effect;
27
+ useEffect(() => {
28
+ const el = root.current;
29
+ const box = inner.current;
30
+ if (!el || !box || disabled)
31
+ return;
32
+ if (fx !== "tilt" && fx !== "glow")
33
+ return;
34
+ if (window.matchMedia("(pointer: coarse)").matches || window.matchMedia("(prefers-reduced-motion: reduce)").matches)
35
+ return;
36
+ let disposed = false;
37
+ let off;
38
+ (async () => {
39
+ const { gsap } = await import("gsap");
40
+ if (disposed)
41
+ return;
42
+ const rx = fx === "tilt" ? gsap.quickTo(box, "rotationX", { duration: 0.5, ease: "power3.out" }) : null;
43
+ const ry = fx === "tilt" ? gsap.quickTo(box, "rotationY", { duration: 0.5, ease: "power3.out" }) : null;
44
+ const move = (e) => {
45
+ const r = el.getBoundingClientRect();
46
+ const nx = (e.clientX - r.left) / r.width;
47
+ const ny = (e.clientY - r.top) / r.height;
48
+ el.style.setProperty("--mx", `${nx * 100}%`);
49
+ el.style.setProperty("--my", `${ny * 100}%`);
50
+ if (fx === "tilt") {
51
+ rx?.((0.5 - ny) * tiltMax);
52
+ ry?.((nx - 0.5) * tiltMax * 1.2);
53
+ }
54
+ };
55
+ const leave = () => {
56
+ if (fx === "tilt") {
57
+ rx?.(0);
58
+ ry?.(0);
59
+ }
60
+ };
61
+ el.addEventListener("pointermove", move);
62
+ el.addEventListener("pointerleave", leave);
63
+ off = () => {
64
+ el.removeEventListener("pointermove", move);
65
+ el.removeEventListener("pointerleave", leave);
66
+ gsap.killTweensOf(box);
67
+ };
68
+ })();
69
+ return () => {
70
+ disposed = true;
71
+ off?.();
72
+ };
73
+ }, [fx, tiltMax, disabled]);
74
+ return (_jsxs("div", { ref: root, className: `ui-hoverfx ui-hoverfx--${fx} ${fill ? "ui-hoverfx--fill" : ""} ${tcls(tone)} ${className}`, style: { "--hf-radius": `${radius}px`, "--hf-strength": strength }, children: [_jsx("div", { ref: inner, className: "ui-hoverfx__inner", children: children }), fx === "shine" && _jsx("span", { className: "ui-hoverfx__shine", "aria-hidden": true }), fx === "scanline" && _jsx("span", { className: "ui-hoverfx__scan", "aria-hidden": true }), fx === "glow" && _jsx("span", { className: "ui-hoverfx__glow", "aria-hidden": true }), fx === "brackets" && (_jsxs(_Fragment, { children: [_jsx("i", { className: "ui-hoverfx__bracket ui-hoverfx__bracket--tl", "aria-hidden": true }), _jsx("i", { className: "ui-hoverfx__bracket ui-hoverfx__bracket--tr", "aria-hidden": true }), _jsx("i", { className: "ui-hoverfx__bracket ui-hoverfx__bracket--bl", "aria-hidden": true }), _jsx("i", { className: "ui-hoverfx__bracket ui-hoverfx__bracket--br", "aria-hidden": true })] }))] }));
75
+ }
@@ -0,0 +1,33 @@
1
+ import type { ReactElement, ReactNode, SVGProps } from "react";
2
+ import { type IconName } from "../../lib/ui/icons.js";
3
+ import { type Tone } from "./variants.js";
4
+ export type IconProps = {
5
+ /** clave del set curado, p. ej. "heart" o "external-link" (agrupadas en `ICON_GROUPS`, `lib/ui/icons.ts`) */
6
+ name?: IconName;
7
+ /** alternativa a `name`: cualquier componente de `pixelarticons/react/*`, sin pasar por el set curado */
8
+ icon?: (props: SVGProps<SVGSVGElement>) => ReactElement;
9
+ /** lado: px (número) o cualquier longitud CSS */
10
+ iconSize?: number | string;
11
+ /** @deprecated usa `iconSize` */
12
+ size?: number | string;
13
+ /** variante de esquinas duras, cuando el icono la tiene (~1 de cada 2); si no, cae a la base */
14
+ sharp?: boolean;
15
+ /** token de color; sin él hereda el color del texto que lo rodea (`currentColor`) */
16
+ tone?: Tone;
17
+ /** nombre accesible; sin él el icono es decorativo y se oculta a los lectores de pantalla */
18
+ label?: string;
19
+ className?: string;
20
+ };
21
+ /**
22
+ * Icono pixel art (Pixelarticons, MIT). SVG con `fill: currentColor`: se tiñe con el color del texto o con
23
+ * un token del tema (`tone`), igual que el resto del kit. `shape-rendering: crispEdges` (ver `.ui-icon`) evita
24
+ * que los bordes de cada píxel se suavicen al escalar.
25
+ */
26
+ export default function Icon({ name, icon, iconSize, size: legacySize, sharp, tone, label, className }: IconProps): import("react").JSX.Element | null;
27
+ /**
28
+ * Para los componentes del kit que reciben un "glifo" como texto (`glyph="→"`, los `items` de FeatureGrid…):
29
+ * `"icon:heart"` (o `"icon:heart:sharp"`) se dibuja como el icono pixel art de ese nombre; cualquier otra cadena
30
+ * pasa tal cual, así que lo existente no cambia. Un nombre que no está en el set se devuelve como texto — el
31
+ * error de tecleo se ve en pantalla en vez de desaparecer.
32
+ */
33
+ export declare function renderGlyph(glyph: string, size?: number | string): ReactNode;
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ICONS } from "../../lib/ui/icons.js";
3
+ import { tcls } from "./variants.js";
4
+ /**
5
+ * Icono pixel art (Pixelarticons, MIT). SVG con `fill: currentColor`: se tiñe con el color del texto o con
6
+ * un token del tema (`tone`), igual que el resto del kit. `shape-rendering: crispEdges` (ver `.ui-icon`) evita
7
+ * que los bordes de cada píxel se suavicen al escalar.
8
+ */
9
+ export default function Icon({ name, icon, iconSize, size: legacySize, sharp = false, tone, label, className = "" }) {
10
+ const size = iconSize ?? legacySize ?? 24;
11
+ const entry = name ? ICONS[name] : undefined;
12
+ const Glyph = icon ?? (entry ? (sharp && entry.sharp ? entry.sharp : entry.base) : undefined);
13
+ if (!Glyph)
14
+ return null;
15
+ return (_jsx(Glyph, { width: size, height: size, className: `ui-icon ${tone ? `ui-icon--tone ${tcls(tone)}` : ""} ${className}`.trim(), role: label ? "img" : undefined, "aria-label": label, "aria-hidden": label ? undefined : true }));
16
+ }
17
+ const GLYPH_ICON = /^icon:([a-z0-9-]+)(:sharp)?$/;
18
+ /**
19
+ * Para los componentes del kit que reciben un "glifo" como texto (`glyph="→"`, los `items` de FeatureGrid…):
20
+ * `"icon:heart"` (o `"icon:heart:sharp"`) se dibuja como el icono pixel art de ese nombre; cualquier otra cadena
21
+ * pasa tal cual, así que lo existente no cambia. Un nombre que no está en el set se devuelve como texto — el
22
+ * error de tecleo se ve en pantalla en vez de desaparecer.
23
+ */
24
+ export function renderGlyph(glyph, size = "1.25em") {
25
+ const m = GLYPH_ICON.exec(glyph);
26
+ if (!m || !(m[1] in ICONS))
27
+ return glyph;
28
+ return _jsx(Icon, { name: m[1], iconSize: size, sharp: Boolean(m[2]) });
29
+ }
@@ -0,0 +1,31 @@
1
+ import "./swiperCss.js";
2
+ import { type Variant } from "./variants.js";
3
+ export declare const GALLERY_LAYOUTS: readonly ["thumbs", "coverflow", "cards", "fade", "cube", "creative", "filmstrip", "mosaic"];
4
+ export type GalleryLayout = (typeof GALLERY_LAYOUTS)[number];
5
+ export type ImageGalleryProps = {
6
+ /** una por línea: «imagen|título|texto». La imagen es una URL/ruta, o `gen:N` para una escena de ejemplo generada (0, 1, 2…) */
7
+ images?: string;
8
+ /**
9
+ * thumbs = imagen grande + tira de miniaturas · coverflow = carrusel 3D centrado · cards = baraja apilada ·
10
+ * fade = fundido a pantalla completa · cube = cubo 3D · creative = diapositivas que entran girando ·
11
+ * filmstrip = tira libre que se arrastra, de anchos distintos · mosaic = mosaico de dos filas
12
+ */
13
+ layout?: GalleryLayout;
14
+ /** proporción de cada imagen (no afecta a filmstrip ni mosaic) */
15
+ ratio?: "4/3" | "16/9" | "21/9" | "1/1" | "3/4";
16
+ /** muestra título y texto sobre cada imagen */
17
+ captions?: boolean;
18
+ /** segundos entre imágenes; 0 = manual (siempre apagado con prefers-reduced-motion) */
19
+ autoplay?: number;
20
+ loop?: boolean;
21
+ /** zoom con doble clic o pellizco (solo en thumbs y fade) */
22
+ zoom?: boolean;
23
+ variant?: Variant;
24
+ className?: string;
25
+ };
26
+ /**
27
+ * Galería de imágenes sobre Swiper (https://swiperjs.com/) con ocho maneras de presentar las mismas fotos. El estilo sale de
28
+ * `variant` (borde, sombra y radio de cada imagen) y de los tokens, igual que el resto del kit; las flechas y los puntos
29
+ * los define components/ui/styles/ui-carousel.css con las variables `--s-*`. Sin imágenes propias usa escenas pixel art generadas (`gen:N`).
30
+ */
31
+ export default function ImageGallery({ images, layout, ratio, captions, autoplay, loop, zoom, variant, className }: ImageGalleryProps): import("react").JSX.Element;
@@ -0,0 +1,79 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { A11y, Autoplay, EffectCards, EffectCoverflow, EffectCreative, EffectCube, EffectFade, FreeMode, Grid, Keyboard, Navigation, Pagination, Thumbs, Zoom } from "swiper/modules";
5
+ import { Swiper, SwiperSlide } from "swiper/react";
6
+ import { resolveImage } from "../../lib/ui/placeholder.js";
7
+ import "./swiperCss.js";
8
+ import { vcls } from "./variants.js";
9
+ export const GALLERY_LAYOUTS = ["thumbs", "coverflow", "cards", "fade", "cube", "creative", "filmstrip", "mosaic"];
10
+ const DEFAULT_IMAGES = "gen:0|Amanecer|Primera luz sobre la bahía\n" +
11
+ "gen:1|Mediodía|Mar limpio y viento suave\n" +
12
+ "gen:2|Atardecer|La hora dorada del swell\n" +
13
+ "gen:3|Noche|Luna llena y marea alta\n" +
14
+ "gen:4|Cala|Aguas verdes entre rocas\n" +
15
+ "gen:5|Ocaso|Rojos y naranjas en el horizonte\n" +
16
+ "gen:6|Bruma|Mañana de niebla fina\n" +
17
+ "gen:7|Aurora|Cielo violeta sobre el agua";
18
+ /** Proporciones de la tira libre: ancho relativo a la altura de cada diapositiva. */
19
+ const STRIP = [1.5, 1, 0.8, 1.3, 1.15, 0.9, 1.4, 1.05];
20
+ function Figure({ s, captions, zoom, variant, fill = false }) {
21
+ const img = _jsx("img", { src: resolveImage(s.src), alt: s.title, loading: "lazy", draggable: false });
22
+ return (_jsxs("figure", { className: `ui-gal__shot ui-surface ${vcls(variant)} ${fill ? "ui-gal__shot--fill" : ""}`, children: [zoom ? _jsx("div", { className: "swiper-zoom-container", children: img }) : img, captions && (s.title || s.text) && (_jsxs("figcaption", { className: "ui-gal__cap", children: [s.title && _jsx("strong", { children: s.title }), s.text && _jsx("span", { children: s.text })] }))] }));
23
+ }
24
+ /**
25
+ * Galería de imágenes sobre Swiper (https://swiperjs.com/) con ocho maneras de presentar las mismas fotos. El estilo sale de
26
+ * `variant` (borde, sombra y radio de cada imagen) y de los tokens, igual que el resto del kit; las flechas y los puntos
27
+ * los define components/ui/styles/ui-carousel.css con las variables `--s-*`. Sin imágenes propias usa escenas pixel art generadas (`gen:N`).
28
+ */
29
+ export default function ImageGallery({ images = DEFAULT_IMAGES, layout = "thumbs", ratio = "4/3", captions = true, autoplay = 0, loop = false, zoom = false, variant = "retro", className = "" }) {
30
+ const shots = useMemo(() => images
31
+ .split("\n")
32
+ .map((l) => l.split("|"))
33
+ .filter((r) => r[0]?.trim())
34
+ .map(([src, title = "", text = ""]) => ({ src, title: title.trim(), text: text.trim() })), [images]);
35
+ const [thumbs, setThumbs] = useState(null);
36
+ const [reduced, setReduced] = useState(false);
37
+ useEffect(() => {
38
+ const mq = matchMedia("(prefers-reduced-motion: reduce)");
39
+ setReduced(mq.matches);
40
+ const on = () => setReduced(mq.matches);
41
+ mq.addEventListener("change", on);
42
+ return () => mq.removeEventListener("change", on);
43
+ }, []);
44
+ const play = !reduced && autoplay > 0 ? { delay: autoplay * 1000, disableOnInteraction: false, pauseOnMouseEnter: true } : false;
45
+ const canLoop = loop && shots.length > 3;
46
+ const base = [A11y, Keyboard];
47
+ const root = `ui-gal ui-gal--${layout} ${vcls(variant)} ${className}`;
48
+ const style = { "--gal-ratio": ratio };
49
+ const key = `${layout}-${zoom}-${canLoop}-${autoplay > 0}-${play !== false}`;
50
+ const slide = (s, i, extra) => (_jsx(SwiperSlide, { style: extra?.width ? { "--w": extra.width } : undefined, children: _jsx(Figure, { s: s, captions: captions, zoom: extra?.zoom, fill: extra?.fill, variant: variant }) }, s.src + i));
51
+ let body;
52
+ switch (layout) {
53
+ case "thumbs":
54
+ body = (_jsxs(_Fragment, { children: [_jsx(Swiper, { modules: [...base, Navigation, Thumbs, ...(zoom ? [Zoom] : []), ...(play ? [Autoplay] : [])], spaceBetween: 10, navigation: true, zoom: zoom, autoplay: play, thumbs: { swiper: thumbs && !thumbs.destroyed ? thumbs : null }, className: "ui-gal__main", children: shots.map((s, i) => slide(s, i, { zoom })) }, key), _jsx(Swiper, { onSwiper: setThumbs, modules: [FreeMode, Thumbs], spaceBetween: 10, slidesPerView: 4, breakpoints: { 700: { slidesPerView: 6 } }, freeMode: true, watchSlidesProgress: true, className: "ui-gal__thumbs", children: shots.map((s, i) => (_jsx(SwiperSlide, { children: _jsx("figure", { className: `ui-gal__thumb ui-surface ${vcls(variant)}`, children: _jsx("img", { src: resolveImage(s.src), alt: s.title, loading: "lazy", draggable: false }) }) }, s.src + i))) }, `t-${key}`)] }));
55
+ break;
56
+ case "coverflow":
57
+ body = (_jsx(Swiper, { modules: [...base, EffectCoverflow, Pagination, ...(play ? [Autoplay] : [])], effect: "coverflow", grabCursor: true, centeredSlides: true, slidesPerView: "auto", loop: canLoop, autoplay: play, coverflowEffect: { rotate: 38, stretch: 0, depth: 170, modifier: 1, slideShadows: true }, pagination: { clickable: true }, children: shots.map((s, i) => slide(s, i)) }, key));
58
+ break;
59
+ case "cards":
60
+ body = (_jsx(Swiper, { modules: [...base, EffectCards, ...(play ? [Autoplay] : [])], effect: "cards", grabCursor: true, autoplay: play, cardsEffect: { slideShadows: false, perSlideOffset: 9, perSlideRotate: 3 }, children: shots.map((s, i) => slide(s, i)) }, key));
61
+ break;
62
+ case "fade":
63
+ body = (_jsx(Swiper, { modules: [...base, EffectFade, Navigation, Pagination, ...(zoom ? [Zoom] : []), ...(play ? [Autoplay] : [])], effect: "fade", fadeEffect: { crossFade: true }, navigation: true, pagination: { type: "fraction" }, zoom: zoom, loop: canLoop, autoplay: play, children: shots.map((s, i) => slide(s, i, { zoom })) }, key));
64
+ break;
65
+ case "cube":
66
+ body = (_jsx(Swiper, { modules: [...base, EffectCube, Pagination, ...(play ? [Autoplay] : [])], effect: "cube", grabCursor: true, cubeEffect: { shadow: true, slideShadows: true, shadowOffset: 24, shadowScale: 0.92 }, pagination: { clickable: true }, autoplay: play, children: shots.map((s, i) => slide(s, i)) }, key));
67
+ break;
68
+ case "creative":
69
+ body = (_jsx(Swiper, { modules: [...base, EffectCreative, Navigation, Pagination, ...(play ? [Autoplay] : [])], effect: "creative", grabCursor: true, creativeEffect: { prev: { shadow: true, translate: ["-110%", 0, -400], rotate: [0, 0, -8] }, next: { translate: ["110%", 0, 0], rotate: [0, 0, 8] } }, navigation: true, pagination: { clickable: true }, loop: canLoop, autoplay: play, children: shots.map((s, i) => slide(s, i)) }, key));
70
+ break;
71
+ case "filmstrip":
72
+ body = (_jsx(Swiper, { modules: [...base, FreeMode, Pagination, ...(play ? [Autoplay] : [])], slidesPerView: "auto", spaceBetween: 14, freeMode: { enabled: true, sticky: false }, grabCursor: true, pagination: { type: "progressbar" }, loop: canLoop, autoplay: play, children: shots.map((s, i) => slide(s, i, { fill: true, width: STRIP[i % STRIP.length] })) }, key));
73
+ break;
74
+ case "mosaic":
75
+ body = (_jsx(Swiper, { modules: [...base, Grid, Pagination, ...(play ? [Autoplay] : [])], slidesPerView: 2, grid: { rows: 2, fill: "row" }, spaceBetween: 12, breakpoints: { 700: { slidesPerView: 3 }, 1000: { slidesPerView: 4 } }, pagination: { clickable: true }, autoplay: play, children: shots.map((s, i) => slide(s, i, { fill: true })) }, key));
76
+ break;
77
+ }
78
+ return (_jsx("div", { className: root, style: style, children: body }));
79
+ }
@@ -0,0 +1,12 @@
1
+ import { type Variant } from "./variants.js";
2
+ export type LogoCloudProps = {
3
+ label?: string;
4
+ /** nombres de marca separados por comas (sin imágenes: son "logos" tipográficos) */
5
+ brands?: string;
6
+ /** row = fila que salta de línea · ticker = cinta continua que se desplaza sola (Swiper) */
7
+ layout?: "row" | "ticker";
8
+ variant?: Variant;
9
+ className?: string;
10
+ };
11
+ /** Tira de marcas en texto — "con la confianza de…" — sin depender de imágenes que el kit no tiene. */
12
+ export default function LogoCloud({ label, brands, layout, variant, className }: LogoCloudProps): import("react").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Carousel from "./Carousel.js";
3
+ import { vcls } from "./variants.js";
4
+ /** Tira de marcas en texto — "con la confianza de…" — sin depender de imágenes que el kit no tiene. */
5
+ export default function LogoCloud({ label = "Con la confianza de", brands = "Nautilus, Costa Brava FM, Surfrider, Deporte Norte, Vela & Mar", layout = "row", variant = "minimal", className = "" }) {
6
+ const list = brands.split(",").map((s) => s.trim()).filter(Boolean);
7
+ const brandEls = list.map((b) => (_jsx("span", { className: "ui-logos__brand", children: b }, b)));
8
+ return (_jsxs("section", { className: `ui-logos ${className}`, children: [label && _jsx("span", { className: "ui-logos__label", children: label }), layout === "ticker" ? (_jsx(Carousel, { ticker: true, variant: variant, label: label || "Marcas", className: "ui-logos__ticker", children: brandEls })) : (_jsx("div", { className: `ui-logos__row ${vcls(variant)}`, children: brandEls }))] }));
9
+ }
@@ -0,0 +1,12 @@
1
+ import { type ButtonProps } from "./Button.js";
2
+ export type MagneticButtonProps = Omit<ButtonProps, "className"> & {
3
+ /** cuánto se acerca al puntero (0 = nada, 1 = pegado) */
4
+ strength?: number;
5
+ /** distancia en px a la que empieza a atraerse */
6
+ radius?: number;
7
+ /** giro sutil hacia el puntero, en grados */
8
+ tilt?: number;
9
+ className?: string;
10
+ };
11
+ /** `Button` que se desplaza hacia el puntero cuando este se acerca. Sin efecto en táctil. */
12
+ export default function MagneticButton({ strength, radius, tilt, className, ...button }: MagneticButtonProps): import("react").JSX.Element;
@@ -0,0 +1,49 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useEffect, useRef } from "react";
4
+ import Button from "./Button.js";
5
+ /** `Button` que se desplaza hacia el puntero cuando este se acerca. Sin efecto en táctil. */
6
+ export default function MagneticButton({ strength = 0.35, radius = 160, tilt = 0, className = "", ...button }) {
7
+ const ref = useRef(null);
8
+ useEffect(() => {
9
+ const btn = ref.current;
10
+ if (!btn || window.matchMedia("(pointer: coarse)").matches)
11
+ return;
12
+ let disposed = false;
13
+ let off;
14
+ (async () => {
15
+ const { gsap } = await import("gsap");
16
+ if (disposed)
17
+ return;
18
+ const qx = gsap.quickTo(btn, "x", { duration: 0.5, ease: "power3.out" });
19
+ const qy = gsap.quickTo(btn, "y", { duration: 0.5, ease: "power3.out" });
20
+ const qr = gsap.quickTo(btn, "rotation", { duration: 0.5, ease: "power3.out" });
21
+ const move = (e) => {
22
+ const r = btn.getBoundingClientRect();
23
+ const dx = e.clientX - (r.left + r.width / 2);
24
+ const dy = e.clientY - (r.top + r.height / 2);
25
+ if (Math.hypot(dx, dy) < radius) {
26
+ qx(dx * strength);
27
+ qy(dy * strength);
28
+ qr((dx / (r.width / 2)) * tilt);
29
+ }
30
+ else {
31
+ qx(0);
32
+ qy(0);
33
+ qr(0);
34
+ }
35
+ };
36
+ window.addEventListener("pointermove", move);
37
+ off = () => {
38
+ window.removeEventListener("pointermove", move);
39
+ gsap.killTweensOf(btn);
40
+ gsap.set(btn, { x: 0, y: 0, rotation: 0 });
41
+ };
42
+ })();
43
+ return () => {
44
+ disposed = true;
45
+ off?.();
46
+ };
47
+ }, [strength, radius, tilt]);
48
+ return _jsx(Button, { ref: ref, className: className, ...button });
49
+ }
@@ -0,0 +1,29 @@
1
+ import { type Tone, type Variant } from "./variants.js";
2
+ export type MarqueeProps = {
3
+ text: string;
4
+ /** segundos que tarda una vuelta completa (más = más lento) */
5
+ duration?: number;
6
+ /** @deprecated usa `duration` */
7
+ speed?: number;
8
+ rows?: number;
9
+ variant?: Variant;
10
+ tone?: Tone;
11
+ /** alternate = las filas van en sentidos opuestos */
12
+ direction?: "alternate" | "left" | "right";
13
+ separator?: string;
14
+ /** tamaño de letra en px */
15
+ fontSize?: number;
16
+ /** @deprecated usa `fontSize` */
17
+ size?: number;
18
+ pauseOnHover?: boolean;
19
+ /** difumina los extremos */
20
+ edgeFade?: boolean;
21
+ /** inclina la cinta en grados */
22
+ tilt?: number;
23
+ className?: string;
24
+ };
25
+ /**
26
+ * Cinta de texto en bucle, en CSS puro (sin JS). El estilo lo da la variante; el color, el token elegido.
27
+ * El texto se repite solo para llenar el ancho; el lector de pantalla recibe una sola vez.
28
+ */
29
+ export default function Marquee({ text, duration, speed: legacySpeed, rows, variant, tone, direction, separator, fontSize, size: legacySize, pauseOnHover, edgeFade, tilt, className, }: MarqueeProps): import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { tcls } from "./variants.js";
3
+ /**
4
+ * Cinta de texto en bucle, en CSS puro (sin JS). El estilo lo da la variante; el color, el token elegido.
5
+ * El texto se repite solo para llenar el ancho; el lector de pantalla recibe una sola vez.
6
+ */
7
+ export default function Marquee({ text, duration, speed: legacySpeed, rows = 2, variant = "terminal", tone = "acc", direction = "alternate", separator = " // ", fontSize, size: legacySize, pauseOnHover = true, edgeFade = false, tilt = 0, className = "", }) {
8
+ const speed = duration ?? legacySpeed ?? 18;
9
+ const size = fontSize ?? legacySize ?? 28;
10
+ const track = `${text}${separator}`.repeat(6);
11
+ return (_jsx("div", { className: `ui-marquee ui-marquee--${variant} ${tcls(tone)} ${pauseOnHover ? "ui-marquee--pause" : ""} ${edgeFade ? "ui-marquee--edges" : ""} ${className}`, style: { fontSize: size, transform: tilt ? `rotate(${tilt}deg)` : undefined }, role: "marquee", "aria-label": text, children: Array.from({ length: Math.max(1, rows) }, (_, i) => (_jsx("div", { className: "ui-marquee__row", "aria-hidden": true, children: _jsxs("div", { className: "ui-marquee__track", style: {
12
+ animationDuration: `${speed * (1 + (i % 3) * 0.25)}s`,
13
+ animationDirection: direction === "left" ? "normal" : direction === "right" ? "reverse" : i % 2 ? "reverse" : "normal",
14
+ }, children: [_jsx("span", { children: track }), _jsx("span", { children: track })] }) }, i))) }));
15
+ }
@@ -0,0 +1,40 @@
1
+ import type { Intent } from "./intent.js";
2
+ import { type Variant } from "./variants.js";
3
+ export type ModalProps = {
4
+ /** texto del botón que abre el diálogo */
5
+ triggerLabel?: string;
6
+ /** @deprecated usa `triggerLabel` */
7
+ trigger?: string;
8
+ title?: string;
9
+ body?: string;
10
+ confirmLabel?: string;
11
+ cancelLabel?: string;
12
+ size?: "sm" | "md" | "lg";
13
+ /** color del botón de confirmar (y del disparador): `danger` para acciones destructivas, como borrar */
14
+ intent?: Intent;
15
+ variant?: Variant;
16
+ /** cambia para abrir el diálogo desde fuera (botón «Repetir animación» del catálogo) */
17
+ playKey?: number;
18
+ /**
19
+ * Modo controlado: si se pasa (aunque sea `false`), sustituye al estado interno y el propio
20
+ * `Modal` deja de pintar su botón `trigger` — es el propio proyecto quien decide cuándo se abre
21
+ * (p. ej. al enviar un formulario). Se cierra llamando a `onOpenChange(false)`.
22
+ */
23
+ open?: boolean;
24
+ onOpenChange?: (open: boolean) => void;
25
+ /** se llama al pulsar «confirmar», antes de cerrar el diálogo */
26
+ onConfirm?: () => void;
27
+ /** se llama al pulsar «cancelar», el fondo o Escape, antes de cerrar el diálogo */
28
+ onCancel?: () => void;
29
+ className?: string;
30
+ };
31
+ /**
32
+ * Diálogo modal: un botón lo abre, un fondo semitransparente cubre la página. Se cierra al pulsar
33
+ * fuera, con Escape o con sus propios botones. `position: fixed` (no portal): escapa de cualquier
34
+ * contenedor con `overflow` sin necesitar un `createPortal`.
35
+ *
36
+ * Sin `open`, es autónomo (pinta su propio botón `trigger` y lleva su estado). Con `open` pasa a
37
+ * modo controlado: no pinta el botón `trigger` (se abre desde donde decida quien lo usa) y notifica
38
+ * cada cambio por `onOpenChange`; `onConfirm`/`onCancel` avisan de qué botón se pulsó.
39
+ */
40
+ export default function Modal({ triggerLabel, trigger: legacyTrigger, title, body, confirmLabel, cancelLabel, size, intent, variant, playKey, open: openProp, onOpenChange, onConfirm, onCancel, className, }: ModalProps): import("react").JSX.Element;
@@ -0,0 +1,51 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import Button from "./Button.js";
5
+ import { vcls } from "./variants.js";
6
+ /**
7
+ * Diálogo modal: un botón lo abre, un fondo semitransparente cubre la página. Se cierra al pulsar
8
+ * fuera, con Escape o con sus propios botones. `position: fixed` (no portal): escapa de cualquier
9
+ * contenedor con `overflow` sin necesitar un `createPortal`.
10
+ *
11
+ * Sin `open`, es autónomo (pinta su propio botón `trigger` y lleva su estado). Con `open` pasa a
12
+ * modo controlado: no pinta el botón `trigger` (se abre desde donde decida quien lo usa) y notifica
13
+ * cada cambio por `onOpenChange`; `onConfirm`/`onCancel` avisan de qué botón se pulsó.
14
+ */
15
+ export default function Modal({ triggerLabel, trigger: legacyTrigger, title = "¿Seguro que quieres continuar?", body = "Esta acción no se puede deshacer. Se borrarán todos tus datos y no podrás recuperarlos.", confirmLabel = "Sí, eliminar", cancelLabel = "Cancelar", size = "md", intent = "accent", variant = "glass", playKey = 0, open: openProp, onOpenChange, onConfirm, onCancel, className = "", }) {
16
+ const trigger = triggerLabel ?? legacyTrigger ?? "Eliminar cuenta";
17
+ const controlled = openProp !== undefined;
18
+ const [openState, setOpenState] = useState(false);
19
+ const open = controlled ? openProp : openState;
20
+ const setOpen = (v) => (controlled ? onOpenChange?.(v) : setOpenState(v));
21
+ const panelRef = useRef(null);
22
+ useEffect(() => {
23
+ if (playKey > 0)
24
+ setOpen(true);
25
+ // eslint-disable-next-line react-hooks/exhaustive-deps
26
+ }, [playKey]);
27
+ useEffect(() => {
28
+ if (!open)
29
+ return;
30
+ panelRef.current?.focus();
31
+ const onKey = (e) => {
32
+ if (e.key === "Escape") {
33
+ onCancel?.();
34
+ setOpen(false);
35
+ }
36
+ };
37
+ window.addEventListener("keydown", onKey);
38
+ return () => window.removeEventListener("keydown", onKey);
39
+ // eslint-disable-next-line react-hooks/exhaustive-deps
40
+ }, [open]);
41
+ return (_jsxs("div", { className: className, children: [!controlled && _jsx(Button, { label: trigger, variant: variant, emphasis: "primary", intent: intent, onClick: () => setOpen(true) }), open && (_jsx("div", { className: "ui-modal__backdrop", onClick: () => {
42
+ onCancel?.();
43
+ setOpen(false);
44
+ }, children: _jsxs("div", { ref: panelRef, role: "dialog", "aria-modal": "true", "aria-label": title, tabIndex: -1, className: `ui-modal ui-modal--${size} ui-surface ${vcls(variant)}`, onClick: (e) => e.stopPropagation(), children: [_jsx("h3", { children: title }), _jsx("p", { children: body }), _jsxs("div", { className: "ui-modal__actions", children: [_jsx(Button, { label: cancelLabel, variant: variant, emphasis: "ghost", onClick: () => {
45
+ onCancel?.();
46
+ setOpen(false);
47
+ } }), _jsx(Button, { label: confirmLabel, variant: variant, emphasis: "primary", intent: intent, onClick: () => {
48
+ onConfirm?.();
49
+ setOpen(false);
50
+ } })] })] }) }))] }));
51
+ }
@@ -0,0 +1,14 @@
1
+ import { type Variant } from "./variants.js";
2
+ export type NavBarProps = {
3
+ brand?: string;
4
+ /** enlaces separados por comas */
5
+ links?: string;
6
+ cta?: string;
7
+ defaultActive?: number;
8
+ variant?: Variant;
9
+ /** barra flotante con forma de píldora, en lugar de a todo el ancho */
10
+ floating?: boolean;
11
+ className?: string;
12
+ };
13
+ /** Barra de navegación con marca, enlaces y llamada a la acción. El enlace activo cambia con la variante. */
14
+ export default function NavBar({ brand, links, cta, defaultActive, variant, floating, className }: NavBarProps): import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import Button from "./Button.js";
5
+ import { vcls } from "./variants.js";
6
+ /** Barra de navegación con marca, enlaces y llamada a la acción. El enlace activo cambia con la variante. */
7
+ export default function NavBar({ brand = "Maré", links = "Clases, Tablas, Reservas, Contacto", cta = "Reservar", defaultActive = 0, variant = "glass", floating = false, className = "" }) {
8
+ const items = useMemo(() => links.split(",").map((s) => s.trim()).filter(Boolean), [links]);
9
+ const [active, setActive] = useState(defaultActive);
10
+ useEffect(() => setActive(defaultActive), [defaultActive]);
11
+ return (_jsxs("nav", { "aria-label": "Principal", className: `ui-nav ui-surface ${floating ? "ui-nav--floating" : ""} ${vcls(variant)} ${className}`, children: [_jsx("span", { className: "ui-nav__brand", children: brand }), _jsx("ul", { className: "ui-nav__links", children: items.map((l, i) => (_jsx("li", { children: _jsx("button", { className: "ui-nav__link", "aria-current": i === active, onClick: () => setActive(i), children: l }) }, l + i))) }), cta && _jsx(Button, { label: cta, variant: variant, size: "sm" })] }));
12
+ }
@@ -0,0 +1,14 @@
1
+ import { type Tone } from "./variants.js";
2
+ export type NeonSignProps = {
3
+ text?: string;
4
+ mode?: "tube" | "outline" | "pixel";
5
+ tone?: Extract<Tone, "acc" | "acc2" | "fg">;
6
+ /** tamaño de letra en px */
7
+ fontSize?: number;
8
+ /** @deprecated usa `fontSize` */
9
+ size?: number;
10
+ flicker?: boolean;
11
+ className?: string;
12
+ };
13
+ /** Rótulo luminoso: tubo de neón, contorno o sombra de píxel. Usa `--acc` / `--acc2`. */
14
+ export default function NeonSign({ text, mode, tone, fontSize, size: legacySize, flicker, className }: NeonSignProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { tcls } from "./variants.js";
3
+ /** Rótulo luminoso: tubo de neón, contorno o sombra de píxel. Usa `--acc` / `--acc2`. */
4
+ export default function NeonSign({ text = "ABIERTO", mode = "tube", tone = "acc", fontSize, size: legacySize, flicker = true, className = "" }) {
5
+ const size = fontSize ?? legacySize ?? 64;
6
+ return (_jsx("p", { className: `ui-neon ui-neon--${mode} ${flicker ? "ui-neon--flicker" : ""} ${tcls(tone)} ${className}`, style: { fontSize: size, margin: 0 }, children: text }));
7
+ }
@@ -0,0 +1,9 @@
1
+ import { type Variant } from "./variants.js";
2
+ export type OtpInputProps = {
3
+ length?: number;
4
+ size?: "sm" | "md" | "lg";
5
+ variant?: Variant;
6
+ className?: string;
7
+ };
8
+ /** Casillas de código de verificación: el foco avanza solo al escribir y retrocede con Retroceso. */
9
+ export default function OtpInput({ length, size, variant, className }: OtpInputProps): import("react").JSX.Element;