@axzydev/axzy_ui_system 1.4.7 → 1.5.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 (226) hide show
  1. package/README.md +2 -11
  2. package/dist/index.cjs +2174 -1570
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.css +1 -1
  5. package/dist/index.css.map +1 -1
  6. package/dist/index.d.cts +232 -19
  7. package/dist/index.d.ts +232 -19
  8. package/dist/index.js +1838 -1238
  9. package/dist/index.js.map +1 -1
  10. package/dist/layered.css +1 -1
  11. package/package.json +3 -16
  12. package/src/App.tsx +90 -73
  13. package/src/components/atoms/badget/badget.tsx +23 -5
  14. package/src/components/atoms/chip/chip.tsx +3 -1
  15. package/src/components/atoms/image/image.tsx +3 -1
  16. package/src/components/atoms/input/input.tsx +8 -5
  17. package/src/components/atoms/loader/loader.tsx +3 -1
  18. package/src/components/atoms/slide/slide.props.ts +12 -0
  19. package/src/components/atoms/slide/slide.tsx +3 -0
  20. package/src/components/molecules/alert/alert.tsx +3 -1
  21. package/src/components/molecules/breadcrumbs/breadcrumbs.tsx +3 -1
  22. package/src/components/molecules/calendar/calendar.tsx +9 -6
  23. package/src/components/molecules/card/card.tsx +5 -2
  24. package/src/components/molecules/chip-input/chip-input.tsx +9 -7
  25. package/src/components/molecules/date-picker/datePicker.tsx +9 -9
  26. package/src/components/molecules/dropdown-menu/dropdown-menu.props.ts +1 -1
  27. package/src/components/molecules/dropdown-menu/dropdown-menu.tsx +6 -3
  28. package/src/components/molecules/field/field.tsx +3 -1
  29. package/src/components/molecules/form-header/form-header.tsx +3 -1
  30. package/src/components/molecules/line-bar-chart/line-bar-chart.props.ts +2 -2
  31. package/src/components/molecules/line-bar-chart/line-bar-chart.tsx +20 -15
  32. package/src/components/molecules/masked-input/masked-input.tsx +3 -1
  33. package/src/components/molecules/multi-select/multi-select.tsx +12 -9
  34. package/src/components/molecules/pagination/pagination.tsx +11 -5
  35. package/src/components/molecules/search-select/search-select.tsx +11 -9
  36. package/src/components/molecules/select/select.tsx +6 -4
  37. package/src/components/molecules/stepper/stepper.tsx +6 -4
  38. package/src/components/molecules/table/table.props.ts +10 -2
  39. package/src/components/molecules/table/table.tsx +163 -114
  40. package/src/components/molecules/time-picker/timePicker.tsx +8 -6
  41. package/src/components/molecules/wysiwyg/wysiwyg.tsx +23 -12
  42. package/src/components/organisms/confirm-dialog/confirm-dialog.props.ts +4 -4
  43. package/src/components/organisms/confirm-dialog/confirm-dialog.tsx +16 -8
  44. package/src/components/organisms/data-table/dataTable.props.ts +3 -2
  45. package/src/components/organisms/data-table/dataTable.tsx +149 -81
  46. package/src/components/organisms/dialog/dialog.tsx +3 -0
  47. package/src/components/organisms/drawer/drawer.tsx +3 -0
  48. package/src/components/organisms/dropfile/dropfile.tsx +19 -17
  49. package/src/components/organisms/form-builder/fieldRenderer.tsx +4 -2
  50. package/src/components/organisms/navbar/navbar.tsx +4 -2
  51. package/src/components/organisms/page-header/page-header.tsx +3 -1
  52. package/src/components/organisms/searchTable/components/PaginationControls.tsx +8 -6
  53. package/src/components/organisms/searchTable/components/PaginationInfo.tsx +3 -1
  54. package/src/components/organisms/searchTable/components/SearchAndSortBar.tsx +7 -5
  55. package/src/components/organisms/searchTable/components/SearchInput.tsx +4 -2
  56. package/src/components/organisms/searchTable/components/SortButton.tsx +13 -11
  57. package/src/components/organisms/searchTable/components/TableEmptyState.tsx +4 -2
  58. package/src/components/organisms/searchTable/components/TableRow.tsx +6 -4
  59. package/src/components/organisms/searchTable/searchTable.tsx +4 -2
  60. package/src/components/organisms/sidebar/sidebar.props.ts +1 -1
  61. package/src/components/organisms/sidebar/sidebar.tsx +9 -4
  62. package/src/components/organisms/toast/toast.tsx +3 -1
  63. package/src/components/organisms/topbar/topbar.tsx +4 -2
  64. package/src/components/templates/page/page.props.ts +4 -4
  65. package/src/components/templates/page/page.tsx +6 -4
  66. package/src/components/theme-provider/themeProvider.tsx +28 -17
  67. package/src/dev.css +77 -2
  68. package/src/global.d.ts +10 -0
  69. package/src/i18n/context.ts +9 -0
  70. package/src/i18n/index.ts +22 -0
  71. package/src/i18n/locales/en.ts +163 -0
  72. package/src/i18n/locales/es.ts +165 -0
  73. package/src/i18n/provider.tsx +44 -0
  74. package/src/i18n/translator.ts +82 -0
  75. package/src/i18n/types.ts +54 -0
  76. package/src/i18n/useITTranslation.ts +20 -0
  77. package/src/index.css +0 -1
  78. package/src/index.ts +9 -3
  79. package/src/sandbox/LandingShowcase.tsx +224 -580
  80. package/src/sandbox/NotFoundShowcase.tsx +51 -36
  81. package/src/sandbox/TopbarSearch.tsx +6 -4
  82. package/src/sandbox/controls.tsx +180 -0
  83. package/src/sandbox/i18n.ts +1929 -0
  84. package/src/sandbox/icons.tsx +25 -0
  85. package/src/sandbox/locale.tsx +72 -0
  86. package/src/sandbox/ui.tsx +377 -0
  87. package/src/sandbox/useHashRoute.ts +46 -10
  88. package/src/showcases/DataShowcases.tsx +201 -80
  89. package/src/showcases/FeedbackShowcases.tsx +37 -30
  90. package/src/showcases/FormShowcases.tsx +302 -214
  91. package/src/showcases/GettingStartedShowcase.tsx +151 -145
  92. package/src/showcases/HelpShowcase.tsx +126 -128
  93. package/src/showcases/HomeShowcase.tsx +678 -482
  94. package/src/showcases/LayoutPrimitivesShowcases.tsx +129 -114
  95. package/src/showcases/NavigationShowcases.tsx +45 -37
  96. package/src/showcases/PageShowcases.tsx +171 -152
  97. package/src/showcases/ProductoDetalle.tsx +272 -129
  98. package/src/showcases/ShowcaseLayout.tsx +270 -216
  99. package/src/showcases/SizesShowcase.tsx +123 -64
  100. package/src/showcases/StructureShowcases.tsx +157 -131
  101. package/src/showcases/portfolioUi.tsx +153 -0
  102. package/src/showcases/techIcons.ts +3 -0
  103. package/src/types/table.types.ts +110 -0
  104. package/src/utils/table.utils.ts +10 -2
  105. package/src/components/atoms/avatar/avatar.doc.mdx +0 -48
  106. package/src/components/atoms/avatar/avatar.stories.tsx +0 -64
  107. package/src/components/atoms/badget/badget.doc.mdx +0 -46
  108. package/src/components/atoms/badget/badget.stories.tsx +0 -75
  109. package/src/components/atoms/button/button.doc.mdx +0 -59
  110. package/src/components/atoms/button/button.stories.tsx +0 -173
  111. package/src/components/atoms/checkbox/checkbox.doc.mdx +0 -54
  112. package/src/components/atoms/checkbox/checkbox.stories.tsx +0 -53
  113. package/src/components/atoms/chip/chip.doc.mdx +0 -47
  114. package/src/components/atoms/chip/chip.stories.tsx +0 -64
  115. package/src/components/atoms/divider/divider.doc.mdx +0 -42
  116. package/src/components/atoms/divider/divider.stories.tsx +0 -33
  117. package/src/components/atoms/flex/flex.doc.mdx +0 -59
  118. package/src/components/atoms/flex/flex.stories.tsx +0 -70
  119. package/src/components/atoms/grid/grid.doc.mdx +0 -54
  120. package/src/components/atoms/grid/grid.stories.tsx +0 -71
  121. package/src/components/atoms/image/image.doc.mdx +0 -44
  122. package/src/components/atoms/image/image.stories.tsx +0 -48
  123. package/src/components/atoms/input/input.doc.mdx +0 -70
  124. package/src/components/atoms/input/input.stories.tsx +0 -113
  125. package/src/components/atoms/input-number/input-number.doc.mdx +0 -74
  126. package/src/components/atoms/input-number/input-number.stories.tsx +0 -111
  127. package/src/components/atoms/loader/loader.doc.mdx +0 -43
  128. package/src/components/atoms/loader/loader.stories.tsx +0 -104
  129. package/src/components/atoms/popover/popover.doc.mdx +0 -53
  130. package/src/components/atoms/popover/popover.stories.tsx +0 -24
  131. package/src/components/atoms/progress/progress.doc.mdx +0 -48
  132. package/src/components/atoms/progress/progress.stories.tsx +0 -39
  133. package/src/components/atoms/radio/radio.doc.mdx +0 -62
  134. package/src/components/atoms/radio/radio.stories.tsx +0 -48
  135. package/src/components/atoms/segmented-control/segmented-control.doc.mdx +0 -60
  136. package/src/components/atoms/segmented-control/segmented-control.stories.tsx +0 -62
  137. package/src/components/atoms/skeleton/skeleton.doc.mdx +0 -43
  138. package/src/components/atoms/skeleton/skeleton.stories.tsx +0 -29
  139. package/src/components/atoms/slide/slide.doc.mdx +0 -46
  140. package/src/components/atoms/slide/slide.stories.tsx +0 -120
  141. package/src/components/atoms/slider/slider.doc.mdx +0 -49
  142. package/src/components/atoms/slider/slider.stories.tsx +0 -29
  143. package/src/components/atoms/stack/stack.doc.mdx +0 -126
  144. package/src/components/atoms/stack/stack.stories.tsx +0 -283
  145. package/src/components/atoms/text/text.doc.mdx +0 -41
  146. package/src/components/atoms/text/text.stories.tsx +0 -66
  147. package/src/components/atoms/textarea/textarea.doc.mdx +0 -64
  148. package/src/components/atoms/textarea/textarea.stories.tsx +0 -26
  149. package/src/components/atoms/tooltip/tooltip.doc.mdx +0 -47
  150. package/src/components/atoms/tooltip/tooltip.stories.tsx +0 -19
  151. package/src/components/isolation/isolation.stories.tsx +0 -202
  152. package/src/components/molecules/accordion/accordion.doc.mdx +0 -47
  153. package/src/components/molecules/accordion/accordion.stories.tsx +0 -68
  154. package/src/components/molecules/alert/alert.doc.mdx +0 -48
  155. package/src/components/molecules/alert/alert.stories.tsx +0 -40
  156. package/src/components/molecules/breadcrumbs/breadcrumbs.doc.mdx +0 -57
  157. package/src/components/molecules/breadcrumbs/breadcrumbs.stories.tsx +0 -20
  158. package/src/components/molecules/calendar/calendar.doc.mdx +0 -76
  159. package/src/components/molecules/calendar/calendar.stories.tsx +0 -90
  160. package/src/components/molecules/card/card.doc.mdx +0 -54
  161. package/src/components/molecules/card/card.stories.tsx +0 -57
  162. package/src/components/molecules/chip-input/chip-input.doc.mdx +0 -44
  163. package/src/components/molecules/chip-input/chip-input.stories.tsx +0 -52
  164. package/src/components/molecules/date-picker/date-picker.doc.mdx +0 -65
  165. package/src/components/molecules/date-picker/date-picker.stories.tsx +0 -88
  166. package/src/components/molecules/dropdown-menu/dropdown-menu.doc.mdx +0 -46
  167. package/src/components/molecules/dropdown-menu/dropdown-menu.stories.tsx +0 -41
  168. package/src/components/molecules/empty-state/empty-state.doc.mdx +0 -50
  169. package/src/components/molecules/empty-state/empty-state.stories.tsx +0 -19
  170. package/src/components/molecules/field/field.doc.mdx +0 -43
  171. package/src/components/molecules/field/field.stories.tsx +0 -42
  172. package/src/components/molecules/form-header/form-header.doc.mdx +0 -38
  173. package/src/components/molecules/form-header/form-header.stories.tsx +0 -52
  174. package/src/components/molecules/masked-input/masked-input.doc.mdx +0 -78
  175. package/src/components/molecules/masked-input/masked-input.stories.tsx +0 -126
  176. package/src/components/molecules/multi-select/multi-select.doc.mdx +0 -55
  177. package/src/components/molecules/multi-select/multi-select.stories.tsx +0 -59
  178. package/src/components/molecules/pagination/pagination.doc.mdx +0 -62
  179. package/src/components/molecules/pagination/pagination.stories.tsx +0 -110
  180. package/src/components/molecules/search-select/search-select.doc.mdx +0 -77
  181. package/src/components/molecules/search-select/search-select.stories.tsx +0 -146
  182. package/src/components/molecules/select/select.doc.mdx +0 -75
  183. package/src/components/molecules/select/select.stories.tsx +0 -85
  184. package/src/components/molecules/stat-card/stat-card.doc.mdx +0 -48
  185. package/src/components/molecules/stat-card/stat-card.stories.tsx +0 -40
  186. package/src/components/molecules/stepper/stepper.doc.mdx +0 -54
  187. package/src/components/molecules/stepper/stepper.stories.tsx +0 -154
  188. package/src/components/molecules/table/table.doc.mdx +0 -123
  189. package/src/components/molecules/table/table.stories.tsx +0 -402
  190. package/src/components/molecules/tabs/tabs.doc.mdx +0 -52
  191. package/src/components/molecules/tabs/tabs.stories.tsx +0 -31
  192. package/src/components/molecules/time-picker/timePicker.doc.mdx +0 -68
  193. package/src/components/molecules/time-picker/timePicker.stories.tsx +0 -130
  194. package/src/components/molecules/wysiwyg/wysiwyg.stories.tsx +0 -95
  195. package/src/components/organisms/confirm-dialog/confirm-dialog.doc.mdx +0 -60
  196. package/src/components/organisms/confirm-dialog/confirm-dialog.stories.tsx +0 -32
  197. package/src/components/organisms/data-table/ITDataTable.doc.mdx +0 -115
  198. package/src/components/organisms/data-table/ITDataTable.stories.tsx +0 -353
  199. package/src/components/organisms/dialog/dialog.doc.mdx +0 -54
  200. package/src/components/organisms/dialog/dialog.stories.tsx +0 -126
  201. package/src/components/organisms/drawer/drawer.doc.mdx +0 -59
  202. package/src/components/organisms/drawer/drawer.stories.tsx +0 -40
  203. package/src/components/organisms/dropfile/dropfile.doc.mdx +0 -74
  204. package/src/components/organisms/dropfile/dropfile.stories.tsx +0 -93
  205. package/src/components/organisms/form-builder/formBuilder.doc.mdx +0 -251
  206. package/src/components/organisms/form-builder/formBuilder.stories.tsx +0 -914
  207. package/src/components/organisms/navbar/navbar.doc.mdx +0 -77
  208. package/src/components/organisms/navbar/navbar.stories.tsx +0 -83
  209. package/src/components/organisms/page-header/page-header.doc.mdx +0 -54
  210. package/src/components/organisms/page-header/page-header.stories.tsx +0 -74
  211. package/src/components/organisms/searchTable/searchTable.doc.mdx +0 -79
  212. package/src/components/organisms/searchTable/searchTable.stories.tsx +0 -73
  213. package/src/components/organisms/sidebar/sidebar.doc.mdx +0 -176
  214. package/src/components/organisms/sidebar/sidebar.stories.tsx +0 -234
  215. package/src/components/organisms/toast/toast.doc.mdx +0 -50
  216. package/src/components/organisms/toast/toast.stories.tsx +0 -137
  217. package/src/components/organisms/topbar/topbar.doc.mdx +0 -72
  218. package/src/components/organisms/topbar/topbar.stories.tsx +0 -79
  219. package/src/components/organisms/triple-filter/tripleFilter.doc.mdx +0 -62
  220. package/src/components/organisms/triple-filter/tripleFilter.stories.tsx +0 -31
  221. package/src/components/templates/layout/layout.doc.mdx +0 -53
  222. package/src/components/templates/layout/layout.stories.tsx +0 -113
  223. package/src/components/templates/page/page.doc.mdx +0 -86
  224. package/src/components/templates/page/page.stories.tsx +0 -97
  225. package/src/components/theme-provider/themeProvider.doc.mdx +0 -85
  226. package/src/components/theme-provider/themeProvider.stories.tsx +0 -58
