feat: bespoke animated design (GSAP+Lenis, brand identity, noindex)

This commit is contained in:
Feedback Studios 2026-06-16 05:08:21 +00:00
parent 32bf6c828c
commit d4dbef11ed
10 changed files with 470 additions and 201 deletions

49
app/components/Cursor.tsx Normal file
View 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
View 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
View 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>
);
}

View 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;
}

View file

@ -1,122 +1,162 @@
:root { :root {
--bg: #0a0a0b; --violet: #8b5cf6;
--bg-2: #101013; --blue: #3b82f6;
--fg: #fafafa; --green: #10b981;
--muted: #a1a1aa; --ink: #111827;
--line: rgba(255, 255, 255, 0.1); --bg-ink: #08080d;
--accent: #6366f1; --bg-ink-2: #0e0e16;
--accent-2: #22d3ee; --fg: #f6f6f9;
--maxw: 1140px; --muted: #9aa0ad;
--maxw: 1180px;
} }
* { box-sizing: border-box; margin: 0; padding: 0; } * { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; } html { scroll-behavior: smooth; }
body { body {
background: var(--bg); background: var(--bg-ink);
color: var(--fg); 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; -webkit-font-smoothing: antialiased;
line-height: 1.5; line-height: 1.45;
overflow-x: hidden;
} }
a { color: inherit; text-decoration: none; } 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 { .grad {
background: linear-gradient(90deg, var(--accent), var(--accent-2)); background: linear-gradient(100deg, var(--violet), var(--blue) 60%, var(--green));
-webkit-background-clip: text; -webkit-background-clip: text; background-clip: text; color: transparent;
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 */ /* HEADER */
.hd { .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; display: flex; justify-content: space-between; align-items: center;
padding: 16px clamp(20px, 5vw, 40px); padding: 18px clamp(22px, 5vw, 48px);
background: rgba(10, 10, 11, 0.7); mix-blend-mode: difference; color: #fff;
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--line);
} }
.logo { font-weight: 700; letter-spacing: -0.02em; } .logo { font-weight: 900; letter-spacing: -.03em; font-size: 1.05rem; }
.hd-nav { display: flex; gap: 1.6rem; align-items: center; font-size: 0.92rem; } .logo-dot { font-weight: 400; opacity: .7; }
.hd-nav a { color: var(--muted); transition: color 0.15s; } .hd-nav { display: flex; gap: 1.8rem; align-items: center; font-size: .92rem; font-weight: 500; }
.hd-nav a:hover { color: var(--fg); } .hd-cta { border: 1px solid rgba(255,255,255,.5); padding: .5rem 1.1rem; border-radius: 999px; }
.hd-cta { color: var(--fg) !important; border: 1px solid var(--line); padding: 0.5rem 1rem; border-radius: 999px; } @media (max-width: 680px) { .hd-nav a:not(.hd-cta) { display: none; } }
@media (max-width: 640px) { .hd-nav a:not(.hd-cta) { display: none; } }
/* HERO */ /* HERO */
.hero { position: relative; overflow: hidden; padding: clamp(80px, 14vw, 160px) 0 clamp(60px, 9vw, 110px); } .hero {
.hero::before { position: relative; min-height: 100svh; display: flex; align-items: center;
content: ""; position: absolute; inset: 0; pointer-events: none; background: linear-gradient(180deg, #efe9fb 0%, #f6f3fd 55%, #eef0fe 100%);
background: radial-gradient(55% 50% at 72% -10%, rgba(99, 102, 241, 0.28), transparent 70%), color: var(--ink); overflow: hidden;
radial-gradient(40% 45% at 0% 110%, rgba(34, 211, 238, 0.12), transparent 70%);
} }
.hero .wrap { position: relative; z-index: 1; } .mesh { position: absolute; inset: 0; z-index: 0; }
.eyebrow { text-transform: uppercase; letter-spacing: 0.18em; font-size: 0.76rem; color: var(--accent-2); margin-bottom: 1.1rem; } .blob { position: absolute; border-radius: 50%; filter: blur(70px); opacity: .75; will-change: transform; }
h1 { font-size: clamp(2.3rem, 6vw, 4.6rem); line-height: 1.04; letter-spacing: -0.03em; font-weight: 800; } .b1 { width: 46vw; height: 46vw; background: var(--violet); top: -8%; left: -6%; animation: drift1 16s ease-in-out infinite; }
.sub { margin-top: 1.5rem; max-width: 620px; font-size: clamp(1rem, 1.5vw, 1.2rem); color: var(--muted); } .b2 { width: 42vw; height: 42vw; background: var(--blue); top: 10%; right: -8%; animation: drift2 19s ease-in-out infinite; }
.actions { margin-top: 2.2rem; display: flex; gap: 1rem; flex-wrap: wrap; } .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; } .hero-inner { position: relative; z-index: 2; padding: 7rem 0 5rem; }
.btn:hover { transform: translateY(-2px); } .eyebrow { text-transform: uppercase; letter-spacing: .24em; font-size: .74rem; font-weight: 700; color: var(--violet); margin-bottom: 1.6rem; }
.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; }
/* PROOF STRIP */ .pillmark { display: inline-flex; flex-direction: column; gap: 7px; margin-bottom: 2rem; }
.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); } .bar { height: 13px; border-radius: 999px; display: block; will-change: transform; }
.strip .dot { opacity: 0.4; } .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 */ /* BANDS */
.band { padding: clamp(56px, 9vw, 110px) 0; } .band { padding: clamp(72px, 12vw, 150px) 0; background: var(--bg-ink); position: relative; }
.band.alt { background: var(--bg-2); } .band.ink:nth-of-type(even) { background: var(--bg-ink-2); }
.band h2 { font-size: clamp(1.7rem, 3.4vw, 2.6rem); letter-spacing: -0.02em; font-weight: 800; } .kicker { text-transform: uppercase; letter-spacing: .2em; font-size: .76rem; font-weight: 700; color: var(--green); margin-bottom: 1.2rem; }
.lead { margin-top: 0.9rem; max-width: 640px; color: var(--muted); font-size: clamp(1rem, 1.4vw, 1.15rem); } .band-h2 { font-size: clamp(1.9rem, 4.4vw, 3.6rem); letter-spacing: -.03em; font-weight: 900; max-width: 18ch; line-height: 1.05; }
.lead strong { color: var(--fg); }
/* GRIDS + CARDS */ .grid3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.2rem; margin-top: 3.5rem; }
.grid3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.1rem; margin-top: 2.6rem; } @media (max-width: 820px) { .grid3 { grid-template-columns: 1fr; } }
.grid4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.1rem; margin-top: 2.6rem; } .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; }
@media (max-width: 900px) { .grid3, .grid4 { grid-template-columns: repeat(2, 1fr); } } .card:hover { border-color: var(--violet); transform: translateY(-4px); background: rgba(139,92,246,.06); }
@media (max-width: 560px) { .grid3, .grid4 { grid-template-columns: 1fr; } } .card h3 { font-size: 1.5rem; font-weight: 800; letter-spacing: -.02em; margin-bottom: .6rem; }
.card p { color: var(--muted); font-size: 1rem; }
.card { /* SERVICES LIST */
background: rgba(255, 255, 255, 0.025); .svc-list { margin-top: 3rem; border-top: 1px solid rgba(255,255,255,.1); }
border: 1px solid var(--line); .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; }
border-radius: 16px; .svc:hover { padding-left: 1.2rem; background: linear-gradient(90deg, rgba(139,92,246,.08), transparent); }
padding: 1.5rem; .svc-n { color: var(--violet); font-weight: 700; font-size: .9rem; }
transition: border-color 0.2s, transform 0.2s; .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; }
.card:hover { border-color: rgba(99, 102, 241, 0.5); transform: translateY(-3px); } @media (max-width: 760px) { .svc { grid-template-columns: 40px 1fr; } .svc-d { grid-column: 2; } }
.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; }
/* PACKAGES */ /* PACKAGES */
.pkg { position: relative; background: rgba(255, 255, 255, 0.025); border: 1px solid var(--line); border-radius: 16px; padding: 1.6rem 1.4rem; } .grid4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.2rem; margin-top: 3.5rem; }
.pkg.featured { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 20px 50px -20px rgba(99, 102, 241, 0.5); } @media (max-width: 980px) { .grid4 { grid-template-columns: repeat(2, 1fr); } }
.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; } @media (max-width: 540px) { .grid4 { grid-template-columns: 1fr; } }
.pkg h3 { font-size: 1.2rem; } .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 .who { color: var(--muted); font-size: 0.85rem; margin: 0.3rem 0 1rem; } .pkg:hover { transform: translateY(-4px); border-color: rgba(255,255,255,.25); }
.pkg ul { list-style: none; display: flex; flex-direction: column; gap: 0.55rem; } .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 li { font-size: 0.92rem; padding-left: 1.4rem; position: relative; } .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 li::before { content: "→"; position: absolute; left: 0; color: var(--accent-2); } .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 */
.cta { position: relative; overflow: hidden; padding: clamp(70px, 11vw, 130px) 0; text-align: center; } .cta { position: relative; overflow: hidden; padding: clamp(90px, 14vw, 180px) 0; text-align: center; background: #efe9fb; color: var(--ink); }
.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; } .mesh-cta .blob { opacity: .55; }
.cta .wrap { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; } .cta .wrap { position: relative; z-index: 2; display: flex; flex-direction: column; align-items: center; }
.cta h2 { font-size: clamp(1.9rem, 4vw, 3rem); letter-spacing: -0.02em; } .cta-h2 { font-size: clamp(2.2rem, 6vw, 4.6rem); font-weight: 900; letter-spacing: -.04em; line-height: 1; }
.cta .lead { margin-left: auto; margin-right: auto; text-align: center; } .cta .hero-sub { color: #3a3f4c; text-align: center; margin-left: auto; margin-right: auto; }
.cta .btn { margin-top: 2rem; } .cta .btn { margin-top: 2.4rem; }
/* FOOTER */ /* FOOTER */
.ft { border-top: 1px solid var(--line); padding: 2.5rem 0; } .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; gap: 1rem; flex-wrap: wrap; font-size: 0.85rem; } .ft .wrap { display: flex; justify-content: space-between; align-items: center; gap: 1rem; flex-wrap: wrap; }
.ft .muted { color: var(--muted); } .ft .muted { color: var(--muted); font-size: .85rem; }
@media (prefers-reduced-motion: reduce) {
* { animation: none !important; }
}

