diff --git a/app/page.tsx b/app/page.tsx
index 0e32c27..973cc18 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -35,8 +35,6 @@ const CURATED: { category: string; slug: string }[] = [
{ category: "blocks", slug: "bloom-menu" },
];
-// Grid of live-preview cards. The first tile is promoted to a large "feature"
-// showcase.
const GRID_CLASS =
"grid grid-cols-1 gap-4 [grid-auto-rows:19rem] sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4";
@@ -73,12 +71,6 @@ function SectionHeader({
}
export default function Home() {
- const curatedComponents = CURATED.flatMap(({ category, slug }) => {
- const cat = registry.find((c) => c.slug === category);
- const comp = cat?.components.find((c) => c.slug === slug);
- return comp ? [{ category, component: comp }] : [];
- });
-
const newComponents = registry
.flatMap((category) =>
category.components
@@ -89,6 +81,19 @@ export default function Home() {
.sort((a, b) =>
(b.component.launchedAt ?? "").localeCompare(a.component.launchedAt ?? ""),
);
+ const newComponentKeys = new Set(
+ newComponents.map(
+ ({ category, component }) => `${category}/${component.slug}`,
+ ),
+ );
+ const curatedComponents = CURATED.flatMap(({ category, slug }) => {
+ const cat = registry.find((c) => c.slug === category);
+ const comp = cat?.components.find((c) => c.slug === slug);
+ return comp ? [{ category, component: comp }] : [];
+ }).filter(
+ ({ category, component }) =>
+ !newComponentKeys.has(`${category}/${component.slug}`),
+ );
return (
diff --git a/components/app/docs/install-command.tsx b/components/app/docs/install-command.tsx
index a8dd5db..8ac5151 100644
--- a/components/app/docs/install-command.tsx
+++ b/components/app/docs/install-command.tsx
@@ -23,7 +23,9 @@ const CYCLE_MS = 1800;
const COMPONENT_SLUGS = registry.flatMap((cat) =>
cat.components.flatMap((comp) =>
comp.examples
- ? comp.examples.filter((e) => e.installSlug).map((e) => e.installSlug!)
+ ? comp.examples.flatMap((example) =>
+ example.installSlug ? [example.installSlug] : [],
+ )
: [comp.slug],
),
);
diff --git a/components/app/landing/landing-component-card.tsx b/components/app/landing/landing-component-card.tsx
index b5af372..4d46aa3 100644
--- a/components/app/landing/landing-component-card.tsx
+++ b/components/app/landing/landing-component-card.tsx
@@ -2,11 +2,13 @@
import Link from "next/link";
import { ArrowUpRight } from "lucide-react";
-import { useState } from "react";
+import { useInView } from "motion/react";
+import { useRef, useState } from "react";
import type { ComponentEntry } from "@/lib/registry";
import { NewBadge } from "@/components/app/docs/new-badge";
import { PreviewFit } from "@/components/app/landing/preview-fit";
import { getPreview } from "@/components/previews";
+import { EASE_OUT_CSS } from "@/lib/ease";
import { cn } from "@/lib/utils";
export type CardVariant = "default" | "wide" | "feature";
@@ -26,12 +28,18 @@ export function LandingComponentCard({
category?: string;
variant?: CardVariant;
}) {
+ const cardRef = useRef
(null);
+ const shouldRenderPreview = useInView(cardRef, {
+ once: true,
+ margin: "400px 0px",
+ });
const Preview = getPreview(category, component.slug);
const [hover, setHover] = useState(false);
const feature = variant === "feature";
return (
setHover(true)}
onPointerLeave={() => setHover(false)}
@@ -40,13 +48,24 @@ export function LandingComponentCard({
>
-
-
- {Preview ? : null}
-
+
+ {shouldRenderPreview ? (
+
+ {Preview ? : null}
+
+ ) : (
+
+ )}
@@ -65,7 +84,10 @@ export function LandingComponentCard({
{component.description}
-
+
diff --git a/components/app/landing/testimonial-card.tsx b/components/app/landing/testimonial-card.tsx
index e4810f9..ebb6004 100644
--- a/components/app/landing/testimonial-card.tsx
+++ b/components/app/landing/testimonial-card.tsx
@@ -1,5 +1,4 @@
-import { enrichTweet } from "react-tweet";
-import type { Tweet } from "react-tweet/api";
+import type { Testimonial } from "@/components/app/landing/testimonials-data";
import { cn } from "@/lib/utils";
function VerifiedBadge() {
@@ -16,20 +15,18 @@ function VerifiedBadge() {
}
export function TestimonialCard({
- tweet,
+ testimonial,
compact = false,
}: {
- tweet: Tweet;
+ testimonial: Testimonial;
compact?: boolean;
}) {
- const t = enrichTweet(tweet);
- const verified = t.user.is_blue_verified || t.user.verified;
// Swap Twitter's 48px `_normal` avatar for the crisper 73px `_bigger`.
- const avatar = t.user.profile_image_url_https.replace("_normal", "_bigger");
+ const avatar = testimonial.user.avatar.replace("_normal", "_bigger");
return (
- {t.user.name}
+ {testimonial.user.name}
- {verified ? : null}
+ {testimonial.user.verified ? : null}
- @{t.user.screen_name}
+ @{testimonial.user.username}
@@ -73,21 +70,7 @@ export function TestimonialCard({
: "mt-4 text-[15px] leading-relaxed",
)}
>
- {t.entities.map((item, i) => {
- if (item.type === "media") return null;
- if (item.type === "text") {
- return (
- // biome-ignore lint/suspicious/noArrayIndexKey: tweet parts are positional and stable
- {item.text}
- );
- }
- return (
- // biome-ignore lint/suspicious/noArrayIndexKey: tweet parts are positional and stable
-
- {item.text}
-
- );
- })}
+ {testimonial.text}
);
diff --git a/components/app/landing/testimonials-data.ts b/components/app/landing/testimonials-data.ts
new file mode 100644
index 0000000..eddb8ca
--- /dev/null
+++ b/components/app/landing/testimonials-data.ts
@@ -0,0 +1,211 @@
+export type Testimonial = {
+ id: string;
+ text: string;
+ user: {
+ name: string;
+ username: string;
+ avatar: string;
+ verified: boolean;
+ };
+};
+
+export const TESTIMONIALS = [
+ {
+ id: "2070915664668512304",
+ text: "Really like the animations throughout @saurra3h's ui-components.\n\nA lot of the components have smooth, thoughtfully designed interactions.",
+ user: {
+ name: "ericts",
+ username: "erictsdotcom",
+ avatar:
+ "https://pbs.twimg.com/profile_images/2037069613461372928/pZVqHmPg_normal.jpg",
+ verified: false,
+ },
+ },
+ {
+ id: "2073486052665537002",
+ text: "@saurra3h @paper Super cool!",
+ user: {
+ name: "Stephen Haney",
+ username: "stephenhaney",
+ avatar:
+ "https://pbs.twimg.com/profile_images/1998948097662259200/PfaIVhfU_normal.jpg",
+ verified: true,
+ },
+ },
+ {
+ id: "2073135185370227162",
+ text: "this > any metric\n\nthis is exactly why i keep building beui",
+ user: {
+ name: "Saurabh | NodeOps",
+ username: "saurra3h",
+ avatar:
+ "https://pbs.twimg.com/profile_images/1865446187295178752/thuJzCdX_normal.jpg",
+ verified: true,
+ },
+ },
+ {
+ id: "2072978320036348221",
+ text: "@saurra3h @paper damn just explored the components, i'm using it ASAP.",
+ user: {
+ name: "Amanjot Singh",
+ username: "amans_twt",
+ avatar:
+ "https://pbs.twimg.com/profile_images/1837376254607077376/k9hrX4mP_normal.jpg",
+ verified: true,
+ },
+ },
+ {
+ id: "2070129442157191185",
+ text: "@saurra3h Your Component Library is amazing been using it for a long time glad to see it constantly evolving",
+ user: {
+ name: "Hossain Jahed",
+ username: "EaseMizeUI",
+ avatar:
+ "https://pbs.twimg.com/profile_images/2016098268883910657/U5EKdUq5_normal.jpg",
+ verified: false,
+ },
+ },
+ {
+ id: "2073494103153586236",
+ text: "@saurra3h @motiondotdev I appreciate the work you’re doing at Beui! It’s great.",
+ user: {
+ name: "Enes Aktaş",
+ username: "ensaktas",
+ avatar:
+ "https://pbs.twimg.com/profile_images/2067486981840396288/p1iCw56a_normal.jpg",
+ verified: true,
+ },
+ },
+ {
+ id: "2071327003790184684",
+ text: "@saurra3h just check it out looks clean, gonna use it!",
+ user: {
+ name: "Tyler Gibbs",
+ username: "Tylerbryy",
+ avatar:
+ "https://pbs.twimg.com/profile_images/2068918593962536960/gmAsn6Nj_normal.jpg",
+ verified: true,
+ },
+ },
+ {
+ id: "2069456887184318562",
+ text: "@saurra3h @vercel @rauchg My dude. These are great",
+ user: {
+ name: "Myles Franklin",
+ username: "myles_franklin",
+ avatar:
+ "https://pbs.twimg.com/profile_images/1793338348398071808/-SrMDjm9_normal.jpg",
+ verified: true,
+ },
+ },
+ {
+ id: "2066804142719275062",
+ text: "@saurra3h I’ve been looking through it in awe man it’s incredible",
+ user: {
+ name: "Jack",
+ username: "jacksportfolio",
+ avatar:
+ "https://pbs.twimg.com/profile_images/2054257567350607872/Mt6Fhivs_normal.jpg",
+ verified: true,
+ },
+ },
+ {
+ id: "2071206392925765751",
+ text: "@saurra3h adding this to ndle app, great work!!",
+ user: {
+ name: "Abhishek",
+ username: "abhishk_084",
+ avatar:
+ "https://pbs.twimg.com/profile_images/2013536935558455296/gxHOeBex_normal.jpg",
+ verified: true,
+ },
+ },
+ {
+ id: "2069415701874720806",
+ text: "@saurra3h @vercel @rauchg the animations are so cool, love to see more!",
+ user: {
+ name: "Thrishank",
+ username: "thrishank007",
+ avatar:
+ "https://pbs.twimg.com/profile_images/1920539073321553920/Vgu4EqC3_normal.jpg",
+ verified: false,
+ },
+ },
+ {
+ id: "2073188569506587028",
+ text: "@saurra3h @shadcn love the component looks super clean!!",
+ user: {
+ name: "Daniel White",
+ username: "dwhitedesign",
+ avatar:
+ "https://pbs.twimg.com/profile_images/2057915040057982976/gSIeaZnQ_normal.jpg",
+ verified: true,
+ },
+ },
+ {
+ id: "2069333890506936655",
+ text: "@saurra3h @vercel @rauchg Pretty cool work, I love the animations",
+ user: {
+ name: "Adetunji | Software Engineer (Web & Mobile)",
+ username: "itzadetunji1",
+ avatar:
+ "https://pbs.twimg.com/profile_images/2008803706284691456/oiK7MclR_normal.jpg",
+ verified: true,
+ },
+ },
+ {
+ id: "2071800087940870242",
+ text: "@saurra3h @motiondotdev spent way too long clicking this just for the animation",
+ user: {
+ name: "Nick Venturi",
+ username: "nickventuri",
+ avatar:
+ "https://pbs.twimg.com/profile_images/2047053248427638784/lY1d_G02_normal.jpg",
+ verified: true,
+ },
+ },
+ {
+ id: "2069108073839435853",
+ text: "@saurra3h Loved the Glitch template. Applying to my site right now! :-)",
+ user: {
+ name: "Alvin",
+ username: "AlvBckr",
+ avatar:
+ "https://pbs.twimg.com/profile_images/2066598344563052544/z8pbOXSj_normal.jpg",
+ verified: true,
+ },
+ },
+ {
+ id: "2071704269816811735",
+ text: "@alibey_10 @saurra3h Really nice work 🫡",
+ user: {
+ name: "Corentin",
+ username: "CorentinClichy",
+ avatar:
+ "https://pbs.twimg.com/profile_images/1911752944212406272/t6lZLeFg_normal.jpg",
+ verified: false,
+ },
+ },
+ {
+ id: "2069459958857650245",
+ text: "@saurra3h this is gorgeous",
+ user: {
+ name: "AJ",
+ username: "attaboiaj",
+ avatar:
+ "https://pbs.twimg.com/profile_images/1620798411338547200/AAlPAkWT_normal.jpg",
+ verified: false,
+ },
+ },
+ {
+ id: "2071569532796256411",
+ text: "SWEET!!",
+ user: {
+ name: "MIRACLE CHIKI 🇳🇬",
+ username: "Udochiki",
+ avatar:
+ "https://pbs.twimg.com/profile_images/2001436282224795648/YFxqfgJB_normal.jpg",
+ verified: false,
+ },
+ },
+] satisfies Testimonial[];
diff --git a/components/app/landing/testimonials.tsx b/components/app/landing/testimonials.tsx
index b6b11a5..eb56637 100644
--- a/components/app/landing/testimonials.tsx
+++ b/components/app/landing/testimonials.tsx
@@ -1,50 +1,12 @@
-import type { Tweet } from "react-tweet/api";
-import { getTweet } from "react-tweet/api";
import { TestimonialCard } from "@/components/app/landing/testimonial-card";
+import { TESTIMONIALS } from "@/components/app/landing/testimonials-data";
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",
- "2073486052665537002",
- "2073135185370227162",
- "2072978320036348221",
- "2070129442157191185",
- "2073494103153586236",
- "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;
-
+export function Testimonials() {
// 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);
+ const mid = Math.ceil(TESTIMONIALS.length / 2);
+ const rowOne = TESTIMONIALS.slice(0, mid);
+ const rowTwo = TESTIMONIALS.slice(mid);
return (
@@ -59,13 +21,21 @@ export async function Testimonials() {