@@ -1,181 +1,324 @@
1
- import { useState } from "react";
2
- import { FaCheck, FaExternalLinkAlt } from "react-icons/fa";
3
- import ITBadget from "../components/atoms/badget/badget";
4
- import ITDivider from "../components/atoms/divider/divider";
1
+ import { useEffect, useState } from "react";
2
+ import { FaArrowLeft, FaArrowRight, FaCheck, FaExternalLinkAlt } from "react-icons/fa";
5
3
  import ITFlex from "../components/atoms/flex/flex";
6
- import ITGrid from "../components/atoms/grid/grid";
7
- import ITStack from "../components/atoms/stack/stack";
8
4
  import ITText from "../components/atoms/text/text";
9
- import ITDialog from "../components/organisms/dialog/dialog";
10
5
  import ProductoIcono from "./ProductoIcono";
11
- import type { Producto } from "./productos";
6
+ import { PRODUCTOS, type Producto } from "./productos";
7
+ import {
8
+ BotonAccion,
9
+ CHIP,
10
+ ControlesPortafolio,
11
+ EYEBROW,
12
+ EnlaceBoton,
13
+ Etiqueta,
14
+ SaltarContenido,
15
+ } from "./portfolioUi";
16
+ import { useITTranslation } from "@/i18n";
12
17
 
