;
+}) {
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/site/src/components/prisma-with/quote-section.tsx b/apps/site/src/components/prisma-with/quote-section.tsx
new file mode 100644
index 0000000000..2be5ed7c7d
--- /dev/null
+++ b/apps/site/src/components/prisma-with/quote-section.tsx
@@ -0,0 +1,23 @@
+import { Quote } from "@prisma-docs/ui/components/quote";
+
+type QuoteSectionData = {
+ text: string;
+ author: {
+ name: string;
+ imageUrl: string;
+ title: string;
+ company: string;
+ };
+};
+
+export function QuoteSection({ data }: { data: QuoteSectionData }) {
+ return (
+
+ );
+}
diff --git a/apps/site/src/components/prisma-with/resources-section.tsx b/apps/site/src/components/prisma-with/resources-section.tsx
new file mode 100644
index 0000000000..8965084fce
--- /dev/null
+++ b/apps/site/src/components/prisma-with/resources-section.tsx
@@ -0,0 +1,55 @@
+import { PostCard } from "@prisma-docs/ui/components/post-card";
+
+type ResourcesSectionData = {
+ title: string;
+ cards: Array<{
+ image: string;
+ url: string;
+ badge: string;
+ date: string;
+ title: string;
+ description: string;
+ author: {
+ name: string;
+ avatar: string;
+ };
+ }>;
+};
+
+export function ResourcesSection({ data }: { data: ResourcesSectionData }) {
+ return (
+
+
+
+ {data.title}
+
+
+ {data.cards.map((card, idx: number) => (
+
+ ))}
+
+
+
+ );
+}
diff --git a/apps/site/src/components/prisma-with/why-section.tsx b/apps/site/src/components/prisma-with/why-section.tsx
new file mode 100644
index 0000000000..1f64c51f24
--- /dev/null
+++ b/apps/site/src/components/prisma-with/why-section.tsx
@@ -0,0 +1,44 @@
+import parse from "html-react-parser";
+import { cn } from "../../lib/cn";
+import { Card, Action } from "@prisma/eclipse";
+
+type WhySectionData = {
+ title: string;
+ cards: Array<{
+ icon: string;
+ title: string;
+ description: string;
+ }>;
+};
+
+export function WhySection({ data }: { data: WhySectionData }) {
+ return (
+
+
+
+
+ {data.title}
+
+
+ {data.cards.map((card) => (
+
+
+
+
+
+ {parse(card.title)}
+
+
+ {parse(card.description)}
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/apps/site/src/data/prisma-with/nestjs.json b/apps/site/src/data/prisma-with/nestjs.json
new file mode 100644
index 0000000000..04a6d47a81
--- /dev/null
+++ b/apps/site/src/data/prisma-with/nestjs.json
@@ -0,0 +1,178 @@
+{
+ "hero": {
+ "tech": "NestJS",
+ "eyebrow": "Prisma in your stack",
+ "icon": "fa-solid fa-layer-group",
+ "imageUrl": "/icons/technologies/nestjs.svg",
+ "imageUrlLight": "/icons/technologies/nestjs-light.svg",
+ "title": "Enterprise-ready database for NestJS apps",
+ "description": "Build high-performance and type-safe NestJS apps with Prisma's developer friendly database tools: The world's most popular TypeScript ORM and the first serverless Postgres database without cold starts. ",
+ "btns": [
+ {
+ "label": "Try NestJS with Prisma",
+ "icon": "fa-regular fa-arrow-right",
+ "url": "https://github.com/prisma/nextjs-prisma-postgres-demo"
+ },
+ {
+ "label": "Read the docs",
+ "icon": "fa-regular fa-book-open",
+ "url": "/docs/guides/nestjs"
+ }
+ ]
+ },
+ "why": {
+ "title": "Why Prisma and Nest.js?",
+ "cards": [
+ {
+ "icon": "fa-regular fa-bolt",
+ "title": "Built for high-performance web apps",
+ "description": "Built on unikernels , Prisma Postgres runs on bare metal servers for peak performance and infinite scalability."
+ },
+ {
+ "icon": "fa-regular fa-head-side-brain",
+ "title": "Serverless, without cold starts",
+ "description": "The first serverless database with pay-as-you-go pricing, no infrastructure management, and zero cold starts."
+ },
+ {
+ "icon": "fa-regular fa-cloud-arrow-down",
+ "title": "Flexible data fetching & rendering",
+ "description": "Display your data using client-side rendering, server-side rendering and static site generation."
+ },
+ {
+ "icon": "fa-regular fa-globe",
+ "title": "Built-in global caching",
+ "description": "Add a cache strategy to any database query and its results will be cached close to your users for peak performance and UX."
+ },
+ {
+ "icon": "fa-regular fa-umbrella",
+ "title": "End-to-end type safety",
+ "description": "Pairing Prisma with Next.js ensures your app is coherently typed, from the database to your React components."
+ },
+ {
+ "icon": "fa-regular fa-users",
+ "title": "Helpful communities",
+ "description": "Both Next.js and Prisma have vibrant communities where you find support, fun events and amazing developers."
+ }
+ ]
+ },
+ "how": {
+ "title": "How Prisma and Next.js fit together",
+ "description": "Prisma’s database tools are the perfect fit for building Next.js applications, serving as the data access layer for Server Components (both static and dynamic), Server Actions, Route Handlers, and standalone services.",
+ "tabs": {
+ "defaultValue": "static-data",
+ "head": [
+ { "title": "Static Data", "value": "static-data" },
+ { "title": "Dynamic Data", "value": "dynamic-data" },
+ { "title": "Server Actions", "value": "server-actions" },
+ { "title": "API Routes", "value": "api-routes" },
+ { "title": "Client Components", "value": "client-components" }
+ ],
+ "body": [
+ {
+ "value": "static-data",
+ "content": "Static Site Generation with Prisma Next.js 13+ introduced a fundamental shift in how we handle data fetching. Server Components, which are the default in the App Router, allow you to fetch data directly in your components without extra wrappers or special functions. This means you can use Prisma directly in your components and automatically get static generation when possible.
Here's how to implement a blog page that's statically generated at build time. The data fetching happens server-side, and the HTML is generated once during build.
"
+ }
+ ]
+ }
+ },
+ "why_prisma": {
+ "title": "Why Prisma and Next.js?",
+ "cards": [
+ {
+ "image": "/images/blog/prisma-nextjs-orm.jpg",
+ "url": "/blog/post-slug",
+ "badge": "Release",
+ "date": "October 22, 2025",
+ "title": "How to use Prisma ORM with Next.js",
+ "description": "Learn how to build a fullstack Next.js 15 app with Prisma Postgres from scratch! This tutorial starts...",
+ "author": {
+ "name": "Johan Schmidt",
+ "avatar": "/images/authors/johan-schmidt.jpg"
+ }
+ },
+ {
+ "image": "/images/blog/nextjs-prisma-postgres-auth.jpg",
+ "badge": "Release",
+ "date": "October 22, 2025",
+ "url": "/blog/post-slug",
+ "title": "Next.js 15 + Prisma Postgres Auth Example",
+ "description": "This demo for a blogging application demonstrates how to build login and functionalit...",
+ "author": {
+ "name": "Johan Schmidt",
+ "avatar": "/images/authors/johan-schmidt.jpg"
+ }
+ },
+ {
+ "image": "/images/blog/nextjs-demo-app.jpg",
+ "badge": "Release",
+ "date": "October 22, 2025",
+ "url": "/blog/post-slug",
+ "title": "Next.js 15 Demo App with Prisma Postgres",
+ "description": "This demo for a blogging application demonstrates how to build login and functionalit...",
+ "author": {
+ "name": "Johan Schmidt",
+ "avatar": "/images/authors/johan-schmidt.jpg"
+ }
+ }
+ ]
+ },
+ "quote": {
+ "text": "“Next.js and Prisma is the ultimate combo if you need a database in React apps! 🚀”",
+ "author": {
+ "name": "Guillermo Rauch",
+ "imageUrl": "/photos/people/guillermo-rauch.jpeg",
+ "title": "CEO & Founder",
+ "company": "Vercel"
+ }
+ },
+ "community": {
+ "title": "Community Examples",
+ "cards": [
+ {
+ "icon": "fa-regular fa-file-lines",
+ "title": "Modern SaaS Starter Kit: next-forge",
+ "description": "A starter template for modern SaaS apps! next-forge comes with Next.js 15, auth, DB & ORM, payments, docs, blog, oitly, analytics, emails, and a lot more, t...",
+ "btn": {
+ "label": "Read tutorial",
+ "url": "#"
+ }
+ },
+ {
+ "icon": "fa-regular fa-file-lines",
+ "title": "Prisma in Next.js - My Fav Way to Work with Databases",
+ "description": "Learn best practices for using Prisma with Next.js App Router! Explore server components, server actions, and edge middleware, plus efficient queryi...",
+ "btn": {
+ "label": "Read tutorial",
+ "url": "#"
+ }
+ },
+ {
+ "icon": "fa-regular fa-file-lines",
+ "title": "Fullstack Form Builder",
+ "description": "This comprehensive 4-hour tutorial teaches you how to build a fullstack application. The form will be responsive, allow for drag & drop functionality, feat...",
+ "btn": {
+ "label": "Read tutorial",
+ "url": "#"
+ }
+ },
+ {
+ "icon": "fa-regular fa-code",
+ "title": "t3 Stack",
+ "description": "t3 is a web development stack focused on simplicity, modularity, and full-stack type safety. It includes Next.js, tRPC, Tailwind, TypeScript, Prisma and Nex...",
+ "btn": {
+ "label": "Explore on GitHub",
+ "url": "#"
+ }
+ },
+ {
+ "icon": "fa-regular fa-code",
+ "title": "Blitz.js",
+ "description": "Blitz.js is an application framework that is built on top of Next.js and Prisma. It brings back the simplicity and conventions of server-rendered fram...",
+ "btn": {
+ "label": "Visit Blitz.js website",
+ "url": "#"
+ }
+ }
+ ]
+ }
+}
diff --git a/apps/site/src/data/prisma-with/nextjs.json b/apps/site/src/data/prisma-with/nextjs.json
new file mode 100644
index 0000000000..9e9588c2d9
--- /dev/null
+++ b/apps/site/src/data/prisma-with/nextjs.json
@@ -0,0 +1,194 @@
+{
+ "hero": {
+ "tech": "Next.js",
+ "eyebrow": "Prisma in your stack",
+ "icon": "fa-solid fa-layer-group",
+ "imageUrl": "/icons/technologies/nextjs.svg",
+ "imageUrlLight": "/icons/technologies/nextjs-light.svg",
+ "title": "The easiest way to use Prisma in Next.js",
+ "description": "Build fast, type-safe Next.js apps with the world's most popular TypeScript ORM and the first serverless Postgres without cold starts.",
+ "btns": [
+ {
+ "label": "Try Next.js with Prisma",
+ "icon": "fa-regular fa-arrow-right",
+ "url": "https://github.com/prisma/nextjs-prisma-postgres-demo"
+ },
+ {
+ "label": "Read the docs",
+ "icon": "fa-regular fa-book-open",
+ "url": "/docs/guides/nextjs"
+ }
+ ]
+ },
+ "why": {
+ "title": "Why Prisma and Next.js?",
+ "cards": [
+ {
+ "icon": "fa-regular fa-bolt",
+ "title": "Built for high-performance web apps",
+ "description": "Built on unikernels , Prisma Postgres runs on bare metal servers for peak performance and infinite scalability."
+ },
+ {
+ "icon": "fa-regular fa-face-icicles",
+ "title": "Serverless, without cold starts",
+ "description": "The first serverless database with pay-as-you-go pricing, no infrastructure management, and zero cold starts."
+ },
+ {
+ "icon": "fa-regular fa-cloud-arrow-up",
+ "title": "Flexible data fetching & rendering",
+ "description": "Display your data using client-side rendering, server-side rendering and static site generation."
+ },
+ {
+ "icon": "fa-regular fa-globe-pointer",
+ "title": "Built-in global caching",
+ "description": "Add a cache strategy to any database query and its results will be cached close to your users for peak performance and UX."
+ },
+ {
+ "icon": "fa-regular fa-umbrella",
+ "title": "End-to-end type safety",
+ "description": "Pairing Prisma with Next.js ensures your app is coherently typed, from the database to your React components."
+ },
+ {
+ "icon": "fa-regular fa-people-arrows",
+ "title": "Helpful communities",
+ "description": "Both Next.js and Prisma have vibrant communities where you find support, fun events and amazing developers."
+ }
+ ]
+ },
+ "how": {
+ "title": "How Prisma and Next.js fit together",
+ "description": "Prisma’s database tools are the perfect fit for building Next.js applications, serving as the data access layer for Server Components (both static and dynamic), Server Actions, Route Handlers, and standalone services.",
+ "tabs": {
+ "defaultValue": "static-data",
+ "head": [
+ { "title": "Static Data", "value": "static-data" },
+ { "title": "Dynamic Data", "value": "dynamic-data" },
+ { "title": "Server Actions", "value": "server-actions" },
+ { "title": "API Routes", "value": "api-routes" },
+ { "title": "Client Components", "value": "client-components" }
+ ],
+ "body": [
+ {
+ "value": "static-data",
+ "content": "Static Site Generation with Prisma Next.js 13+ introduced a fundamental shift in how we handle data fetching. Server Components, which are the default in the App Router, allow you to fetch data directly in your components without extra wrappers or special functions. This means you can use Prisma directly in your components and automatically get static generation when possible.
Here's how to implement a blog page that's statically generated at build time. The data fetching happens server-side, and the HTML is generated once during build.
"
+ },
+ {
+ "value": "dynamic-data",
+ "content": "Dynamic Server-Side Data Fetching with Prisma For dynamic data that needs to be fresh on every request, like a user's dashboard or authenticated content, Server Components can be used. They automatically execute on the server for each request, making them perfect for displaying real-time or user-specific data. The following example shows how to implement a dashboard that displays user-specific content.
"
+ },
+ {
+ "value": "server-actions",
+ "content": "Server Actions with Prisma Server Actions can be used in place of API routes for many cases. They allow you to define server-side functions that can be called directly from your components, making them perfect for database mutations. They're also progressive enhancement-friendly, working even without JavaScript enabled.
"
+ },
+ {
+ "value": "api-routes",
+ "content": "API Routes with Route Handlers While Server Actions cover many use cases, sometimes you still need traditional API endpoints, like for webhook handlers or public APIs. Next.js 13+ introduces Route Handlers as a replacement for API routes. These handlers are more powerful and flexible than the old API routes, supporting modern Web API standards.
"
+ },
+ {
+ "value": "client-components",
+ "content": "Client Components with Server Data Sometimes you need to combine server-side data fetching with client-side interactivity. Next.js makes this easy by allowing you to pass server-fetched data to Client Components. This pattern is perfect for cases where you want the initial data to be server-rendered but need client-side interactivity after the initial load.
"
+ }
+ ]
+ }
+ },
+ "why_prisma": {
+ "title": "Why Prisma and Next.js?",
+ "cards": [
+ {
+ "image": "/images/blog/prisma-nextjs-orm.jpg",
+ "url": "/blog/post-slug",
+ "badge": "Vercel",
+ "date": "October 22, 2025",
+ "title": "How to use Prisma ORM with Next.js",
+ "description": "Learn how to build a fullstack Next.js 15 app with Prisma Postgres from scratch! This tutorial starts...",
+ "author": {
+ "name": "Johan Schmidt",
+ "avatar": "/images/authors/johan-schmidt.jpg"
+ }
+ },
+ {
+ "image": "/images/blog/nextjs-prisma-postgres-auth.jpg",
+ "badge": "Prisma Postgres",
+ "date": "October 22, 2025",
+ "url": "/blog/post-slug",
+ "title": "Next.js 15 + Prisma Postgres Auth Example",
+ "description": "This demo for a blogging application demonstrates how to build login and functionalit...",
+ "author": {
+ "name": "Johan Schmidt",
+ "avatar": "/images/authors/johan-schmidt.jpg"
+ }
+ },
+ {
+ "image": "/images/blog/nextjs-demo-app.jpg",
+ "badge": "Release",
+ "date": "October 22, 2025",
+ "url": "/blog/post-slug",
+ "title": "Next.js 15 Demo App with Prisma Postgres",
+ "description": "This demo for a blogging application demonstrates how to build login and functionalit...",
+ "author": {
+ "name": "Johan Schmidt",
+ "avatar": "/images/authors/johan-schmidt.jpg"
+ }
+ }
+ ]
+ },
+ "quote": {
+ "text": "“Next.js and Prisma is the ultimate combo if you need a database in React apps! 🚀”",
+ "author": {
+ "name": "Guillermo Rauch",
+ "imageUrl": "/photos/people/guillermo-rauch.jpeg",
+ "title": "CEO & Founder",
+ "company": "Vercel"
+ }
+ },
+ "community": {
+ "title": "Community Examples",
+ "cards": [
+ {
+ "icon": "fa-regular fa-file-lines",
+ "title": "Modern SaaS Starter Kit: next-forge",
+ "description": "A starter template for modern SaaS apps! next-forge comes with Next.js 15, auth, DB & ORM, payments, docs, blog, oitly, analytics, emails, and a lot more, t...",
+ "btn": {
+ "label": "Read tutorial",
+ "url": "https://www.next-forge.com/"
+ }
+ },
+ {
+ "icon": "fa-regular fa-file-lines",
+ "title": "Prisma in Next.js - My Fav Way to Work with Databases",
+ "description": "Learn best practices for using Prisma with Next.js App Router! Explore server components, server actions, and edge middleware, plus efficient queryi...",
+ "btn": {
+ "label": "Read tutorial",
+ "url": "https://www.youtube.com/watch?v=QXxy8Uv1LnQ"
+ }
+ },
+ {
+ "icon": "fa-regular fa-file-lines",
+ "title": "Fullstack Form Builder",
+ "description": "This comprehensive 4-hour tutorial teaches you how to build a fullstack application. The form will be responsive, allow for drag & drop functionality, feat...",
+ "btn": {
+ "label": "Watch tutorial",
+ "url": "https://www.youtube.com/watch?v=QGXUUXy0AMw&ab_channel=CodewithKliton"
+ }
+ },
+ {
+ "icon": "fa-regular fa-code",
+ "title": "t3 Stack",
+ "description": "t3 is a web development stack focused on simplicity, modularity, and full-stack type safety. It includes Next.js, tRPC, Tailwind, TypeScript, Prisma and Nex...",
+ "btn": {
+ "label": "Explore on GitHub",
+ "url": "https://github.com/t3-oss/create-t3-app"
+ }
+ },
+ {
+ "icon": "fa-regular fa-code",
+ "title": "Blitz.js",
+ "description": "Blitz.js is an application framework that is built on top of Next.js and Prisma. It brings back the simplicity and conventions of server-rendered fram...",
+ "btn": {
+ "label": "Visit Blitz.js website",
+ "url": "https://blitzjs.com/"
+ }
+ }
+ ]
+ }
+}
diff --git a/apps/site/src/data/prisma-with/remix.json b/apps/site/src/data/prisma-with/remix.json
new file mode 100644
index 0000000000..25759838c4
--- /dev/null
+++ b/apps/site/src/data/prisma-with/remix.json
@@ -0,0 +1,184 @@
+{
+ "hero": {
+ "tech": "Remix",
+ "eyebrow": "Prisma in your stack",
+ "icon": "fa-solid fa-layer-group",
+ "imageUrl": "/icons/technologies/remix.svg",
+ "imageUrlLight": "/icons/technologies/remix-light.svg",
+ "title": "Build fullstack Remix apps with Prisma",
+ "description": "Prisma is the perfect database toolkit for Remix applications. Get type-safe database access, migrations, and data modeling that works seamlessly with Remix loaders and actions.",
+ "btns": [
+ {
+ "label": "Try Remix with Prisma",
+ "icon": "fa-regular fa-arrow-right",
+ "url": "https://github.com/prisma/remix-prisma-starter"
+ },
+ {
+ "label": "Read the docs",
+ "icon": "fa-regular fa-book-open",
+ "url": "/docs/guides/remix"
+ }
+ ]
+ },
+ "why": {
+ "title": "Why Prisma and Remix?",
+ "cards": [
+ {
+ "icon": "fa-regular fa-bolt",
+ "title": "Server-side data fetching",
+ "description": "Remix loaders run on the server, making Prisma the perfect choice for fetching data with full type safety from database to UI."
+ },
+ {
+ "icon": "fa-regular fa-arrows-rotate",
+ "title": "Seamless mutations",
+ "description": "Use Prisma in Remix actions to handle form submissions and mutations with end-to-end type safety."
+ },
+ {
+ "icon": "fa-regular fa-database",
+ "title": "Optimistic UI updates",
+ "description": "Remix's optimistic UI patterns work perfectly with Prisma's predictable data layer for instant user feedback."
+ },
+ {
+ "icon": "fa-regular fa-shield-check",
+ "title": "Type-safe by default",
+ "description": "Prisma Client generates types from your schema, ensuring your entire Remix app is coherently typed."
+ },
+ {
+ "icon": "fa-regular fa-gauge-high",
+ "title": "Edge-ready architecture",
+ "description": "Deploy to the edge with Prisma Accelerate for ultra-low latency database queries worldwide."
+ },
+ {
+ "icon": "fa-regular fa-code-branch",
+ "title": "Developer experience",
+ "description": "Prisma Studio + Remix Dev Tools = the ultimate fullstack development experience."
+ }
+ ]
+ },
+ "how": {
+ "title": "How Prisma and Remix fit together",
+ "description": "Prisma integrates seamlessly with Remix's data loading and mutation patterns. Use Prisma Client in loaders for data fetching and in actions for mutations, all with full type safety.",
+ "tabs": {
+ "defaultValue": "loaders",
+ "head": [
+ { "title": "Loaders", "value": "loaders" },
+ { "title": "Actions", "value": "actions" },
+ { "title": "Error Handling", "value": "error-handling" }
+ ],
+ "body": [
+ {
+ "value": "loaders",
+ "content": "Data Loading with Prisma Remix loaders are the perfect place to use Prisma. They run on the server, so you have full access to your database. The data you return is automatically serialized and sent to your component.
"
+ },
+ {
+ "value": "actions",
+ "content": "Mutations with Prisma Remix actions handle form submissions and mutations. Use Prisma to create, update, or delete data, then redirect or return updated data to your UI.
"
+ },
+ {
+ "value": "error-handling",
+ "content": "Error Boundaries Combine Remix's error boundaries with Prisma's error handling for robust, user-friendly error states throughout your application.
"
+ }
+ ]
+ }
+ },
+ "why_prisma": {
+ "title": "Learn more about Prisma and Remix",
+ "cards": [
+ {
+ "image": "/images/blog/remix-prisma-starter.jpg",
+ "url": "/blog/remix-prisma-starter",
+ "badge": "Tutorial",
+ "date": "November 15, 2024",
+ "title": "Build a fullstack Remix app with Prisma",
+ "description": "Learn how to build a complete Remix application with Prisma from scratch, including authentication and CRUD operations.",
+ "author": {
+ "name": "Sarah Johnson",
+ "avatar": "/images/authors/sarah-johnson.jpg"
+ }
+ },
+ {
+ "image": "/images/blog/remix-optimistic-ui.jpg",
+ "badge": "Guide",
+ "date": "October 8, 2024",
+ "url": "/blog/remix-optimistic-ui",
+ "title": "Optimistic UI with Remix and Prisma",
+ "description": "Master optimistic UI patterns in Remix with Prisma for instant user feedback and better UX.",
+ "author": {
+ "name": "Mike Chen",
+ "avatar": "/images/authors/mike-chen.jpg"
+ }
+ },
+ {
+ "image": "/images/blog/remix-edge-deployment.jpg",
+ "badge": "Guide",
+ "date": "September 22, 2024",
+ "url": "/blog/remix-edge-deployment",
+ "title": "Deploy Remix + Prisma to the Edge",
+ "description": "Learn how to deploy your Remix app with Prisma to edge networks for global low-latency performance.",
+ "author": {
+ "name": "Alex Rivera",
+ "avatar": "/images/authors/alex-rivera.jpg"
+ }
+ }
+ ]
+ },
+ "quote": {
+ "text": "Prisma + Remix is my go-to stack for building modern web apps. The DX is unmatched!",
+ "author": {
+ "name": "Kent C. Dodds",
+ "imageUrl": "/photos/people/kent-c-dodds.jpeg",
+ "title": "Educator & Engineer",
+ "company": "EpicWeb.dev"
+ }
+ },
+ "community": {
+ "title": "Community Resources",
+ "cards": [
+ {
+ "icon": "fa-regular fa-file-lines",
+ "title": "Remix Blues Stack",
+ "description": "The official Remix Blues Stack comes with Prisma pre-configured. Perfect for PostgreSQL-based apps with authentication, testing, and deployment setup.",
+ "btn": {
+ "label": "View on GitHub",
+ "url": "https://github.com/remix-run/blues-stack"
+ }
+ },
+ {
+ "icon": "fa-regular fa-video",
+ "title": "Kent C. Dodds: Remix + Prisma Workshop",
+ "description": "Deep dive into building production-ready Remix applications with Prisma. Covers data modeling, migrations, and best practices.",
+ "btn": {
+ "label": "Watch workshop",
+ "url": "#"
+ }
+ },
+ {
+ "icon": "fa-regular fa-code",
+ "title": "Remix SaaS Template",
+ "description": "A complete SaaS starter template with Remix, Prisma, authentication, payments, and multi-tenancy built-in.",
+ "btn": {
+ "label": "Explore template",
+ "url": "#"
+ }
+ },
+ {
+ "icon": "fa-regular fa-book",
+ "title": "Prisma in Remix Cookbook",
+ "description": "Collection of recipes and patterns for common use cases when using Prisma with Remix, from pagination to real-time updates.",
+ "btn": {
+ "label": "Read cookbook",
+ "url": "#"
+ }
+ },
+ {
+ "icon": "fa-regular fa-graduation-cap",
+ "title": "Fullstack Remix Course",
+ "description": "Learn to build a complete e-commerce application with Remix and Prisma. Includes video lessons, exercises, and project files.",
+ "btn": {
+ "label": "Start learning",
+ "url": "#"
+ }
+ }
+ ]
+ }
+}
diff --git a/apps/site/src/lib/cn.ts b/apps/site/src/lib/cn.ts
new file mode 100644
index 0000000000..ba66fd250b
--- /dev/null
+++ b/apps/site/src/lib/cn.ts
@@ -0,0 +1 @@
+export { twMerge as cn } from 'tailwind-merge';
diff --git a/apps/site/src/lib/shiki_prisma.ts b/apps/site/src/lib/shiki_prisma.ts
new file mode 100644
index 0000000000..d240aa9828
--- /dev/null
+++ b/apps/site/src/lib/shiki_prisma.ts
@@ -0,0 +1,207 @@
+import { createHighlighter, type ThemeRegistration } from "shiki";
+
+const prismaTheme: ThemeRegistration = {
+ name: "prisma-dark",
+ type: "dark",
+ colors: {
+ "editor.background": "transparent",
+ "editor.foreground": "var(--color-foreground-neutral-weak)",
+ },
+ settings: [
+ {
+ scope: ["comment", "punctuation.definition.comment"],
+ settings: {
+ foreground: "var(--color-disabled)",
+ },
+ },
+ {
+ scope: [
+ "variable",
+ "variable.other.readwrite",
+ "variable.other.object",
+ "variable.other.property",
+ "support.variable",
+ ],
+ settings: {
+ foreground: "var(--color-foreground-neutral-weak)",
+ },
+ },
+ {
+ scope: [
+ "variable.other.constant",
+ "variable.language.this",
+ "variable.language.super",
+ ],
+ settings: {
+ foreground: "var(--color-background-ppg-reverse-strong)",
+ },
+ },
+ {
+ scope: [
+ "keyword",
+ "keyword.control",
+ "keyword.operator.new",
+ "keyword.control.import",
+ "keyword.control.export",
+ "keyword.control.from",
+ "keyword.control.default",
+ "keyword.control.as",
+ "keyword.control.async",
+ "keyword.control.await",
+ "storage.type",
+ "storage.modifier",
+ "storage.type.function",
+ "storage.type.class",
+ "storage.type.const",
+ "storage.type.let",
+ "storage.type.var",
+ ],
+ settings: {
+ foreground: "var(--color-background-orm-reverse-strong)",
+ },
+ },
+ {
+ scope: [
+ "entity.name.function",
+ "meta.function-call",
+ "support.function",
+ "entity.name.method",
+ ],
+ settings: {
+ foreground: "var(--color-background-ppg-reverse-strong)",
+ },
+ },
+ {
+ scope: [
+ "entity.name.type",
+ "entity.name.class",
+ "support.type",
+ "support.class",
+ "meta.import variable.other.readwrite",
+ "meta.export variable.other.readwrite",
+ ],
+ settings: {
+ foreground: "var(--color-background-orm-reverse-strong)",
+ },
+ },
+ {
+ scope: [
+ "string",
+ "string.quoted.single",
+ "string.quoted.double",
+ "string.template",
+ "punctuation.definition.string.begin",
+ "punctuation.definition.string.end",
+ ],
+ settings: {
+ foreground: "var(--color-background-ppg-reverse-strong)",
+ },
+ },
+ {
+ scope: ["constant.numeric", "constant.language", "constant.character"],
+ settings: {
+ foreground: "var(--color-background-warning-reverse-strong)",
+ },
+ },
+ {
+ scope: [
+ "constant.language.boolean",
+ "constant.language.null",
+ "constant.language.undefined",
+ ],
+ settings: {
+ foreground: "var(--color-background-orm-reverse-strong)",
+ },
+ },
+ {
+ scope: [
+ "keyword.operator",
+ "keyword.operator.arithmetic",
+ "keyword.operator.assignment",
+ "keyword.operator.comparison",
+ "keyword.operator.logical",
+ ],
+ settings: {
+ foreground: "var(--color-background-orm-reverse-strong)",
+ },
+ },
+ {
+ scope: ["keyword.operator.type", "keyword.operator.expression"],
+ settings: {
+ foreground: "var(--color-background-orm-reverse-strong)",
+ },
+ },
+ {
+ scope: [
+ "punctuation.accessor",
+ "punctuation.separator",
+ "punctuation.terminator",
+ "punctuation.definition.block",
+ "punctuation.definition.parameters",
+ "punctuation.definition.arguments",
+ "meta.brace.round",
+ "meta.brace.square",
+ "meta.brace.curly",
+ ],
+ settings: {
+ foreground: "var(--color-foreground-neutral-weak)",
+ },
+ },
+ {
+ scope: ["entity.name.tag", "punctuation.definition.tag"],
+ settings: {
+ foreground: "#f92672",
+ },
+ },
+ {
+ scope: ["entity.other.attribute-name"],
+ settings: {
+ foreground: "var(--color-background-ppg-reverse-strong)",
+ },
+ },
+ {
+ scope: ["meta.tag.attributes string.quoted"],
+ settings: {
+ foreground: "#96E072",
+ },
+ },
+ {
+ scope: [
+ "punctuation.definition.template-expression",
+ "punctuation.section.embedded",
+ ],
+ settings: {
+ foreground: "#f92672",
+ },
+ },
+ {
+ scope: ["meta.template.expression"],
+ settings: {
+ foreground: "#D5CED9",
+ },
+ },
+ {
+ scope: ["support.type.primitive", "support.type.builtin"],
+ settings: {
+ foreground: "#7cb7ff",
+ },
+ },
+ {
+ scope: ["variable.parameter", "meta.parameters variable"],
+ settings: {
+ foreground: "var(--color-background-warning-reverse-strong)",
+ },
+ },
+ {
+ scope: ["invalid", "invalid.illegal"],
+ settings: {
+ foreground: "#FC644D",
+ },
+ },
+ ],
+};
+
+export const prisma_highlighter = await createHighlighter({
+ themes: [prismaTheme],
+ langs: ["typescript", "javascript", "jsx", "tsx", "json", "bash", "sh"],
+});
diff --git a/apps/site/src/lib/blog-metadata.ts b/apps/site/src/lib/site-metadata.ts
similarity index 100%
rename from apps/site/src/lib/blog-metadata.ts
rename to apps/site/src/lib/site-metadata.ts
diff --git a/packages/eclipse/src/components/button.tsx b/packages/eclipse/src/components/button.tsx
index 061f56253c..256477d76d 100644
--- a/packages/eclipse/src/components/button.tsx
+++ b/packages/eclipse/src/components/button.tsx
@@ -5,29 +5,36 @@ import { cn } from "../lib/cn";
const buttonVariants = cva(
"flex flex-row justify-center items-center rounded-square transition-all duration-50 cursor-pointer disabled:bg-background-neutral-weak! disabled:border-none! disabled:text-foreground-neutral-weaker! disabled:cursor-not-allowed disabled:shadow-none",
{
- variants: {
- 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",
- "default-stronger": "bg-background-neutral text-foreground-neutral hover:bg-background-neutral-strong",
- default: "bg-background-default hover:bg-background-neutral border border-stroke-neutral hover:border-stroke-neutral-strong text-foreground-neutral shadow-box-low",
- "default-weaker": "bg-transparent hover:bg-background-neutral text-foreground-neutral",
- error: "bg-background-error-reverse text-foreground-error-reverse hover:bg-background-error-reverse-strong shadow-box-low",
- success: "bg-background-success-reverse text-foreground-success-reverse hover:bg-background-success-reverse-strong shadow-box-low",
- link: "text-foreground-neutral underline-offset-4 hover:underline focus-visible:ring-foreground-neutral",
+ variants: {
+ 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",
+ "default-stronger":
+ "bg-background-neutral text-foreground-neutral hover:bg-background-neutral-strong border border-stroke-neutral-strong",
+ default:
+ "bg-background-default hover:bg-background-neutral border border-stroke-neutral hover:border-stroke-neutral-strong text-foreground-neutral shadow-box-low",
+ "default-weaker":
+ "bg-transparent hover:bg-background-neutral text-foreground-neutral",
+ error:
+ "bg-background-error-reverse text-foreground-error-reverse hover:bg-background-error-reverse-strong shadow-box-low",
+ success:
+ "bg-background-success-reverse text-foreground-success-reverse hover:bg-background-success-reverse-strong shadow-box-low",
+ link: "text-foreground-neutral underline-offset-4 hover:underline focus-visible:ring-foreground-neutral",
+ },
+ size: {
+ lg: "px-2 h-element-lg type-text-sm-strong",
+ xl: "px-3 h-element-xl type-text-sm-strong",
+ "2xl": "px-3 h-element-2xl type-text-sm-strong",
+ "3xl": "px-4 h-element-3xl type-text-sm-strong",
+ "4xl": "px-4 h-element-4xl type-heading-md",
+ },
},
- size: {
- lg: "px-2 h-element-lg type-text-sm-strong",
- xl: "px-3 h-element-xl type-text-sm-strong",
- "2xl": "px-3 h-element-2xl type-text-sm-strong",
- "4xl": "px-4 h-element-4xl type-heading-md",
+ defaultVariants: {
+ variant: "default",
+ size: "lg",
},
},
- defaultVariants: {
- variant: "default",
- size: "lg",
- },
-});
+);
type ButtonBaseProps = VariantProps;
@@ -47,27 +54,27 @@ const Button = React.forwardRef<
HTMLButtonElement | HTMLAnchorElement,
ButtonProps
>(({ className, variant, size, href, ...props }, ref) => {
- const classNames = cn(buttonVariants({ variant, size, className }));
-
- if (href) {
- return (
- }
- href={href}
- {...(props as React.AnchorHTMLAttributes)}
- />
- );
- }
+ const classNames = cn(buttonVariants({ variant, size, className }));
+ if (href) {
return (
- }
- {...(props as React.ButtonHTMLAttributes)}
+ ref={ref as React.Ref}
+ href={href}
+ {...(props as React.AnchorHTMLAttributes)}
/>
);
- });
+ }
+
+ return (
+ }
+ {...(props as React.ButtonHTMLAttributes)}
+ />
+ );
+});
Button.displayName = "Button";
diff --git a/packages/eclipse/src/styles/globals.css b/packages/eclipse/src/styles/globals.css
index 673d7c3e58..f00310d57a 100644
--- a/packages/eclipse/src/styles/globals.css
+++ b/packages/eclipse/src/styles/globals.css
@@ -4,551 +4,592 @@
@source "../components/**/*.{tsx,mjs,js}";
@theme {
- /* Blur */
- --blur-surface-low: 1rem;
- --blur-surface: 1.5rem;
- --blur-surface-high: 2.5rem;
-
- /* Box Shadow */
- --shadow-box-low: 0 0.0625rem 0.125rem 0 rgba(0, 0, 0, 0.04);
- --shadow-box: 0 0.5rem 0.5rem -0.5rem rgba(0, 0, 0, 0.04), 0 0.125rem 0.125rem 0 rgba(0, 0, 0, 0.04);
- --shadow-box-high: 0 0.5rem 1rem -0.25rem rgba(0, 0, 0, 0.04), 0 0.125rem 0.125rem 0 rgba(0, 0, 0, 0.04);
-
- /* Radius */
- --radius-circle: 999px;
- --radius-square-low: 0.1875rem;
- --radius-square: 0.375rem;
- --radius-square-high: 0.75rem;
-
- /* Spacing - Margin */
- --spacing-margin-4xs: 0.25rem;
- --spacing-margin-3xs: 0.5rem;
- --spacing-margin-2xs: 0.75rem;
- --spacing-margin-xs: 1rem;
- --spacing-margin-sm: 1.25rem;
- --spacing-margin-md: 1.5rem;
- --spacing-margin-lg: 1.75rem;
- --spacing-margin-xl: 2rem;
- --spacing-margin-4xl: 3rem;
-
- /* Spacing - Element Sizing */
- --spacing-element-2xs: 0.75rem;
- --spacing-element-xs: 1rem;
- --spacing-element-sm: 1.25rem;
- --spacing-element-md: 1.5rem;
- --spacing-element-lg: 1.75rem;
- --spacing-element-xl: 2rem;
- --spacing-element-2xl: 2.25rem;
- --spacing-element-3xl: 2.5rem;
- --spacing-element-4xl: 3rem;
- --spacing-element-5xl: 4rem;
- --spacing-element-6xl: 6rem;
-
- /* Typography - Font Families */
- --font-sans-display: "Mona Sans VF", "Inter", "Roboto", "Helvetica Neue", "Arial Nova", "Nimbus Sans", "Arial", sans-serif;
- --font-sans-display-settings: "ss01" on, "ss02" on, "ss05" on, "ss06" on;
- --font-sans: "Inter", "Roboto", "Helvetica Neue", "Arial Nova", "Nimbus Sans", "Arial", sans-serif;
- --font-sans-settings: "cv01" on, "cv02" on, "cv06" on, "cv07" on, "cv08" on, "cv10" on;
- --font-mono: "Mona Sans Mono VF", ui-monospace, "Cascadia Code", "Source Code Pro", "Menlo", "Consolas", "DejaVu Sans Mono", monospace;
- --font-mono--settings: "ss01" on, "ss02" on, "ss05" on, "ss06" on;
-
- /* Typography - Font Sizes with paired Line Heights */
- --text-2xs: 0.6875rem;
- --text-2xs--line-height: 1rem;
- --text-xs: 0.75rem;
- --text-xs--line-height: 1rem;
- --text-sm: 0.875rem;
- --text-sm--line-height: 1.25rem;
- --text-md: 1rem;
- --text-md--line-height: 1.5rem;
- --text-lg: 1.125rem;
- --text-lg--line-height: 1.75rem;
- --text-xl: 1.25rem;
- --text-xl--line-height: 1.75rem;
- --text-2xl: 1.5rem;
- --text-2xl--line-height: 2rem;
- --text-3xl: 1.875rem;
- --text-3xl--line-height: 2.25rem;
- --text-4xl: 2.5rem;
- --text-4xl--line-height: 3rem;
- --text-5xl: 4rem;
- --text-5xl--line-height: 4.5rem;
-
- /* Background Colors */
- --color-background-default: #ffffff;
- --color-background-default-050: rgb(255 255 255 / 0.5);
- --color-background-default-075: rgb(255 255 255 / 0.75);
- --color-background-neutral-strong: #e5e7eb;
- --color-background-neutral: #f3f4f6;
- --color-background-neutral-weak: #f9fafb;
- --color-background-neutral-weaker: #fcfdfd;
- --color-background-neutral-reverse-strong: #111827;
- --color-background-neutral-reverse: #1f2937;
- --color-background-ppg-strong: #ccfbf1;
- --color-background-ppg: #f0fdfa;
- --color-background-ppg-reverse-strong: #0d9488;
- --color-background-ppg-reverse: #14b8a6;
- --color-background-orm-strong: #e0e7ff;
- --color-background-orm: #eef2ff;
- --color-background-orm-reverse-strong: #4f46e5;
- --color-background-orm-reverse: #6366f1;
- --color-background-error-strong: #fee2e2;
- --color-background-error: #fef2f2;
- --color-background-error-reverse-strong: #dc2626;
- --color-background-error-reverse: #ef4444;
- --color-background-success-strong: #ccfbf1;
- --color-background-success: #f0fdfa;
- --color-background-success-reverse-strong: #0d9488;
- --color-background-success-reverse: #14b8a6;
- --color-background-warning-strong: #ffedd5;
- --color-background-warning: #fff7ed;
- --color-background-warning-reverse-strong: #ea580c;
- --color-background-warning-reverse: #f97316;
- --color-background-blue-strong: #dbeafe;
- --color-background-blue: #eff6ff;
- --color-background-cyan-strong: #cffafe;
- --color-background-cyan: #ecfeff;
- --color-background-fuchsia-strong: #fae8ff;
- --color-background-fuchsia: #fdf4ff;
- --color-background-lime-strong: #ecfccb;
- --color-background-lime: #f7fee7;
- --color-background-pink-strong: #fce7f3;
- --color-background-pink: #fdf2f8;
- --color-background-purple-strong: #f3e8ff;
- --color-background-purple: #faf5ff;
- --color-background-sky-strong: #e0f2fe;
- --color-background-sky: #f0f9ff;
- --color-background-violet-strong: #ede9fe;
- --color-background-violet: #f5f3ff;
- --color-background-yellow-strong: #fef9c3;
- --color-background-yellow: #fefce8;
-
- /* Foreground Colors */
- --color-foreground-neutral: #111827;
- --color-foreground-neutral-weak: #6b7280;
- --color-foreground-neutral-weaker: #9ca3af;
- --color-foreground-neutral-reverse: #ffffff;
- --color-foreground-neutral-reverse-weak: #9ca3af;
- --color-foreground-ppg-strong: #0f766e;
- --color-foreground-ppg: #0d9488;
- --color-foreground-ppg-weak: #14b8a6;
- --color-foreground-ppg-reverse: #ffffff;
- --color-foreground-ppg-reverse-weak: #99f6e4;
- --color-foreground-orm-strong: #4338ca;
- --color-foreground-orm: #4f46e5;
- --color-foreground-orm-weak: #6366f1;
- --color-foreground-orm-reverse: #ffffff;
- --color-foreground-orm-reverse-weak: #c7d2fe;
- --color-foreground-error-strong: #b91c1c;
- --color-foreground-error: #dc2626;
- --color-foreground-error-weak: #ef4444;
- --color-foreground-error-reverse: #ffffff;
- --color-foreground-error-reverse-weak: #fecaca;
- --color-foreground-success-strong: #0f766e;
- --color-foreground-success: #0d9488;
- --color-foreground-success-weak: #14b8a6;
- --color-foreground-success-reverse: #ffffff;
- --color-foreground-success-reverse-weak: #99f6e4;
- --color-foreground-warning-strong: #c2410c;
- --color-foreground-warning: #ea580c;
- --color-foreground-warning-weak: #f97316;
- --color-foreground-warning-reverse: #ffffff;
- --color-foreground-warning-reverse-weak: #fed7aa;
- --color-foreground-blue-strong: #1d4ed8;
- --color-foreground-blue: #2563eb;
- --color-foreground-blue-weak: #3b82f6;
- --color-foreground-cyan-strong: #0e7490;
- --color-foreground-cyan: #0891b2;
- --color-foreground-cyan-weak: #06b6d4;
- --color-foreground-fuchsia-strong: #a21caf;
- --color-foreground-fuchsia: #c026d3;
- --color-foreground-fuchsia-weak: #d946ef;
- --color-foreground-lime-strong: #4d7c0f;
- --color-foreground-lime: #65a30d;
- --color-foreground-lime-weak: #84cc16;
- --color-foreground-pink-strong: #be185d;
- --color-foreground-pink: #db2777;
- --color-foreground-pink-weak: #ec4899;
- --color-foreground-purple-strong: #7e22ce;
- --color-foreground-purple: #9333ea;
- --color-foreground-purple-weak: #a855f7;
- --color-foreground-sky-strong: #0369a1;
- --color-foreground-sky: #0284c7;
- --color-foreground-sky-weak: #0ea5e9;
- --color-foreground-violet-strong: #6d28d9;
- --color-foreground-violet: #7c3aed;
- --color-foreground-violet-weak: #8b5cf6;
- --color-foreground-yellow-strong: #a16207;
- --color-foreground-yellow: #ca8a04;
- --color-foreground-yellow-weak: #eab308;
-
- /* Stroke Colors */
- --color-stroke-neutral-stronger: #111827;
- --color-stroke-neutral-strong: #d1d5db;
- --color-stroke-neutral: #e5e7eb;
- --color-stroke-neutral-weak: #f3f4f6;
- --color-stroke-ppg: #0d9488;
- --color-stroke-orm: #4f46e5;
- --color-stroke-error: #dc2626;
- --color-stroke-success: #0d9488;
- --color-stroke-warning: #ea580c;
- --color-stroke-blue: #2563eb;
- --color-stroke-cyan: #0891b2;
- --color-stroke-fuchsia: #c026d3;
- --color-stroke-lime: #65a30d;
- --color-stroke-pink: #db2777;
- --color-stroke-purple: #9333ea;
- --color-stroke-sky: #0284c7;
- --color-stroke-violet: #7c3aed;
- --color-stroke-yellow: #ca8a04;
-
- /* Chart Colors */
- --chart-1: var(--color-foreground-pink);
- --chart-2: var(--color-foreground-fuchsia);
- --chart-3: var(--color-foreground-lime);
- --chart-4: var(--color-foreground-sky);
- --chart-5: var(--color-foreground-purple);
- --chart-6: var(--color-foreground-neutral-weak);
+ /* Blur */
+ --blur-surface-low: 1rem;
+ --blur-surface: 1.5rem;
+ --blur-surface-high: 2.5rem;
+
+ /* Box Shadow */
+ --shadow-box-low: 0 0.0625rem 0.125rem 0 rgba(0, 0, 0, 0.04);
+ --shadow-box:
+ 0 0.5rem 0.5rem -0.5rem rgba(0, 0, 0, 0.04),
+ 0 0.125rem 0.125rem 0 rgba(0, 0, 0, 0.04);
+ --shadow-box-high:
+ 0 0.5rem 1rem -0.25rem rgba(0, 0, 0, 0.04),
+ 0 0.125rem 0.125rem 0 rgba(0, 0, 0, 0.04);
+
+ /* Radius */
+ --radius-circle: 999px;
+ --radius-square-low: 0.1875rem;
+ --radius-square: 0.375rem;
+ --radius-square-high: 0.75rem;
+
+ /* Spacing - Margin */
+ --spacing-margin-4xs: 0.25rem;
+ --spacing-margin-3xs: 0.5rem;
+ --spacing-margin-2xs: 0.75rem;
+ --spacing-margin-xs: 1rem;
+ --spacing-margin-sm: 1.25rem;
+ --spacing-margin-md: 1.5rem;
+ --spacing-margin-lg: 1.75rem;
+ --spacing-margin-xl: 2rem;
+ --spacing-margin-4xl: 3rem;
+
+ /* Spacing - Element Sizing */
+ --spacing-element-2xs: 0.75rem;
+ --spacing-element-xs: 1rem;
+ --spacing-element-sm: 1.25rem;
+ --spacing-element-md: 1.5rem;
+ --spacing-element-lg: 1.75rem;
+ --spacing-element-xl: 2rem;
+ --spacing-element-2xl: 2.25rem;
+ --spacing-element-3xl: 2.5rem;
+ --spacing-element-4xl: 3rem;
+ --spacing-element-5xl: 4rem;
+ --spacing-element-6xl: 6rem;
+
+ /* Typography - Font Families */
+ --font-sans-display:
+ "Mona Sans VF", "Inter", "Roboto", "Helvetica Neue", "Arial Nova",
+ "Nimbus Sans", "Arial", sans-serif;
+ --font-sans-display-settings: "ss01" on, "ss02" on, "ss05" on, "ss06" on;
+ --font-sans:
+ "Inter", "Roboto", "Helvetica Neue", "Arial Nova", "Nimbus Sans",
+ "Arial", sans-serif;
+ --font-sans-settings:
+ "cv01" on, "cv02" on, "cv06" on, "cv07" on, "cv08" on, "cv10" on;
+ --font-mono:
+ "Mona Sans Mono VF", ui-monospace, "Cascadia Code", "Source Code Pro",
+ "Menlo", "Consolas", "DejaVu Sans Mono", monospace;
+ --font-mono--settings: "ss01" on, "ss02" on, "ss05" on, "ss06" on;
+
+ /* Typography - Font Sizes with paired Line Heights */
+ --text-2xs: 0.6875rem;
+ --text-2xs--line-height: 1rem;
+ --text-xs: 0.75rem;
+ --text-xs--line-height: 1rem;
+ --text-sm: 0.875rem;
+ --text-sm--line-height: 1.25rem;
+ --text-md: 1rem;
+ --text-md--line-height: 1.5rem;
+ --text-lg: 1.125rem;
+ --text-lg--line-height: 1.75rem;
+ --text-xl: 1.25rem;
+ --text-xl--line-height: 1.75rem;
+ --text-2xl: 1.5rem;
+ --text-2xl--line-height: 2rem;
+ --text-3xl: 1.875rem;
+ --text-3xl--line-height: 2.25rem;
+ --text-4xl: 2.5rem;
+ --text-4xl--line-height: 3rem;
+ --text-5xl: 4rem;
+ --text-5xl--line-height: 4.5rem;
+
+ /* Background Colors */
+ --color-disabled: #4a5568;
+ --color-background-default: #ffffff;
+ --color-background-default-050: rgb(255 255 255 / 0.5);
+ --color-background-default-075: rgb(255 255 255 / 0.75);
+ --color-background-neutral-strong: #e5e7eb;
+ --color-background-neutral: #f3f4f6;
+ --color-background-neutral-weak: #f9fafb;
+ --color-background-neutral-weaker: #fcfdfd;
+ --color-background-neutral-reverse-strong: #111827;
+ --color-background-neutral-reverse: #1f2937;
+ --color-background-ppg-strong: #ccfbf1;
+ --color-background-ppg: #f0fdfa;
+ --color-background-ppg-reverse-strong: #0d9488;
+ --color-background-ppg-reverse: #14b8a6;
+ --color-background-orm-strong: #e0e7ff;
+ --color-background-orm: #eef2ff;
+ --color-background-orm-reverse-strong: #4f46e5;
+ --color-background-orm-reverse: #6366f1;
+ --color-background-error-strong: #fee2e2;
+ --color-background-error: #fef2f2;
+ --color-background-error-reverse-strong: #dc2626;
+ --color-background-error-reverse: #ef4444;
+ --color-background-success-strong: #ccfbf1;
+ --color-background-success: #f0fdfa;
+ --color-background-success-reverse-strong: #0d9488;
+ --color-background-success-reverse: #14b8a6;
+ --color-background-warning-strong: #ffedd5;
+ --color-background-warning: #fff7ed;
+ --color-background-warning-reverse-strong: #ea580c;
+ --color-background-warning-reverse: #f97316;
+ --color-background-blue-strong: #dbeafe;
+ --color-background-blue: #eff6ff;
+ --color-background-cyan-strong: #cffafe;
+ --color-background-cyan: #ecfeff;
+ --color-background-fuchsia-strong: #fae8ff;
+ --color-background-fuchsia: #fdf4ff;
+ --color-background-lime-strong: #ecfccb;
+ --color-background-lime: #f7fee7;
+ --color-background-pink-strong: #fce7f3;
+ --color-background-pink: #fdf2f8;
+ --color-background-purple-strong: #f3e8ff;
+ --color-background-purple: #faf5ff;
+ --color-background-sky-strong: #e0f2fe;
+ --color-background-sky: #f0f9ff;
+ --color-background-violet-strong: #ede9fe;
+ --color-background-violet: #f5f3ff;
+ --color-background-yellow-strong: #fef9c3;
+ --color-background-yellow: #fefce8;
+
+ /* Foreground Colors */
+ --color-foreground-neutral: #111827;
+ --color-foreground-neutral-weak: #6b7280;
+ --color-foreground-neutral-weaker: #9ca3af;
+ --color-foreground-neutral-reverse: #ffffff;
+ --color-foreground-neutral-reverse-weak: #9ca3af;
+ --color-foreground-ppg-strong: #0f766e;
+ --color-foreground-ppg: #0d9488;
+ --color-foreground-ppg-weak: #14b8a6;
+ --color-foreground-ppg-reverse: #ffffff;
+ --color-foreground-ppg-reverse-weak: #99f6e4;
+ --color-foreground-orm-strong: #4338ca;
+ --color-foreground-orm: #4f46e5;
+ --color-foreground-orm-weak: #6366f1;
+ --color-foreground-orm-reverse: #ffffff;
+ --color-foreground-orm-reverse-weak: #c7d2fe;
+ --color-foreground-error-strong: #b91c1c;
+ --color-foreground-error: #dc2626;
+ --color-foreground-error-weak: #ef4444;
+ --color-foreground-error-reverse: #ffffff;
+ --color-foreground-error-reverse-weak: #fecaca;
+ --color-foreground-success-strong: #0f766e;
+ --color-foreground-success: #0d9488;
+ --color-foreground-success-weak: #14b8a6;
+ --color-foreground-success-reverse: #ffffff;
+ --color-foreground-success-reverse-weak: #99f6e4;
+ --color-foreground-warning-strong: #c2410c;
+ --color-foreground-warning: #ea580c;
+ --color-foreground-warning-weak: #f97316;
+ --color-foreground-warning-reverse: #ffffff;
+ --color-foreground-warning-reverse-weak: #fed7aa;
+ --color-foreground-blue-strong: #1d4ed8;
+ --color-foreground-blue: #2563eb;
+ --color-foreground-blue-weak: #3b82f6;
+ --color-foreground-cyan-strong: #0e7490;
+ --color-foreground-cyan: #0891b2;
+ --color-foreground-cyan-weak: #06b6d4;
+ --color-foreground-fuchsia-strong: #a21caf;
+ --color-foreground-fuchsia: #c026d3;
+ --color-foreground-fuchsia-weak: #d946ef;
+ --color-foreground-lime-strong: #4d7c0f;
+ --color-foreground-lime: #65a30d;
+ --color-foreground-lime-weak: #84cc16;
+ --color-foreground-pink-strong: #be185d;
+ --color-foreground-pink: #db2777;
+ --color-foreground-pink-weak: #ec4899;
+ --color-foreground-purple-strong: #7e22ce;
+ --color-foreground-purple: #9333ea;
+ --color-foreground-purple-weak: #a855f7;
+ --color-foreground-sky-strong: #0369a1;
+ --color-foreground-sky: #0284c7;
+ --color-foreground-sky-weak: #0ea5e9;
+ --color-foreground-violet-strong: #6d28d9;
+ --color-foreground-violet: #7c3aed;
+ --color-foreground-violet-weak: #8b5cf6;
+ --color-foreground-yellow-strong: #a16207;
+ --color-foreground-yellow: #ca8a04;
+ --color-foreground-yellow-weak: #eab308;
+
+ /* Stroke Colors */
+ --color-stroke-neutral-stronger: #111827;
+ --color-stroke-neutral-strong: #d1d5db;
+ --color-stroke-neutral: #e5e7eb;
+ --color-stroke-neutral-weak: #f3f4f6;
+ --color-stroke-ppg: #0d9488;
+ --color-stroke-ppg-weak: #99f6e4;
+ --color-stroke-orm: #4f46e5;
+ --color-stroke-error: #dc2626;
+ --color-stroke-success: #0d9488;
+ --color-stroke-warning: #ea580c;
+ --color-stroke-blue: #2563eb;
+ --color-stroke-cyan: #0891b2;
+ --color-stroke-fuchsia: #c026d3;
+ --color-stroke-lime: #65a30d;
+ --color-stroke-pink: #db2777;
+ --color-stroke-purple: #9333ea;
+ --color-stroke-sky: #0284c7;
+ --color-stroke-violet: #7c3aed;
+ --color-stroke-yellow: #ca8a04;
+
+ /* Chart Colors */
+ --chart-1: var(--color-foreground-pink);
+ --chart-2: var(--color-foreground-fuchsia);
+ --chart-3: var(--color-foreground-lime);
+ --chart-4: var(--color-foreground-sky);
+ --chart-5: var(--color-foreground-purple);
+ --chart-6: var(--color-foreground-neutral-weak);
}
.dark {
- /* Background Colors */
- --color-background-default: #030712;
- --color-background-default-050: rgb(10 14 20 / 0.5);
- --color-background-default-075: rgb(10 14 20 / 0.75);
- --color-background-neutral-strong: #374151;
- --color-background-neutral: #1f2937;
- --color-background-neutral-weak: #111827;
- --color-background-neutral-weaker: #0a101d;
- --color-background-neutral-reverse-strong: #f9fafb;
- --color-background-neutral-reverse: #f3f4f6;
- --color-background-ppg-strong: #134e4a;
- --color-background-ppg: #042f2e;
- --color-background-ppg-reverse-strong: #2dd4bf;
- --color-background-ppg-reverse: #14b8a6;
- --color-background-orm-strong: #312e81;
- --color-background-orm: #1e1b4b;
- --color-background-orm-reverse-strong: #818cf8;
- --color-background-orm-reverse: #6366f1;
- --color-background-error-strong: #7f1d1d;
- --color-background-error: #450a0a;
- --color-background-error-reverse-strong: #f87171;
- --color-background-error-reverse: #ef4444;
- --color-background-success-strong: #134e4a;
- --color-background-success: #042f2e;
- --color-background-success-reverse-strong: #2dd4bf;
- --color-background-success-reverse: #14b8a6;
- --color-background-warning-strong: #7c2d12;
- --color-background-warning: #431407;
- --color-background-warning-reverse-strong: #fb923c;
- --color-background-warning-reverse: #f97316;
- --color-background-blue-strong: #1e3a8a;
- --color-background-blue: #172554;
- --color-background-cyan-strong: #164e63;
- --color-background-cyan: #083344;
- --color-background-fuchsia-strong: #701a75;
- --color-background-fuchsia: #4a044e;
- --color-background-lime-strong: #365314;
- --color-background-lime: #1a2e05;
- --color-background-pink-strong: #831843;
- --color-background-pink: #500724;
- --color-background-purple-strong: #581c87;
- --color-background-purple: #3b0764;
- --color-background-sky-strong: #0c4a6e;
- --color-background-sky: #082f49;
- --color-background-violet-strong: #4c1d95;
- --color-background-violet: #2e1065;
- --color-background-yellow-strong: #713f12;
- --color-background-yellow: #422006;
-
- /* Foreground Colors */
- --color-foreground-neutral: #f9fafb;
- --color-foreground-neutral-weak: #9ca3af;
- --color-foreground-neutral-weaker: #6b7280;
- --color-foreground-neutral-reverse: #030712;
- --color-foreground-neutral-reverse-weak: #4b5563;
- --color-foreground-ppg-strong: #2dd4bf;
- --color-foreground-ppg: #14b8a6;
- --color-foreground-ppg-weak: #0d9488;
- --color-foreground-ppg-reverse: #f9fafb;
- --color-foreground-ppg-reverse-weak: #99f6e4;
- --color-foreground-orm-strong: #818cf8;
- --color-foreground-orm: #6366f1;
- --color-foreground-orm-weak: #4f46e5;
- --color-foreground-orm-reverse: #f9fafb;
- --color-foreground-orm-reverse-weak: #c7d2fe;
- --color-foreground-error-strong: #f87171;
- --color-foreground-error: #ef4444;
- --color-foreground-error-weak: #dc2626;
- --color-foreground-error-reverse: #f9fafb;
- --color-foreground-error-reverse-weak: #fecaca;
- --color-foreground-success-strong: #2dd4bf;
- --color-foreground-success: #14b8a6;
- --color-foreground-success-weak: #0d9488;
- --color-foreground-success-reverse: #f9fafb;
- --color-foreground-success-reverse-weak: #99f6e4;
- --color-foreground-warning-strong: #fb923c;
- --color-foreground-warning: #f97316;
- --color-foreground-warning-weak: #ea580c;
- --color-foreground-warning-reverse: #f9fafb;
- --color-foreground-warning-reverse-weak: #fed7aa;
- --color-foreground-blue-strong: #93c5fd;
- --color-foreground-blue: #60a5fa;
- --color-foreground-blue-weak: #3b82f6;
- --color-foreground-cyan-strong: #22d3ee;
- --color-foreground-cyan: #06b6d4;
- --color-foreground-cyan-weak: #0891b2;
- --color-foreground-fuchsia-strong: #e879f9;
- --color-foreground-fuchsia: #d946ef;
- --color-foreground-fuchsia-weak: #c026d3;
- --color-foreground-lime-strong: #a3e635;
- --color-foreground-lime: #84cc16;
- --color-foreground-lime-weak: #65a30d;
- --color-foreground-pink-strong: #f472b6;
- --color-foreground-pink: #ec4899;
- --color-foreground-pink-weak: #db2777;
- --color-foreground-purple-strong: #c084fc;
- --color-foreground-purple: #a855f7;
- --color-foreground-purple-weak: #9333ea;
- --color-foreground-sky-strong: #38bdf8;
- --color-foreground-sky: #0ea5e9;
- --color-foreground-sky-weak: #0284c7;
- --color-foreground-violet-strong: #a78bfa;
- --color-foreground-violet: #8b5cf6;
- --color-foreground-violet-weak: #7c3aed;
- --color-foreground-yellow-strong: #facc15;
- --color-foreground-yellow: #eab308;
- --color-foreground-yellow-weak: #ca8a04;
-
- /* Stroke Colors */
- --color-stroke-neutral-stronger: #f9fafb;
- --color-stroke-neutral-strong: #374151;
- --color-stroke-neutral: #1f2937;
- --color-stroke-neutral-weak: #111827;
- --color-stroke-ppg: #2dd4bf;
- --color-stroke-orm: #818cf8;
- --color-stroke-error: #f87171;
- --color-stroke-success: #2dd4bf;
- --color-stroke-warning: #fb923c;
- --color-stroke-blue: #60a5fa;
- --color-stroke-cyan: #22d3ee;
- --color-stroke-fuchsia: #e879f9;
- --color-stroke-lime: #a3e635;
- --color-stroke-pink: #f472b6;
- --color-stroke-purple: #c084fc;
- --color-stroke-sky: #38bdf8;
- --color-stroke-violet: #a78bfa;
- --color-stroke-yellow: #facc15;
+ /* Background Colors */
+ --color-disabled: #4a5568;
+ --color-background-default: #030712;
+ --color-background-default-050: rgb(10 14 20 / 0.5);
+ --color-background-default-075: rgb(10 14 20 / 0.75);
+ --color-background-neutral-strong: #374151;
+ --color-background-neutral: #1f2937;
+ --color-background-neutral-weak: #111827;
+ --color-background-neutral-weaker: #0a101d;
+ --color-background-neutral-reverse-strong: #f9fafb;
+ --color-background-neutral-reverse: #f3f4f6;
+ --color-background-ppg-strong: #134e4a;
+ --color-background-ppg: #042f2e;
+ --color-background-ppg-reverse-strong: #2dd4bf;
+ --color-background-ppg-reverse: #14b8a6;
+ --color-background-orm-strong: #312e81;
+ --color-background-orm: #1e1b4b;
+ --color-background-orm-reverse-strong: #818cf8;
+ --color-background-orm-reverse: #6366f1;
+ --color-background-error-strong: #7f1d1d;
+ --color-background-error: #450a0a;
+ --color-background-error-reverse-strong: #f87171;
+ --color-background-error-reverse: #ef4444;
+ --color-background-success-strong: #134e4a;
+ --color-background-success: #042f2e;
+ --color-background-success-reverse-strong: #2dd4bf;
+ --color-background-success-reverse: #14b8a6;
+ --color-background-warning-strong: #7c2d12;
+ --color-background-warning: #431407;
+ --color-background-warning-reverse-strong: #fb923c;
+ --color-background-warning-reverse: #f97316;
+ --color-background-blue-strong: #1e3a8a;
+ --color-background-blue: #172554;
+ --color-background-cyan-strong: #164e63;
+ --color-background-cyan: #083344;
+ --color-background-fuchsia-strong: #701a75;
+ --color-background-fuchsia: #4a044e;
+ --color-background-lime-strong: #365314;
+ --color-background-lime: #1a2e05;
+ --color-background-pink-strong: #831843;
+ --color-background-pink: #500724;
+ --color-background-purple-strong: #581c87;
+ --color-background-purple: #3b0764;
+ --color-background-sky-strong: #0c4a6e;
+ --color-background-sky: #082f49;
+ --color-background-violet-strong: #4c1d95;
+ --color-background-violet: #2e1065;
+ --color-background-yellow-strong: #713f12;
+ --color-background-yellow: #422006;
+
+ /* Foreground Colors */
+ --color-foreground-neutral: #f9fafb;
+ --color-foreground-neutral-weak: #9ca3af;
+ --color-foreground-neutral-weaker: #6b7280;
+ --color-foreground-neutral-reverse: #030712;
+ --color-foreground-neutral-reverse-weak: #4b5563;
+ --color-foreground-ppg-strong: #2dd4bf;
+ --color-foreground-ppg: #14b8a6;
+ --color-foreground-ppg-weak: #0d9488;
+ --color-foreground-ppg-reverse: #f9fafb;
+ --color-foreground-ppg-reverse-weak: #99f6e4;
+ --color-foreground-orm-strong: #818cf8;
+ --color-foreground-orm: #6366f1;
+ --color-foreground-orm-weak: #4f46e5;
+ --color-foreground-orm-reverse: #f9fafb;
+ --color-foreground-orm-reverse-weak: #c7d2fe;
+ --color-foreground-error-strong: #f87171;
+ --color-foreground-error: #ef4444;
+ --color-foreground-error-weak: #dc2626;
+ --color-foreground-error-reverse: #f9fafb;
+ --color-foreground-error-reverse-weak: #fecaca;
+ --color-foreground-success-strong: #2dd4bf;
+ --color-foreground-success: #14b8a6;
+ --color-foreground-success-weak: #0d9488;
+ --color-foreground-success-reverse: #f9fafb;
+ --color-foreground-success-reverse-weak: #99f6e4;
+ --color-foreground-warning-strong: #fb923c;
+ --color-foreground-warning: #f97316;
+ --color-foreground-warning-weak: #ea580c;
+ --color-foreground-warning-reverse: #f9fafb;
+ --color-foreground-warning-reverse-weak: #fed7aa;
+ --color-foreground-blue-strong: #93c5fd;
+ --color-foreground-blue: #60a5fa;
+ --color-foreground-blue-weak: #3b82f6;
+ --color-foreground-cyan-strong: #22d3ee;
+ --color-foreground-cyan: #06b6d4;
+ --color-foreground-cyan-weak: #0891b2;
+ --color-foreground-fuchsia-strong: #e879f9;
+ --color-foreground-fuchsia: #d946ef;
+ --color-foreground-fuchsia-weak: #c026d3;
+ --color-foreground-lime-strong: #a3e635;
+ --color-foreground-lime: #84cc16;
+ --color-foreground-lime-weak: #65a30d;
+ --color-foreground-pink-strong: #f472b6;
+ --color-foreground-pink: #ec4899;
+ --color-foreground-pink-weak: #db2777;
+ --color-foreground-purple-strong: #c084fc;
+ --color-foreground-purple: #a855f7;
+ --color-foreground-purple-weak: #9333ea;
+ --color-foreground-sky-strong: #38bdf8;
+ --color-foreground-sky: #0ea5e9;
+ --color-foreground-sky-weak: #0284c7;
+ --color-foreground-violet-strong: #a78bfa;
+ --color-foreground-violet: #8b5cf6;
+ --color-foreground-violet-weak: #7c3aed;
+ --color-foreground-yellow-strong: #facc15;
+ --color-foreground-yellow: #eab308;
+ --color-foreground-yellow-weak: #ca8a04;
+
+ /* Stroke Colors */
+ --color-stroke-neutral-stronger: #f9fafb;
+ --color-stroke-neutral-strong: #374151;
+ --color-stroke-neutral: #1f2937;
+ --color-stroke-neutral-weak: #111827;
+ --color-stroke-ppg: #2dd4bf;
+ --color-stroke-ppg-weak: #115e59;
+ --color-stroke-orm: #818cf8;
+ --color-stroke-error: #f87171;
+ --color-stroke-success: #2dd4bf;
+ --color-stroke-warning: #fb923c;
+ --color-stroke-blue: #60a5fa;
+ --color-stroke-cyan: #22d3ee;
+ --color-stroke-fuchsia: #e879f9;
+ --color-stroke-lime: #a3e635;
+ --color-stroke-pink: #f472b6;
+ --color-stroke-purple: #c084fc;
+ --color-stroke-sky: #38bdf8;
+ --color-stroke-violet: #a78bfa;
+ --color-stroke-yellow: #facc15;
}
@layer utilities {
-
- /* Gradients */
- .bg-gradient-orm {
- background: linear-gradient(to right, #7c7ff1, #a5a8f5, #9ea2f3, #c6c9f9, #d8dcf5, #d8dcf5, #eff1fd);
- }
-
- .bg-gradient-ppg {
- background: linear-gradient(to right, #2bc9b4, #4dd4c1, #67ddd0, #8ae5dc, #b8f1e9, #c7f1ec, #e8fcf9);
- }
-
- .bg-gradient-ppg-orm {
- background: linear-gradient(90deg, var(--color-background-ppg) 0%, var(--color-background-orm) 100%);
- }
-
- /* Typography - Titles */
- .type-title-5xl,
- .type-title-4xl,
- .type-title-3xl,
- .type-title-xl,
- .type-title-lg,
- .type-title-md,
- .type-title-sm {
- font: normal 900 var(--text-2xl)/var(--text-2xl--line-height) var(--font-sans-display);
- font-feature-settings: var(--font-sans-display-settings);
- font-variant-numeric: lining-nums tabular-nums slashed-zero;
- font-stretch: 125%;
- font-width: 125%;
- }
-
- .type-title-5xl {
- font-size: var(--text-5xl);
- line-height: var(--text-5xl--line-height);
- }
-
- .type-title-4xl {
- font-size: var(--text-4xl);
- line-height: var(--text-4xl--line-height);
- }
-
- .type-title-3xl {
- font-size: var(--text-3xl);
- line-height: var(--text-3xl--line-height);
- }
-
- .type-title-xl,
- .type-title-lg,
- .type-title-md {
- font-weight: 800;
- font-stretch: 110%;
- font-width: 110%;
- }
-
- .type-title-xl {
- font-size: var(--text-xl);
- line-height: var(--text-xl--line-height);
- }
-
- .type-title-lg {
- font-size: var(--text-lg);
- line-height: var(--text-lg--line-height);
- }
-
- .type-title-md {
- font-size: var(--text-md);
- line-height: var(--text-md--line-height);
- }
-
- .type-title-sm {
- font-size: var(--text-sm);
- font-weight: 800;
- line-height: var(--text-sm--line-height);
- letter-spacing: 0.06875rem;
- text-transform: uppercase;
- }
-
- /* Typography - Headings */
- .type-heading-2xl,
- .type-heading-xl,
- .type-heading-lg,
- .type-heading-md,
- .type-heading-sm,
- .type-heading-xs {
- font: normal 700 var(--text-2xl)/var(--text-2xl--line-height) var(--font-sans-display);
- font-feature-settings: var(--font-sans-display-settings);
- font-variant-numeric: lining-nums tabular-nums slashed-zero;
- }
-
- .type-heading-xl {
- font-size: var(--text-xl);
- line-height: var(--text-xl--line-height);
- }
-
- .type-heading-lg {
- font-size: var(--text-lg);
- line-height: var(--text-lg--line-height);
- }
-
- .type-heading-md {
- font-size: var(--text-lg);
- font-weight: 650;
- line-height: var(--text-lg--line-height);
- }
-
- .type-heading-sm {
- font-size: var(--text-sm);
- font-weight: 650;
- line-height: var(--text-sm--line-height);
- }
-
- .type-heading-xs {
- font-size: var(--text-xs);
- font-weight: 650;
- line-height: var(--text-xs--line-height);
- }
-
- .type-heading-2xs {
- font: normal 600 var(--text-2xs)/var(--text-2xs--line-height) var(--font-sans);
- font-feature-settings: var(--font-sans-settings);
- font-variant-numeric: lining-nums tabular-nums slashed-zero;
- letter-spacing: 0.06875rem;
- text-transform: uppercase;
- }
-
- /* Typography - Text */
- .type-text-md,
- .type-text-md-strong,
- .type-text-md-stronger,
- .type-text-sm,
- .type-text-sm-strong,
- .type-text-sm-stronger,
- .type-text-xs,
- .type-text-xs-strong,
- .type-text-xs-stronger {
- font: normal 400 var(--text-md)/var(--text-md--line-height) var(--font-sans);
- font-feature-settings: var(--font-sans-settings);
- font-variant-numeric: lining-nums proportional-nums slashed-zero;
- }
-
- .type-text-sm,
- .type-text-sm-strong,
- .type-text-sm-stronger {
- font-size: var(--text-sm);
- line-height: var(--text-sm--line-height);
- }
-
- .type-text-xs,
- .type-text-xs-strong,
- .type-text-xs-stronger {
- font-size: var(--text-xs);
- line-height: var(--text-xs--line-height);
- }
-
- .type-text-md-strong,
- .type-text-sm-strong,
- .type-text-xs-strong {
- font-weight: 500;
- }
-
- .type-text-md-stronger,
- .type-text-sm-stronger,
- .type-text-xs-stronger {
- font-weight: 600;
- }
-
- /* Typography - Code */
- .type-code-md,
- .type-code-md-strong,
- .type-code-sm,
- .type-code-sm-strong,
- .type-code-xs,
- .type-code-xs-strong {
- font: normal 400 var(--text-md)/var(--text-md--line-height) var(--font-mono);
- font-feature-settings: var(--font-mono--settings);
- }
-
- .type-code-sm,
- .type-code-sm-strong {
- font-size: var(--text-sm);
- line-height: var(--text-sm--line-height);
- }
-
- .type-code-xs,
- .type-code-xs-strong {
- font-size: var(--text-xs);
- line-height: var(--text-xs--line-height);
- }
-
- .type-code-md-strong,
- .type-code-sm-strong,
- .type-code-xs-strong {
- font-weight: 500;
- }
-
- /* File input button styles */
- input[type="file"]:valid {
- @apply text-foreground-neutral;
- }
-
- input[type="file"]:invalid {
- @apply text-foreground-error;
- }
-
- input[type="file"] {
- @apply font-normal cursor-pointer text-foreground-neutral-weak disabled:text-foreground-neutral-weaker p-0 border-0;
- }
-
- input[type="file"]::file-selector-button {
- @apply font-medium cursor-pointer rounded-square bg-background-neutral py-1 px-2 disabled:bg-background-neutral-weak text-foreground-neutral disabled:text-foreground-neutral-weaker;
- }
-}
\ No newline at end of file
+ /* Gradients */
+ .bg-gradient-orm {
+ background: linear-gradient(
+ to right,
+ #7c7ff1,
+ #a5a8f5,
+ #9ea2f3,
+ #c6c9f9,
+ #d8dcf5,
+ #d8dcf5,
+ #eff1fd
+ );
+ }
+
+ .bg-gradient-ppg {
+ background: linear-gradient(
+ to right,
+ #2bc9b4,
+ #4dd4c1,
+ #67ddd0,
+ #8ae5dc,
+ #b8f1e9,
+ #c7f1ec,
+ #e8fcf9
+ );
+ }
+
+ .bg-gradient-ppg-orm {
+ background: linear-gradient(
+ 90deg,
+ var(--color-background-ppg) 0%,
+ var(--color-background-orm) 100%
+ );
+ }
+
+ /* Typography - Titles */
+ .type-title-5xl,
+ .type-title-4xl,
+ .type-title-3xl,
+ .type-title-xl,
+ .type-title-lg,
+ .type-title-md,
+ .type-title-sm {
+ font: normal 900 var(--text-2xl)/var(--text-2xl--line-height)
+ var(--font-sans-display);
+ font-feature-settings: var(--font-sans-display-settings);
+ font-variant-numeric: lining-nums tabular-nums slashed-zero;
+ font-stretch: 125%;
+ font-width: 125%;
+ }
+
+ .type-title-5xl {
+ font-size: var(--text-5xl);
+ line-height: var(--text-5xl--line-height);
+ }
+
+ .type-title-4xl {
+ font-size: var(--text-4xl);
+ line-height: var(--text-4xl--line-height);
+ }
+
+ .type-title-3xl {
+ font-size: var(--text-3xl);
+ line-height: var(--text-3xl--line-height);
+ }
+
+ .type-title-xl,
+ .type-title-lg,
+ .type-title-md {
+ font-weight: 800;
+ font-stretch: 110%;
+ font-width: 110%;
+ }
+
+ .type-title-xl {
+ font-size: var(--text-xl);
+ line-height: var(--text-xl--line-height);
+ }
+
+ .type-title-lg {
+ font-size: var(--text-lg);
+ line-height: var(--text-lg--line-height);
+ }
+
+ .type-title-md {
+ font-size: var(--text-md);
+ line-height: var(--text-md--line-height);
+ }
+
+ .type-title-sm {
+ font-size: var(--text-sm);
+ font-weight: 800;
+ line-height: var(--text-sm--line-height);
+ letter-spacing: 0.06875rem;
+ text-transform: uppercase;
+ }
+
+ /* Typography - Headings */
+ .type-heading-2xl,
+ .type-heading-xl,
+ .type-heading-lg,
+ .type-heading-md,
+ .type-heading-sm,
+ .type-heading-xs {
+ font: normal 700 var(--text-2xl)/var(--text-2xl--line-height)
+ var(--font-sans-display);
+ font-feature-settings: var(--font-sans-display-settings);
+ font-variant-numeric: lining-nums tabular-nums slashed-zero;
+ }
+
+ .type-heading-xl {
+ font-size: var(--text-xl);
+ line-height: var(--text-xl--line-height);
+ }
+
+ .type-heading-lg {
+ font-size: var(--text-lg);
+ line-height: var(--text-lg--line-height);
+ }
+
+ .type-heading-md {
+ font-size: var(--text-lg);
+ font-weight: 650;
+ line-height: var(--text-lg--line-height);
+ }
+
+ .type-heading-sm {
+ font-size: var(--text-sm);
+ font-weight: 650;
+ line-height: var(--text-sm--line-height);
+ }
+
+ .type-heading-xs {
+ font-size: var(--text-xs);
+ font-weight: 650;
+ line-height: var(--text-xs--line-height);
+ }
+
+ .type-heading-2xs {
+ font: normal 600 var(--text-2xs)/var(--text-2xs--line-height)
+ var(--font-sans);
+ font-feature-settings: var(--font-sans-settings);
+ font-variant-numeric: lining-nums tabular-nums slashed-zero;
+ letter-spacing: 0.06875rem;
+ text-transform: uppercase;
+ }
+
+ /* Typography - Text */
+ .type-text-md,
+ .type-text-md-strong,
+ .type-text-md-stronger,
+ .type-text-sm,
+ .type-text-sm-strong,
+ .type-text-sm-stronger,
+ .type-text-xs,
+ .type-text-xs-strong,
+ .type-text-xs-stronger {
+ font: normal 400 var(--text-md)/var(--text-md--line-height)
+ var(--font-sans);
+ font-feature-settings: var(--font-sans-settings);
+ font-variant-numeric: lining-nums proportional-nums slashed-zero;
+ }
+
+ .type-text-sm,
+ .type-text-sm-strong,
+ .type-text-sm-stronger {
+ font-size: var(--text-sm);
+ line-height: var(--text-sm--line-height);
+ }
+
+ .type-text-xs,
+ .type-text-xs-strong,
+ .type-text-xs-stronger {
+ font-size: var(--text-xs);
+ line-height: var(--text-xs--line-height);
+ }
+
+ .type-text-md-strong,
+ .type-text-sm-strong,
+ .type-text-xs-strong {
+ font-weight: 500;
+ }
+
+ .type-text-md-stronger,
+ .type-text-sm-stronger,
+ .type-text-xs-stronger {
+ font-weight: 600;
+ }
+
+ /* Typography - Code */
+ .type-code-md,
+ .type-code-md-strong,
+ .type-code-sm,
+ .type-code-sm-strong,
+ .type-code-xs,
+ .type-code-xs-strong {
+ font: normal 400 var(--text-md)/var(--text-md--line-height)
+ var(--font-mono);
+ font-feature-settings: var(--font-mono--settings);
+ }
+
+ .type-code-sm,
+ .type-code-sm-strong {
+ font-size: var(--text-sm);
+ line-height: var(--text-sm--line-height);
+ }
+
+ .type-code-xs,
+ .type-code-xs-strong {
+ font-size: var(--text-xs);
+ line-height: var(--text-xs--line-height);
+ }
+
+ .type-code-md-strong,
+ .type-code-sm-strong,
+ .type-code-xs-strong {
+ font-weight: 500;
+ }
+
+ /* File input button styles */
+ input[type="file"]:valid {
+ @apply text-foreground-neutral;
+ }
+
+ input[type="file"]:invalid {
+ @apply text-foreground-error;
+ }
+
+ input[type="file"] {
+ @apply font-normal cursor-pointer text-foreground-neutral-weak disabled:text-foreground-neutral-weaker p-0 border-0;
+ }
+
+ input[type="file"]::file-selector-button {
+ @apply font-medium cursor-pointer rounded-square bg-background-neutral py-1 px-2 disabled:bg-background-neutral-weak text-foreground-neutral disabled:text-foreground-neutral-weaker;
+ }
+}
diff --git a/packages/ui/src/components/author-avatar-group.tsx b/packages/ui/src/components/author-avatar-group.tsx
new file mode 100644
index 0000000000..80ca29f0ac
--- /dev/null
+++ b/packages/ui/src/components/author-avatar-group.tsx
@@ -0,0 +1,46 @@
+import { Avatar } from "@prisma/eclipse";
+
+export type AuthorProfile = {
+ name: string;
+ imageSrc?: string | null;
+};
+
+type AuthorAvatarGroupProps = {
+ authors: AuthorProfile[];
+ className?: string;
+};
+
+export function AuthorAvatarGroup({
+ authors = [],
+ className,
+}: AuthorAvatarGroupProps) {
+ if (authors.length === 0) {
+ return null;
+ }
+
+ return (
+
+
+ {authors.map((author, index) =>
+ author.imageSrc ? (
+ 0 ? "-ml-1.5 border border-background-default" : ""
+ }
+ />
+ ) : null,
+ )}
+
+ {authors.map((author) => author.name).join(", ")}
+
+ );
+}
diff --git a/packages/ui/src/components/post-card.tsx b/packages/ui/src/components/post-card.tsx
new file mode 100644
index 0000000000..1c2eeb3153
--- /dev/null
+++ b/packages/ui/src/components/post-card.tsx
@@ -0,0 +1,124 @@
+"use client";
+
+import { Badge, Card } from "@prisma/eclipse";
+import { cn } from "../lib/cn";
+import { AuthorAvatarGroup, type AuthorProfile } from "./author-avatar-group";
+
+export type PostCardItem = {
+ url: string;
+ title: string;
+ date: string;
+ excerpt?: string | null;
+ author?: AuthorProfile | null;
+ imageSrc?: string | null;
+ imageAlt?: string | null;
+ badge?: string | null;
+};
+
+export function PostCard({
+ post,
+ featured = false,
+ vertical = false,
+}: {
+ post: PostCardItem;
+ featured?: boolean;
+ vertical?: boolean;
+}) {
+ const isFeatured = featured;
+ const imageSizes = isFeatured ? "(min-width: 640px) 50vw, 100vw" : "384px";
+
+ const containerClassName = cn(
+ "group grid overflow-hidden",
+ isFeatured
+ ? "grid-cols-1 md:grid-cols-2 gap-4 bg-background-default rounded-square border border-stroke-neutral shadow-box-low"
+ : "sm:grid-cols-[1fr_384px] border-b pb-4 sm:pb-6 border-stroke-neutral gap-8",
+ vertical ? "sm:grid-cols-1 grid-rows-2" : "",
+ );
+ const imageClassName = cn(
+ "object-cover transition-transform duration-300 group-hover:scale-[1.02]",
+ !isFeatured && "rounded-square",
+ );
+ const imageWrapperClassName = cn(
+ "relative aspect-video w-full h-full",
+ isFeatured ? "order-1" : "order-2 max-w-96 hidden sm:block",
+ vertical && "order-none! h-52 cover",
+ );
+
+ const titleClassName = cn(
+ "text-foreground-neutral font-mona-sans mt-4 mb-2",
+ isFeatured
+ ? "text-2xl font-bold"
+ : "text-md md:text-lg font-[650] sm:font-bold",
+ );
+ const excerptClassName = cn(
+ "text-sm text-foreground-neutral-weak line-clamp-2",
+ isFeatured && "leading-[20px]!",
+ );
+ const authorClassName = cn(
+ "items-center gap-2 font-semibold text-sm mt-4 md:mt-0",
+ isFeatured ? "flex" : "hidden sm:flex",
+ );
+
+ const postBody = (
+ <>
+
+
+ {post.badge && (
+
+ )}
+ {post.date && (
+
+ {post.date}
+
+ )}
+
+ {post.title &&
{post.title} }
+ {post.excerpt &&
{post.excerpt}
}
+
+ {post.author && (
+
+ )}
+ >
+ );
+
+ return (
+
+ {post.imageSrc && (
+
+
+
+ )}
+ {isFeatured ? (
+
+ {postBody}
+
+ ) : (
+
+ {postBody}
+
+ )}
+
+ );
+}
diff --git a/packages/ui/src/components/quote.tsx b/packages/ui/src/components/quote.tsx
new file mode 100644
index 0000000000..e2c84009b1
--- /dev/null
+++ b/packages/ui/src/components/quote.tsx
@@ -0,0 +1,52 @@
+import { Avatar, Separator } from "@prisma/eclipse";
+import { cn } from "../lib/cn";
+export const Quote = ({
+ children,
+ author,
+ className,
+}: {
+ children: React.ReactNode;
+ author?: { name: string; imageUrl: string; title?: string; company?: string };
+ className?: string;
+}) => {
+ return (
+
+ {children}
+ {author && (
+
+ )}
+
+ );
+};
diff --git a/packages/ui/src/styles/globals.css b/packages/ui/src/styles/globals.css
index 8c48df7f22..882da3a042 100644
--- a/packages/ui/src/styles/globals.css
+++ b/packages/ui/src/styles/globals.css
@@ -130,7 +130,6 @@
}
.stretch-display {
font-variation-settings:
- "wght" 700,
+ "wght" 900,
"wdth" 125;
}
-
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f11ecc6306..7cf7c58d65 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -591,6 +591,9 @@ importers:
cors:
specifier: ^2.8.6
version: 2.8.6
+ html-react-parser:
+ specifier: ^5.2.17
+ version: 5.2.17(@types/react@19.2.14)(react@19.2.4)
lucide-react:
specifier: 'catalog:'
version: 0.575.0(react@19.2.4)
@@ -615,6 +618,12 @@ importers:
remark-directive:
specifier: 'catalog:'
version: 4.0.0
+ shiki:
+ specifier: 3.22.0
+ version: 3.22.0
+ tailwind-merge:
+ specifier: 'catalog:'
+ version: 3.5.0
zod:
specifier: 'catalog:'
version: 4.3.6
@@ -4089,10 +4098,23 @@ packages:
dexie@4.3.0:
resolution: {integrity: sha512-5EeoQpJvMKHe6zWt/FSIIuRa3CWlZeIl6zKXt+Lz7BU6RoRRLgX9dZEynRfXrkLcldKYCBiz7xekTEylnie1Ug==}
+ dom-serializer@2.0.0:
+ resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
+
+ domelementtype@2.3.0:
+ resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
+
+ domhandler@5.0.3:
+ resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
+ engines: {node: '>= 4'}
+
dompurify@3.3.2:
resolution: {integrity: sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==}
engines: {node: '>=20'}
+ domutils@3.2.2:
+ resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
+
dotenv@16.6.1:
resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
engines: {node: '>=12'}
@@ -4117,10 +4139,18 @@ packages:
resolution: {integrity: sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==}
engines: {node: '>=10.13.0'}
+ entities@4.5.0:
+ resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
+ engines: {node: '>=0.12'}
+
entities@6.0.1:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'}
+ entities@7.0.1:
+ resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
+ engines: {node: '>=0.12'}
+
env-paths@4.0.0:
resolution: {integrity: sha512-pxP8eL2SwwaTRi/KHYwLYXinDs7gL3jxFcBYmEdYfZmZXbaVDvdppd0XBU8qVz03rDfKZMXg1omHCbsJjZrMsw==}
engines: {node: '>=20'}
@@ -4484,12 +4514,27 @@ packages:
hookable@6.0.1:
resolution: {integrity: sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==}
+ html-dom-parser@5.1.8:
+ resolution: {integrity: sha512-MCIUng//mF2qTtGHXJWr6OLfHWmg3Pm8ezpfiltF83tizPWY17JxT4dRLE8lykJ5bChJELoY3onQKPbufJHxYA==}
+
+ html-react-parser@5.2.17:
+ resolution: {integrity: sha512-m+K/7Moq1jodAB4VL0RXSOmtwLUYoAsikZhwd+hGQe5Vtw2dbWfpFd60poxojMU0Tsh9w59mN1QLEcoHz0Dx9w==}
+ peerDependencies:
+ '@types/react': 0.14 || 15 || 16 || 17 || 18 || 19
+ react: 0.14 || 15 || 16 || 17 || 18 || 19
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
html-url-attributes@3.0.1:
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
html-void-elements@3.0.0:
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
+ htmlparser2@10.1.0:
+ resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==}
+
https-proxy-agent@5.0.1:
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
engines: {node: '>= 6'}
@@ -5241,6 +5286,9 @@ packages:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-property@2.0.2:
+ resolution: {integrity: sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==}
+
react-redux@9.2.0:
resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==}
peerDependencies:
@@ -8887,10 +8935,28 @@ snapshots:
dexie@4.3.0: {}
+ dom-serializer@2.0.0:
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+ entities: 4.5.0
+
+ domelementtype@2.3.0: {}
+
+ domhandler@5.0.3:
+ dependencies:
+ domelementtype: 2.3.0
+
dompurify@3.3.2:
optionalDependencies:
'@types/trusted-types': 2.0.7
+ domutils@3.2.2:
+ dependencies:
+ dom-serializer: 2.0.0
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+
dotenv@16.6.1: {}
dts-resolver@2.1.3(oxc-resolver@11.19.1):
@@ -8906,8 +8972,12 @@ snapshots:
graceful-fs: 4.2.11
tapable: 2.3.0
+ entities@4.5.0: {}
+
entities@6.0.1: {}
+ entities@7.0.1: {}
+
env-paths@4.0.0:
dependencies:
is-safe-filename: 0.1.1
@@ -9361,10 +9431,32 @@ snapshots:
hookable@6.0.1: {}
+ html-dom-parser@5.1.8:
+ dependencies:
+ domhandler: 5.0.3
+ htmlparser2: 10.1.0
+
+ html-react-parser@5.2.17(@types/react@19.2.14)(react@19.2.4):
+ dependencies:
+ domhandler: 5.0.3
+ html-dom-parser: 5.1.8
+ react: 19.2.4
+ react-property: 2.0.2
+ style-to-js: 1.1.21
+ optionalDependencies:
+ '@types/react': 19.2.14
+
html-url-attributes@3.0.1: {}
html-void-elements@3.0.0: {}
+ htmlparser2@10.1.0:
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+ domutils: 3.2.2
+ entities: 7.0.1
+
https-proxy-agent@5.0.1:
dependencies:
agent-base: 6.0.2
@@ -10395,6 +10487,8 @@ snapshots:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
+ react-property@2.0.2: {}
+
react-redux@9.2.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1):
dependencies:
'@types/use-sync-external-store': 0.0.6