feat(v3): rediseño con cloud dividers, animaciones scroll reveal, galería stock, página /servicios con 9 planes, 404 gracioso con robot, planos dark mode con todos los planes

- 9 planes de hosting con tabla completa de Mauri (USD/año)
- /servicios muestra los 3 servicios a medida + 9 planes categorizados
- /planes: 3 destacados + grilla con los 6 restantes
- 404 con robot SVG animado tomando mate (pronet.uy + peugeot style)
- Cloud dividers SVG entre secciones de la home
- Scroll reveal animations (.reveal + .stagger con IntersectionObserver)
- Galería de imágenes stock (datacenters, cables, código) en AVIF + WebP + JPG
- Sección de testimonios con placeholder honesto (Working on it)
- Back-to-top arreglado: ahora usa color naranja + icono blanco, visible en ambos temas
- WorldMap con animación SVG de 4 países
- Toggle light/dark funcional con anti-flash script
- Footer siempre dark con Maldonado, Uruguay
- i18n es/en con toggle y contenido bilingüe

Tech: Astro 5 + Tailwind v4 + Keystatic + Gitea + lftp
This commit is contained in:
Mauri
2026-06-08 23:27:55 -03:00
parent 393f6b0dc3
commit 79fc763025
38 changed files with 1069 additions and 113 deletions
+54
View File
@@ -0,0 +1,54 @@
---
interface Props {
lang: 'es' | 'en';
}
const { lang } = Astro.props;
const items = [
{ initials: 'M', name: 'Mauricio', role: lang === 'en' ? 'Founder & SysAdmin' : 'Fundador y SysAdmin', quote: lang === 'en' ? 'We answer the phone. That is the whole point.' : 'Atendemos el teléfono. Ese es todo el punto.' },
{ initials: '?', name: lang === 'en' ? 'Your client here' : 'Tu cliente acá', role: lang === 'en' ? 'Reserved spot' : 'Espacio reservado', quote: lang === 'en' ? 'This space is for your first testimonial.' : 'Este espacio es para tu primer testimonio.' },
{ initials: '?', name: lang === 'en' ? 'Coming soon' : 'Próximamente', role: lang === 'en' ? 'Reserved spot' : 'Espacio reservado', quote: lang === 'en' ? 'Tell us when a client is happy and we add it here.' : 'Contanos cuando un cliente esté conforme y lo sumamos acá.' },
];
---
<section class="py-20 md:py-28" style="background: var(--hds-bg-soft);">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-12 reveal">
<span class="inline-block px-3 py-1 rounded-full text-xs font-semibold tracking-[0.2em] uppercase" style="background: var(--hds-bg); color: var(--color-hds-naranja);">
{lang === 'en' ? 'Testimonials' : 'Testimonios'}
</span>
<h2 class="font-display mt-4" style="font-size: clamp(2rem, 5vw, 3.5rem); color: var(--hds-fg);">
{lang === 'en' ? 'What clients say' : 'Lo que dicen los clientes'}.
</h2>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 stagger">
{items.map((item, idx) => (
<article class="rounded-2xl p-8 relative" style={`background: var(--hds-card); border: 1px solid var(--hds-line); ${idx === 0 ? 'border-color: var(--color-hds-naranja); border-width: 1.5px;' : ''}`}>
<svg class="absolute top-6 right-6 opacity-20" width="40" height="40" viewBox="0 0 24 24" fill="var(--color-hds-naranja)" aria-hidden="true">
<path d="M3 21c3 0 7-1 7-8V5H3v6h3c0 4-1 5-3 5v5zm10 0c3 0 7-1 7-8V5h-7v6h3c0 4-1 5-3 5v5z"/>
</svg>
<div class="flex items-center gap-3 mb-5">
<div class="w-12 h-12 rounded-full flex items-center justify-center font-display text-xl" style={`background: ${idx === 0 ? 'var(--color-hds-naranja)' : 'var(--hds-bg-soft)'}; color: ${idx === 0 ? 'white' : 'var(--color-hds-naranja)'};`}>
{item.initials}
</div>
<div>
<div class="font-semibold text-sm" style="color: var(--hds-fg);">{item.name}</div>
<div class="text-xs" style="color: var(--hds-fg-muted);">{item.role}</div>
</div>
</div>
<blockquote class="text-[15px] leading-relaxed" style="color: var(--hds-fg-soft);">
"{item.quote}"
</blockquote>
</article>
))}
</div>
<div class="mt-10 text-center">
<p class="text-sm italic" style="color: var(--hds-fg-muted);">
{lang === 'en' ? 'Customer testimonials are published with explicit consent. Working on it.' : 'Los testimonios se publican con consentimiento explícito del cliente. Working on it.'}
</p>
</div>
</div>
</section>