@axzydev/axzy_ui_system 1.4.8 → 1.5.1

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 (223) hide show
  1. package/README.md +2 -11
  2. package/dist/index.cjs +1826 -1351
  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 +115 -14
  7. package/dist/index.d.ts +115 -14
  8. package/dist/index.js +1481 -1010
  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.tsx +30 -28
  39. package/src/components/molecules/time-picker/timePicker.tsx +8 -6
  40. package/src/components/molecules/wysiwyg/wysiwyg.tsx +23 -12
  41. package/src/components/organisms/confirm-dialog/confirm-dialog.props.ts +4 -4
  42. package/src/components/organisms/confirm-dialog/confirm-dialog.tsx +16 -8
  43. package/src/components/organisms/data-table/dataTable.tsx +31 -25
  44. package/src/components/organisms/dialog/dialog.tsx +3 -0
  45. package/src/components/organisms/drawer/drawer.tsx +3 -0
  46. package/src/components/organisms/dropfile/dropfile.tsx +19 -17
  47. package/src/components/organisms/form-builder/fieldRenderer.tsx +4 -2
  48. package/src/components/organisms/navbar/navbar.tsx +4 -2
  49. package/src/components/organisms/page-header/page-header.tsx +3 -1
  50. package/src/components/organisms/searchTable/components/PaginationControls.tsx +8 -6
  51. package/src/components/organisms/searchTable/components/PaginationInfo.tsx +3 -1
  52. package/src/components/organisms/searchTable/components/SearchAndSortBar.tsx +7 -5
  53. package/src/components/organisms/searchTable/components/SearchInput.tsx +4 -2
  54. package/src/components/organisms/searchTable/components/SortButton.tsx +13 -11
  55. package/src/components/organisms/searchTable/components/TableEmptyState.tsx +4 -2
  56. package/src/components/organisms/searchTable/components/TableRow.tsx +6 -4
  57. package/src/components/organisms/searchTable/searchTable.tsx +4 -2
  58. package/src/components/organisms/sidebar/sidebar.props.ts +1 -1
  59. package/src/components/organisms/sidebar/sidebar.tsx +9 -4
  60. package/src/components/organisms/toast/toast.tsx +3 -1
  61. package/src/components/organisms/topbar/topbar.tsx +4 -2
  62. package/src/components/templates/page/page.props.ts +4 -4
  63. package/src/components/templates/page/page.tsx +6 -4
  64. package/src/components/theme-provider/themeProvider.tsx +28 -17
  65. package/src/dev.css +61 -2
  66. package/src/global.d.ts +10 -0
  67. package/src/i18n/context.ts +9 -0
  68. package/src/i18n/index.ts +22 -0
  69. package/src/i18n/locales/en.ts +163 -0
  70. package/src/i18n/locales/es.ts +165 -0
  71. package/src/i18n/provider.tsx +44 -0
  72. package/src/i18n/translator.ts +82 -0
  73. package/src/i18n/types.ts +54 -0
  74. package/src/i18n/useITTranslation.ts +20 -0
  75. package/src/index.css +0 -1
  76. package/src/index.ts +7 -1
  77. package/src/sandbox/LandingShowcase.tsx +224 -580
  78. package/src/sandbox/NotFoundShowcase.tsx +51 -36
  79. package/src/sandbox/TopbarSearch.tsx +6 -4
  80. package/src/sandbox/controls.tsx +180 -0
  81. package/src/sandbox/i18n.ts +1939 -0
  82. package/src/sandbox/icons.tsx +25 -0
  83. package/src/sandbox/locale.tsx +72 -0
  84. package/src/sandbox/ui.tsx +474 -0
  85. package/src/sandbox/useHashRoute.ts +46 -10
  86. package/src/showcases/DataShowcases.tsx +103 -87
  87. package/src/showcases/FeedbackShowcases.tsx +37 -30
  88. package/src/showcases/FormShowcases.tsx +303 -215
  89. package/src/showcases/GettingStartedShowcase.tsx +151 -145
  90. package/src/showcases/HelpShowcase.tsx +126 -128
  91. package/src/showcases/HomeShowcase.tsx +678 -482
  92. package/src/showcases/LayoutPrimitivesShowcases.tsx +129 -114
  93. package/src/showcases/NavigationShowcases.tsx +45 -37
  94. package/src/showcases/PageShowcases.tsx +171 -152
  95. package/src/showcases/ProductoDetalle.tsx +272 -129
  96. package/src/showcases/ShowcaseLayout.tsx +256 -219
  97. package/src/showcases/SizesShowcase.tsx +123 -64
  98. package/src/showcases/StructureShowcases.tsx +203 -138
  99. package/src/showcases/portfolioUi.tsx +153 -0
  100. package/src/showcases/techIcons.ts +3 -0
  101. package/src/utils/table.utils.ts +10 -2
  102. package/src/components/atoms/avatar/avatar.doc.mdx +0 -48
  103. package/src/components/atoms/avatar/avatar.stories.tsx +0 -64
  104. package/src/components/atoms/badget/badget.doc.mdx +0 -46
  105. package/src/components/atoms/badget/badget.stories.tsx +0 -75
  106. package/src/components/atoms/button/button.doc.mdx +0 -59
  107. package/src/components/atoms/button/button.stories.tsx +0 -173
  108. package/src/components/atoms/checkbox/checkbox.doc.mdx +0 -54
  109. package/src/components/atoms/checkbox/checkbox.stories.tsx +0 -53
  110. package/src/components/atoms/chip/chip.doc.mdx +0 -47
  111. package/src/components/atoms/chip/chip.stories.tsx +0 -64
  112. package/src/components/atoms/divider/divider.doc.mdx +0 -42
  113. package/src/components/atoms/divider/divider.stories.tsx +0 -33
  114. package/src/components/atoms/flex/flex.doc.mdx +0 -59
  115. package/src/components/atoms/flex/flex.stories.tsx +0 -70
  116. package/src/components/atoms/grid/grid.doc.mdx +0 -54
  117. package/src/components/atoms/grid/grid.stories.tsx +0 -71
  118. package/src/components/atoms/image/image.doc.mdx +0 -44
  119. package/src/components/atoms/image/image.stories.tsx +0 -48
  120. package/src/components/atoms/input/input.doc.mdx +0 -70
  121. package/src/components/atoms/input/input.stories.tsx +0 -113
  122. package/src/components/atoms/input-number/input-number.doc.mdx +0 -74
  123. package/src/components/atoms/input-number/input-number.stories.tsx +0 -111
  124. package/src/components/atoms/loader/loader.doc.mdx +0 -43
  125. package/src/components/atoms/loader/loader.stories.tsx +0 -104
  126. package/src/components/atoms/popover/popover.doc.mdx +0 -53
  127. package/src/components/atoms/popover/popover.stories.tsx +0 -24
  128. package/src/components/atoms/progress/progress.doc.mdx +0 -48
  129. package/src/components/atoms/progress/progress.stories.tsx +0 -39
  130. package/src/components/atoms/radio/radio.doc.mdx +0 -62
  131. package/src/components/atoms/radio/radio.stories.tsx +0 -48
  132. package/src/components/atoms/segmented-control/segmented-control.doc.mdx +0 -60
  133. package/src/components/atoms/segmented-control/segmented-control.stories.tsx +0 -62
  134. package/src/components/atoms/skeleton/skeleton.doc.mdx +0 -43
  135. package/src/components/atoms/skeleton/skeleton.stories.tsx +0 -29
  136. package/src/components/atoms/slide/slide.doc.mdx +0 -46
  137. package/src/components/atoms/slide/slide.stories.tsx +0 -120
  138. package/src/components/atoms/slider/slider.doc.mdx +0 -49
  139. package/src/components/atoms/slider/slider.stories.tsx +0 -29
  140. package/src/components/atoms/stack/stack.doc.mdx +0 -126
  141. package/src/components/atoms/stack/stack.stories.tsx +0 -283
  142. package/src/components/atoms/text/text.doc.mdx +0 -41
  143. package/src/components/atoms/text/text.stories.tsx +0 -66
  144. package/src/components/atoms/textarea/textarea.doc.mdx +0 -64
  145. package/src/components/atoms/textarea/textarea.stories.tsx +0 -26
  146. package/src/components/atoms/tooltip/tooltip.doc.mdx +0 -47
  147. package/src/components/atoms/tooltip/tooltip.stories.tsx +0 -19
  148. package/src/components/isolation/isolation.stories.tsx +0 -202
  149. package/src/components/molecules/accordion/accordion.doc.mdx +0 -47
  150. package/src/components/molecules/accordion/accordion.stories.tsx +0 -68
  151. package/src/components/molecules/alert/alert.doc.mdx +0 -48
  152. package/src/components/molecules/alert/alert.stories.tsx +0 -40
  153. package/src/components/molecules/breadcrumbs/breadcrumbs.doc.mdx +0 -57
  154. package/src/components/molecules/breadcrumbs/breadcrumbs.stories.tsx +0 -20
  155. package/src/components/molecules/calendar/calendar.doc.mdx +0 -76
  156. package/src/components/molecules/calendar/calendar.stories.tsx +0 -90
  157. package/src/components/molecules/card/card.doc.mdx +0 -54
  158. package/src/components/molecules/card/card.stories.tsx +0 -57
  159. package/src/components/molecules/chip-input/chip-input.doc.mdx +0 -44
  160. package/src/components/molecules/chip-input/chip-input.stories.tsx +0 -52
  161. package/src/components/molecules/date-picker/date-picker.doc.mdx +0 -65
  162. package/src/components/molecules/date-picker/date-picker.stories.tsx +0 -88
  163. package/src/components/molecules/dropdown-menu/dropdown-menu.doc.mdx +0 -46
  164. package/src/components/molecules/dropdown-menu/dropdown-menu.stories.tsx +0 -41
  165. package/src/components/molecules/empty-state/empty-state.doc.mdx +0 -50
  166. package/src/components/molecules/empty-state/empty-state.stories.tsx +0 -19
  167. package/src/components/molecules/field/field.doc.mdx +0 -43
  168. package/src/components/molecules/field/field.stories.tsx +0 -42
  169. package/src/components/molecules/form-header/form-header.doc.mdx +0 -38
  170. package/src/components/molecules/form-header/form-header.stories.tsx +0 -52
  171. package/src/components/molecules/masked-input/masked-input.doc.mdx +0 -78
  172. package/src/components/molecules/masked-input/masked-input.stories.tsx +0 -126
  173. package/src/components/molecules/multi-select/multi-select.doc.mdx +0 -55
  174. package/src/components/molecules/multi-select/multi-select.stories.tsx +0 -59
  175. package/src/components/molecules/pagination/pagination.doc.mdx +0 -62
  176. package/src/components/molecules/pagination/pagination.stories.tsx +0 -110
  177. package/src/components/molecules/search-select/search-select.doc.mdx +0 -77
  178. package/src/components/molecules/search-select/search-select.stories.tsx +0 -146
  179. package/src/components/molecules/select/select.doc.mdx +0 -75
  180. package/src/components/molecules/select/select.stories.tsx +0 -85
  181. package/src/components/molecules/stat-card/stat-card.doc.mdx +0 -48
  182. package/src/components/molecules/stat-card/stat-card.stories.tsx +0 -40
  183. package/src/components/molecules/stepper/stepper.doc.mdx +0 -54
  184. package/src/components/molecules/stepper/stepper.stories.tsx +0 -154
  185. package/src/components/molecules/table/table.doc.mdx +0 -140
  186. package/src/components/molecules/table/table.stories.tsx +0 -402
  187. package/src/components/molecules/tabs/tabs.doc.mdx +0 -52
  188. package/src/components/molecules/tabs/tabs.stories.tsx +0 -31
  189. package/src/components/molecules/time-picker/timePicker.doc.mdx +0 -68
  190. package/src/components/molecules/time-picker/timePicker.stories.tsx +0 -130
  191. package/src/components/molecules/wysiwyg/wysiwyg.stories.tsx +0 -95
  192. package/src/components/organisms/confirm-dialog/confirm-dialog.doc.mdx +0 -60
  193. package/src/components/organisms/confirm-dialog/confirm-dialog.stories.tsx +0 -32
  194. package/src/components/organisms/data-table/ITDataTable.doc.mdx +0 -132
  195. package/src/components/organisms/data-table/ITDataTable.stories.tsx +0 -353
  196. package/src/components/organisms/dialog/dialog.doc.mdx +0 -54
  197. package/src/components/organisms/dialog/dialog.stories.tsx +0 -126
  198. package/src/components/organisms/drawer/drawer.doc.mdx +0 -59
  199. package/src/components/organisms/drawer/drawer.stories.tsx +0 -40
  200. package/src/components/organisms/dropfile/dropfile.doc.mdx +0 -74
  201. package/src/components/organisms/dropfile/dropfile.stories.tsx +0 -93
  202. package/src/components/organisms/form-builder/formBuilder.doc.mdx +0 -251
  203. package/src/components/organisms/form-builder/formBuilder.stories.tsx +0 -914
  204. package/src/components/organisms/navbar/navbar.doc.mdx +0 -77
  205. package/src/components/organisms/navbar/navbar.stories.tsx +0 -83
  206. package/src/components/organisms/page-header/page-header.doc.mdx +0 -54
  207. package/src/components/organisms/page-header/page-header.stories.tsx +0 -74
  208. package/src/components/organisms/searchTable/searchTable.doc.mdx +0 -79
  209. package/src/components/organisms/searchTable/searchTable.stories.tsx +0 -73
  210. package/src/components/organisms/sidebar/sidebar.doc.mdx +0 -176
  211. package/src/components/organisms/sidebar/sidebar.stories.tsx +0 -234
  212. package/src/components/organisms/toast/toast.doc.mdx +0 -50
  213. package/src/components/organisms/toast/toast.stories.tsx +0 -137
  214. package/src/components/organisms/topbar/topbar.doc.mdx +0 -72
  215. package/src/components/organisms/topbar/topbar.stories.tsx +0 -79
  216. package/src/components/organisms/triple-filter/tripleFilter.doc.mdx +0 -62
  217. package/src/components/organisms/triple-filter/tripleFilter.stories.tsx +0 -31
  218. package/src/components/templates/layout/layout.doc.mdx +0 -53
  219. package/src/components/templates/layout/layout.stories.tsx +0 -113
  220. package/src/components/templates/page/page.doc.mdx +0 -86
  221. package/src/components/templates/page/page.stories.tsx +0 -97
  222. package/src/components/theme-provider/themeProvider.doc.mdx +0 -85
  223. 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
  }