diff --git a/apps/site/package.json b/apps/site/package.json index c9d6de8357..300d4dcd96 100644 --- a/apps/site/package.json +++ b/apps/site/package.json @@ -23,6 +23,7 @@ "npm-to-yarn": "catalog:", "posthog-js": "catalog:", "react": "catalog:", + "react-animated-numbers": "catalog:", "react-dom": "catalog:", "react-tweet": "catalog:", "remark-directive": "catalog:", diff --git a/apps/site/public/icons/technologies/vscode.svg b/apps/site/public/icons/technologies/vscode.svg index 97ca79854d..1d3d60733a 100644 --- a/apps/site/public/icons/technologies/vscode.svg +++ b/apps/site/public/icons/technologies/vscode.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/apps/site/public/illustrations/orm/collaborative.svg b/apps/site/public/illustrations/orm/collaborative.svg new file mode 100644 index 0000000000..b0dcf4b8e8 --- /dev/null +++ b/apps/site/public/illustrations/orm/collaborative.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/site/public/illustrations/orm/collaborative_light.svg b/apps/site/public/illustrations/orm/collaborative_light.svg new file mode 100644 index 0000000000..9a31577bcb --- /dev/null +++ b/apps/site/public/illustrations/orm/collaborative_light.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/site/public/illustrations/orm/data.svg b/apps/site/public/illustrations/orm/data.svg new file mode 100644 index 0000000000..cad84b6374 --- /dev/null +++ b/apps/site/public/illustrations/orm/data.svg @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/site/public/illustrations/orm/data_light.svg b/apps/site/public/illustrations/orm/data_light.svg new file mode 100644 index 0000000000..0b2f4c6dcb --- /dev/null +++ b/apps/site/public/illustrations/orm/data_light.svg @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/site/public/illustrations/orm/ide.svg b/apps/site/public/illustrations/orm/ide.svg new file mode 100644 index 0000000000..20466d3255 --- /dev/null +++ b/apps/site/public/illustrations/orm/ide.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/apps/site/public/illustrations/orm/ide_light.svg b/apps/site/public/illustrations/orm/ide_light.svg new file mode 100644 index 0000000000..0109d93445 --- /dev/null +++ b/apps/site/public/illustrations/orm/ide_light.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/apps/site/public/illustrations/orm/orm_1.svg b/apps/site/public/illustrations/orm/orm_1.svg new file mode 100644 index 0000000000..79328521f6 --- /dev/null +++ b/apps/site/public/illustrations/orm/orm_1.svg @@ -0,0 +1,325 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/site/public/illustrations/orm/orm_1_light.svg b/apps/site/public/illustrations/orm/orm_1_light.svg new file mode 100644 index 0000000000..de4b32f7ad --- /dev/null +++ b/apps/site/public/illustrations/orm/orm_1_light.svg @@ -0,0 +1,325 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/site/public/illustrations/orm/orm_2.svg b/apps/site/public/illustrations/orm/orm_2.svg new file mode 100644 index 0000000000..c01e156d78 --- /dev/null +++ b/apps/site/public/illustrations/orm/orm_2.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/site/public/illustrations/orm/orm_2_light.svg b/apps/site/public/illustrations/orm/orm_2_light.svg new file mode 100644 index 0000000000..33e133d9f0 --- /dev/null +++ b/apps/site/public/illustrations/orm/orm_2_light.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/site/public/illustrations/orm/thumbnail.png b/apps/site/public/illustrations/orm/thumbnail.png new file mode 100644 index 0000000000..eb51934c56 Binary files /dev/null and b/apps/site/public/illustrations/orm/thumbnail.png differ diff --git a/apps/site/public/illustrations/orm/typesafe.svg b/apps/site/public/illustrations/orm/typesafe.svg new file mode 100644 index 0000000000..d53be4ddae --- /dev/null +++ b/apps/site/public/illustrations/orm/typesafe.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/site/public/illustrations/orm/typesafe_light.svg b/apps/site/public/illustrations/orm/typesafe_light.svg new file mode 100644 index 0000000000..fad44e51de --- /dev/null +++ b/apps/site/public/illustrations/orm/typesafe_light.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/site/src/app/global.css b/apps/site/src/app/global.css index 799fd38d4c..de0bcc039f 100644 --- a/apps/site/src/app/global.css +++ b/apps/site/src/app/global.css @@ -123,6 +123,14 @@ body { transition: background 0.3s ease; } +.box.orm { + background: linear-gradient( + 180deg, + var(--color-background-default) 0%, + var(--color-background-orm) 262.5% + ) !important; +} + .box-visible { opacity: 1; transform: scale(1) translateY(0); @@ -175,6 +183,31 @@ body { var(--color-teal-400) 360deg ); } +/* Dark mode hover effect */ +.box.orm:hover::before { + background: conic-gradient( + from var(--angle, 0deg) at 50% 50%, + var(--color-indigo-400) 0deg, + rgba(22, 163, 148, 0.3) 30deg, + rgba(22, 29, 43, 0.8) 90deg, + rgba(22, 29, 43, 0.8) 270deg, + rgba(22, 163, 148, 0.3) 330deg, + var(--color-indigo-400) 360deg + ); +} + +/* Light mode hover effect */ +[data-theme="light"] .box.orm:hover::before { + background: conic-gradient( + from var(--angle, 0deg) at 50% 50%, + var(--color-indigo-400) 0deg, + rgba(22, 163, 148, 0.3) 30deg, + rgba(229, 231, 235, 0.8) 90deg, + rgba(229, 231, 235, 0.8) 270deg, + rgba(22, 163, 148, 0.3) 330deg, + var(--color-indigo-400) 360deg + ); +} .box:hover { transform: translateY(-1px) scale(1.005); @@ -192,3 +225,14 @@ body { .running { animation-play-state: running; } + +.link-btn { + color: var(--color-background-orm-reverse-strong); +} +.link-btn:hover { + color: var(--color-foreground-orm-reverse); +} +.link-btn > *:not(i) { + text-decoration: underline; + text-underline-offset: 3px; +} diff --git a/apps/site/src/app/orm/page.tsx b/apps/site/src/app/orm/page.tsx new file mode 100644 index 0000000000..458441949d --- /dev/null +++ b/apps/site/src/app/orm/page.tsx @@ -0,0 +1,441 @@ +import type { Metadata } from "next"; +import { + SITE_HOME_DESCRIPTION, + SITE_HOME_TITLE, +} from "../../lib/blog-metadata"; +import { Action, Button, Card, Separator } from "@prisma/eclipse"; +import { Bento } from "@/components/homepage/bento"; +import { CardSection } from "@/components/homepage/card-section/card-section"; +import review from "../../data/homepage.json"; +import Testimonials from "../../components/homepage/testimonials"; +import { InfoStats } from "@/components/orm/info-stats"; +import { cn } from "@/lib/cn"; +import { Card as FeatureCard } from "@/components/homepage/bento"; +import { YouTubePlayer } from "@prisma-docs/ui/components/youtube-player"; + +const statsSection = [ + { + icon: "fa-brands fa-github", + number: "45k+", + text: "Stars on GitHub", + link: "https://github.com/prisma/prisma", + }, + { + icon: "fa-regular fa-rocket-launch", + number: "250k+", + text: "Active developers", + link: "https://www.npmjs.com/package/prisma", + }, +]; +const badge_list = [ + { + title: "supported languages", + list: [ + { label: "JavaScript", url: "/js" }, + { label: "TypeScript", url: "/ts" }, + ], + }, + { + title: "community-supported languages", + list: [ + { label: "Python", url: "/py" }, + { label: "Dart", url: "/dart" }, + { label: "GO", url: "/GO" }, + { label: "Rust", url: "/rust" }, + ], + }, +]; +const CardFooter = () => ( + <> + +
+ {badge_list.map((badge: any) => ( +
+
+ {badge.title} +
+
+ {badge.list && + badge.list.map((item: any) => ( + + ))} +
+
+ ))} +
+ +); +const twoCol = [ + { + content: ( + <> +
+
+ Why Prisma ORM +
+

+ Delightful DB workflows +

+
+

+ Database workflows can feel brittle and error-prone. Prisma ORM + increases productivity and confidence when working with databases and + makes workflows like data modeling, migrations and querying easy. +

+ + ), + imageUrl: null, + imageAlt: null, + mobileImageUrl: null, + mobileImageAlt: null, + logos: null, + other: ( + + ), + useDefaultLogos: true, + visualPosition: "right" as const, + visualType: "other" as const, + footer: , + }, + { + content: ( +
+

+ Works with your favorite databases and frameworks +

+

+ Prisma's compatibility with popular tools ensures no stack 
lock-in, + lower integration costs, and smooth transitions. +

+ + Learn more + + +
+ ), + imageUrl: null, + imageAlt: null, + mobileImageUrl: null, + mobileImageAlt: null, + color: "orm" as const, + logos: null, + useDefaultLogos: true, + visualPosition: "right" as const, + visualType: "logoGrid" as const, + }, +]; +const twoCol_2 = [ + { + content: ( +
+
+
+ Prisma Benchmarks +
+

+ Prisma vs other ORMs +

+
+

+ A meaningful comparison of database query latencies across database + providers and ORM libraries in the Node.js & TypeScript ecosystem. +

+
+ ), + imageUrl: "/illustrations/orm/orm_1", + imageAlt: "ORM illustration", + mobileImageUrl: "/illustrations/orm/orm_1", + mobileImageAlt: "ORM illustration", + logos: null, + noShadow: true, + useDefaultLogos: true, + visualPosition: "left" as const, + visualType: "image" as const, + }, + { + content: ( +
+
+
+ Prisma Client +
+

+ Type-safe database client +

+
+

+ Prisma Client is a query builder that’s tailored to your schema. We + designed its API to be intuitive, both for SQL veterans and developers + brand new to databases. The auto-completion helps you figure out your + query without the need for documentation. +

+ + Learn more + + +
+ ), + imageUrl: "/illustrations/orm/orm_2", + imageAlt: "ORM illustration", + mobileImageUrl: "/illustrations/orm/orm_2", + mobileImageAlt: "ORM illustration", + color: "orm" as const, + noShadow: true, + logos: null, + useDefaultLogos: true, + visualPosition: "right" as const, + visualType: "image" as const, + }, +]; + +const twoCol_3 = [ + { + icon: "/icons/technologies/vscode.svg", + title: "Extra ergonomy in VS Code", + description: + "Auto-completion, linting, formatting and more help Prisma developers in VSCode stay confident and productive.", + btn: { + url: "/", + label: "Download Prisma VSCode Extension", + icon: "fa-regular fa-arrow-up-right", + }, + }, + { + icon: "/icons/technologies/ts.svg", + title: "Make fewer errors with TypeScript", + description: + "Prisma ORM provides the strongest type-safety guarantees of all the ORMs in the TypeScript ecosystem.", + btn: { + url: "/", + label: "Read comparison with TypeORM", + icon: "fa-regular fa-arrow-up-right", + }, + }, +]; + +const features = [ + { + title: "Manage databases", + subtitle: "Created directly in your IDE.", + image: "/illustrations/orm/ide", + alt: "Manage dbs", + icon: "fa-light fa-screwdriver-wrench", + link: "/mcp", + }, + { + title: "Type-safety", + subtitle: "Code faster with auto-completion and type safety.", + image: "/illustrations/orm/typesafe", + alt: "Type-safe queries", + icon: "fa-light fa-message-text", + link: "https://prisma.io/docs/orm/prisma-client/type-safety", + }, + { + title: "Data model you can read", + subtitle: "The Prisma schema is intuitive and easy to use", + image: "/illustrations/orm/collaborative", + alt: "Collaborative work", + icon: "fa-light fa-screen-users", + link: "https://console.prisma.io", + }, + { + title: "Browse your data", + subtitle: "Explore, filter, and edit your data with an interface.", + image: "/illustrations/orm/data", + alt: "Data browsing", + icon: "fa-light fa-magnifying-glass-arrow-right", + link: "/studio", + }, +]; + +export const metadata: Metadata = { + title: SITE_HOME_TITLE, + description: SITE_HOME_DESCRIPTION, +}; + +export default function ORM() { + return ( +
+
+
+
+

+ Next-generation Node.js and TypeScript ORM +

+

+ Real Postgres with the developer experience and infrastructure to + ship faster. +

+
+ + +
+
+
+
+ {statsSection?.map((stat: any, index: number) => ( + + ))} +
+
+
+ +
+
+
+
+ +
+ {twoCol_3.map((stat, index) => ( +
+ + {stat.title} + +

+ {stat.title} +

+

+ {stat.description} +

+ +
+ ))} +
+
+
+
+
+

+ Streamline your +
+ development workflow +

+
+

+ Integrate Prisma into your development ecosystem and focus on your + team’s core competencies +

+ +
+
+
+
+
+ {features.map((card: any) => ( + + ))} +
+
+ + {review?.testimonials?.length > 0 && ( +
+
+
+
+
+ +
+
+
+
+ )} +
+
+
+
+

+ Ready to get started? +

+

+ Start from scratch, add Prisma ORM to your existing project, or + explore how to build an app using your favorite framework. +

+
+
+ + +
+
+ Free to get started, no credit card needed. +
+
+
+
+
+ ); +} diff --git a/apps/site/src/components/homepage/bento.tsx b/apps/site/src/components/homepage/bento.tsx index a55c2f25ba..16a08757c7 100644 --- a/apps/site/src/components/homepage/bento.tsx +++ b/apps/site/src/components/homepage/bento.tsx @@ -29,20 +29,29 @@ interface BentoProps { bentoSection: { boxes: BentoBox[]; }; + hero?: React.ReactNode; + color?: "orm" | "ppg"; } -const HeroContent = ({ className = "" }: { className?: string }) => ( -
-

- Your database, right in your workflow -

-
-); +const HeroContent = ({ + className = "", + hero, +}: { + className?: string; + hero?: React.ReactNode; +}) => + hero || ( +
+

+ Your database, right in your workflow +

+
+ ); const useResponsiveLayout = () => { const [isDesktop, setIsDesktop] = useState(true); @@ -57,7 +66,7 @@ const useResponsiveLayout = () => { return { isDesktop }; }; -export const Bento = ({ bentoSection }: BentoProps) => { +export const Bento = ({ bentoSection, hero, color }: BentoProps) => { const { isDesktop } = useResponsiveLayout(); // Transform Sanity data to internal CardData format @@ -76,29 +85,37 @@ export const Bento = ({ bentoSection }: BentoProps) => { return (
{/* Desktop Layout (961+): Original 3-row layout with text in middle */} - + {isDesktop ? ( <>
{CARDS.filter((card) => card.row === "top").map((card) => ( - + ))}
{firstCenterCard && ( - + )} {secondCenterCard && ( - + )}
) : (
{CARDS.map((card) => ( - + ))}
)} @@ -108,9 +125,10 @@ export const Bento = ({ bentoSection }: BentoProps) => { interface CardProps { card: CardData; + color?: "orm" | "ppg"; } -const Card = ({ card }: CardProps) => { +export const Card = ({ card, color }: CardProps) => { const cardRef = useRef(null); const isCenterCard = ["4", "5"].includes(card.id); const { resolvedTheme } = useTheme(); @@ -156,12 +174,13 @@ const Card = ({ card }: CardProps) => { "box-visible", "w-full", isCenterCard && "w-full md:order-0", + color, )} onMouseMove={handleMouseMove} onMouseLeave={handleMouseLeave} >
- +
@@ -181,7 +200,6 @@ const Card = ({ card }: CardProps) => { ? `${card.image}_light.svg` : `${card.image}.svg` } - alt={card.title} className="px-4 z-2 pt-0 pb-0 min-w-full min-h-[60%] object-fill object-[top_left] [mask-image:linear-gradient(to_bottom,rgba(0,0,0,1)_60%,transparent_90%)] [-webkit-mask-image:linear-gradient(to_bottom,rgba(0,0,0,1)_60%,transparent_90%)]" /> diff --git a/apps/site/src/components/homepage/card-section/card-section.tsx b/apps/site/src/components/homepage/card-section/card-section.tsx index 3537d0b6f8..1715dbcc12 100644 --- a/apps/site/src/components/homepage/card-section/card-section.tsx +++ b/apps/site/src/components/homepage/card-section/card-section.tsx @@ -12,9 +12,13 @@ interface TwoColumnItem { mobileImageUrl: string | null; mobileImageAlt: string | null; logos: any[] | null; + footer?: ReactNode; + color?: "orm" | "ppg"; + other?: ReactNode; useDefaultLogos: boolean; visualPosition: "left" | "right"; - visualType: "logoGrid" | "image"; + visualType: "logoGrid" | "image" | "other"; + noShadow?: boolean; } interface CardSectionProps { @@ -58,13 +62,18 @@ export const CardSection = ({ cardSection }: CardSectionProps) => { item.visualType === "logoGrid" ? "max-w-full" : "lg:w-full", )} > + {item.other && item.visualType === "other" && item.other} {item.visualType === "logoGrid" && item.useDefaultLogos && ( - + )} {item.visualType === "image" && item.imageUrl && ( <> { /> {item.mobileImageUrl && ( { )}
+ {item.footer && <>{item.footer}} ))}
diff --git a/apps/site/src/components/homepage/card-section/logo-grid.tsx b/apps/site/src/components/homepage/card-section/logo-grid.tsx index 8a7daf78db..5de56907d4 100644 --- a/apps/site/src/components/homepage/card-section/logo-grid.tsx +++ b/apps/site/src/components/homepage/card-section/logo-grid.tsx @@ -5,6 +5,7 @@ import { useState, useEffect, memo } from "react"; import defaultLogosData from "./default-logos.json"; import { useTheme } from "@prisma-docs/ui/components/theme-provider"; import GlowCursor from "@/components/homepage/glow-cursor"; +import { cn } from "@/lib/cn"; // Inline keyframe animations const AnimationStyles = () => ( @@ -31,11 +32,13 @@ const AnimationStyles = () => ( // Inline LogoBar component const LogoBar = ({ logos, + color, direction = "right", pauseOnHover = false, duplicateCount = 3, }: { logos: Logo[]; + color?: "orm" | "ppg"; direction?: "left" | "right"; pauseOnHover?: boolean; duplicateCount?: number; @@ -54,7 +57,12 @@ const LogoBar = ({ @@ -80,6 +88,7 @@ interface Logo { interface LogoGridProps { logos?: Logo[]; type?: "spotlight" | "track"; + color?: "orm" | "ppg"; } // ============================================================================ @@ -119,33 +128,40 @@ LogoImage.displayName = "LogoImage"; // SPOTLIGHT MODE COMPONENT // ============================================================================ -const SpotlightMode = memo(({ logos }: { logos: Logo[] }) => { - const logoSize = 50; - const visibleLogos = logos.slice(0, 21); - - return ( - -
- {/* Logo grid */} -
-
- {visibleLogos.map((logo, index) => ( - - - - ))} +const SpotlightMode = memo( + ({ logos, color }: { logos: Logo[]; color?: "orm" | "ppg" }) => { + const logoSize = 50; + const visibleLogos = logos.slice(0, 21); + + return ( + +
+ {/* Logo grid */} +
+
+ {visibleLogos.map((logo, index) => ( + + + + ))} +
-
- - ); -}); + + ); + }, +); SpotlightMode.displayName = "SpotlightMode"; @@ -153,36 +169,49 @@ SpotlightMode.displayName = "SpotlightMode"; // TRACK MODE COMPONENT // ============================================================================ -const TrackMode = memo(({ logos }: { logos: Logo[] }) => { - // Split logos into 3 even groups - const logosPerBar = Math.ceil(logos.length / 3); - const logosBar1 = logos.slice(0, logosPerBar); - const logosBar2 = logos.slice(logosPerBar, logosPerBar * 2); - const logosBar3 = logos.slice(logosPerBar * 2); +const TrackMode = memo( + ({ logos, color }: { logos: Logo[]; color?: "orm" | "ppg" }) => { + // Split logos into 3 even groups + const logosPerBar = Math.ceil(logos.length / 3); + const logosBar1 = logos.slice(0, logosPerBar); + const logosBar2 = logos.slice(logosPerBar, logosPerBar * 2); + const logosBar3 = logos.slice(logosPerBar * 2); - return ( -
-
- - - -
- ); -}); + return ( +
+
+ + + +
+ ); + }, +); TrackMode.displayName = "TrackMode"; @@ -193,6 +222,7 @@ TrackMode.displayName = "TrackMode"; export const LogoGrid = ({ logos: propLogos, type = "spotlight", + color = undefined, }: LogoGridProps) => { const logos = propLogos && propLogos.length > 0 ? propLogos : defaultLogosData; @@ -201,9 +231,9 @@ export const LogoGrid = ({ <> {type === "track" ? ( - + ) : ( - + )} ); diff --git a/apps/site/src/components/homepage/testimonials/index.tsx b/apps/site/src/components/homepage/testimonials/index.tsx index 7c72fb182d..7cd52606ea 100644 --- a/apps/site/src/components/homepage/testimonials/index.tsx +++ b/apps/site/src/components/homepage/testimonials/index.tsx @@ -8,11 +8,13 @@ type TestimonialsType = { list: Array; noShadow?: boolean; mask?: string; + color?: string; }; type TestimonialColProps = { list: TestimonialItemType[]; reverse?: boolean; + color?: string; }; const MemoizedTestimonialItem = memo(TestimonialItem); @@ -26,7 +28,7 @@ const getColumnSlices = (list: TestimonialItemType[]) => { ]; }; -const TestimonialCol = ({ list, reverse }: TestimonialColProps) => ( +const TestimonialCol = ({ color, list, reverse }: TestimonialColProps) => (
( > {list.map((testimonial: TestimonialItemType, idx) => ( ))} {list.map((testimonial: TestimonialItemType, idx) => ( @@ -59,12 +63,14 @@ const TestimonialCol = ({ list, reverse }: TestimonialColProps) => ( > {list.map((testimonial: TestimonialItemType, idx) => ( ))} {list.map((testimonial: TestimonialItemType, idx) => ( @@ -78,7 +84,7 @@ const getTabletSlices = (list: TestimonialItemType[]) => { return [list.slice(0, half), list.slice(half)]; }; -const Testimonials = ({ list, noShadow, mask }: TestimonialsType) => { +const Testimonials = ({ color, list, noShadow, mask }: TestimonialsType) => { const [col1, col2, col3] = getColumnSlices(list); const [tabletCol1, tabletCol2] = getTabletSlices(list); @@ -94,7 +100,7 @@ const Testimonials = ({ list, noShadow, mask }: TestimonialsType) => {
{/* Mobile */}
- +
{/* Tablet */} @@ -105,8 +111,8 @@ const Testimonials = ({ list, noShadow, mask }: TestimonialsType) => { "[&>*:nth-child(2)]:flex [&>*]:flex-1", )} > - - + +
{/* Desktop */} @@ -117,9 +123,9 @@ const Testimonials = ({ list, noShadow, mask }: TestimonialsType) => { "[&>*:nth-child(2)]:flex [&>*:nth-child(3)]:flex [&>*]:flex-1", )} > - - - + + +
); diff --git a/apps/site/src/components/homepage/testimonials/testimonial-item.tsx b/apps/site/src/components/homepage/testimonials/testimonial-item.tsx index 552fbe6ddc..0eafcb806f 100644 --- a/apps/site/src/components/homepage/testimonials/testimonial-item.tsx +++ b/apps/site/src/components/homepage/testimonials/testimonial-item.tsx @@ -12,6 +12,7 @@ export type TestimonialItemType = { startups?: boolean; imageAlt?: string; key?: string | number; + color?: string; }; export const TestimonialItem = ({ @@ -22,6 +23,7 @@ export const TestimonialItem = ({ imageUrl, imageAlt, startups, + color, ...rest }: TestimonialItemType) => (
) : ( company && ( - + {company} ) diff --git a/apps/site/src/components/orm/info-stats.tsx b/apps/site/src/components/orm/info-stats.tsx new file mode 100644 index 0000000000..a5182ace66 --- /dev/null +++ b/apps/site/src/components/orm/info-stats.tsx @@ -0,0 +1,48 @@ +"use client"; +import { FunctionComponent } from "react"; +import AnimatedNumbers from "react-animated-numbers"; +import { cn } from "@/lib/cn"; +export const InfoStats: FunctionComponent<{ + icon?: any; + number?: string; + text?: string; + link?: string; +}> = ({ icon, number, text, link }) => { + const match = number?.match(/^(\d+)(\D.*)?$/); + + return ( + match && ( + + ) + ); +}; diff --git a/packages/eclipse/src/components/action.tsx b/packages/eclipse/src/components/action.tsx index e173a76037..fd0d2b8c6b 100644 --- a/packages/eclipse/src/components/action.tsx +++ b/packages/eclipse/src/components/action.tsx @@ -10,6 +10,7 @@ const actionVariants = cva( color: { ppg: "bg-background-ppg text-foreground-ppg", orm: "bg-background-orm text-foreground-orm", + "orm-reverse": "bg-background-orm-reverse text-foreground-orm-reverse", error: "bg-background-error text-foreground-error", success: "bg-background-success text-foreground-success", warning: "bg-background-warning text-foreground-warning", @@ -22,7 +23,8 @@ const actionVariants = cva( violet: "bg-background-violet text-foreground-violet", yellow: "bg-background-yellow text-foreground-yellow", neutral: "bg-background-neutral text-foreground-neutral", - "neutral-reversed": "bg-background-neutral-reverse text-foreground-neutral-reverse", + "neutral-reversed": + "bg-background-neutral-reverse text-foreground-neutral-reverse", }, size: { lg: "size-element-lg p-1.5", diff --git a/packages/eclipse/src/components/button.tsx b/packages/eclipse/src/components/button.tsx index 256477d76d..608a74b33a 100644 --- a/packages/eclipse/src/components/button.tsx +++ b/packages/eclipse/src/components/button.tsx @@ -9,6 +9,8 @@ const buttonVariants = cva( variant: { ppg: "bg-background-ppg-reverse text-foreground-ppg-reverse hover:bg-background-ppg-reverse-strong shadow-box-low", orm: "bg-background-orm-reverse text-foreground-orm-reverse hover:bg-background-orm-reverse-strong shadow-box-low", + "orm-reverse": + "bg-background-orm text-foreground-orm hover:bg-background-orm-strong shadow-box-low", "default-stronger": "bg-background-neutral text-foreground-neutral hover:bg-background-neutral-strong border border-stroke-neutral-strong", default: diff --git a/packages/ui/package.json b/packages/ui/package.json index bae4750462..c6ff4e0dc3 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -22,6 +22,7 @@ "clsx": "catalog:", "feed": "catalog:", "lucide-react": "catalog:", + "react-intersection-observer": "^10.0.3", "tailwind-merge": "catalog:", "vaul": "catalog:" }, diff --git a/packages/ui/src/components/youtube-player.tsx b/packages/ui/src/components/youtube-player.tsx new file mode 100644 index 0000000000..1639ba692e --- /dev/null +++ b/packages/ui/src/components/youtube-player.tsx @@ -0,0 +1,109 @@ +"use client"; +import { cn } from "../lib/cn"; +import { useEffect, useState } from "react"; +import { useInView } from "react-intersection-observer"; + +export const YouTubePlayer = ({ + video, + thumbnail, + className, + serverlessTalk = false, + autoplay = false, + overlay, + playOnView = false, +}: { + className?: string; + serverlessTalk?: boolean; + video: string; + thumbnail?: string; + playOnView?: boolean; + autoplay?: boolean; + overlay?: string; +}) => { + const [playing, setPlaying] = useState(false); + const [shouldAutoplay, setShouldAutoplay] = useState(autoplay); + const getAutoplayParams = () => + shouldAutoplay + ? `${video.includes("?") ? "&" : "?"}autoplay=1&mute=1&rel=0` + : ""; + + const { ref, inView } = useInView({ + threshold: 0.2, + triggerOnce: true, + }); + + useEffect(() => { + if (inView && playOnView && !playing) { + setPlaying(true); + setShouldAutoplay(true); + } + }, [inView, playOnView, playing]); + + return ( +
+ {overlay && ( +
+ )} + {thumbnail && !playing && ( +
{ + setPlaying(true); + setShouldAutoplay(true); + }} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + setPlaying(true); + setShouldAutoplay(true); + } + }} + className={cn( + "relative z-20 block w-full h-full object-cover", + serverlessTalk && "absolute z-[21] bg-[#151630]", + )} + > + thumbnail +
+ )} +
+ {video && ( +