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

View file

@ -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>
);
}

View file

@ -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 ·&nbsp;
</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
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} */
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;

View file

@ -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"