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
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 25 KiB |
@@ -4,8 +4,8 @@ const label = 'Volver arriba';
|
||||
<button
|
||||
type="button"
|
||||
id="back-to-top"
|
||||
class="fixed bottom-24 right-6 z-30 inline-flex items-center justify-center w-11 h-11 rounded-full text-white shadow-md opacity-0 invisible transition-all"
|
||||
style="background: var(--hds-fg);"
|
||||
class="fixed bottom-24 right-6 z-30 inline-flex items-center justify-center w-11 h-11 rounded-full shadow-md opacity-0 invisible transition-all"
|
||||
style="background: var(--color-hds-naranja); color: #FAF6EE;"
|
||||
aria-label={label}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
|
||||
@@ -18,7 +18,7 @@ const items = [
|
||||
---
|
||||
<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="max-w-3xl mb-14 md:mb-20">
|
||||
<div class="max-w-3xl mb-14 md:mb-20 reveal">
|
||||
<SectionEyebrow text={t('diff.eyebrow')} />
|
||||
<h2 class="font-display leading-tight mt-5" style="font-size: clamp(2rem, 5vw, 3.5rem); color: var(--hds-fg);">
|
||||
{t('diff.title')}
|
||||
@@ -28,7 +28,7 @@ const items = [
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 stagger">
|
||||
{items.map((item) => (
|
||||
<article class="service-card">
|
||||
<div class="icon-circle mb-6">
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
import SectionEyebrow from './SectionEyebrow.astro';
|
||||
import FAQAccordion from './FAQAccordion.astro';
|
||||
|
||||
interface Props {
|
||||
lang: 'es' | 'en';
|
||||
}
|
||||
const { lang } = Astro.props;
|
||||
|
||||
const faqs = lang === 'en' ? [
|
||||
{ q: 'What makes you different from a regular hosting provider?', a: 'A real person answers your call. We are a small team of senior SysAdmins/SREs based in Maldonado, Uruguay. No tickets, no chatbots, no offshore call centers.' },
|
||||
{ q: 'Where is my data physically located?', a: 'We operate infrastructure across four countries: Canada, the United States, Germany and Uruguay. We can work with you to choose the region that best fits your compliance and latency needs.' },
|
||||
{ q: 'Do you offer 24/7 support?', a: 'No. We are available during business hours (Monday to Friday, 9 to 18 UYT). Outside those hours we monitor critical alerts, but human support is provided during business hours.' },
|
||||
{ q: 'Do you manage WordPress sites?', a: 'Yes. We are part of the official WordPress ecosystem. We offer hosting, maintenance, security patches, performance tuning and full development.' },
|
||||
{ q: 'Can you build my company website from scratch?', a: 'Yes. We design and develop institutional sites and e-commerce stores (WooCommerce) with focus on performance, accessibility (WCAG AA) and SEO.' },
|
||||
{ q: 'What if I need a plan that is not listed?', a: 'We have 9 plans total (Básico, WordPress, Institucional, Empresarial, E-commerce, Avanzado, Avanzado HD, Corporativo, Corporativo HD). Chat with us on WhatsApp and we will tailor a plan for you.' },
|
||||
{ q: 'Do you provide cloud servers and virtualization?', a: 'Yes. We install and administer Linux servers, KVM virtualization, private clouds (Nextcloud-style), high availability and 24/7 monitoring with Prometheus/Grafana.' },
|
||||
] : [
|
||||
{ q: '¿Qué los diferencia de un proveedor de hosting tradicional?', a: 'Una persona real te atiende. Somos un equipo chico de SysAdmins/SREs senior radicados en Maldonado, Uruguay. Sin tickets, sin chatbots, sin call centers offshore.' },
|
||||
{ q: '¿Dónde están físicamente mis datos?', a: 'Operamos infraestructura en cuatro países: Canadá, Estados Unidos, Alemania y Uruguay. Podemos trabajar con vos para elegir la región que mejor se ajuste a tus necesidades de compliance y latencia.' },
|
||||
{ q: '¿Tienen soporte 24/7?', a: 'No. Estamos disponibles en horario comercial (lunes a viernes, 9 a 18 hs UYT). Fuera de ese horario monitoreamos alertas críticas, pero el soporte humano se brinda en horario comercial.' },
|
||||
{ q: '¿Manejan sitios WordPress?', a: 'Sí. Somos parte del ecosistema oficial de WordPress. Ofrecemos hosting, mantenimiento, parches de seguridad, optimización de performance y desarrollo completo.' },
|
||||
{ q: '¿Pueden armar el sitio web de mi empresa desde cero?', a: 'Sí. Diseñamos y desarrollamos sitios institucionales y e-commerce (WooCommerce) con foco en performance, accesibilidad (WCAG AA) y SEO.' },
|
||||
{ q: '¿Qué pasa si necesito un plan que no está en la lista?', a: 'Tenemos 9 planes (Básico, WordPress, Institucional, Empresarial, E-commerce, Avanzado, Avanzado HD, Corporativo, Corporativo HD). Charlá con nosotros por WhatsApp y te armamos un plan a medida.' },
|
||||
{ q: '¿Brindan servidores cloud y virtualización?', a: 'Sí. Instalamos y administramos servidores Linux, virtualización KVM, nubes privadas (estilo Nextcloud), alta disponibilidad y monitoreo con Prometheus/Grafana.' },
|
||||
];
|
||||
---
|
||||
<section class="py-20 md:py-28" style="background: var(--hds-bg);">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-12">
|
||||
<div class="lg:col-span-4 reveal">
|
||||
<SectionEyebrow text={lang === 'en' ? 'Common questions' : 'Preguntas comunes'} />
|
||||
<h2 class="font-display mt-5" style="font-size: clamp(2rem, 4.5vw, 3rem); color: var(--hds-fg);">
|
||||
{lang === 'en'
|
||||
? <>Everything you wanted to <span style="color: var(--color-hds-naranja); font-style: italic;">ask</span>.</>
|
||||
: <>Todo lo que querías <span style="color: var(--color-hds-naranja); font-style: italic;">preguntar</span>.</>}
|
||||
</h2>
|
||||
<p class="text-base mt-5" style="color: var(--hds-fg-soft);">
|
||||
{lang === 'en'
|
||||
? "Did not find your answer? Write to us and we will help."
|
||||
: '¿No encontraste tu respuesta? Escribinos y te ayudamos.'}
|
||||
</p>
|
||||
<a
|
||||
href="https://wa.me/59899812487"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn-primary text-sm mt-6"
|
||||
>
|
||||
{lang === 'en' ? 'Chat on WhatsApp' : 'Hablar por WhatsApp'}
|
||||
</a>
|
||||
</div>
|
||||
<div class="lg:col-span-8 reveal">
|
||||
<div class="rounded-2xl p-6 md:p-8" style="background: var(--hds-card); border: 1px solid var(--hds-line);">
|
||||
<FAQAccordion items={faqs} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
interface Props {
|
||||
lang: 'es' | 'en';
|
||||
}
|
||||
const { lang } = Astro.props;
|
||||
|
||||
const items = [
|
||||
{ src: 'server-room', alt: lang === 'en' ? 'Server room with rack infrastructure' : 'Sala de servidores con racks de infraestructura', title: lang === 'en' ? 'Infrastructure' : 'Infraestructura', col: 'md:col-span-2', row: 'md:row-span-2' },
|
||||
{ src: 'cables', alt: lang === 'en' ? 'Network cables' : 'Cables de red', title: lang === 'en' ? 'Network' : 'Red', col: '', row: '' },
|
||||
{ src: 'code', alt: lang === 'en' ? 'Code on screen' : 'Código en pantalla', title: 'Dev', col: '', row: '' },
|
||||
{ src: 'team', alt: lang === 'en' ? 'Team collaboration' : 'Colaboración del equipo', title: lang === 'en' ? 'Team' : 'Equipo', col: 'md:col-span-2', row: '' },
|
||||
{ src: 'workspace', alt: lang === 'en' ? 'Workspace' : 'Espacio de trabajo', title: lang === 'en' ? 'Workspace' : 'Workspace', col: '', row: '' },
|
||||
{ src: 'racks', alt: lang === 'en' ? 'Server racks' : 'Racks de servidores', title: lang === 'en' ? 'Datacenter' : 'Datacenter', col: '', row: '' },
|
||||
];
|
||||
---
|
||||
<section class="py-20 md:py-28" style="background: var(--hds-bg);">
|
||||
<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-soft); color: var(--color-hds-naranja);">
|
||||
{lang === 'en' ? 'Our work' : 'Nuestro trabajo'}
|
||||
</span>
|
||||
<h2 class="font-display mt-4" style="font-size: clamp(2rem, 5vw, 3.5rem); color: var(--hds-fg);">
|
||||
{lang === 'en' ? 'Behind the scenes' : 'Detrás de escena'}.
|
||||
</h2>
|
||||
<p class="mt-4 max-w-2xl mx-auto" style="color: var(--hds-fg-soft);">
|
||||
{lang === 'en'
|
||||
? 'Datacenters, infrastructure, team and the work that powers your projects.'
|
||||
: 'Datacenters, infraestructura, equipo y el trabajo que sostiene tus proyectos.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 auto-rows-[180px] gap-3 md:gap-4 stagger">
|
||||
{items.map((item, idx) => (
|
||||
<figure class={`relative overflow-hidden rounded-xl group ${item.col} ${item.row}`}>
|
||||
<picture>
|
||||
<source type="image/avif" srcset={`/images/gallery/${item.src}.avif`} />
|
||||
<source type="image/webp" srcset={`/images/gallery/${item.src}.webp`} />
|
||||
<img
|
||||
src={`/images/gallery/${item.src}.jpg`}
|
||||
alt={item.alt}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
|
||||
style={item.row ? '' : 'aspect-ratio: 4/3;'}
|
||||
/>
|
||||
</picture>
|
||||
<figcaption class="absolute inset-x-0 bottom-0 p-4 bg-gradient-to-t from-black/80 to-transparent text-white opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<span class="text-xs font-semibold tracking-[0.2em] uppercase" style="color: var(--color-hds-naranja);">
|
||||
{item.title}
|
||||
</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -8,10 +8,36 @@ interface Props {
|
||||
|
||||
const { t, whatsappUrl } = Astro.props;
|
||||
---
|
||||
<section class="relative overflow-hidden pt-24 pb-32 md:pt-32 md:pb-40">
|
||||
<section class="relative overflow-hidden pt-20 pb-28 md:pt-32 md:pb-40">
|
||||
<div class="absolute inset-0 particle-grid" aria-hidden="true"></div>
|
||||
<div class="absolute inset-0" style="background: linear-gradient(180deg, transparent 0%, var(--hds-bg) 100%);" aria-hidden="true"></div>
|
||||
|
||||
<!-- Cuadraditos decorativos (estilo grilla del logo) -->
|
||||
<div class="absolute top-32 right-8 md:right-16 opacity-50" aria-hidden="true">
|
||||
<svg width="120" height="120" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0" y="0" width="12" height="12" fill="#EE7623" rx="1"/>
|
||||
<rect x="14" y="0" width="12" height="12" fill="#FA9F5C" rx="1"/>
|
||||
<rect x="28" y="0" width="12" height="12" fill="#FFCBA1" rx="1"/>
|
||||
<rect x="0" y="14" width="12" height="12" fill="#FA9F5C" rx="1"/>
|
||||
<rect x="14" y="14" width="12" height="12" fill="#EE7623" rx="1"/>
|
||||
<rect x="28" y="14" width="12" height="12" fill="#FA9F5C" rx="1"/>
|
||||
<rect x="0" y="28" width="12" height="12" fill="#FFCBA1" rx="1"/>
|
||||
<rect x="14" y="28" width="12" height="12" fill="#FA9F5C" rx="1"/>
|
||||
<rect x="28" y="28" width="12" height="12" fill="#EE7623" rx="1"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="absolute bottom-12 left-8 md:left-16 opacity-40" aria-hidden="true">
|
||||
<svg width="80" height="80" viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0" y="0" width="10" height="10" fill="#FA9F5C" rx="1"/>
|
||||
<rect x="12" y="0" width="10" height="10" fill="#EE7623" rx="1"/>
|
||||
<rect x="0" y="12" width="10" height="10" fill="#FFCBA1" rx="1"/>
|
||||
<rect x="12" y="12" width="10" height="10" fill="#FA9F5C" rx="1"/>
|
||||
<rect x="0" y="24" width="10" height="10" fill="#EE7623" rx="1"/>
|
||||
<rect x="12" y="24" width="10" height="10" fill="#FFCBA1" rx="1"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="max-w-4xl">
|
||||
<div class="eyebrow mb-7">
|
||||
@@ -24,15 +50,15 @@ const { t, whatsappUrl } = Astro.props;
|
||||
</h1>
|
||||
|
||||
<p class="text-lg md:text-xl leading-relaxed mb-10 max-w-2xl" style="color: var(--hds-fg-soft);">
|
||||
{t('hero.body')}
|
||||
Soporte humano senior en Maldonado, Uruguay. Del otro lado hay una persona que conoce tu proyecto, no un ticket automático. La misma tecnología que los gigantes, con la cercanía que nos diferencia.
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-4 mb-14">
|
||||
<a href="#planes" class="btn-primary">
|
||||
{t('hero.ctaPrimary')}
|
||||
<a href="/planes/" class="btn-primary">
|
||||
Ver planes
|
||||
</a>
|
||||
<a href={whatsappUrl} target="_blank" rel="noopener noreferrer" class="btn-secondary">
|
||||
{t('hero.ctaSecondary')}
|
||||
Hablar por WhatsApp
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -47,7 +73,7 @@ const { t, whatsappUrl } = Astro.props;
|
||||
</span>
|
||||
<span class="flex items-center gap-2">
|
||||
<span style="color: var(--color-hds-naranja);">●</span>
|
||||
{t('diff.support.title')}
|
||||
Soporte humano
|
||||
</span>
|
||||
<span class="flex items-center gap-2">
|
||||
<span style="color: var(--color-hds-naranja);">●</span>
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
import { monthlyEquivalent, type Plan } from '@/lib/pricing';
|
||||
|
||||
interface Props {
|
||||
plans: Plan[];
|
||||
whatsappUrl: string;
|
||||
}
|
||||
|
||||
const { plans, whatsappUrl } = Astro.props;
|
||||
|
||||
const categoryLabel: Record<Plan['category'], string> = {
|
||||
entry: 'Entrada',
|
||||
mid: 'Profesional',
|
||||
business: 'Business',
|
||||
premium: 'Premium',
|
||||
};
|
||||
|
||||
const categoryColor: Record<Plan['category'], string> = {
|
||||
entry: 'var(--color-hds-durazno)',
|
||||
mid: 'var(--color-hds-naranja)',
|
||||
business: 'var(--color-hds-coral)',
|
||||
premium: 'var(--hds-tinta)',
|
||||
};
|
||||
---
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||
{plans.map((plan) => (
|
||||
<article class="group relative rounded-xl p-6 transition-all hover:-translate-y-0.5 hover:shadow-md" style="background: var(--hds-card); border: 1px solid var(--hds-line);">
|
||||
<div class="absolute top-0 left-0 right-0 h-1 rounded-t-xl" style={`background: ${categoryColor[plan.category]};`}></div>
|
||||
|
||||
<div class="flex items-start justify-between gap-3 mb-4">
|
||||
<div>
|
||||
<div class="text-[10px] font-bold tracking-[0.2em] uppercase mb-1" style="color: var(--hds-fg-muted);">
|
||||
{categoryLabel[plan.category]}
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold" style="color: var(--hds-fg);">{plan.name}</h3>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="font-display leading-none" style="font-size: 2.5rem; color: var(--color-hds-naranja); font-weight: 500; letter-spacing: -0.02em;">${plan.priceUsdYear}</div>
|
||||
<div class="text-[10px] font-semibold tracking-wider uppercase" style="color: var(--hds-fg-muted);">USD/año</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-xs mb-4" style="color: var(--hds-fg-muted);">
|
||||
~USD {monthlyEquivalent(plan.priceUsdYear)}/mes · {plan.technical} USD técnico · {plan.pricePerMb} USD/MB
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2 text-xs mb-4 pb-4 border-b" style="border-color: var(--hds-line);">
|
||||
<div>
|
||||
<div class="font-semibold" style="color: var(--hds-fg);">{plan.features[0]?.match(/\d+/)?.[0] || '—'} GB</div>
|
||||
<div style="color: var(--hds-fg-muted);">disco</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-semibold" style="color: var(--hds-fg);">{plan.features[2]?.includes('∞') ? '∞' : (plan.features[2]?.match(/\d+/)?.[0] || '—')}</div>
|
||||
<div style="color: var(--hds-fg-muted);">cuentas FTP</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-semibold" style="color: var(--hds-fg);">{plan.features[3]?.match(/\d+/)?.[0] || '—'}</div>
|
||||
<div style="color: var(--hds-fg-muted);">correos</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-semibold" style="color: var(--hds-fg);">{plan.features[5]?.match(/\d+/)?.[0] || '0'}</div>
|
||||
<div style="color: var(--hds-fg-muted);">bases de datos</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href={`https://wa.me/59899812487?text=${encodeURIComponent(`Hola, me interesa el plan ${plan.name} (USD ${plan.priceUsdYear}/año)`)}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn-ghost text-sm"
|
||||
>
|
||||
Ver detalles
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<line x1="5" y1="12" x2="19" y2="12"/>
|
||||
<polyline points="12 5 19 12 12 19"/>
|
||||
</svg>
|
||||
</a>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
import { monthlyEquivalent, type Plan } from '@/lib/pricing';
|
||||
|
||||
interface Props {
|
||||
plans: Plan[];
|
||||
whatsappUrl: string;
|
||||
}
|
||||
|
||||
const { plans, whatsappUrl } = Astro.props;
|
||||
---
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 lg:gap-8">
|
||||
{plans.map((plan) => (
|
||||
<div
|
||||
class="relative rounded-2xl p-8 transition-all flex flex-col h-full group hover:-translate-y-1"
|
||||
style={plan.recommended
|
||||
? `background: var(--hds-card); border: 2px solid var(--color-hds-naranja); box-shadow: 0 16px 48px rgba(238,118,35,0.18);`
|
||||
: `background: var(--hds-card); border: 1px solid var(--hds-line);`}
|
||||
>
|
||||
{plan.recommended && (
|
||||
<div class="absolute -top-3.5 left-1/2 -translate-x-1/2 text-[11px] font-bold tracking-[0.2em] uppercase px-4 py-1.5 rounded-full whitespace-nowrap" style="background: var(--color-hds-naranja); color: white; box-shadow: 0 4px 16px rgba(238,118,35,0.4);">
|
||||
⭐ Recomendado
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="mb-6">
|
||||
<div class="text-xs font-semibold tracking-[0.25em] uppercase mb-3" style={`color: ${plan.recommended ? 'var(--color-hds-naranja)' : 'var(--hds-fg-muted)'};`}>
|
||||
{plan.name}
|
||||
</div>
|
||||
<div class="flex items-baseline gap-1.5">
|
||||
<span class="text-2xl font-semibold" style="color: var(--hds-fg);">USD</span>
|
||||
<span class="font-display leading-none" style="font-size: 4.5rem; color: var(--hds-fg); font-weight: 500; letter-spacing: -0.03em;">{plan.priceUsdYear}</span>
|
||||
</div>
|
||||
<div class="text-sm mt-2" style="color: var(--hds-fg-muted);">
|
||||
por año · ~USD {monthlyEquivalent(plan.priceUsdYear)}/mes
|
||||
</div>
|
||||
<div class="text-xs mt-1.5 inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full" style="background: var(--hds-bg-soft); color: var(--hds-fg-muted);">
|
||||
<span>Técnico: USD {plan.technical}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t pt-6 mb-6 flex-grow" style="border-color: var(--hds-line);">
|
||||
<ul class="space-y-2.5 text-sm" style="color: var(--hds-fg-soft);">
|
||||
{plan.features.slice(0, 6).map((feature) => (
|
||||
<li class="flex items-start gap-2.5">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mt-0.5" style="color: var(--color-hds-naranja);" aria-hidden="true">
|
||||
<polyline points="20 6 9 17 4 12"/>
|
||||
</svg>
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
{plan.features.length > 6 && (
|
||||
<li class="text-xs italic" style="color: var(--hds-fg-muted);">
|
||||
+ {plan.features.length - 6} características más
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href={`https://wa.me/59899812487?text=${encodeURIComponent(`Hola, me interesa el plan ${plan.name} (USD ${plan.priceUsdYear}/año)`)}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class={plan.recommended ? 'btn-primary justify-center' : 'btn-secondary justify-center'}
|
||||
>
|
||||
Elegir {plan.name}
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -16,14 +16,14 @@ const steps = [
|
||||
---
|
||||
<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="max-w-3xl mb-14 md:mb-20">
|
||||
<div class="max-w-3xl mb-14 md:mb-20 reveal">
|
||||
<SectionEyebrow text={t('process.eyebrow')} />
|
||||
<h2 class="font-display leading-tight mt-5" style="font-size: clamp(2rem, 5vw, 3.5rem); color: var(--hds-fg);">
|
||||
{t('process.title')}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<ol class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-10 md:gap-8 relative">
|
||||
<ol class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-10 md:gap-8 relative stagger">
|
||||
<div class="hidden lg:block absolute top-12 left-[12.5%] right-[12.5%] h-px" style="background: linear-gradient(to right, transparent, var(--color-hds-naranja) 20%, var(--color-hds-naranja) 80%, transparent);" aria-hidden="true"></div>
|
||||
|
||||
{steps.map((step) => (
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
const script = `
|
||||
(function() {
|
||||
if (!('IntersectionObserver' in window)) {
|
||||
document.querySelectorAll('.reveal, .stagger').forEach(el => el.classList.add('is-visible'));
|
||||
return;
|
||||
}
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('is-visible');
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.12, rootMargin: '0px 0px -50px 0px' });
|
||||
document.querySelectorAll('.reveal, .stagger').forEach(el => observer.observe(el));
|
||||
})();
|
||||
`;
|
||||
---
|
||||
<script is:inline set:html={script}></script>
|
||||
@@ -91,7 +91,7 @@ const groups = [
|
||||
---
|
||||
<section id="servicios" class="py-20 md:py-28" style="background: var(--hds-bg);">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="max-w-3xl mb-14 md:mb-20">
|
||||
<div class="max-w-3xl mb-14 md:mb-20 reveal">
|
||||
<SectionEyebrow text={t('services.eyebrow')} />
|
||||
<h2 class="font-display leading-tight mt-5" style="font-size: clamp(2rem, 5vw, 3.5rem); color: var(--hds-fg);">
|
||||
{t('services.title')}
|
||||
|
||||
@@ -16,7 +16,7 @@ const stats = [
|
||||
---
|
||||
<section class="py-20 md:py-24" style="background: var(--hds-bg); border-top: 1px solid var(--hds-line); border-bottom: 1px solid var(--hds-line);">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-10 md:gap-8">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-10 md:gap-8 stagger">
|
||||
{stats.map((stat) => (
|
||||
<div class="text-center">
|
||||
<div class="font-display leading-none mb-3" style="font-size: clamp(3rem, 6vw, 4.5rem); color: var(--color-hds-naranja); font-weight: 500;">
|
||||
|
||||
@@ -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>
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* Gitea webhook → rebuild + redeploy
|
||||
*
|
||||
* Triggereado por push a branch main en mauri/hostingdelsur.net
|
||||
* Ejecuta: git pull, npm install, npm run build, rsync dist a public_html
|
||||
*
|
||||
* Logs: /home/hostingd/deploy.log
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$secret = getenv('DEPLOY_SECRET') ?: 'hds-deploy-2026';
|
||||
$signature = $_SERVER['HTTP_X_GITEA_SIGNATURE'] ?? '';
|
||||
$payload = file_get_contents('php://input');
|
||||
|
||||
$expected = hash_hmac('sha256', $payload, $secret);
|
||||
if (!hash_equals($expected, $signature)) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['ok' => false, 'error' => 'Invalid signature']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$body = json_decode($payload, true);
|
||||
$ref = $body['ref'] ?? '';
|
||||
$repoName = $body['repository']['name'] ?? '';
|
||||
|
||||
if ($ref !== 'refs/heads/main' || $repoName !== 'hostingdelsur.net') {
|
||||
http_response_code(200);
|
||||
echo json_encode(['ok' => true, 'message' => 'Ignored (not main or wrong repo)']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$logFile = '/home/hostingd/deploy.log';
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$logLine = "[{$timestamp}] Deploy started for {$repoName} @ {$ref}\n";
|
||||
|
||||
$projectDir = '/home/hostingd/hostingdelsur.net';
|
||||
$distDir = $projectDir . '/dist';
|
||||
$publicHtml = '/home/hostingd/public_html';
|
||||
|
||||
$commands = [
|
||||
"cd {$projectDir} && git pull origin main 2>&1",
|
||||
"cd {$projectDir} && npm install --production=false 2>&1",
|
||||
"cd {$projectDir} && npm run build 2>&1",
|
||||
"rsync -a --delete --exclude='mwp' --exclude='.well-known' --exclude='.smtp-credentials.json' --exclude='api' --exclude='.htaccess' {$distDir}/ {$publicHtml}/ 2>&1",
|
||||
];
|
||||
|
||||
foreach ($commands as $cmd) {
|
||||
$output = shell_exec($cmd . ' 2>&1');
|
||||
$logLine .= "$ {$cmd}\n{$output}\n";
|
||||
}
|
||||
|
||||
$logLine .= "[{$timestamp}] Deploy completed\n\n";
|
||||
file_put_contents($logFile, $logLine, FILE_APPEND);
|
||||
|
||||
http_response_code(200);
|
||||
echo json_encode(['ok' => true, 'message' => 'Deploy triggered', 'log' => basename($logFile)]);
|
||||
@@ -21,6 +21,7 @@ import Navbar from '@/components/Navbar.astro';
|
||||
import Footer from '@/components/Footer.astro';
|
||||
import WhatsappFab from '@/components/WhatsappFab.astro';
|
||||
import BackToTop from '@/components/BackToTop.astro';
|
||||
import ScrollReveal from '@/components/ScrollReveal.astro';
|
||||
|
||||
const orgJsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
@@ -120,6 +121,7 @@ const localBusinessJsonLd = {
|
||||
<slot />
|
||||
</main>
|
||||
<Footer t={t} lang={currentLang} />
|
||||
<ScrollReveal />
|
||||
<WhatsappFab lang={currentLang} />
|
||||
<BackToTop />
|
||||
</body>
|
||||
|
||||
@@ -2,9 +2,11 @@ export interface Plan {
|
||||
slug: string;
|
||||
name: string;
|
||||
priceUsdYear: number;
|
||||
technical: number;
|
||||
pricePerMb: number;
|
||||
category: 'entry' | 'mid' | 'business' | 'premium';
|
||||
recommended?: boolean;
|
||||
features: string[];
|
||||
featuresEn?: string[];
|
||||
}
|
||||
|
||||
export const PLANS: Plan[] = [
|
||||
@@ -12,57 +14,196 @@ export const PLANS: Plan[] = [
|
||||
slug: 'basico',
|
||||
name: 'Básico',
|
||||
priceUsdYear: 59,
|
||||
recommended: false,
|
||||
technical: 53,
|
||||
pricePerMb: 11.80,
|
||||
category: 'entry',
|
||||
features: [
|
||||
'5 GB espacio en disco',
|
||||
'7.500 MB transferencia',
|
||||
'1 cuenta FTP',
|
||||
'10 cuentas de correo',
|
||||
'50 mensajes por hora',
|
||||
'0 bases de datos',
|
||||
'SSL gratis (Let\'s Encrypt)',
|
||||
'Backups automáticos diarios',
|
||||
'cPanel incluido',
|
||||
'Soporte técnico real',
|
||||
'100% CPU · 512 MB RAM',
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'wordpress',
|
||||
name: 'WordPress',
|
||||
priceUsdYear: 99,
|
||||
technical: 89,
|
||||
pricePerMb: 19.80,
|
||||
category: 'entry',
|
||||
features: [
|
||||
'5 GB espacio en disco',
|
||||
'7.500 MB transferencia',
|
||||
'1 cuenta FTP',
|
||||
'10 cuentas de correo',
|
||||
'50 mensajes por hora',
|
||||
'0 bases de datos',
|
||||
'WordPress pre-instalado',
|
||||
'Backups automáticos diarios',
|
||||
'cPanel + WP-CLI',
|
||||
'200% CPU · 512 MB RAM',
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'institucional',
|
||||
name: 'Institucional',
|
||||
priceUsdYear: 129,
|
||||
technical: 116,
|
||||
pricePerMb: 8.60,
|
||||
category: 'mid',
|
||||
recommended: true,
|
||||
features: [
|
||||
'15 GB espacio en disco',
|
||||
'22.500 MB transferencia',
|
||||
'∞ cuentas FTP',
|
||||
'30 cuentas de correo',
|
||||
'1 base de datos MySQL',
|
||||
'150 mensajes por hora',
|
||||
'1 base de datos',
|
||||
'2 subdominios',
|
||||
'1 dominio parqueado',
|
||||
'250% CPU speed',
|
||||
'2 GB RAM',
|
||||
'Soporte prioritario',
|
||||
'SSL gratis',
|
||||
'250% CPU · 2 GB RAM',
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'empresarial',
|
||||
name: 'Empresarial',
|
||||
priceUsdYear: 169,
|
||||
technical: 152,
|
||||
pricePerMb: 8.45,
|
||||
category: 'mid',
|
||||
features: [
|
||||
'20 GB espacio en disco',
|
||||
'30.000 MB transferencia',
|
||||
'∞ cuentas FTP',
|
||||
'40 cuentas de correo',
|
||||
'250 mensajes por hora',
|
||||
'2 bases de datos',
|
||||
'3 subdominios',
|
||||
'2 dominios parkados',
|
||||
'SSL gratis',
|
||||
'250% CPU · 2 GB RAM',
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'e-commerce',
|
||||
name: 'E-commerce',
|
||||
priceUsdYear: 219,
|
||||
recommended: false,
|
||||
technical: 197,
|
||||
pricePerMb: 7.30,
|
||||
category: 'business',
|
||||
features: [
|
||||
'30 GB espacio en disco',
|
||||
'45.000 MB transferencia',
|
||||
'∞ cuentas FTP',
|
||||
'50 cuentas de correo',
|
||||
'3 bases de datos MySQL',
|
||||
'350 mensajes por hora',
|
||||
'3 bases de datos',
|
||||
'∞ subdominios',
|
||||
'3 dominios parkados',
|
||||
'300% CPU speed',
|
||||
'2.5 GB RAM',
|
||||
'Soporte prioritario',
|
||||
'Optimización para WooCommerce',
|
||||
'Optimización WooCommerce',
|
||||
'300% CPU · 2.5 GB RAM',
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'avanzado',
|
||||
name: 'Avanzado',
|
||||
priceUsdYear: 289,
|
||||
technical: 260,
|
||||
pricePerMb: 5.78,
|
||||
category: 'business',
|
||||
features: [
|
||||
'50 GB espacio en disco',
|
||||
'75.000 MB transferencia',
|
||||
'∞ cuentas FTP',
|
||||
'75 cuentas de correo',
|
||||
'500 mensajes por hora',
|
||||
'4 bases de datos',
|
||||
'∞ subdominios',
|
||||
'4 dominios parkados',
|
||||
'1 dominio agregado',
|
||||
'350% CPU · 3 GB RAM',
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'avanzado-hd',
|
||||
name: 'Avanzado HD',
|
||||
priceUsdYear: 349,
|
||||
technical: 314,
|
||||
pricePerMb: 5.37,
|
||||
category: 'premium',
|
||||
features: [
|
||||
'65 GB espacio en disco',
|
||||
'80.000 MB transferencia',
|
||||
'∞ cuentas FTP',
|
||||
'75 cuentas de correo',
|
||||
'500 mensajes por hora',
|
||||
'5 bases de datos',
|
||||
'∞ subdominios',
|
||||
'9 dominios parkados',
|
||||
'1 dominio agregado',
|
||||
'400% CPU · 3 GB RAM',
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'corporativo',
|
||||
name: 'Corporativo',
|
||||
priceUsdYear: 399,
|
||||
technical: 359,
|
||||
pricePerMb: 4.99,
|
||||
category: 'premium',
|
||||
features: [
|
||||
'80 GB espacio en disco',
|
||||
'100.000 MB transferencia',
|
||||
'∞ cuentas FTP',
|
||||
'100 cuentas de correo',
|
||||
'1.000 mensajes por hora',
|
||||
'10 bases de datos',
|
||||
'∞ subdominios',
|
||||
'9 dominios parkados',
|
||||
'2 dominios agregados',
|
||||
'400% CPU · 5 GB RAM',
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'corporativo-hd',
|
||||
name: 'Corporativo HD',
|
||||
priceUsdYear: 549,
|
||||
technical: 494,
|
||||
pricePerMb: 4.58,
|
||||
category: 'premium',
|
||||
features: [
|
||||
'120 GB espacio en disco',
|
||||
'125.000 MB transferencia',
|
||||
'∞ cuentas FTP',
|
||||
'120 cuentas de correo',
|
||||
'1.200 mensajes por hora',
|
||||
'10 bases de datos',
|
||||
'∞ subdominios',
|
||||
'12 dominios parkados',
|
||||
'3 dominios agregados',
|
||||
'450% CPU · 5 GB RAM',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const ADDITIONAL_PLANS = [
|
||||
'WordPress',
|
||||
'Empresarial',
|
||||
'Avanzado',
|
||||
'Avanzado HD',
|
||||
'Corporativo',
|
||||
'Corporativo HD',
|
||||
];
|
||||
export const FEATURED_PLAN_SLUGS = ['basico', 'institucional', 'e-commerce'] as const;
|
||||
|
||||
export function getFeaturedPlans() {
|
||||
return PLANS.filter((p) => FEATURED_PLAN_SLUGS.includes(p.slug as any));
|
||||
}
|
||||
|
||||
export function getOtherPlans() {
|
||||
return PLANS.filter((p) => !FEATURED_PLAN_SLUGS.includes(p.slug as any));
|
||||
}
|
||||
|
||||
export const ADDITIONAL_PLANS = PLANS.map((p) => p.name);
|
||||
|
||||
export function monthlyEquivalent(annual: number): string {
|
||||
return (annual / 12).toFixed(2);
|
||||
|
||||
@@ -1,23 +1,202 @@
|
||||
---
|
||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||
import CloudDivider from '@/components/CloudDivider.astro';
|
||||
---
|
||||
<BaseLayout
|
||||
title="404"
|
||||
description="Página no encontrada"
|
||||
title="404 — Ups, el server se fue a buscar yerba"
|
||||
description="La página que buscás no existe. Pero tenemos un robot tomando mate para hacerte compañía."
|
||||
>
|
||||
<section class="py-32 bg-hds-marfil">
|
||||
<div class="max-w-2xl mx-auto px-4 text-center">
|
||||
<p class="text-6xl font-display text-hds-naranja mb-4">404</p>
|
||||
<h1 class="text-3xl md:text-4xl text-hds-tinta mb-4">Página no encontrada</h1>
|
||||
<p class="text-hds-grafito mb-8">
|
||||
La página que buscás no existe o fue movida.
|
||||
</p>
|
||||
<a
|
||||
href="/"
|
||||
class="inline-flex items-center gap-2 bg-hds-naranja hover:bg-hds-naranja-hover text-white text-sm font-semibold px-5 py-2.5 rounded-md transition-colors"
|
||||
>
|
||||
Volver al inicio
|
||||
</a>
|
||||
<section class="relative overflow-hidden py-16 md:py-24 min-h-[80vh] flex items-center" style="background: var(--hds-bg-soft);">
|
||||
<div class="absolute inset-0 particle-grid opacity-40" aria-hidden="true"></div>
|
||||
|
||||
<div class="relative max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 w-full">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
||||
|
||||
<!-- Robot SVG -->
|
||||
<div class="order-2 lg:order-1" aria-hidden="true">
|
||||
<svg viewBox="0 0 480 480" xmlns="http://www.w3.org/2000/svg" class="w-full max-w-md mx-auto">
|
||||
<defs>
|
||||
<linearGradient id="bg-grad" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#FAF6EE" stop-opacity="0"/>
|
||||
<stop offset="100%" stop-color="#EE7623" stop-opacity="0.08"/>
|
||||
</linearGradient>
|
||||
<filter id="soft-shadow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur in="SourceAlpha" stdDeviation="8"/>
|
||||
<feOffset dx="0" dy="4" result="offsetblur"/>
|
||||
<feComponentTransfer><feFuncA type="linear" slope="0.15"/></feComponentTransfer>
|
||||
<feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Fondo circular -->
|
||||
<circle cx="240" cy="240" r="220" fill="url(#bg-grad)"/>
|
||||
|
||||
<!-- Patrón de cuadraditos naranja (estilo logo) alrededor -->
|
||||
<g opacity="0.35">
|
||||
<rect x="60" y="80" width="8" height="8" fill="#FA9F5C" rx="1"/>
|
||||
<rect x="68" y="80" width="8" height="8" fill="#FA9F5C" rx="1"/>
|
||||
<rect x="76" y="80" width="8" height="8" fill="#FA9F5C" rx="1"/>
|
||||
<rect x="404" y="80" width="8" height="8" fill="#FA9F5C" rx="1"/>
|
||||
<rect x="412" y="80" width="8" height="8" fill="#FA9F5C" rx="1"/>
|
||||
<rect x="68" y="88" width="8" height="8" fill="#FFCBA1" rx="1"/>
|
||||
<rect x="412" y="88" width="8" height="8" fill="#FFCBA1" rx="1"/>
|
||||
<rect x="60" y="380" width="8" height="8" fill="#FA9F5C" rx="1"/>
|
||||
<rect x="68" y="380" width="8" height="8" fill="#FA9F5C" rx="1"/>
|
||||
<rect x="404" y="380" width="8" height="8" fill="#FA9F5C" rx="1"/>
|
||||
<rect x="412" y="380" width="8" height="8" fill="#FA9F5C" rx="1"/>
|
||||
<rect x="68" y="388" width="8" height="8" fill="#FFCBA1" rx="1"/>
|
||||
<rect x="412" y="388" width="8" height="8" fill="#FFCBA1" rx="1"/>
|
||||
</g>
|
||||
|
||||
<!-- Sombra del robot -->
|
||||
<ellipse cx="240" cy="420" rx="100" ry="10" fill="#EE7623" opacity="0.12"/>
|
||||
|
||||
<!-- Cuerpo (pecho) -->
|
||||
<g filter="url(#soft-shadow)">
|
||||
<rect x="160" y="240" width="160" height="140" rx="20" fill="#E8E2D5" stroke="#EE7623" stroke-width="3"/>
|
||||
<!-- Detalle del pecho (LED) -->
|
||||
<circle cx="200" cy="280" r="6" fill="#EE7623">
|
||||
<animate attributeName="opacity" values="1;0.3;1" dur="1.8s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<circle cx="200" cy="280" r="6" fill="#EE7623" opacity="0.3"/>
|
||||
<rect x="220" y="276" width="80" height="8" rx="2" fill="#1A1612" opacity="0.15"/>
|
||||
<rect x="220" y="290" width="60" height="6" rx="2" fill="#1A1612" opacity="0.1"/>
|
||||
<!-- Botón de pánico -->
|
||||
<circle cx="280" cy="340" r="14" fill="#FAF6EE" stroke="#EE7623" stroke-width="2"/>
|
||||
<circle cx="280" cy="340" r="6" fill="#EE7623"/>
|
||||
</g>
|
||||
|
||||
<!-- Cabeza -->
|
||||
<g filter="url(#soft-shadow)">
|
||||
<rect x="170" y="100" width="140" height="140" rx="24" fill="#F5EFE3" stroke="#EE7623" stroke-width="3"/>
|
||||
</g>
|
||||
|
||||
<!-- Antena -->
|
||||
<line x1="240" y1="100" x2="240" y2="68" stroke="#EE7623" stroke-width="4" stroke-linecap="round"/>
|
||||
<circle cx="240" cy="60" r="8" fill="#EE7623">
|
||||
<animate attributeName="r" values="6;9;6" dur="2s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
|
||||
<!-- Ojos (cierra uno como diciendo "uy") -->
|
||||
<g>
|
||||
<!-- Ojo izquierdo: cerrado/apretado (guiño de "uy") -->
|
||||
<path d="M 200 165 Q 210 172 220 165" stroke="#1A1612" stroke-width="4" fill="none" stroke-linecap="round"/>
|
||||
<line x1="195" y1="158" x2="225" y2="155" stroke="#1A1612" stroke-width="2" stroke-linecap="round" opacity="0.5"/>
|
||||
<!-- Pestaña del ojo cerrado -->
|
||||
<path d="M 195 158 Q 210 168 225 158" stroke="#1A1612" stroke-width="3" fill="none" stroke-linecap="round"/>
|
||||
|
||||
<!-- Ojo derecho: abierto y mirando de reojo (culpable) -->
|
||||
<ellipse cx="260" cy="167" rx="10" ry="12" fill="#FAF6EE" stroke="#1A1612" stroke-width="2"/>
|
||||
<circle cx="263" cy="170" r="5" fill="#1A1612"/>
|
||||
<circle cx="264" cy="167" r="2" fill="#FAF6EE"/>
|
||||
</g>
|
||||
|
||||
<!-- Mejillas rosadas (apenas sonrojado) -->
|
||||
<ellipse cx="190" cy="195" rx="8" ry="4" fill="#FA9F5C" opacity="0.6"/>
|
||||
<ellipse cx="290" cy="195" rx="8" ry="4" fill="#FA9F5C" opacity="0.6"/>
|
||||
|
||||
<!-- Boca: "Oops" -->
|
||||
<ellipse cx="240" cy="210" rx="14" ry="10" fill="#1A1612"/>
|
||||
<ellipse cx="240" cy="212" rx="10" ry="6" fill="#FAF6EE"/>
|
||||
<line x1="240" y1="205" x2="240" y2="218" stroke="#1A1612" stroke-width="1.5" opacity="0.3"/>
|
||||
|
||||
<!-- Brazos -->
|
||||
<g filter="url(#soft-shadow)">
|
||||
<!-- Brazo izquierdo: en gesto "ups" (mano levantada, como diciendo "yo no fui") -->
|
||||
<rect x="120" y="180" width="24" height="80" rx="12" fill="#F5EFE3" stroke="#EE7623" stroke-width="3" transform="rotate(-25 132 220)"/>
|
||||
<!-- Mano izquierda abierta -->
|
||||
<circle cx="110" cy="170" r="18" fill="#F5EFE3" stroke="#EE7623" stroke-width="3"/>
|
||||
<!-- Dedos abiertos -->
|
||||
<line x1="92" y1="160" x2="86" y2="152" stroke="#EE7623" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="98" y1="155" x2="94" y2="145" stroke="#EE7623" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="106" y1="153" x2="104" y2="142" stroke="#EE7623" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="114" y1="156" x2="115" y2="146" stroke="#EE7623" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="120" y1="162" x2="124" y2="155" stroke="#EE7623" stroke-width="3" stroke-linecap="round"/>
|
||||
</g>
|
||||
|
||||
<!-- Brazo derecho: sosteniendo el mate -->
|
||||
<g filter="url(#soft-shadow)">
|
||||
<rect x="336" y="220" width="24" height="80" rx="12" fill="#F5EFE3" stroke="#EE7623" stroke-width="3" transform="rotate(30 348 260)"/>
|
||||
<!-- Mano -->
|
||||
<circle cx="358" cy="290" r="16" fill="#F5EFE3" stroke="#EE7623" stroke-width="3"/>
|
||||
</g>
|
||||
|
||||
<!-- MATE (en la mano derecha) -->
|
||||
<g transform="translate(330 280)">
|
||||
<!-- Cuerpo del mate (calabaza) -->
|
||||
<ellipse cx="22" cy="28" rx="24" ry="20" fill="#1A1612"/>
|
||||
<!-- Brillo del mate -->
|
||||
<ellipse cx="14" cy="22" rx="6" ry="4" fill="#FAF6EE" opacity="0.2"/>
|
||||
<!-- Borde superior (donde va la yerba) -->
|
||||
<ellipse cx="22" cy="14" rx="20" ry="5" fill="#EE7623"/>
|
||||
<ellipse cx="22" cy="13" rx="18" ry="3" fill="#FFCBA1"/>
|
||||
<!-- Bombilla (pico) -->
|
||||
<rect x="35" y="6" width="8" height="14" rx="2" fill="#8A8580"/>
|
||||
<rect x="36" y="2" width="6" height="6" rx="1" fill="#C8C2B8"/>
|
||||
<!-- Capa de yerba encima -->
|
||||
<ellipse cx="22" cy="11" rx="14" ry="2" fill="#2E7D5B" opacity="0.8"/>
|
||||
</g>
|
||||
|
||||
<!-- Vapor del mate animado -->
|
||||
<g opacity="0.4">
|
||||
<path d="M 380 270 Q 384 262 380 254" stroke="#8A8580" stroke-width="2" fill="none" stroke-linecap="round">
|
||||
<animate attributeName="opacity" values="0;0.6;0" dur="2.5s" repeatCount="indefinite"/>
|
||||
<animate attributeName="transform" values="translateY(0); translateY(-10px); translateY(0)" dur="2.5s" repeatCount="indefinite"/>
|
||||
</path>
|
||||
<path d="M 392 270 Q 396 262 392 254" stroke="#8A8580" stroke-width="2" fill="none" stroke-linecap="round">
|
||||
<animate attributeName="opacity" values="0;0.6;0" dur="2.5s" begin="0.5s" repeatCount="indefinite"/>
|
||||
</path>
|
||||
<path d="M 386 274 Q 390 266 386 258" stroke="#8A8580" stroke-width="2" fill="none" stroke-linecap="round">
|
||||
<animate attributeName="opacity" values="0;0.6;0" dur="2.5s" begin="1s" repeatCount="indefinite"/>
|
||||
</path>
|
||||
</g>
|
||||
|
||||
<!-- Piernas -->
|
||||
<g filter="url(#soft-shadow)">
|
||||
<rect x="195" y="380" width="30" height="50" rx="10" fill="#F5EFE3" stroke="#EE7623" stroke-width="3"/>
|
||||
<rect x="255" y="380" width="30" height="50" rx="10" fill="#F5EFE3" stroke="#EE7623" stroke-width="3"/>
|
||||
<!-- Pies -->
|
||||
<ellipse cx="210" cy="432" rx="22" ry="8" fill="#EE7623"/>
|
||||
<ellipse cx="270" cy="432" rx="22" ry="8" fill="#EE7623"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Texto -->
|
||||
<div class="order-1 lg:order-2 text-center lg:text-left">
|
||||
<div class="inline-block">
|
||||
<span class="inline-flex items-center gap-3 text-xs font-semibold tracking-[0.25em] uppercase" style="color: var(--color-hds-naranja);">
|
||||
<span class="inline-block w-10 h-px" style="background: var(--color-hds-naranja);"></span>
|
||||
Error 404
|
||||
<span class="inline-block w-10 h-px" style="background: var(--color-hds-naranja);"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h1 class="font-display leading-tight mt-5 mb-6" style="font-size: clamp(2.25rem, 5vw, 3.75rem); color: var(--hds-fg);">
|
||||
Por acá no hay <span style="color: var(--color-hds-naranja); font-style: italic;">nada que ver</span>.<br/>
|
||||
<span class="text-2xl md:text-3xl" style="color: var(--hds-fg-soft);">El robot estaba tomando mate.</span>
|
||||
</h1>
|
||||
|
||||
<p class="text-lg mb-8 max-w-md mx-auto lg:mx-0" style="color: var(--hds-fg-soft);">
|
||||
La página que buscás no existe, fue movida o la URL está mal escrita. Mientras nuestro robot vuelve a su puesto, te convidamos un mate virtual.
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-3 justify-center lg:justify-start">
|
||||
<a href="/" class="btn-primary text-sm">
|
||||
Volver al inicio
|
||||
</a>
|
||||
<a href="https://wa.me/59899812487" target="_blank" rel="noopener noreferrer" class="btn-secondary text-sm">
|
||||
Pedinos ayuda por WhatsApp
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 pt-6 border-t flex flex-wrap items-center justify-center lg:justify-start gap-x-6 gap-y-2 text-sm" style="border-color: var(--hds-line); color: var(--hds-fg-muted);">
|
||||
<a href="/servicios/" class="hover:text-hds-naranja transition-colors">Ver servicios</a>
|
||||
<a href="/planes/" class="hover:text-hds-naranja transition-colors">Ver planes</a>
|
||||
<a href="/contacto/" class="hover:text-hds-naranja transition-colors">Contacto</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
@@ -3,8 +3,12 @@ import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||
import Hero from '@/components/Hero.astro';
|
||||
import StatsSection from '@/components/StatsSection.astro';
|
||||
import DifferenceCards from '@/components/DifferenceCards.astro';
|
||||
import CloudDivider from '@/components/CloudDivider.astro';
|
||||
import ServicesSection from '@/components/ServicesSection.astro';
|
||||
import ProcessSteps from '@/components/ProcessSteps.astro';
|
||||
import GalleryMosaic from '@/components/GalleryMosaic.astro';
|
||||
import Testimonials from '@/components/Testimonials.astro';
|
||||
import FAQAccordion from '@/components/FaqSection.astro';
|
||||
import CtaFinal from '@/components/CtaFinal.astro';
|
||||
import { getLangFromUrl, useTranslations } from '@/i18n/utils';
|
||||
|
||||
@@ -22,9 +26,25 @@ const description = lang === 'en'
|
||||
lang={lang}
|
||||
>
|
||||
<Hero t={t} whatsappUrl={whatsappUrl} />
|
||||
|
||||
<CloudDivider fillClass="fill-hds-bg-soft" />
|
||||
|
||||
<StatsSection t={t} />
|
||||
<DifferenceCards t={t} />
|
||||
|
||||
<CloudDivider fillClass="fill-hds-bg" flip={true} />
|
||||
|
||||
<ServicesSection t={t} lang={lang} />
|
||||
|
||||
<CloudDivider fillClass="fill-hds-bg-soft" />
|
||||
|
||||
<ProcessSteps t={t} />
|
||||
|
||||
<GalleryMosaic lang={lang} />
|
||||
|
||||
<Testimonials lang={lang} />
|
||||
|
||||
<FAQAccordion lang={lang} />
|
||||
|
||||
<CtaFinal t={t} whatsappUrl={whatsappUrl} />
|
||||
</BaseLayout>
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
---
|
||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||
import SectionEyebrow from '@/components/SectionEyebrow.astro';
|
||||
import PricingTiers from '@/components/PricingTiers.astro';
|
||||
import PricingTiersFeatured from '@/components/PricingTiersFeatured.astro';
|
||||
import PricingGrid from '@/components/PricingGrid.astro';
|
||||
import CloudDivider from '@/components/CloudDivider.astro';
|
||||
import CtaFinal from '@/components/CtaFinal.astro';
|
||||
import { PLANS, ADDITIONAL_PLANS, monthlyEquivalent } from '@/lib/pricing';
|
||||
import { PLANS, getFeaturedPlans, getOtherPlans, ADDITIONAL_PLANS } from '@/lib/pricing';
|
||||
import { getLangFromUrl, useTranslations } from '@/i18n/utils';
|
||||
|
||||
const lang = getLangFromUrl(Astro.url);
|
||||
const t = useTranslations(lang);
|
||||
const whatsappUrl = 'https://wa.me/59899812487';
|
||||
const base = lang === 'en' ? '/en' : '';
|
||||
|
||||
const featured = getFeaturedPlans();
|
||||
const other = getOtherPlans();
|
||||
---
|
||||
<BaseLayout
|
||||
title={lang === 'en' ? 'Hosting plans' : 'Planes de hosting'}
|
||||
description={lang === 'en'
|
||||
? 'Hosting plans in USD, billed annually. Basic, Institutional and E-commerce tiers with optional WordPress, Advanced and Corporate plans.'
|
||||
: 'Planes de hosting en USD, con cobro anual. Básico, Institucional y E-commerce, más planes opcionales WordPress, Avanzado y Corporativo.'}
|
||||
? 'Hosting plans in USD, billed annually. From basic to corporate HD, choose the plan that fits your project.'
|
||||
: 'Planes de hosting en USD, con cobro anual. Desde básico hasta corporativo HD, elegí el plan que se adapta a tu proyecto.'}
|
||||
lang={lang}
|
||||
>
|
||||
<section class="pt-20 pb-12 md:pt-28 md:pb-16" style="background: var(--hds-bg-soft);">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<section class="pt-20 pb-12 md:pt-28 md:pb-16 relative" style="background: var(--hds-bg-soft);">
|
||||
<div class="absolute inset-0 particle-grid opacity-60" aria-hidden="true"></div>
|
||||
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<SectionEyebrow text={lang === 'en' ? 'Hosting plans' : 'Planes de hosting'} />
|
||||
<h1 class="font-display leading-tight mt-5" style="font-size: clamp(2.5rem, 6vw, 4.5rem); color: var(--hds-fg);">
|
||||
Elegí el plan que se<br/>adapta a tu <span style="color: var(--color-hds-naranja); font-style: italic;">proyecto</span>.
|
||||
@@ -32,37 +38,51 @@ const base = lang === 'en' ? '/en' : '';
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 md:py-24" style="background: var(--hds-bg);">
|
||||
<CloudDivider fillClass="fill-hds-bg" />
|
||||
|
||||
<section class="py-16 md:py-20" style="background: var(--hds-bg);">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<PricingTiers
|
||||
plans={PLANS}
|
||||
whatsappUrl={whatsappUrl}
|
||||
/>
|
||||
<div class="text-center mb-12">
|
||||
<span class="inline-block px-3 py-1 rounded-full text-xs font-semibold tracking-[0.2em] uppercase" style="background: var(--hds-bg-soft); color: var(--color-hds-naranja);">
|
||||
{lang === 'en' ? 'Most popular' : 'Más elegidos'}
|
||||
</span>
|
||||
<h2 class="font-display mt-4" style="font-size: clamp(1.75rem, 4vw, 2.5rem); color: var(--hds-fg);">
|
||||
{lang === 'en' ? 'Featured plans' : 'Planes destacados'}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<PricingTiersFeatured plans={featured} whatsappUrl={whatsappUrl} />
|
||||
|
||||
<div class="mt-10 text-center text-sm space-y-1" style="color: var(--hds-fg-muted);">
|
||||
<p>⚠️ {lang === 'en'
|
||||
? 'Prices are in US Dollars (USD). Billed annually.'
|
||||
: 'Los precios están expresados en dólares estadounidenses (USD). El cobro es anual.'}</p>
|
||||
<p>{lang === 'en'
|
||||
? 'Features may vary based on project and server configuration.'
|
||||
: 'Las características pueden variar según el proyecto y la configuración del servidor.'}</p>
|
||||
<p>⚠️ {lang === 'en' ? 'Prices are in US Dollars (USD). Billed annually.' : 'Los precios están expresados en dólares estadounidenses (USD). El cobro es anual.'}</p>
|
||||
<p>{lang === 'en' ? 'Features may vary based on project and server configuration.' : 'Las características pueden variar según el proyecto y la configuración del servidor.'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20" style="background: var(--hds-bg-soft);">
|
||||
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<h2 class="font-display mb-4" style="font-size: clamp(1.75rem, 4vw, 2.5rem); color: var(--hds-fg);">
|
||||
{lang === 'en' ? 'Need a different plan?' : '¿Necesitás otro plan?'}
|
||||
</h2>
|
||||
<p class="text-lg mb-8" style="color: var(--hds-fg-soft);">
|
||||
{lang === 'en' ? 'We also have ' : 'También tenemos '}
|
||||
<strong>{ADDITIONAL_PLANS.join(', ')}</strong>.
|
||||
{lang === 'en' ? ' Tell us about your project and we will tailor a plan to you.' : ' Charlá con nosotros y te armamos un plan a medida.'}
|
||||
</p>
|
||||
<a href={whatsappUrl} target="_blank" rel="noopener noreferrer" class="btn-primary">
|
||||
{lang === 'en' ? 'Chat on WhatsApp' : 'Hablar por WhatsApp'}
|
||||
</a>
|
||||
<CloudDivider fillClass="fill-hds-bg-soft" flip={true} />
|
||||
|
||||
<section class="py-20 md:py-24" 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">
|
||||
<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(--hds-fg-soft);">
|
||||
{lang === 'en' ? 'Complete catalog' : 'Catálogo completo'}
|
||||
</span>
|
||||
<h2 class="font-display mt-4" style="font-size: clamp(1.75rem, 4vw, 2.5rem); color: var(--hds-fg);">
|
||||
{lang === 'en' ? 'All 9 plans' : 'Los 9 planes'}
|
||||
</h2>
|
||||
<p class="mt-3 max-w-2xl mx-auto" style="color: var(--hds-fg-soft);">
|
||||
{lang === 'en' ? 'From basic personal hosting to corporate HD with everything. Find the one that fits.' : 'Desde el hosting personal básico hasta corporativo HD con todo incluido. Encontrá el que mejor se ajusta.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<PricingGrid plans={other} whatsappUrl={whatsappUrl} />
|
||||
|
||||
<div class="mt-16 text-center">
|
||||
<p class="text-sm" style="color: var(--hds-fg-muted);">
|
||||
{lang === 'en' ? 'Need a custom plan?' : '¿Necesitás un plan a medida?'} <a href={whatsappUrl} target="_blank" rel="noopener noreferrer" class="font-semibold" style="color: var(--color-hds-naranja);">{lang === 'en' ? 'Chat with us' : 'Charla con nosotros'}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
---
|
||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||
import SectionEyebrow from '@/components/SectionEyebrow.astro';
|
||||
import ServiceCard from '@/components/ServiceCard.astro';
|
||||
import CloudDivider from '@/components/CloudDivider.astro';
|
||||
import CtaFinal from '@/components/CtaFinal.astro';
|
||||
import { PLANS } from '@/lib/pricing';
|
||||
import { getLangFromUrl, useTranslations } from '@/i18n/utils';
|
||||
import MailIcon from '@/components/icons/MailIcon.astro';
|
||||
import WordpressIcon from '@/components/icons/WordpressIcon.astro';
|
||||
@@ -16,29 +17,52 @@ const t = useTranslations(lang);
|
||||
const base = lang === 'en' ? '/en' : '';
|
||||
const whatsappUrl = 'https://wa.me/59899812487';
|
||||
|
||||
const groups = [
|
||||
const planIcons: Record<string, any> = {
|
||||
basico: MailIcon,
|
||||
wordpress: WordpressIcon,
|
||||
institucional: CodeIcon,
|
||||
empresarial: ServerIcon,
|
||||
'e-commerce': ShopIcon,
|
||||
avanzado: CloudIcon || ServerIcon,
|
||||
'avanzado-hd': ServerIcon,
|
||||
corporativo: ServerIcon,
|
||||
'corporativo-hd': ServerIcon,
|
||||
};
|
||||
|
||||
import CloudIcon from '@/components/icons/CloudIcon.astro';
|
||||
|
||||
const customServices = [
|
||||
{
|
||||
title: t('services.cat.hosting'),
|
||||
items: [
|
||||
{ slug: 'hosting-correo-web', title: lang === 'en' ? 'Hosting for email & web' : 'Hosting correo y web', desc: lang === 'en' ? 'Optimized hosting with cPanel for WordPress, PHP and MySQL.' : 'Alojamiento optimizado con cPanel para WordPress, PHP y MySQL.', Icon: MailIcon, border: 'var(--color-hds-naranja)' },
|
||||
{ slug: 'mantenimiento-wordpress', title: lang === 'en' ? 'WordPress maintenance' : 'Mantenimiento WordPress', desc: lang === 'en' ? 'Security patches and updates by official WordPress ecosystem experts.' : 'Parches de seguridad y actualizaciones por expertos del ecosistema WP.', Icon: WordpressIcon, border: 'var(--color-hds-naranja)' },
|
||||
],
|
||||
slug: 'desarrollo-webs-institucionales',
|
||||
title: lang === 'en' ? 'Institutional websites' : 'Desarrollo webs institucionales',
|
||||
desc: lang === 'en' ? 'Corporate sites tailored to your business and identity.' : 'Sitios corporativos a medida de tu negocio e identidad.',
|
||||
Icon: CodeIcon,
|
||||
},
|
||||
{
|
||||
title: t('services.cat.design'),
|
||||
items: [
|
||||
{ slug: 'desarrollo-webs-institucionales', title: lang === 'en' ? 'Institutional websites' : 'Desarrollo webs institucionales', desc: lang === 'en' ? 'Corporate sites tailored to your business and identity.' : 'Sitios corporativos a medida de tu negocio e identidad.', Icon: CodeIcon, border: 'var(--color-hds-coral)' },
|
||||
{ slug: 'e-commerce', title: lang === 'en' ? 'E-commerce websites' : 'Sitios web con e-commerce', desc: lang === 'en' ? 'WooCommerce stores with secure payments and inventory.' : 'Tiendas WooCommerce con pagos seguros y gestión de stock.', Icon: ShopIcon, border: 'var(--color-hds-coral)' },
|
||||
{ slug: 'gestion-contenido-traducciones', title: lang === 'en' ? 'Content & translations' : 'Gestión de contenido y traducciones', desc: lang === 'en' ? 'Recurring editorial: copy, updates, blog and translations.' : 'Servicio editorial recurrente: redacción, blog, traducciones.', Icon: TranslateIcon, border: 'var(--color-hds-coral)' },
|
||||
],
|
||||
slug: 'gestion-contenido-traducciones',
|
||||
title: lang === 'en' ? 'Content & translations' : 'Gestión de contenido y traducciones',
|
||||
desc: lang === 'en' ? 'Recurring editorial: copy, updates, blog and translations.' : 'Servicio editorial recurrente: redacción, blog, traducciones.',
|
||||
Icon: TranslateIcon,
|
||||
},
|
||||
{
|
||||
title: t('services.cat.infra'),
|
||||
items: [
|
||||
{ slug: 'servidores-linux-virtualizacion', title: lang === 'en' ? 'Linux servers & virtualization' : 'Servidores Linux y virtualización', desc: lang === 'en' ? 'Linux servers, KVM virtualization, private clouds.' : 'Servidores Linux, virtualización KVM, nubes privadas.', Icon: ServerIcon, border: 'var(--color-hds-durazno)' },
|
||||
],
|
||||
slug: 'servidores-linux-virtualizacion',
|
||||
title: lang === 'en' ? 'Linux servers & virtualization' : 'Servidores Linux y virtualización',
|
||||
desc: lang === 'en' ? 'Linux servers, KVM virtualization, private clouds.' : 'Servidores Linux, virtualización KVM, nubes privadas.',
|
||||
Icon: ServerIcon,
|
||||
},
|
||||
];
|
||||
|
||||
const plansByCategory = {
|
||||
hosting: PLANS.filter(p => ['basico', 'wordpress', 'institucional', 'empresarial'].includes(p.slug)),
|
||||
business: PLANS.filter(p => ['e-commerce', 'avanzado', 'avanzado-hd'].includes(p.slug)),
|
||||
corporate: PLANS.filter(p => ['corporativo', 'corporativo-hd'].includes(p.slug)),
|
||||
};
|
||||
|
||||
const categoryInfo: Record<keyof typeof plansByCategory, { title: string; subtitle: string; }> = {
|
||||
hosting: { title: lang === 'en' ? 'Hosting plans' : 'Planes de hosting', subtitle: lang === 'en' ? 'For sites, blogs and WordPress' : 'Para sitios, blogs y WordPress' },
|
||||
business: { title: lang === 'en' ? 'Business plans' : 'Planes business', subtitle: lang === 'en' ? 'For e-commerce and growing businesses' : 'Para e-commerce y empresas en crecimiento' },
|
||||
corporate: { title: lang === 'en' ? 'Corporate plans' : 'Planes corporativos', subtitle: lang === 'en' ? 'For high-traffic and enterprise' : 'Para alto tráfico y empresas' },
|
||||
};
|
||||
---
|
||||
<BaseLayout
|
||||
title={lang === 'en' ? 'Services' : 'Servicios'}
|
||||
@@ -47,8 +71,9 @@ const groups = [
|
||||
: 'Servicios de hosting, desarrollo web, e-commerce, gestión de contenido e infraestructura.'}
|
||||
lang={lang}
|
||||
>
|
||||
<section class="pt-20 pb-12 md:pt-28 md:pb-16" style="background: var(--hds-bg-soft);">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<section class="pt-20 pb-12 md:pt-28 md:pb-16 relative" style="background: var(--hds-bg-soft);">
|
||||
<div class="absolute inset-0 particle-grid opacity-60" aria-hidden="true"></div>
|
||||
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<SectionEyebrow text={lang === 'en' ? 'What we do' : 'Lo que hacemos'} />
|
||||
<h1 class="font-display leading-tight mt-5" style="font-size: clamp(2.5rem, 6vw, 4.5rem); color: var(--hds-fg);">
|
||||
{lang === 'en' ? 'Our' : 'Nuestros'} <span style="color: var(--color-hds-naranja); font-style: italic;">{lang === 'en' ? 'services' : 'servicios'}</span>.
|
||||
@@ -61,26 +86,112 @@ const groups = [
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<CloudDivider fillClass="fill-hds-bg" />
|
||||
|
||||
<section class="py-20 md:py-24" style="background: var(--hds-bg);">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 space-y-14">
|
||||
{groups.map((group) => (
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 space-y-16">
|
||||
|
||||
<!-- Custom services -->
|
||||
<div>
|
||||
<div class="flex items-baseline gap-4 mb-6">
|
||||
<h2 class="text-2xl md:text-3xl font-display" style="color: var(--hds-fg);">
|
||||
{lang === 'en' ? 'Custom services' : 'Servicios a medida'}
|
||||
</h2>
|
||||
<span class="text-xs font-semibold tracking-[0.25em] uppercase" style="color: var(--color-hds-naranja);">
|
||||
{customServices.length} {lang === 'en' ? 'services' : 'servicios'}
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||
{customServices.map((service) => (
|
||||
<a
|
||||
href={`${base}/servicios/${service.slug}/`}
|
||||
class="service-card group flex flex-col h-full"
|
||||
style="border-top: 4px solid var(--color-hds-coral); padding-top: 1.5rem;"
|
||||
>
|
||||
<div class="icon-circle mb-5" style="width: 48px; height: 48px;">
|
||||
<service.Icon class="w-6 h-6" />
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold mb-2 transition-colors" style="color: var(--hds-fg);">
|
||||
{service.title}
|
||||
</h3>
|
||||
<p class="text-sm leading-relaxed mb-4 flex-grow" style="color: var(--hds-fg-soft);">
|
||||
{service.desc}
|
||||
</p>
|
||||
<span class="btn-ghost text-sm mt-auto">
|
||||
{lang === 'en' ? 'View details' : 'Ver detalles'}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<line x1="5" y1="12" x2="19" y2="12"/>
|
||||
<polyline points="12 5 19 12 12 19"/>
|
||||
</svg>
|
||||
</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- All 9 plans as services -->
|
||||
{Object.entries(plansByCategory).map(([key, plans]) => (
|
||||
<div>
|
||||
<div class="flex flex-wrap items-baseline gap-3 mb-6">
|
||||
<h2 class="text-2xl md:text-3xl font-display" style="color: var(--hds-fg);">{group.title}</h2>
|
||||
<div class="flex flex-wrap items-baseline gap-4 mb-6">
|
||||
<h2 class="text-2xl md:text-3xl font-display" style="color: var(--hds-fg);">
|
||||
{categoryInfo[key as keyof typeof categoryInfo].title}
|
||||
</h2>
|
||||
<span class="text-sm" style="color: var(--hds-fg-muted);">
|
||||
{categoryInfo[key as keyof typeof categoryInfo].subtitle}
|
||||
</span>
|
||||
<span class="text-xs font-semibold tracking-[0.25em] uppercase" style="color: var(--color-hds-naranja);">
|
||||
{group.items.length} {group.items.length === 1 ? (lang === 'en' ? 'service' : 'servicio') : (lang === 'en' ? 'services' : 'servicios')}
|
||||
{plans.length} {plans.length === 1 ? (lang === 'en' ? 'plan' : 'plan') : (lang === 'en' ? 'plans' : 'planes')}
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||
{group.items.map((item) => (
|
||||
<ServiceCard
|
||||
href={`${base}/servicios/${item.slug}/`}
|
||||
title={item.title}
|
||||
description={item.desc}
|
||||
Icon={item.Icon}
|
||||
borderColor={item.border}
|
||||
/>
|
||||
))}
|
||||
{plans.map((plan) => {
|
||||
const Icon = planIcons[plan.slug] || ServerIcon;
|
||||
return (
|
||||
<a
|
||||
href={`${base}/planes/#${plan.slug}`}
|
||||
class="service-card group flex flex-col h-full relative"
|
||||
style={`border-top: 4px solid ${plan.recommended ? 'var(--color-hds-naranja)' : 'var(--hds-line)'}; padding-top: 1.5rem;`}
|
||||
>
|
||||
{plan.recommended && (
|
||||
<div class="absolute -top-3 right-4 text-[10px] font-bold tracking-[0.15em] uppercase px-2.5 py-0.5 rounded-full" style="background: var(--color-hds-naranja); color: white;">
|
||||
⭐ Top
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="icon-circle mb-4" style="width: 48px; height: 48px;">
|
||||
<Icon class="w-6 h-6" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-baseline justify-between gap-2 mb-2">
|
||||
<h3 class="text-lg font-semibold transition-colors" style="color: var(--hds-fg);">
|
||||
{plan.name}
|
||||
</h3>
|
||||
<div class="font-display leading-none" style="font-size: 1.75rem; color: var(--color-hds-naranja); font-weight: 500;">${plan.priceUsdYear}</div>
|
||||
</div>
|
||||
|
||||
<p class="text-xs mb-3" style="color: var(--hds-fg-muted);">
|
||||
USD/año · {plan.features[0]}
|
||||
</p>
|
||||
|
||||
<ul class="text-xs space-y-1 mb-4 flex-grow" style="color: var(--hds-fg-soft);">
|
||||
{plan.features.slice(1, 4).map((feature) => (
|
||||
<li class="flex items-start gap-1.5">
|
||||
<span style="color: var(--color-hds-naranja);">·</span>
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<span class="btn-ghost text-sm mt-auto">
|
||||
{lang === 'en' ? 'View plan' : 'Ver plan'}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<line x1="5" y1="12" x2="19" y2="12"/>
|
||||
<polyline points="12 5 19 12 12 19"/>
|
||||
</svg>
|
||||
</span>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -230,4 +230,65 @@
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
.reveal { opacity: 1 !important; transform: none !important; }
|
||||
}
|
||||
|
||||
/* Scroll reveal animations */
|
||||
.reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(24px);
|
||||
transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1), transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
.reveal.is-visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.reveal-delay-1 { transition-delay: 0.1s; }
|
||||
.reveal-delay-2 { transition-delay: 0.2s; }
|
||||
.reveal-delay-3 { transition-delay: 0.3s; }
|
||||
.reveal-delay-4 { transition-delay: 0.4s; }
|
||||
|
||||
/* Stagger children */
|
||||
.stagger > * {
|
||||
opacity: 0;
|
||||
transform: translateY(16px);
|
||||
transition: opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1), transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
.stagger.is-visible > *:nth-child(1) { opacity: 1; transform: translateY(0); transition-delay: 0s; }
|
||||
.stagger.is-visible > *:nth-child(2) { opacity: 1; transform: translateY(0); transition-delay: 0.08s; }
|
||||
.stagger.is-visible > *:nth-child(3) { opacity: 1; transform: translateY(0); transition-delay: 0.16s; }
|
||||
.stagger.is-visible > *:nth-child(4) { opacity: 1; transform: translateY(0); transition-delay: 0.24s; }
|
||||
.stagger.is-visible > *:nth-child(5) { opacity: 1; transform: translateY(0); transition-delay: 0.32s; }
|
||||
.stagger.is-visible > *:nth-child(6) { opacity: 1; transform: translateY(0); transition-delay: 0.40s; }
|
||||
.stagger.is-visible > *:nth-child(7) { opacity: 1; transform: translateY(0); transition-delay: 0.48s; }
|
||||
.stagger.is-visible > *:nth-child(8) { opacity: 1; transform: translateY(0); transition-delay: 0.56s; }
|
||||
.stagger.is-visible > *:nth-child(9) { opacity: 1; transform: translateY(0); transition-delay: 0.64s; }
|
||||
|
||||
/* Float subtle */
|
||||
@keyframes floaty {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-8px); }
|
||||
}
|
||||
.floaty {
|
||||
animation: floaty 4s ease-in-out infinite;
|
||||
}
|
||||
.floaty-delay {
|
||||
animation: floaty 4s ease-in-out infinite;
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
/* Pulse glow */
|
||||
@keyframes pulse-glow {
|
||||
0%, 100% { box-shadow: 0 0 0 0 rgba(238,118,35,0.4); }
|
||||
50% { box-shadow: 0 0 0 12px rgba(238,118,35,0); }
|
||||
}
|
||||
.pulse-glow {
|
||||
animation: pulse-glow 2.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Counter */
|
||||
@keyframes countUp {
|
||||
from { opacity: 0.3; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||