feat: premium dark 'Signal' homepage — EN revenue content, generated assets, SEO/AEO, animated

This commit is contained in:
Feedback Studios 2026-06-16 06:25:07 +00:00
parent daeb3f6cea
commit ae0be1c48e
23 changed files with 1907 additions and 1291 deletions

View file

@ -0,0 +1,67 @@
"use client";
/**
* CaseStudies large film-cell cards. Each is a real <article> with the
* generated capsule visual, a problem -> result narrative, the method, and the
* headline metric. Alternating media side creates an editorial rhythm; refined
* scale-on-hover on the image. Images are explicit-sized + lazy-loaded.
*/
import Reveal from "./Reveal";
import { cases } from "../content";
export default function CaseStudies() {
return (
<section id="work" className="cases" aria-label="Case studies">
<div className="wrap">
<Reveal>
<p className="kicker">Case studies</p>
<h2 className="section__title">Proof, not promises.</h2>
<p className="section__lead">
A few of the businesses we&apos;ve grown. Same approach every time:
tie the work to the revenue, then prove it.
</p>
</Reveal>
<div className="cases__list">
{cases.map((c, i) => (
<Reveal key={c.tag} y={60}>
<article className="case hoverable" data-cursor="view work">
<div className="case__media">
<span className="case__tag">{c.tag}</span>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={c.img}
alt={c.alt}
className="case__img"
width={900}
height={760}
loading={i === 0 ? "eager" : "lazy"}
decoding="async"
/>
</div>
<div className="case__body">
<p className="case__problem">{c.problem}</p>
<h3 className="case__result">
<em>{c.result}</em>
</h3>
<p className="case__how">{c.how}</p>
<p className="case__metric">
<span className="case__metric-num">{c.metricNum}</span>
<span className="case__metric-label">{c.metricLabel}</span>
</p>
</div>
</article>
</Reveal>
))}
</div>
<div className="cases__cta">
<a className="btn btn--ghost hoverable" href="#contact">
<span>View all work </span>
</a>
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,74 @@
"use client";
/**
* CountUp animates a number from 0 to its target when it scrolls into view.
* Uses GSAP + ScrollTrigger, formats with optional prefix/suffix/decimals and
* tabular figures. Under reduced-motion it renders the final value immediately.
* The full value is also written to the DOM on mount so it is correct even if
* JS/animation never runs (accessible + SSR-safe).
*/
import { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
type Props = {
value: number;
prefix?: string;
suffix?: string;
decimals?: number;
className?: string;
};
export default function CountUp({
value,
prefix = "",
suffix = "",
decimals = 0,
className = "",
}: Props) {
const ref = useRef<HTMLSpanElement>(null);
const format = (n: number) =>
`${prefix}${n.toLocaleString("en-US", {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals,
})}${suffix}`;
useEffect(() => {
const el = ref.current;
if (!el) return;
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
el.textContent = format(value);
return;
}
const obj = { n: 0 };
el.textContent = format(0);
const ctx = gsap.context(() => {
gsap.to(obj, {
n: value,
duration: 2,
ease: "power2.out",
scrollTrigger: { trigger: el, start: "top 85%", once: true },
onUpdate: () => {
el.textContent = format(obj.n);
},
});
}, el);
return () => ctx.revert();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value, prefix, suffix, decimals]);
// Render full value at SSR/first paint; the effect resets to 0 then animates.
return (
<span ref={ref} className={className}>
{format(value)}
</span>
);
}

62
app/components/Faq.tsx Normal file
View file

@ -0,0 +1,62 @@
"use client";
/**
* Faq accessible accordion built on real <button> controls with
* aria-expanded / aria-controls and a CSS grid-rows reveal (no JS height math,
* animates cleanly, collapsible content stays in the DOM for SEO/AEO). The
* answer copy matches the FAQPage JSON-LD in layout.tsx verbatim.
*/
import { useState } from "react";
import Reveal from "./Reveal";
import { faqs } from "../content";
export default function Faq() {
const [open, setOpen] = useState<number | null>(0);
return (
<section id="faq" className="faq" aria-label="Frequently asked questions">
<div className="wrap faq__inner">
<div className="faq__head">
<p className="kicker">FAQ</p>
<h2 className="section__title">
Questions about working with a digital marketing agency
</h2>
</div>
<div className="faq__list">
{faqs.map((f, i) => {
const isOpen = open === i;
return (
<div className="faq__item" key={f.q} data-open={isOpen}>
<h3>
<button
type="button"
className="faq__q hoverable"
aria-expanded={isOpen}
aria-controls={`faq-a-${i}`}
id={`faq-q-${i}`}
onClick={() => setOpen(isOpen ? null : i)}
>
{f.q}
<span className="faq__icon" aria-hidden="true" />
</button>
</h3>
<div
className="faq__a"
id={`faq-a-${i}`}
role="region"
aria-labelledby={`faq-q-${i}`}
>
<div className="faq__a-inner">
<p>{f.a}</p>
</div>
</div>
</div>
);
})}
</div>
</div>
</section>
);
}

View file

@ -1,11 +1,20 @@
"use client"; "use client";
/**
* Hero type IS the hero. A display headline at 8-14vw reveals line by line
* (manual masked split via KineticText), the iridescent wave asset floats as a
* tasteful accent layer, and a trust line anchors the claim. Parallax on the
* accent + headline as the hero scrolls out.
*/
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { gsap } from "gsap"; import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import KineticText from "./KineticText"; import KineticText from "./KineticText";
import PillMark from "./PillMark";
import Magnetic from "./Magnetic"; import Magnetic from "./Magnetic";
gsap.registerPlugin(ScrollTrigger);
export default function Hero() { export default function Hero() {
const root = useRef<HTMLElement>(null); const root = useRef<HTMLElement>(null);
@ -17,35 +26,22 @@ export default function Hero() {
const ctx = gsap.context(() => { const ctx = gsap.context(() => {
const tl = gsap.timeline({ defaults: { ease: "power4.out" } }); const tl = gsap.timeline({ defaults: { ease: "power4.out" } });
tl.from(".hero__eyebrow", { y: 18, opacity: 0, duration: 0.9, delay: 0.15 }) tl.from(".hero__eyebrow", { y: 18, opacity: 0, duration: 0.9, delay: 0.15 })
.from(".hero__accent", { opacity: 0, scale: 1.06, duration: 1.4 }, 0)
.from(".hero__sub", { y: 22, opacity: 0, duration: 0.9 }, "-=0.2") .from(".hero__sub", { y: 22, opacity: 0, duration: 0.9 }, "-=0.2")
.from( .from(".hero__actions > *", { y: 20, opacity: 0, stagger: 0.1, duration: 0.7 }, "-=0.5")
".hero__actions > *", .from(".hero__trust", { opacity: 0, duration: 0.8 }, "-=0.4");
{ y: 20, opacity: 0, stagger: 0.1, duration: 0.7 },
"-=0.5"
)
.from(".hero__meta", { opacity: 0, duration: 0.8 }, "-=0.4");
// parallax on the floating mark as you scroll the hero out // parallax: accent drifts up, headline lifts + fades as hero scrolls out
gsap.to(".hero__mark", { gsap.to(".hero__accent", {
yPercent: -22, yPercent: -18,
ease: "none", ease: "none",
scrollTrigger: { scrollTrigger: { trigger: el, start: "top top", end: "bottom top", scrub: true },
trigger: el,
start: "top top",
end: "bottom top",
scrub: true,
},
}); });
gsap.to(".hero__display", { gsap.to(".hero__display", {
yPercent: 12, yPercent: 14,
opacity: 0.25, opacity: 0.18,
ease: "none", ease: "none",
scrollTrigger: { scrollTrigger: { trigger: el, start: "top top", end: "bottom top", scrub: true },
trigger: el,
start: "top top",
end: "bottom top",
scrub: true,
},
}); });
}, el); }, el);
@ -54,68 +50,63 @@ export default function Hero() {
return ( return (
<section className="hero" ref={root}> <section className="hero" ref={root}>
{/* iridescent wave accent — decorative texture layer */}
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src="/assets/hero-iridescent-1.png"
alt=""
aria-hidden="true"
className="hero__accent"
width={1344}
height={768}
fetchPriority="high"
decoding="async"
/>
<div className="hero__inner wrap"> <div className="hero__inner wrap">
<p className="hero__eyebrow"> <p className="hero__eyebrow">
<span className="dot" aria-hidden="true" /> <span className="dot" aria-hidden="true" />
Agencia de marketing AI-native Digital marketing agency
</p> </p>
<h1 className="hero__display"> <h1 className="hero__display">
<KineticText as="span" className="hero__line" text="No alquilamos" immediate delay={0.25} /> <KineticText as="span" className="hero__line" text="Marketing that" immediate delay={0.25} />
<span className="hero__line hero__line--mixed"> <KineticText as="span" className="hero__line" text="grows your" immediate delay={0.38} />
<KineticText as="span" text="las" immediate delay={0.4} />
<em className="hero__serif"> herramientas.</em>
</span>
<KineticText <KineticText
as="span" as="span"
className="hero__line hero__line--grad" className="hero__line hero__line--grad"
text="Construimos la máquina." text="revenue."
immediate immediate
delay={0.5} delay={0.5}
highlight={[0, 2]} highlight={[0, 0]}
/> />
</h1> </h1>
<div className="hero__mark" aria-hidden="true">
<PillMark animate breathe />
</div>
<p className="hero__sub"> <p className="hero__sub">
Estrategia humana sobre <strong>nuestra propia plataforma de IA</strong>. We&apos;re a results-driven digital marketing agency. We run paid, SEO,
Web, SEO, ads y contenido más rápido, más medible y a mejor coste que and content programs built around <strong>your revenue targets</strong>,
una agencia tradicional. then show you what they returned. Every month.
</p> </p>
<div className="hero__actions"> <div className="hero__actions">
<Magnetic strength={0.5}> <Magnetic strength={0.5}>
<a className="btn btn--primary hoverable" href="#contacto"> <a className="btn btn--primary hoverable" href="#contact" data-cursor="let's talk">
<span>Habla con nosotros</span> <span>Get your growth audit</span>
</a> </a>
</Magnetic> </Magnetic>
<Magnetic strength={0.3}> <a className="hero__secondary hoverable" href="#results">
<a className="btn btn--ghost hoverable" href="#servicios"> See the results <span aria-hidden="true"></span>
<span>Ver qué hacemos</span>
</a> </a>
</Magnetic>
</div> </div>
<dl className="hero__meta"> <p className="hero__trust">
<div> <strong>$40M+</strong> in client revenue generated
<dt>Entrega</dt> <span className="hero__trust-sep" aria-hidden="true" />
<dd>en días, no semanas</dd> <strong>50+</strong> brands grown
</div> </p>
<div>
<dt>Reporting</dt>
<dd>cada acción medida</dd>
</div>
<div>
<dt>Infraestructura</dt>
<dd>propia, no alquilada</dd>
</div>
</dl>
</div> </div>
<a className="hero__scroll hoverable" href="#ventaja" aria-label="Bajar a la siguiente sección"> <a className="hero__scroll hoverable" href="#position" aria-label="Scroll to the next section">
<span>scroll</span> <span>scroll</span>
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"> <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
<path d="M12 4v16m0 0l-6-6m6 6l6-6" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> <path d="M12 4v16m0 0l-6-6m6 6l6-6" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />

View file

@ -3,11 +3,12 @@
/** /**
* Iridescence generative WebGL backdrop. * Iridescence generative WebGL backdrop.
* *
* A living interpretation of the brand cloudscape: domain-warped fractal noise * A living interpretation of the brand wave: domain-warped fractal noise mapped
* mapped onto an iridescent pastel palette (lavender -> blue -> rose -> mint), * onto the brand spectrum (violet -> blue -> rose -> emerald) glowing out of a
* with a faint rainbow refraction band and animated film grain. Drifts on its * near-black field, with a faint rainbow refraction band and animated film
* own and leans toward the cursor. Falls back to a static CSS gradient when * grain. Drifts on its own and leans toward the cursor. Falls back to a static
* WebGL is unavailable or the user prefers reduced motion. * dark CSS gradient when WebGL is unavailable or the user prefers reduced
* motion.
* *
* Renders behind everything (fixed, -z) and is purely decorative (aria-hidden). * Renders behind everything (fixed, -z) and is purely decorative (aria-hidden).
*/ */
@ -34,8 +35,8 @@ const FRAG = /* glsl */ `
const vec3 VIOLET = vec3(0.545, 0.361, 0.965); // #8b5cf6 const vec3 VIOLET = vec3(0.545, 0.361, 0.965); // #8b5cf6
const vec3 BLUE = vec3(0.231, 0.510, 0.965); // #3b82f6 const vec3 BLUE = vec3(0.231, 0.510, 0.965); // #3b82f6
const vec3 ROSE = vec3(0.957, 0.475, 0.851); // soft pink const vec3 ROSE = vec3(0.957, 0.475, 0.851); // soft pink
const vec3 MINT = vec3(0.063, 0.725, 0.506); // #10b981 softened const vec3 MINT = vec3(0.204, 0.827, 0.600); // #34d399 emerald
const vec3 HAZE = vec3(0.945, 0.949, 0.992); // near-white lavender const vec3 BASE = vec3(0.031, 0.031, 0.047); // near-black #08080c
// -- hash / value noise -------------------------------------------------- // -- hash / value noise --------------------------------------------------
vec2 hash22(vec2 p) { vec2 hash22(vec2 p) {
@ -91,34 +92,34 @@ const FRAG = /* glsl */ `
); );
float f = fbm(p + 2.0 * r); float f = fbm(p + 2.0 * r);
// build the iridescent gradient from the field // the brand spectrum, glowing ADDITIVELY out of a near-black base
vec3 col = HAZE; vec3 glow = vec3(0.0);
col = mix(col, VIOLET, smoothstep(0.15, 0.85, f)); glow += VIOLET * smoothstep(0.35, 0.95, f);
col = mix(col, BLUE, smoothstep(0.30, 0.95, r.x)); glow += BLUE * smoothstep(0.45, 1.0, r.x) * 0.9;
col = mix(col, ROSE, smoothstep(0.55, 1.05, q.y) * 0.65); glow += ROSE * smoothstep(0.62, 1.05, q.y) * 0.5;
col = mix(col, MINT, smoothstep(0.62, 1.0, r.y) * 0.30); glow += MINT * smoothstep(0.66, 1.0, r.y) * 0.55;
// central radiant bloom (echoes the cloudscape light source up high) // concentrate the light into a soft diagonal wave band (echoes the asset)
vec2 center = vec2(0.5 * aspect, 0.86); float band = smoothstep(0.62, 0.0, abs((uv.x - uv.y) * 1.25 - 0.05 + 0.06 * sin(t * 2.0)));
float d = distance(p, center); float field = smoothstep(0.2, 0.85, f);
float bloom = smoothstep(0.9, 0.0, d); float energy = (0.35 + 0.65 * band) * field;
col = mix(col, HAZE, bloom * 0.55);
// faint rainbow refraction band, lower-left like the asset vec3 col = BASE + glow * energy * (0.55 + 0.45 * uIntensity);
float band = smoothstep(0.5, 0.0, abs((uv.x - uv.y) * 1.3 - 0.15 + 0.05 * sin(t * 2.0)));
// faint rainbow refraction along the band
vec3 spectrum = 0.5 + 0.5 * cos(6.2831 * (vec3(0.0, 0.33, 0.67) + (uv.x + uv.y) * 0.7)); vec3 spectrum = 0.5 + 0.5 * cos(6.2831 * (vec3(0.0, 0.33, 0.67) + (uv.x + uv.y) * 0.7));
col += spectrum * band * 0.06 * (0.4 + uIntensity); col += spectrum * band * 0.05 * (0.4 + uIntensity);
// lift overall to keep it airy / pastel and protect text legibility // darken edges so content stays the focus
col = mix(col, vec3(1.0), 0.18); float vig = smoothstep(1.3, 0.2, distance(uv, vec2(0.5)));
col *= 0.55 + 0.45 * vig;
// soft vignette to anchor content readability // keep the base from washing out; clamp the glow softly
float vig = smoothstep(1.25, 0.25, distance(uv, vec2(0.5))); col = max(col, BASE * 0.6);
col *= 0.82 + 0.18 * vig;
// animated grain to kill banding + add texture // animated grain to kill banding + add texture
float g = grain(uv, floor(uTime * 12.0) * 0.5); float g = grain(uv, floor(uTime * 12.0) * 0.5);
col += (g - 0.5) * 0.035; col += (g - 0.5) * 0.03;
gl_FragColor = vec4(col, 1.0); gl_FragColor = vec4(col, 1.0);
} }
@ -148,7 +149,7 @@ export default function Iridescence() {
} }
const gl = renderer.gl; const gl = renderer.gl;
gl.clearColor(0.95, 0.95, 0.99, 1); gl.clearColor(0.031, 0.031, 0.047, 1);
const uniforms = { const uniforms = {
uTime: { value: 0 }, uTime: { value: 0 },

View file

@ -1,18 +1,20 @@
"use client"; "use client";
/** /**
* Marquee seamless looping band of disciplines, separated by the brand pill * ProofBar "trusted by" band. A labelled, seamless looping marquee of the
* glyph. Pure CSS animation (transform only); pauses under reduced-motion via * industries we grow, separated by the brand pill glyph. Pure CSS transform
* the stylesheet. Decorative. * animation; pauses under reduced-motion via the stylesheet. Decorative track,
* but the label and items are real text. (Swap industry labels for greyscale
* client logos before launch.)
*/ */
const items = [ const items = [
"Web & Desarrollo", "E-commerce",
"SEO & GEO", "B2B SaaS",
"Paid Ads", "Clinics",
"Contenido", "Professional services",
"Diseño & Marca", "Retail",
"Automatización", "Hospitality",
]; ];
function Row() { function Row() {
@ -30,11 +32,16 @@ function Row() {
export default function Marquee() { export default function Marquee() {
return ( return (
<div className="marquee" aria-hidden="true"> <section className="proof" aria-label="Industries we work with">
<p className="proof__label">
Trusted by teams that care about the sales number
</p>
<div className="marquee">
<div className="marquee__track"> <div className="marquee__track">
<Row /> <Row />
<Row /> <Row />
</div> </div>
</div> </div>
</section>
); );
} }

View file

@ -0,0 +1,44 @@
"use client";
/**
* Metrics four headline results with animated count-up on scroll-in.
* The ROAS metric is the single emerald-accented "signature" number.
*/
import Reveal from "./Reveal";
import CountUp from "./CountUp";
import { metrics } from "../content";
export default function Metrics() {
return (
<section id="results" className="metrics" aria-label="Results">
<div className="wrap">
<Reveal>
<p className="kicker">The numbers</p>
<h2 className="section__title">Results we can show you.</h2>
</Reveal>
<div className="metrics__inner">
<Reveal className="metrics__grid" stagger={0.1}>
{metrics.map((m) => (
<div
className={`metric${"accent" in m && m.accent ? " metric--accent" : ""}`}
key={m.label}
>
<p className="metric__num">
<CountUp
value={m.value}
prefix={"prefix" in m ? m.prefix : ""}
suffix={m.suffix}
decimals={"decimals" in m ? m.decimals : 0}
/>
</p>
<p className="metric__label">{m.label}</p>
</div>
))}
</Reveal>
</div>
</div>
</section>
);
}

View file

@ -1,98 +0,0 @@
"use client";
/**
* Packages four tiers, each named for an outcome rather than a task list.
* "Motor" is the featured tier (lifted, gradient spine). Cards reveal on scroll
* and the featured one carries a continuously-shifting gradient border.
*/
import Reveal from "./Reveal";
type Pkg = {
n: string;
who: string;
price: string;
f: string[];
featured?: boolean;
};
const packages: Pkg[] = [
{
n: "Sitio AI-native",
who: "Necesitas web, ya",
price: "Proyecto",
f: ["Web en código, editable", "SEO base + tracking", "Entregada en tiempo récord"],
},
{
n: "Base",
who: "Estás arrancando",
price: "Mensual",
f: ["Fundamentos SEO", "Contenido inicial", "Analítica conectada"],
},
{
n: "Motor",
who: "Quieres crecer en serio",
price: "Mensual",
f: ["Ads + SEO + contenido", "Dashboard de resultados", "Optimización mensual", "Plataforma de IA incluida"],
featured: true,
},
{
n: "Partner",
who: "Quieres escalar",
price: "Retainer",
f: ["Full-stack de crecimiento", "Automatización a medida", "Prioridad y roadmap", "Acceso directo al equipo"],
},
];
export default function Packages() {
return (
<section id="paquetes" className="packages">
<div className="wrap">
<Reveal>
<p className="kicker">04 Paquetes</p>
<h2 className="packages__title">
Cada uno con un resultado. <span className="serif-em">No listas de tareas.</span>
</h2>
</Reveal>
<Reveal className="packages__grid" stagger={0.1}>
{packages.map((p) => (
<article
className={`pkg hoverable${p.featured ? " pkg--featured" : ""}`}
key={p.n}
data-cursor={p.featured ? "el más elegido" : "ver"}
>
{p.featured && <span className="pkg__tag">Más elegido</span>}
<header className="pkg__head">
<span className="pkg__price">{p.price}</span>
<h3 className="pkg__name">{p.n}</h3>
<p className="pkg__who">{p.who}</p>
</header>
<ul className="pkg__features">
{p.f.map((x) => (
<li key={x}>
<svg viewBox="0 0 20 20" width="18" height="18" aria-hidden="true">
<path
d="M4 10.5l3.5 3.5L16 6"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<span>{x}</span>
</li>
))}
</ul>
<a className="pkg__cta hoverable" href="#contacto">
Empezar con {p.n}
<span aria-hidden="true"></span>
</a>
</article>
))}
</Reveal>
</div>
</section>
);
}

View file

@ -0,0 +1,81 @@
"use client";
/**
* Process "The Feedback Loop". The intro column pins (sticky) while the four
* steps scroll past; each step lights up as it reaches the viewport center, and
* a serif count in the sticky column advances with it. This is the
* scrollytelling beat. Degrades to a plain stacked list under reduced-motion
* (all steps shown active, no pin behaviour) and on narrow screens.
*/
import { useEffect, useRef, useState } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { processSteps } from "../content";
gsap.registerPlugin(ScrollTrigger);
export default function Process() {
const root = useRef<HTMLElement>(null);
const [active, setActive] = useState(0);
useEffect(() => {
const el = root.current;
if (!el) return;
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
setActive(processSteps.length - 1);
return;
}
const ctx = gsap.context(() => {
gsap.utils.toArray<HTMLElement>(".pstep").forEach((step, i) => {
ScrollTrigger.create({
trigger: step,
start: "top 60%",
end: "bottom 60%",
onToggle: (self) => {
if (self.isActive) setActive(i);
},
});
});
}, el);
return () => ctx.revert();
}, []);
return (
<section id="process" className="process" ref={root} aria-label="How it works">
<div className="wrap process__inner">
<div className="process__sticky">
<p className="kicker">How it works</p>
<h2 className="section__title">
The <span className="serif-em">Feedback Loop.</span>
</h2>
<p
className="process__count"
aria-hidden="true"
key={active}
>
0{active + 1}
</p>
</div>
<ol className="process__steps">
{processSteps.map((p, i) => (
<li
className={`pstep${i <= active ? " is-active" : ""}`}
key={p.n}
aria-current={i === active ? "step" : undefined}
>
<div className="pstep__top">
<span className="pstep__n">{p.n}</span>
<h3 className="pstep__name">{p.name}</h3>
</div>
<p className="pstep__desc">{p.desc}</p>
</li>
))}
</ol>
</div>
</section>
);
}

View file

@ -0,0 +1,37 @@
"use client";
/**
* Services deliberately broken editorial grid. Six services on a 6-column
* grid; the first two cards span 3 (breaking the safe 2-col-per-card rhythm),
* the rest span 2. Serif index numerals, spectrum top-line on hover.
*/
import Reveal from "./Reveal";
import { services } from "../content";
export default function Services() {
return (
<section id="services" className="services" aria-label="Services">
<div className="wrap">
<Reveal>
<p className="kicker">What we do</p>
<h2 className="section__title">How we grow your business.</h2>
</Reveal>
<Reveal className="services__grid" stagger={0.08}>
{services.map((s, i) => (
<article
className={`service hoverable${i < 2 ? " service--wide" : ""}`}
key={s.id}
data-cursor="more"
>
<span className="service__index">0{i + 1}</span>
<h3 className="service__name">{s.name}</h3>
<p className="service__desc">{s.desc}</p>
</article>
))}
</Reveal>
</div>
</section>
);
}

View file

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

View file

@ -10,10 +10,10 @@ import { useEffect, useRef, useState } from "react";
import PillMark from "./PillMark"; import PillMark from "./PillMark";
const links = [ const links = [
{ href: "#ventaja", label: "Ventaja" }, { href: "#services", label: "Services" },
{ href: "#servicios", label: "Servicios" }, { href: "#work", label: "Work" },
{ href: "#sectores", label: "Sectores" }, { href: "#process", label: "About" },
{ href: "#paquetes", label: "Paquetes" }, { href: "#faq", label: "FAQ" },
]; ];
export default function SiteHeader() { export default function SiteHeader() {
@ -44,14 +44,14 @@ export default function SiteHeader() {
className={`site-header${scrolled ? " is-scrolled" : ""}${open ? " is-open" : ""}`} className={`site-header${scrolled ? " is-scrolled" : ""}${open ? " is-open" : ""}`}
> >
<div className="site-header__inner"> <div className="site-header__inner">
<a className="brand hoverable" href="#top" aria-label="Feedback Studios, inicio"> <a className="brand hoverable" href="#top" aria-label="Feedback Studios, home">
<PillMark className="brand__mark" title="Feedback Studios" /> <PillMark className="brand__mark" title="Feedback Studios" />
<span className="brand__word"> <span className="brand__word">
feedback<span className="brand__word-2">studios</span> feedback<span className="brand__word-2">studios</span>
</span> </span>
</a> </a>
<nav className="site-nav" aria-label="Principal"> <nav className="site-nav" aria-label="Primary">
<ul> <ul>
{links.map((l) => ( {links.map((l) => (
<li key={l.href}> <li key={l.href}>
@ -61,8 +61,8 @@ export default function SiteHeader() {
</li> </li>
))} ))}
</ul> </ul>
<a className="btn btn--sm btn--primary hoverable" href="#contacto" onClick={() => setOpen(false)}> <a className="btn btn--sm btn--primary hoverable" href="#contact" onClick={() => setOpen(false)}>
<span>Hablemos</span> <span>Get a growth audit</span>
</a> </a>
</nav> </nav>
@ -70,7 +70,7 @@ export default function SiteHeader() {
type="button" type="button"
className="site-header__toggle hoverable" className="site-header__toggle hoverable"
aria-expanded={open} aria-expanded={open}
aria-label={open ? "Cerrar menú" : "Abrir menú"} aria-label={open ? "Close menu" : "Open menu"}
onClick={() => setOpen((v) => !v)} onClick={() => setOpen((v) => !v)}
> >
<span /> <span />

137
app/content.ts Normal file
View file

@ -0,0 +1,137 @@
/**
* Single source of truth for on-page copy + structured data.
* Mirrors content/home-content.md (EN, revenue-focused marketing).
* NOTE: metrics, cases and testimonials are illustrative SAMPLES for the
* prototype swap for real, verifiable data before launch.
*/
export const SITE = {
name: "Feedback Studios",
url: "https://studiosfeedback.com",
tagline: "Marketing that grows your revenue.",
email: "hello@feedbackstudios.com",
contactEmail: "feedback.studios.design@gmail.com",
booking: "https://cal.feedback-studios.com",
description:
"Most agencies sell activity. Feedback Studios builds paid, SEO and content programs around one number: your revenue. And we report on it every month.",
};
export const services = [
{
id: "seo",
name: "SEO that compounds",
desc: "Rank for the searches your buyers make, and keep ranking long after the invoice clears.",
},
{
id: "paid",
name: "Paid ads that pay back",
desc: "Google, Meta, TikTok, and LinkedIn, managed to ROAS and pipeline. Not vanity clicks.",
},
{
id: "content",
name: "Content that converts",
desc: "Articles, landing pages, and assets that pull in qualified traffic and turn it into customers.",
},
{
id: "social",
name: "Social that builds demand",
desc: "Feeds and creative that make people remember you before they're ready to buy.",
},
{
id: "web",
name: "Web & landing pages",
desc: "Fast, on-brand pages built for one job: conversion.",
},
{
id: "creative",
name: "Creative & brand",
desc: "Scroll-stopping creative that still sells.",
},
] as const;
export const metrics = [
{ value: 40, prefix: "$", suffix: "M+", label: "client revenue generated" },
{ value: 3.8, suffix: "×", decimals: 1, label: "average return on ad spend", accent: true },
{ value: 183, prefix: "+", suffix: "%", label: "average organic traffic in 6 months" },
{ value: 92, suffix: "%", label: "client retention" },
] as const;
export const cases = [
{
img: "/assets/case-1.png",
alt: "Glossy 3D capsule cluster in brand violet, blue and emerald, representing the e-commerce fashion case study",
tag: "E-commerce · Fashion",
problem: "Rising ad costs were eating the margin.",
result: "34% CPA and +52% ROAS in 90 days",
how: "Meta + Google Shopping restructure.",
metricNum: "+52%",
metricLabel: "ROAS in 90 days",
},
{
img: "/assets/case-2.png",
alt: "Suspended glossy capsules in brand colors, representing the B2B SaaS case study",
tag: "B2B SaaS",
problem: "Plenty of traffic, no pipeline.",
result: "+217% qualified demo requests in 6 months",
how: "SEO + content + LinkedIn.",
metricNum: "+217%",
metricLabel: "demo requests",
},
{
img: "/assets/case-3.png",
alt: "Cascade of glossy capsules in brand colors, representing the aesthetic clinic case study",
tag: "Aesthetic clinic",
problem: "Empty calendar despite the ad spend.",
result: "+128 booked consultations a month",
how: "Paid + landing-page rebuild.",
metricNum: "+128",
metricLabel: "consultations / month",
},
] as const;
export const processSteps = [
{ n: "01", name: "Audit", desc: "We find where your revenue is leaking." },
{ n: "02", name: "Plan", desc: "A channel mix and targets tied to your numbers, not guesswork." },
{ n: "03", name: "Execute", desc: "We build, launch, and optimize. Fast." },
{ n: "04", name: "Report", desc: "Every month: marketing tied straight to pipeline and sales." },
] as const;
export const testimonials = [
{
quote:
"We were spending $20k a month on ads with nothing to show. Six months later, marketing is our most predictable growth channel.",
by: "Name, Role, Company",
},
{
quote:
"They talk in revenue, not impressions. First agency that moved our pipeline.",
by: "Name, Role, Company",
},
] as const;
export const faqs = [
{
q: "What does a digital marketing agency do?",
a: "A digital marketing agency plans and runs campaigns across search, paid media, social, and content to grow a business. Feedback Studios focuses on revenue, not vanity metrics, and puts your budget into the channels that convert.",
},
{
q: "How much does a digital marketing agency cost?",
a: "Most retainers run from about $2,000 to $20,000+ a month, depending on scope, channels, and competition. Feedback Studios scopes the work to your growth goals, and we also offer one-time audits and project work if a full retainer isn't the right fit yet.",
},
{
q: "How long until I see results?",
a: "Paid campaigns can bring in leads within 2 to 4 weeks. SEO and content usually show real organic growth in 3 to 6 months. We set clear 30, 60, and 90-day milestones so you always know what to expect, and when.",
},
{
q: "What makes Feedback Studios different?",
a: "We measure success by revenue, not impressions or followers. Every campaign is built around your bottom line, with monthly reporting that ties what we do straight to pipeline and sales. If it doesn't grow the business, we stop doing it.",
},
{
q: "Which businesses do you work with?",
a: "We work with e-commerce brands, B2B SaaS, clinics, and professional-service firms that want to grow revenue through digital channels. Clients range from scaling startups to established companies that have outgrown their current marketing.",
},
{
q: "What should I look for in a marketing agency?",
a: "Look for transparent reporting, real case studies with hard numbers, a clear process for your channels, and a team that talks in revenue, not reach. Ask to see work in your industry before you sign anything.",
},
] as const;

File diff suppressed because it is too large Load diff

View file

@ -2,12 +2,14 @@ import type { Metadata } from "next";
import "./globals.css"; import "./globals.css";
import SmoothScroll from "./components/SmoothScroll"; import SmoothScroll from "./components/SmoothScroll";
import Cursor from "./components/Cursor"; import Cursor from "./components/Cursor";
import { SITE, services, faqs } from "./content";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Feedback Studios — La agencia de marketing AI-native", title: "Results-driven digital marketing agency | Feedback Studios",
description: description: SITE.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. // Test environment — keep everything out of search engines.
// Remove these robots directives (here + app/robots.ts + next.config.mjs
// X-Robots-Tag header) to flip the site to index when it goes live.
robots: { robots: {
index: false, index: false,
follow: false, follow: false,
@ -16,15 +18,81 @@ export const metadata: Metadata = {
}, },
}; };
/* JSON-LD: Organization + WebSite (+SearchAction) + Service (per core
service) + FAQPage. Structure is launch-ready; the page stays noindex
in this test env via the robots directives above. */
function StructuredData() {
const graph = [
{
"@type": "Organization",
"@id": `${SITE.url}/#organization`,
name: SITE.name,
url: SITE.url,
description: SITE.description,
email: SITE.email,
slogan: SITE.tagline,
knowsAbout: [
"digital marketing",
"search engine optimization",
"paid advertising",
"content marketing",
"social media marketing",
"conversion rate optimization",
],
},
{
"@type": "WebSite",
"@id": `${SITE.url}/#website`,
url: SITE.url,
name: SITE.name,
publisher: { "@id": `${SITE.url}/#organization` },
potentialAction: {
"@type": "SearchAction",
target: {
"@type": "EntryPoint",
urlTemplate: `${SITE.url}/?s={search_term_string}`,
},
"query-input": "required name=search_term_string",
},
},
...services.map((s) => ({
"@type": "Service",
name: s.name,
description: s.desc,
serviceType: s.name,
provider: { "@id": `${SITE.url}/#organization` },
areaServed: "Worldwide",
})),
{
"@type": "FAQPage",
"@id": `${SITE.url}/#faq`,
mainEntity: faqs.map((f) => ({
"@type": "Question",
name: f.q,
acceptedAnswer: { "@type": "Answer", text: f.a },
})),
},
];
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({ "@context": "https://schema.org", "@graph": graph }),
}}
/>
);
}
export default function RootLayout({ export default function RootLayout({
children, children,
}: { }: {
children: React.ReactNode; children: React.ReactNode;
}) { }) {
return ( return (
<html lang="es"> <html lang="en">
<head> <head>
{/* Satoshi (brand) + Instrument Serif (editorial accent) */} {/* Satoshi (workhorse) + Instrument Serif (editorial accent) */}
<link rel="preconnect" href="https://api.fontshare.com" crossOrigin="" /> <link rel="preconnect" href="https://api.fontshare.com" crossOrigin="" />
<link <link
rel="stylesheet" rel="stylesheet"
@ -37,12 +105,14 @@ export default function RootLayout({
href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&display=swap" href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&display=swap"
/> />
<meta name="robots" content="noindex, nofollow, noarchive, nosnippet" /> <meta name="robots" content="noindex, nofollow, noarchive, nosnippet" />
<meta name="theme-color" content="#0a0a12" /> <meta name="theme-color" content="#08080c" />
</head> </head>
<body> <body>
<StructuredData />
<a href="#main" className="skip-link"> <a href="#main" className="skip-link">
Saltar al contenido Skip to content
</a> </a>
<div className="grain" aria-hidden="true" />
<SmoothScroll /> <SmoothScroll />
<Cursor /> <Cursor />
{children} {children}

View file

@ -4,37 +4,14 @@ import Hero from "./components/Hero";
import Marquee from "./components/Marquee"; import Marquee from "./components/Marquee";
import Reveal from "./components/Reveal"; import Reveal from "./components/Reveal";
import KineticText from "./components/KineticText"; import KineticText from "./components/KineticText";
import ServicesJourney from "./components/ServicesJourney"; import Services from "./components/Services";
import Packages from "./components/Packages"; import Metrics from "./components/Metrics";
import CaseStudies from "./components/CaseStudies";
import Process from "./components/Process";
import Faq from "./components/Faq";
import PillMark from "./components/PillMark"; import PillMark from "./components/PillMark";
import Magnetic from "./components/Magnetic";
const advantages = [ import { testimonials } from "./content";
{
k: "Más rápido",
metric: "días",
sub: "no semanas",
d: "Nuestra plataforma hace el trabajo pesado. Entregamos en días lo que una agencia tradicional tarda en arrancar.",
},
{
k: "Más medible",
metric: "100%",
sub: "trazable",
d: "Cada acción tiene su dashboard y su resultado. Sabes qué funciona y qué no — sin humo ni informes de relleno.",
},
{
k: "Mejor coste",
metric: "0",
sub: "horas vacías",
d: "No pagas por horas: pagas por resultado. La IA absorbe lo repetitivo, el equipo se concentra en la estrategia.",
},
];
const sectors = [
{ t: "Clínicas estéticas", d: "Captación de pacientes, reputación y agenda llena." },
{ t: "Servicios profesionales", d: "Autoridad, leads cualificados y cierre." },
{ t: "E-commerce", d: "ROAS sostenible y catálogo que convierte." },
{ t: "SaaS & Tech", d: "Activación, retención y growth medible." },
];
export default function Home() { export default function Home() {
return ( return (
@ -46,110 +23,107 @@ export default function Home() {
<span id="top" /> <span id="top" />
<Hero /> <Hero />
{/* SOCIAL PROOF BAR */}
<Marquee /> <Marquee />
{/* VENTAJA INJUSTA — dark chapter */} {/* POSITIONING / PROBLEM — big editorial statement */}
<section id="ventaja" className="band band--ink advantage"> <section id="position" className="positioning" aria-label="Our positioning">
<div className="wrap positioning__grid">
<Reveal>
<p className="kicker">Why we exist</p>
<p className="positioning__statement">
Most marketing budgets buy{" "}
<span className="muted">activity</span>, not{" "}
<span className="serif-em">outcomes.</span>
</p>
</Reveal>
<Reveal className="positioning__aside" y={40}>
<span className="serif-em">We started to fix that.</span>
Dashboards fill up with impressions and &ldquo;engagement&rdquo;
while the sales number sits still. Every campaign we run is built
to move revenue, and we report on it the way your CFO would.
</Reveal>
</div>
</section>
{/* SERVICES — broken grid */}
<Services />
{/* RESULTS — animated count-up */}
<Metrics />
{/* CASE STUDIES — large film-cell cards */}
<CaseStudies />
{/* PROCESS — pinned scrollytelling */}
<Process />
{/* TESTIMONIALS + PARTNERS */}
<section id="voices" className="tmonials" aria-label="What clients say">
<div className="wrap"> <div className="wrap">
<Reveal> <Reveal>
<p className="kicker kicker--light">01 Ventaja injusta</p> <p className="kicker">In their words</p>
<h2 className="band__title"> <h2 className="section__title">
<KineticText as="span" text="No alquilamos herramientas." />{" "} The number is the <span className="serif-em">whole point.</span>
<KineticText
as="span"
className="band__title-grad"
text="Construimos la máquina."
highlight={[0, 2]}
/>
</h2> </h2>
</Reveal> </Reveal>
<div className="advantage__grid"> <Reveal className="tmonials__grid" stagger={0.12}>
{advantages.map((a, i) => ( {testimonials.map((t) => (
<Reveal className="advantage__item" key={a.k} y={56}> <figure className="tmonial" key={t.by}>
<span className="advantage__index">0{i + 1}</span> <span className="tmonial__mark" aria-hidden="true">
<p className="advantage__metric"> &ldquo;
{a.metric} </span>
<span className="advantage__metric-sub">{a.sub}</span> <blockquote>
</p> <p className="tmonial__quote">{t.quote}</p>
<h3 className="advantage__k">{a.k}</h3> </blockquote>
<p className="advantage__d">{a.d}</p> <figcaption className="tmonial__by">{t.by}</figcaption>
</Reveal> </figure>
))} ))}
</div>
</div>
</section>
{/* SERVICIOS — horizontal journey */}
<ServicesJourney />
{/* SECTORES — deep dive */}
<section id="sectores" className="band sectors">
<div className="wrap sectors__wrap">
<div className="sectors__intro">
<Reveal>
<p className="kicker">03 Profundidad por sector</p>
<h2 className="sectors__title">
No hacemos marketing genérico.{" "}
<span className="serif-em">Hablamos tu negocio.</span>
</h2>
<p className="sectors__lead">
Construimos soluciones a medida de cada sector: el mismo motor de
IA, afinado con el contexto, el vocabulario y los KPIs que de verdad
importan en tu mercado.
</p>
<a className="link-arrow hoverable" href="#contacto">
¿Tu sector no está? Cuéntanoslo
<span aria-hidden="true"></span>
</a>
</Reveal> </Reveal>
</div>
<Reveal className="sectors__list" stagger={0.1}> <Reveal className="partners">
{sectors.map((s) => ( <span className="partners__label">Partners</span>
<div className="sector hoverable" key={s.t} data-cursor="ver"> <span className="partner">Google Partner</span>
<PillMark className="sector__mark" /> <span className="partner">Meta Business Partner</span>
<div>
<h3 className="sector__t">{s.t}</h3>
<p className="sector__d">{s.d}</p>
</div>
</div>
))}
</Reveal> </Reveal>
</div> </div>
</section> </section>
{/* PAQUETES */} {/* FAQ */}
<Packages /> <Faq />
{/* CTA — light iridescent close */} {/* FINAL CTA */}
<section id="contacto" className="cta"> <section id="contact" className="cta" aria-label="Get in touch">
<span className="cta__glow" aria-hidden="true" />
<Reveal className="wrap cta__wrap"> <Reveal className="wrap cta__wrap">
<PillMark className="cta__mark" animate breathe /> <PillMark className="cta__mark" animate breathe />
<h2 className="cta__title"> <h2 className="cta__title">
<KineticText as="span" text="¿Hablamos de tu" />{" "} <KineticText as="span" text="Ready to" />{" "}
<KineticText <KineticText
as="span" as="span"
className="cta__title-grad" className="cta__title-grad"
text="crecimiento?" text="grow?"
highlight={[0, 0]} highlight={[0, 0]}
/> />
</h2> </h2>
<p className="cta__lead"> <p className="cta__lead">
Cuéntanos dónde estás y te enseñamos, con datos, cómo llegar al No long contracts. No vanity reports. Marketing you can measure in
siguiente nivel. Respuesta en menos de 24h. sales.
</p> </p>
<Magnetic strength={0.4}>
<a <a
className="btn btn--primary btn--lg hoverable" className="btn btn--primary btn--lg hoverable"
href="mailto:feedback.studios.design@gmail.com" href="https://cal.feedback-studios.com"
data-cursor="escríbenos" data-cursor="book a call"
> >
<span>Habla con nosotros</span> <span>Book a call</span>
</a> </a>
</Magnetic>
<p className="cta__mail"> <p className="cta__mail">
o escríbenos a{" "} or email{" "}
<a className="hoverable" href="mailto:feedback.studios.design@gmail.com"> <a className="hoverable" href="mailto:hello@feedbackstudios.com">
feedback.studios.design@gmail.com hello@feedbackstudios.com
</a> </a>
</p> </p>
</Reveal> </Reveal>
@ -163,18 +137,35 @@ export default function Home() {
feedback<span className="brand__word-2">studios</span> feedback<span className="brand__word-2">studios</span>
</span> </span>
<p className="site-footer__tag"> <p className="site-footer__tag">
La agencia de marketing AI-native. Estrategia humana, A results-driven digital marketing agency. We build paid, SEO and
infraestructura propia. content programs around your revenue.
</p> </p>
</div> </div>
<nav className="site-footer__nav" aria-label="Pie">
<a className="hoverable" href="#servicios">Servicios</a> <div className="site-footer__col">
<a className="hoverable" href="#paquetes">Paquetes</a> <h2>Sitemap</h2>
<a className="hoverable" href="#sectores">Sectores</a> <nav aria-label="Footer">
<a className="hoverable" href="mailto:feedback.studios.design@gmail.com">Contacto</a> <a className="hoverable" href="#services">Services</a>
<a className="hoverable" href="#work">Work</a>
<a className="hoverable" href="#process">About</a>
<a className="hoverable" href="#faq">FAQ</a>
<a className="hoverable" href="#contact">Contact</a>
</nav> </nav>
</div>
<div className="site-footer__col">
<h2>Connect</h2>
<nav aria-label="Social and legal">
<a className="hoverable" href="https://www.linkedin.com" rel="noopener">LinkedIn</a>
<a className="hoverable" href="mailto:hello@feedbackstudios.com">Email</a>
<a className="hoverable" href="#">Privacy</a>
<a className="hoverable" href="#">Terms</a>
</nav>
</div>
<p className="site-footer__legal"> <p className="site-footer__legal">
© 2026 Feedback Studios · Marketing AI-native con infraestructura propia <span>© 2026 Feedback Studios</span>
<span>Marketing that grows your revenue.</span>
</p> </p>
</div> </div>
</footer> </footer>

83
content/home-content.md Normal file
View file

@ -0,0 +1,83 @@
# Feedback Studios — Homepage Content (EN, humanized)
> Voice: direct, human, a bit opinionated. Short sentences mixed with longer ones. Contractions. No em dashes, no AI vocabulary, no "full-service" slop. Focus: marketing that grows revenue.
> ⚠️ Metrics, case studies, testimonials and logos are illustrative SAMPLES for the prototype. Swap for real, verifiable client data before launch (E-E-A-T / honesty).
---
## SEO / META
- **Title (≤60):** `Results-driven digital marketing agency | Feedback Studios`
- **Meta description (~150):** `Most agencies sell activity. Feedback Studios builds paid, SEO and content programs around one number: your revenue. And we report on it every month.`
- **Primary keyword:** digital marketing agency · results-driven marketing · revenue
- **Schema (JSON-LD):** Organization, WebSite (+SearchAction), Service (per core service), FAQPage, AggregateRating (only with real reviews).
- **Note:** test env stays noindex. When live: index, allow AI crawlers (GPTBot, OAI-SearchBot, PerplexityBot, ClaudeBot, Googlebot, Bingbot), add llms.txt.
---
## 1. NAV
Logo · Services · Work · About · FAQ · **button: "Get a growth audit"**
## 2. HERO
- **Eyebrow:** Digital marketing agency
- **H1:** Marketing that grows your revenue.
- **Sub:** We're a results-driven digital marketing agency. We run paid, SEO, and content programs built around your revenue targets, then show you what they returned. Every month.
- **Primary CTA:** Get your growth audit
- **Secondary:** See the results →
- **Trust line:** $40M+ in client revenue generated · 50+ brands grown
- **Visual:** custom WebGL / motion signature moment. No stock.
## 3. SOCIAL PROOF BAR
Label: "Trusted by teams that care about the sales number" · 68 greyscale client logos (or industry labels: E-commerce · B2B SaaS · Clinics · Professional services).
## 4. POSITIONING / PROBLEM
Most marketing budgets buy activity, not outcomes. Dashboards fill up with impressions and "engagement" while the sales number sits still. We started Feedback Studios to fix that. Every campaign we run is built to move revenue, and we report on it the way your CFO would.
## 5. SERVICES — H2: "How we grow your business"
- **SEO that compounds** — Rank for the searches your buyers make, and keep ranking long after the invoice clears.
- **Paid ads that pay back** — Google, Meta, TikTok, and LinkedIn, managed to ROAS and pipeline. Not vanity clicks.
- **Content that converts** — Articles, landing pages, and assets that pull in qualified traffic and turn it into customers.
- **Social that builds demand** — Feeds and creative that make people remember you before they're ready to buy.
- **Web & landing pages** — Fast, on-brand pages built for one job: conversion.
- **Creative & brand** — Scroll-stopping creative that still sells.
## 6. RESULTS / METRICS (animated count-up — SAMPLE)
- $40M+ — client revenue generated
- 3.8× — average return on ad spend
- +183% — average organic traffic in 6 months
- 92% — client retention
## 7. CASE STUDIES — H2: "Proof, not promises" (SAMPLE, 3)
1. **E-commerce, fashion.** Rising ad costs were eating the margin. → **34% CPA and +52% ROAS in 90 days** (Meta + Google Shopping restructure).
2. **B2B SaaS.** Plenty of traffic, no pipeline. → **+217% qualified demo requests in 6 months** (SEO + content + LinkedIn).
3. **Aesthetic clinic.** Empty calendar despite the ad spend. → **+128 booked consultations a month** (paid + landing-page rebuild).
CTA: View all work →
## 8. PROCESS — H2: "How it works" · Framework: **The Feedback Loop**
1. **Audit** — We find where your revenue is leaking.
2. **Plan** — A channel mix and targets tied to your numbers, not guesswork.
3. **Execute** — We build, launch, and optimize. Fast.
4. **Report** — Every month: marketing tied straight to pipeline and sales.
## 9. TESTIMONIALS (SAMPLE)
- "We were spending $20k a month on ads with nothing to show. Six months later, marketing is our most predictable growth channel." — *Name, Role, Company*
- "They talk in revenue, not impressions. First agency that moved our pipeline." — *Name, Role, Company*
## 10. AWARDS / PARTNERS
Google Partner · Meta Business Partner · (+ real awards/press when available).
## 11. FAQ — H2: "Questions about working with a digital marketing agency" (AEO answer capsules, 4060 words)
1. **What does a digital marketing agency do?** A digital marketing agency plans and runs campaigns across search, paid media, social, and content to grow a business. Feedback Studios focuses on revenue, not vanity metrics, and puts your budget into the channels that convert.
2. **How much does a digital marketing agency cost?** Most retainers run from about $2,000 to $20,000+ a month, depending on scope, channels, and competition. Feedback Studios scopes the work to your growth goals, and we also offer one-time audits and project work if a full retainer isn't the right fit yet.
3. **How long until I see results?** Paid campaigns can bring in leads within 2 to 4 weeks. SEO and content usually show real organic growth in 3 to 6 months. We set clear 30, 60, and 90-day milestones so you always know what to expect, and when.
4. **What makes Feedback Studios different?** We measure success by revenue, not impressions or followers. Every campaign is built around your bottom line, with monthly reporting that ties what we do straight to pipeline and sales. If it doesn't grow the business, we stop doing it.
5. **Which businesses do you work with?** We work with e-commerce brands, B2B SaaS, clinics, and professional-service firms that want to grow revenue through digital channels. Clients range from scaling startups to established companies that have outgrown their current marketing.
6. **What should I look for in a marketing agency?** Look for transparent reporting, real case studies with hard numbers, a clear process for your channels, and a team that talks in revenue, not reach. Ask to see work in your industry before you sign anything.
## 12. FINAL CTA
- **H2:** Ready to grow?
- **Sub:** No long contracts. No vanity reports. Marketing you can measure in sales.
- **CTA:** Book a call (→ cal.feedback-studios.com)
- Secondary: or email hello@feedbackstudios.com
## 13. FOOTER
Logo + one-line positioning · Sitemap (Services, Work, About, FAQ, Contact) · LinkedIn · Privacy · Terms · © 2026 · NAP (city/country) when available.

466
package-lock.json generated
View file

@ -10,7 +10,7 @@
"dependencies": { "dependencies": {
"gsap": "^3.12.5", "gsap": "^3.12.5",
"lenis": "^1.1.14", "lenis": "^1.1.14",
"next": "15.1.6", "next": "^15.5.19",
"ogl": "^1.0.11", "ogl": "^1.0.11",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",
@ -36,10 +36,20 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@img/colour": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=18"
}
},
"node_modules/@img/sharp-darwin-arm64": { "node_modules/@img/sharp-darwin-arm64": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -55,13 +65,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.0.4" "@img/sharp-libvips-darwin-arm64": "1.2.4"
} }
}, },
"node_modules/@img/sharp-darwin-x64": { "node_modules/@img/sharp-darwin-x64": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -77,13 +87,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.0.4" "@img/sharp-libvips-darwin-x64": "1.2.4"
} }
}, },
"node_modules/@img/sharp-libvips-darwin-arm64": { "node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.0.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -97,9 +107,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-darwin-x64": { "node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.0.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -113,9 +123,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-arm": { "node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.0.5", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -129,9 +139,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-arm64": { "node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.0.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -144,10 +154,42 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
} }
}, },
"node_modules/@img/sharp-libvips-linux-ppc64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
"integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
"cpu": [
"ppc64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-riscv64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
"integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
"cpu": [
"riscv64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-s390x": { "node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.0.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
"integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@ -161,9 +203,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-x64": { "node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.0.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -177,9 +219,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linuxmusl-arm64": { "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.0.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
"integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -193,9 +235,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linuxmusl-x64": { "node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.0.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
"integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -209,9 +251,9 @@
} }
}, },
"node_modules/@img/sharp-linux-arm": { "node_modules/@img/sharp-linux-arm": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -227,13 +269,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.0.5" "@img/sharp-libvips-linux-arm": "1.2.4"
} }
}, },
"node_modules/@img/sharp-linux-arm64": { "node_modules/@img/sharp-linux-arm64": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -249,13 +291,57 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.0.4" "@img/sharp-libvips-linux-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-ppc64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
"integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
"cpu": [
"ppc64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-riscv64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
"integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
"cpu": [
"riscv64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-riscv64": "1.2.4"
} }
}, },
"node_modules/@img/sharp-linux-s390x": { "node_modules/@img/sharp-linux-s390x": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
"integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@ -271,13 +357,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.0.4" "@img/sharp-libvips-linux-s390x": "1.2.4"
} }
}, },
"node_modules/@img/sharp-linux-x64": { "node_modules/@img/sharp-linux-x64": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -293,13 +379,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.0.4" "@img/sharp-libvips-linux-x64": "1.2.4"
} }
}, },
"node_modules/@img/sharp-linuxmusl-arm64": { "node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
"integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -315,13 +401,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4" "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
} }
}, },
"node_modules/@img/sharp-linuxmusl-x64": { "node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
"integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -337,20 +423,20 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.0.4" "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
} }
}, },
"node_modules/@img/sharp-wasm32": { "node_modules/@img/sharp-wasm32": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
"integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
"cpu": [ "cpu": [
"wasm32" "wasm32"
], ],
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"@emnapi/runtime": "^1.2.0" "@emnapi/runtime": "^1.7.0"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
@ -359,10 +445,29 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
} }
}, },
"node_modules/@img/sharp-win32-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
"integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-ia32": { "node_modules/@img/sharp-win32-ia32": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
"integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -379,9 +484,9 @@
} }
}, },
"node_modules/@img/sharp-win32-x64": { "node_modules/@img/sharp-win32-x64": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -398,15 +503,15 @@
} }
}, },
"node_modules/@next/env": { "node_modules/@next/env": {
"version": "15.1.6", "version": "15.5.19",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.6.tgz", "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.19.tgz",
"integrity": "sha512-d9AFQVPEYNr+aqokIiPLNK/MTyt3DWa/dpKveiAaVccUadFbhFEvY6FXYX2LJO2Hv7PHnLBu2oWwB4uBuHjr/w==", "integrity": "sha512-sWWluFvcv5v3Fxznmf2ZfjyoVQt/64oCnYqS90inQWGzMPK1VjvekPiz3OPHKmFT30EnHrjlbyaHLt3M0vWabw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@next/swc-darwin-arm64": { "node_modules/@next/swc-darwin-arm64": {
"version": "15.1.6", "version": "15.5.19",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.6.tgz", "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.19.tgz",
"integrity": "sha512-u7lg4Mpl9qWpKgy6NzEkz/w0/keEHtOybmIl0ykgItBxEM5mYotS5PmqTpo+Rhg8FiOiWgwr8USxmKQkqLBCrw==", "integrity": "sha512-jx9wWlTKueHKPvVOndyr7WuaevWCkuYqsQ8gC0TMPKAVWG3MhcdMrjfo9tvIZNXd0QOUYXXvAcZ325y8Uq7uzg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -420,9 +525,9 @@
} }
}, },
"node_modules/@next/swc-darwin-x64": { "node_modules/@next/swc-darwin-x64": {
"version": "15.1.6", "version": "15.5.19",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.6.tgz", "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.19.tgz",
"integrity": "sha512-x1jGpbHbZoZ69nRuogGL2MYPLqohlhnT9OCU6E6QFewwup+z+M6r8oU47BTeJcWsF2sdBahp5cKiAcDbwwK/lg==", "integrity": "sha512-291KFcsIQ3OenRdiUDFOR6W3wezzH4auENXm1gbm1Bjd4ANMMRgxPrWTUztQN43BnVoVuMnHCrLeECIMwgFKbA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -436,9 +541,9 @@
} }
}, },
"node_modules/@next/swc-linux-arm64-gnu": { "node_modules/@next/swc-linux-arm64-gnu": {
"version": "15.1.6", "version": "15.5.19",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.6.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.19.tgz",
"integrity": "sha512-jar9sFw0XewXsBzPf9runGzoivajeWJUc/JkfbLTC4it9EhU8v7tCRLH7l5Y1ReTMN6zKJO0kKAGqDk8YSO2bg==", "integrity": "sha512-WeH+nelQyyMeE2f8FxBRZNrGipya5zHZV2vjzfCOAYyiI6am+NbnWAAldOBFQBB2w0DjJcsvrKqoFT2b7+5YoA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -452,9 +557,9 @@
} }
}, },
"node_modules/@next/swc-linux-arm64-musl": { "node_modules/@next/swc-linux-arm64-musl": {
"version": "15.1.6", "version": "15.5.19",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.6.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.19.tgz",
"integrity": "sha512-+n3u//bfsrIaZch4cgOJ3tXCTbSxz0s6brJtU3SzLOvkJlPQMJ+eHVRi6qM2kKKKLuMY+tcau8XD9CJ1OjeSQQ==", "integrity": "sha512-5xTOE0lDlDCSSfp+BAif7j17VRRCjWp//ZPZy6NI0QpdrhxtQnsZguSx0xAAZ0c9XZLrLLwCe/XVe5YPrRilKw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -468,9 +573,9 @@
} }
}, },
"node_modules/@next/swc-linux-x64-gnu": { "node_modules/@next/swc-linux-x64-gnu": {
"version": "15.1.6", "version": "15.5.19",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.6.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.19.tgz",
"integrity": "sha512-SpuDEXixM3PycniL4iVCLyUyvcl6Lt0mtv3am08sucskpG0tYkW1KlRhTgj4LI5ehyxriVVcfdoxuuP8csi3kQ==", "integrity": "sha512-LTxRmMgqqMv05Had879W00Fm53quiJd3Zuz8h1JSNJ3nGSlbZ/7Tjs1tKyScgN3Au3t3MyPsjPlq60fMmSHLsg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -484,9 +589,9 @@
} }
}, },
"node_modules/@next/swc-linux-x64-musl": { "node_modules/@next/swc-linux-x64-musl": {
"version": "15.1.6", "version": "15.5.19",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.6.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.19.tgz",
"integrity": "sha512-L4druWmdFSZIIRhF+G60API5sFB7suTbDRhYWSjiw0RbE+15igQvE2g2+S973pMGvwN3guw7cJUjA/TmbPWTHQ==", "integrity": "sha512-eoNQSpA5PQfB9wBO4RA47MTDXWz1fizy9Y3Z6e4DetYIF3dvjuu8sj7aIGn/bFCU6lnFzTK34NtCaffP4NsQ7Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -500,9 +605,9 @@
} }
}, },
"node_modules/@next/swc-win32-arm64-msvc": { "node_modules/@next/swc-win32-arm64-msvc": {
"version": "15.1.6", "version": "15.5.19",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.6.tgz", "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.19.tgz",
"integrity": "sha512-s8w6EeqNmi6gdvM19tqKKWbCyOBvXFbndkGHl+c9YrzsLARRdCHsD9S1fMj8gsXm9v8vhC8s3N8rjuC/XrtkEg==", "integrity": "sha512-6UNt2dFuCHOe446sm/Kp69nUe8/wIhnh9bm6Xcqw4qEWCOppLMOvhTBVgvM7invVUNr4SPpP6NOQsACtn2IN9Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -516,9 +621,9 @@
} }
}, },
"node_modules/@next/swc-win32-x64-msvc": { "node_modules/@next/swc-win32-x64-msvc": {
"version": "15.1.6", "version": "15.5.19",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.6.tgz", "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.19.tgz",
"integrity": "sha512-6xomMuu54FAFxttYr5PJbEfu96godcxBTRk1OhAvJq0/EnmFU/Ybiax30Snis4vdWZ9LGpf7Roy5fSs7v/5ROQ==", "integrity": "sha512-PhmojAHyqMne56HBLGu9dhDnHPuFmEjrXSQMM/nW0J6j849lk3ESrVtqNJcCk8CKOV7brpTTbaYAjwKPzKM69w==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -531,12 +636,6 @@
"node": ">= 10" "node": ">= 10"
} }
}, },
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
"license": "Apache-2.0"
},
"node_modules/@swc/helpers": { "node_modules/@swc/helpers": {
"version": "0.5.15", "version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@ -576,17 +675,6 @@
"@types/react": "^19.2.0" "@types/react": "^19.2.0"
} }
}, },
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
"dependencies": {
"streamsearch": "^1.1.0"
},
"engines": {
"node": ">=10.16.0"
}
},
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001799", "version": "1.0.30001799",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz",
@ -613,51 +701,6 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"license": "MIT",
"optional": true,
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT",
"optional": true
},
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"license": "MIT",
"optional": true,
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
@ -681,13 +724,6 @@
"integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==", "integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==",
"license": "Standard 'no charge' license: https://gsap.com/standard-license." "license": "Standard 'no charge' license: https://gsap.com/standard-license."
}, },
"node_modules/is-arrayish": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
"license": "MIT",
"optional": true
},
"node_modules/lenis": { "node_modules/lenis": {
"version": "1.3.23", "version": "1.3.23",
"resolved": "https://registry.npmjs.org/lenis/-/lenis-1.3.23.tgz", "resolved": "https://registry.npmjs.org/lenis/-/lenis-1.3.23.tgz",
@ -738,16 +774,13 @@
} }
}, },
"node_modules/next": { "node_modules/next": {
"version": "15.1.6", "version": "15.5.19",
"resolved": "https://registry.npmjs.org/next/-/next-15.1.6.tgz", "resolved": "https://registry.npmjs.org/next/-/next-15.5.19.tgz",
"integrity": "sha512-Hch4wzbaX0vKQtalpXvUiw5sYivBy4cm5rzUKrBnUB/y436LGrvOUqYvlSeNVCWFO/770gDlltR9gqZH62ct4Q==", "integrity": "sha512-xNOW6tYshGX1/Oi3F8uuk4gpDeWsSUE/1Z0G5uUMekIxaQ0xc03UXd9II0VQHYMWviMeA0OHpJFAKsHf8bTYVg==",
"deprecated": "This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/CVE-2025-66478 for more details.",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@next/env": "15.1.6", "@next/env": "15.5.19",
"@swc/counter": "0.1.3",
"@swc/helpers": "0.5.15", "@swc/helpers": "0.5.15",
"busboy": "1.6.0",
"caniuse-lite": "^1.0.30001579", "caniuse-lite": "^1.0.30001579",
"postcss": "8.4.31", "postcss": "8.4.31",
"styled-jsx": "5.1.6" "styled-jsx": "5.1.6"
@ -759,19 +792,19 @@
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0" "node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@next/swc-darwin-arm64": "15.1.6", "@next/swc-darwin-arm64": "15.5.19",
"@next/swc-darwin-x64": "15.1.6", "@next/swc-darwin-x64": "15.5.19",
"@next/swc-linux-arm64-gnu": "15.1.6", "@next/swc-linux-arm64-gnu": "15.5.19",
"@next/swc-linux-arm64-musl": "15.1.6", "@next/swc-linux-arm64-musl": "15.5.19",
"@next/swc-linux-x64-gnu": "15.1.6", "@next/swc-linux-x64-gnu": "15.5.19",
"@next/swc-linux-x64-musl": "15.1.6", "@next/swc-linux-x64-musl": "15.5.19",
"@next/swc-win32-arm64-msvc": "15.1.6", "@next/swc-win32-arm64-msvc": "15.5.19",
"@next/swc-win32-x64-msvc": "15.1.6", "@next/swc-win32-x64-msvc": "15.5.19",
"sharp": "^0.33.5" "sharp": "^0.34.3"
}, },
"peerDependencies": { "peerDependencies": {
"@opentelemetry/api": "^1.1.0", "@opentelemetry/api": "^1.1.0",
"@playwright/test": "^1.41.2", "@playwright/test": "^1.51.1",
"babel-plugin-react-compiler": "*", "babel-plugin-react-compiler": "*",
"react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
"react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
@ -873,16 +906,16 @@
} }
}, },
"node_modules/sharp": { "node_modules/sharp": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"color": "^4.2.3", "@img/colour": "^1.0.0",
"detect-libc": "^2.0.3", "detect-libc": "^2.1.2",
"semver": "^7.6.3" "semver": "^7.7.3"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
@ -891,35 +924,30 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-arm64": "0.34.5",
"@img/sharp-darwin-x64": "0.33.5", "@img/sharp-darwin-x64": "0.34.5",
"@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-arm64": "1.2.4",
"@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.2.4",
"@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm": "1.2.4",
"@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-arm64": "1.2.4",
"@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-ppc64": "1.2.4",
"@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linux-riscv64": "1.2.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.2.4",
"@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-libvips-linux-x64": "1.2.4",
"@img/sharp-linux-arm": "0.33.5", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
"@img/sharp-linux-arm64": "0.33.5", "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
"@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-arm": "0.34.5",
"@img/sharp-linux-x64": "0.33.5", "@img/sharp-linux-arm64": "0.34.5",
"@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linux-ppc64": "0.34.5",
"@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-linux-riscv64": "0.34.5",
"@img/sharp-wasm32": "0.33.5", "@img/sharp-linux-s390x": "0.34.5",
"@img/sharp-win32-ia32": "0.33.5", "@img/sharp-linux-x64": "0.34.5",
"@img/sharp-win32-x64": "0.33.5" "@img/sharp-linuxmusl-arm64": "0.34.5",
} "@img/sharp-linuxmusl-x64": "0.34.5",
}, "@img/sharp-wasm32": "0.34.5",
"node_modules/simple-swizzle": { "@img/sharp-win32-arm64": "0.34.5",
"version": "0.2.4", "@img/sharp-win32-ia32": "0.34.5",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", "@img/sharp-win32-x64": "0.34.5"
"integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
"license": "MIT",
"optional": true,
"dependencies": {
"is-arrayish": "^0.3.1"
} }
}, },
"node_modules/source-map-js": { "node_modules/source-map-js": {
@ -937,14 +965,6 @@
"integrity": "sha512-otEk9vnD8qwfLsk3Lx0gz+qRkNIJCx0mlyL47ImP/DjMuV39d75Lpfwjn9fHteDRz0aoOblSzQjSNT9+Sswxcg==", "integrity": "sha512-otEk9vnD8qwfLsk3Lx0gz+qRkNIJCx0mlyL47ImP/DjMuV39d75Lpfwjn9fHteDRz0aoOblSzQjSNT9+Sswxcg==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/styled-jsx": { "node_modules/styled-jsx": {
"version": "5.1.6", "version": "5.1.6",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",

View file

@ -13,16 +13,16 @@
"dependencies": { "dependencies": {
"gsap": "^3.12.5", "gsap": "^3.12.5",
"lenis": "^1.1.14", "lenis": "^1.1.14",
"next": "15.1.6", "next": "^15.5.19",
"ogl": "^1.0.11", "ogl": "^1.0.11",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",
"split-type": "^0.3.4" "split-type": "^0.3.4"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.7.3",
"@types/node": "^22.10.7", "@types/node": "^22.10.7",
"@types/react": "^19.0.7", "@types/react": "^19.0.7",
"@types/react-dom": "^19.0.3" "@types/react-dom": "^19.0.3",
"typescript": "^5.7.3"
} }
} }

BIN
public/assets/case-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
public/assets/case-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
public/assets/case-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB