@axzydev/axzy_ui_system 1.3.6 → 1.4.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.
- package/dist/index.cjs +8 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/molecules/date-picker/datePicker.tsx +1 -0
- package/src/components/molecules/dropdown-menu/dropdown-menu.tsx +1 -0
- package/src/components/molecules/multi-select/multi-select.tsx +1 -0
- package/src/components/molecules/search-select/search-select.tsx +1 -0
- package/src/components/molecules/time-picker/timePicker.tsx +1 -0
- package/src/components/organisms/dialog/dialog.stories.tsx +48 -0
- package/src/hooks/useClickOutside.ts +6 -0
- package/src/hooks/useFloatingPanel.ts +4 -1
- package/src/showcases/FeedbackShowcases.tsx +16 -1
- package/src/showcases/HomeShowcase.tsx +552 -331
- package/src/showcases/ProductoDetalle.tsx +181 -0
- package/src/showcases/ProductoIcono.tsx +25 -0
- package/src/showcases/productos.ts +179 -0
- package/src/showcases/techIcons.ts +67 -0
|
@@ -1,67 +1,301 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { useState, type ReactNode } from "react";
|
|
2
|
+
import {
|
|
3
|
+
FaBoxOpen,
|
|
4
|
+
FaCloud,
|
|
5
|
+
FaCode,
|
|
6
|
+
FaDatabase,
|
|
7
|
+
FaDownload,
|
|
8
|
+
FaExternalLinkAlt,
|
|
9
|
+
FaGithub,
|
|
10
|
+
FaLayerGroup,
|
|
11
|
+
FaLinkedin,
|
|
12
|
+
FaMapMarkerAlt,
|
|
13
|
+
FaMedium,
|
|
14
|
+
FaMobileAlt,
|
|
15
|
+
FaSearch,
|
|
16
|
+
FaServer,
|
|
17
|
+
} from "react-icons/fa";
|
|
4
18
|
import ITBadget from "../components/atoms/badget/badget";
|
|
5
19
|
import ITButton from "../components/atoms/button/button";
|
|
6
|
-
import ITCard from "../components/molecules/card/card";
|
|
7
20
|
import ITDivider from "../components/atoms/divider/divider";
|
|
8
21
|
import ITFlex from "../components/atoms/flex/flex";
|
|
9
22
|
import ITGrid from "../components/atoms/grid/grid";
|
|
10
23
|
import ITStack from "../components/atoms/stack/stack";
|
|
11
24
|
import ITText from "../components/atoms/text/text";
|
|
25
|
+
import { techIcon, type TechIconEntry } from "./techIcons";
|
|
26
|
+
import { PRODUCTOS, type Producto } from "./productos";
|
|
27
|
+
import ProductoDetalle from "./ProductoDetalle";
|
|
28
|
+
import ProductoIcono from "./ProductoIcono";
|
|
29
|
+
|
|
30
|
+
const CV_URL = "/Martin_Amaro_CV_ES_2026.pdf";
|
|
31
|
+
const GITHUB_URL = "https://github.com/maag070208";
|
|
32
|
+
const LINKEDIN_URL = "https://www.linkedin.com/in/maag070208/";
|
|
33
|
+
const MEDIUM_URL = "https://medium.com/@axzydev";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Degradados literales para los íconos de las métricas. Tailwind v4 no ve las
|
|
37
|
+
* clases construidas en runtime, así que van escritas completas y se eligen por
|
|
38
|
+
* posición.
|
|
39
|
+
*/
|
|
40
|
+
const ACCENTS = [
|
|
41
|
+
"from-primary-500 to-info-500",
|
|
42
|
+
"from-purple-500 to-primary-500",
|
|
43
|
+
"from-primary-600 to-purple-600",
|
|
44
|
+
"from-info-500 to-purple-500",
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Encabezado de sección: eyebrow en mayúsculas, título y subtítulo opcional.
|
|
49
|
+
* Mismo patrón que el landing del sistema (`#ui-system`).
|
|
50
|
+
*/
|
|
51
|
+
const SectionHeading = ({
|
|
52
|
+
eyebrow,
|
|
53
|
+
title,
|
|
54
|
+
subtitle,
|
|
55
|
+
className = "",
|
|
56
|
+
}: {
|
|
57
|
+
/** Texto chico en mayúsculas sobre el título. */
|
|
58
|
+
eyebrow: string;
|
|
59
|
+
/** Título de la sección. */
|
|
60
|
+
title: string;
|
|
61
|
+
/** Frase de apoyo debajo del título. */
|
|
62
|
+
subtitle?: string;
|
|
63
|
+
/** Clases extra del contenedor (márgenes). */
|
|
64
|
+
className?: string;
|
|
65
|
+
}) => (
|
|
66
|
+
<ITStack spacing={1} className={className}>
|
|
67
|
+
<ITText as="p" className="!text-[11px] !font-bold uppercase !tracking-wider text-primary-600 dark:text-primary-400">
|
|
68
|
+
{eyebrow}
|
|
69
|
+
</ITText>
|
|
70
|
+
<ITText as="h2" className="!text-xl !font-bold !tracking-tight text-slate-900 dark:text-white">
|
|
71
|
+
{title}
|
|
72
|
+
</ITText>
|
|
73
|
+
{subtitle && (
|
|
74
|
+
<ITText as="p" className="!text-sm text-slate-500 dark:text-slate-400">
|
|
75
|
+
{subtitle}
|
|
76
|
+
</ITText>
|
|
77
|
+
)}
|
|
78
|
+
</ITStack>
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Ícono de tecnología local, invertido en modo oscuro cuando la marca es
|
|
83
|
+
* monocromática (GitHub, Express).
|
|
84
|
+
*/
|
|
85
|
+
const TechIconImg = ({
|
|
86
|
+
icon,
|
|
87
|
+
alt,
|
|
88
|
+
size = "w-7 h-7",
|
|
89
|
+
}: {
|
|
90
|
+
/** Ícono resuelto por `techIcons`. */
|
|
91
|
+
icon: TechIconEntry;
|
|
92
|
+
/** Texto alternativo. */
|
|
93
|
+
alt: string;
|
|
94
|
+
/** Clases de tamaño. @default "w-7 h-7" */
|
|
95
|
+
size?: string;
|
|
96
|
+
}) => (
|
|
97
|
+
<img
|
|
98
|
+
src={icon.src}
|
|
99
|
+
alt={alt}
|
|
100
|
+
className={`${size} object-contain ${icon.mono ? "dark:invert" : ""}`}
|
|
101
|
+
loading="lazy"
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
/** Tarjeta de una tecnología: ícono en caja blanca + nombre. */
|
|
106
|
+
const TechTile = ({ icon, label }: { icon: TechIconEntry; label: string }) => (
|
|
107
|
+
<div className="group flex h-full flex-col items-center justify-center gap-2 rounded-xl border border-slate-100 bg-slate-50/70 p-3 transition-all duration-200 hover:-translate-y-0.5 hover:border-primary-300 hover:bg-white hover:shadow-md dark:border-slate-800 dark:bg-slate-950/40 dark:hover:border-primary-700 dark:hover:bg-slate-900">
|
|
108
|
+
<div className="flex h-11 w-11 items-center justify-center rounded-lg bg-white shadow-sm dark:bg-slate-900">
|
|
109
|
+
<TechIconImg icon={icon} alt={label} size="w-7 h-7" />
|
|
110
|
+
</div>
|
|
111
|
+
<ITText as="span" className="w-full truncate text-center text-[11px] font-semibold text-slate-600 dark:text-slate-300">
|
|
112
|
+
{label}
|
|
113
|
+
</ITText>
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
/** Métrica del hero: valor grande, ícono con degradado y texto de apoyo. */
|
|
118
|
+
const StatCard = ({ stat, index }: { stat: { label: string; value: string; hint: string; icon: ReactNode }; index: number }) => (
|
|
119
|
+
<div className="flex h-full flex-col gap-4 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm transition-all duration-300 hover:-translate-y-0.5 hover:border-primary-300 hover:shadow-lg dark:border-slate-800 dark:bg-slate-900 dark:hover:border-primary-700">
|
|
120
|
+
<div className="flex items-start justify-between gap-3">
|
|
121
|
+
<ITText as="p" className="!text-3xl !font-extrabold !leading-none !tracking-tight tabular-nums text-slate-900 dark:text-white">
|
|
122
|
+
{stat.value}
|
|
123
|
+
</ITText>
|
|
124
|
+
<span className={`flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br text-[13px] text-white shadow-sm ${ACCENTS[index % ACCENTS.length]}`}>
|
|
125
|
+
{stat.icon}
|
|
126
|
+
</span>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<div className="mt-auto">
|
|
130
|
+
<ITText as="p" className="!text-sm !font-semibold text-primary-600 dark:text-primary-400">
|
|
131
|
+
{stat.label}
|
|
132
|
+
</ITText>
|
|
133
|
+
<ITText as="p" className="!mt-0.5 !text-[11px] text-slate-400 dark:text-slate-500">
|
|
134
|
+
{stat.hint}
|
|
135
|
+
</ITText>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Tarjeta de producto, compacta: portada (captura real o degradado), nombre,
|
|
142
|
+
* descripción corta, stack y acceso al detalle.
|
|
143
|
+
*/
|
|
144
|
+
const TarjetaProducto = ({
|
|
145
|
+
producto,
|
|
146
|
+
onVerDetalle,
|
|
147
|
+
}: {
|
|
148
|
+
producto: Producto;
|
|
149
|
+
onVerDetalle: (producto: Producto) => void;
|
|
150
|
+
}) => {
|
|
151
|
+
const capturas = producto.capturas ?? [];
|
|
152
|
+
const portada = capturas[0];
|
|
153
|
+
const tieneDetalle = capturas.length > 0;
|
|
154
|
+
const enlaceInterno = producto.enlace?.startsWith("#");
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<div className="group flex h-full flex-col gap-4 rounded-2xl border border-slate-200 bg-white p-4 shadow-sm transition-all duration-300 hover:-translate-y-1 hover:border-primary-300 hover:shadow-xl dark:border-slate-800 dark:bg-slate-900 dark:hover:border-primary-700">
|
|
158
|
+
{/* Portada */}
|
|
159
|
+
<button
|
|
160
|
+
type="button"
|
|
161
|
+
disabled={!tieneDetalle}
|
|
162
|
+
onClick={() => onVerDetalle(producto)}
|
|
163
|
+
aria-label={tieneDetalle ? `Ver detalle de ${producto.nombre}` : producto.nombre}
|
|
164
|
+
className="relative block aspect-[16/10] w-full overflow-hidden rounded-xl border border-slate-200 bg-slate-50 dark:border-slate-800 dark:bg-slate-950/60"
|
|
165
|
+
>
|
|
166
|
+
{portada ? (
|
|
167
|
+
<img
|
|
168
|
+
src={portada.src}
|
|
169
|
+
alt={`${producto.nombre}: ${portada.titulo}`}
|
|
170
|
+
className="h-full w-full object-cover object-top transition-transform duration-300 group-hover:scale-[1.02]"
|
|
171
|
+
loading="lazy"
|
|
172
|
+
/>
|
|
173
|
+
) : (
|
|
174
|
+
<div className={`flex h-full w-full items-center justify-center bg-gradient-to-br ${producto.gradiente} text-white`}>
|
|
175
|
+
<ProductoIcono icono={producto.icono} size={44} />
|
|
176
|
+
</div>
|
|
177
|
+
)}
|
|
178
|
+
|
|
179
|
+
{capturas.length > 1 && (
|
|
180
|
+
<span className="absolute bottom-2 right-2 rounded-full bg-slate-900/70 px-2.5 py-1 text-[10px] font-semibold text-white backdrop-blur-sm">
|
|
181
|
+
{capturas.length} capturas
|
|
182
|
+
</span>
|
|
183
|
+
)}
|
|
184
|
+
</button>
|
|
185
|
+
|
|
186
|
+
{/* Encabezado */}
|
|
187
|
+
<ITFlex align="center" justify="between" gap={3}>
|
|
188
|
+
<ITFlex align="center" gap={2.5}>
|
|
189
|
+
<div className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br ${producto.gradiente} text-white shadow-sm`}>
|
|
190
|
+
<ProductoIcono icono={producto.icono} size={16} />
|
|
191
|
+
</div>
|
|
192
|
+
<ITText as="h3" className="!text-base !font-bold !tracking-tight text-slate-900 dark:text-white">
|
|
193
|
+
{producto.nombre}
|
|
194
|
+
</ITText>
|
|
195
|
+
</ITFlex>
|
|
196
|
+
<ITBadget label={producto.tipo} color="primary" variant="outlined" />
|
|
197
|
+
</ITFlex>
|
|
198
|
+
|
|
199
|
+
<ITText as="p" className="!text-sm !leading-relaxed text-slate-600 dark:text-slate-300">
|
|
200
|
+
{producto.descripcion}
|
|
201
|
+
</ITText>
|
|
202
|
+
|
|
203
|
+
<ITFlex gap={1.5} wrap="wrap">
|
|
204
|
+
{producto.stack.map((tecnologia) => (
|
|
205
|
+
<span
|
|
206
|
+
key={tecnologia}
|
|
207
|
+
className="rounded-md bg-slate-100 px-2 py-0.5 text-[10px] font-medium text-slate-500 dark:bg-slate-800 dark:text-slate-400"
|
|
208
|
+
>
|
|
209
|
+
{tecnologia}
|
|
210
|
+
</span>
|
|
211
|
+
))}
|
|
212
|
+
</ITFlex>
|
|
213
|
+
|
|
214
|
+
{/* Acciones */}
|
|
215
|
+
<ITFlex align="center" justify="between" gap={3} wrap="wrap" className="mt-auto pt-2">
|
|
216
|
+
{tieneDetalle ? (
|
|
217
|
+
<ITButton variant="filled" color="primary" size="sm" onClick={() => onVerDetalle(producto)}>
|
|
218
|
+
Ver detalle
|
|
219
|
+
</ITButton>
|
|
220
|
+
) : (
|
|
221
|
+
<ITText as="span" />
|
|
222
|
+
)}
|
|
223
|
+
|
|
224
|
+
{producto.enlace && (
|
|
225
|
+
<a
|
|
226
|
+
href={producto.enlace}
|
|
227
|
+
{...(enlaceInterno ? {} : { target: "_blank", rel: "noopener noreferrer" })}
|
|
228
|
+
className="!text-xs !font-semibold !text-primary-600 dark:!text-primary-400 inline-flex items-center gap-1.5"
|
|
229
|
+
>
|
|
230
|
+
{!enlaceInterno && <FaExternalLinkAlt size={11} />}
|
|
231
|
+
{producto.enlaceTexto ?? "Ver el repositorio"}
|
|
232
|
+
</a>
|
|
233
|
+
)}
|
|
234
|
+
</ITFlex>
|
|
235
|
+
</div>
|
|
236
|
+
);
|
|
237
|
+
};
|
|
12
238
|
|
|
13
239
|
export const HomeShowcase = () => {
|
|
14
240
|
const [techSearch, setTechSearch] = useState("");
|
|
15
241
|
const [selectedCategory, setSelectedCategory] = useState("all");
|
|
242
|
+
const [detalle, setDetalle] = useState<Producto | null>(null);
|
|
16
243
|
|
|
17
244
|
const LANGUAGES = [
|
|
18
|
-
{ name: "TypeScript", icon: "
|
|
19
|
-
{ name: "JavaScript", icon: "
|
|
20
|
-
{ name: "C#", icon: "
|
|
21
|
-
{ name: "Python", icon: "
|
|
22
|
-
{ name: "Kotlin", icon: "
|
|
23
|
-
{ name: "Dart", icon: "
|
|
245
|
+
{ name: "TypeScript", icon: techIcon("typescript") },
|
|
246
|
+
{ name: "JavaScript", icon: techIcon("javascript") },
|
|
247
|
+
{ name: "C#", icon: techIcon("csharp") },
|
|
248
|
+
{ name: "Python", icon: techIcon("python") },
|
|
249
|
+
{ name: "Kotlin", icon: techIcon("kotlin") },
|
|
250
|
+
{ name: "Dart", icon: techIcon("dart") },
|
|
24
251
|
];
|
|
25
252
|
|
|
26
253
|
const FRAMEWORKS = [
|
|
27
|
-
{ name: "Angular", icon: "
|
|
28
|
-
{ name: "React", icon: "
|
|
29
|
-
{ name: "Ionic", icon: "
|
|
30
|
-
{ name: ".NET", icon: "
|
|
31
|
-
{ name: "Node.js", icon: "
|
|
32
|
-
{ name: "Flutter", icon: "
|
|
33
|
-
{ name: "Express", icon: "
|
|
34
|
-
{ name: "Electron", icon: "
|
|
254
|
+
{ name: "Angular", icon: techIcon("angular") },
|
|
255
|
+
{ name: "React", icon: techIcon("react") },
|
|
256
|
+
{ name: "Ionic", icon: techIcon("ionic") },
|
|
257
|
+
{ name: ".NET", icon: techIcon("dotnet") },
|
|
258
|
+
{ name: "Node.js", icon: techIcon("nodejs") },
|
|
259
|
+
{ name: "Flutter", icon: techIcon("flutter") },
|
|
260
|
+
{ name: "Express", icon: techIcon("express") },
|
|
261
|
+
{ name: "Electron", icon: techIcon("electron") },
|
|
35
262
|
];
|
|
36
263
|
|
|
37
264
|
const TECH_STACK = [
|
|
38
|
-
{ name: "TypeScript", icon: "
|
|
39
|
-
{ name: "JavaScript", icon: "
|
|
40
|
-
{ name: "Angular", icon: "
|
|
41
|
-
{ name: "React", icon: "
|
|
42
|
-
{ name: "Redux", icon: "
|
|
43
|
-
{ name: "HTML5", icon: "
|
|
44
|
-
{ name: "CSS3", icon: "
|
|
45
|
-
{ name: "Sass", icon: "
|
|
46
|
-
{ name: "C#", icon: "
|
|
47
|
-
{ name: "Python", icon: "
|
|
48
|
-
{ name: "Node.js", icon: "
|
|
49
|
-
{ name: "Express", icon: "
|
|
50
|
-
{ name: ".NET Core", icon: "
|
|
51
|
-
{ name: "MongoDB", icon: "
|
|
52
|
-
{ name: "MySQL", icon: "
|
|
53
|
-
{ name: "Flutter", icon: "
|
|
54
|
-
{ name: "Kotlin", icon: "
|
|
55
|
-
{ name: "Android", icon: "
|
|
56
|
-
{ name: "Ionic", icon: "
|
|
57
|
-
{ name: "Docker", icon: "
|
|
58
|
-
{ name: "Git", icon: "
|
|
59
|
-
{ name: "GitHub", icon: "
|
|
60
|
-
{ name: "GitLab", icon: "
|
|
61
|
-
{ name: "npm", icon: "
|
|
62
|
-
{ name: "Electron", icon: "
|
|
63
|
-
{ name: "Visual Studio", icon: "
|
|
64
|
-
{ name: "Jest", icon: "
|
|
265
|
+
{ name: "TypeScript", icon: techIcon("typescript"), category: "frontend" },
|
|
266
|
+
{ name: "JavaScript", icon: techIcon("javascript"), category: "frontend" },
|
|
267
|
+
{ name: "Angular", icon: techIcon("angular"), category: "frontend" },
|
|
268
|
+
{ name: "React", icon: techIcon("react"), category: "frontend" },
|
|
269
|
+
{ name: "Redux", icon: techIcon("redux"), category: "frontend" },
|
|
270
|
+
{ name: "HTML5", icon: techIcon("html5"), category: "frontend" },
|
|
271
|
+
{ name: "CSS3", icon: techIcon("css3"), category: "frontend" },
|
|
272
|
+
{ name: "Sass", icon: techIcon("sass"), category: "frontend" },
|
|
273
|
+
{ name: "C#", icon: techIcon("csharp"), category: "backend" },
|
|
274
|
+
{ name: "Python", icon: techIcon("python"), category: "backend" },
|
|
275
|
+
{ name: "Node.js", icon: techIcon("nodejs"), category: "backend" },
|
|
276
|
+
{ name: "Express", icon: techIcon("express"), category: "backend" },
|
|
277
|
+
{ name: ".NET Core", icon: techIcon("dotnet"), category: "backend" },
|
|
278
|
+
{ name: "MongoDB", icon: techIcon("mongodb"), category: "database" },
|
|
279
|
+
{ name: "MySQL", icon: techIcon("mysql"), category: "database" },
|
|
280
|
+
{ name: "Flutter", icon: techIcon("flutter"), category: "mobile" },
|
|
281
|
+
{ name: "Kotlin", icon: techIcon("kotlin"), category: "mobile" },
|
|
282
|
+
{ name: "Android", icon: techIcon("android"), category: "mobile" },
|
|
283
|
+
{ name: "Ionic", icon: techIcon("ionic"), category: "mobile" },
|
|
284
|
+
{ name: "Docker", icon: techIcon("docker"), category: "devops" },
|
|
285
|
+
{ name: "Git", icon: techIcon("git"), category: "devops" },
|
|
286
|
+
{ name: "GitHub", icon: techIcon("github"), category: "devops" },
|
|
287
|
+
{ name: "GitLab", icon: techIcon("gitlab"), category: "devops" },
|
|
288
|
+
{ name: "npm", icon: techIcon("npm"), category: "devops" },
|
|
289
|
+
{ name: "Electron", icon: techIcon("electron"), category: "desktop" },
|
|
290
|
+
{ name: "Visual Studio", icon: techIcon("visualstudio"), category: "tools" },
|
|
291
|
+
{ name: "Jest", icon: techIcon("jest"), category: "testing" },
|
|
292
|
+
];
|
|
293
|
+
|
|
294
|
+
const STATS = [
|
|
295
|
+
{ label: "Experiencia", value: "5+", hint: "años como fullstack", icon: <FaCode /> },
|
|
296
|
+
{ label: "Tecnologías", value: `${TECH_STACK.length}`, hint: "en el stack", icon: <FaLayerGroup /> },
|
|
297
|
+
{ label: "Productos", value: `${PRODUCTOS.length}`, hint: "propios y en uso", icon: <FaBoxOpen /> },
|
|
298
|
+
{ label: "Modalidad", value: "Remoto", hint: "disponible ya", icon: <FaMapMarkerAlt /> },
|
|
65
299
|
];
|
|
66
300
|
|
|
67
301
|
const categoriesList = [
|
|
@@ -73,324 +307,311 @@ export const HomeShowcase = () => {
|
|
|
73
307
|
{ id: "devops", name: "DevOps", icon: <FaCloud size={10} /> },
|
|
74
308
|
];
|
|
75
309
|
|
|
76
|
-
const filteredTech = TECH_STACK.filter(tech => {
|
|
310
|
+
const filteredTech = TECH_STACK.filter((tech) => {
|
|
77
311
|
const matchesSearch = tech.name.toLowerCase().includes(techSearch.toLowerCase());
|
|
78
312
|
const matchesCategory = selectedCategory === "all" || tech.category === selectedCategory;
|
|
79
313
|
return matchesSearch && matchesCategory;
|
|
80
314
|
});
|
|
81
315
|
|
|
82
316
|
return (
|
|
83
|
-
<ITStack spacing={10}>
|
|
317
|
+
<ITStack spacing={10} className="animate-fadeIn overflow-x-clip pb-5">
|
|
84
318
|
{/* ─── HERO ─── */}
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
<img
|
|
97
|
-
src="/personalFoto.jpg"
|
|
98
|
-
alt="Asael Amaro"
|
|
99
|
-
className="w-full h-full object-cover"
|
|
100
|
-
/>
|
|
101
|
-
</div>
|
|
319
|
+
<section
|
|
320
|
+
aria-labelledby="home-hero-title"
|
|
321
|
+
className="relative overflow-hidden rounded-3xl bg-gradient-to-br from-primary-50 via-white to-purple-50 px-8 py-14 dark:from-primary-950/30 dark:via-slate-900 dark:to-purple-950/30 sm:px-14"
|
|
322
|
+
>
|
|
323
|
+
<div aria-hidden className="it-landing-float pointer-events-none absolute -top-24 -right-16 h-72 w-72 rounded-full bg-primary-400/20 blur-3xl" />
|
|
324
|
+
<div
|
|
325
|
+
aria-hidden
|
|
326
|
+
className="it-landing-float pointer-events-none absolute -bottom-24 -left-16 h-72 w-72 rounded-full bg-purple-400/20 blur-3xl"
|
|
327
|
+
style={{ animationDelay: "1.5s" }}
|
|
328
|
+
/>
|
|
329
|
+
<div aria-hidden className="it-landing-grid pointer-events-none absolute inset-0 text-slate-400/50 dark:text-slate-600/40" />
|
|
102
330
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
331
|
+
<div className="relative z-10 flex flex-col gap-12 xl:flex-row xl:items-center xl:gap-14">
|
|
332
|
+
<div className="min-w-0 max-w-3xl flex-1 xl:max-w-2xl">
|
|
333
|
+
<div className="it-landing-rise">
|
|
334
|
+
<ITBadget label="Portafolio · 2026" color="primary" variant="outlined" className="w-fit" />
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
<ITText
|
|
338
|
+
as="h1"
|
|
339
|
+
id="home-hero-title"
|
|
340
|
+
className="it-landing-rise !mt-5 !text-4xl !font-extrabold !tracking-tight text-slate-900 dark:text-white sm:!text-5xl"
|
|
341
|
+
style={{ animationDelay: "70ms" }}
|
|
342
|
+
>
|
|
343
|
+
Hola, soy{" "}
|
|
344
|
+
<span className="bg-gradient-to-r from-primary-600 via-primary-500 to-purple-500 bg-clip-text text-transparent">
|
|
345
|
+
Asael Amaro
|
|
346
|
+
</span>
|
|
347
|
+
</ITText>
|
|
348
|
+
|
|
349
|
+
<ITText
|
|
350
|
+
as="p"
|
|
351
|
+
className="it-landing-rise !mt-4 !text-base !leading-relaxed max-w-xl text-slate-600 dark:text-slate-300"
|
|
352
|
+
style={{ animationDelay: "140ms" }}
|
|
353
|
+
>
|
|
354
|
+
Fullstack Developer con 4+ años creando aplicaciones web modernas.
|
|
355
|
+
Especializado en TypeScript, Angular y React, con foco en arquitectura
|
|
356
|
+
limpia y en productos que la gente usa todos los días.
|
|
357
|
+
</ITText>
|
|
358
|
+
|
|
359
|
+
<ITFlex
|
|
360
|
+
gap={3}
|
|
361
|
+
wrap="wrap"
|
|
362
|
+
align="center"
|
|
363
|
+
className="it-landing-rise !mt-7"
|
|
364
|
+
style={{ animationDelay: "210ms" }}
|
|
365
|
+
>
|
|
366
|
+
<a href={CV_URL} download>
|
|
367
|
+
<ITButton variant="filled" color="primary" size="lg" icon={<FaDownload size={13} />}>
|
|
368
|
+
Descargar CV
|
|
369
|
+
</ITButton>
|
|
370
|
+
</a>
|
|
371
|
+
<a href={GITHUB_URL} target="_blank" rel="noopener noreferrer">
|
|
372
|
+
<ITButton variant="outlined" color="secondary" size="lg" icon={<FaGithub size={13} />}>
|
|
373
|
+
GitHub
|
|
374
|
+
</ITButton>
|
|
375
|
+
</a>
|
|
376
|
+
</ITFlex>
|
|
106
377
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
378
|
+
<div
|
|
379
|
+
className="it-landing-rise mt-7 inline-flex flex-wrap items-center gap-x-3 gap-y-2 rounded-2xl border border-primary-200 bg-gradient-to-r from-primary-50 to-purple-50 px-4 py-2 shadow-sm dark:border-primary-800/50 dark:from-primary-950/30 dark:to-purple-950/30"
|
|
380
|
+
style={{ animationDelay: "280ms" }}
|
|
381
|
+
>
|
|
382
|
+
<span className="relative flex h-2 w-2 shrink-0">
|
|
383
|
+
<span className="absolute inline-flex h-full w-full rounded-full bg-success-400 opacity-75" />
|
|
384
|
+
<span className="relative inline-flex h-2 w-2 rounded-full bg-success-500" />
|
|
385
|
+
</span>
|
|
386
|
+
<code className="text-sm font-medium text-slate-800 dark:text-slate-200">
|
|
387
|
+
Disponible para proyectos fullstack
|
|
388
|
+
</code>
|
|
389
|
+
<a
|
|
390
|
+
href={LINKEDIN_URL}
|
|
391
|
+
target="_blank"
|
|
392
|
+
rel="noopener noreferrer"
|
|
393
|
+
className="inline-flex items-center gap-1.5 text-xs font-semibold text-primary-600 dark:text-primary-400"
|
|
394
|
+
>
|
|
395
|
+
<FaLinkedin size={12} />
|
|
396
|
+
LinkedIn
|
|
397
|
+
</a>
|
|
398
|
+
</div>
|
|
399
|
+
</div>
|
|
400
|
+
|
|
401
|
+
{/* Foto en tarjeta flotante */}
|
|
402
|
+
<div className="flex w-full shrink-0 justify-center xl:w-[300px] xl:justify-end">
|
|
403
|
+
<div
|
|
404
|
+
className="it-landing-float w-full max-w-[300px] overflow-hidden rounded-2xl border border-slate-200/80 bg-white/85 shadow-xl backdrop-blur-sm dark:border-slate-700/70 dark:bg-slate-900/85"
|
|
405
|
+
style={{ animationDelay: "0.8s" }}
|
|
406
|
+
>
|
|
407
|
+
<img
|
|
408
|
+
src="/personalFoto.jpg"
|
|
409
|
+
alt="Asael Amaro"
|
|
410
|
+
className="aspect-[4/5] w-full object-cover"
|
|
411
|
+
/>
|
|
412
|
+
<div className="flex items-center justify-between gap-3 border-t border-slate-200/80 px-4 py-3.5 dark:border-slate-700/70">
|
|
413
|
+
<div>
|
|
414
|
+
<ITText as="p" className="!text-xs !font-bold text-slate-900 dark:text-white">
|
|
415
|
+
Fullstack Developer
|
|
114
416
|
</ITText>
|
|
115
|
-
<ITText as="p"
|
|
116
|
-
|
|
117
|
-
Especializado en TypeScript, Angular y React. Apasionado por la arquitectura limpia,
|
|
118
|
-
el código escalable y las tecnologías emergentes.
|
|
417
|
+
<ITText as="p" className="!text-[11px] text-slate-400 dark:text-slate-500">
|
|
418
|
+
Remoto · México
|
|
119
419
|
</ITText>
|
|
120
|
-
</
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
<div>
|
|
129
|
-
<ITText as="div" className="text-lg font-bold text-slate-800 dark:text-white">4+</ITText>
|
|
130
|
-
<ITText as="div" muted className="text-xs font-medium">Años exp.</ITText>
|
|
131
|
-
</div>
|
|
132
|
-
</ITFlex>
|
|
133
|
-
<ITFlex align="center" gap={3}>
|
|
134
|
-
<div className="w-10 h-10 rounded-xl bg-purple-100 dark:bg-purple-950/40 flex items-center justify-center text-purple-600 dark:text-purple-400">
|
|
135
|
-
<FaMapMarkerAlt size={16} />
|
|
136
|
-
</div>
|
|
137
|
-
<div>
|
|
138
|
-
<ITText as="div" className="text-lg font-bold text-slate-800 dark:text-white">Remoto</ITText>
|
|
139
|
-
<ITText as="div" muted className="text-xs font-medium">Disponible</ITText>
|
|
140
|
-
</div>
|
|
141
|
-
</ITFlex>
|
|
142
|
-
</ITFlex>
|
|
143
|
-
|
|
144
|
-
{/* CTAs */}
|
|
145
|
-
<ITFlex gap={2.5} wrap="wrap" align="center">
|
|
146
|
-
<a href="/Martin_Amaro_CV_ES_2026.pdf" download>
|
|
147
|
-
<ITButton variant="filled" color="primary" size="sm">
|
|
148
|
-
<ITFlex align="center" gap={2}>
|
|
149
|
-
<FaDownload size={12} />
|
|
150
|
-
Descargar CV
|
|
151
|
-
</ITFlex>
|
|
152
|
-
</ITButton>
|
|
153
|
-
</a>
|
|
154
|
-
<ITText as="span" muted className="text-xs">|</ITText>
|
|
155
|
-
<a href="https://github.com/maag070208" target="_blank" rel="noopener noreferrer">
|
|
156
|
-
<ITButton variant="text" color="gray" size="sm">
|
|
157
|
-
<ITFlex align="center" gap={1.5}>
|
|
158
|
-
<FaGithub size={14} />
|
|
159
|
-
GitHub
|
|
160
|
-
</ITFlex>
|
|
161
|
-
</ITButton>
|
|
162
|
-
</a>
|
|
163
|
-
<a href="https://www.linkedin.com/in/maag070208/" target="_blank" rel="noopener noreferrer">
|
|
164
|
-
<ITButton variant="text" color="gray" size="sm">
|
|
165
|
-
<ITFlex align="center" gap={1.5}>
|
|
166
|
-
<FaLinkedin size={14} />
|
|
167
|
-
LinkedIn
|
|
168
|
-
</ITFlex>
|
|
169
|
-
</ITButton>
|
|
170
|
-
</a>
|
|
171
|
-
<a href="https://medium.com/@axzydev" target="_blank" rel="noopener noreferrer">
|
|
172
|
-
<ITButton variant="text" color="gray" size="sm">
|
|
173
|
-
<ITFlex align="center" gap={1.5}>
|
|
174
|
-
<FaMedium size={14} />
|
|
175
|
-
Medium
|
|
176
|
-
</ITFlex>
|
|
177
|
-
</ITButton>
|
|
178
|
-
</a>
|
|
179
|
-
</ITFlex>
|
|
180
|
-
</ITStack>
|
|
181
|
-
</ITGrid>
|
|
182
|
-
</ITGrid>
|
|
420
|
+
</div>
|
|
421
|
+
<span className="inline-flex shrink-0 items-center gap-1.5 rounded-full border border-success-500/40 px-2.5 py-1 text-[10px] font-semibold text-success-600 dark:text-success-400">
|
|
422
|
+
<span className="h-1.5 w-1.5 rounded-full bg-success-500" />
|
|
423
|
+
Disponible
|
|
424
|
+
</span>
|
|
425
|
+
</div>
|
|
426
|
+
</div>
|
|
427
|
+
</div>
|
|
183
428
|
</div>
|
|
184
|
-
</
|
|
185
|
-
|
|
186
|
-
{/* ───
|
|
187
|
-
<
|
|
188
|
-
<ITGrid
|
|
189
|
-
|
|
190
|
-
<
|
|
191
|
-
<
|
|
429
|
+
</section>
|
|
430
|
+
|
|
431
|
+
{/* ─── MÉTRICAS ─── */}
|
|
432
|
+
<section aria-label="Métricas del perfil">
|
|
433
|
+
<ITGrid container spacing={4}>
|
|
434
|
+
{STATS.map((stat, index) => (
|
|
435
|
+
<ITGrid item xs={6} lg={3} key={stat.label}>
|
|
436
|
+
<StatCard stat={stat} index={index} />
|
|
437
|
+
</ITGrid>
|
|
438
|
+
))}
|
|
439
|
+
</ITGrid>
|
|
440
|
+
</section>
|
|
441
|
+
|
|
442
|
+
{/* ─── LENGUAJES Y FRAMEWORKS ─── */}
|
|
443
|
+
<section>
|
|
444
|
+
<SectionHeading
|
|
445
|
+
eyebrow="Base técnica"
|
|
446
|
+
title="Lenguajes y frameworks"
|
|
447
|
+
subtitle="Con los que trabajo todos los días."
|
|
448
|
+
className="!mb-5"
|
|
449
|
+
/>
|
|
450
|
+
|
|
451
|
+
<ITGrid container spacing={5}>
|
|
452
|
+
<ITGrid item xs={12} lg={6}>
|
|
453
|
+
<div className="h-full rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
|
|
454
|
+
<ITText as="h3" className="!text-[11px] !font-bold uppercase !tracking-wider text-slate-400 dark:text-slate-500">
|
|
455
|
+
Lenguajes
|
|
456
|
+
</ITText>
|
|
457
|
+
<div className="mt-4 grid grid-cols-3 gap-3 sm:grid-cols-6 lg:grid-cols-3">
|
|
192
458
|
{LANGUAGES.map((lang) => (
|
|
193
|
-
<
|
|
194
|
-
<div className="group flex flex-col items-center gap-2.5 p-3.5 rounded-xl bg-white/80 dark:bg-slate-900/60 border border-slate-100 dark:border-slate-800 hover:border-primary-400/40 hover:shadow-md hover:-translate-y-0.5 transition-all duration-200">
|
|
195
|
-
<div className="w-10 h-10 flex items-center justify-center rounded-lg bg-slate-50 dark:bg-slate-950/40 border border-slate-100 dark:border-slate-800/80 group-hover:scale-110 transition-transform duration-200">
|
|
196
|
-
<img src={lang.icon} alt={lang.name} className="w-7 h-7 object-contain" />
|
|
197
|
-
</div>
|
|
198
|
-
<ITText as="span" className="text-[11px] font-semibold text-slate-600 dark:text-slate-300 text-center">
|
|
199
|
-
{lang.name}
|
|
200
|
-
</ITText>
|
|
201
|
-
</div>
|
|
202
|
-
</ITGrid>
|
|
459
|
+
<TechTile key={lang.name} icon={lang.icon} label={lang.name} />
|
|
203
460
|
))}
|
|
204
|
-
</
|
|
205
|
-
</
|
|
206
|
-
</
|
|
207
|
-
</ITGrid>
|
|
461
|
+
</div>
|
|
462
|
+
</div>
|
|
463
|
+
</ITGrid>
|
|
208
464
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
465
|
+
<ITGrid item xs={12} lg={6}>
|
|
466
|
+
<div className="h-full rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
|
|
467
|
+
<ITText as="h3" className="!text-[11px] !font-bold uppercase !tracking-wider text-slate-400 dark:text-slate-500">
|
|
468
|
+
Frameworks
|
|
469
|
+
</ITText>
|
|
470
|
+
<div className="mt-4 grid grid-cols-3 gap-3 sm:grid-cols-4 lg:grid-cols-4">
|
|
213
471
|
{FRAMEWORKS.map((fw) => (
|
|
214
|
-
<
|
|
215
|
-
<div className="group flex flex-col items-center gap-2.5 p-3.5 rounded-xl bg-white/80 dark:bg-slate-900/60 border border-slate-100 dark:border-slate-800 hover:border-primary-400/40 hover:shadow-md hover:-translate-y-0.5 transition-all duration-200">
|
|
216
|
-
<div className="w-10 h-10 flex items-center justify-center rounded-lg bg-slate-50 dark:bg-slate-950/40 border border-slate-100 dark:border-slate-800/80 group-hover:scale-110 transition-transform duration-200">
|
|
217
|
-
<img src={fw.icon} alt={fw.name} className="w-7 h-7 object-contain" />
|
|
218
|
-
</div>
|
|
219
|
-
<ITText as="span" className="text-[11px] font-semibold text-slate-600 dark:text-slate-300 text-center">
|
|
220
|
-
{fw.name}
|
|
221
|
-
</ITText>
|
|
222
|
-
</div>
|
|
223
|
-
</ITGrid>
|
|
472
|
+
<TechTile key={fw.name} icon={fw.icon} label={fw.name} />
|
|
224
473
|
))}
|
|
225
|
-
</
|
|
226
|
-
</
|
|
227
|
-
</
|
|
474
|
+
</div>
|
|
475
|
+
</div>
|
|
476
|
+
</ITGrid>
|
|
228
477
|
</ITGrid>
|
|
229
|
-
</
|
|
478
|
+
</section>
|
|
230
479
|
|
|
231
|
-
{/* ───
|
|
232
|
-
<
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
<input
|
|
258
|
-
type="text"
|
|
259
|
-
placeholder="Buscar tecnología..."
|
|
260
|
-
value={techSearch}
|
|
261
|
-
onChange={(e) => setTechSearch(e.target.value)}
|
|
262
|
-
className="w-full bg-white dark:bg-slate-900/80 text-slate-800 dark:text-white pl-8 pr-4 py-2 text-xs rounded-xl outline-none focus:ring-2 focus:ring-primary-500/50 border border-slate-200 dark:border-slate-700 transition-all"
|
|
263
|
-
/>
|
|
264
|
-
</div>
|
|
480
|
+
{/* ─── STACK TECNOLÓGICO ─── */}
|
|
481
|
+
<section>
|
|
482
|
+
<SectionHeading
|
|
483
|
+
eyebrow="Stack"
|
|
484
|
+
title="Tecnologías que uso"
|
|
485
|
+
subtitle="Filtra por área o busca por nombre."
|
|
486
|
+
className="!mb-5"
|
|
487
|
+
/>
|
|
488
|
+
|
|
489
|
+
<ITFlex align="center" justify="between" wrap="wrap" gap={4} className="!mb-6">
|
|
490
|
+
<ITFlex gap={2} wrap="wrap">
|
|
491
|
+
{categoriesList.map((cat) => (
|
|
492
|
+
<button
|
|
493
|
+
key={cat.id}
|
|
494
|
+
type="button"
|
|
495
|
+
onClick={() => setSelectedCategory(cat.id)}
|
|
496
|
+
className={`inline-flex items-center gap-1.5 rounded-lg px-3.5 py-1.5 text-xs font-semibold transition-all ${
|
|
497
|
+
selectedCategory === cat.id
|
|
498
|
+
? "bg-primary-500 text-white shadow-sm"
|
|
499
|
+
: "bg-slate-100 text-slate-500 hover:bg-slate-200 dark:bg-slate-800 dark:text-slate-400 dark:hover:bg-slate-700"
|
|
500
|
+
}`}
|
|
501
|
+
>
|
|
502
|
+
{cat.icon}
|
|
503
|
+
{cat.name}
|
|
504
|
+
</button>
|
|
505
|
+
))}
|
|
265
506
|
</ITFlex>
|
|
266
507
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
{
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
src={tech.icon}
|
|
279
|
-
alt={tech.name}
|
|
280
|
-
className="w-7 h-7 object-contain"
|
|
281
|
-
/>
|
|
282
|
-
</div>
|
|
283
|
-
<ITText as="span" className="text-[9px] font-semibold text-slate-500 dark:text-slate-400 text-center truncate w-full group-hover:text-slate-700 dark:group-hover:text-slate-200 transition-colors">
|
|
284
|
-
{tech.name}
|
|
285
|
-
</ITText>
|
|
286
|
-
</div>
|
|
287
|
-
</ITGrid>
|
|
288
|
-
))}
|
|
289
|
-
</ITGrid>
|
|
290
|
-
) : (
|
|
291
|
-
<div className="text-center py-12">
|
|
292
|
-
<ITText muted className="text-sm">No se encontraron tecnologías para "{techSearch}"</ITText>
|
|
293
|
-
</div>
|
|
294
|
-
)}
|
|
508
|
+
<div className="relative w-full sm:w-64">
|
|
509
|
+
<FaSearch className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" size={12} />
|
|
510
|
+
<input
|
|
511
|
+
type="text"
|
|
512
|
+
placeholder="Buscar tecnología..."
|
|
513
|
+
value={techSearch}
|
|
514
|
+
onChange={(e) => setTechSearch(e.target.value)}
|
|
515
|
+
className="w-full rounded-xl border border-slate-200 bg-white py-2 pl-8 pr-4 text-xs text-slate-800 outline-none transition-all focus:border-primary-400 focus:ring-2 focus:ring-primary-500/30 dark:border-slate-700 dark:bg-slate-900 dark:text-white"
|
|
516
|
+
/>
|
|
517
|
+
</div>
|
|
518
|
+
</ITFlex>
|
|
295
519
|
|
|
296
|
-
|
|
297
|
-
<
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
520
|
+
{filteredTech.length > 0 ? (
|
|
521
|
+
<ITGrid container spacing={3}>
|
|
522
|
+
{filteredTech.map((tech, index) => (
|
|
523
|
+
<ITGrid item xs={4} sm={3} md={2} lg={2} key={`${tech.name}-${index}`}>
|
|
524
|
+
<TechTile icon={tech.icon} label={tech.name} />
|
|
525
|
+
</ITGrid>
|
|
526
|
+
))}
|
|
527
|
+
</ITGrid>
|
|
528
|
+
) : (
|
|
529
|
+
<div className="rounded-2xl border border-slate-200 bg-white py-12 text-center shadow-sm dark:border-slate-800 dark:bg-slate-900">
|
|
530
|
+
<ITText as="p" className="!text-sm text-slate-500 dark:text-slate-400">
|
|
531
|
+
No se encontraron tecnologías para “{techSearch}”.
|
|
532
|
+
</ITText>
|
|
533
|
+
</div>
|
|
534
|
+
)}
|
|
303
535
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
className="border-slate-200/60 dark:border-slate-800/60 shadow-sm"
|
|
308
|
-
>
|
|
309
|
-
<ITText as="p" muted className="!text-sm !mb-4">
|
|
310
|
-
Productos destacados que he construido.
|
|
536
|
+
<ITDivider className="!my-6" />
|
|
537
|
+
<ITText as="p" className="!text-xs text-center text-slate-400 dark:text-slate-500">
|
|
538
|
+
{filteredTech.length} de {TECH_STACK.length} tecnologías
|
|
311
539
|
</ITText>
|
|
540
|
+
</section>
|
|
541
|
+
|
|
542
|
+
{/* ─── PRODUCTOS ─── */}
|
|
543
|
+
<section>
|
|
544
|
+
<SectionHeading
|
|
545
|
+
eyebrow="Portafolio"
|
|
546
|
+
title="Productos que construí"
|
|
547
|
+
subtitle="Los que tienen capturas se abren en su pantalla de detalle."
|
|
548
|
+
className="!mb-5"
|
|
549
|
+
/>
|
|
550
|
+
|
|
312
551
|
<ITGrid container spacing={5}>
|
|
313
|
-
{
|
|
314
|
-
|
|
552
|
+
{PRODUCTOS.map((producto) => (
|
|
553
|
+
<ITGrid item xs={12} md={6} key={producto.id}>
|
|
554
|
+
<TarjetaProducto producto={producto} onVerDetalle={setDetalle} />
|
|
555
|
+
</ITGrid>
|
|
556
|
+
))}
|
|
557
|
+
</ITGrid>
|
|
558
|
+
</section>
|
|
559
|
+
|
|
560
|
+
{/* ─── CIERRE ─── */}
|
|
561
|
+
<section className="relative overflow-hidden rounded-3xl border border-primary-200 bg-gradient-to-br from-primary-50 via-white to-purple-50 p-6 shadow-sm dark:border-primary-900/40 dark:from-primary-950/30 dark:via-slate-900 dark:to-purple-950/30 sm:p-10">
|
|
562
|
+
<div aria-hidden className="it-landing-float pointer-events-none absolute -top-20 -right-16 h-56 w-56 rounded-full bg-primary-400/20 blur-3xl" />
|
|
563
|
+
<div aria-hidden className="it-landing-grid pointer-events-none absolute inset-0 text-slate-400/40 dark:text-slate-600/30" />
|
|
564
|
+
|
|
565
|
+
<div className="relative z-10">
|
|
566
|
+
<div className="flex flex-col gap-6 lg:flex-row lg:items-start lg:justify-between lg:gap-10">
|
|
567
|
+
<SectionHeading
|
|
568
|
+
eyebrow="Siguientes pasos"
|
|
569
|
+
title="¿Trabajamos juntos?"
|
|
570
|
+
subtitle="Estoy disponible para proyectos fullstack, de producto o de diseño de sistemas."
|
|
571
|
+
/>
|
|
572
|
+
|
|
573
|
+
<ITFlex gap={3} wrap="wrap" align="center" className="shrink-0">
|
|
574
|
+
<a href={CV_URL} download>
|
|
575
|
+
<ITButton variant="filled" color="primary" icon={<FaDownload size={13} />}>
|
|
576
|
+
Descargar CV
|
|
577
|
+
</ITButton>
|
|
578
|
+
</a>
|
|
579
|
+
<a href={LINKEDIN_URL} target="_blank" rel="noopener noreferrer">
|
|
580
|
+
<ITButton variant="outlined" color="secondary" icon={<FaLinkedin size={13} />}>
|
|
581
|
+
LinkedIn
|
|
582
|
+
</ITButton>
|
|
583
|
+
</a>
|
|
584
|
+
</ITFlex>
|
|
585
|
+
</div>
|
|
586
|
+
|
|
587
|
+
<ITText as="p" className="!mt-6 !text-xs text-slate-500 dark:text-slate-400">
|
|
588
|
+
También escribo en{" "}
|
|
315
589
|
<a
|
|
316
|
-
href=
|
|
317
|
-
|
|
590
|
+
href={MEDIUM_URL}
|
|
591
|
+
target="_blank"
|
|
592
|
+
rel="noopener noreferrer"
|
|
593
|
+
className="inline-flex items-center gap-1 font-semibold text-primary-600 dark:text-primary-400"
|
|
318
594
|
>
|
|
319
|
-
<
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
<FaLayerGroup size={20} />
|
|
324
|
-
</div>
|
|
325
|
-
<ITBadget label="Propio" color="primary" variant="filled" />
|
|
326
|
-
</ITFlex>
|
|
327
|
-
<div>
|
|
328
|
-
<ITText as="h3" className="!text-lg !font-bold flex items-center gap-2">
|
|
329
|
-
AXZY UI System
|
|
330
|
-
<FaArrowRight size={14} className="text-primary-500 opacity-0 -translate-x-1 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200" />
|
|
331
|
-
</ITText>
|
|
332
|
-
<ITText as="p" muted className="!text-sm !leading-relaxed mt-1.5">
|
|
333
|
-
Librería de componentes React con Tailwind CSS v4. Más de 50
|
|
334
|
-
componentes enterprise con theming en runtime, TypeScript nativo y
|
|
335
|
-
accesibilidad.
|
|
336
|
-
</ITText>
|
|
337
|
-
</div>
|
|
338
|
-
<ITFlex gap={2} wrap="wrap">
|
|
339
|
-
<ITBadget label="React" color="gray" variant="outlined" />
|
|
340
|
-
<ITBadget label="TypeScript" color="gray" variant="outlined" />
|
|
341
|
-
<ITBadget label="Tailwind" color="gray" variant="outlined" />
|
|
342
|
-
<ITBadget label="npm" color="gray" variant="outlined" />
|
|
343
|
-
</ITFlex>
|
|
344
|
-
</ITStack>
|
|
345
|
-
<ITText as="span" className="!text-xs !font-semibold !text-primary-600 dark:!text-primary-400 inline-flex items-center gap-1.5">
|
|
346
|
-
<SiReact size={12} />
|
|
347
|
-
Ver proyecto
|
|
348
|
-
</ITText>
|
|
349
|
-
</ITStack>
|
|
350
|
-
</a>
|
|
351
|
-
</ITGrid>
|
|
352
|
-
|
|
353
|
-
{/* CheckApp */}
|
|
354
|
-
<ITGrid item xs={12} md={6}>
|
|
595
|
+
<FaMedium size={11} />
|
|
596
|
+
Medium
|
|
597
|
+
</a>{" "}
|
|
598
|
+
y el código de mis productos está en{" "}
|
|
355
599
|
<a
|
|
356
|
-
href=
|
|
600
|
+
href={GITHUB_URL}
|
|
357
601
|
target="_blank"
|
|
358
602
|
rel="noopener noreferrer"
|
|
359
|
-
className="
|
|
603
|
+
className="inline-flex items-center gap-1 font-semibold text-primary-600 dark:text-primary-400"
|
|
360
604
|
>
|
|
361
|
-
<
|
|
362
|
-
|
|
363
|
-
<ITFlex align="center" justify="between">
|
|
364
|
-
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-emerald-500 to-cyan-500 text-white flex items-center justify-center shadow-md">
|
|
365
|
-
<FaCheck size={20} />
|
|
366
|
-
</div>
|
|
367
|
-
<ITBadget label="En vivo" color="gray" variant="outlined" />
|
|
368
|
-
</ITFlex>
|
|
369
|
-
<div>
|
|
370
|
-
<ITText as="h3" className="!text-lg !font-bold flex items-center gap-2">
|
|
371
|
-
CheckApp
|
|
372
|
-
<FaExternalLinkAlt size={12} className="text-slate-400 opacity-0 -translate-x-1 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200" />
|
|
373
|
-
</ITText>
|
|
374
|
-
<ITText as="p" muted className="!text-sm !leading-relaxed mt-1.5">
|
|
375
|
-
Aplicación web para consultar información de manera rápida y
|
|
376
|
-
sencilla. Desplegada y disponible en línea.
|
|
377
|
-
</ITText>
|
|
378
|
-
</div>
|
|
379
|
-
<ITFlex gap={2} wrap="wrap">
|
|
380
|
-
<ITBadget label="Node.js" color="gray" variant="outlined" />
|
|
381
|
-
<ITBadget label="React" color="gray" variant="outlined" />
|
|
382
|
-
<ITBadget label="Deploy" color="gray" variant="outlined" />
|
|
383
|
-
</ITFlex>
|
|
384
|
-
</ITStack>
|
|
385
|
-
<ITText as="span" className="!text-xs !font-semibold !text-emerald-600 dark:!text-emerald-400 inline-flex items-center gap-1.5">
|
|
386
|
-
<SiNodedotjs size={12} />
|
|
387
|
-
Abrir CheckApp
|
|
388
|
-
</ITText>
|
|
389
|
-
</ITStack>
|
|
605
|
+
<FaGithub size={11} />
|
|
606
|
+
GitHub
|
|
390
607
|
</a>
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
608
|
+
.
|
|
609
|
+
</ITText>
|
|
610
|
+
</div>
|
|
611
|
+
</section>
|
|
612
|
+
|
|
613
|
+
{/* Pantalla de detalle del producto elegido */}
|
|
614
|
+
{detalle && <ProductoDetalle producto={detalle} onClose={() => setDetalle(null)} />}
|
|
394
615
|
</ITStack>
|
|
395
616
|
);
|
|
396
617
|
};
|