View file

@ -1,10 +1,19 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import "./globals.css"; import "./globals.css";
import SmoothScroll from "./components/SmoothScroll";
import Cursor from "./components/Cursor";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Feedback Studios — La agencia de marketing AI-native", title: "Feedback Studios — La agencia de marketing AI-native",
description: 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.", "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({ export default function RootLayout({
@ -14,7 +23,18 @@ export default function RootLayout({
}) { }) {
return ( return (
<html lang="es"> <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> </html>
); );
} }

View file

@ -1,11 +1,5 @@
const services = [ import Hero from "./components/Hero";
{ t: "Web & Desarrollo", d: "Sitios en código, rápidos y editables. Sin plantillas genéricas." }, import Reveal from "./components/Reveal";
{ 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." },
];
const pillars = [ const pillars = [
{ k: "Más rápido", d: "Entregamos en días lo que una agencia tradicional tarda semanas." }, { 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." }, { 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 = [ 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: "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"] }, { 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"] }, { 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() { export default function Home() {
return ( return (
<> <>
<header className="hd"> <header className="hd">
<span className="logo">Feedback Studios</span> <span className="logo">feedback<span className="logo-dot">studios</span></span>
<nav className="hd-nav"> <nav className="hd-nav">
<a href="#servicios">Servicios</a> <a href="#servicios" className="hoverable">Servicios</a>
<a href="#paquetes">Paquetes</a> <a href="#paquetes" className="hoverable">Paquetes</a>
<a href="#contacto" className="hd-cta">Habla con nosotros</a> <a href="#contacto" className="hd-cta hoverable">Habla con nosotros</a>
</nav> </nav>
</header> </header>
<main> <main>
{/* HERO */} <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>
{/* PROOF STRIP */} {/* MARQUEE */}
<section className="strip wrap"> <div className="marquee" aria-hidden>
<span>+60 marcas confían en nosotros</span> <div className="marquee-track">
<span className="dot">·</span> {Array.from({ length: 2 }).map((_, i) => (
<span>Web · SEO · Ads · Contenido</span> <span key={i}>
<span className="dot">·</span> Web · SEO · Paid Ads · Contenido · Diseño · Automatización · IA ·&nbsp;
<span>Resultados medibles</span> </span>
</section> ))}
</div>
</div>
{/* VENTAJA INJUSTA */} {/* VENTAJA INJUSTA */}
<section className="band"> <section className="band ink">
<div className="wrap"> <div className="wrap">
<h2>Nuestra ventaja injusta</h2> <Reveal>
<p className="lead"> <p className="kicker">01 Ventaja injusta</p>
No alquilamos herramientas ni envolvemos un ChatGPT. Operamos tu <h2 className="band-h2">
marketing sobre <strong>nuestra propia plataforma de IA</strong>. No alquilamos herramientas. <span className="grad">Construimos la máquina.</span>
</p> </h2>
<div className="grid3"> </Reveal>
<Reveal className="grid3" stagger={0.12}>
{pillars.map((p) => ( {pillars.map((p) => (
<div className="card" key={p.k}> <div className="card" key={p.k}>
<h3>{p.k}</h3> <h3>{p.k}</h3>
<p>{p.d}</p> <p>{p.d}</p>
</div> </div>
))} ))}
</div> </Reveal>
</div> </div>
</section> </section>
{/* SERVICIOS */} {/* SERVICIOS */}
<section id="servicios" className="band alt"> <section id="servicios" className="band ink">
<div className="wrap"> <div className="wrap">
<h2>Qué hacemos</h2> <Reveal>
<p className="lead">Todo lo que necesita tu crecimiento, bajo un mismo techo y un mismo sistema.</p> <p className="kicker">02 Qué hacemos</p>
<div className="grid3"> <h2 className="band-h2">Todo tu crecimiento, un mismo sistema.</h2>
</Reveal>
<Reveal className="svc-list" stagger={0.1}>
{services.map((s) => ( {services.map((s) => (
<div className="card" key={s.t}> <div className="svc hoverable" key={s.t}>
<h3>{s.t}</h3> <span className="svc-n">{s.n}</span>
<p>{s.d}</p> <span className="svc-t">{s.t}</span>
<span className="svc-d">{s.d}</span>
</div> </div>
))} ))}
</div> </Reveal>
</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>
</div> </div>
</section> </section>
{/* PAQUETES */} {/* PAQUETES */}
<section id="paquetes" className="band alt"> <section id="paquetes" className="band ink">
<div className="wrap"> <div className="wrap">
<h2>Paquetes</h2> <Reveal>
<p className="lead">Cada uno con un resultado prometido y su dashboard. No listas de tareas.</p> <p className="kicker">03 Paquetes</p>
<div className="grid4"> <h2 className="band-h2">Cada uno con un resultado. No listas de tareas.</h2>
</Reveal>
<Reveal className="grid4" stagger={0.1}>
{packages.map((p) => ( {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>} {p.featured && <span className="tag">Más popular</span>}
<h3>{p.n}</h3> <h3>{p.n}</h3>
<p className="who">{p.who}</p> <p className="who">{p.who}</p>
<ul> <ul>{p.f.map((x) => <li key={x}>{x}</li>)}</ul>
{p.f.map((x) => (
<li key={x}>{x}</li>
))}
</ul>
</div> </div>
))} ))}
</div> </Reveal>
</div> </div>
</section> </section>
{/* PROCESO */} {/* CTA */}
<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 */}
<section id="contacto" className="cta"> <section id="contacto" className="cta">
<div className="wrap"> <div className="mesh mesh-cta" aria-hidden>
<h2>¿Hablamos de tu crecimiento?</h2> <span className="blob b1" /><span className="blob b3" />
<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> </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> </section>
</main> </main>
<footer className="ft"> <footer className="ft">
<div className="wrap"> <div className="wrap">
<span>© 2026 Feedback Studios</span> <span className="logo">feedback<span className="logo-dot">studios</span></span>
<span className="muted">Agencia de marketing AI-native · construido sobre infraestructura propia</span> <span className="muted">© 2026 · Agencia de marketing AI-native · infraestructura propia</span>
</div> </div>
</footer> </footer>
</> </>

8
app/robots.ts Normal file
View 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: "/" },
};
}

View file

@ -1,6 +1,17 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
reactStrictMode: true, 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; export default nextConfig;

View file

@ -11,6 +11,8 @@
"start": "next start -H 0.0.0.0 -p 3000" "start": "next start -H 0.0.0.0 -p 3000"
}, },
"dependencies": { "dependencies": {
"gsap": "^3.12.5",
"lenis": "^1.1.14",
"next": "15.1.6", "next": "15.1.6",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0" "react-dom": "19.0.0"