+
+ );
+}
diff --git a/components/app/landing/testimonials.tsx b/components/app/landing/testimonials.tsx
new file mode 100644
index 0000000..489cba5
--- /dev/null
+++ b/components/app/landing/testimonials.tsx
@@ -0,0 +1,72 @@
+import type { Tweet } from "react-tweet/api";
+import { getTweet } from "react-tweet/api";
+import { TestimonialCard } from "@/components/app/landing/testimonial-card";
+import { Marquee } from "@/components/motion/marquee";
+
+// Public tweets shown as social proof. IDs only — the content is pulled from
+// Twitter's syndication API on the server and rendered statically, so no
+// async component streams into the client (which trips a React 19 / Next 15
+// Flight bug: "chunk.reason.enqueueModel is not a function").
+const TWEET_IDS = [
+ "2070915664668512304",
+ "2073135185370227162",
+ "2072978320036348221",
+ "2070129442157191185",
+ "2071327003790184684",
+ "2069456887184318562",
+ "2066804142719275062",
+ "2071206392925765751",
+ "2069415701874720806",
+ "2073188569506587028",
+ "2069333890506936655",
+ "2071800087940870242",
+ "2069108073839435853",
+ "2071704269816811735",
+ "2069459958857650245",
+ "2071569532796256411",
+];
+
+export async function Testimonials() {
+ const tweets = await Promise.all(
+ TWEET_IDS.map(async (id) => {
+ try {
+ return await getTweet(id);
+ } catch {
+ return undefined;
+ }
+ }),
+ );
+ const found = tweets.filter((t): t is Tweet => t != null);
+ if (found.length === 0) return null;
+
+ // Split into two rows that scroll in opposite directions.
+ const mid = Math.ceil(found.length / 2);
+ const rowOne = found.slice(0, mid);
+ const rowTwo = found.slice(mid);
+
+ return (
+
+
+
+ Testimonials
+
+
+ Loved by builders.
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/motion/marquee.tsx b/components/motion/marquee.tsx
index 8914742..b22f092 100644
--- a/components/motion/marquee.tsx
+++ b/components/motion/marquee.tsx
@@ -33,7 +33,9 @@ export function Marquee({
fade && vertical && "[mask-image:linear-gradient(to_bottom,transparent,black_12%,black_88%,transparent)]",
className,
)}
- style={{ "--gap": gap } as React.CSSProperties}
+ // gap on the wrapper too, so the seam between the two tracks matches the
+ // spacing between items and the loop stays even.
+ style={{ "--gap": gap, gap } as React.CSSProperties}
>
{[0, 1].map((dup) => (