163 lines
5.2 KiB
TypeScript
163 lines
5.2 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* ServicesJourney — pinned horizontal-scroll track of services.
|
|
*
|
|
* On desktop the section pins and the panels scroll sideways as you scroll
|
|
* down (GSAP ScrollTrigger pin + scrub), with a progress bar. On mobile / when
|
|
* the pin would be awkward, and under reduced-motion, it degrades to a normal
|
|
* vertical stack (no pin, panels just stack and reveal). Each panel is a real
|
|
* <article> so the content is accessible regardless of layout.
|
|
*/
|
|
|
|
import { useEffect, useRef } from "react";
|
|
import { gsap } from "gsap";
|
|
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
|
|
|
gsap.registerPlugin(ScrollTrigger);
|
|
|
|
const services = [
|
|
{
|
|
n: "01",
|
|
t: "Web & Desarrollo",
|
|
d: "Sitios construidos en código, veloces y 100% editables. Sin plantillas, sin atajos: arquitectura que escala contigo.",
|
|
tags: ["Next.js", "Headless", "Core Web Vitals"],
|
|
},
|
|
{
|
|
n: "02",
|
|
t: "SEO & GEO",
|
|
d: "Posicionamiento técnico y de contenido — para Google y para los motores de IA. Reporting real, no humo.",
|
|
tags: ["Técnico", "Contenido", "Answer Engines"],
|
|
},
|
|
{
|
|
n: "03",
|
|
t: "Paid Ads",
|
|
d: "Google, Meta, TikTok y LinkedIn. Creatividad asistida por IA y pujas optimizadas hacia el resultado, no el clic.",
|
|
tags: ["Performance", "Creatividad IA", "Atribución"],
|
|
},
|
|
{
|
|
n: "04",
|
|
t: "Contenido",
|
|
d: "Piezas pensadas para rankear y para convertir. Producción a escala sin perder la voz de tu marca.",
|
|
tags: ["Editorial", "Vídeo", "Social"],
|
|
},
|
|
{
|
|
n: "05",
|
|
t: "Diseño & Marca",
|
|
d: "Identidad y dirección de arte con criterio. Sistemas de diseño que viven en producto, no en un PDF.",
|
|
tags: ["Identidad", "Design System", "Motion"],
|
|
},
|
|
{
|
|
n: "06",
|
|
t: "Automatización",
|
|
d: "Flujos que devuelven horas: captación de leads, reporting automático y operativa conectada de punta a punta.",
|
|
tags: ["Leads", "Reporting", "Ops"],
|
|
},
|
|
];
|
|
|
|
export default function ServicesJourney() {
|
|
const section = useRef<HTMLElement>(null);
|
|
const track = useRef<HTMLDivElement>(null);
|
|
const bar = useRef<HTMLSpanElement>(null);
|
|
|
|
useEffect(() => {
|
|
const sec = section.current;
|
|
const trk = track.current;
|
|
if (!sec || !trk) return;
|
|
|
|
const mm = gsap.matchMedia();
|
|
|
|
// Desktop with motion allowed: pin + horizontal scroll.
|
|
mm.add(
|
|
"(min-width: 900px) and (prefers-reduced-motion: no-preference)",
|
|
() => {
|
|
const distance = () => trk.scrollWidth - window.innerWidth;
|
|
|
|
const tween = gsap.to(trk, {
|
|
x: () => -distance(),
|
|
ease: "none",
|
|
scrollTrigger: {
|
|
trigger: sec,
|
|
start: "top top",
|
|
end: () => "+=" + distance(),
|
|
scrub: 0.6,
|
|
pin: true,
|
|
invalidateOnRefresh: true,
|
|
onUpdate: (self) => {
|
|
if (bar.current) {
|
|
bar.current.style.transform = `scaleX(${self.progress})`;
|
|
}
|
|
},
|
|
},
|
|
});
|
|
|
|
// Per-panel inner reveal as it slides into center.
|
|
gsap.utils.toArray<HTMLElement>(".sjourney__panel").forEach((panel) => {
|
|
gsap.from(panel.querySelectorAll(".sjourney__reveal"), {
|
|
y: 40,
|
|
opacity: 0,
|
|
duration: 0.6,
|
|
stagger: 0.06,
|
|
ease: "power3.out",
|
|
scrollTrigger: {
|
|
trigger: panel,
|
|
containerAnimation: tween,
|
|
start: "left 80%",
|
|
},
|
|
});
|
|
});
|
|
|
|
return () => {
|
|
if (bar.current) bar.current.style.transform = "scaleX(0)";
|
|
};
|
|
}
|
|
);
|
|
|
|
// Mobile / reduced-motion: simple stacked reveals.
|
|
mm.add("(max-width: 899px)", () => {
|
|
gsap.utils.toArray<HTMLElement>(".sjourney__panel").forEach((panel) => {
|
|
gsap.from(panel, {
|
|
y: 36,
|
|
opacity: 0,
|
|
duration: 0.7,
|
|
ease: "power3.out",
|
|
scrollTrigger: { trigger: panel, start: "top 88%" },
|
|
});
|
|
});
|
|
});
|
|
|
|
return () => mm.revert();
|
|
}, []);
|
|
|
|
return (
|
|
<section id="servicios" className="sjourney" ref={section} aria-label="Servicios">
|
|
<div className="sjourney__head wrap">
|
|
<p className="kicker">02 — Qué hacemos</p>
|
|
<h2 className="sjourney__title">
|
|
Todo tu crecimiento, <span className="serif-em">un mismo sistema.</span>
|
|
</h2>
|
|
<div className="sjourney__progress" aria-hidden="true">
|
|
<span ref={bar} className="sjourney__progress-bar" />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="sjourney__viewport">
|
|
<div className="sjourney__track" ref={track}>
|
|
{services.map((s) => (
|
|
<article className="sjourney__panel hoverable" key={s.t} data-cursor="explorar">
|
|
<span className="sjourney__n sjourney__reveal">{s.n}</span>
|
|
<h3 className="sjourney__panel-title sjourney__reveal">{s.t}</h3>
|
|
<p className="sjourney__panel-desc sjourney__reveal">{s.d}</p>
|
|
<ul className="sjourney__tags sjourney__reveal">
|
|
{s.tags.map((tag) => (
|
|
<li key={tag}>{tag}</li>
|
|
))}
|
|
</ul>
|
|
<span className="sjourney__pill" aria-hidden="true" />
|
|
</article>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|