@imansi/templates 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +557 -0
- package/package.json +35 -0
- package/src/auth/ForgotPasswordPage.jsx +176 -0
- package/src/auth/LoginPage.jsx +265 -0
- package/src/auth/RegisterPage.jsx +302 -0
- package/src/auth/ResetPasswordPage.jsx +158 -0
- package/src/auth/Verify2FAPage.jsx +175 -0
- package/src/dashboard/BillingPage.jsx +345 -0
- package/src/dashboard/DashboardHome.jsx +246 -0
- package/src/dashboard/NotificationsPage.jsx +230 -0
- package/src/dashboard/ProfilePage.jsx +272 -0
- package/src/dashboard/SettingsPage.jsx +415 -0
- package/src/dashboard/UsersTablePage.jsx +317 -0
- package/src/extra/CareersPage.jsx +166 -0
- package/src/extra/ChangelogPage.jsx +127 -0
- package/src/extra/CheckoutPage.jsx +383 -0
- package/src/extra/ComingSoonPage.jsx +147 -0
- package/src/extra/ErrorPage.jsx +91 -0
- package/src/extra/InvoicePage.jsx +241 -0
- package/src/extra/LegalPage.jsx +115 -0
- package/src/extra/MaintenancePage.jsx +172 -0
- package/src/extra/OnboardingPage.jsx +197 -0
- package/src/extra/StatusPage.jsx +181 -0
- package/src/index.js +53 -0
- package/src/layouts/AuthLayout.jsx +104 -0
- package/src/layouts/DashboardLayout.jsx +249 -0
- package/src/layouts/MarketingLayout.jsx +184 -0
- package/src/marketing/AboutPage.jsx +189 -0
- package/src/marketing/BlogPage.jsx +182 -0
- package/src/marketing/BlogPostPage.jsx +192 -0
- package/src/marketing/ContactPage.jsx +546 -0
- package/src/marketing/DocsPage.jsx +256 -0
- package/src/marketing/HomePage.jsx +702 -0
- package/src/marketing/LandingPage.jsx +755 -0
- package/src/marketing/PricingPage.jsx +205 -0
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Link } from 'react-router-dom';
|
|
3
|
+
import {
|
|
4
|
+
Avatar,
|
|
5
|
+
AvatarFallback,
|
|
6
|
+
Button,
|
|
7
|
+
Tabs,
|
|
8
|
+
TabsList,
|
|
9
|
+
TabsTrigger,
|
|
10
|
+
TabsContent,
|
|
11
|
+
cn,
|
|
12
|
+
} from '@imansi/ui';
|
|
13
|
+
import { MarketingLayout } from '../layouts/MarketingLayout.jsx';
|
|
14
|
+
|
|
15
|
+
const Icons = {
|
|
16
|
+
arrow: (
|
|
17
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
18
|
+
<path d="M5 12h14" />
|
|
19
|
+
<path d="M12 5l7 7-7 7" />
|
|
20
|
+
</svg>
|
|
21
|
+
),
|
|
22
|
+
check: (
|
|
23
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
24
|
+
<polyline points="4,12 10,18 20,6" />
|
|
25
|
+
</svg>
|
|
26
|
+
),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/* ============================================================
|
|
30
|
+
Mockup tipo Linear — RESPONSIVE
|
|
31
|
+
- Mobile: solo contenido (sin sidebar)
|
|
32
|
+
- Tablet+: sidebar + contenido
|
|
33
|
+
============================================================ */
|
|
34
|
+
function AppMockup({ view = 'issues' }) {
|
|
35
|
+
return (
|
|
36
|
+
<div className="rounded-xl border border-border bg-card overflow-hidden shadow-2xl shadow-black/20">
|
|
37
|
+
{/* Title bar */}
|
|
38
|
+
<div className="flex items-center gap-2 px-3 sm:px-4 py-2.5 sm:py-3 border-b border-border bg-muted/20">
|
|
39
|
+
<div className="flex gap-1.5">
|
|
40
|
+
<span className="h-2.5 w-2.5 rounded-full bg-muted-foreground/25" />
|
|
41
|
+
<span className="h-2.5 w-2.5 rounded-full bg-muted-foreground/25" />
|
|
42
|
+
<span className="h-2.5 w-2.5 rounded-full bg-muted-foreground/25" />
|
|
43
|
+
</div>
|
|
44
|
+
<div className="flex-1 flex justify-center min-w-0">
|
|
45
|
+
<div className="inline-flex items-center gap-1.5 text-[9px] sm:text-[10px] text-muted-foreground font-mono bg-background/50 px-2 sm:px-2.5 py-1 rounded-md truncate">
|
|
46
|
+
<span className="h-1.5 w-1.5 rounded-full bg-success shrink-0" />
|
|
47
|
+
<span className="truncate">app.imansi.com</span>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
{/* Grid: 1 col en mobile, sidebar + content en md+ */}
|
|
53
|
+
<div className="grid md:grid-cols-[180px_1fr] min-h-[360px] md:min-h-[420px]">
|
|
54
|
+
{/* Sidebar — solo en md+ */}
|
|
55
|
+
<aside className="hidden md:block border-r border-border bg-muted/10 p-3 space-y-5">
|
|
56
|
+
<div className="flex items-center gap-2 px-1.5 py-1">
|
|
57
|
+
<div className="h-6 w-6 rounded-md bg-primary flex items-center justify-center text-[10px] font-bold text-primary-foreground">
|
|
58
|
+
I
|
|
59
|
+
</div>
|
|
60
|
+
<span className="text-[11px] font-semibold">Imansi</span>
|
|
61
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-muted-foreground ml-auto">
|
|
62
|
+
<polyline points="6 9 12 15 18 9" />
|
|
63
|
+
</svg>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div className="space-y-0.5">
|
|
67
|
+
<p className="px-2 pb-1 text-[9px] uppercase tracking-wider text-muted-foreground/60 font-semibold">
|
|
68
|
+
Workspace
|
|
69
|
+
</p>
|
|
70
|
+
{[
|
|
71
|
+
{ label: 'Inbox', badge: '3' },
|
|
72
|
+
{ label: 'My Issues', active: true },
|
|
73
|
+
{ label: 'Backlog' },
|
|
74
|
+
].map((item) => (
|
|
75
|
+
<div
|
|
76
|
+
key={item.label}
|
|
77
|
+
className={cn(
|
|
78
|
+
'flex items-center gap-2 px-2 py-1.5 rounded-md text-[11px] transition-colors',
|
|
79
|
+
item.active
|
|
80
|
+
? 'bg-primary/10 text-foreground font-medium'
|
|
81
|
+
: 'text-muted-foreground'
|
|
82
|
+
)}
|
|
83
|
+
>
|
|
84
|
+
<span
|
|
85
|
+
className={cn(
|
|
86
|
+
'h-1.5 w-1.5 rounded-full',
|
|
87
|
+
item.active ? 'bg-primary' : 'bg-muted-foreground/40'
|
|
88
|
+
)}
|
|
89
|
+
/>
|
|
90
|
+
<span>{item.label}</span>
|
|
91
|
+
{item.badge && (
|
|
92
|
+
<span className="ml-auto text-[9px] bg-muted rounded px-1.5 py-0.5">
|
|
93
|
+
{item.badge}
|
|
94
|
+
</span>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
))}
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div className="space-y-0.5">
|
|
101
|
+
<p className="px-2 pb-1 text-[9px] uppercase tracking-wider text-muted-foreground/60 font-semibold">
|
|
102
|
+
Teams
|
|
103
|
+
</p>
|
|
104
|
+
{['Design', 'Engineering', 'Product'].map((team) => (
|
|
105
|
+
<div
|
|
106
|
+
key={team}
|
|
107
|
+
className="flex items-center gap-2 px-2 py-1.5 rounded-md text-[11px] text-muted-foreground"
|
|
108
|
+
>
|
|
109
|
+
<span className="h-1.5 w-1.5 rounded-full bg-muted-foreground/40" />
|
|
110
|
+
<span>{team}</span>
|
|
111
|
+
</div>
|
|
112
|
+
))}
|
|
113
|
+
</div>
|
|
114
|
+
</aside>
|
|
115
|
+
|
|
116
|
+
{/* Main */}
|
|
117
|
+
<div className="bg-background/20 min-w-0">
|
|
118
|
+
{view === 'issues' && (
|
|
119
|
+
<div>
|
|
120
|
+
<div className="flex items-center gap-2 sm:gap-3 px-4 sm:px-5 py-3 sm:py-3.5 border-b border-border">
|
|
121
|
+
<span className="text-[11px] sm:text-[12px] font-semibold">
|
|
122
|
+
My Issues
|
|
123
|
+
</span>
|
|
124
|
+
<span className="text-[9px] sm:text-[10px] text-muted-foreground bg-muted/60 rounded-md px-1.5 py-0.5">
|
|
125
|
+
12
|
|
126
|
+
</span>
|
|
127
|
+
<div className="ml-auto flex -space-x-1.5">
|
|
128
|
+
{['A', 'J', 'M'].map((initial, i) => (
|
|
129
|
+
<div
|
|
130
|
+
key={i}
|
|
131
|
+
className={cn(
|
|
132
|
+
'h-5 w-5 rounded-full border-2 border-background flex items-center justify-center text-[8px] font-bold',
|
|
133
|
+
i === 0 && 'bg-primary text-primary-foreground',
|
|
134
|
+
i === 1 && 'bg-success text-success-foreground',
|
|
135
|
+
i === 2 && 'bg-warning text-warning-foreground'
|
|
136
|
+
)}
|
|
137
|
+
>
|
|
138
|
+
{initial}
|
|
139
|
+
</div>
|
|
140
|
+
))}
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<div className="divide-y divide-border/60">
|
|
145
|
+
{[
|
|
146
|
+
{ id: 'ISS-101', title: 'Design tokens migration', status: 'progress', priority: 'high', assignee: 'A' },
|
|
147
|
+
{ id: 'ISS-102', title: 'Landing page redesign', status: 'todo', priority: 'med', assignee: 'J' },
|
|
148
|
+
{ id: 'ISS-103', title: 'Auth flow error handling', status: 'progress', priority: 'high', assignee: 'M' },
|
|
149
|
+
{ id: 'ISS-104', title: 'Component library docs', status: 'done', priority: 'low', assignee: 'A' },
|
|
150
|
+
{ id: 'ISS-105', title: 'Dashboard charts perf', status: 'todo', priority: 'med', assignee: 'C' },
|
|
151
|
+
{ id: 'ISS-106', title: 'Search command palette', status: 'progress', priority: 'med', assignee: 'J' },
|
|
152
|
+
].map((issue) => (
|
|
153
|
+
<div
|
|
154
|
+
key={issue.id}
|
|
155
|
+
className="flex items-center gap-2 sm:gap-3 px-4 sm:px-5 py-2.5 text-[10px] sm:text-[11px] hover:bg-muted/30 transition-colors"
|
|
156
|
+
>
|
|
157
|
+
<span className="text-[8px] sm:text-[9px] text-muted-foreground font-mono w-12 sm:w-14 shrink-0">
|
|
158
|
+
{issue.id}
|
|
159
|
+
</span>
|
|
160
|
+
<span
|
|
161
|
+
className={cn(
|
|
162
|
+
'h-2.5 w-2.5 sm:h-3 sm:w-3 rounded-full shrink-0',
|
|
163
|
+
issue.status === 'done' && 'bg-success',
|
|
164
|
+
issue.status === 'progress' && 'border-2 border-dashed border-primary',
|
|
165
|
+
issue.status === 'todo' && 'border-2 border-muted-foreground/40'
|
|
166
|
+
)}
|
|
167
|
+
/>
|
|
168
|
+
<span className="flex-1 truncate text-foreground/90">
|
|
169
|
+
{issue.title}
|
|
170
|
+
</span>
|
|
171
|
+
{issue.priority === 'high' && (
|
|
172
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" className="text-destructive shrink-0">
|
|
173
|
+
<path d="M12 19V5M5 12l7-7 7 7" />
|
|
174
|
+
</svg>
|
|
175
|
+
)}
|
|
176
|
+
<div className="h-4 w-4 rounded-full bg-primary/20 text-primary flex items-center justify-center text-[8px] font-bold shrink-0">
|
|
177
|
+
{issue.assignee}
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
))}
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
)}
|
|
184
|
+
|
|
185
|
+
{view === 'command' && (
|
|
186
|
+
<div className="p-4 sm:p-8">
|
|
187
|
+
<div className="rounded-lg border border-border bg-card shadow-xl max-w-md mx-auto">
|
|
188
|
+
<div className="flex items-center gap-2 sm:gap-2.5 px-3 sm:px-3.5 py-2.5 sm:py-3 border-b border-border">
|
|
189
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-muted-foreground shrink-0">
|
|
190
|
+
<circle cx="11" cy="11" r="8" />
|
|
191
|
+
<path d="m21 21-4.3-4.3" />
|
|
192
|
+
</svg>
|
|
193
|
+
<span className="text-[10px] sm:text-[11px] text-muted-foreground flex-1 truncate">
|
|
194
|
+
Type a command or search...
|
|
195
|
+
</span>
|
|
196
|
+
<kbd className="text-[8px] sm:text-[9px] bg-muted px-1.5 py-0.5 rounded font-mono shrink-0">
|
|
197
|
+
ESC
|
|
198
|
+
</kbd>
|
|
199
|
+
</div>
|
|
200
|
+
<div className="p-1.5 space-y-0.5">
|
|
201
|
+
{[
|
|
202
|
+
{ label: 'Create new issue', shortcut: 'C', active: true },
|
|
203
|
+
{ label: 'Change theme', shortcut: 'T' },
|
|
204
|
+
{ label: 'Invite teammate', shortcut: 'I' },
|
|
205
|
+
{ label: 'Open settings', shortcut: 'S' },
|
|
206
|
+
{ label: 'Toggle sidebar', shortcut: 'B' },
|
|
207
|
+
].map((item) => (
|
|
208
|
+
<div
|
|
209
|
+
key={item.label}
|
|
210
|
+
className={cn(
|
|
211
|
+
'flex items-center gap-2 sm:gap-2.5 px-2 sm:px-2.5 py-1.5 sm:py-2 rounded-md text-[10px] sm:text-[11px]',
|
|
212
|
+
item.active
|
|
213
|
+
? 'bg-primary/10 text-foreground'
|
|
214
|
+
: 'text-muted-foreground'
|
|
215
|
+
)}
|
|
216
|
+
>
|
|
217
|
+
<span
|
|
218
|
+
className={cn(
|
|
219
|
+
'h-3.5 w-3.5 sm:h-4 sm:w-4 rounded border shrink-0',
|
|
220
|
+
item.active
|
|
221
|
+
? 'bg-primary border-primary'
|
|
222
|
+
: 'border-muted-foreground/30'
|
|
223
|
+
)}
|
|
224
|
+
/>
|
|
225
|
+
<span className="flex-1 truncate">{item.label}</span>
|
|
226
|
+
<kbd className="text-[8px] sm:text-[9px] bg-muted px-1.5 py-0.5 rounded font-mono shrink-0">
|
|
227
|
+
{item.shortcut}
|
|
228
|
+
</kbd>
|
|
229
|
+
</div>
|
|
230
|
+
))}
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
)}
|
|
235
|
+
|
|
236
|
+
{view === 'themes' && (
|
|
237
|
+
<div className="p-4 sm:p-8 space-y-4">
|
|
238
|
+
<div className="text-center">
|
|
239
|
+
<p className="text-[11px] sm:text-[12px] font-semibold mb-1">Tema</p>
|
|
240
|
+
<p className="text-[9px] sm:text-[10px] text-muted-foreground">
|
|
241
|
+
Cambiá el tema sin tocar componentes.
|
|
242
|
+
</p>
|
|
243
|
+
</div>
|
|
244
|
+
<div className="grid grid-cols-2 gap-2 sm:gap-2.5 max-w-md mx-auto">
|
|
245
|
+
{['minimal', 'modern', 'compact', 'warm'].map((theme) => (
|
|
246
|
+
<div
|
|
247
|
+
key={theme}
|
|
248
|
+
data-theme={theme}
|
|
249
|
+
data-mode="dark"
|
|
250
|
+
className="rounded-lg border border-border bg-background p-2.5 sm:p-3 space-y-2 sm:space-y-2.5"
|
|
251
|
+
>
|
|
252
|
+
<div className="flex gap-1 sm:gap-1.5">
|
|
253
|
+
<div className="h-3.5 w-3.5 sm:h-4 sm:w-4 rounded-md bg-primary" />
|
|
254
|
+
<div className="h-3.5 w-3.5 sm:h-4 sm:w-4 rounded-md bg-success" />
|
|
255
|
+
<div className="h-3.5 w-3.5 sm:h-4 sm:w-4 rounded-md bg-warning" />
|
|
256
|
+
<div className="h-3.5 w-3.5 sm:h-4 sm:w-4 rounded-md bg-destructive" />
|
|
257
|
+
</div>
|
|
258
|
+
<p className="text-[9px] sm:text-[10px] capitalize text-muted-foreground font-medium">
|
|
259
|
+
{theme}
|
|
260
|
+
</p>
|
|
261
|
+
</div>
|
|
262
|
+
))}
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
)}
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/* ============================================================
|
|
273
|
+
LandingPage
|
|
274
|
+
============================================================ */
|
|
275
|
+
export function LandingPage({
|
|
276
|
+
brandName = 'Imansi',
|
|
277
|
+
brandLogo,
|
|
278
|
+
navLinks = [],
|
|
279
|
+
headerCTA = { label: 'Empezar', href: '/registro' },
|
|
280
|
+
headerSecondaryCTA = { label: 'Iniciar sesión', href: '/login' },
|
|
281
|
+
footerColumns = [],
|
|
282
|
+
|
|
283
|
+
badge = 'v0.2.0 disponible',
|
|
284
|
+
badgeHref = '/changelog',
|
|
285
|
+
title = 'Software para construir software',
|
|
286
|
+
titleHighlight = 'construir software',
|
|
287
|
+
subtitle = 'Un design system completo con 50+ componentes, 6 temas y 28 páginas. Todo lo que necesitás para lanzar tu producto.',
|
|
288
|
+
primaryCTA = { label: 'Empezar gratis', href: '/registro' },
|
|
289
|
+
secondaryCTA = { label: 'Ver demo', href: '/login' },
|
|
290
|
+
caption = 'Sin tarjeta de crédito · Cancelá cuando quieras',
|
|
291
|
+
|
|
292
|
+
logosLabel = 'Usado por equipos de',
|
|
293
|
+
logos = ['Acme', 'Globex', 'Initech', 'Umbrella', 'Soylent', 'Hooli'],
|
|
294
|
+
|
|
295
|
+
featuresLabel = 'Features',
|
|
296
|
+
featuresTitle = 'Una base sólida para cada decisión',
|
|
297
|
+
featuresSubtitle = 'Diseñado con las mejores prácticas. Nada de deuda técnica.',
|
|
298
|
+
features = [],
|
|
299
|
+
|
|
300
|
+
productLabel = 'Producto',
|
|
301
|
+
productTitle = 'Hacé más con menos',
|
|
302
|
+
productSubtitle = 'Mirá cómo se ve en la práctica.',
|
|
303
|
+
productTabs = [],
|
|
304
|
+
|
|
305
|
+
testimonialsLabel = 'Testimonios',
|
|
306
|
+
testimonialsTitle = 'Amado por los equipos',
|
|
307
|
+
testimonialsSubtitle = 'Lo que dicen los que ya construyeron con Imansi.',
|
|
308
|
+
testimonials = [],
|
|
309
|
+
|
|
310
|
+
ctaTitle = 'Empezá ahora',
|
|
311
|
+
ctaSubtitle = 'Unite a los equipos que ya están construyendo con Imansi.',
|
|
312
|
+
ctaPrimary = { label: 'Crear cuenta', href: '/registro' },
|
|
313
|
+
ctaSecondary = { label: 'Documentación', href: '/docs' },
|
|
314
|
+
}) {
|
|
315
|
+
const [activeTab, setActiveTab] = useState(productTabs[0]?.value || '');
|
|
316
|
+
|
|
317
|
+
function renderTitle(text, highlight) {
|
|
318
|
+
if (!highlight) return text;
|
|
319
|
+
const parts = text.split(highlight);
|
|
320
|
+
if (parts.length < 2) return text;
|
|
321
|
+
return (
|
|
322
|
+
<>
|
|
323
|
+
{parts[0]}
|
|
324
|
+
<span className="bg-gradient-to-r from-primary to-accent bg-clip-text text-transparent">
|
|
325
|
+
{highlight}
|
|
326
|
+
</span>
|
|
327
|
+
{parts[1]}
|
|
328
|
+
</>
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return (
|
|
333
|
+
<MarketingLayout
|
|
334
|
+
brandName={brandName}
|
|
335
|
+
brandLogo={brandLogo}
|
|
336
|
+
navLinks={navLinks}
|
|
337
|
+
headerCTA={headerCTA}
|
|
338
|
+
headerSecondaryCTA={headerSecondaryCTA}
|
|
339
|
+
footerColumns={footerColumns}
|
|
340
|
+
>
|
|
341
|
+
{/* ============================================================
|
|
342
|
+
HERO
|
|
343
|
+
============================================================ */}
|
|
344
|
+
<section className="relative overflow-hidden">
|
|
345
|
+
<div className="absolute inset-0 pointer-events-none" aria-hidden="true">
|
|
346
|
+
<div
|
|
347
|
+
className="absolute top-[-30%] left-1/2 -translate-x-1/2 w-[900px] sm:w-[1400px] h-[700px] sm:h-[1000px]"
|
|
348
|
+
style={{
|
|
349
|
+
background: `radial-gradient(ellipse at center, var(--color-primary) 0%, transparent 55%)`,
|
|
350
|
+
opacity: 0.16,
|
|
351
|
+
filter: 'blur(100px)',
|
|
352
|
+
}}
|
|
353
|
+
/>
|
|
354
|
+
<div
|
|
355
|
+
className="absolute inset-0"
|
|
356
|
+
style={{
|
|
357
|
+
backgroundImage: `
|
|
358
|
+
linear-gradient(currentColor 1px, transparent 1px),
|
|
359
|
+
linear-gradient(90deg, currentColor 1px, transparent 1px)
|
|
360
|
+
`,
|
|
361
|
+
backgroundSize: '48px 48px',
|
|
362
|
+
opacity: 0.02,
|
|
363
|
+
maskImage: 'radial-gradient(ellipse 70% 55% at 50% 35%, black 30%, transparent 80%)',
|
|
364
|
+
}}
|
|
365
|
+
/>
|
|
366
|
+
</div>
|
|
367
|
+
|
|
368
|
+
<div className="relative imansi-container pt-20 pb-16 sm:pt-28 sm:pb-24 lg:pt-36 lg:pb-28">
|
|
369
|
+
<div className="max-w-3xl mx-auto text-center">
|
|
370
|
+
{badge && (
|
|
371
|
+
<div className="mb-6 sm:mb-8 flex justify-center">
|
|
372
|
+
<Link
|
|
373
|
+
to={badgeHref}
|
|
374
|
+
className="inline-flex items-center gap-2 rounded-full border border-border bg-card/60 backdrop-blur px-3 sm:px-3.5 py-1.5 text-[11px] sm:text-caption font-medium transition-colors hover:border-primary/40 group"
|
|
375
|
+
>
|
|
376
|
+
<span className="flex h-1.5 w-1.5 rounded-full bg-primary" />
|
|
377
|
+
<span>{badge}</span>
|
|
378
|
+
<svg
|
|
379
|
+
width="12"
|
|
380
|
+
height="12"
|
|
381
|
+
viewBox="0 0 24 24"
|
|
382
|
+
fill="none"
|
|
383
|
+
stroke="currentColor"
|
|
384
|
+
strokeWidth="2.5"
|
|
385
|
+
strokeLinecap="round"
|
|
386
|
+
strokeLinejoin="round"
|
|
387
|
+
className="transition-transform group-hover:translate-x-0.5"
|
|
388
|
+
>
|
|
389
|
+
<path d="M9 18l6-6-6-6" />
|
|
390
|
+
</svg>
|
|
391
|
+
</Link>
|
|
392
|
+
</div>
|
|
393
|
+
)}
|
|
394
|
+
|
|
395
|
+
<h1 className="text-h1 sm:text-display font-bold tracking-[-0.035em] leading-[1.05] mb-5 sm:mb-6">
|
|
396
|
+
{renderTitle(title, titleHighlight)}
|
|
397
|
+
</h1>
|
|
398
|
+
|
|
399
|
+
<p className="text-small sm:text-body text-muted-foreground leading-relaxed max-w-2xl mx-auto mb-8 sm:mb-10">
|
|
400
|
+
{subtitle}
|
|
401
|
+
</p>
|
|
402
|
+
|
|
403
|
+
<div className="flex flex-col sm:flex-row flex-wrap justify-center gap-3 mb-4 sm:mb-5">
|
|
404
|
+
{primaryCTA && (
|
|
405
|
+
<Link to={primaryCTA.href} className="w-full sm:w-auto">
|
|
406
|
+
<Button size="lg" className="gap-2 w-full sm:w-auto">
|
|
407
|
+
{primaryCTA.label}
|
|
408
|
+
{Icons.arrow}
|
|
409
|
+
</Button>
|
|
410
|
+
</Link>
|
|
411
|
+
)}
|
|
412
|
+
{secondaryCTA && (
|
|
413
|
+
<Link to={secondaryCTA.href} className="w-full sm:w-auto">
|
|
414
|
+
<Button size="lg" variant="outline" className="w-full sm:w-auto">
|
|
415
|
+
{secondaryCTA.label}
|
|
416
|
+
</Button>
|
|
417
|
+
</Link>
|
|
418
|
+
)}
|
|
419
|
+
</div>
|
|
420
|
+
|
|
421
|
+
{caption && (
|
|
422
|
+
<p className="text-[11px] sm:text-caption text-muted-foreground">{caption}</p>
|
|
423
|
+
)}
|
|
424
|
+
</div>
|
|
425
|
+
|
|
426
|
+
{/* Mockup */}
|
|
427
|
+
<div className="relative mt-14 sm:mt-20 lg:mt-24 max-w-6xl mx-auto">
|
|
428
|
+
<div
|
|
429
|
+
className="absolute -inset-x-4 sm:-inset-x-12 -top-8 sm:-top-12 -bottom-12 sm:-bottom-20 pointer-events-none"
|
|
430
|
+
style={{
|
|
431
|
+
background: `radial-gradient(ellipse at center, var(--color-primary) 0%, transparent 60%)`,
|
|
432
|
+
opacity: 0.14,
|
|
433
|
+
filter: 'blur(60px)',
|
|
434
|
+
}}
|
|
435
|
+
aria-hidden="true"
|
|
436
|
+
/>
|
|
437
|
+
|
|
438
|
+
<div className="relative">
|
|
439
|
+
<AppMockup view="issues" />
|
|
440
|
+
</div>
|
|
441
|
+
|
|
442
|
+
{/* Floating cards — solo desktop grande */}
|
|
443
|
+
<div className="absolute -top-4 -left-8 hidden xl:block">
|
|
444
|
+
<div className="rounded-lg border border-border bg-card p-3 shadow-2xl shadow-black/30 backdrop-blur">
|
|
445
|
+
<div className="flex items-center gap-3">
|
|
446
|
+
<div className="flex h-9 w-9 items-center justify-center rounded-md bg-success/10 text-success">
|
|
447
|
+
{Icons.check}
|
|
448
|
+
</div>
|
|
449
|
+
<div>
|
|
450
|
+
<p className="text-[11px] font-semibold leading-none mb-1">
|
|
451
|
+
Deploy successful
|
|
452
|
+
</p>
|
|
453
|
+
<p className="text-[10px] text-muted-foreground leading-none">
|
|
454
|
+
hace 2 minutos
|
|
455
|
+
</p>
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
</div>
|
|
459
|
+
</div>
|
|
460
|
+
|
|
461
|
+
<div className="absolute -bottom-4 -right-8 hidden xl:block">
|
|
462
|
+
<div className="rounded-lg border border-border bg-card p-3.5 shadow-2xl shadow-black/30 backdrop-blur">
|
|
463
|
+
<p className="text-[10px] uppercase tracking-wider text-muted-foreground font-medium mb-2.5">
|
|
464
|
+
Equipo
|
|
465
|
+
</p>
|
|
466
|
+
<div className="flex -space-x-2">
|
|
467
|
+
{['A', 'J', 'M', 'C'].map((initial, i) => (
|
|
468
|
+
<div
|
|
469
|
+
key={i}
|
|
470
|
+
className={cn(
|
|
471
|
+
'h-6 w-6 rounded-full border-2 border-card flex items-center justify-center text-[9px] font-bold',
|
|
472
|
+
i === 0 && 'bg-primary text-primary-foreground',
|
|
473
|
+
i === 1 && 'bg-success text-success-foreground',
|
|
474
|
+
i === 2 && 'bg-warning text-warning-foreground',
|
|
475
|
+
i === 3 && 'bg-destructive text-destructive-foreground'
|
|
476
|
+
)}
|
|
477
|
+
>
|
|
478
|
+
{initial}
|
|
479
|
+
</div>
|
|
480
|
+
))}
|
|
481
|
+
<div className="h-6 w-6 rounded-full bg-muted border-2 border-card flex items-center justify-center text-[9px] font-medium">
|
|
482
|
+
+8
|
|
483
|
+
</div>
|
|
484
|
+
</div>
|
|
485
|
+
</div>
|
|
486
|
+
</div>
|
|
487
|
+
</div>
|
|
488
|
+
</div>
|
|
489
|
+
</section>
|
|
490
|
+
|
|
491
|
+
{/* ============================================================
|
|
492
|
+
LOGOS
|
|
493
|
+
============================================================ */}
|
|
494
|
+
{logos.length > 0 && (
|
|
495
|
+
<section className="border-y border-border/60">
|
|
496
|
+
<div className="imansi-container py-10 sm:py-14">
|
|
497
|
+
<p className="text-center text-[10px] sm:text-[11px] uppercase tracking-[0.2em] text-muted-foreground/70 font-medium mb-6 sm:mb-10">
|
|
498
|
+
{logosLabel}
|
|
499
|
+
</p>
|
|
500
|
+
<div className="flex flex-wrap justify-center items-center gap-x-8 sm:gap-x-12 lg:gap-x-16 gap-y-4 sm:gap-y-6">
|
|
501
|
+
{logos.map((logo) => (
|
|
502
|
+
<span
|
|
503
|
+
key={logo}
|
|
504
|
+
className="text-h4 font-bold tracking-tight text-muted-foreground/30 hover:text-muted-foreground/60 transition-colors"
|
|
505
|
+
>
|
|
506
|
+
{logo}
|
|
507
|
+
</span>
|
|
508
|
+
))}
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
</section>
|
|
512
|
+
)}
|
|
513
|
+
|
|
514
|
+
{/* ============================================================
|
|
515
|
+
FEATURES
|
|
516
|
+
============================================================ */}
|
|
517
|
+
{features.length > 0 && (
|
|
518
|
+
<section className="imansi-container py-20 sm:py-24 lg:py-28">
|
|
519
|
+
<div className="max-w-2xl mx-auto text-center mb-14 sm:mb-16 lg:mb-20 space-y-3 sm:space-y-4">
|
|
520
|
+
{featuresLabel && (
|
|
521
|
+
<p className="text-caption uppercase tracking-[0.2em] text-primary font-semibold">
|
|
522
|
+
{featuresLabel}
|
|
523
|
+
</p>
|
|
524
|
+
)}
|
|
525
|
+
<h2 className="text-h1 sm:text-display font-bold tracking-[-0.025em] leading-[1.1]">
|
|
526
|
+
{featuresTitle}
|
|
527
|
+
</h2>
|
|
528
|
+
<p className="text-small sm:text-body text-muted-foreground leading-relaxed">
|
|
529
|
+
{featuresSubtitle}
|
|
530
|
+
</p>
|
|
531
|
+
</div>
|
|
532
|
+
|
|
533
|
+
<div className="grid gap-x-8 lg:gap-x-10 gap-y-12 lg:gap-y-14 md:grid-cols-3 max-w-5xl mx-auto">
|
|
534
|
+
{features.map((feature, i) => (
|
|
535
|
+
<div key={i} className="text-center space-y-4 sm:space-y-5">
|
|
536
|
+
<div className="flex h-12 w-12 sm:h-14 sm:w-14 items-center justify-center rounded-xl border border-border bg-card text-foreground mx-auto">
|
|
537
|
+
{feature.icon}
|
|
538
|
+
</div>
|
|
539
|
+
<div className="space-y-2.5 sm:space-y-3">
|
|
540
|
+
<h3 className="text-h4 font-semibold tracking-tight">
|
|
541
|
+
{feature.title}
|
|
542
|
+
</h3>
|
|
543
|
+
<p className="text-small text-muted-foreground leading-relaxed max-w-xs mx-auto">
|
|
544
|
+
{feature.description}
|
|
545
|
+
</p>
|
|
546
|
+
</div>
|
|
547
|
+
</div>
|
|
548
|
+
))}
|
|
549
|
+
</div>
|
|
550
|
+
</section>
|
|
551
|
+
)}
|
|
552
|
+
|
|
553
|
+
{/* ============================================================
|
|
554
|
+
PRODUCT TABS
|
|
555
|
+
============================================================ */}
|
|
556
|
+
{productTabs.length > 0 && (
|
|
557
|
+
<section className="relative border-y border-border/60 bg-muted/20 overflow-hidden">
|
|
558
|
+
<div
|
|
559
|
+
className="absolute inset-0 pointer-events-none"
|
|
560
|
+
style={{
|
|
561
|
+
backgroundImage: `
|
|
562
|
+
linear-gradient(currentColor 1px, transparent 1px),
|
|
563
|
+
linear-gradient(90deg, currentColor 1px, transparent 1px)
|
|
564
|
+
`,
|
|
565
|
+
backgroundSize: '48px 48px',
|
|
566
|
+
opacity: 0.02,
|
|
567
|
+
}}
|
|
568
|
+
aria-hidden="true"
|
|
569
|
+
/>
|
|
570
|
+
|
|
571
|
+
<div className="relative imansi-container py-20 sm:py-24 lg:py-28">
|
|
572
|
+
<div className="max-w-2xl mx-auto text-center mb-12 sm:mb-14 lg:mb-16 space-y-3 sm:space-y-4">
|
|
573
|
+
{productLabel && (
|
|
574
|
+
<p className="text-caption uppercase tracking-[0.2em] text-primary font-semibold">
|
|
575
|
+
{productLabel}
|
|
576
|
+
</p>
|
|
577
|
+
)}
|
|
578
|
+
<h2 className="text-h1 sm:text-display font-bold tracking-[-0.025em] leading-[1.1]">
|
|
579
|
+
{productTitle}
|
|
580
|
+
</h2>
|
|
581
|
+
<p className="text-small sm:text-body text-muted-foreground leading-relaxed">
|
|
582
|
+
{productSubtitle}
|
|
583
|
+
</p>
|
|
584
|
+
</div>
|
|
585
|
+
|
|
586
|
+
<Tabs value={activeTab} onValueChange={setActiveTab} className="max-w-5xl mx-auto">
|
|
587
|
+
{/* TabsList — centrada con scroll horizontal en mobile */}
|
|
588
|
+
<div className="flex justify-center mb-10 sm:mb-12 overflow-x-auto -mx-4 px-4 sm:mx-0 sm:px-0">
|
|
589
|
+
<TabsList className="bg-card border border-border shrink-0">
|
|
590
|
+
{productTabs.map((tab) => (
|
|
591
|
+
<TabsTrigger key={tab.value} value={tab.value}>
|
|
592
|
+
{tab.label}
|
|
593
|
+
</TabsTrigger>
|
|
594
|
+
))}
|
|
595
|
+
</TabsList>
|
|
596
|
+
</div>
|
|
597
|
+
|
|
598
|
+
{productTabs.map((tab) => (
|
|
599
|
+
<TabsContent key={tab.value} value={tab.value}>
|
|
600
|
+
<div className="space-y-8 sm:space-y-10 lg:space-y-12">
|
|
601
|
+
<div className="max-w-2xl mx-auto text-center space-y-2.5 sm:space-y-3">
|
|
602
|
+
<h3 className="text-h2 font-bold tracking-tight">
|
|
603
|
+
{tab.title}
|
|
604
|
+
</h3>
|
|
605
|
+
<p className="text-small sm:text-body text-muted-foreground leading-relaxed">
|
|
606
|
+
{tab.description}
|
|
607
|
+
</p>
|
|
608
|
+
</div>
|
|
609
|
+
|
|
610
|
+
<div className="relative">
|
|
611
|
+
<div
|
|
612
|
+
className="absolute -inset-4 sm:-inset-6 pointer-events-none"
|
|
613
|
+
style={{
|
|
614
|
+
background: `radial-gradient(ellipse at center, var(--color-primary) 0%, transparent 65%)`,
|
|
615
|
+
opacity: 0.12,
|
|
616
|
+
filter: 'blur(40px)',
|
|
617
|
+
}}
|
|
618
|
+
aria-hidden="true"
|
|
619
|
+
/>
|
|
620
|
+
<div className="relative">
|
|
621
|
+
<AppMockup view={tab.mockupView} />
|
|
622
|
+
</div>
|
|
623
|
+
</div>
|
|
624
|
+
|
|
625
|
+
{tab.features?.length > 0 && (
|
|
626
|
+
<ul className="grid gap-3 sm:grid-cols-2 max-w-2xl mx-auto pt-2">
|
|
627
|
+
{tab.features.map((f, j) => (
|
|
628
|
+
<li
|
|
629
|
+
key={j}
|
|
630
|
+
className="flex items-start gap-2.5 text-small text-muted-foreground"
|
|
631
|
+
>
|
|
632
|
+
<span className="mt-0.5 text-primary shrink-0">
|
|
633
|
+
{Icons.check}
|
|
634
|
+
</span>
|
|
635
|
+
<span>{f}</span>
|
|
636
|
+
</li>
|
|
637
|
+
))}
|
|
638
|
+
</ul>
|
|
639
|
+
)}
|
|
640
|
+
</div>
|
|
641
|
+
</TabsContent>
|
|
642
|
+
))}
|
|
643
|
+
</Tabs>
|
|
644
|
+
</div>
|
|
645
|
+
</section>
|
|
646
|
+
)}
|
|
647
|
+
|
|
648
|
+
{/* ============================================================
|
|
649
|
+
TESTIMONIALS
|
|
650
|
+
============================================================ */}
|
|
651
|
+
{testimonials.length > 0 && (
|
|
652
|
+
<section className="imansi-container py-20 sm:py-24 lg:py-28">
|
|
653
|
+
<div className="max-w-2xl mx-auto text-center mb-14 sm:mb-16 lg:mb-20 space-y-3 sm:space-y-4">
|
|
654
|
+
{testimonialsLabel && (
|
|
655
|
+
<p className="text-caption uppercase tracking-[0.2em] text-primary font-semibold">
|
|
656
|
+
{testimonialsLabel}
|
|
657
|
+
</p>
|
|
658
|
+
)}
|
|
659
|
+
<h2 className="text-h1 sm:text-display font-bold tracking-[-0.025em] leading-[1.1]">
|
|
660
|
+
{testimonialsTitle}
|
|
661
|
+
</h2>
|
|
662
|
+
<p className="text-small sm:text-body text-muted-foreground leading-relaxed">
|
|
663
|
+
{testimonialsSubtitle}
|
|
664
|
+
</p>
|
|
665
|
+
</div>
|
|
666
|
+
|
|
667
|
+
<div className="grid gap-5 sm:gap-6 md:grid-cols-3 max-w-5xl mx-auto">
|
|
668
|
+
{testimonials.map((t, i) => (
|
|
669
|
+
<div
|
|
670
|
+
key={i}
|
|
671
|
+
className="rounded-xl border border-border bg-card p-5 sm:p-6 space-y-5 sm:space-y-6 flex flex-col text-center"
|
|
672
|
+
>
|
|
673
|
+
<p className="text-small leading-relaxed text-foreground flex-1">
|
|
674
|
+
"{t.quote}"
|
|
675
|
+
</p>
|
|
676
|
+
<div className="flex items-center justify-center gap-3 pt-4 sm:pt-5 border-t border-border">
|
|
677
|
+
<Avatar size="sm">
|
|
678
|
+
<AvatarFallback>
|
|
679
|
+
{t.author?.charAt(0)}
|
|
680
|
+
</AvatarFallback>
|
|
681
|
+
</Avatar>
|
|
682
|
+
<div className="text-left">
|
|
683
|
+
<p className="text-caption font-semibold">
|
|
684
|
+
{t.author}
|
|
685
|
+
</p>
|
|
686
|
+
<p className="text-[10px] text-muted-foreground">
|
|
687
|
+
{t.role}
|
|
688
|
+
</p>
|
|
689
|
+
</div>
|
|
690
|
+
</div>
|
|
691
|
+
</div>
|
|
692
|
+
))}
|
|
693
|
+
</div>
|
|
694
|
+
</section>
|
|
695
|
+
)}
|
|
696
|
+
|
|
697
|
+
{/* ============================================================
|
|
698
|
+
CTA FINAL
|
|
699
|
+
============================================================ */}
|
|
700
|
+
<section className="imansi-container pb-20 sm:pb-24 lg:pb-28">
|
|
701
|
+
<div className="relative overflow-hidden rounded-2xl border border-border bg-card p-8 sm:p-14 lg:p-24">
|
|
702
|
+
<div
|
|
703
|
+
className="absolute inset-0 pointer-events-none"
|
|
704
|
+
style={{
|
|
705
|
+
backgroundImage: `
|
|
706
|
+
radial-gradient(circle at 15% 15%, var(--color-primary) 0%, transparent 40%),
|
|
707
|
+
radial-gradient(circle at 85% 85%, var(--color-accent) 0%, transparent 40%)
|
|
708
|
+
`,
|
|
709
|
+
opacity: 0.14,
|
|
710
|
+
}}
|
|
711
|
+
aria-hidden="true"
|
|
712
|
+
/>
|
|
713
|
+
<div
|
|
714
|
+
className="absolute inset-0 pointer-events-none"
|
|
715
|
+
style={{
|
|
716
|
+
backgroundImage: `
|
|
717
|
+
linear-gradient(currentColor 1px, transparent 1px),
|
|
718
|
+
linear-gradient(90deg, currentColor 1px, transparent 1px)
|
|
719
|
+
`,
|
|
720
|
+
backgroundSize: '40px 40px',
|
|
721
|
+
opacity: 0.03,
|
|
722
|
+
}}
|
|
723
|
+
aria-hidden="true"
|
|
724
|
+
/>
|
|
725
|
+
|
|
726
|
+
<div className="relative max-w-2xl mx-auto text-center space-y-5 sm:space-y-6">
|
|
727
|
+
<h2 className="text-h1 sm:text-display font-bold tracking-[-0.025em] leading-[1.1]">
|
|
728
|
+
{ctaTitle}
|
|
729
|
+
</h2>
|
|
730
|
+
<p className="text-small sm:text-body text-muted-foreground leading-relaxed max-w-xl mx-auto">
|
|
731
|
+
{ctaSubtitle}
|
|
732
|
+
</p>
|
|
733
|
+
<div className="flex flex-col sm:flex-row flex-wrap justify-center gap-3 pt-3 sm:pt-4">
|
|
734
|
+
{ctaPrimary && (
|
|
735
|
+
<Link to={ctaPrimary.href} className="w-full sm:w-auto">
|
|
736
|
+
<Button size="lg" className="gap-2 w-full sm:w-auto">
|
|
737
|
+
{ctaPrimary.label}
|
|
738
|
+
{Icons.arrow}
|
|
739
|
+
</Button>
|
|
740
|
+
</Link>
|
|
741
|
+
)}
|
|
742
|
+
{ctaSecondary && (
|
|
743
|
+
<Link to={ctaSecondary.href} className="w-full sm:w-auto">
|
|
744
|
+
<Button size="lg" variant="outline" className="w-full sm:w-auto">
|
|
745
|
+
{ctaSecondary.label}
|
|
746
|
+
</Button>
|
|
747
|
+
</Link>
|
|
748
|
+
)}
|
|
749
|
+
</div>
|
|
750
|
+
</div>
|
|
751
|
+
</div>
|
|
752
|
+
</section>
|
|
753
|
+
</MarketingLayout>
|
|
754
|
+
);
|
|
755
|
+
}
|