feat: Awwwards transformation Phases 1-6
Phase 1 — Design System: - Glassmorphism tokens (--glass-bg, --glass-border, --blur-glass) - Gradient tokens (--hds-gradient-hero, --hds-gradient-section) - Motion tokens (--ease-spring, --ease-out-expo) - .glass-card component class with hover uplift + mouse-tracking glow - .noise-overlay: SVG feTurbulence texture at 0.025 opacity - Buttons with spring easing, prefers-reduced-motion support Phase 2 — Preloader + Hero: - Preloader.astro: vanilla JS logo fade-in + loading bar + slide-up - Uses sessionStorage to skip on repeat visits - Hero.astro: full-bleed 100dvh, GSAP timeline, glass ring decoration - Parallax floating SVG shapes (sine yoyo animation) - i18n: hero.scrollDown key Phase 3 — 'Por qué elegirnos' Bento Grid: - DifferenceCards.astro: asymmetric 3-column bento grid - Glass cards with mouse-tracking glow (--mouse-x/--mouse-y) - GSAP ScrollTrigger stagger reveal (opacity 0→1, y: 40→0) Phase 4 — Pricing + Process: - planes.astro: 5 visible + 4 in details accordion - Empresarial as hero card, price annual only (no toggle) - ProcessSteps.astro: timeline + ScrollTrigger progress bar Phase 5 — Global Polish: - LenisScroll.astro: smooth scroll (duration 1.2, cubic easing) - GSAP ticker sync, noise overlay in BaseLayout Phase 6 — Deploy: - deploy.sh: auto-detects repo path, auto-builds if stale - Relative paths instead of hardcoded /root/opencode/development
This commit is contained in:
Generated
+15
@@ -14,8 +14,10 @@
|
|||||||
"@fontsource/mulish": "^5.1.0",
|
"@fontsource/mulish": "^5.1.0",
|
||||||
"@fontsource/quicksand": "^5.2.10",
|
"@fontsource/quicksand": "^5.2.10",
|
||||||
"@keystatic/core": "^0.5.0",
|
"@keystatic/core": "^0.5.0",
|
||||||
|
"@studio-freight/lenis": "^1.0.42",
|
||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
"astro": "^5.0.0",
|
"astro": "^5.0.0",
|
||||||
|
"gsap": "^3.15.0",
|
||||||
"sharp": "^0.33.5",
|
"sharp": "^0.33.5",
|
||||||
"tailwindcss": "^4.0.0"
|
"tailwindcss": "^4.0.0"
|
||||||
},
|
},
|
||||||
@@ -4004,6 +4006,13 @@
|
|||||||
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@studio-freight/lenis": {
|
||||||
|
"version": "1.0.42",
|
||||||
|
"resolved": "https://registry.npmjs.org/@studio-freight/lenis/-/lenis-1.0.42.tgz",
|
||||||
|
"integrity": "sha512-HJAGf2DeM+BTvKzHv752z6Z7zy6bA643nZM7W88Ft9tnw2GsJSp6iJ+3cekjyMIWH+cloL2U9X82dKXgdU8kPg==",
|
||||||
|
"deprecated": "The '@studio-freight/lenis' package has been renamed to 'lenis'. Please update your dependencies: npm install lenis and visit the documentation: https://www.npmjs.com/package/lenis",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@swc/helpers": {
|
"node_modules/@swc/helpers": {
|
||||||
"version": "0.5.23",
|
"version": "0.5.23",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz",
|
||||||
@@ -6664,6 +6673,12 @@
|
|||||||
"graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
|
"graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/gsap": {
|
||||||
|
"version": "3.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.15.0.tgz",
|
||||||
|
"integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==",
|
||||||
|
"license": "Standard 'no charge' license: https://gsap.com/standard-license."
|
||||||
|
},
|
||||||
"node_modules/h3": {
|
"node_modules/h3": {
|
||||||
"version": "1.15.11",
|
"version": "1.15.11",
|
||||||
"resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz",
|
"resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz",
|
||||||
|
|||||||
@@ -18,8 +18,10 @@
|
|||||||
"@fontsource/mulish": "^5.1.0",
|
"@fontsource/mulish": "^5.1.0",
|
||||||
"@fontsource/quicksand": "^5.2.10",
|
"@fontsource/quicksand": "^5.2.10",
|
||||||
"@keystatic/core": "^0.5.0",
|
"@keystatic/core": "^0.5.0",
|
||||||
|
"@studio-freight/lenis": "^1.0.42",
|
||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
"astro": "^5.0.0",
|
"astro": "^5.0.0",
|
||||||
|
"gsap": "^3.15.0",
|
||||||
"sharp": "^0.33.5",
|
"sharp": "^0.33.5",
|
||||||
"tailwindcss": "^4.0.0"
|
"tailwindcss": "^4.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
+15
-7
@@ -1,24 +1,32 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
DIST="/root/opencode/development/hostingdelsur.net/dist"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO="$(dirname "$SCRIPT_DIR")"
|
||||||
|
DIST="$REPO/dist"
|
||||||
REMOTE="server1"
|
REMOTE="server1"
|
||||||
REMOTE_PATH="/home/hostingd/public_html"
|
REMOTE_PATH="/home/hostingd/public_html"
|
||||||
LOCAL_USER="root"
|
|
||||||
|
|
||||||
echo "=== Deploy hostingdelsur.net (rsync) ==="
|
echo "=== Deploy hostingdelsur.net (rsync) ==="
|
||||||
echo "Local: $DIST"
|
echo "Repo: $REPO"
|
||||||
|
echo "Dist: $DIST"
|
||||||
echo "Remote: $REMOTE:$REMOTE_PATH"
|
echo "Remote: $REMOTE:$REMOTE_PATH"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# Build if dist doesn't exist or is stale
|
||||||
|
if [ ! -d "$DIST" ] || [ "$REPO/package.json" -nt "$DIST" ]; then
|
||||||
|
echo "→ Building..."
|
||||||
|
npm run build --prefix "$REPO"
|
||||||
|
fi
|
||||||
|
|
||||||
# Copy PHP endpoints into dist (they don't go through Astro)
|
# Copy PHP endpoints into dist (they don't go through Astro)
|
||||||
mkdir -p "$DIST/api"
|
mkdir -p "$DIST/api"
|
||||||
cp /root/opencode/development/hostingdelsur.net/src/forms/contact.php "$DIST/api/contact.php"
|
cp "$REPO/src/forms/contact.php" "$DIST/api/contact.php"
|
||||||
cp /root/opencode/development/hostingdelsur.net/src/forms/deploy-webhook.php "$DIST/api/deploy-webhook.php"
|
cp "$REPO/src/forms/deploy-webhook.php" "$DIST/api/deploy-webhook.php"
|
||||||
cp /root/opencode/development/hostingdelsur.net/src/forms/api-htaccess "$DIST/api/.htaccess"
|
cp "$REPO/src/forms/api-htaccess" "$DIST/api/.htaccess"
|
||||||
|
|
||||||
# Copy root .htaccess (HTTPS forzado, security headers)
|
# Copy root .htaccess (HTTPS forzado, security headers)
|
||||||
cp /root/opencode/development/hostingdelsur.net/scripts/htaccess.conf "$DIST/.htaccess"
|
cp "$REPO/scripts/htaccess.conf" "$DIST/.htaccess"
|
||||||
|
|
||||||
# cPanel recognizes 404.shtml specifically (Server-Side Includes)
|
# cPanel recognizes 404.shtml specifically (Server-Side Includes)
|
||||||
# Astro generates 404.html — duplicate as .shtml for cPanel
|
# Astro generates 404.html — duplicate as .shtml for cPanel
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
---
|
---
|
||||||
|
// "Por qué elegirnos" — Bento grid with glass cards + magnetic hover glow
|
||||||
|
// Phase 3 — Organic Fluidity Premium
|
||||||
import SectionEyebrow from './SectionEyebrow.astro';
|
import SectionEyebrow from './SectionEyebrow.astro';
|
||||||
import type { TranslationKey } from '@/i18n/ui';
|
import type { TranslationKey } from '@/i18n/ui';
|
||||||
import MateIcon from './icons/MateIcon.astro';
|
import MateIcon from './icons/MateIcon.astro';
|
||||||
@@ -10,15 +12,23 @@ interface Props {
|
|||||||
}
|
}
|
||||||
const { t } = Astro.props;
|
const { t } = Astro.props;
|
||||||
|
|
||||||
const items = [
|
interface DiffItem {
|
||||||
{ Icon: MateIcon, titleKey: 'diff.support.title' as TranslationKey, bodyKey: 'diff.support.body' as TranslationKey },
|
Icon?: typeof MateIcon;
|
||||||
|
titleKey: TranslationKey;
|
||||||
|
bodyKey: TranslationKey;
|
||||||
|
hero?: boolean;
|
||||||
|
area?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const items: DiffItem[] = [
|
||||||
|
{ Icon: MateIcon, titleKey: 'diff.support.title' as TranslationKey, bodyKey: 'diff.support.body' as TranslationKey, hero: true },
|
||||||
{ Icon: SovereigntyIcon, titleKey: 'diff.sovereignty.title' as TranslationKey, bodyKey: 'diff.sovereignty.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 },
|
{ 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);">
|
<section class="py-24 md:py-32" style="background: var(--hds-bg-soft);" id="por-que-elegirnos">
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<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 reveal">
|
<div class="max-w-3xl mb-16 md:mb-20 reveal">
|
||||||
<SectionEyebrow text={t('diff.eyebrow')} />
|
<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);">
|
<h2 class="font-display leading-tight mt-5" style="font-size: clamp(2rem, 5vw, 3.5rem); color: var(--hds-fg);">
|
||||||
{t('diff.title')}
|
{t('diff.title')}
|
||||||
@@ -28,20 +38,116 @@ const items = [
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 stagger">
|
<!-- Bento Grid: asymmetric 3-column layout -->
|
||||||
{items.map((item) => (
|
<div class="bento-grid">
|
||||||
<article class="service-card">
|
{items.map((item, i) => (
|
||||||
|
<article
|
||||||
|
class:list={[
|
||||||
|
'glass-card bento-item p-8 md:p-10 flex flex-col',
|
||||||
|
{ 'bento-hero md:col-span-2 md:row-span-2': item.hero },
|
||||||
|
]}
|
||||||
|
data-bento-index={i}
|
||||||
|
>
|
||||||
<div class="icon-circle mb-6">
|
<div class="icon-circle mb-6">
|
||||||
<item.Icon class="w-7 h-7" />
|
{item.Icon && <item.Icon class="w-7 h-7" />}
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-xl md:text-2xl font-semibold mb-3" style="color: var(--hds-fg);">
|
<h3
|
||||||
|
class:list={[
|
||||||
|
'mb-4',
|
||||||
|
{ 'text-2xl md:text-3xl': item.hero, 'text-xl md:text-2xl': !item.hero },
|
||||||
|
]}
|
||||||
|
style="color: var(--hds-fg); font-weight: 600;"
|
||||||
|
>
|
||||||
{t(item.titleKey)}
|
{t(item.titleKey)}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-[15px] leading-relaxed" style="color: var(--hds-fg-soft);">
|
<p
|
||||||
|
class:list={[
|
||||||
|
'leading-relaxed',
|
||||||
|
{ 'text-base md:text-lg': item.hero, 'text-[15px]': !item.hero },
|
||||||
|
]}
|
||||||
|
style="color: var(--hds-fg-soft);"
|
||||||
|
>
|
||||||
{t(item.bodyKey)}
|
{t(item.bodyKey)}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{item.hero && (
|
||||||
|
<div class="mt-6 pt-6 border-t flex items-center gap-4" style="border-color: var(--glass-border);">
|
||||||
|
<div class="flex -space-x-2">
|
||||||
|
<div class="w-8 h-8 rounded-full flex items-center justify-center text-[10px] font-bold text-white" style="background: var(--color-hds-naranja);">M</div>
|
||||||
|
<div class="w-8 h-8 rounded-full flex items-center justify-center text-[10px] font-bold" style="background: var(--color-hds-coral); color: var(--hds-tinta);">+2</div>
|
||||||
|
</div>
|
||||||
|
<span class="text-sm" style="color: var(--hds-fg-soft);">
|
||||||
|
{t('diff.eyebrow') === 'La diferencia' ? 'Equipo de 3 ingenieros SRE' : 'Team of 3 SRE engineers'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</article>
|
</article>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import gsap from 'gsap';
|
||||||
|
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||||
|
|
||||||
|
gsap.registerPlugin(ScrollTrigger);
|
||||||
|
|
||||||
|
// Mouse-tracking glow on glass cards
|
||||||
|
document.querySelectorAll('.bento-item').forEach((card) => {
|
||||||
|
card.addEventListener('mousemove', (e) => {
|
||||||
|
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
||||||
|
const x = ((e.clientX - rect.left) / rect.width) * 100;
|
||||||
|
const y = ((e.clientY - rect.top) / rect.height) * 100;
|
||||||
|
(e.currentTarget as HTMLElement).style.setProperty('--mouse-x', `${x}%`);
|
||||||
|
(e.currentTarget as HTMLElement).style.setProperty('--mouse-y', `${y}%`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ScrollTrigger stagger reveal
|
||||||
|
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
if (!prefersReduced) {
|
||||||
|
gsap.from('.bento-item', {
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: '.bento-grid',
|
||||||
|
start: 'top 80%',
|
||||||
|
toggleActions: 'play none none none',
|
||||||
|
},
|
||||||
|
opacity: 0,
|
||||||
|
y: 40,
|
||||||
|
duration: 0.7,
|
||||||
|
stagger: 0.12,
|
||||||
|
ease: 'power3.out',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.bento-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.bento-grid {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
grid-auto-rows: auto;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bento-item {
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bento-hero {
|
||||||
|
min-height: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.bento-hero {
|
||||||
|
min-height: 380px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const faqs = lang === 'en' ? [
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="lg:col-span-8 reveal">
|
<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);">
|
<div class="glass-card p-6 md:p-8">
|
||||||
<FAQAccordion items={faqs} />
|
<FAQAccordion items={faqs} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+186
-31
@@ -8,50 +8,90 @@ interface Props {
|
|||||||
|
|
||||||
const { t, whatsappUrl } = Astro.props;
|
const { t, whatsappUrl } = Astro.props;
|
||||||
---
|
---
|
||||||
<section class="relative overflow-hidden pt-20 pb-28 md:pt-32 md:pb-40">
|
<section
|
||||||
<div class="absolute inset-0 particle-grid" aria-hidden="true"></div>
|
id="hero"
|
||||||
<div class="absolute inset-0" style="background: linear-gradient(180deg, transparent 0%, var(--hds-bg) 100%);" aria-hidden="true"></div>
|
class="relative overflow-hidden flex items-center"
|
||||||
|
style="min-height: 100dvh; background: var(--hds-bg);"
|
||||||
|
>
|
||||||
|
<!-- Atmospheric radial gradient -->
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 pointer-events-none"
|
||||||
|
style="background: var(--hds-gradient-hero);"
|
||||||
|
aria-hidden="true"
|
||||||
|
></div>
|
||||||
|
|
||||||
<div class="absolute top-32 right-8 md:right-16 opacity-50" aria-hidden="true">
|
<!-- Floating decorative shapes (parallax) -->
|
||||||
<svg width="120" height="120" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
<div class="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
|
||||||
<rect x="0" y="0" width="12" height="12" fill="#EE7623" rx="1"/>
|
<div id="hero-float-top" class="absolute opacity-30" style="top: 15%; right: 12%;">
|
||||||
<rect x="14" y="0" width="12" height="12" fill="#FA9F5C" rx="1"/>
|
<svg width="120" height="120" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||||
<rect x="28" y="0" width="12" height="12" fill="#FFCBA1" rx="1"/>
|
<rect x="0" y="0" width="12" height="12" fill="#EE7623" rx="1"/>
|
||||||
<rect x="0" y="14" width="12" height="12" fill="#FA9F5C" rx="1"/>
|
<rect x="14" y="0" 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="0" width="12" height="12" fill="#FFCBA1" rx="1"/>
|
||||||
<rect x="28" y="14" width="12" height="12" fill="#FA9F5C" rx="1"/>
|
<rect x="0" 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="14" width="12" height="12" fill="#EE7623" rx="1"/>
|
||||||
<rect x="14" y="28" width="12" height="12" fill="#FA9F5C" rx="1"/>
|
<rect x="28" y="14" width="12" height="12" fill="#FA9F5C" rx="1"/>
|
||||||
<rect x="28" y="28" width="12" height="12" fill="#EE7623" rx="1"/>
|
<rect x="0" y="28" width="12" height="12" fill="#FFCBA1" rx="1"/>
|
||||||
</svg>
|
<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 id="hero-float-mid" class="absolute opacity-25" style="top: 55%; left: 8%;">
|
||||||
|
<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 id="hero-float-bot" class="absolute opacity-20" style="bottom: 20%; right: 20%;">
|
||||||
|
<svg width="64" height="64" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="0" y="0" width="8" height="8" fill="#EE7623" rx="1"/>
|
||||||
|
<rect x="10" y="0" width="8" height="8" fill="#FFCBA1" rx="1"/>
|
||||||
|
<rect x="0" y="10" width="8" height="8" fill="#FA9F5C" rx="1"/>
|
||||||
|
<rect x="10" y="10" width="8" height="8" fill="#EE7623" rx="1"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="absolute bottom-12 left-8 md:left-16 opacity-40" aria-hidden="true">
|
<!-- Decorative glass ring (subtle) -->
|
||||||
<svg width="80" height="80" viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg">
|
<div
|
||||||
<rect x="0" y="0" width="10" height="10" fill="#FA9F5C" rx="1"/>
|
id="hero-glass-ring"
|
||||||
<rect x="12" y="0" width="10" height="10" fill="#EE7623" rx="1"/>
|
class="absolute pointer-events-none rounded-full opacity-0"
|
||||||
<rect x="0" y="12" width="10" height="10" fill="#FFCBA1" rx="1"/>
|
style="width: min(600px, 80vw); height: min(600px, 80vw);
|
||||||
<rect x="12" y="12" width="10" height="10" fill="#FA9F5C" rx="1"/>
|
top: 50%; left: 70%; transform: translate(-50%, -50%);
|
||||||
<rect x="0" y="24" width="10" height="10" fill="#EE7623" rx="1"/>
|
border: 1px solid var(--glass-border);
|
||||||
<rect x="12" y="24" width="10" height="10" fill="#FFCBA1" rx="1"/>
|
background: var(--glass-bg);
|
||||||
</svg>
|
backdrop-filter: blur(40px);
|
||||||
</div>
|
-webkit-backdrop-filter: blur(40px);"
|
||||||
|
aria-hidden="true"
|
||||||
|
></div>
|
||||||
|
|
||||||
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<!-- Content -->
|
||||||
|
<div class="relative z-10 w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-32 md:py-40">
|
||||||
<div class="max-w-4xl">
|
<div class="max-w-4xl">
|
||||||
<div class="eyebrow mb-7">
|
<div id="hero-eyebrow" class="eyebrow mb-7 opacity-0">
|
||||||
<span>{t('hero.eyebrow')}</span>
|
<span>{t('hero.eyebrow')}</span>
|
||||||
</div>
|
</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;">
|
<h1
|
||||||
|
id="hero-headline"
|
||||||
|
class="font-display leading-[0.98] mb-8 opacity-0"
|
||||||
|
style="font-size: clamp(2.75rem, 7vw, 5.5rem); color: var(--hds-fg); letter-spacing: -0.025em; font-weight: 400;"
|
||||||
|
>
|
||||||
{t('hero.title')}
|
{t('hero.title')}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p class="text-lg md:text-xl leading-relaxed mb-10 max-w-2xl" style="color: var(--hds-fg-soft);">
|
<p
|
||||||
|
id="hero-body"
|
||||||
|
class="text-lg md:text-xl leading-relaxed mb-10 max-w-2xl opacity-0"
|
||||||
|
style="color: var(--hds-fg-soft);"
|
||||||
|
>
|
||||||
{t('hero.body')}
|
{t('hero.body')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="flex flex-col sm:flex-row gap-4 mb-14">
|
<div id="hero-ctas" class="flex flex-col sm:flex-row gap-4 mb-14 opacity-0">
|
||||||
<a href="/planes/" class="btn-primary">
|
<a href="/planes/" class="btn-primary">
|
||||||
{t('hero.ctaPrimary')}
|
{t('hero.ctaPrimary')}
|
||||||
</a>
|
</a>
|
||||||
@@ -60,7 +100,7 @@ const { t, whatsappUrl } = Astro.props;
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap items-center gap-x-8 gap-y-3 text-sm" style="color: var(--hds-fg-muted);">
|
<div id="hero-badges" class="flex flex-wrap items-center gap-x-8 gap-y-3 text-sm opacity-0" style="color: var(--hds-fg-muted);">
|
||||||
<span class="flex items-center gap-2">
|
<span class="flex items-center gap-2">
|
||||||
<span style="color: var(--color-hds-naranja);">●</span>
|
<span style="color: var(--color-hds-naranja);">●</span>
|
||||||
{t('hero.badge26')}
|
{t('hero.badge26')}
|
||||||
@@ -80,4 +120,119 @@ const { t, whatsappUrl } = Astro.props;
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Scroll indicator -->
|
||||||
|
<div id="hero-scroll-indicator" class="absolute bottom-8 left-1/2 -translate-x-1/2 opacity-0 pointer-events-none" aria-hidden="true">
|
||||||
|
<div class="flex flex-col items-center gap-2" style="color: var(--hds-fg-muted);">
|
||||||
|
<span class="text-[10px] uppercase tracking-[0.3em] font-semibold">{t('hero.scrollDown')}</span>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M12 5v14M19 12l-7 7-7-7"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import gsap from 'gsap';
|
||||||
|
|
||||||
|
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
|
||||||
|
if (!prefersReduced) {
|
||||||
|
const heroTimeline = gsap.timeline({ delay: 0.3 });
|
||||||
|
|
||||||
|
heroTimeline
|
||||||
|
.to('#hero-glass-ring', {
|
||||||
|
opacity: 1,
|
||||||
|
scale: 1.05,
|
||||||
|
duration: 1.4,
|
||||||
|
ease: 'power3.out',
|
||||||
|
})
|
||||||
|
.to('#hero-eyebrow', {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
duration: 0.6,
|
||||||
|
ease: 'power2.out',
|
||||||
|
}, '-=0.8')
|
||||||
|
.to('#hero-headline', {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
duration: 0.8,
|
||||||
|
ease: 'power3.out',
|
||||||
|
}, '-=0.1')
|
||||||
|
.to('#hero-body', {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
duration: 0.7,
|
||||||
|
ease: 'power2.out',
|
||||||
|
}, '-=0.2')
|
||||||
|
.to('#hero-ctas', {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
duration: 0.6,
|
||||||
|
ease: 'power2.out',
|
||||||
|
}, '-=0.3')
|
||||||
|
.to('#hero-badges', {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
duration: 0.5,
|
||||||
|
}, '-=0.4')
|
||||||
|
.to('#hero-scroll-indicator', {
|
||||||
|
opacity: 1,
|
||||||
|
duration: 0.6,
|
||||||
|
}, '-=0.2');
|
||||||
|
|
||||||
|
// Parallax floating shapes
|
||||||
|
gsap.to('#hero-float-top', {
|
||||||
|
y: -30,
|
||||||
|
x: -15,
|
||||||
|
duration: 3,
|
||||||
|
ease: 'sine.inOut',
|
||||||
|
repeat: -1,
|
||||||
|
yoyo: true,
|
||||||
|
});
|
||||||
|
gsap.to('#hero-float-mid', {
|
||||||
|
y: 25,
|
||||||
|
x: 12,
|
||||||
|
duration: 4,
|
||||||
|
ease: 'sine.inOut',
|
||||||
|
repeat: -1,
|
||||||
|
yoyo: true,
|
||||||
|
delay: 0.5,
|
||||||
|
});
|
||||||
|
gsap.to('#hero-float-bot', {
|
||||||
|
y: -20,
|
||||||
|
x: -10,
|
||||||
|
duration: 3.5,
|
||||||
|
ease: 'sine.inOut',
|
||||||
|
repeat: -1,
|
||||||
|
yoyo: true,
|
||||||
|
delay: 1,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Show everything without animation
|
||||||
|
document.querySelectorAll('#hero-eyebrow, #hero-headline, #hero-body, #hero-ctas, #hero-badges, #hero-scroll-indicator, #hero-glass-ring').forEach(el => {
|
||||||
|
(el as HTMLElement).style.opacity = '1';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#hero-eyebrow {
|
||||||
|
transform: translateY(16px);
|
||||||
|
}
|
||||||
|
#hero-headline {
|
||||||
|
transform: translateY(24px);
|
||||||
|
}
|
||||||
|
#hero-body {
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
#hero-ctas {
|
||||||
|
transform: translateY(16px);
|
||||||
|
}
|
||||||
|
#hero-badges {
|
||||||
|
transform: translateY(12px);
|
||||||
|
}
|
||||||
|
#hero-glass-ring {
|
||||||
|
transform: translate(-50%, -50%) scale(0.85);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
// Lenis smooth scroll — Phase 5
|
||||||
|
// Synced with GSAP ticker for scroll-driven animations
|
||||||
|
---
|
||||||
|
<script>
|
||||||
|
import Lenis from '@studio-freight/lenis';
|
||||||
|
import gsap from 'gsap';
|
||||||
|
|
||||||
|
const lenis = new Lenis({
|
||||||
|
duration: 1.2,
|
||||||
|
easing: (t: number) => 1 - Math.pow(1 - t, 3),
|
||||||
|
smoothWheel: true,
|
||||||
|
wheelMultiplier: 1,
|
||||||
|
touchMultiplier: 1.5,
|
||||||
|
});
|
||||||
|
|
||||||
|
function raf(time: number) {
|
||||||
|
lenis.raf(time);
|
||||||
|
requestAnimationFrame(raf);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(raf);
|
||||||
|
|
||||||
|
// Sync Lenis with GSAP ScrollTrigger
|
||||||
|
gsap.ticker.add((time: number) => {
|
||||||
|
lenis.raf(time * 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
gsap.ticker.lagSmoothing(0);
|
||||||
|
|
||||||
|
// Connect GSAP ScrollTrigger to Lenis scroll
|
||||||
|
const scrollTriggerConfig = {
|
||||||
|
onUpdate: (self: { direction: number; progress: number }) => {
|
||||||
|
// Lenis handles the actual scroll, GSAP reads from it
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
---
|
||||||
|
// Preloader: logo scale-in + loading bar + split-door reveal
|
||||||
|
// Phase 2 — Organic Fluidity Premium
|
||||||
|
---
|
||||||
|
<div id="preloader" class="fixed inset-0 z-[9998] flex flex-col items-center justify-center" style="background: var(--hds-bg);">
|
||||||
|
<div class="flex flex-col items-center gap-8">
|
||||||
|
<img
|
||||||
|
id="preloader-logo"
|
||||||
|
src="/logo/hds-color.svg"
|
||||||
|
alt="Hosting del Sur"
|
||||||
|
class="h-16 w-auto opacity-0"
|
||||||
|
/>
|
||||||
|
<div id="preloader-bar" class="w-48 h-[3px] rounded-full overflow-hidden" style="background: var(--hds-line);">
|
||||||
|
<div id="preloader-bar-fill" class="h-full" style="background: var(--color-hds-naranja); width: 0%; border-radius: inherit;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script is:inline>
|
||||||
|
(function() {
|
||||||
|
var hasVisited = sessionStorage.getItem('hds-preloaded');
|
||||||
|
|
||||||
|
if (hasVisited) {
|
||||||
|
var el = document.getElementById('preloader');
|
||||||
|
if (el) el.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionStorage.setItem('hds-preloaded', '1');
|
||||||
|
|
||||||
|
var logo = document.getElementById('preloader-logo');
|
||||||
|
var barFill = document.getElementById('preloader-bar-fill');
|
||||||
|
var preloader = document.getElementById('preloader');
|
||||||
|
|
||||||
|
if (!logo || !barFill || !preloader) return;
|
||||||
|
|
||||||
|
// Lightweight inline tween engine (avoids bundle dependency)
|
||||||
|
var start = performance.now();
|
||||||
|
var duration = 1800;
|
||||||
|
|
||||||
|
function easeOut(t) { return 1 - Math.pow(1 - t, 3); }
|
||||||
|
function easeInOut(t) { return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2; }
|
||||||
|
|
||||||
|
function step(time) {
|
||||||
|
var elapsed = time - start;
|
||||||
|
var progress = Math.min(elapsed / duration, 1);
|
||||||
|
|
||||||
|
// Phase 1 (0-28%): logo fade in + unblur
|
||||||
|
var p1 = Math.min(progress / 0.28, 1);
|
||||||
|
logo.style.opacity = easeOut(p1);
|
||||||
|
logo.style.filter = 'blur(' + ((1 - easeOut(p1)) * 8) + 'px)';
|
||||||
|
|
||||||
|
// Phase 2 (28%-72%): bar fill
|
||||||
|
var p2 = progress < 0.28 ? 0 : Math.min((progress - 0.28) / 0.44, 1);
|
||||||
|
barFill.style.width = (easeInOut(p2) * 100) + '%';
|
||||||
|
|
||||||
|
// Phase 3 (82%-100%): slide up
|
||||||
|
var p3 = progress < 0.82 ? 0 : Math.min((progress - 0.82) / 0.18, 1);
|
||||||
|
preloader.style.transform = 'translateY(' + (easeInOut(p3) * -100) + '%)';
|
||||||
|
|
||||||
|
if (progress < 1) {
|
||||||
|
requestAnimationFrame(step);
|
||||||
|
} else {
|
||||||
|
preloader.style.pointerEvents = 'none';
|
||||||
|
preloader.style.transform = 'translateY(-100%)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(step);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#preloader {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
#preloader-logo {
|
||||||
|
filter: blur(8px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
---
|
---
|
||||||
|
// "Cómo trabajamos" — Timeline with GSAP ScrollTrigger progress bar
|
||||||
|
// Phase 4 — Organic Fluidity Premium
|
||||||
import SectionEyebrow from './SectionEyebrow.astro';
|
import SectionEyebrow from './SectionEyebrow.astro';
|
||||||
import type { TranslationKey } from '@/i18n/ui';
|
import type { TranslationKey } from '@/i18n/ui';
|
||||||
|
import gsap from 'gsap';
|
||||||
|
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||||
|
|
||||||
|
gsap.registerPlugin(ScrollTrigger);
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
t: (key: TranslationKey) => string;
|
t: (key: TranslationKey) => string;
|
||||||
@@ -14,32 +20,133 @@ const steps = [
|
|||||||
{ num: '04', titleKey: 'process.step4.title' as TranslationKey, bodyKey: 'process.step4.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);">
|
<section class="py-24 md:py-32 relative overflow-hidden" style="background: var(--hds-bg-soft);" id="como-trabajamos">
|
||||||
|
<div class="absolute inset-0 pointer-events-none" style="background: var(--hds-gradient-section);" aria-hidden="true"></div>
|
||||||
|
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<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 reveal">
|
<div class="max-w-3xl mb-16 md:mb-20 reveal">
|
||||||
<SectionEyebrow text={t('process.eyebrow')} />
|
<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);">
|
<h2 class="font-display leading-tight mt-5" style="font-size: clamp(2rem, 5vw, 3.5rem); color: var(--hds-fg);">
|
||||||
{t('process.title')}
|
{t('process.title')}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ol class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-10 md:gap-8 relative stagger">
|
<!-- Timeline with progress bar -->
|
||||||
<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>
|
<div class="process-timeline">
|
||||||
|
<!-- Vertical progress bar track -->
|
||||||
|
<div class="process-progress-track" aria-hidden="true">
|
||||||
|
<div id="process-progress-fill" class="process-progress-fill"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{steps.map((step) => (
|
<div class="process-steps">
|
||||||
<li class="relative">
|
{steps.map((step, i) => (
|
||||||
<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">
|
<div id={`process-step-${i}`} class="process-step-wrap">
|
||||||
{step.num}
|
<article class="glass-card process-step-card p-8 md:p-10">
|
||||||
<span class="absolute -bottom-1 left-0 w-10 h-1" style="background: var(--color-hds-naranja);"></span>
|
<div class="flex items-start gap-6">
|
||||||
|
<div class="font-display leading-none flex-shrink-0" style="font-size: 3.5rem; color: var(--color-hds-naranja); font-weight: 500; opacity: 0.9;">
|
||||||
|
{step.num}
|
||||||
|
</div>
|
||||||
|
<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] md:text-base leading-relaxed" style="color: var(--hds-fg-soft);">
|
||||||
|
{t(step.bodyKey)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-xl md:text-2xl font-semibold mb-3" style="color: var(--hds-fg);">
|
))}
|
||||||
{t(step.titleKey)}
|
</div>
|
||||||
</h3>
|
</div>
|
||||||
<p class="text-[15px] leading-relaxed" style="color: var(--hds-fg-soft);">
|
|
||||||
{t(step.bodyKey)}
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ol>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import gsap from 'gsap';
|
||||||
|
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||||
|
|
||||||
|
gsap.registerPlugin(ScrollTrigger);
|
||||||
|
|
||||||
|
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
if (!prefersReduced) {
|
||||||
|
const steps = document.querySelectorAll('.process-step-card');
|
||||||
|
const progressFill = document.getElementById('process-progress-fill');
|
||||||
|
|
||||||
|
if (steps.length && progressFill) {
|
||||||
|
// Animate progress bar as user scrolls through the section
|
||||||
|
ScrollTrigger.create({
|
||||||
|
trigger: '.process-steps',
|
||||||
|
start: 'top 70%',
|
||||||
|
end: 'bottom bottom',
|
||||||
|
onUpdate: (self) => {
|
||||||
|
progressFill.style.setProperty('--progress', `${self.progress * 100}%`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stagger reveal each step card
|
||||||
|
gsap.from('.process-step-card', {
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: '.process-steps',
|
||||||
|
start: 'top 75%',
|
||||||
|
toggleActions: 'play none none none',
|
||||||
|
},
|
||||||
|
opacity: 0,
|
||||||
|
x: -30,
|
||||||
|
duration: 0.7,
|
||||||
|
stagger: 0.15,
|
||||||
|
ease: 'power3.out',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.process-timeline {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
max-width: 48rem;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-progress-track {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 3px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: var(--hds-line);
|
||||||
|
position: relative;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-progress-fill {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 40vh;
|
||||||
|
background: var(--color-hds-naranja);
|
||||||
|
border-radius: inherit;
|
||||||
|
clip-path: inset(0 0 calc(100% - var(--progress, 0%)) 0);
|
||||||
|
transition: clip-path 0.1s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-steps {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-step-card {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.process-timeline {
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.process-progress-track {
|
||||||
|
width: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const { href, title, description, Icon, borderColor = 'var(--color-hds-naranja)'
|
|||||||
---
|
---
|
||||||
<a
|
<a
|
||||||
href={href}
|
href={href}
|
||||||
class="service-card group flex flex-col h-full"
|
class="glass-card group flex flex-col h-full"
|
||||||
style={`border-top: 4px solid ${borderColor}; padding-top: 1.5rem;`}
|
style={`border-top: 4px solid ${borderColor}; padding-top: 1.5rem;`}
|
||||||
>
|
>
|
||||||
<div class="icon-circle mb-5" style="width: 48px; height: 48px;">
|
<div class="icon-circle mb-5" style="width: 48px; height: 48px;">
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const ui = {
|
|||||||
'hero.badgeUptime': 'Uptime 99.9%',
|
'hero.badgeUptime': 'Uptime 99.9%',
|
||||||
'hero.badgeSupport': 'Soporte humano',
|
'hero.badgeSupport': 'Soporte humano',
|
||||||
'hero.badgeLocation': 'Maldonado, Uruguay',
|
'hero.badgeLocation': 'Maldonado, Uruguay',
|
||||||
|
'hero.scrollDown': 'Deslizá',
|
||||||
'stats.label1': 'Años de experiencia',
|
'stats.label1': 'Años de experiencia',
|
||||||
'stats.sub1': 'Desde el 2000',
|
'stats.sub1': 'Desde el 2000',
|
||||||
'stats.label2': 'Proyectos hospedados',
|
'stats.label2': 'Proyectos hospedados',
|
||||||
@@ -80,6 +81,7 @@ export const ui = {
|
|||||||
'hero.badgeUptime': '99.9% uptime',
|
'hero.badgeUptime': '99.9% uptime',
|
||||||
'hero.badgeSupport': 'Human support',
|
'hero.badgeSupport': 'Human support',
|
||||||
'hero.badgeLocation': 'Maldonado, Uruguay',
|
'hero.badgeLocation': 'Maldonado, Uruguay',
|
||||||
|
'hero.scrollDown': 'Scroll',
|
||||||
'stats.label1': 'Years of experience',
|
'stats.label1': 'Years of experience',
|
||||||
'stats.sub1': 'Since 2000',
|
'stats.sub1': 'Since 2000',
|
||||||
'stats.label2': 'Projects hosted',
|
'stats.label2': 'Projects hosted',
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ const canonicalUrl = canonical ? `${siteUrl}${canonical}` : `${siteUrl}${Astro.u
|
|||||||
import '@/styles/global.css';
|
import '@/styles/global.css';
|
||||||
import Navbar from '@/components/Navbar.astro';
|
import Navbar from '@/components/Navbar.astro';
|
||||||
import Footer from '@/components/Footer.astro';
|
import Footer from '@/components/Footer.astro';
|
||||||
|
import Preloader from '@/components/Preloader.astro';
|
||||||
import WhatsappFab from '@/components/WhatsappFab.astro';
|
import WhatsappFab from '@/components/WhatsappFab.astro';
|
||||||
import BackToTop from '@/components/BackToTop.astro';
|
import BackToTop from '@/components/BackToTop.astro';
|
||||||
import ScrollReveal from '@/components/ScrollReveal.astro';
|
import ScrollReveal from '@/components/ScrollReveal.astro';
|
||||||
|
import LenisScroll from '@/components/LenisScroll.astro';
|
||||||
|
|
||||||
const orgJsonLd = {
|
const orgJsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
@@ -116,11 +118,14 @@ const localBusinessJsonLd = {
|
|||||||
<a href="#main" class="skip-link">
|
<a href="#main" class="skip-link">
|
||||||
{currentLang === 'en' ? 'Skip to content' : 'Saltar al contenido'}
|
{currentLang === 'en' ? 'Skip to content' : 'Saltar al contenido'}
|
||||||
</a>
|
</a>
|
||||||
|
<Preloader />
|
||||||
|
<div class="noise-overlay" aria-hidden="true"></div>
|
||||||
<Navbar t={t} lang={currentLang} />
|
<Navbar t={t} lang={currentLang} />
|
||||||
<main id="main">
|
<main id="main">
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
<Footer t={t} lang={currentLang} />
|
<Footer t={t} lang={currentLang} />
|
||||||
|
<LenisScroll />
|
||||||
<ScrollReveal />
|
<ScrollReveal />
|
||||||
<WhatsappFab lang={currentLang} />
|
<WhatsappFab lang={currentLang} />
|
||||||
<BackToTop />
|
<BackToTop />
|
||||||
|
|||||||
+163
-43
@@ -2,10 +2,9 @@
|
|||||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||||
import SectionEyebrow from '@/components/SectionEyebrow.astro';
|
import SectionEyebrow from '@/components/SectionEyebrow.astro';
|
||||||
import PricingTiersFeatured from '@/components/PricingTiersFeatured.astro';
|
import PricingTiersFeatured from '@/components/PricingTiersFeatured.astro';
|
||||||
import PricingGrid from '@/components/PricingGrid.astro';
|
|
||||||
import CloudDivider from '@/components/CloudDivider.astro';
|
import CloudDivider from '@/components/CloudDivider.astro';
|
||||||
import CtaFinal from '@/components/CtaFinal.astro';
|
import CtaFinal from '@/components/CtaFinal.astro';
|
||||||
import { PLANS, getFeaturedPlans, getOtherPlans, ADDITIONAL_PLANS } from '@/lib/pricing';
|
import { PLANS } from '@/lib/pricing';
|
||||||
import { getLangFromUrl, useTranslations } from '@/i18n/utils';
|
import { getLangFromUrl, useTranslations } from '@/i18n/utils';
|
||||||
|
|
||||||
const lang = getLangFromUrl(Astro.url);
|
const lang = getLangFromUrl(Astro.url);
|
||||||
@@ -13,8 +12,16 @@ const t = useTranslations(lang);
|
|||||||
const whatsappUrl = 'https://wa.me/59899812487';
|
const whatsappUrl = 'https://wa.me/59899812487';
|
||||||
const base = lang === 'en' ? '/en' : '';
|
const base = lang === 'en' ? '/en' : '';
|
||||||
|
|
||||||
const featured = getFeaturedPlans();
|
// 5 visible plans on home — Empresarial as hero
|
||||||
const other = getOtherPlans();
|
const VISIBLE_SLUGS = ['basico', 'institucional', 'empresarial', 'e-commerce', 'avanzado'];
|
||||||
|
const visible = PLANS.filter(p => VISIBLE_SLUGS.includes(p.slug));
|
||||||
|
const empresarial = PLANS.find(p => p.slug === 'empresarial');
|
||||||
|
const visibleWithoutHero = visible.filter(p => p.slug !== 'empresarial');
|
||||||
|
const advanced = PLANS.filter(p => !VISIBLE_SLUGS.includes(p.slug));
|
||||||
|
|
||||||
|
function monthly(annual: number): string {
|
||||||
|
return (annual / 12).toFixed(2);
|
||||||
|
}
|
||||||
---
|
---
|
||||||
<BaseLayout
|
<BaseLayout
|
||||||
title={lang === 'en' ? 'Hosting plans' : 'Planes de hosting'}
|
title={lang === 'en' ? 'Hosting plans' : 'Planes de hosting'}
|
||||||
@@ -23,66 +30,179 @@ const other = getOtherPlans();
|
|||||||
: 'Planes de hosting en USD, con cobro anual. Desde básico hasta corporativo HD, elegí el plan que se adapta a tu proyecto.'}
|
: '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}
|
lang={lang}
|
||||||
>
|
>
|
||||||
<section class="pt-20 pb-12 md:pt-28 md:pb-16 relative" style="background: var(--hds-bg-soft);">
|
<section class="pt-24 pb-16 md:pt-32 md:pb-20 relative overflow-hidden" style="background: var(--hds-bg-soft);">
|
||||||
<div class="absolute inset-0 particle-grid opacity-60" aria-hidden="true"></div>
|
<div class="absolute inset-0" style="background: var(--hds-gradient-hero);" aria-hidden="true"></div>
|
||||||
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||||
<SectionEyebrow text={lang === 'en' ? 'Hosting plans' : 'Planes de hosting'} />
|
<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);">
|
<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>.
|
{lang === 'en' ? 'Plans designed around your' : 'Planes pensados alrededor de tu'} <span style="color: var(--color-hds-naranja);">{lang === 'en' ? 'project' : 'proyecto'}</span>.
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-lg mt-5 max-w-2xl" style="color: var(--hds-fg-soft);">
|
<p class="text-lg mt-5 max-w-2xl mx-auto" style="color: var(--hds-fg-soft);">
|
||||||
{lang === 'en'
|
{lang === 'en'
|
||||||
? 'Prices in USD, billed annually. Features may vary depending on the project and server configuration.'
|
? 'Prices in USD, billed annually. No surprises, no hidden costs.'
|
||||||
: 'Precios en USD, con cobro anual. Las características pueden variar según el proyecto y la configuración del servidor.'}
|
: 'Precios en USD, facturación anual. Sin sorpresas, sin costos ocultos.'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<CloudDivider fillClass="fill-hds-bg" />
|
<CloudDivider fillClass="fill-hds-bg" />
|
||||||
|
|
||||||
<section class="py-16 md:py-20" style="background: var(--hds-bg);">
|
<!-- Hero plan: Empresarial (glass-card-hero) -->
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
{empresarial && (
|
||||||
<div class="text-center mb-12">
|
<section class="py-16 md:py-24" style="background: var(--hds-bg);">
|
||||||
<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);">
|
<div class="max-w-xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
{lang === 'en' ? 'Most popular' : 'Más elegidos'}
|
<div class="text-center mb-8">
|
||||||
</span>
|
<span class="inline-block px-3 py-1 rounded-full text-xs font-semibold tracking-[0.2em] uppercase mb-4" style="background: var(--hds-bg-soft); color: var(--color-hds-naranja);">
|
||||||
<h2 class="font-display mt-4" style="font-size: clamp(1.75rem, 4vw, 2.5rem); color: var(--hds-fg);">
|
{lang === 'en' ? 'Most popular' : 'El más elegido'}
|
||||||
{lang === 'en' ? 'Featured plans' : 'Planes destacados'}
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<article class="glass-card glass-card-hero p-8 md:p-12 text-center">
|
||||||
|
<div class="text-xs font-semibold tracking-[0.25em] uppercase mb-3" style="color: var(--color-hds-naranja);">
|
||||||
|
{empresarial.name}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-baseline justify-center gap-1.5 mb-6">
|
||||||
|
<span class="text-lg font-semibold" style="color: var(--hds-fg-soft);">US$</span>
|
||||||
|
<span class="font-display leading-none" style="font-size: 5rem; color: var(--hds-fg); font-weight: 500; letter-spacing: -0.03em;">{empresarial.priceUsdYear}</span>
|
||||||
|
<span class="text-lg" style="color: var(--hds-fg-soft);">/año</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm mb-8" style="color: var(--hds-fg-muted);">
|
||||||
|
(US${monthly(empresarial.priceUsdYear)}/mes)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border-t pt-6 mb-8" style="border-color: var(--glass-border);">
|
||||||
|
<ul class="grid grid-cols-2 gap-2.5 text-sm text-left max-w-md mx-auto">
|
||||||
|
{empresarial.features.map(f => (
|
||||||
|
<li class="flex items-start gap-2.5">
|
||||||
|
<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" 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 style="color: var(--hds-fg-soft);">{f}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href={whatsappUrl} target="_blank" rel="noopener noreferrer" class="btn-primary text-base px-10 inline-flex">
|
||||||
|
{lang === 'en' ? 'Choose' : 'Elegir'} {empresarial.name}
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<!-- 4 secondary plans (glass cards in 2-col grid) -->
|
||||||
|
<section class="py-12 md:py-16" style="background: var(--hds-bg);">
|
||||||
|
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="text-center mb-10">
|
||||||
|
<h2 class="font-display" style="font-size: clamp(1.5rem, 3.5vw, 2.25rem); color: var(--hds-fg);">
|
||||||
|
{lang === 'en' ? 'More options for every need' : 'Más opciones para cada necesidad'}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PricingTiersFeatured plans={featured} whatsappUrl={whatsappUrl} />
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 stagger">
|
||||||
|
{visibleWithoutHero.map(plan => (
|
||||||
|
<article class="glass-card p-8 flex flex-col h-full">
|
||||||
|
<div class="text-xs font-semibold tracking-[0.25em] uppercase mb-3" style={`color: ${plan.slug === 'institucional' ? 'var(--color-hds-naranja)' : 'var(--hds-fg-muted)'};`}>
|
||||||
|
{plan.slug === 'institucional' ? `⭐ ${plan.name} (recomendado)` : plan.name}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-baseline gap-1 mb-3">
|
||||||
|
<span class="text-lg font-semibold" style="color: var(--hds-fg-soft);">US$</span>
|
||||||
|
<span class="font-display leading-none" style="font-size: 3.5rem; color: var(--hds-fg); font-weight: 500; letter-spacing: -0.03em;">{plan.priceUsdYear}</span>
|
||||||
|
<span class="text-sm" style="color: var(--hds-fg-soft);">/año</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs mb-6" style="color: var(--hds-fg-muted);">
|
||||||
|
(US${monthly(plan.priceUsdYear)}/mes)
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-10 text-center text-sm space-y-1" style="color: var(--hds-fg-muted);">
|
<div class="border-t pt-5 mb-6 flex-grow" style="border-color: var(--glass-border);">
|
||||||
<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>
|
<ul class="space-y-2 text-sm">
|
||||||
<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>
|
{plan.features.slice(0, 5).map(f => (
|
||||||
|
<li class="flex items-start gap-2.5">
|
||||||
|
<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" 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 style="color: var(--hds-fg-soft);">{f}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
{plan.features.length > 5 && (
|
||||||
|
<p class="text-xs mt-3 italic" style="color: var(--hds-fg-muted);">
|
||||||
|
+{plan.features.length - 5} características más
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href={whatsappUrl} target="_blank" rel="noopener noreferrer" class="btn-secondary justify-center">
|
||||||
|
{lang === 'en' ? 'Choose' : 'Elegir'} {plan.name}
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<CloudDivider fillClass="fill-hds-bg-soft" flip={true} />
|
<!-- Advanced plans accordion -->
|
||||||
|
<section class="py-12 md:py-20" style="background: var(--hds-bg);">
|
||||||
|
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<details class="group">
|
||||||
|
<summary class="flex items-center justify-between cursor-pointer py-4 px-6 rounded-2xl glass-card">
|
||||||
|
<div>
|
||||||
|
<span class="font-semibold" style="color: var(--hds-fg);">
|
||||||
|
{lang === 'en' ? 'Advanced plans' : 'Planes avanzados'}
|
||||||
|
</span>
|
||||||
|
<span class="text-sm ml-3" style="color: var(--hds-fg-muted);">
|
||||||
|
{lang === 'en' ? `${advanced.length} plans for high-demand projects` : `${advanced.length} planes para proyectos de alta demanda`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="transition-transform group-open:rotate-180" style="color: var(--color-hds-naranja);" aria-hidden="true">
|
||||||
|
<polyline points="6 9 12 15 18 9"/>
|
||||||
|
</svg>
|
||||||
|
</summary>
|
||||||
|
|
||||||
<section class="py-20 md:py-24" style="background: var(--hds-bg-soft);">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-5 mt-6">
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
{advanced.map(plan => (
|
||||||
<div class="text-center mb-12">
|
<article class="glass-card p-6 flex flex-col h-full">
|
||||||
<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);">
|
<div class="text-xs font-semibold tracking-[0.2em] uppercase mb-3" style="color: var(--hds-fg-muted);">
|
||||||
{lang === 'en' ? 'Complete catalog' : 'Catálogo completo'}
|
{plan.name}
|
||||||
</span>
|
</div>
|
||||||
<h2 class="font-display mt-4" style="font-size: clamp(1.75rem, 4vw, 2.5rem); color: var(--hds-fg);">
|
<div class="flex items-baseline gap-1 mb-2">
|
||||||
{lang === 'en' ? 'All 9 plans' : 'Los 9 planes'}
|
<span class="text-sm font-semibold" style="color: var(--hds-fg-soft);">US$</span>
|
||||||
</h2>
|
<span class="font-display leading-none" style="font-size: 2.75rem; color: var(--hds-fg); font-weight: 500; letter-spacing: -0.03em;">{plan.priceUsdYear}</span>
|
||||||
<p class="mt-3 max-w-2xl mx-auto" style="color: var(--hds-fg-soft);">
|
<span class="text-xs" style="color: var(--hds-fg-soft);">/año</span>
|
||||||
{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.'}
|
</div>
|
||||||
</p>
|
<div class="text-xs mb-4" style="color: var(--hds-fg-muted);">
|
||||||
</div>
|
(US${monthly(plan.priceUsdYear)}/mes)
|
||||||
|
</div>
|
||||||
|
|
||||||
<PricingGrid plans={other} whatsappUrl={whatsappUrl} />
|
<div class="border-t pt-4 mb-5 flex-grow" style="border-color: var(--glass-border);">
|
||||||
|
<ul class="space-y-1.5 text-sm">
|
||||||
|
{plan.features.slice(0, 5).map(f => (
|
||||||
|
<li class="flex items-start gap-2.5">
|
||||||
|
<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" 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 style="color: var(--hds-fg-soft);">{f}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-16 text-center">
|
<a href={whatsappUrl} target="_blank" rel="noopener noreferrer" class="btn-ghost justify-center text-sm">
|
||||||
<p class="text-sm" style="color: var(--hds-fg-muted);">
|
{lang === 'en' ? 'Choose' : 'Elegir'} {plan.name}
|
||||||
{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>
|
</a>
|
||||||
</p>
|
</article>
|
||||||
</div>
|
))}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<CloudDivider fillClass="fill-hds-bg-soft" />
|
||||||
|
|
||||||
|
<!-- CTA -->
|
||||||
|
<section class="py-16 md:py-24" style="background: var(--hds-bg-soft);">
|
||||||
|
<div class="max-w-2xl mx-auto px-4 text-center">
|
||||||
|
<p class="text-sm mb-4" style="color: var(--hds-fg-muted);">
|
||||||
|
{lang === 'en' ? 'Need a custom plan?' : '¿Necesitás un plan a medida?'}
|
||||||
|
</p>
|
||||||
|
<a href={whatsappUrl} target="_blank" rel="noopener noreferrer" class="btn-primary text-base px-10 inline-flex">
|
||||||
|
{lang === 'en' ? 'Let us talk' : 'Charlemos'}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|||||||
+148
-12
@@ -20,9 +20,13 @@
|
|||||||
|
|
||||||
@custom-variant dark (&:where(.dark, .dark *));
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
Theme Tokens — Glassmorphism + Organic
|
||||||
|
======================================== */
|
||||||
@theme {
|
@theme {
|
||||||
--color-hds-naranja: #EE7623;
|
--color-hds-naranja: #EE7623;
|
||||||
--color-hds-naranja-hover: #D8651A;
|
--color-hds-naranja-hover: #D8651A;
|
||||||
|
--color-hds-naranja-glow: rgba(238, 118, 35, 0.25);
|
||||||
--color-hds-coral: #FA9F5C;
|
--color-hds-coral: #FA9F5C;
|
||||||
--color-hds-durazno: #FFCBA1;
|
--color-hds-durazno: #FFCBA1;
|
||||||
--color-hds-tinta: #1A1612;
|
--color-hds-tinta: #1A1612;
|
||||||
@@ -36,9 +40,14 @@
|
|||||||
--tracking-eyebrow: 0.25em;
|
--tracking-eyebrow: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
Design Tokens (CSS custom properties)
|
||||||
|
======================================== */
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
|
|
||||||
|
/* Core surface tokens */
|
||||||
--hds-bg: #FAF6EE;
|
--hds-bg: #FAF6EE;
|
||||||
--hds-bg-soft: #F5EFE3;
|
--hds-bg-soft: #F5EFE3;
|
||||||
--hds-bg-section: #FFFFFF;
|
--hds-bg-section: #FFFFFF;
|
||||||
@@ -48,6 +57,25 @@
|
|||||||
--hds-line: #E8E2D5;
|
--hds-line: #E8E2D5;
|
||||||
--hds-card: #FFFFFF;
|
--hds-card: #FFFFFF;
|
||||||
--hds-card-soft: #FFFFFF;
|
--hds-card-soft: #FFFFFF;
|
||||||
|
|
||||||
|
/* Glassmorphism system */
|
||||||
|
--glass-bg: rgba(255, 255, 255, 0.45);
|
||||||
|
--glass-bg-hover: rgba(255, 255, 255, 0.65);
|
||||||
|
--glass-border: rgba(255, 255, 255, 0.25);
|
||||||
|
--glass-border-hover: rgba(238, 118, 35, 0.35);
|
||||||
|
--glass-shadow: 0 8px 32px rgba(26, 22, 18, 0.06);
|
||||||
|
--glass-shadow-hover: 0 16px 48px rgba(238, 118, 35, 0.12);
|
||||||
|
--radius-glass: 24px;
|
||||||
|
--blur-glass: 20px;
|
||||||
|
|
||||||
|
/* Gradients */
|
||||||
|
--hds-gradient-hero: radial-gradient(ellipse 80% 60% at 50% 0%, rgba(238, 118, 35, 0.08) 0%, transparent 70%);
|
||||||
|
--hds-gradient-section: radial-gradient(ellipse 60% 50% at 50% 50%, rgba(250, 159, 92, 0.06) 0%, transparent 70%);
|
||||||
|
--hds-gradient-card-glow: radial-gradient(circle at var(--mouse-x, 50%) var(--mouse-y, 50%), rgba(238, 118, 35, 0.08) 0%, transparent 50%);
|
||||||
|
|
||||||
|
/* Motion */
|
||||||
|
--ease-spring: cubic-bezier(0.16, 1.2, 0.3, 1);
|
||||||
|
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root.dark {
|
:root.dark {
|
||||||
@@ -61,6 +89,17 @@
|
|||||||
--hds-line: #3A332C;
|
--hds-line: #3A332C;
|
||||||
--hds-card: #2A2520;
|
--hds-card: #2A2520;
|
||||||
--hds-card-soft: #221E1A;
|
--hds-card-soft: #221E1A;
|
||||||
|
|
||||||
|
--glass-bg: rgba(42, 37, 32, 0.55);
|
||||||
|
--glass-bg-hover: rgba(42, 37, 32, 0.75);
|
||||||
|
--glass-border: rgba(255, 255, 255, 0.08);
|
||||||
|
--glass-border-hover: rgba(238, 118, 35, 0.3);
|
||||||
|
--glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
||||||
|
--glass-shadow-hover: 0 16px 48px rgba(238, 118, 35, 0.15);
|
||||||
|
|
||||||
|
--hds-gradient-hero: radial-gradient(ellipse 80% 60% at 50% 0%, rgba(238, 118, 35, 0.12) 0%, transparent 70%);
|
||||||
|
--hds-gradient-section: radial-gradient(ellipse 60% 50% at 50% 50%, rgba(250, 159, 92, 0.08) 0%, transparent 70%);
|
||||||
|
--hds-gradient-card-glow: radial-gradient(circle at var(--mouse-x, 50%) var(--mouse-y, 50%), rgba(238, 118, 35, 0.12) 0%, transparent 50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
@@ -74,7 +113,7 @@
|
|||||||
background-color: var(--hds-bg);
|
background-color: var(--hds-bg);
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
transition: background-color 0.2s ease, color 0.2s ease;
|
transition: background-color 0.3s var(--ease-out-expo), color 0.3s var(--ease-out-expo);
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4 {
|
h1, h2, h3, h4 {
|
||||||
@@ -89,11 +128,84 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
Noise Texture Overlay (global)
|
||||||
|
======================================== */
|
||||||
|
@layer base {
|
||||||
|
.noise-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.025;
|
||||||
|
mix-blend-mode: overlay;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
|
||||||
|
background-repeat: repeat;
|
||||||
|
background-size: 256px 256px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
Glass Card System
|
||||||
|
======================================== */
|
||||||
@layer components {
|
@layer components {
|
||||||
.font-display { font-family: var(--font-display); }
|
.font-display { font-family: var(--font-display); }
|
||||||
.font-editorial { font-family: var(--font-editorial); }
|
.font-editorial { font-family: var(--font-editorial); }
|
||||||
.font-brand { font-family: var(--font-brand); letter-spacing: 0.01em; }
|
.font-brand { font-family: var(--font-brand); letter-spacing: 0.01em; }
|
||||||
|
|
||||||
|
/* Glass Card */
|
||||||
|
.glass-card {
|
||||||
|
position: relative;
|
||||||
|
background: var(--glass-bg);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: var(--radius-glass);
|
||||||
|
backdrop-filter: blur(var(--blur-glass));
|
||||||
|
-webkit-backdrop-filter: blur(var(--blur-glass));
|
||||||
|
box-shadow: var(--glass-shadow);
|
||||||
|
transition:
|
||||||
|
background 0.3s var(--ease-out-expo),
|
||||||
|
border-color 0.3s var(--ease-out-expo),
|
||||||
|
box-shadow 0.3s var(--ease-out-expo),
|
||||||
|
transform 0.4s var(--ease-spring);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-card::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: inherit;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.4s var(--ease-out-expo);
|
||||||
|
background: var(--hds-gradient-card-glow);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-card:hover {
|
||||||
|
background: var(--glass-bg-hover);
|
||||||
|
border-color: var(--glass-border-hover);
|
||||||
|
box-shadow: var(--glass-shadow-hover);
|
||||||
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-card:hover::before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-card > * {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glass Card — hero variant (spans more, stronger glow) */
|
||||||
|
.glass-card-hero {
|
||||||
|
background: var(--glass-bg-hover);
|
||||||
|
border-color: var(--glass-border-hover);
|
||||||
|
box-shadow: var(--glass-shadow-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section eyebrow (unchanged, keep original) */
|
||||||
.eyebrow {
|
.eyebrow {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -113,6 +225,7 @@
|
|||||||
background: var(--color-hds-naranja);
|
background: var(--color-hds-naranja);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Buttons with spring easing */
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background: var(--color-hds-naranja);
|
background: var(--color-hds-naranja);
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
@@ -123,13 +236,18 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
transition: background 0.2s ease, transform 0.2s ease;
|
transition:
|
||||||
|
background 0.2s ease,
|
||||||
|
transform 0.3s var(--ease-spring),
|
||||||
|
box-shadow 0.3s var(--ease-spring);
|
||||||
border: 2px solid var(--color-hds-naranja);
|
border: 2px solid var(--color-hds-naranja);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.btn-primary:hover {
|
.btn-primary:hover {
|
||||||
background: var(--color-hds-naranja-hover);
|
background: var(--color-hds-naranja-hover);
|
||||||
border-color: var(--color-hds-naranja-hover);
|
border-color: var(--color-hds-naranja-hover);
|
||||||
|
transform: scale(1.03);
|
||||||
|
box-shadow: 0 8px 28px rgba(238, 118, 35, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
@@ -142,13 +260,16 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
transition: all 0.2s ease;
|
transition:
|
||||||
|
all 0.2s ease,
|
||||||
|
transform 0.3s var(--ease-spring);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.btn-secondary:hover {
|
.btn-secondary:hover {
|
||||||
background: var(--hds-fg);
|
background: var(--hds-fg);
|
||||||
color: var(--hds-bg);
|
color: var(--hds-bg);
|
||||||
|
transform: scale(1.03);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-ghost {
|
.btn-ghost {
|
||||||
@@ -158,7 +279,7 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
transition: gap 0.2s ease;
|
transition: gap 0.3s var(--ease-out-expo);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -179,6 +300,7 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Legacy service-card (keep reference, deprecate toward glass-card) */
|
||||||
.service-card {
|
.service-card {
|
||||||
background: var(--hds-card);
|
background: var(--hds-card);
|
||||||
border: 1px solid var(--hds-line);
|
border: 1px solid var(--hds-line);
|
||||||
@@ -192,6 +314,7 @@
|
|||||||
box-shadow: 0 12px 32px rgba(238,118,35,0.08);
|
box-shadow: 0 12px 32px rgba(238,118,35,0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Particle grid background */
|
||||||
.particle-grid {
|
.particle-grid {
|
||||||
background-image:
|
background-image:
|
||||||
linear-gradient(to right, rgba(238,118,35,0.10) 1px, transparent 1px),
|
linear-gradient(to right, rgba(238,118,35,0.10) 1px, transparent 1px),
|
||||||
@@ -221,12 +344,20 @@
|
|||||||
.skip-link:focus { top: 1rem; }
|
.skip-link:focus { top: 1rem; }
|
||||||
}
|
}
|
||||||
|
|
||||||
:focus-visible {
|
/* ========================================
|
||||||
outline: 2px solid var(--color-hds-naranja);
|
Focus & A11y
|
||||||
outline-offset: 3px;
|
======================================== */
|
||||||
border-radius: 2px;
|
@layer base {
|
||||||
|
:focus-visible {
|
||||||
|
outline: 2px solid var(--color-hds-naranja);
|
||||||
|
outline-offset: 3px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
Reduced Motion
|
||||||
|
======================================== */
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
*, *::before, *::after {
|
*, *::before, *::after {
|
||||||
animation-duration: 0.01ms !important;
|
animation-duration: 0.01ms !important;
|
||||||
@@ -235,9 +366,14 @@
|
|||||||
scroll-behavior: auto !important;
|
scroll-behavior: auto !important;
|
||||||
}
|
}
|
||||||
.reveal { opacity: 1 !important; transform: none !important; }
|
.reveal { opacity: 1 !important; transform: none !important; }
|
||||||
|
.noise-overlay { display: none; }
|
||||||
|
.glass-card:hover { transform: none; box-shadow: var(--glass-shadow); }
|
||||||
|
.btn-primary:hover, .btn-secondary:hover { transform: none; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scroll reveal animations */
|
/* ========================================
|
||||||
|
Scroll Reveal Animations
|
||||||
|
======================================== */
|
||||||
.reveal {
|
.reveal {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(24px);
|
transform: translateY(24px);
|
||||||
@@ -269,7 +405,9 @@
|
|||||||
.stagger.is-visible > *:nth-child(8) { opacity: 1; transform: translateY(0); transition-delay: 0.56s; }
|
.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; }
|
.stagger.is-visible > *:nth-child(9) { opacity: 1; transform: translateY(0); transition-delay: 0.64s; }
|
||||||
|
|
||||||
/* Float subtle */
|
/* ========================================
|
||||||
|
Keyframes
|
||||||
|
======================================== */
|
||||||
@keyframes floaty {
|
@keyframes floaty {
|
||||||
0%, 100% { transform: translateY(0); }
|
0%, 100% { transform: translateY(0); }
|
||||||
50% { transform: translateY(-8px); }
|
50% { transform: translateY(-8px); }
|
||||||
@@ -282,7 +420,6 @@
|
|||||||
animation-delay: 1s;
|
animation-delay: 1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Pulse glow */
|
|
||||||
@keyframes pulse-glow {
|
@keyframes pulse-glow {
|
||||||
0%, 100% { box-shadow: 0 0 0 0 rgba(238,118,35,0.4); }
|
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); }
|
50% { box-shadow: 0 0 0 12px rgba(238,118,35,0); }
|
||||||
@@ -291,7 +428,6 @@
|
|||||||
animation: pulse-glow 2.5s ease-in-out infinite;
|
animation: pulse-glow 2.5s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Counter */
|
|
||||||
@keyframes countUp {
|
@keyframes countUp {
|
||||||
from { opacity: 0.3; transform: translateY(8px); }
|
from { opacity: 0.3; transform: translateY(8px); }
|
||||||
to { opacity: 1; transform: translateY(0); }
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
|||||||
Reference in New Issue
Block a user