13
18
  interface Props {
14
19
  /** Producto que se muestra. */
15
20
  producto: Producto;
16
- /** Cierra la pantalla de detalle. */
17
- onClose: () => void;
21
+ /** Vuelve al portafolio. */
22
+ onBack: () => void;
18
23
  }
19
24
 
25
+ /** Ancho de la hoja: el mismo que el masthead del portafolio. */
26
+ const HOJA = "mx-auto w-full max-w-[1180px] px-5 sm:px-8 lg:px-10";
27
+
28
+ /** Filete de 1px entre bloques. */
29
+ const FILETE = "border-slate-200 dark:border-slate-800";
30
+
31
+ /** Productos que tienen pantalla de detalle (los que traen capturas). */
32
+ const CON_DETALLE = PRODUCTOS.filter((p) => (p.capturas?.length ?? 0) > 0);
33
+
20
34
  /**
21
- * Pantalla de detalle de un producto del portafolio.
35
+ * Página de detalle de un producto del portafolio (`#proyecto/<id>`).
22
36
  *
23
- * Se abre a pantalla completa sobre la home: a la izquierda la galería de
24
- * capturas reales (con miniaturas y pie), a la derecha la ficha del producto
25
- * (qué resuelve, stack y datos técnicos).
37
+ * Es una **página completa**, no un modal: cabecera propia con volver al
38
+ * portafolio, ficha del producto (qué resuelve y datos técnicos en filas con
39
+ * filete), galería de capturas reales con miniaturas y navegación entre
40
+ * proyectos. La URL es compartible y el botón atrás del navegador funciona.
26
41
  *
27
42
  * @example
28
43
  * ```tsx
29
- * {detalle && <ProductoDetalle producto={detalle} onClose={() => setDetalle(null)} />}
44
+ * <ProductoDetalle producto={producto} onBack={() => goToPortfolio()} />
30
45
  * ```
31
46
  */
