feat: sitio hostingdelsur.net v2 con Astro 5, Tailwind v4, soporte light/dark, i18n es/en, Keystatic CMS, reCAPTCHA v3
- Arista Pro Alternate Regular self-hosted (font corporativa) - Toggle theme con CSS variables y @custom-variant dark - 6 servicios en 3 categorías (Hosting & Correo / Diseño & Contenido / Infraestructura) - 3 planes destacados (Básico USD 59, Institucional USD 129, E-commerce USD 219) - Datacenters en 4 países (Canadá, USA, Alemania, Uruguay) sin ciudades en el sitio - Sede operativa en Maldonado, Uruguay - i18n es/en con contenido duplicado en Keystatic - Endpoint PHP para form de contacto con PHPMailer + reCAPTCHA v3 + honeypot + rate limit - WorldMap con animación SVG de los 4 países - 29 páginas generadas, 0 JS por default - Sitemap auto + robots.txt - JSON-LD Organization + ProfessionalService con areaServed
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
---
|
||||
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);"
|
||||
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">
|
||||
<polyline points="18 15 12 9 6 15"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<script is:inline>
|
||||
(function() {
|
||||
const btn = document.getElementById('back-to-top');
|
||||
if (!btn) return;
|
||||
let visible = false;
|
||||
function update() {
|
||||
const should = window.scrollY > 400;
|
||||
if (should !== visible) {
|
||||
visible = should;
|
||||
if (should) {
|
||||
btn.classList.remove('opacity-0', 'invisible');
|
||||
btn.classList.add('opacity-100', 'visible');
|
||||
} else {
|
||||
btn.classList.remove('opacity-100', 'visible');
|
||||
btn.classList.add('opacity-0', 'invisible');
|
||||
}
|
||||
}
|
||||
}
|
||||
window.addEventListener('scroll', update, { passive: true });
|
||||
btn.addEventListener('click', () => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
interface Props {
|
||||
variant?: 'wave' | 'cloud';
|
||||
flip?: boolean;
|
||||
fillClass?: string;
|
||||
}
|
||||
const { variant = 'wave', flip = false, fillClass = 'fill-hds-bg' } = Astro.props;
|
||||
---
|
||||
{variant === 'wave' ? (
|
||||
<div class={`w-full overflow-hidden leading-none ${flip ? 'rotate-180' : ''}`} aria-hidden="true">
|
||||
<svg class="w-full h-12 md:h-16" viewBox="0 0 1440 80" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path class={fillClass} d="M0,32 C240,80 480,0 720,32 C960,64 1200,16 1440,48 L1440,80 L0,80 Z" />
|
||||
</svg>
|
||||
</div>
|
||||
) : (
|
||||
<div class={`w-full overflow-hidden leading-none ${flip ? 'rotate-180' : ''}`} aria-hidden="true">
|
||||
<svg class="w-full h-16 md:h-24" viewBox="0 0 1440 120" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path class={fillClass} d="M0,60 C120,100 240,20 360,50 C480,80 600,30 720,55 C840,80 960,40 1080,60 C1200,80 1320,45 1440,65 L1440,120 L0,120 Z" />
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
import SectionEyebrow from './SectionEyebrow.astro';
|
||||
import type { TranslationKey } from '@/i18n/ui';
|
||||
|
||||
interface Props {
|
||||
t: (key: TranslationKey) => string;
|
||||
whatsappUrl: string;
|
||||
}
|
||||
const { t, whatsappUrl } = Astro.props;
|
||||
---
|
||||
<section class="relative overflow-hidden py-20 md:py-32" style="background: #0F0E0C; color: #FAF6EE;">
|
||||
<div class="absolute inset-0 particle-grid" aria-hidden="true" style="opacity: 0.7;"></div>
|
||||
|
||||
<div class="relative max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<div class="inline-block">
|
||||
<span class="inline-flex items-center gap-3 text-xs font-semibold tracking-[0.25em] uppercase" style="color: #FA9F5C;">
|
||||
<span class="inline-block w-10 h-px" style="background: #FA9F5C;"></span>
|
||||
{t('ctaFinal.eyebrow') || 'Hablemos'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h2 class="font-display leading-tight mt-5 mb-5" style="font-size: clamp(2.25rem, 5vw, 4rem); color: #FAF6EE;">
|
||||
{t('ctaFinal.title')}
|
||||
</h2>
|
||||
<p class="text-lg md:text-xl max-w-2xl mx-auto mb-10" style="color: #C8C2B8;">
|
||||
{t('ctaFinal.body')}
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<a href={whatsappUrl} target="_blank" rel="noopener noreferrer" class="btn-primary text-base">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z"/>
|
||||
</svg>
|
||||
{t('ctaFinal.button')}
|
||||
</a>
|
||||
<a href="mailto:contacto@hostingdelsur.net" class="inline-flex items-center justify-center gap-2 px-7 py-3.5 rounded-md text-base font-semibold transition-colors" style="border: 2px solid #FAF6EE; color: #FAF6EE; background: transparent;" onmouseover="this.style.background='#FAF6EE'; this.style.color='#0F0E0C';" onmouseout="this.style.background='transparent'; this.style.color='#FAF6EE';">
|
||||
contacto@hostingdelsur.net
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
import SectionEyebrow from './SectionEyebrow.astro';
|
||||
import type { TranslationKey } from '@/i18n/ui';
|
||||
import MateIcon from './icons/MateIcon.astro';
|
||||
import SovereigntyIcon from './icons/SovereigntyIcon.astro';
|
||||
import GlobeIcon from './icons/GlobeIcon.astro';
|
||||
|
||||
interface Props {
|
||||
t: (key: TranslationKey) => string;
|
||||
}
|
||||
const { t } = Astro.props;
|
||||
|
||||
const items = [
|
||||
{ Icon: MateIcon, titleKey: 'diff.support.title' as TranslationKey, bodyKey: 'diff.support.body' as TranslationKey },
|
||||
{ Icon: SovereigntyIcon, titleKey: 'diff.sovereignty.title' as TranslationKey, bodyKey: 'diff.sovereignty.body' as TranslationKey },
|
||||
{ Icon: GlobeIcon, titleKey: 'diff.infra.title' as TranslationKey, bodyKey: 'diff.infra.body' as TranslationKey },
|
||||
];
|
||||
---
|
||||
<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">
|
||||
<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')}
|
||||
</h2>
|
||||
<p class="text-lg mt-5 max-w-2xl" style="color: var(--hds-fg-soft);">
|
||||
{t('diff.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{items.map((item) => (
|
||||
<article class="service-card">
|
||||
<div class="icon-circle mb-6">
|
||||
<item.Icon class="w-7 h-7" />
|
||||
</div>
|
||||
<h3 class="text-xl md:text-2xl font-semibold mb-3" style="color: var(--hds-fg);">
|
||||
{t(item.titleKey)}
|
||||
</h3>
|
||||
<p class="text-[15px] leading-relaxed" style="color: var(--hds-fg-soft);">
|
||||
{t(item.bodyKey)}
|
||||
</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
interface FaqItem {
|
||||
q: string;
|
||||
a: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
items: FaqItem[];
|
||||
}
|
||||
const { items } = Astro.props;
|
||||
|
||||
const groupId = `faq-${Math.random().toString(36).slice(2, 8)}`;
|
||||
---
|
||||
<div class="divide-y" style="border-color: var(--hds-line);">
|
||||
{items.map((item, idx) => {
|
||||
const inputId = `${groupId}-${idx}`;
|
||||
return (
|
||||
<details class="group" style="border-color: var(--hds-line);">
|
||||
<summary
|
||||
class="flex items-start justify-between gap-4 py-5 cursor-pointer list-none transition-colors"
|
||||
style="color: var(--hds-fg);"
|
||||
>
|
||||
<h3 class="text-base md:text-lg font-semibold pr-4">{item.q}</h3>
|
||||
<span class="flex-shrink-0 mt-1 icon-circle transition-transform group-open:rotate-45" style="width: 28px; height: 28px;">
|
||||
<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" aria-hidden="true">
|
||||
<line x1="12" y1="5" x2="12" y2="19"/>
|
||||
<line x1="5" y1="12" x2="19" y2="12"/>
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div class="pb-5 pr-12 text-[15px] leading-relaxed" style="color: var(--hds-fg-soft);">
|
||||
{item.a}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -0,0 +1,109 @@
|
||||
---
|
||||
import type { Lang } from '@/i18n/utils';
|
||||
import type { TranslationKey } from '@/i18n/ui';
|
||||
|
||||
interface Props {
|
||||
t: (key: TranslationKey) => string;
|
||||
lang: Lang;
|
||||
}
|
||||
|
||||
const { t, lang } = Astro.props;
|
||||
const year = new Date().getFullYear();
|
||||
const base = lang === 'en' ? '/en' : '';
|
||||
|
||||
const navLinks: { href: string; key: TranslationKey }[] = [
|
||||
{ href: `${base}/`, key: 'nav.home' },
|
||||
{ href: `${base}/servicios/`, key: 'nav.services' },
|
||||
{ href: `${base}/planes/`, key: 'nav.plans' },
|
||||
{ href: `${base}/nosotros/`, key: 'nav.about' },
|
||||
{ href: `${base}/instructivos/`, key: 'nav.tutorials' },
|
||||
{ href: `${base}/contacto/`, key: 'nav.contact' },
|
||||
];
|
||||
|
||||
const legalLinks = [
|
||||
{ href: `${base}/legal/privacidad/`, key: 'footer.privacy' as TranslationKey },
|
||||
{ href: `${base}/legal/terminos/`, key: 'footer.terms' as TranslationKey },
|
||||
];
|
||||
---
|
||||
<footer class="py-14 md:py-20" style="background: #0F0E0C; color: #FAF6EE;">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 md:grid-cols-12 gap-10 md:gap-8">
|
||||
<div class="md:col-span-4">
|
||||
<a href={lang === 'en' ? '/en/' : '/'} class="inline-flex items-center gap-2.5 mb-4" aria-label="Hosting del Sur">
|
||||
<img src="/logo/hds-mark.svg" alt="" width="36" height="36" class="h-9 w-9" aria-hidden="true" style="filter: brightness(0) invert(1);" />
|
||||
<span class="text-lg font-display tracking-tight">
|
||||
<span style="color: #EE7623;">hosting</span> del <span style="color: #EE7623;">sur</span>
|
||||
</span>
|
||||
</a>
|
||||
<p class="text-sm leading-relaxed max-w-sm" style="color: #C8C2B8;">
|
||||
{t('footer.tagline')}
|
||||
</p>
|
||||
<p class="text-xs mt-4" style="color: #8A8580;">
|
||||
{lang === 'en' ? 'Web, email, infrastructure and human support in Uruguay.' : 'Web, correo, infraestructura y soporte humano en Uruguay.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2">
|
||||
<h2 class="text-xs font-semibold tracking-[0.25em] uppercase mb-4" style="color: #FA9F5C;">
|
||||
{t('footer.nav')}
|
||||
</h2>
|
||||
<ul class="space-y-2.5">
|
||||
{navLinks.map((link) => (
|
||||
<li>
|
||||
<a
|
||||
href={link.href}
|
||||
class="text-sm transition-colors hover:text-white"
|
||||
style="color: #C8C2B8;"
|
||||
>
|
||||
{t(link.key)}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-3">
|
||||
<h2 class="text-xs font-semibold tracking-[0.25em] uppercase mb-4" style="color: #FA9F5C;">
|
||||
{t('footer.contact')}
|
||||
</h2>
|
||||
<ul class="space-y-2.5 text-sm" style="color: #C8C2B8;">
|
||||
<li>
|
||||
<a href="https://wa.me/59899812487" target="_blank" rel="noopener noreferrer" class="hover:text-white transition-colors">
|
||||
+598 99 812 487
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="mailto:contacto@hostingdelsur.net" class="hover:text-white transition-colors break-all">
|
||||
contacto@hostingdelsur.net
|
||||
</a>
|
||||
</li>
|
||||
<li>Maldonado, Uruguay</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-3">
|
||||
<h2 class="text-xs font-semibold tracking-[0.25em] uppercase mb-4" style="color: #FA9F5C;">
|
||||
{t('footer.legal')}
|
||||
</h2>
|
||||
<ul class="space-y-2.5">
|
||||
{legalLinks.map((link) => (
|
||||
<li>
|
||||
<a
|
||||
href={link.href}
|
||||
class="text-sm transition-colors hover:text-white"
|
||||
style="color: #C8C2B8;"
|
||||
>
|
||||
{t(link.key)}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-12 pt-6 border-t flex flex-col sm:flex-row sm:justify-between gap-3 text-xs" style="border-color: #2A2520; color: #8A8580;">
|
||||
<p>© {year} Hosting del Sur. {t('footer.copyright')}</p>
|
||||
<p>Maldonado, Uruguay</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
import type { TranslationKey } from '@/i18n/ui';
|
||||
|
||||
interface Props {
|
||||
t: (key: TranslationKey) => string;
|
||||
whatsappUrl: string;
|
||||
}
|
||||
|
||||
const { t, whatsappUrl } = Astro.props;
|
||||
---
|
||||
<section class="relative overflow-hidden pt-24 pb-32 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>
|
||||
|
||||
<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">
|
||||
<span>Premium hosting · Desde 2000</span>
|
||||
</div>
|
||||
|
||||
<h1 class="font-display leading-[0.98] mb-8" style="font-size: clamp(2.75rem, 7vw, 5.5rem); color: var(--hds-fg); letter-spacing: -0.025em; font-weight: 400;">
|
||||
Alojamiento en la nube<br/>
|
||||
que te <span style="color: var(--color-hds-naranja); font-style: italic;">conoce</span>.
|
||||
</h1>
|
||||
|
||||
<p class="text-lg md:text-xl leading-relaxed mb-10 max-w-2xl" style="color: var(--hds-fg-soft);">
|
||||
{t('hero.body')}
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-4 mb-14">
|
||||
<a href="#planes" class="btn-primary">
|
||||
{t('hero.ctaPrimary')}
|
||||
</a>
|
||||
<a href={whatsappUrl} target="_blank" rel="noopener noreferrer" class="btn-secondary">
|
||||
{t('hero.ctaSecondary')}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-x-8 gap-y-3 text-sm" style="color: var(--hds-fg-muted);">
|
||||
<span class="flex items-center gap-2">
|
||||
<span style="color: var(--color-hds-naranja);">●</span>
|
||||
26+ años
|
||||
</span>
|
||||
<span class="flex items-center gap-2">
|
||||
<span style="color: var(--color-hds-naranja);">●</span>
|
||||
Uptime 99.9%
|
||||
</span>
|
||||
<span class="flex items-center gap-2">
|
||||
<span style="color: var(--color-hds-naranja);">●</span>
|
||||
{t('diff.support.title')}
|
||||
</span>
|
||||
<span class="flex items-center gap-2">
|
||||
<span style="color: var(--color-hds-naranja);">●</span>
|
||||
Maldonado, Uruguay
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
import { LANGUAGES, DEFAULT_LANG, type Lang } from '@/i18n/utils';
|
||||
|
||||
interface Props {
|
||||
lang: Lang;
|
||||
}
|
||||
const { lang } = Astro.props;
|
||||
const path = Astro.url.pathname;
|
||||
|
||||
function otherLangUrl(): string {
|
||||
const cleanPath = path.replace(/^\/(en|es)/, '') || '/';
|
||||
if (lang === DEFAULT_LANG) {
|
||||
return `/en${cleanPath === '/' ? '' : cleanPath}`;
|
||||
}
|
||||
return cleanPath;
|
||||
}
|
||||
|
||||
const other = LANGUAGES.find((l) => l.code !== lang);
|
||||
const isEn = lang === 'en';
|
||||
---
|
||||
<a
|
||||
href={otherLangUrl()}
|
||||
hreflang={other?.code}
|
||||
class="text-xs font-semibold tracking-wider px-2.5 py-1.5 rounded border transition-colors"
|
||||
style="border-color: var(--hds-line); color: var(--hds-fg-soft);"
|
||||
aria-label={isEn ? 'Switch language to Spanish' : 'Cambiar idioma a inglés'}
|
||||
onmouseover="this.style.borderColor='var(--color-hds-naranja)';this.style.color='var(--color-hds-naranja)';"
|
||||
onmouseout="this.style.borderColor='var(--hds-line)';this.style.color='var(--hds-fg-soft)';"
|
||||
>
|
||||
{other?.label}
|
||||
</a>
|
||||
@@ -0,0 +1,143 @@
|
||||
---
|
||||
import ThemeToggle from './ThemeToggle.astro';
|
||||
import LangToggle from './LangToggle.astro';
|
||||
import { type Lang } from '@/i18n/utils';
|
||||
import type { TranslationKey } from '@/i18n/ui';
|
||||
|
||||
interface Props {
|
||||
t: (key: TranslationKey) => string;
|
||||
lang: Lang;
|
||||
currentPath?: string;
|
||||
}
|
||||
|
||||
const { t, lang, currentPath } = Astro.props;
|
||||
const path = currentPath || Astro.url.pathname;
|
||||
|
||||
const navItems: { label: string; href: string; key: TranslationKey }[] = [
|
||||
{ label: t('nav.home'), href: lang === 'en' ? '/en/' : '/', key: 'nav.home' },
|
||||
{ label: t('nav.services'), href: lang === 'en' ? '/en/servicios/' : '/servicios/', key: 'nav.services' },
|
||||
{ label: t('nav.plans'), href: lang === 'en' ? '/en/planes/' : '/planes/', key: 'nav.plans' },
|
||||
{ label: t('nav.about'), href: lang === 'en' ? '/en/nosotros/' : '/nosotros/', key: 'nav.about' },
|
||||
{ label: t('nav.tutorials'), href: lang === 'en' ? '/en/instructivos/' : '/instructivos/', key: 'nav.tutorials' },
|
||||
{ label: t('nav.contact'), href: lang === 'en' ? '/en/contacto/' : '/contacto/', key: 'nav.contact' },
|
||||
];
|
||||
|
||||
const whatsappUrl = 'https://wa.me/59899812487';
|
||||
const isActive = (href: string) => {
|
||||
const normalized = path.replace(/\/$/, '') || '/';
|
||||
const target = href.replace(/\/$/, '') || '/';
|
||||
return normalized === target;
|
||||
};
|
||||
---
|
||||
<header id="navbar" class="sticky top-0 z-40 backdrop-blur-md border-b transition-colors" style="background: rgba(250,246,238,0.85); border-color: var(--hds-line);">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between h-16 md:h-20">
|
||||
<a href={lang === 'en' ? '/en/' : '/'} class="flex items-center gap-2.5 flex-shrink-0" aria-label="Hosting del Sur - Inicio">
|
||||
<img
|
||||
src="/logo/hds-mark.svg"
|
||||
alt=""
|
||||
width="32"
|
||||
height="32"
|
||||
class="h-8 w-8"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span class="text-base font-display tracking-tight" style="color: var(--hds-fg);">
|
||||
<span style="color: var(--color-hds-naranja);">hosting</span> del <span style="color: var(--color-hds-naranja);">sur</span>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<nav class="hidden lg:flex items-center gap-7" aria-label="Principal">
|
||||
{navItems.map((item) => (
|
||||
<a
|
||||
href={item.href}
|
||||
class:list={[
|
||||
"text-sm font-medium transition-colors",
|
||||
isActive(item.href) ? "text-hds-naranja" : ""
|
||||
]}
|
||||
style={isActive(item.href) ? 'color: var(--color-hds-naranja);' : 'color: var(--hds-fg-soft);'}
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<LangToggle lang={lang} />
|
||||
<ThemeToggle lang={lang} />
|
||||
<a
|
||||
href={whatsappUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="hidden md:inline-flex btn-primary text-sm"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z"/>
|
||||
</svg>
|
||||
<span>{t('nav.cta')}</span>
|
||||
</a>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="lg:hidden p-2 -mr-2"
|
||||
aria-label="Abrir menú"
|
||||
aria-expanded="false"
|
||||
aria-controls="mobile-menu"
|
||||
id="mobile-menu-button"
|
||||
style="color: var(--hds-fg);"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<line x1="3" y1="6" x2="21" y2="6"/>
|
||||
<line x1="3" y1="12" x2="21" y2="12"/>
|
||||
<line x1="3" y1="18" x2="21" y2="18"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="mobile-menu" class="lg:hidden hidden border-t py-4" style="border-color: var(--hds-line);">
|
||||
<nav class="flex flex-col gap-3" aria-label="Móvil">
|
||||
{navItems.map((item) => (
|
||||
<a
|
||||
href={item.href}
|
||||
class="text-base font-medium py-2"
|
||||
style={isActive(item.href) ? 'color: var(--color-hds-naranja);' : 'color: var(--hds-fg-soft);'}
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
))}
|
||||
<a
|
||||
href={whatsappUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="md:hidden inline-flex items-center justify-center gap-2 btn-primary text-sm mt-2"
|
||||
>
|
||||
<span>{t('nav.cta')}</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<script is:inline>
|
||||
(function() {
|
||||
const btn = document.getElementById('mobile-menu-button');
|
||||
const menu = document.getElementById('mobile-menu');
|
||||
if (!btn || !menu) return;
|
||||
btn.addEventListener('click', () => {
|
||||
const isOpen = !menu.classList.contains('hidden');
|
||||
if (isOpen) {
|
||||
menu.classList.add('hidden');
|
||||
btn.setAttribute('aria-expanded', 'false');
|
||||
} else {
|
||||
menu.classList.remove('hidden');
|
||||
btn.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
:global(:root.dark) #navbar {
|
||||
background: rgba(26,22,18,0.85) !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
interface Props {
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
opacity?: number;
|
||||
}
|
||||
const { size = 'md', opacity = 0.10 } = Astro.props;
|
||||
|
||||
const sizes = {
|
||||
sm: 40,
|
||||
md: 56,
|
||||
lg: 72,
|
||||
};
|
||||
const cell = sizes[size];
|
||||
---
|
||||
<div
|
||||
class="absolute inset-0 pointer-events-none"
|
||||
aria-hidden="true"
|
||||
style={`background-image: linear-gradient(to right, rgba(238,118,35,${opacity}) 1px, transparent 1px), linear-gradient(to bottom, rgba(238,118,35,${opacity}) 1px, transparent 1px); background-size: ${cell}px ${cell}px;`}
|
||||
></div>
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
interface Props {
|
||||
plans: Array<{
|
||||
slug: string;
|
||||
name: string;
|
||||
priceUsdYear: number;
|
||||
recommended?: boolean;
|
||||
features: string[];
|
||||
}>;
|
||||
whatsappUrl: string;
|
||||
additionalNote?: string;
|
||||
}
|
||||
|
||||
const { plans, whatsappUrl, additionalNote } = Astro.props;
|
||||
|
||||
function monthlyEquivalent(annual: number): string {
|
||||
return (annual / 12).toFixed(2);
|
||||
}
|
||||
---
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{plans.map((plan) => (
|
||||
<div
|
||||
class="relative rounded-2xl p-8 transition-all flex flex-col h-full"
|
||||
style={plan.recommended
|
||||
? `background: var(--hds-card); border: 2px solid var(--color-hds-naranja); box-shadow: 0 16px 40px rgba(238,118,35,0.15);`
|
||||
: `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 12px rgba(238,118,35,0.3);">
|
||||
⭐ Recomendado
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="mb-5">
|
||||
<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">
|
||||
<span class="text-2xl font-semibold" style="color: var(--hds-fg);">USD</span>
|
||||
<span class="font-display leading-none" style="font-size: 4rem; color: var(--hds-fg); font-weight: 500; letter-spacing: -0.02em;">{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>
|
||||
|
||||
<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.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>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href={whatsappUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class={plan.recommended ? 'btn-primary justify-center' : 'btn-secondary justify-center'}
|
||||
>
|
||||
Elegir {plan.name}
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{additionalNote && (
|
||||
<p class="text-center text-sm mt-6 italic" style="color: var(--hds-fg-muted);">
|
||||
{additionalNote}
|
||||
</p>
|
||||
)}
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
import SectionEyebrow from './SectionEyebrow.astro';
|
||||
import type { TranslationKey } from '@/i18n/ui';
|
||||
|
||||
interface Props {
|
||||
t: (key: TranslationKey) => string;
|
||||
}
|
||||
const { t } = Astro.props;
|
||||
|
||||
const steps = [
|
||||
{ num: '01', titleKey: 'process.step1.title' as TranslationKey, bodyKey: 'process.step1.body' as TranslationKey },
|
||||
{ num: '02', titleKey: 'process.step2.title' as TranslationKey, bodyKey: 'process.step2.body' as TranslationKey },
|
||||
{ num: '03', titleKey: 'process.step3.title' as TranslationKey, bodyKey: 'process.step3.body' as TranslationKey },
|
||||
{ num: '04', titleKey: 'process.step4.title' as TranslationKey, bodyKey: 'process.step4.body' as TranslationKey },
|
||||
];
|
||||
---
|
||||
<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">
|
||||
<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">
|
||||
<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) => (
|
||||
<li class="relative">
|
||||
<div class="font-display leading-none mb-4 relative inline-block" style="font-size: 5rem; color: var(--color-hds-naranja); font-weight: 500; opacity: 0.85;" aria-hidden="true">
|
||||
{step.num}
|
||||
<span class="absolute -bottom-1 left-0 w-10 h-1" style="background: var(--color-hds-naranja);"></span>
|
||||
</div>
|
||||
<h3 class="text-xl md:text-2xl font-semibold mb-3" style="color: var(--hds-fg);">
|
||||
{t(step.titleKey)}
|
||||
</h3>
|
||||
<p class="text-[15px] leading-relaxed" style="color: var(--hds-fg-soft);">
|
||||
{t(step.bodyKey)}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
interface Props {
|
||||
text: string;
|
||||
align?: 'left' | 'center';
|
||||
light?: boolean;
|
||||
}
|
||||
const { text, align = 'left', light = false } = Astro.props;
|
||||
---
|
||||
{align === 'center' ? (
|
||||
<div class="text-center">
|
||||
<span class="inline-flex items-center gap-3 text-xs font-semibold tracking-[0.25em] uppercase" style={`color: ${light ? '#FA9F5C' : 'var(--color-hds-naranja)'}`}>
|
||||
<span class="inline-block w-10 h-px" style={`background: ${light ? '#FA9F5C' : 'var(--color-hds-naranja)'}`}></span>
|
||||
{text}
|
||||
<span class="inline-block w-10 h-px" style={`background: ${light ? '#FA9F5C' : 'var(--color-hds-naranja)'}`}></span>
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div class="inline-flex items-center gap-3 text-xs font-semibold tracking-[0.25em] uppercase" style={`color: ${light ? '#FA9F5C' : 'var(--color-hds-naranja)'}`}>
|
||||
{text}
|
||||
<span class="inline-block w-12 h-px" style={`background: ${light ? '#FA9F5C' : 'var(--color-hds-naranja)'}`}></span>
|
||||
</div>
|
||||
)}
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
import type { TranslationKey } from '@/i18n/ui';
|
||||
|
||||
interface Props {
|
||||
href: string;
|
||||
title: string;
|
||||
description: string;
|
||||
Icon: any;
|
||||
borderColor?: string;
|
||||
}
|
||||
|
||||
const { href, title, description, Icon, borderColor = 'var(--color-hds-naranja)' } = Astro.props;
|
||||
---
|
||||
<a
|
||||
href={href}
|
||||
class="service-card group flex flex-col h-full"
|
||||
style={`border-top: 4px solid ${borderColor}; padding-top: 1.5rem;`}
|
||||
>
|
||||
<div class="icon-circle mb-5" style="width: 48px; height: 48px;">
|
||||
<Icon class="w-6 h-6" />
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold mb-2 transition-colors" style="color: var(--hds-fg);">
|
||||
{title}
|
||||
</h3>
|
||||
<p class="text-sm leading-relaxed mb-4 flex-grow" style="color: var(--hds-fg-soft);">
|
||||
{description}
|
||||
</p>
|
||||
<span class="btn-ghost mt-auto">
|
||||
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>
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
import ServiceCard from './ServiceCard.astro';
|
||||
import type { TranslationKey } from '@/i18n/ui';
|
||||
|
||||
interface Service {
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string;
|
||||
Icon: any;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
categoryTitle: string;
|
||||
categoryKey: TranslationKey;
|
||||
services: Service[];
|
||||
lang: 'es' | 'en';
|
||||
}
|
||||
|
||||
const { categoryTitle, categoryKey, services, lang } = Astro.props;
|
||||
const base = lang === 'en' ? '/en' : '';
|
||||
---
|
||||
<div class="mb-12 last:mb-0">
|
||||
<div class="flex items-baseline justify-between mb-6">
|
||||
<h3 class="text-2xl md:text-3xl text-hds-tinta">
|
||||
{categoryTitle}
|
||||
</h3>
|
||||
<span class="text-xs font-semibold tracking-wider uppercase text-hds-coral">
|
||||
{categoryKey}
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-6">
|
||||
{services.map((service) => (
|
||||
<ServiceCard
|
||||
href={`${base}/servicios/${service.slug}/`}
|
||||
title={service.title}
|
||||
description={service.description}
|
||||
Icon={service.Icon}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,140 @@
|
||||
---
|
||||
import ServiceCard from './ServiceCard.astro';
|
||||
import SectionEyebrow from './SectionEyebrow.astro';
|
||||
import type { TranslationKey } from '@/i18n/ui';
|
||||
import MailIcon from './icons/MailIcon.astro';
|
||||
import WordpressIcon from './icons/WordpressIcon.astro';
|
||||
import CodeIcon from './icons/CodeIcon.astro';
|
||||
import ShopIcon from './icons/ShopIcon.astro';
|
||||
import TranslateIcon from './icons/TranslateIcon.astro';
|
||||
import ServerIcon from './icons/ServerIcon.astro';
|
||||
|
||||
interface Props {
|
||||
t: (key: TranslationKey) => string;
|
||||
lang: 'es' | 'en';
|
||||
}
|
||||
|
||||
const { t, lang } = Astro.props;
|
||||
const base = lang === 'en' ? '/en' : '';
|
||||
|
||||
const groups = [
|
||||
{
|
||||
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)',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
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)',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
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)',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
---
|
||||
<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">
|
||||
<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')}
|
||||
</h2>
|
||||
<p class="text-lg mt-5 max-w-2xl" style="color: var(--hds-fg-soft);">
|
||||
{t('services.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-14">
|
||||
{groups.map((group) => (
|
||||
<div>
|
||||
<div class="flex items-baseline gap-4 mb-6">
|
||||
<h3 class="text-2xl md:text-3xl font-display" style="color: var(--hds-fg);">
|
||||
{group.title}
|
||||
</h3>
|
||||
<span class="text-xs font-semibold tracking-[0.25em] uppercase" style="color: var(--color-hds-naranja);">
|
||||
{group.items.length} {group.items.length === 1 ? 'servicio' : 'servicios'}
|
||||
</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}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-14">
|
||||
<a href={`${base}/servicios/`} class="btn-secondary">
|
||||
Ver todos los servicios
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" 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>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
interface Props {
|
||||
size?: number;
|
||||
duration?: number;
|
||||
suffix?: string;
|
||||
label: string;
|
||||
sublabel?: string;
|
||||
light?: boolean;
|
||||
}
|
||||
|
||||
const { size = 96, duration = 2000, suffix = '', label, sublabel, light = false } = Astro.props;
|
||||
const numId = `stat-${Math.random().toString(36).slice(2, 9)}`;
|
||||
---
|
||||
<div class="text-center group">
|
||||
<div class={`stat-number font-display leading-none ${light ? 'text-white' : ''}`} style={`font-size: ${size}px; color: var(--color-hds-naranja); font-weight: 500;`} data-target={typeof label === 'string' && /^\d+$/.test(label) ? label : '0'} data-suffix={suffix} data-duration={duration} id={numId}>
|
||||
{label}{suffix}
|
||||
</div>
|
||||
<div class={`text-sm font-semibold mt-3 ${light ? 'text-hds-crema-soft' : ''}`} style={light ? 'color: #FAF6EE' : 'color: var(--hds-fg-soft)'}>
|
||||
<slot name="label">{label}</slot>
|
||||
</div>
|
||||
{sublabel && (
|
||||
<div class={`text-xs mt-1 ${light ? 'text-hds-line' : ''}`} style={light ? 'color: #C8C2B8' : 'color: var(--hds-fg-muted)'}>
|
||||
{sublabel}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
import type { TranslationKey } from '@/i18n/ui';
|
||||
import StatCounter from './StatCounter.astro';
|
||||
|
||||
interface Props {
|
||||
t: (key: TranslationKey) => string;
|
||||
}
|
||||
const { t } = Astro.props;
|
||||
|
||||
const stats = [
|
||||
{ value: '26+', label: 'Años de experiencia', sub: 'Desde el 2000' },
|
||||
{ value: '100+', label: 'Proyectos hospedados', sub: 'Webs, e-commerce, infra' },
|
||||
{ value: '4', label: 'Países', sub: 'Canadá, USA, Alemania, UY' },
|
||||
{ value: 'Senior', label: 'Equipo senior', sub: 'SysAdmin / SRE' },
|
||||
];
|
||||
---
|
||||
<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">
|
||||
{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;">
|
||||
{stat.value}
|
||||
</div>
|
||||
<div class="text-sm font-semibold" style="color: var(--hds-fg-soft);">
|
||||
{stat.label}
|
||||
</div>
|
||||
<div class="text-xs mt-1" style="color: var(--hds-fg-muted);">
|
||||
{stat.sub}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
import { DEFAULT_LANG, type Lang } from '@/i18n/utils';
|
||||
|
||||
interface Props {
|
||||
lang: Lang;
|
||||
}
|
||||
const { lang } = Astro.props;
|
||||
|
||||
const isDark = lang === 'en' ? 'dark' : 'light';
|
||||
---
|
||||
<button
|
||||
type="button"
|
||||
id="theme-toggle"
|
||||
aria-label={lang === 'en' ? 'Switch theme' : 'Cambiar tema'}
|
||||
class="p-2 rounded-md hover:bg-hds-line transition-colors"
|
||||
style="color: var(--hds-fg-soft);"
|
||||
>
|
||||
<svg class="theme-icon-sun w-5 h-5 hidden dark:block" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="4"/>
|
||||
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/>
|
||||
</svg>
|
||||
<svg class="theme-icon-moon w-5 h-5 block dark:hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<script is:inline>
|
||||
(function() {
|
||||
const btn = document.getElementById('theme-toggle');
|
||||
if (!btn) return;
|
||||
btn.addEventListener('click', () => {
|
||||
const isDark = document.documentElement.classList.toggle('dark');
|
||||
try { localStorage.setItem('theme', isDark ? 'dark' : 'light'); } catch(e) {}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
import type { Lang } from '@/i18n/utils';
|
||||
|
||||
interface Props {
|
||||
lang: Lang;
|
||||
}
|
||||
|
||||
const { lang } = Astro.props;
|
||||
const label = lang === 'en' ? 'Chat on WhatsApp' : 'Hablar por WhatsApp';
|
||||
---
|
||||
<a
|
||||
href="https://wa.me/59899812487"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="fixed bottom-6 right-6 z-30 inline-flex items-center justify-center w-14 h-14 rounded-full text-white shadow-xl hover:scale-105 transition-transform"
|
||||
style="background: #25D366;"
|
||||
aria-label={label}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z"/>
|
||||
</svg>
|
||||
</a>
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
const { class: className = 'w-12 h-12' } = Astro.props;
|
||||
---
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none" class={className} aria-hidden="true">
|
||||
<path d="M14 30 Q8 24 14 18" stroke="#EE7623" stroke-width="2.5" fill="none" stroke-linecap="round"/>
|
||||
<path d="M34 30 Q40 24 34 18" stroke="#EE7623" stroke-width="2.5" fill="none" stroke-linecap="round"/>
|
||||
<rect x="20" y="16" width="8" height="20" rx="2" fill="#FA9F5C"/>
|
||||
<circle cx="24" cy="10" r="2" fill="#EE7623"/>
|
||||
<path d="M22 12 Q22 14 24 16 M26 12 Q26 14 24 16" stroke="#EE7623" stroke-width="1.2" fill="none" stroke-linecap="round"/>
|
||||
</svg>
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
const { class: className = 'w-12 h-12' } = Astro.props;
|
||||
---
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none" class={className} aria-hidden="true">
|
||||
<path d="M16 8 L8 24 L16 40" stroke="#EE7623" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M32 8 L40 24 L32 40" stroke="#EE7623" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M28 10 L20 38" stroke="#FA9F5C" stroke-width="2.5" fill="none" stroke-linecap="round"/>
|
||||
<path d="M11 28 L37 28" stroke="#FA9F5C" stroke-width="2.5" fill="none" stroke-linecap="round"/>
|
||||
<rect x="6" y="6" width="3" height="3" fill="#FFCBA1"/>
|
||||
<rect x="39" y="6" width="3" height="3" fill="#FFCBA1"/>
|
||||
<rect x="6" y="39" width="3" height="3" fill="#FFCBA1"/>
|
||||
<rect x="39" y="39" width="3" height="3" fill="#FFCBA1"/>
|
||||
</svg>
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
const { class: className = 'w-12 h-12' } = Astro.props;
|
||||
---
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none" class={className} aria-hidden="true">
|
||||
<circle cx="24" cy="24" r="18" fill="none" stroke="#EE7623" stroke-width="2"/>
|
||||
<ellipse cx="24" cy="24" rx="8" ry="18" fill="none" stroke="#EE7623" stroke-width="1.5"/>
|
||||
<path d="M6 24 L42 24" stroke="#EE7623" stroke-width="1.5"/>
|
||||
<path d="M9 14 Q24 18 39 14" stroke="#FA9F5C" stroke-width="1.5" fill="none"/>
|
||||
<path d="M9 34 Q24 30 39 34" stroke="#FA9F5C" stroke-width="1.5" fill="none"/>
|
||||
<circle cx="24" cy="24" r="2" fill="#EE7623"/>
|
||||
</svg>
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
const { class: className = 'w-12 h-12' } = Astro.props;
|
||||
---
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none" class={className} aria-hidden="true">
|
||||
<rect x="8" y="14" width="26" height="20" rx="2" fill="#EE7623"/>
|
||||
<path d="M34 18 L38 18 Q42 18 42 22 Q42 26 38 26 L34 26" stroke="#EE7623" stroke-width="2.5" fill="none" stroke-linecap="round"/>
|
||||
<path d="M34 22 L38 22 Q40 22 40 24" stroke="#FA9F5C" stroke-width="1.5" fill="none" stroke-linecap="round"/>
|
||||
<path d="M21 8 L21 14" stroke="#FA9F5C" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M15 8 L15 14" stroke="#FA9F5C" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M27 8 L27 14" stroke="#FA9F5C" stroke-width="2" stroke-linecap="round"/>
|
||||
<rect x="12" y="20" width="6" height="2" fill="#FAF6EE" rx="1"/>
|
||||
<rect x="12" y="24" width="10" height="2" fill="#FAF6EE" rx="1" opacity="0.7"/>
|
||||
<rect x="12" y="28" width="8" height="2" fill="#FAF6EE" rx="1" opacity="0.5"/>
|
||||
</svg>
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
const { class: className = 'w-12 h-12' } = Astro.props;
|
||||
---
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none" class={className} aria-hidden="true">
|
||||
<circle cx="24" cy="32" r="6" fill="#EE7623"/>
|
||||
<path d="M24 26 L24 18" stroke="#EE7623" stroke-width="2.5" stroke-linecap="round"/>
|
||||
<path d="M16 8 L16 18" stroke="#FA9F5C" stroke-width="2.5" stroke-linecap="round"/>
|
||||
<path d="M32 8 L32 18" stroke="#FA9F5C" stroke-width="2.5" stroke-linecap="round"/>
|
||||
<path d="M14 18 Q24 12 34 18" stroke="#EE7623" stroke-width="2.5" fill="none" stroke-linecap="round"/>
|
||||
<path d="M14 18 L34 18" stroke="#EE7623" stroke-width="2.5" stroke-linecap="round"/>
|
||||
<rect x="11" y="6" width="4" height="4" fill="#FFCBA1" rx="0.5"/>
|
||||
<rect x="17" y="6" width="4" height="4" fill="#FFCBA1" rx="0.5"/>
|
||||
<rect x="27" y="6" width="4" height="4" fill="#FFCBA1" rx="0.5"/>
|
||||
<rect x="33" y="6" width="4" height="4" fill="#FFCBA1" rx="0.5"/>
|
||||
<path d="M19 32 Q19 36 22 38" stroke="#FA9F5C" stroke-width="1.5" fill="none" stroke-linecap="round"/>
|
||||
<path d="M29 32 Q29 36 26 38" stroke="#FA9F5C" stroke-width="1.5" fill="none" stroke-linecap="round"/>
|
||||
</svg>
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
const { class: className = 'w-12 h-12' } = Astro.props;
|
||||
---
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none" class={className} aria-hidden="true">
|
||||
<rect x="8" y="12" width="14" height="10" rx="1" fill="#EE7623"/>
|
||||
<rect x="26" y="12" width="14" height="10" rx="1" fill="#EE7623"/>
|
||||
<rect x="8" y="26" width="14" height="10" rx="1" fill="#FA9F5C"/>
|
||||
<rect x="26" y="26" width="14" height="10" rx="1" fill="#FA9F5C"/>
|
||||
<circle cx="15" cy="17" r="1.5" fill="#FAF6EE"/>
|
||||
<circle cx="33" cy="17" r="1.5" fill="#FAF6EE"/>
|
||||
<circle cx="15" cy="31" r="1.5" fill="#FAF6EE"/>
|
||||
<circle cx="33" cy="31" r="1.5" fill="#FAF6EE"/>
|
||||
<path d="M22 17 L22 31" stroke="#FFCBA1" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M15 22 L33 22" stroke="#FFCBA1" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
const { class: className = 'w-12 h-12' } = Astro.props;
|
||||
---
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none" class={className} aria-hidden="true">
|
||||
<path d="M10 14 L10 38 L38 38 L38 18 L30 10 L10 14 Z" fill="#EE7623" stroke="#EE7623" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M30 10 L30 18 L38 18" fill="none" stroke="#FAF6EE" stroke-width="2" stroke-linejoin="round"/>
|
||||
<rect x="14" y="22" width="20" height="2" fill="#FAF6EE" rx="1"/>
|
||||
<rect x="14" y="26" width="14" height="2" fill="#FAF6EE" rx="1" opacity="0.7"/>
|
||||
<rect x="14" y="30" width="10" height="2" fill="#FAF6EE" rx="1" opacity="0.5"/>
|
||||
<path d="M32 28 L40 22 L40 32 L32 36 Z" fill="#FA9F5C" stroke="#FA9F5C" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
<circle cx="36" cy="29" r="1.2" fill="#FAF6EE"/>
|
||||
</svg>
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
const { class: className = 'w-12 h-12' } = Astro.props;
|
||||
---
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none" class={className} aria-hidden="true">
|
||||
<rect x="10" y="20" width="28" height="20" rx="2" fill="none" stroke="#EE7623" stroke-width="2.5"/>
|
||||
<path d="M18 20 L18 14 Q18 10 22 10 L26 10 Q30 10 30 14 L30 20" fill="none" stroke="#EE7623" stroke-width="2.5" stroke-linejoin="round"/>
|
||||
<circle cx="24" cy="30" r="2.5" fill="#FA9F5C"/>
|
||||
<path d="M24 32.5 L24 36" stroke="#FA9F5C" stroke-width="2" stroke-linecap="round"/>
|
||||
<rect x="8" y="38" width="32" height="2" fill="#FFCBA1" rx="1"/>
|
||||
</svg>
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
const { class: className = 'w-12 h-12' } = Astro.props;
|
||||
---
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none" class={className} aria-hidden="true">
|
||||
<rect x="8" y="14" width="22" height="20" rx="1" fill="#EE7623"/>
|
||||
<path d="M30 14 L40 14 L40 34 L30 34" fill="none" stroke="#EE7623" stroke-width="2.5"/>
|
||||
<path d="M30 14 L30 34" stroke="#EE7623" stroke-width="2.5"/>
|
||||
<path d="M34 18 L40 18 M34 22 L40 22 M34 26 L40 26" stroke="#FA9F5C" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M12 20 L20 20 M12 24 L26 24 M12 28 L22 28" stroke="#FAF6EE" stroke-width="1.8" stroke-linecap="round"/>
|
||||
<path d="M22 30 L26 26 M26 30 L22 26" stroke="#FFCBA1" stroke-width="1.8" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="10" r="3" fill="#FA9F5C"/>
|
||||
<path d="M32 8 L32 12 M30 10 L34 10" stroke="#FAF6EE" stroke-width="1.2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
const { class: className = 'w-12 h-12' } = Astro.props;
|
||||
---
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none" class={className} aria-hidden="true">
|
||||
<rect x="8" y="10" width="32" height="28" rx="3" fill="none" stroke="#EE7623" stroke-width="2.5"/>
|
||||
<circle cx="24" cy="24" r="8" fill="none" stroke="#EE7623" stroke-width="2.5"/>
|
||||
<path d="M16 14 L24 22 L32 14" stroke="#FA9F5C" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<rect x="11" y="6" width="3" height="3" fill="#FFCBA1" rx="0.5"/>
|
||||
<rect x="14" y="6" width="3" height="3" fill="#FFCBA1" rx="0.5"/>
|
||||
<rect x="31" y="6" width="3" height="3" fill="#FFCBA1" rx="0.5"/>
|
||||
<rect x="34" y="6" width="3" height="3" fill="#FFCBA1" rx="0.5"/>
|
||||
<rect x="6" y="11" width="3" height="3" fill="#FA9F5C" rx="0.5"/>
|
||||
<rect x="6" y="14" width="3" height="3" fill="#FA9F5C" rx="0.5"/>
|
||||
<rect x="39" y="31" width="3" height="3" fill="#FA9F5C" rx="0.5"/>
|
||||
<rect x="39" y="34" width="3" height="3" fill="#FA9F5C" rx="0.5"/>
|
||||
</svg>
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
const { class: className = '' } = Astro.props;
|
||||
---
|
||||
<svg viewBox="0 0 1000 500" xmlns="http://www.w3.org/2000/svg" class={className} aria-hidden="true">
|
||||
<defs>
|
||||
<linearGradient id="mapGrad" x1="0" x2="0" y1="0" y2="1">
|
||||
<stop offset="0" stop-color="#EE7623" stop-opacity="0.05"/>
|
||||
<stop offset="1" stop-color="#EE7623" stop-opacity="0.12"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<path
|
||||
d="M 130,140 Q 90,180 110,230 Q 90,290 140,330 Q 180,360 230,340 Q 280,330 290,290 Q 320,250 300,210 Q 280,160 230,140 Q 180,120 130,140 Z"
|
||||
fill="url(#mapGrad)" stroke="var(--color-hds-naranja)" stroke-width="1.5" stroke-dasharray="4 3" opacity="0.5"
|
||||
/>
|
||||
<path
|
||||
d="M 280,180 Q 320,150 380,160 Q 440,150 470,180 Q 480,210 460,240 Q 420,270 380,260 Q 330,250 300,230 Q 270,210 280,180 Z"
|
||||
fill="url(#mapGrad)" stroke="var(--color-hds-naranja)" stroke-width="1.5" stroke-dasharray="4 3" opacity="0.5"
|
||||
/>
|
||||
<path
|
||||
d="M 470,200 Q 510,180 560,190 Q 610,200 620,240 Q 600,280 550,290 Q 500,290 470,260 Q 450,230 470,200 Z"
|
||||
fill="url(#mapGrad)" stroke="var(--color-hds-naranja)" stroke-width="1.5" stroke-dasharray="4 3" opacity="0.5"
|
||||
/>
|
||||
<path
|
||||
d="M 620,290 Q 670,300 720,330 Q 770,360 800,400 Q 820,440 780,460 Q 730,470 680,440 Q 640,410 620,370 Q 600,330 620,290 Z"
|
||||
fill="url(#mapGrad)" stroke="var(--color-hds-naranja)" stroke-width="1.5" stroke-dasharray="4 3" opacity="0.5"
|
||||
/>
|
||||
|
||||
<g>
|
||||
<circle cx="170" cy="240" r="8" fill="var(--color-hds-naranja)">
|
||||
<animate attributeName="r" values="6;10;6" dur="2.5s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" values="0.4;0;0.4" dur="2.5s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<circle cx="170" cy="240" r="5" fill="var(--color-hds-naranja)"/>
|
||||
<text x="170" y="220" text-anchor="middle" font-size="14" font-weight="600" fill="var(--hds-fg)">Canadá</text>
|
||||
</g>
|
||||
|
||||
<g>
|
||||
<circle cx="370" cy="210" r="8" fill="var(--color-hds-naranja)">
|
||||
<animate attributeName="r" values="6;10;6" dur="2.5s" begin="0.4s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" values="0.4;0;0.4" dur="2.5s" begin="0.4s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<circle cx="370" cy="210" r="5" fill="var(--color-hds-naranja)"/>
|
||||
<text x="370" y="190" text-anchor="middle" font-size="14" font-weight="600" fill="var(--hds-fg)">USA</text>
|
||||
</g>
|
||||
|
||||
<g>
|
||||
<circle cx="540" cy="230" r="8" fill="var(--color-hds-naranja)">
|
||||
<animate attributeName="r" values="6;10;6" dur="2.5s" begin="0.8s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" values="0.4;0;0.4" dur="2.5s" begin="0.8s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<circle cx="540" cy="230" r="5" fill="var(--color-hds-naranja)"/>
|
||||
<text x="540" y="210" text-anchor="middle" font-size="14" font-weight="600" fill="var(--hds-fg)">Alemania</text>
|
||||
</g>
|
||||
|
||||
<g>
|
||||
<circle cx="720" cy="390" r="10" fill="var(--color-hds-naranja)">
|
||||
<animate attributeName="r" values="8;14;8" dur="2.5s" begin="1.2s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" values="0.4;0;0.4" dur="2.5s" begin="1.2s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<circle cx="720" cy="390" r="6" fill="var(--color-hds-naranja)"/>
|
||||
<circle cx="720" cy="390" r="2.5" fill="white"/>
|
||||
<text x="720" y="370" text-anchor="middle" font-size="14" font-weight="600" fill="var(--hds-fg)">Uruguay</text>
|
||||
<text x="720" y="425" text-anchor="middle" font-size="10" fill="var(--hds-fg-muted)">Sede operativa</text>
|
||||
</g>
|
||||
</svg>
|
||||
Reference in New Issue
Block a user