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,48 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { forwardRef } from "react";
4
+ import { renderGlyph } from "./Icon.js";
5
+ import { INTENTS } from "./intent.js";
6
+ import { vcls } from "./variants.js";
7
+ /** Color del botón: el vocabulario común de todo el kit (ver intent.ts). */
8
+ export const BUTTON_INTENTS = INTENTS;
9
+ /** Énfasis: primary = relleno · secondary = superficie del estilo · outline = solo borde · ghost = solo texto · link = enlace */
10
+ export const BUTTON_EMPHASES = ["primary", "secondary", "outline", "ghost", "link"];
11
+ /** @deprecated usa `BUTTON_EMPHASES` / `ButtonEmphasis` */
12
+ export const BUTTON_TONES = BUTTON_EMPHASES;
13
+ /**
14
+ * Botón del sistema. La variante decide superficie, borde, radio, sombra y tipografía; `emphasis` el énfasis e `intent`
15
+ * el color (el intent sustituye `--acc` solo dentro del botón, así que funciona igual en los 7 estilos).
16
+ */
17
+ const Button = forwardRef(function Button({ label, variant = "solid", emphasis, tone: legacyTone, intent = "accent", size = "md", glyph = "", glyphPosition = "end", iconOnly = false, fullWidth = false, disabled = false, active, loading = false, href, type = "button", onClick, className = "", ...aria }, ref) {
18
+ const tone = emphasis ?? legacyTone ?? "primary"; // (la clase CSS sigue siendo ui-button--<énfasis>)
19
+ const g = glyph ? _jsx("span", { className: "ui-button__glyph", "aria-hidden": true, children: renderGlyph(glyph) }) : null;
20
+ const blocked = disabled || loading;
21
+ const cls = [
22
+ "ui-button",
23
+ `ui-button--${tone}`,
24
+ `ui-button--${size}`,
25
+ `ui-intent--${intent}`,
26
+ fullWidth && "ui-button--full",
27
+ iconOnly && "ui-button--icon",
28
+ active && "is-active",
29
+ loading && "is-loading",
30
+ vcls(variant),
31
+ className,
32
+ ]
33
+ .filter(Boolean)
34
+ .join(" ");
35
+ const content = (_jsxs(_Fragment, { children: [loading && _jsx("span", { className: "ui-button__spin", "aria-hidden": true }), !iconOnly && glyphPosition === "start" && g, iconOnly ? (g ?? _jsx("span", { "aria-hidden": true, children: label.slice(0, 1) })) : _jsx("span", { className: "ui-button__label", children: label }), !iconOnly && glyphPosition === "end" && g] }));
36
+ const common = {
37
+ ...aria,
38
+ className: cls,
39
+ "aria-label": iconOnly ? label : aria["aria-label"],
40
+ "aria-pressed": active === undefined ? aria["aria-pressed"] : active,
41
+ "aria-busy": loading || undefined,
42
+ };
43
+ if (href !== undefined) {
44
+ return (_jsx("a", { ref: ref, ...common, href: blocked ? undefined : href, "aria-disabled": blocked || undefined, role: active === undefined ? undefined : "button", onClick: blocked ? undefined : onClick, children: content }));
45
+ }
46
+ return (_jsx("button", { ref: ref, ...common, type: type, disabled: blocked, onClick: onClick, children: content }));
47
+ });
48
+ export default Button;
@@ -0,0 +1,13 @@
1
+ import { type Variant } from "./variants.js";
2
+ export type CTASectionProps = {
3
+ kicker?: string;
4
+ title?: string;
5
+ subtitle?: string;
6
+ primaryCta?: string;
7
+ secondaryCta?: string;
8
+ align?: "left" | "center";
9
+ variant?: Variant;
10
+ className?: string;
11
+ };
12
+ /** Banner de llamada a la acción final: titular corto, una frase y uno o dos botones sobre una superficie destacada. */
13
+ export default function CTASection({ kicker, title, subtitle, primaryCta, secondaryCta, align, variant, className, }: CTASectionProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Button from "./Button.js";
3
+ import { vcls } from "./variants.js";
4
+ /** Banner de llamada a la acción final: titular corto, una frase y uno o dos botones sobre una superficie destacada. */
5
+ export default function CTASection({ kicker = "", title = "El swell llega el sábado", subtitle = "Quedan doce plazas. Trae toalla, nosotros ponemos el resto.", primaryCta = "Apuntarme", secondaryCta = "", align = "center", variant = "neon", className = "", }) {
6
+ return (_jsxs("section", { className: `ui-cta ui-cta--${align} ui-surface ${vcls(variant)} ${className}`, children: [kicker && _jsx("span", { className: "ui-cta__kicker", children: kicker }), _jsx("h3", { children: title }), subtitle && _jsx("p", { children: subtitle }), (primaryCta || secondaryCta) && (_jsxs("div", { className: "ui-cta__actions", children: [primaryCta && _jsx(Button, { label: primaryCta, variant: variant, emphasis: "primary", glyph: "\u2192" }), secondaryCta && _jsx(Button, { label: secondaryCta, variant: variant, emphasis: "ghost" })] }))] }));
7
+ }
@@ -0,0 +1,30 @@
1
+ import { type ReactNode } from "react";
2
+ import "./swiperCss.js";
3
+ import { type Variant } from "./variants.js";
4
+ export type CarouselProps = {
5
+ /** cada hijo es una diapositiva */
6
+ children: ReactNode;
7
+ /** diapositivas visibles en pantallas anchas (en móvil baja sola a 1, en tablet a 2). Ignorado con `effect` ≠ slide */
8
+ perView?: number;
9
+ /** separación entre diapositivas, en px */
10
+ gap?: number;
11
+ loop?: boolean;
12
+ /** segundos entre diapositivas; 0 = sin reproducción automática (siempre apagado con prefers-reduced-motion) */
13
+ autoplay?: number;
14
+ arrows?: boolean;
15
+ dots?: "none" | "bullets" | "progress" | "fraction";
16
+ /** slide = desplazamiento · fade = fundido · cards = baraja apilada */
17
+ effect?: "slide" | "fade" | "cards";
18
+ /** cinta continua sin paradas (marcas, logos): ignora flechas, puntos y `perView` */
19
+ ticker?: boolean;
20
+ variant?: Variant;
21
+ /** nombre accesible del carrusel */
22
+ label?: string;
23
+ className?: string;
24
+ };
25
+ /**
26
+ * Carrusel genérico sobre Swiper (https://swiperjs.com/) con el lenguaje del kit: flechas y puntos cuadrados que leen las
27
+ * variables `--s-*` de la variante, y sin estilos propios de color (todo sale de los tokens). Sirve para cualquier lista de
28
+ * tarjetas: los componentes de sección lo usan con su prop `layout="carousel"`.
29
+ */
30
+ export default function Carousel({ children, perView, gap, loop, autoplay, arrows, dots, effect, ticker, variant, label, className }: CarouselProps): import("react").JSX.Element;
@@ -0,0 +1,80 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Children, useEffect, useMemo, useState } from "react";
4
+ import { A11y, Autoplay, EffectCards, EffectFade, FreeMode, Keyboard, Navigation, Pagination } from "swiper/modules";
5
+ import { Swiper, SwiperSlide } from "swiper/react";
6
+ import "./swiperCss.js";
7
+ import { vcls } from "./variants.js";
8
+ function usePrefersReducedMotion() {
9
+ const [reduced, setReduced] = useState(false);
10
+ useEffect(() => {
11
+ const mq = matchMedia("(prefers-reduced-motion: reduce)");
12
+ setReduced(mq.matches);
13
+ const on = () => setReduced(mq.matches);
14
+ mq.addEventListener("change", on);
15
+ return () => mq.removeEventListener("change", on);
16
+ }, []);
17
+ return reduced;
18
+ }
19
+ /**
20
+ * Carrusel genérico sobre Swiper (https://swiperjs.com/) con el lenguaje del kit: flechas y puntos cuadrados que leen las
21
+ * variables `--s-*` de la variante, y sin estilos propios de color (todo sale de los tokens). Sirve para cualquier lista de
22
+ * tarjetas: los componentes de sección lo usan con su prop `layout="carousel"`.
23
+ */
24
+ export default function Carousel({ children, perView = 3, gap = 20, loop = false, autoplay = 0, arrows = true, dots = "bullets", effect = "slide", ticker = false, variant = "glass", label = "Carrusel", className = "" }) {
25
+ const reduced = usePrefersReducedMotion();
26
+ const slides = Children.toArray(children);
27
+ const single = effect !== "slide" && !ticker;
28
+ const pv = single ? 1 : Math.max(1, Math.round(perView));
29
+ const play = !reduced && (ticker || autoplay > 0);
30
+ const config = useMemo(() => {
31
+ const modules = [A11y, Keyboard];
32
+ const props = { a11y: { containerMessage: label }, keyboard: { enabled: true, onlyInViewport: true }, spaceBetween: gap, grabCursor: true };
33
+ if (ticker) {
34
+ modules.push(FreeMode, Autoplay);
35
+ Object.assign(props, {
36
+ slidesPerView: "auto",
37
+ loop: true,
38
+ freeMode: { enabled: true, momentum: false },
39
+ speed: 6000,
40
+ allowTouchMove: false,
41
+ autoplay: play ? { delay: 0, disableOnInteraction: false, pauseOnMouseEnter: true } : false,
42
+ });
43
+ return { modules, props };
44
+ }
45
+ if (effect === "fade") {
46
+ modules.push(EffectFade);
47
+ Object.assign(props, { effect: "fade", fadeEffect: { crossFade: true }, slidesPerView: 1 });
48
+ }
49
+ else if (effect === "cards") {
50
+ modules.push(EffectCards);
51
+ Object.assign(props, { effect: "cards", slidesPerView: 1, cardsEffect: { slideShadows: false, perSlideOffset: 10, perSlideRotate: 3 } });
52
+ }
53
+ else {
54
+ Object.assign(props, {
55
+ slidesPerView: 1,
56
+ breakpoints: { 0: { slidesPerView: 1 }, 700: { slidesPerView: Math.min(2, pv) }, 1000: { slidesPerView: pv } },
57
+ });
58
+ }
59
+ if (arrows) {
60
+ modules.push(Navigation);
61
+ props.navigation = true;
62
+ }
63
+ if (dots !== "none") {
64
+ modules.push(Pagination);
65
+ props.pagination = { type: dots === "bullets" ? "bullets" : dots === "progress" ? "progressbar" : "fraction", clickable: dots === "bullets" };
66
+ }
67
+ if (autoplay > 0) {
68
+ modules.push(Autoplay);
69
+ props.autoplay = play ? { delay: autoplay * 1000, disableOnInteraction: false, pauseOnMouseEnter: true } : false;
70
+ }
71
+ props.loop = loop && slides.length > pv;
72
+ return { modules, props };
73
+ }, [ticker, effect, arrows, dots, autoplay, play, loop, gap, pv, slides.length, label]);
74
+ // La cinta necesita más diapositivas que el ancho del contenedor para no dejar huecos al dar la vuelta
75
+ const items = ticker ? [...slides, ...slides, ...slides] : slides;
76
+ const cls = ["ui-car", vcls(variant), ticker ? "ui-car--ticker" : "", effect === "cards" ? "ui-car--cards" : "", effect === "fade" ? "ui-car--fade" : "", className].filter(Boolean).join(" ");
77
+ // Cambiar módulos o efecto exige montar de nuevo el Swiper
78
+ const key = `${ticker}-${effect}-${pv}-${arrows}-${dots}-${autoplay > 0}-${play}-${loop}`;
79
+ return (_jsx("div", { className: cls, children: _jsx(Swiper, { modules: config.modules, ...config.props, children: items.map((s, i) => (_jsx(SwiperSlide, { children: s }, i))) }, key) }));
80
+ }
@@ -0,0 +1,11 @@
1
+ import { type Variant } from "./variants.js";
2
+ export type CheckboxGroupProps = {
3
+ label?: string;
4
+ options?: string;
5
+ /** valores marcados al inicio, separados por comas */
6
+ defaultValue?: string;
7
+ variant?: Variant;
8
+ className?: string;
9
+ };
10
+ /** Lista de casillas independientes. Retro y terminal las muestran como texto `[x]`/`[ ]` (ver CSS `--s-ff`). */
11
+ export default function CheckboxGroup({ label, options, defaultValue, variant, className, }: CheckboxGroupProps): import("react").JSX.Element;
@@ -0,0 +1,20 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { vcls } from "./variants.js";
5
+ /** Lista de casillas independientes. Retro y terminal las muestran como texto `[x]`/`[ ]` (ver CSS `--s-ff`). */
6
+ export default function CheckboxGroup({ label = "Notificaciones", options = "Email, SMS, Push, Newsletter", defaultValue = "Email, Push", variant = "glass", className = "", }) {
7
+ const list = useMemo(() => options.split(",").map((s) => s.trim()).filter(Boolean), [options]);
8
+ const initial = useMemo(() => new Set(defaultValue.split(",").map((s) => s.trim()).filter(Boolean)), [defaultValue]);
9
+ const [checked, setChecked] = useState(initial);
10
+ useEffect(() => setChecked(initial), [initial]);
11
+ const toggle = (o) => setChecked((s) => {
12
+ const next = new Set(s);
13
+ if (next.has(o))
14
+ next.delete(o);
15
+ else
16
+ next.add(o);
17
+ return next;
18
+ });
19
+ return (_jsxs("fieldset", { className: `ui-cgroup ${vcls(variant)} ${className}`, children: [label && _jsx("legend", { children: label }), list.map((o) => (_jsxs("label", { className: "ui-cgroup__item", children: [_jsx("button", { type: "button", role: "checkbox", "aria-checked": checked.has(o), className: `ui-cgroup__box ${checked.has(o) ? "is-on" : ""}`, onClick: () => toggle(o), children: _jsx("span", { "aria-hidden": true, children: "\u2713" }) }), o] }, o)))] }));
20
+ }
@@ -0,0 +1,11 @@
1
+ import { type Variant } from "./variants.js";
2
+ export type CodeBlockProps = {
3
+ code?: string;
4
+ language?: string;
5
+ filename?: string;
6
+ showLineNumbers?: boolean;
7
+ variant?: Variant;
8
+ className?: string;
9
+ };
10
+ /** Bloque de código con cabecera, numeración de línea opcional y botón de copiar. Sin resaltado de sintaxis. */
11
+ export default function CodeBlock({ code, language, filename, showLineNumbers, variant, className, }: CodeBlockProps): import("react").JSX.Element;
@@ -0,0 +1,20 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { vcls } from "./variants.js";
5
+ /** Bloque de código con cabecera, numeración de línea opcional y botón de copiar. Sin resaltado de sintaxis. */
6
+ export default function CodeBlock({ code = 'export function greet(name: string) {\n return `Hola, ${name}`;\n}', language = "tsx", filename = "", showLineNumbers = true, variant = "terminal", className = "", }) {
7
+ const [copied, setCopied] = useState(false);
8
+ const lines = code.split("\n");
9
+ const copy = async () => {
10
+ try {
11
+ await navigator.clipboard.writeText(code);
12
+ setCopied(true);
13
+ setTimeout(() => setCopied(false), 1400);
14
+ }
15
+ catch {
16
+ /* portapapeles no disponible */
17
+ }
18
+ };
19
+ return (_jsxs("div", { className: `ui-code ui-surface ${vcls(variant)} ${className}`, children: [_jsxs("div", { className: "ui-code__head", children: [_jsxs("div", { className: "ui-code__dots", "aria-hidden": true, children: [_jsx("i", {}), _jsx("i", {}), _jsx("i", {})] }), _jsx("span", { className: "ui-code__name", children: filename || language }), _jsx("button", { type: "button", className: "ui-code__copy", onClick: copy, children: copied ? "Copiado ✓" : "Copiar" })] }), _jsx("pre", { className: "ui-code__pre", children: _jsx("code", { children: lines.map((line, i) => (_jsxs("span", { className: "ui-code__line", children: [showLineNumbers && (_jsx("span", { className: "ui-code__ln", "aria-hidden": true, children: i + 1 })), _jsx("span", { children: line || " " })] }, i))) }) })] }));
20
+ }
@@ -0,0 +1,25 @@
1
+ import { type Variant } from "./variants.js";
2
+ export type ContactFormProps = {
3
+ title?: string;
4
+ subtitle?: string;
5
+ /** qué campos mostrar, separados por comas: "name,email,message" (contacto) o "email" (boletín) */
6
+ fields?: string;
7
+ namePlaceholder?: string;
8
+ emailPlaceholder?: string;
9
+ messagePlaceholder?: string;
10
+ submitLabel?: string;
11
+ successTitle?: string;
12
+ successMessage?: string;
13
+ /** stacked = formulario en columna; inline = campo(s) y botón en una fila (boletín) */
14
+ layout?: "stacked" | "inline";
15
+ variant?: Variant;
16
+ /** se llama con los datos del formulario al enviarlo; la demo siempre muestra el estado de éxito */
17
+ onSubmit?: (data: Record<string, string>) => void;
18
+ className?: string;
19
+ };
20
+ /**
21
+ * Formulario de contacto (o de boletín, con `fields="email"` y `layout="inline"`). Gestiona su propio
22
+ * estado de envío: al enviarlo muestra un mensaje de éxito con opción de volver a escribir. El envío
23
+ * real (llamada a la API, proveedor de email…) se conecta con `onSubmit`.
24
+ */
25
+ export default function ContactForm({ title, subtitle, fields, namePlaceholder, emailPlaceholder, messagePlaceholder, submitLabel, successTitle, successMessage, layout, variant, onSubmit, className, }: ContactFormProps): import("react").JSX.Element;
@@ -0,0 +1,26 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import Button from "./Button.js";
5
+ import TextField from "./TextField.js";
6
+ import { vcls } from "./variants.js";
7
+ /**
8
+ * Formulario de contacto (o de boletín, con `fields="email"` y `layout="inline"`). Gestiona su propio
9
+ * estado de envío: al enviarlo muestra un mensaje de éxito con opción de volver a escribir. El envío
10
+ * real (llamada a la API, proveedor de email…) se conecta con `onSubmit`.
11
+ */
12
+ export default function ContactForm({ title = "Escríbenos", subtitle = "Te respondemos en menos de 24 horas.", fields = "name,email,message", namePlaceholder = "Tu nombre", emailPlaceholder = "tu@correo.com", messagePlaceholder = "Cuéntanos qué necesitas…", submitLabel = "Enviar mensaje", successTitle = "Mensaje enviado", successMessage = "Gracias, te contestaremos pronto.", layout = "stacked", variant = "glass", onSubmit, className = "", }) {
13
+ const list = fields.split(",").map((s) => s.trim());
14
+ const has = (k) => list.includes(k);
15
+ const [sent, setSent] = useState(false);
16
+ const submit = (e) => {
17
+ e.preventDefault();
18
+ const data = Object.fromEntries(new FormData(e.currentTarget).entries());
19
+ onSubmit?.(data);
20
+ setSent(true);
21
+ };
22
+ if (sent) {
23
+ return (_jsxs("div", { className: `ui-cform ui-cform--sent ui-surface ${vcls(variant)} ${className}`, children: [_jsx("strong", { children: successTitle }), _jsx("p", { children: successMessage }), _jsx("button", { type: "button", className: "ui-cform__again", onClick: () => setSent(false), children: "Enviar otro mensaje" })] }));
24
+ }
25
+ return (_jsxs("form", { className: `ui-cform ui-cform--${layout} ui-surface ${vcls(variant)} ${className}`, onSubmit: submit, children: [title && _jsx("h3", { children: title }), subtitle && _jsx("p", { className: "ui-cform__sub", children: subtitle }), _jsxs("div", { className: "ui-cform__row", children: [has("name") && _jsx(TextField, { label: layout === "inline" ? "" : "Nombre", name: "name", placeholder: namePlaceholder, variant: variant, required: true }), has("email") && _jsx(TextField, { label: layout === "inline" ? "" : "Email", name: "email", type: "email", placeholder: emailPlaceholder, variant: variant, required: true }), has("message") && _jsx(TextField, { label: "Mensaje", name: "message", placeholder: messagePlaceholder, variant: variant, multiline: true, rows: 4, required: true }), _jsx(Button, { type: "submit", label: submitLabel, variant: variant, emphasis: "primary", fullWidth: layout === "stacked" })] })] }));
26
+ }
@@ -0,0 +1,10 @@
1
+ export type CrtOverlayProps = {
2
+ scanlines?: number;
3
+ vignette?: number;
4
+ flicker?: number;
5
+ /** barra de barrido que recorre la pantalla */
6
+ sweep?: boolean;
7
+ className?: string;
8
+ };
9
+ /** Capa de efectos de monitor CRT (líneas de barrido, viñeta, parpadeo). Se superpone al contenedor y no bloquea el puntero. */
10
+ export default function CrtOverlay({ scanlines, vignette, flicker, sweep, className }: CrtOverlayProps): import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** Capa de efectos de monitor CRT (líneas de barrido, viñeta, parpadeo). Se superpone al contenedor y no bloquea el puntero. */
3
+ export default function CrtOverlay({ scanlines = 0.3, vignette = 0.6, flicker = 0.4, sweep = true, className = "" }) {
4
+ return (_jsxs("div", { className: `ui-crt ${className}`, style: { "--scan": scanlines, "--vig": vignette, "--flk": flicker }, "aria-hidden": true, children: [_jsx("div", { className: "ui-crt__scan" }), flicker > 0 && _jsx("div", { className: "ui-crt__flick" }), sweep && _jsx("div", { className: "ui-crt__bar" }), _jsx("div", { className: "ui-crt__vig" })] }));
5
+ }
@@ -0,0 +1,12 @@
1
+ import { type Variant } from "./variants.js";
2
+ export type DividerProps = {
3
+ label?: string;
4
+ /** caracteres de la regla en el modo texto */
5
+ pattern?: string;
6
+ /** line = línea CSS; text = repetición de `pattern` */
7
+ mode?: "line" | "text";
8
+ variant?: Variant;
9
+ className?: string;
10
+ };
11
+ /** Separador con etiqueta opcional. El modo `text` dibuja la regla con caracteres. */
12
+ export default function Divider({ label, pattern, mode, variant, className }: DividerProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { vcls } from "./variants.js";
3
+ /** Separador con etiqueta opcional. El modo `text` dibuja la regla con caracteres. */
4
+ export default function Divider({ label = "o continúa con", pattern = "-=", mode = "line", variant = "minimal", className = "" }) {
5
+ const rule = (k) => (_jsx("span", { className: `ui-div__rule ${mode === "line" ? "ui-div__rule--line" : ""}`, "aria-hidden": true, children: mode === "text" ? (pattern || "-").repeat(80) : "" }, k));
6
+ return (_jsxs("div", { role: "separator", "aria-label": label, className: `ui-div ${vcls(variant)} ${className}`, children: [rule("a"), label && _jsx("span", { children: label }), rule("b")] }));
7
+ }
@@ -0,0 +1,16 @@
1
+ import { type Variant } from "./variants.js";
2
+ export type DrawerProps = {
3
+ /** texto del botón que abre el panel */
4
+ triggerLabel?: string;
5
+ /** @deprecated usa `triggerLabel` */
6
+ trigger?: string;
7
+ side?: "left" | "right";
8
+ title?: string;
9
+ body?: string;
10
+ variant?: Variant;
11
+ /** cambia para abrir el panel desde fuera (botón «Repetir animación» del catálogo) */
12
+ playKey?: number;
13
+ className?: string;
14
+ };
15
+ /** Panel lateral deslizante (menú móvil, carrito, filtros). Se cierra con el fondo, Escape o su botón. */
16
+ export default function Drawer({ triggerLabel, trigger: legacyTrigger, side, title, body, variant, playKey, className, }: DrawerProps): import("react").JSX.Element;
@@ -0,0 +1,25 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useState } from "react";
4
+ import Button from "./Button.js";
5
+ import { vcls } from "./variants.js";
6
+ /** Panel lateral deslizante (menú móvil, carrito, filtros). Se cierra con el fondo, Escape o su botón. */
7
+ export default function Drawer({ triggerLabel, trigger: legacyTrigger, side = "left", title = "Menú", body = "Clases, Tablas, Reservas, Contacto, Ayuda.", variant = "glass", playKey = 0, className = "", }) {
8
+ const trigger = triggerLabel ?? legacyTrigger ?? "Abrir menú";
9
+ const [open, setOpen] = useState(false);
10
+ useEffect(() => {
11
+ if (playKey > 0)
12
+ setOpen(true);
13
+ }, [playKey]);
14
+ useEffect(() => {
15
+ if (!open)
16
+ return;
17
+ const onKey = (e) => {
18
+ if (e.key === "Escape")
19
+ setOpen(false);
20
+ };
21
+ window.addEventListener("keydown", onKey);
22
+ return () => window.removeEventListener("keydown", onKey);
23
+ }, [open]);
24
+ return (_jsxs("div", { className: className, children: [_jsx(Button, { label: trigger, variant: variant, emphasis: "primary", onClick: () => setOpen(true) }), open && (_jsx("div", { className: "ui-drawer__backdrop", onClick: () => setOpen(false), children: _jsxs("div", { role: "dialog", "aria-modal": "true", "aria-label": title, className: `ui-drawer ui-drawer--${side} ui-surface ${vcls(variant)}`, onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { className: "ui-drawer__head", children: [_jsx("strong", { children: title }), _jsx("button", { type: "button", className: "ui-drawer__close", "aria-label": "Cerrar", onClick: () => setOpen(false), children: "\u00D7" })] }), _jsx("p", { children: body })] }) }))] }));
25
+ }
@@ -0,0 +1,10 @@
1
+ import { type Variant } from "./variants.js";
2
+ export type DropdownProps = {
3
+ label?: string;
4
+ items?: string;
5
+ align?: "left" | "right";
6
+ variant?: Variant;
7
+ className?: string;
8
+ };
9
+ /** Menú de acciones (⋮): un botón que abre una lista de comandos, no un formulario de selección (ver Select). */
10
+ export default function Dropdown({ label, items, align, variant, className }: DropdownProps): import("react").JSX.Element;
@@ -0,0 +1,21 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { vcls } from "./variants.js";
5
+ /** Menú de acciones (⋮): un botón que abre una lista de comandos, no un formulario de selección (ver Select). */
6
+ export default function Dropdown({ label = "⋮", items = "Editar, Duplicar, Archivar, Eliminar", align = "right", variant = "glass", className = "" }) {
7
+ const list = items.split(",").map((s) => s.trim()).filter(Boolean);
8
+ const [open, setOpen] = useState(false);
9
+ const root = useRef(null);
10
+ useEffect(() => {
11
+ if (!open)
12
+ return;
13
+ const onDoc = (e) => {
14
+ if (root.current && !root.current.contains(e.target))
15
+ setOpen(false);
16
+ };
17
+ document.addEventListener("mousedown", onDoc);
18
+ return () => document.removeEventListener("mousedown", onDoc);
19
+ }, [open]);
20
+ return (_jsxs("div", { ref: root, className: `ui-ddown ${className}`, children: [_jsx("button", { type: "button", className: `ui-ddown__trigger ui-surface ${vcls(variant)}`, "aria-haspopup": "menu", "aria-expanded": open, onClick: () => setOpen((v) => !v), children: label }), open && (_jsx("ul", { role: "menu", className: `ui-ddown__menu ui-ddown__menu--${align} ui-surface ${vcls(variant)}`, children: list.map((o) => (_jsx("li", { children: _jsx("button", { type: "button", role: "menuitem", className: "ui-ddown__item", onClick: () => setOpen(false), children: o }) }, o))) }))] }));
21
+ }
@@ -0,0 +1,14 @@
1
+ import type { Variant } from "./variants.js";
2
+ export type FAQSectionProps = {
3
+ kicker?: string;
4
+ title?: string;
5
+ subtitle?: string;
6
+ /** una entrada por línea con el formato «Pregunta|Respuesta» (igual que `Accordion`) */
7
+ items?: string;
8
+ multiple?: boolean;
9
+ align?: "left" | "center";
10
+ variant?: Variant;
11
+ className?: string;
12
+ };
13
+ /** Sección de preguntas frecuentes: cabecera + `Accordion`, lista para pegar al final de una landing. */
14
+ export default function FAQSection({ kicker, title, subtitle, items, multiple, align, variant, className, }: FAQSectionProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Accordion from "./Accordion.js";
3
+ import SectionHeader from "./SectionHeader.js";
4
+ /** Sección de preguntas frecuentes: cabecera + `Accordion`, lista para pegar al final de una landing. */
5
+ export default function FAQSection({ kicker = "Preguntas frecuentes", title = "Todo lo que necesitas saber", subtitle = "", items, multiple = true, align = "center", variant = "glass", className = "", }) {
6
+ return (_jsxs("section", { className: `ui-faqs ${align === "left" ? "ui-faqs--left" : ""} ${className}`, children: [_jsx(SectionHeader, { kicker: kicker, title: title, subtitle: subtitle, align: align, variant: "minimal" }), _jsx("div", { className: "ui-faqs__body", children: _jsx(Accordion, { items: items, multiple: multiple, variant: variant }) })] }));
7
+ }
@@ -0,0 +1,18 @@
1
+ import { type Variant } from "./variants.js";
2
+ export type FeatureGridProps = {
3
+ kicker?: string;
4
+ title?: string;
5
+ subtitle?: string;
6
+ /** una por línea: «glifo|título|texto». El glifo puede ser un icono pixel art: `icon:heart` (ver Icon) */
7
+ items?: string;
8
+ /** grid = rejilla · carousel = carrusel de Swiper */
9
+ layout?: "grid" | "carousel";
10
+ /** con layout="carousel": tarjetas visibles en pantallas anchas */
11
+ perView?: number;
12
+ /** con layout="carousel": segundos entre tarjetas (0 = manual) */
13
+ autoplay?: number;
14
+ variant?: Variant;
15
+ className?: string;
16
+ };
17
+ /** Cabecera de sección + rejilla de "por qué elegirnos" (glifo + título + texto), la más pedida en cualquier landing. */
18
+ export default function FeatureGrid({ kicker, title, subtitle, items, layout, perView, autoplay, variant, className }: FeatureGridProps): import("react").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Carousel from "./Carousel.js";
3
+ import { renderGlyph } from "./Icon.js";
4
+ import SectionHeader from "./SectionHeader.js";
5
+ import { vcls } from "./variants.js";
6
+ const DEFAULT_ITEMS = "~|Monitores titulados|Todo el equipo tiene titulación oficial de escuela de surf y primeros auxilios.\n" +
7
+ "#|Grupos reducidos|Máximo seis alumnos por clase para que cada uno reciba atención de verdad.\n" +
8
+ "+|Material incluido|Neopreno, tabla y bolsa de deporte, sin coste extra ni sorpresas.";
9
+ /** Cabecera de sección + rejilla de "por qué elegirnos" (glifo + título + texto), la más pedida en cualquier landing. */
10
+ export default function FeatureGrid({ kicker = "Por qué nosotros", title = "Lo que nos hace distintos", subtitle = "", items = DEFAULT_ITEMS, layout = "grid", perView = 3, autoplay = 0, variant = "minimal", className = "" }) {
11
+ const rows = items.split("\n").map((l) => l.split("|")).filter((r) => r[0]?.trim());
12
+ const cards = rows.map(([glyph, t, text], i) => (_jsxs("div", { className: `ui-feats__item ui-surface ${vcls(variant)}`, children: [_jsx("span", { className: "ui-feats__glyph", "aria-hidden": true, children: renderGlyph(glyph, 24) }), _jsx("strong", { children: t }), _jsx("p", { children: text })] }, t + i)));
13
+ return (_jsxs("section", { className: `ui-feats ${className}`, children: [_jsx(SectionHeader, { kicker: kicker, title: title, subtitle: subtitle, align: "center", variant: "minimal" }), layout === "carousel" ? (_jsx(Carousel, { perView: perView, autoplay: autoplay, variant: variant, label: title, children: cards })) : (_jsx("div", { className: "ui-feats__grid", children: cards }))] }));
14
+ }
@@ -0,0 +1,14 @@
1
+ import { type Variant } from "./variants.js";
2
+ export type FooterProps = {
3
+ brand?: string;
4
+ tagline?: string;
5
+ /** columnas separadas por «;», cada una «Título: enlace1, enlace2, enlace3» */
6
+ columns?: string;
7
+ /** iniciales o siglas de redes, separadas por comas; vacío = ninguna. Cada una puede ser un icono pixel art: `icon:github` (ver Icon) */
8
+ social?: string;
9
+ copyright?: string;
10
+ variant?: Variant;
11
+ className?: string;
12
+ };
13
+ /** Pie de página: marca, columnas de enlaces, redes y línea de copyright. Cierre habitual de cualquier landing. */
14
+ export default function Footer({ brand, tagline, columns, social, copyright, variant, className, }: FooterProps): import("react").JSX.Element;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { renderGlyph } from "./Icon.js";
3
+ import { vcls } from "./variants.js";
4
+ const DEFAULT_COLUMNS = "Escuela: Clases, Monitores, Ubicación; Ayuda: Reservas, Cancelaciones, FAQ; Legal: Privacidad, Términos, Cookies";
5
+ /** Pie de página: marca, columnas de enlaces, redes y línea de copyright. Cierre habitual de cualquier landing. */
6
+ export default function Footer({ brand = "Maré Surf Club", tagline = "Del primer remo a tu primera ola verde.", columns = DEFAULT_COLUMNS, social = "X, IG, YT", copyright = "© 2026 Maré Surf Club. Todos los derechos reservados.", variant = "minimal", className = "", }) {
7
+ const cols = columns
8
+ .split(";")
9
+ .map((c) => {
10
+ const [head, rest] = c.split(":");
11
+ return { title: head?.trim() ?? "", links: (rest ?? "").split(",").map((l) => l.trim()).filter(Boolean) };
12
+ })
13
+ .filter((c) => c.title);
14
+ const soc = social.split(",").map((s) => s.trim()).filter(Boolean);
15
+ return (_jsxs("footer", { className: `ui-footer ui-surface ${vcls(variant)} ${className}`, children: [_jsxs("div", { className: "ui-footer__top", children: [_jsxs("div", { className: "ui-footer__brand", children: [_jsx("strong", { children: brand }), tagline && _jsx("p", { children: tagline }), soc.length > 0 && (_jsx("div", { className: "ui-footer__social", children: soc.map((s) => (_jsx("span", { "aria-label": s.startsWith("icon:") ? s.slice(5).replace(/:sharp$/, "") : undefined, children: renderGlyph(s, 18) }, s))) }))] }), cols.length > 0 && (_jsx("nav", { className: "ui-footer__cols", children: cols.map((c) => (_jsxs("div", { children: [_jsx("span", { children: c.title }), _jsx("ul", { children: c.links.map((l) => (_jsx("li", { children: l }, l))) })] }, c.title))) }))] }), copyright && _jsx("div", { className: "ui-footer__bottom", children: copyright })] }));
16
+ }
@@ -0,0 +1,22 @@
1
+ import { type Tone } from "./variants.js";
2
+ export type GlyphCursorProps = {
3
+ /** glyph = un carácter; ring = anillo; dot = punto */
4
+ mode?: "glyph" | "ring" | "dot";
5
+ glyph?: string;
6
+ /** tamaño del cursor en px */
7
+ cursorSize?: number;
8
+ /** @deprecated usa `cursorSize` */
9
+ size?: number;
10
+ /** retardo del seguimiento en segundos (mayor = más "flotante") */
11
+ lag?: number;
12
+ spin?: boolean;
13
+ glow?: boolean;
14
+ tone?: Tone;
15
+ /** distancia al puntero en px */
16
+ offset?: number;
17
+ };
18
+ /**
19
+ * Elemento que persigue al puntero dentro de su contenedor padre (no de la ventana), así se puede
20
+ * usar en cualquier panel. Colócalo como hijo del elemento donde debe actuar. Sin efecto en táctil.
21
+ */
22
+ export default function GlyphCursor({ mode, glyph, cursorSize, size: legacySize, lag, spin, glow, tone, offset }: GlyphCursorProps): import("react").JSX.Element;
@@ -0,0 +1,50 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useEffect, useRef } from "react";
4
+ import { tcls } from "./variants.js";
5
+ /**
6
+ * Elemento que persigue al puntero dentro de su contenedor padre (no de la ventana), así se puede
7
+ * usar en cualquier panel. Colócalo como hijo del elemento donde debe actuar. Sin efecto en táctil.
8
+ */
9
+ export default function GlyphCursor({ mode = "glyph", glyph = "*", cursorSize, size: legacySize, lag = 0.45, spin = true, glow = true, tone = "acc", offset = 14 }) {
10
+ const size = cursorSize ?? legacySize ?? 22;
11
+ const scope = useRef(null);
12
+ const glyphEl = useRef(null);
13
+ useEffect(() => {
14
+ const host = scope.current?.parentElement;
15
+ const g = glyphEl.current;
16
+ if (!host || !g || window.matchMedia("(pointer: coarse)").matches)
17
+ return;
18
+ let disposed = false;
19
+ let off;
20
+ (async () => {
21
+ const { gsap } = await import("gsap");
22
+ if (disposed)
23
+ return;
24
+ gsap.set(g, { autoAlpha: 0 });
25
+ const qx = gsap.quickTo(g, "x", { duration: lag, ease: "power3.out" });
26
+ const qy = gsap.quickTo(g, "y", { duration: lag, ease: "power3.out" });
27
+ const move = (e) => {
28
+ const r = host.getBoundingClientRect();
29
+ const o = mode === "glyph" ? offset : 0;
30
+ qx(e.clientX - r.left + o);
31
+ qy(e.clientY - r.top + o);
32
+ gsap.to(g, { autoAlpha: 1, duration: 0.2, overwrite: "auto" });
33
+ };
34
+ const leave = () => void gsap.to(g, { autoAlpha: 0, duration: 0.2 });
35
+ host.addEventListener("pointermove", move);
36
+ host.addEventListener("pointerleave", leave);
37
+ off = () => {
38
+ host.removeEventListener("pointermove", move);
39
+ host.removeEventListener("pointerleave", leave);
40
+ gsap.killTweensOf(g);
41
+ };
42
+ })();
43
+ return () => {
44
+ disposed = true;
45
+ off?.();
46
+ };
47
+ }, [lag, mode, offset]);
48
+ const dim = mode === "glyph" ? undefined : { width: size, height: size };
49
+ return (_jsx("div", { ref: scope, className: `ui-cursor-scope ${tcls(tone)}`, "aria-hidden": true, children: _jsx("span", { ref: glyphEl, className: `ui-cursor ui-cursor--${mode} ${spin && mode === "glyph" ? "ui-cursor--spin" : ""} ${glow ? "ui-cursor--glow" : ""}`, style: { fontSize: size, ...dim, boxShadow: glow && mode !== "glyph" ? "0 0 12px var(--tone, var(--acc))" : undefined }, children: mode === "glyph" ? glyph : "" }) }));
50
+ }
@@ -0,0 +1,15 @@
1
+ export type GridBackgroundProps = {
2
+ kind?: "dots" | "lines" | "cross" | "diagonal";
3
+ /** tamaño de la celda en px */
4
+ cellSize?: number;
5
+ /** @deprecated usa `cellSize` */
6
+ size?: number;
7
+ opacity?: number;
8
+ /** difumina hacia los bordes */
9
+ fade?: boolean;
10
+ /** desplazamiento lento */
11
+ drift?: boolean;
12
+ className?: string;
13
+ };
14
+ /** Fondo de rejilla en CSS puro (sin canvas): la opción más ligera para fondos discretos. Rellena su contenedor. */
15
+ export default function GridBackground({ kind, cellSize, size: legacySize, opacity, fade, drift, className }: GridBackgroundProps): import("react").JSX.Element;