32
- export default function ProductoDetalle({ producto, onClose }: Props) {
47
+ export default function ProductoDetalle({ producto, onBack }: Props) {
48
+ const { t } = useITTranslation();
33
49
  const capturas = producto.capturas ?? [];
34
50
  const [activa, setActiva] = useState(capturas[0]);
35
51
  const hayGaleria = capturas.length > 0;
52
+ const enlaceExterno = Boolean(producto.enlace && !producto.enlace.startsWith("#"));
53
+
54
+ // Navegación entre proyectos: solo entre los que tienen pantalla de detalle.
55
+ const indice = CON_DETALLE.findIndex((p) => p.id === producto.id);
56
+ const anterior = indice > 0 ? CON_DETALLE[indice - 1] : undefined;
57
+ const siguiente =
58
+ indice >= 0 && indice < CON_DETALLE.length - 1 ? CON_DETALLE[indice + 1] : undefined;
59
+
60
+ // Cada producto entra por arriba y con su primera captura activa.
61
+ useEffect(() => {
62
+ window.scrollTo({ top: 0, behavior: "auto" });
63
+ setActiva(producto.capturas?.[0]);
64
+ }, [producto]);
36
65
 
37
66
  return (
38
- <ITDialog isOpen onClose={onClose} title={producto.nombre} useFormHeader fullScreen>
39
- <div className="mx-auto w-full max-w-6xl">
40
- <ITGrid container spacing={8}>
41
- {/* ─── Galería ─── */}
42
- {hayGaleria && activa && (
43
- <ITGrid item xs={12} lg={7}>
44
- <ITStack spacing={3}>
45
- <div className="overflow-hidden rounded-xl border border-slate-200 dark:border-slate-800 bg-slate-50 dark:bg-slate-950/60">
46
- <img
47
- src={activa.src}
48
- alt={`${producto.nombre}: ${activa.titulo}`}
49
- className="block w-full"
50
- />
51
- </div>
52
-
53
- <ITFlex gap={2} wrap="wrap">
67
+ <>
68
+ <SaltarContenido />
69
+
70
+ {/* Cabecera: volver + producto + enlace */}
71
+ <header className="sticky top-0 z-[100] border-b border-slate-200 bg-white/85 backdrop-blur-md dark:border-slate-800 dark:bg-[#0b0d10]/85">
72
+ <div className={`${HOJA} flex h-14 items-center gap-3`}>
73
+ <BotonAccion onClick={onBack} icono={<FaArrowLeft size={11} />} className="!px-3 !py-1.5">
74
+ {t("showcase.producto.back")}
75
+ </BotonAccion>
76
+ <span aria-hidden className="text-slate-300 dark:text-slate-700">
77
+ /
78
+ </span>
79
+ <ITText
80
+ as="span"
81
+ className="truncate font-display !text-[13px] !font-bold -tracking-[0.01em] text-slate-900 dark:text-white"
82
+ >
83
+ {producto.nombre}
84
+ </ITText>
85
+
86
+ <div className="ml-auto flex items-center gap-2 sm:gap-3">
87
+ {producto.enlace && (
88
+ <a
89
+ href={producto.enlace}
90
+ {...(enlaceExterno ? { target: "_blank", rel: "noopener noreferrer" } : {})}
91
+ className="hidden shrink-0 items-center gap-1.5 font-mono !text-[11px] uppercase !tracking-[0.14em] !text-primary-700 dark:!text-primary-500 sm:inline-flex"
92
+ >
93
+ {enlaceExterno && <FaExternalLinkAlt size={10} />}
94
+ {producto.enlaceTexto ?? t("showcase.home.viewRepo")}
95
+ </a>
96
+ )}
97
+ <ControlesPortafolio />
98
+ </div>
99
+ </div>
100
+ </header>
101
+
102
+ <main id="contenido">
103
+ {/* ─── FICHA ─── */}
104
+ <div className={`${HOJA} pb-10 pt-10 sm:pt-14`}>
105
+ <ITFlex align="center" gap={3} wrap="wrap">
106
+ <span className="grid h-11 w-11 place-items-center rounded-[10px] border border-slate-200 bg-white text-primary-700 dark:border-slate-800 dark:bg-slate-900 dark:text-primary-500">
107
+ <ProductoIcono icono={producto.icono} size={20} />
108
+ </span>
109
+ <Etiqueta>{producto.tipo}</Etiqueta>
110
+ <ITText as="span" className={EYEBROW}>
111
+ {`${String(indice + 1).padStart(2, "0")} / ${String(CON_DETALLE.length).padStart(2, "0")}`}
112
+ </ITText>
113
+ </ITFlex>
114
+
115
+ <ITText
116
+ as="h1"
117
+ className="font-display !mt-6 !text-[34px] !font-bold !leading-[1.08] -tracking-[0.02em] text-slate-900 dark:text-white sm:!text-[46px]"
118
+ >
119
+ {producto.nombre}
120
+ </ITText>
121
+
122
+ <ITText
123
+ as="p"
124
+ className="!mt-5 max-w-[70ch] !text-[15.5px] !leading-[1.75] text-slate-600 dark:text-slate-300 sm:!text-[16.5px]"
125
+ >
126
+ {producto.detalle ?? producto.descripcion}
127
+ </ITText>
128
+
129
+ <ITFlex gap={1.5} wrap="wrap" className="!mt-6">
130
+ {producto.stack.map((tecnologia) => (
131
+ <span key={tecnologia} className={CHIP}>
132
+ {tecnologia}
133
+ </span>
134
+ ))}
135
+ </ITFlex>
136
+ </div>
137
+
138
+ {/* ─── GALERÍA ─── */}
139
+ {hayGaleria && activa && (
140
+ <section aria-label={t("showcase.producto.gallery")}>
141
+ <div className={HOJA}>
142
+ <div className={`overflow-hidden rounded-[10px] border bg-slate-950 ${FILETE}`}>
143
+ <img src={activa.src} alt={`${producto.nombre}: ${activa.titulo}`} className="block w-full" />
144
+ </div>
145
+
146
+ {capturas.length > 1 && (
147
+ <ITFlex gap={2} wrap="wrap" className="!mt-3">
54
148
  {capturas.map((captura) => (
55
149
  <button
56
150
  key={captura.src}
57
151
  type="button"
58
152
  title={captura.titulo}
59
153
  aria-label={captura.titulo}
154
+ aria-pressed={activa.src === captura.src}
60
155
  onClick={() => setActiva(captura)}
61
- className={`h-14 w-20 shrink-0 overflow-hidden rounded-lg border-2 bg-slate-100 dark:bg-slate-950 transition-all ${
156
+ className={`h-16 w-24 shrink-0 overflow-hidden rounded-[8px] border-2 bg-slate-100 transition-all dark:bg-slate-950 ${
62
157
  activa.src === captura.src
63
- ? "border-primary-500 opacity-100"
64
- : "border-transparent opacity-55 hover:opacity-100"
158
+ ? "border-primary-700 dark:border-primary-500"
159
+ : "border-transparent opacity-60 hover:opacity-100"
65
160
  }`}
66
161
  >
67
162
  <img src={captura.src} alt="" className="h-full w-full object-cover object-top" />
68
163
  </button>
69
164
  ))}
70
165
  </ITFlex>
166
+ )}
71
167
 
72
- <ITText as="p" muted className="!text-xs">
73
- {activa.titulo}
74
- </ITText>
75
- </ITStack>
76
- </ITGrid>
168
+ <ITText
169
+ as="p"
170
+ className="!mt-3 font-mono !text-[11px] !leading-relaxed text-slate-500 dark:text-slate-400"
171
+ >
172
+ {activa.titulo}
173
+ </ITText>
174
+ </div>
175
+ </section>
176
+ )}
177
+
178
+ {/* ─── QUÉ RESUELVE + FICHA TÉCNICA ─── */}
179
+ <div className={`${HOJA} mt-12 grid gap-10 lg:grid-cols-12`}>
180
+ {!!producto.caracteristicas?.length && (
181
+ <section className="lg:col-span-7">
182
+ <ITText as="h2" className={EYEBROW}>
183
+ {t("showcase.producto.whatItSolves")}
184
+ </ITText>
185
+ <ul className={`mt-4 border-t ${FILETE}`}>
186
+ {producto.caracteristicas.map((caracteristica) => (
187
+ <li key={caracteristica} className={`flex gap-3 border-b py-3.5 ${FILETE}`}>
188
+ <FaCheck
189
+ size={10}
190
+ className="mt-1.5 shrink-0 text-primary-700 dark:text-primary-500"
191
+ />
192
+ <ITText
193
+ as="span"
194
+ className="!text-[14px] !leading-relaxed text-slate-600 dark:text-slate-300"
195
+ >
196
+ {caracteristica}
197
+ </ITText>
198
+ </li>
199
+ ))}
200
+ </ul>
201
+ </section>
77
202
  )}
78
203
 
79
- {/* ─── Ficha ─── */}
80
- <ITGrid item xs={12} lg={hayGaleria ? 5 : 12}>
81
- <ITStack spacing={5}>
82
- <ITFlex align="center" justify="between" gap={3}>
83
- <ITFlex align="center" gap={3}>
84
- <div className={`w-12 h-12 rounded-xl bg-gradient-to-br ${producto.gradiente} text-white flex items-center justify-center shadow-md shrink-0`}>
85
- <ProductoIcono icono={producto.icono} size={20} />
204
+ {!!producto.ficha?.length && (
205
+ <section className="lg:col-span-5">
206
+ <ITText as="h2" className={EYEBROW}>
207
+ {t("showcase.producto.specSheet")}
208
+ </ITText>
209
+ <dl className={`mt-4 border-t ${FILETE}`}>
210
+ {producto.ficha.map((dato) => (
211
+ <div key={dato.etiqueta} className={`border-b py-3.5 ${FILETE}`}>
212
+ <dt className="font-mono text-[11px] uppercase tracking-[0.12em] text-slate-500 dark:text-slate-400">
213
+ {dato.etiqueta}
214
+ </dt>
215
+ <dd className="mt-1.5 text-[13px] leading-relaxed text-slate-700 dark:text-slate-200">
216
+ {dato.valor}
217
+ </dd>
86
218
  </div>
87
- <ITText as="h2" className="!text-xl !font-bold">
88
- {producto.nombre}
89
- </ITText>
90
- </ITFlex>
91
- <ITBadget label={producto.tipo} color="primary" variant="outlined" />
92
- </ITFlex>
219
+ ))}
220
+ </dl>
221
+ </section>
222
+ )}
223
+ </div>
93
224
 
94
- {producto.detalle && (
95
- <ITText as="p" muted className="!text-sm !leading-relaxed">
96
- {producto.detalle}
97
- </ITText>
98
- )}
225
+ {producto.nota && (
226
+ <div className={HOJA}>
227
+ <ITText
228
+ as="p"
229
+ className="!mt-8 max-w-[74ch] font-mono !text-[11px] !leading-relaxed text-slate-500 dark:text-slate-400"
230
+ >
231
+ {producto.nota}
232
+ </ITText>
233
+ </div>
234
+ )}
99
235
 
100
- {!!producto.caracteristicas?.length && (
101
- <div>
102
- <ITText as="h3" className="!text-sm !font-bold !mb-2.5">
103
- Qué resuelve
236
+ {/* ─── ANTERIOR / SIGUIENTE ─── */}
237
+ {(anterior || siguiente) && (
238
+ <nav aria-label={t("showcase.producto.navLabel")} className={`mt-14 border-t ${FILETE}`}>
239
+ <div className={`${HOJA} grid gap-px sm:grid-cols-2`}>
240
+ {anterior && (
241
+ <a
242
+ href={`#proyecto/${anterior.id}`}
243
+ className={`group flex flex-col gap-1.5 py-6 sm:pr-8 ${
244
+ siguiente ? `sm:border-r ${FILETE}` : ""
245
+ }`}
246
+ >
247
+ <ITText as="span" className={`inline-flex items-center gap-2 ${EYEBROW}`}>
248
+ <FaArrowLeft size={9} aria-hidden />
249
+ {t("showcase.producto.prev")}
104
250
  </ITText>
105
- <ITStack spacing={2}>
106
- {producto.caracteristicas.map((caracteristica) => (
107
- <ITFlex key={caracteristica} align="start" gap={2}>
108
- <FaCheck size={11} className="mt-1 shrink-0 text-emerald-500" />
109
- <ITText as="span" muted className="!text-xs !leading-relaxed">
110
- {caracteristica}
111
- </ITText>
112
- </ITFlex>
113
- ))}
114
- </ITStack>
115
- </div>
116
- )}
117
-
118
- <div>
119
- <ITText as="h3" className="!text-sm !font-bold !mb-2.5">
120
- Stack
121
- </ITText>
122
- <ITFlex gap={2} wrap="wrap">
123
- {producto.stack.map((tecnologia) => (
124
- <ITBadget key={tecnologia} label={tecnologia} color="gray" variant="outlined" />
125
- ))}
126
- </ITFlex>
127
- </div>
128
-
129
- {!!producto.ficha?.length && (
130
- <>
131
- <ITDivider />
132
-
133
- <div>
134
- <ITText as="h3" className="!text-sm !font-bold !mb-2.5">
135
- Ficha técnica
136
- </ITText>
137
- <ITStack spacing={2.5}>
138
- {producto.ficha.map((dato) => (
139
- <ITGrid container spacing={2} key={dato.etiqueta}>
140
- <ITGrid item xs={4}>
141
- <ITText as="span" muted className="!text-xs !font-semibold">
142
- {dato.etiqueta}
143
- </ITText>
144
- </ITGrid>
145
- <ITGrid item xs={8}>
146
- <ITText as="span" className="!text-xs !leading-relaxed">
147
- {dato.valor}
148
- </ITText>
149
- </ITGrid>
150
- </ITGrid>
151
- ))}
152
- </ITStack>
153
- </div>
154
- </>
251
+ <ITText
252
+ as="span"
253
+ className="font-display !text-[19px] !font-bold -tracking-[0.02em] text-slate-900 group-hover:text-primary-700 dark:text-white dark:group-hover:text-primary-500"
254
+ >
255
+ {anterior.nombre}
256
+ </ITText>
257
+ <ITText as="span" className="!text-[12px] text-slate-500 dark:text-slate-400">
258
+ {anterior.tipo}
259
+ </ITText>
260
+ </a>
155
261
  )}
156
262
 
157
- {producto.enlace && (
263
+ {siguiente && (
158
264
  <a
159
- href={producto.enlace}
160
- {...(producto.enlace.startsWith("#")
161
- ? {}
162
- : { target: "_blank", rel: "noopener noreferrer" })}
163
- className="!text-xs !font-semibold !text-primary-600 dark:!text-primary-400 inline-flex items-center gap-1.5"
265
+ href={`#proyecto/${siguiente.id}`}
266
+ className={`group flex flex-col gap-1.5 py-6 sm:pl-8 ${
267
+ anterior ? "" : "sm:col-start-2 sm:items-end sm:text-right"
268
+ }`}
164
269
  >
165
- {!producto.enlace.startsWith("#") && <FaExternalLinkAlt size={11} />}
166
- {producto.enlaceTexto ?? "Ver el repositorio"}
270
+ <ITText as="span" className={`inline-flex items-center gap-2 ${EYEBROW}`}>
271
+ {t("showcase.producto.next")}
272
+ <FaArrowRight size={9} aria-hidden />
273
+ </ITText>
274
+ <ITText
275
+ as="span"
276
+ className="font-display !text-[19px] !font-bold -tracking-[0.02em] text-slate-900 group-hover:text-primary-700 dark:text-white dark:group-hover:text-primary-500"
277
+ >
278
+ {siguiente.nombre}
279
+ </ITText>
280
+ <ITText as="span" className="!text-[12px] text-slate-500 dark:text-slate-400">
281
+ {siguiente.tipo}
282
+ </ITText>
167
283
  </a>
168
284
  )}
285
+ </div>
286
+ </nav>
287
+ )}
288
+
289
+ {/* ─── CIERRE ─── */}
290
+ <section className={`border-t ${FILETE}`}>
291
+ <div
292
+ className={`${HOJA} flex flex-col gap-6 py-12 sm:flex-row sm:items-center sm:justify-between`}
293
+ >
294
+ <div>
295
+ <ITText as="p" className={EYEBROW}>
296
+ {t("showcase.home.eyebrowNext")}
297
+ </ITText>
298
+ <ITText
299
+ as="h2"
300
+ className="font-display !mt-2.5 !text-[24px] !font-bold -tracking-[0.02em] text-slate-900 dark:text-white sm:!text-[30px]"
301
+ >
302
+ {t("showcase.producto.cta")}
303
+ </ITText>
304
+ </div>
169
305
 
170
- {producto.nota && (
171
- <ITText as="p" muted className="!text-[11px] !leading-relaxed">
172
- {producto.nota}
173
- </ITText>
306
+ <ITFlex gap={3} wrap="wrap" align="center" className="shrink-0">
307
+ <BotonAccion onClick={onBack} icono={<FaArrowLeft size={11} />}>
308
+ {t("showcase.producto.back")}
309
+ </BotonAccion>
310
+ {producto.enlace && (
311
+ <EnlaceBoton
312
+ href={producto.enlace}
313
+ icono={enlaceExterno ? <FaExternalLinkAlt size={11} /> : undefined}
314
+ >
315
+ {producto.enlaceTexto ?? t("showcase.home.viewRepo")}
316
+ </EnlaceBoton>
174
317
  )}
175
- </ITStack>
176
- </ITGrid>
177
- </ITGrid>
178
- </div>
179
- </ITDialog>
318
+ </ITFlex>
319
+ </div>
320
+ </section>
321
+ </main>
322
+ </>
180
323
  );
181
324
  }