feat: bespoke animated design (GSAP+Lenis, brand identity, noindex)
This commit is contained in:
parent
32bf6c828c
commit
d4dbef11ed
10 changed files with 470 additions and 201 deletions
49
app/components/Cursor.tsx
Normal file
49
app/components/Cursor.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { gsap } from "gsap";
|
||||
|
||||
export default function Cursor() {
|
||||
const dot = useRef<HTMLDivElement>(null);
|
||||
const ring = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (window.matchMedia("(pointer: coarse)").matches) return;
|
||||
if (!dot.current || !ring.current) return;
|
||||
|
||||
const xD = gsap.quickTo(dot.current, "x", { duration: 0.15, ease: "power3" });
|
||||
const yD = gsap.quickTo(dot.current, "y", { duration: 0.15, ease: "power3" });
|
||||
const xR = gsap.quickTo(ring.current, "x", { duration: 0.5, ease: "power3" });
|
||||
const yR = gsap.quickTo(ring.current, "y", { duration: 0.5, ease: "power3" });
|
||||
|
||||
const move = (e: MouseEvent) => {
|
||||
xD(e.clientX);
|
||||
yD(e.clientY);
|
||||
xR(e.clientX);
|
||||
yR(e.clientY);
|
||||
};
|
||||
|
||||
const over = (e: Event) => {
|
||||
if ((e.target as HTMLElement).closest("a,button,.hoverable")) {
|
||||
ring.current?.classList.add("cursor-ring--big");
|
||||
}
|
||||
};
|
||||
const out = () => ring.current?.classList.remove("cursor-ring--big");
|
||||
|
||||
window.addEventListener("mousemove", move);
|
||||
document.addEventListener("mouseover", over);
|
||||
document.addEventListener("mouseout", out);
|
||||
return () => {
|
||||
window.removeEventListener("mousemove", move);
|
||||
document.removeEventListener("mouseover", over);
|
||||
document.removeEventListener("mouseout", out);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={ring} className="cursor-ring" aria-hidden />
|
||||
<div ref={dot} className="cursor-dot" aria-hidden />
|
||||
</>
|
||||
);
|
||||
}
|
||||
108
app/components/Hero.tsx
Normal file
108
app/components/Hero.tsx
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { gsap } from "gsap";
|
||||
|
||||
const line1 = "La mayoría de agencias alquilan sus herramientas.".split(" ");
|
||||
const line2 = "Nosotros construimos la nuestra.".split(" ");
|
||||
|
||||
function Words({ words, grad }: { words: string[]; grad?: boolean }) {
|
||||
return (
|
||||
<span className={"line" + (grad ? " grad" : "")}>
|
||||
{words.map((w, i) => (
|
||||
<span className="word" key={i}>
|
||||
<span className="word-in">{w}</span>{" "}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Hero() {
|
||||
const root = useRef<HTMLElement>(null);
|
||||
const mark = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const el = root.current;
|
||||
if (!el) return;
|
||||
|
||||
const ctx = gsap.context(() => {
|
||||
const tl = gsap.timeline({ defaults: { ease: "power4.out" } });
|
||||
tl.from(".eyebrow", { y: 20, opacity: 0, duration: 0.8 })
|
||||
.from(
|
||||
".word-in",
|
||||
{ yPercent: 115, duration: 1.05, stagger: 0.045 },
|
||||
"-=0.4"
|
||||
)
|
||||
.from(".hero-sub", { y: 24, opacity: 0, duration: 0.9 }, "-=0.7")
|
||||
.from(".hero-actions > *", { y: 20, opacity: 0, stagger: 0.12, duration: 0.7 }, "-=0.6")
|
||||
.from(".bar", { scaleX: 0, transformOrigin: "left", stagger: 0.08, duration: 0.7, ease: "power3.inOut" }, "-=1.1");
|
||||
|
||||
// floating pill-mark
|
||||
gsap.to(".bar", {
|
||||
y: "+=8",
|
||||
duration: 2.4,
|
||||
ease: "sine.inOut",
|
||||
repeat: -1,
|
||||
yoyo: true,
|
||||
stagger: { each: 0.15, from: "random" },
|
||||
});
|
||||
|
||||
}, el);
|
||||
|
||||
const onMove = (e: MouseEvent) => {
|
||||
const rx = (e.clientX / window.innerWidth - 0.5) * 2;
|
||||
const ry = (e.clientY / window.innerHeight - 0.5) * 2;
|
||||
gsap.to(mark.current, { x: rx * 22, y: ry * 22, duration: 0.8, ease: "power3" });
|
||||
gsap.to(".blob", { x: rx * 30, y: ry * 30, duration: 1.2, ease: "power3" });
|
||||
};
|
||||
window.addEventListener("mousemove", onMove);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("mousemove", onMove);
|
||||
ctx.revert();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section className="hero" ref={root}>
|
||||
<div className="mesh" aria-hidden>
|
||||
<span className="blob b1" />
|
||||
<span className="blob b2" />
|
||||
<span className="blob b3" />
|
||||
<span className="blob b4" />
|
||||
</div>
|
||||
|
||||
<div className="hero-inner wrap">
|
||||
<p className="eyebrow">Agencia de marketing AI-native</p>
|
||||
|
||||
<div ref={mark} className="pillmark" aria-hidden>
|
||||
<span className="bar bar-grad" />
|
||||
<div className="bar-row">
|
||||
<span className="bar bar-ink" />
|
||||
<span className="bar bar-blue" />
|
||||
</div>
|
||||
<span className="bar bar-green" />
|
||||
</div>
|
||||
|
||||
<h1 className="hero-h1">
|
||||
<Words words={line1} />
|
||||
<Words words={line2} grad />
|
||||
</h1>
|
||||
|
||||
<p className="hero-sub">
|
||||
Estrategia humana + nuestra propia plataforma de IA. Web, SEO, ads y
|
||||
contenido — más rápido, más medible y a mejor coste que una agencia
|
||||
tradicional.
|
||||
</p>
|
||||
|
||||
<div className="hero-actions">
|
||||
<a className="btn primary hoverable" href="#contacto">Habla con nosotros</a>
|
||||
<a className="btn ghost hoverable" href="#servicios">Ver qué hacemos</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="scroll-hint" aria-hidden>scroll</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
46
app/components/Reveal.tsx
Normal file
46
app/components/Reveal.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useRef, ReactNode } from "react";
|
||||
import { gsap } from "gsap";
|
||||
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
||||
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
export default function Reveal({
|
||||
children,
|
||||
className = "",
|
||||
stagger = 0,
|
||||
y = 40,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
stagger?: number;
|
||||
y?: number;
|
||||
}) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const el = ref.current;
|
||||
if (!el) return;
|
||||
const targets = stagger > 0 ? Array.from(el.children) : [el];
|
||||
|
||||
const ctx = gsap.context(() => {
|
||||
gsap.from(targets, {
|
||||
y,
|
||||
opacity: 0,
|
||||
duration: 1,
|
||||
ease: "power3.out",
|
||||
stagger,
|
||||
scrollTrigger: { trigger: el, start: "top 85%" },
|
||||
});
|
||||
}, el);
|
||||
|
||||
return () => ctx.revert();
|
||||
}, [stagger, y]);
|
||||
|
||||
return (
|
||||
<div ref={ref} className={className}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
31
app/components/SmoothScroll.tsx
Normal file
31
app/components/SmoothScroll.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import Lenis from "lenis";
|
||||
import { gsap } from "gsap";
|
||||
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
||||
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
export default function SmoothScroll() {
|
||||
useEffect(() => {
|
||||
const lenis = new Lenis({
|
||||
duration: 1.15,
|
||||
smoothWheel: true,
|
||||
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
|
||||
});
|
||||
|
||||
lenis.on("scroll", ScrollTrigger.update);
|
||||
|
||||
const ticker = (time: number) => lenis.raf(time * 1000);
|
||||
gsap.ticker.add(ticker);
|
||||
gsap.ticker.lagSmoothing(0);
|
||||
|
||||
return () => {
|
||||
gsap.ticker.remove(ticker);
|
||||
lenis.destroy();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
218
app/globals.css
218
app/globals.css
|
|
@ -1,122 +1,162 @@
|
|||
:root {
|
||||
--bg: #0a0a0b;
|
||||
--bg-2: #101013;
|
||||
--fg: #fafafa;
|
||||
--muted: #a1a1aa;
|
||||
--line: rgba(255, 255, 255, 0.1);
|
||||
--accent: #6366f1;
|
||||
--accent-2: #22d3ee;
|
||||
--maxw: 1140px;
|
||||
--violet: #8b5cf6;
|
||||
--blue: #3b82f6;
|
||||
--green: #10b981;
|
||||
--ink: #111827;
|
||||
--bg-ink: #08080d;
|
||||
--bg-ink-2: #0e0e16;
|
||||
--fg: #f6f6f9;
|
||||
--muted: #9aa0ad;
|
||||
--maxw: 1180px;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
html { scroll-behavior: smooth; }
|
||||
|
||||
body {
|
||||
background: var(--bg);
|
||||
background: var(--bg-ink);
|
||||
color: var(--fg);
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
font-family: "Satoshi", ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
line-height: 1.5;
|
||||
line-height: 1.45;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
a { color: inherit; text-decoration: none; }
|
||||
::selection { background: var(--violet); color: #fff; }
|
||||
|
||||
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 clamp(20px, 5vw, 40px); width: 100%; }
|
||||
|
||||
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 clamp(22px, 5vw, 48px); width: 100%; }
|
||||
.grad {
|
||||
background: linear-gradient(90deg, var(--accent), var(--accent-2));
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
background: linear-gradient(100deg, var(--violet), var(--blue) 60%, var(--green));
|
||||
-webkit-background-clip: text; background-clip: text; color: transparent;
|
||||
}
|
||||
|
||||
/* CUSTOM CURSOR */
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
body { cursor: none; }
|
||||
a, button, .hoverable { cursor: none; }
|
||||
}
|
||||
.cursor-dot, .cursor-ring {
|
||||
position: fixed; top: 0; left: 0; border-radius: 50%;
|
||||
pointer-events: none; z-index: 9999; transform: translate(-50%, -50%);
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
.cursor-dot { width: 7px; height: 7px; background: #fff; }
|
||||
.cursor-ring { width: 34px; height: 34px; border: 1px solid #fff; transition: width .25s, height .25s, opacity .25s; }
|
||||
.cursor-ring--big { width: 64px; height: 64px; opacity: .6; }
|
||||
@media (hover: none), (pointer: coarse) { .cursor-dot, .cursor-ring { display: none; } }
|
||||
|
||||
/* HEADER */
|
||||
.hd {
|
||||
position: sticky; top: 0; z-index: 50;
|
||||
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 16px clamp(20px, 5vw, 40px);
|
||||
background: rgba(10, 10, 11, 0.7);
|
||||
backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid var(--line);
|
||||
padding: 18px clamp(22px, 5vw, 48px);
|
||||
mix-blend-mode: difference; color: #fff;
|
||||
}
|
||||
.logo { font-weight: 700; letter-spacing: -0.02em; }
|
||||
.hd-nav { display: flex; gap: 1.6rem; align-items: center; font-size: 0.92rem; }
|
||||
.hd-nav a { color: var(--muted); transition: color 0.15s; }
|
||||
.hd-nav a:hover { color: var(--fg); }
|
||||
.hd-cta { color: var(--fg) !important; border: 1px solid var(--line); padding: 0.5rem 1rem; border-radius: 999px; }
|
||||
@media (max-width: 640px) { .hd-nav a:not(.hd-cta) { display: none; } }
|
||||
.logo { font-weight: 900; letter-spacing: -.03em; font-size: 1.05rem; }
|
||||
.logo-dot { font-weight: 400; opacity: .7; }
|
||||
.hd-nav { display: flex; gap: 1.8rem; align-items: center; font-size: .92rem; font-weight: 500; }
|
||||
.hd-cta { border: 1px solid rgba(255,255,255,.5); padding: .5rem 1.1rem; border-radius: 999px; }
|
||||
@media (max-width: 680px) { .hd-nav a:not(.hd-cta) { display: none; } }
|
||||
|
||||
/* HERO */
|
||||
.hero { position: relative; overflow: hidden; padding: clamp(80px, 14vw, 160px) 0 clamp(60px, 9vw, 110px); }
|
||||
.hero::before {
|
||||
content: ""; position: absolute; inset: 0; pointer-events: none;
|
||||
background: radial-gradient(55% 50% at 72% -10%, rgba(99, 102, 241, 0.28), transparent 70%),
|
||||
radial-gradient(40% 45% at 0% 110%, rgba(34, 211, 238, 0.12), transparent 70%);
|
||||
.hero {
|
||||
position: relative; min-height: 100svh; display: flex; align-items: center;
|
||||
background: linear-gradient(180deg, #efe9fb 0%, #f6f3fd 55%, #eef0fe 100%);
|
||||
color: var(--ink); overflow: hidden;
|
||||
}
|
||||
.hero .wrap { position: relative; z-index: 1; }
|
||||
.eyebrow { text-transform: uppercase; letter-spacing: 0.18em; font-size: 0.76rem; color: var(--accent-2); margin-bottom: 1.1rem; }
|
||||
h1 { font-size: clamp(2.3rem, 6vw, 4.6rem); line-height: 1.04; letter-spacing: -0.03em; font-weight: 800; }
|
||||
.sub { margin-top: 1.5rem; max-width: 620px; font-size: clamp(1rem, 1.5vw, 1.2rem); color: var(--muted); }
|
||||
.actions { margin-top: 2.2rem; display: flex; gap: 1rem; flex-wrap: wrap; }
|
||||
.mesh { position: absolute; inset: 0; z-index: 0; }
|
||||
.blob { position: absolute; border-radius: 50%; filter: blur(70px); opacity: .75; will-change: transform; }
|
||||
.b1 { width: 46vw; height: 46vw; background: var(--violet); top: -8%; left: -6%; animation: drift1 16s ease-in-out infinite; }
|
||||
.b2 { width: 42vw; height: 42vw; background: var(--blue); top: 10%; right: -8%; animation: drift2 19s ease-in-out infinite; }
|
||||
.b3 { width: 34vw; height: 34vw; background: #f5a8e4; bottom: -10%; left: 18%; opacity: .6; animation: drift3 22s ease-in-out infinite; }
|
||||
.b4 { width: 26vw; height: 26vw; background: var(--green); bottom: 4%; right: 14%; opacity: .45; animation: drift1 18s ease-in-out infinite reverse; }
|
||||
@keyframes drift1 { 0%,100% { transform: translate(0,0) scale(1); } 50% { transform: translate(6%, 8%) scale(1.12); } }
|
||||
@keyframes drift2 { 0%,100% { transform: translate(0,0) scale(1); } 50% { transform: translate(-7%, 5%) scale(1.08); } }
|
||||
@keyframes drift3 { 0%,100% { transform: translate(0,0) scale(1); } 50% { transform: translate(5%, -6%) scale(1.15); } }
|
||||
|
||||
.btn { padding: 0.85rem 1.5rem; border-radius: 999px; font-weight: 600; font-size: 0.95rem; transition: transform 0.15s; display: inline-block; }
|
||||
.btn:hover { transform: translateY(-2px); }
|
||||
.btn.primary { background: linear-gradient(90deg, var(--accent), var(--accent-2)); color: #04060a; }
|
||||
.btn.ghost { border: 1px solid var(--line); color: var(--fg); }
|
||||
.btn.big { padding: 1.05rem 2.2rem; font-size: 1.05rem; }
|
||||
.hero-inner { position: relative; z-index: 2; padding: 7rem 0 5rem; }
|
||||
.eyebrow { text-transform: uppercase; letter-spacing: .24em; font-size: .74rem; font-weight: 700; color: var(--violet); margin-bottom: 1.6rem; }
|
||||
|
||||
/* PROOF STRIP */
|
||||
.strip { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; padding: 22px clamp(20px, 5vw, 40px); color: var(--muted); font-size: 0.9rem; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
|
||||
.strip .dot { opacity: 0.4; }
|
||||
.pillmark { display: inline-flex; flex-direction: column; gap: 7px; margin-bottom: 2rem; }
|
||||
.bar { height: 13px; border-radius: 999px; display: block; will-change: transform; }
|
||||
.bar-grad { width: 70px; background: linear-gradient(90deg, var(--violet), var(--blue)); }
|
||||
.bar-row { display: flex; gap: 7px; }
|
||||
.bar-ink { width: 44px; background: var(--ink); }
|
||||
.bar-blue { width: 19px; background: var(--blue); }
|
||||
.bar-green { width: 70px; background: var(--green); }
|
||||
|
||||
.hero-h1 { font-size: clamp(2.5rem, 7vw, 6rem); line-height: .98; letter-spacing: -.04em; font-weight: 900; max-width: 16ch; }
|
||||
.line { display: block; }
|
||||
.word { display: inline-block; overflow: hidden; margin-right: .22em; }
|
||||
.word-in { display: inline-block; will-change: transform; }
|
||||
.hero-h1 .line:first-child { color: var(--ink); opacity: .92; }
|
||||
|
||||
.hero-sub { margin-top: 2rem; max-width: 560px; font-size: clamp(1rem, 1.5vw, 1.22rem); color: #3a3f4c; font-weight: 500; }
|
||||
.hero-actions { margin-top: 2.6rem; display: flex; gap: 1rem; flex-wrap: wrap; }
|
||||
|
||||
.btn { padding: .95rem 1.7rem; border-radius: 999px; font-weight: 700; font-size: .96rem; transition: transform .25s, box-shadow .25s; display: inline-block; }
|
||||
.btn:hover { transform: translateY(-3px); }
|
||||
.btn.primary { background: linear-gradient(100deg, var(--violet), var(--blue)); color: #fff; box-shadow: 0 12px 30px -10px rgba(99,102,241,.6); }
|
||||
.btn.ghost { border: 1px solid rgba(17,24,39,.25); color: var(--ink); }
|
||||
.btn.big { padding: 1.15rem 2.4rem; font-size: 1.05rem; }
|
||||
.cta .btn.ghost, .band .btn.ghost { border-color: rgba(255,255,255,.3); color: #fff; }
|
||||
|
||||
.scroll-hint { position: absolute; bottom: 26px; left: 50%; transform: translateX(-50%); z-index: 2; font-size: .72rem; text-transform: uppercase; letter-spacing: .3em; color: var(--ink); opacity: .5; animation: bob 2s ease-in-out infinite; }
|
||||
@keyframes bob { 0%,100% { transform: translate(-50%,0); } 50% { transform: translate(-50%,6px); } }
|
||||
|
||||
/* MARQUEE */
|
||||
.marquee { background: var(--bg-ink); border-top: 1px solid rgba(255,255,255,.08); border-bottom: 1px solid rgba(255,255,255,.08); padding: 1.1rem 0; overflow: hidden; white-space: nowrap; }
|
||||
.marquee-track { display: inline-block; animation: scrollx 26s linear infinite; font-weight: 700; font-size: 1.05rem; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; }
|
||||
@keyframes scrollx { from { transform: translateX(0); } to { transform: translateX(-50%); } }
|
||||
|
||||
/* BANDS */
|
||||
.band { padding: clamp(56px, 9vw, 110px) 0; }
|
||||
.band.alt { background: var(--bg-2); }
|
||||
.band h2 { font-size: clamp(1.7rem, 3.4vw, 2.6rem); letter-spacing: -0.02em; font-weight: 800; }
|
||||
.lead { margin-top: 0.9rem; max-width: 640px; color: var(--muted); font-size: clamp(1rem, 1.4vw, 1.15rem); }
|
||||
.lead strong { color: var(--fg); }
|
||||
.band { padding: clamp(72px, 12vw, 150px) 0; background: var(--bg-ink); position: relative; }
|
||||
.band.ink:nth-of-type(even) { background: var(--bg-ink-2); }
|
||||
.kicker { text-transform: uppercase; letter-spacing: .2em; font-size: .76rem; font-weight: 700; color: var(--green); margin-bottom: 1.2rem; }
|
||||
.band-h2 { font-size: clamp(1.9rem, 4.4vw, 3.6rem); letter-spacing: -.03em; font-weight: 900; max-width: 18ch; line-height: 1.05; }
|
||||
|
||||
/* GRIDS + CARDS */
|
||||
.grid3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.1rem; margin-top: 2.6rem; }
|
||||
.grid4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.1rem; margin-top: 2.6rem; }
|
||||
@media (max-width: 900px) { .grid3, .grid4 { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 560px) { .grid3, .grid4 { grid-template-columns: 1fr; } }
|
||||
.grid3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.2rem; margin-top: 3.5rem; }
|
||||
@media (max-width: 820px) { .grid3 { grid-template-columns: 1fr; } }
|
||||
.card { background: rgba(255,255,255,.03); border: 1px solid rgba(255,255,255,.08); border-radius: 18px; padding: 1.9rem; transition: border-color .3s, transform .3s, background .3s; }
|
||||
.card:hover { border-color: var(--violet); transform: translateY(-4px); background: rgba(139,92,246,.06); }
|
||||
.card h3 { font-size: 1.5rem; font-weight: 800; letter-spacing: -.02em; margin-bottom: .6rem; }
|
||||
.card p { color: var(--muted); font-size: 1rem; }
|
||||
|
||||
.card {
|
||||
background: rgba(255, 255, 255, 0.025);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 16px;
|
||||
padding: 1.5rem;
|
||||
transition: border-color 0.2s, transform 0.2s;
|
||||
}
|
||||
.card:hover { border-color: rgba(99, 102, 241, 0.5); transform: translateY(-3px); }
|
||||
.card h3 { font-size: 1.15rem; margin-bottom: 0.5rem; letter-spacing: -0.01em; }
|
||||
.card p { color: var(--muted); font-size: 0.96rem; }
|
||||
.card .step { font-size: 0.85rem; font-weight: 700; color: var(--accent-2); letter-spacing: 0.1em; }
|
||||
|
||||
.vertical { display: grid; gap: 2rem; }
|
||||
/* SERVICES LIST */
|
||||
.svc-list { margin-top: 3rem; border-top: 1px solid rgba(255,255,255,.1); }
|
||||
.svc { display: grid; grid-template-columns: 70px 1fr 1.3fr; gap: 1.5rem; align-items: baseline; padding: 1.6rem 0; border-bottom: 1px solid rgba(255,255,255,.1); transition: padding .3s, background .3s; }
|
||||
.svc:hover { padding-left: 1.2rem; background: linear-gradient(90deg, rgba(139,92,246,.08), transparent); }
|
||||
.svc-n { color: var(--violet); font-weight: 700; font-size: .9rem; }
|
||||
.svc-t { font-size: clamp(1.3rem, 2.6vw, 2.1rem); font-weight: 800; letter-spacing: -.02em; }
|
||||
.svc-d { color: var(--muted); font-size: 1rem; }
|
||||
@media (max-width: 760px) { .svc { grid-template-columns: 40px 1fr; } .svc-d { grid-column: 2; } }
|
||||
|
||||
/* PACKAGES */
|
||||
.pkg { position: relative; background: rgba(255, 255, 255, 0.025); border: 1px solid var(--line); border-radius: 16px; padding: 1.6rem 1.4rem; }
|
||||
.pkg.featured { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 20px 50px -20px rgba(99, 102, 241, 0.5); }
|
||||
.pkg .tag { position: absolute; top: -11px; left: 1.4rem; background: linear-gradient(90deg, var(--accent), var(--accent-2)); color: #04060a; font-size: 0.7rem; font-weight: 700; padding: 0.25rem 0.7rem; border-radius: 999px; }
|
||||
.pkg h3 { font-size: 1.2rem; }
|
||||
.pkg .who { color: var(--muted); font-size: 0.85rem; margin: 0.3rem 0 1rem; }
|
||||
.pkg ul { list-style: none; display: flex; flex-direction: column; gap: 0.55rem; }
|
||||
.pkg li { font-size: 0.92rem; padding-left: 1.4rem; position: relative; }
|
||||
.pkg li::before { content: "→"; position: absolute; left: 0; color: var(--accent-2); }
|
||||
.grid4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.2rem; margin-top: 3.5rem; }
|
||||
@media (max-width: 980px) { .grid4 { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 540px) { .grid4 { grid-template-columns: 1fr; } }
|
||||
.pkg { position: relative; background: rgba(255,255,255,.03); border: 1px solid rgba(255,255,255,.1); border-radius: 18px; padding: 1.8rem 1.5rem; transition: transform .3s, border-color .3s; }
|
||||
.pkg:hover { transform: translateY(-4px); border-color: rgba(255,255,255,.25); }
|
||||
.pkg.featured { border-color: var(--violet); background: linear-gradient(180deg, rgba(139,92,246,.12), rgba(255,255,255,.02)); box-shadow: 0 30px 60px -30px rgba(139,92,246,.6); }
|
||||
.pkg .tag { position: absolute; top: -12px; left: 1.5rem; background: linear-gradient(100deg, var(--violet), var(--blue)); color: #fff; font-size: .68rem; font-weight: 800; padding: .3rem .8rem; border-radius: 999px; text-transform: uppercase; letter-spacing: .05em; }
|
||||
.pkg h3 { font-size: 1.35rem; font-weight: 800; }
|
||||
.pkg .who { color: var(--muted); font-size: .85rem; margin: .3rem 0 1.2rem; }
|
||||
.pkg ul { list-style: none; display: flex; flex-direction: column; gap: .65rem; }
|
||||
.pkg li { font-size: .94rem; padding-left: 1.5rem; position: relative; color: #d4d7de; }
|
||||
.pkg li::before { content: "→"; position: absolute; left: 0; color: var(--green); font-weight: 700; }
|
||||
|
||||
/* CTA */
|
||||
.cta { position: relative; overflow: hidden; padding: clamp(70px, 11vw, 130px) 0; text-align: center; }
|
||||
.cta::before { content: ""; position: absolute; inset: 0; background: radial-gradient(50% 80% at 50% 0%, rgba(99, 102, 241, 0.22), transparent 70%); pointer-events: none; }
|
||||
.cta .wrap { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; }
|
||||
.cta h2 { font-size: clamp(1.9rem, 4vw, 3rem); letter-spacing: -0.02em; }
|
||||
.cta .lead { margin-left: auto; margin-right: auto; text-align: center; }
|
||||
.cta .btn { margin-top: 2rem; }
|
||||
.cta { position: relative; overflow: hidden; padding: clamp(90px, 14vw, 180px) 0; text-align: center; background: #efe9fb; color: var(--ink); }
|
||||
.mesh-cta .blob { opacity: .55; }
|
||||
.cta .wrap { position: relative; z-index: 2; display: flex; flex-direction: column; align-items: center; }
|
||||
.cta-h2 { font-size: clamp(2.2rem, 6vw, 4.6rem); font-weight: 900; letter-spacing: -.04em; line-height: 1; }
|
||||
.cta .hero-sub { color: #3a3f4c; text-align: center; margin-left: auto; margin-right: auto; }
|
||||
.cta .btn { margin-top: 2.4rem; }
|
||||
|
||||
/* FOOTER */
|
||||
.ft { border-top: 1px solid var(--line); padding: 2.5rem 0; }
|
||||
.ft .wrap { display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap; font-size: 0.85rem; }
|
||||
.ft .muted { color: var(--muted); }
|
||||
.ft { background: var(--bg-ink); border-top: 1px solid rgba(255,255,255,.08); padding: 3rem 0; }
|
||||
.ft .wrap { display: flex; justify-content: space-between; align-items: center; gap: 1rem; flex-wrap: wrap; }
|
||||
.ft .muted { color: var(--muted); font-size: .85rem; }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* { animation: none !important; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,19 @@
|
|||
import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
import SmoothScroll from "./components/SmoothScroll";
|
||||
import Cursor from "./components/Cursor";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Feedback Studios — La agencia de marketing AI-native",
|
||||
description:
|
||||
"Estrategia humana, ejecución sobre nuestra propia plataforma de IA. Web, SEO, ads y contenido: más rápido, más medible y a mejor coste.",
|
||||
// Test environment — keep everything out of search engines.
|
||||
robots: {
|
||||
index: false,
|
||||
follow: false,
|
||||
nocache: true,
|
||||
googleBot: { index: false, follow: false },
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
|
|
@ -14,7 +23,18 @@ export default function RootLayout({
|
|||
}) {
|
||||
return (
|
||||
<html lang="es">
|
||||
<body>{children}</body>
|
||||
<head>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://api.fontshare.com/v2/css?f[]=satoshi@400,500,700,900&display=swap"
|
||||
/>
|
||||
<meta name="robots" content="noindex, nofollow, noarchive, nosnippet" />
|
||||
</head>
|
||||
<body>
|
||||
<SmoothScroll />
|
||||
<Cursor />
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
176
app/page.tsx
176
app/page.tsx
|
|
@ -1,11 +1,5 @@
|
|||
const services = [
|
||||
{ t: "Web & Desarrollo", d: "Sitios en código, rápidos y editables. Sin plantillas genéricas." },
|
||||
{ t: "SEO", d: "Posicionamiento técnico y de contenido, con reporting real." },
|
||||
{ t: "Paid Ads", d: "Google, Meta, TikTok, LinkedIn — creatividad + optimización con IA." },
|
||||
{ t: "Contenido", d: "Artículos y piezas optimizadas para Google y para IA (GEO)." },
|
||||
{ t: "Diseño & Marca", d: "Identidad y dirección de arte con criterio, no plantillas." },
|
||||
{ t: "Automatización", d: "Flujos que ahorran horas: leads, reporting, operativa." },
|
||||
];
|
||||
import Hero from "./components/Hero";
|
||||
import Reveal from "./components/Reveal";
|
||||
|
||||
const pillars = [
|
||||
{ k: "Más rápido", d: "Entregamos en días lo que una agencia tradicional tarda semanas." },
|
||||
|
|
@ -13,6 +7,15 @@ const pillars = [
|
|||
{ k: "Mejor coste", d: "Nuestra plataforma hace el trabajo pesado. Pagas resultado, no horas." },
|
||||
];
|
||||
|
||||
const services = [
|
||||
{ n: "01", t: "Web & Desarrollo", d: "Sitios en código, rápidos y editables. Sin plantillas." },
|
||||
{ n: "02", t: "SEO", d: "Posicionamiento técnico y de contenido, con reporting real." },
|
||||
{ n: "03", t: "Paid Ads", d: "Google, Meta, TikTok, LinkedIn — creatividad + IA." },
|
||||
{ n: "04", t: "Contenido", d: "Piezas optimizadas para Google y para IA (GEO)." },
|
||||
{ n: "05", t: "Diseño & Marca", d: "Identidad y dirección de arte con criterio." },
|
||||
{ n: "06", t: "Automatización", d: "Flujos que ahorran horas: leads, reporting, operativa." },
|
||||
];
|
||||
|
||||
const packages = [
|
||||
{ n: "Sitio AI-native", who: "Necesitas web ya", f: ["Web editable en código", "SEO base + tracking", "Listo en tiempo récord"] },
|
||||
{ n: "Base", who: "Estás arrancando", f: ["Fundamentos SEO", "Contenido inicial", "Analítica"] },
|
||||
|
|
@ -20,157 +23,108 @@ const packages = [
|
|||
{ n: "Partner", who: "Quieres escalar", f: ["Full-stack de crecimiento", "Automatización a medida", "Prioridad y plataforma"] },
|
||||
];
|
||||
|
||||
const steps = [
|
||||
{ n: "01", t: "Diagnóstico", d: "Analizamos tu negocio, tu mercado y tus números." },
|
||||
{ n: "02", t: "Sistema", d: "Montamos el motor: web, canales, automatización." },
|
||||
{ n: "03", t: "Crecimiento", d: "Iteramos sobre datos. Tú ves el resultado en tu dashboard." },
|
||||
];
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<header className="hd">
|
||||
<span className="logo">Feedback Studios</span>
|
||||
<span className="logo">feedback<span className="logo-dot">studios</span></span>
|
||||
<nav className="hd-nav">
|
||||
<a href="#servicios">Servicios</a>
|
||||
<a href="#paquetes">Paquetes</a>
|
||||
<a href="#contacto" className="hd-cta">Habla con nosotros</a>
|
||||
<a href="#servicios" className="hoverable">Servicios</a>
|
||||
<a href="#paquetes" className="hoverable">Paquetes</a>
|
||||
<a href="#contacto" className="hd-cta hoverable">Habla con nosotros</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
{/* HERO */}
|
||||
<section className="hero">
|
||||
<div className="wrap">
|
||||
<p className="eyebrow">Agencia de marketing AI-native</p>
|
||||
<h1>
|
||||
La mayoría de agencias alquilan sus herramientas.
|
||||
<br />
|
||||
<span className="grad">Nosotros construimos la nuestra.</span>
|
||||
</h1>
|
||||
<p className="sub">
|
||||
Estrategia humana + nuestra propia plataforma de IA. Web, SEO, ads
|
||||
y contenido — más rápido, más medible y a mejor coste que una
|
||||
agencia tradicional.
|
||||
</p>
|
||||
<div className="actions">
|
||||
<a className="btn primary" href="#contacto">Habla con nosotros</a>
|
||||
<a className="btn ghost" href="#servicios">Ver qué hacemos</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<Hero />
|
||||
|
||||
{/* PROOF STRIP */}
|
||||
<section className="strip wrap">
|
||||
<span>+60 marcas confían en nosotros</span>
|
||||
<span className="dot">·</span>
|
||||
<span>Web · SEO · Ads · Contenido</span>
|
||||
<span className="dot">·</span>
|
||||
<span>Resultados medibles</span>
|
||||
</section>
|
||||
{/* MARQUEE */}
|
||||
<div className="marquee" aria-hidden>
|
||||
<div className="marquee-track">
|
||||
{Array.from({ length: 2 }).map((_, i) => (
|
||||
<span key={i}>
|
||||
Web · SEO · Paid Ads · Contenido · Diseño · Automatización · IA ·
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* VENTAJA INJUSTA */}
|
||||
<section className="band">
|
||||
<section className="band ink">
|
||||
<div className="wrap">
|
||||
<h2>Nuestra ventaja injusta</h2>
|
||||
<p className="lead">
|
||||
No alquilamos herramientas ni envolvemos un ChatGPT. Operamos tu
|
||||
marketing sobre <strong>nuestra propia plataforma de IA</strong>.
|
||||
</p>
|
||||
<div className="grid3">
|
||||
<Reveal>
|
||||
<p className="kicker">01 — Ventaja injusta</p>
|
||||
<h2 className="band-h2">
|
||||
No alquilamos herramientas. <span className="grad">Construimos la máquina.</span>
|
||||
</h2>
|
||||
</Reveal>
|
||||
<Reveal className="grid3" stagger={0.12}>
|
||||
{pillars.map((p) => (
|
||||
<div className="card" key={p.k}>
|
||||
<h3>{p.k}</h3>
|
||||
<p>{p.d}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Reveal>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* SERVICIOS */}
|
||||
<section id="servicios" className="band alt">
|
||||
<section id="servicios" className="band ink">
|
||||
<div className="wrap">
|
||||
<h2>Qué hacemos</h2>
|
||||
<p className="lead">Todo lo que necesita tu crecimiento, bajo un mismo techo y un mismo sistema.</p>
|
||||
<div className="grid3">
|
||||
<Reveal>
|
||||
<p className="kicker">02 — Qué hacemos</p>
|
||||
<h2 className="band-h2">Todo tu crecimiento, un mismo sistema.</h2>
|
||||
</Reveal>
|
||||
<Reveal className="svc-list" stagger={0.1}>
|
||||
{services.map((s) => (
|
||||
<div className="card" key={s.t}>
|
||||
<h3>{s.t}</h3>
|
||||
<p>{s.d}</p>
|
||||
<div className="svc hoverable" key={s.t}>
|
||||
<span className="svc-n">{s.n}</span>
|
||||
<span className="svc-t">{s.t}</span>
|
||||
<span className="svc-d">{s.d}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* PROFUNDIDAD POR SECTOR */}
|
||||
<section className="band">
|
||||
<div className="wrap vertical">
|
||||
<div>
|
||||
<p className="eyebrow">Soluciones a medida</p>
|
||||
<h2>Generalistas por fuera, profundos por dentro</h2>
|
||||
<p className="lead">
|
||||
Para sectores exigentes construimos soluciones específicas sobre
|
||||
nuestra plataforma — por ejemplo, clínicas estéticas con captación,
|
||||
seguimiento y operativa a medida. Profundidad que un generalista no tiene.
|
||||
</p>
|
||||
</div>
|
||||
</Reveal>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* PAQUETES */}
|
||||
<section id="paquetes" className="band alt">
|
||||
<section id="paquetes" className="band ink">
|
||||
<div className="wrap">
|
||||
<h2>Paquetes</h2>
|
||||
<p className="lead">Cada uno con un resultado prometido y su dashboard. No listas de tareas.</p>
|
||||
<div className="grid4">
|
||||
<Reveal>
|
||||
<p className="kicker">03 — Paquetes</p>
|
||||
<h2 className="band-h2">Cada uno con un resultado. No listas de tareas.</h2>
|
||||
</Reveal>
|
||||
<Reveal className="grid4" stagger={0.1}>
|
||||
{packages.map((p) => (
|
||||
<div className={"pkg" + (p.featured ? " featured" : "")} key={p.n}>
|
||||
<div className={"pkg hoverable" + (p.featured ? " featured" : "")} key={p.n}>
|
||||
{p.featured && <span className="tag">Más popular</span>}
|
||||
<h3>{p.n}</h3>
|
||||
<p className="who">{p.who}</p>
|
||||
<ul>
|
||||
{p.f.map((x) => (
|
||||
<li key={x}>{x}</li>
|
||||
))}
|
||||
</ul>
|
||||
<ul>{p.f.map((x) => <li key={x}>{x}</li>)}</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Reveal>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* PROCESO */}
|
||||
<section className="band">
|
||||
<div className="wrap">
|
||||
<h2>Cómo trabajamos</h2>
|
||||
<div className="grid3">
|
||||
{steps.map((s) => (
|
||||
<div className="card" key={s.n}>
|
||||
<span className="step">{s.n}</span>
|
||||
<h3>{s.t}</h3>
|
||||
<p>{s.d}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CONTACTO */}
|
||||
{/* CTA */}
|
||||
<section id="contacto" className="cta">
|
||||
<div className="wrap">
|
||||
<h2>¿Hablamos de tu crecimiento?</h2>
|
||||
<p className="lead">Cuéntanos dónde estás y te enseñamos cómo llegar al siguiente nivel.</p>
|
||||
<a className="btn primary big" href="mailto:feedback.studios.design@gmail.com">Habla con nosotros</a>
|
||||
<div className="mesh mesh-cta" aria-hidden>
|
||||
<span className="blob b1" /><span className="blob b3" />
|
||||
</div>
|
||||
<Reveal className="wrap">
|
||||
<h2 className="cta-h2">¿Hablamos de tu <span className="grad">crecimiento</span>?</h2>
|
||||
<p className="hero-sub">Cuéntanos dónde estás y te enseñamos cómo llegar al siguiente nivel.</p>
|
||||
<a className="btn primary big hoverable" href="mailto:feedback.studios.design@gmail.com">Habla con nosotros</a>
|
||||
</Reveal>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer className="ft">
|
||||
<div className="wrap">
|
||||
<span>© 2026 Feedback Studios</span>
|
||||
<span className="muted">Agencia de marketing AI-native · construido sobre infraestructura propia</span>
|
||||
<span className="logo">feedback<span className="logo-dot">studios</span></span>
|
||||
<span className="muted">© 2026 · Agencia de marketing AI-native · infraestructura propia</span>
|
||||
</div>
|
||||
</footer>
|
||||
</>
|
||||
|
|
|
|||
8
app/robots.ts
Normal file
8
app/robots.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import type { MetadataRoute } from "next";
|
||||
|
||||
// Test environment: disallow all crawling.
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: { userAgent: "*", disallow: "/" },
|
||||
};
|
||||
}
|
||||
|
|
@ -1,6 +1,17 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
// Test environment — hard noindex at the header level too.
|
||||
source: "/:path*",
|
||||
headers: [
|
||||
{ key: "X-Robots-Tag", value: "noindex, nofollow, noarchive, nosnippet" },
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
"start": "next start -H 0.0.0.0 -p 3000"
|
||||
},
|
||||
"dependencies": {
|
||||
"gsap": "^3.12.5",
|
||||
"lenis": "^1.1.14",
|
||||
"next": "15.1.6",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0"
|
||||
|
|
|
|||
Loading…
Reference in a new issue