diff --git a/apps/site/content/changelog/2024-12-19.mdx b/apps/site/content/changelog/2024-12-19.mdx index 6be7f17802c..0b31d840271 100644 --- a/apps/site/content/changelog/2024-12-19.mdx +++ b/apps/site/content/changelog/2024-12-19.mdx @@ -1,6 +1,7 @@ --- title: "Prisma ORM v6.1.0: tracing is now stable" date: "2024-12-19" +category: "orm" version: "v6.1.0" slug: "2024-12-19" headline: "Prisma ORM v6.1.0: tracing is now stable" diff --git a/apps/site/content/changelog/2025-01-09.mdx b/apps/site/content/changelog/2025-01-09.mdx index 9dd75d66fc1..50caa20d1e4 100644 --- a/apps/site/content/changelog/2025-01-09.mdx +++ b/apps/site/content/changelog/2025-01-09.mdx @@ -1,6 +1,7 @@ --- title: "Prisma ORM v6.2.0: omit API is now GA" date: "2025-01-09" +category: "orm" version: "v6.2.0" slug: "2025-01-09" headline: "Prisma ORM v6.2.0: omit API is now GA" diff --git a/apps/site/content/changelog/2025-01-30.mdx b/apps/site/content/changelog/2025-01-30.mdx index 2b020fb4250..c804cfff4b5 100644 --- a/apps/site/content/changelog/2025-01-30.mdx +++ b/apps/site/content/changelog/2025-01-30.mdx @@ -1,6 +1,7 @@ --- title: "Prisma ORM v6.3.0: Prisma Studio returns to Console" date: "2025-01-30" +category: "orm" version: "v6.3.0" slug: "2025-01-30" headline: "Prisma ORM v6.3.0: Prisma Studio returns to Console" diff --git a/apps/site/content/changelog/2025-02-20.mdx b/apps/site/content/changelog/2025-02-20.mdx index 99cd97908fe..a5afa6cbba8 100644 --- a/apps/site/content/changelog/2025-02-20.mdx +++ b/apps/site/content/changelog/2025-02-20.mdx @@ -1,6 +1,7 @@ --- title: "Prisma ORM v6.4.0: Prisma Postgres is GA" date: "2025-02-20" +category: "orm" version: "v6.4.0" slug: "2025-02-20" headline: "Prisma ORM v6.4.0: Prisma Postgres is GA" diff --git a/apps/site/content/changelog/2026-06-11.mdx b/apps/site/content/changelog/2026-06-11.mdx index c07cfe2258f..5485ba431c2 100644 --- a/apps/site/content/changelog/2026-06-11.mdx +++ b/apps/site/content/changelog/2026-06-11.mdx @@ -1,6 +1,7 @@ --- title: "Deploy TypeScript apps next to Prisma Postgres: Prisma Compute is now in Public Beta" date: "2026-06-11" +highlight: true version: "2026-06-11" slug: "2026-06-11" headline: "Deploy TypeScript apps next to Prisma Postgres: Prisma Compute is now in Public Beta" diff --git a/apps/site/content/changelog/2026-07-03.mdx b/apps/site/content/changelog/2026-07-03.mdx index ce133047c24..b53eec6cb93 100644 --- a/apps/site/content/changelog/2026-07-03.mdx +++ b/apps/site/content/changelog/2026-07-03.mdx @@ -1,6 +1,7 @@ --- title: "Set up Prisma 8 with new guides and debug failed Prisma Compute builds" date: "2026-07-03" +category: "compute" version: "2026-07-03" slug: "2026-07-03" headline: "Set up Prisma 8 with new guides and debug failed Prisma Compute builds" diff --git a/apps/site/content/changelog/2026-07-24.mdx b/apps/site/content/changelog/2026-07-24.mdx index 44d0b9d0d79..7a7c97b3116 100644 --- a/apps/site/content/changelog/2026-07-24.mdx +++ b/apps/site/content/changelog/2026-07-24.mdx @@ -1,6 +1,8 @@ --- title: "Store files with Object Store buckets and see your workspace at a glance" date: "2026-07-24" +category: "postgres" +highlight: true version: "2026-07-24" slug: "2026-07-24" headline: "Store files with Object Store buckets and see your workspace at a glance" diff --git a/apps/site/content/changelog/2026-08-28.mdx b/apps/site/content/changelog/2026-08-28.mdx index b06da5b2a56..0b83ec5d52d 100644 --- a/apps/site/content/changelog/2026-08-28.mdx +++ b/apps/site/content/changelog/2026-08-28.mdx @@ -1,6 +1,7 @@ --- title: "Prisma Compute is now generally available" date: "2026-08-28" +highlight: true version: "2026-08-28" slug: "2026-08-28" headline: "Prisma Compute is now generally available" diff --git a/apps/site/public/brand/robot-nod.png b/apps/site/public/brand/robot-nod.png new file mode 100644 index 00000000000..4002e993c7f Binary files /dev/null and b/apps/site/public/brand/robot-nod.png differ diff --git a/apps/site/public/brand/robot-nod.webp b/apps/site/public/brand/robot-nod.webp new file mode 100644 index 00000000000..38aea6c43bc Binary files /dev/null and b/apps/site/public/brand/robot-nod.webp differ diff --git a/apps/site/public/customers/stories/amplication.png b/apps/site/public/customers/stories/amplication.png deleted file mode 100644 index 6303acbb86c..00000000000 Binary files a/apps/site/public/customers/stories/amplication.png and /dev/null differ diff --git a/apps/site/public/customers/stories/bucket.png b/apps/site/public/customers/stories/bucket.png deleted file mode 100644 index da77b1c09f9..00000000000 Binary files a/apps/site/public/customers/stories/bucket.png and /dev/null differ diff --git a/apps/site/public/customers/stories/elsevierstory.svg b/apps/site/public/customers/stories/elsevierstory.svg deleted file mode 100644 index fe396ff0a2d..00000000000 --- a/apps/site/public/customers/stories/elsevierstory.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/site/public/customers/stories/formbricks.svg b/apps/site/public/customers/stories/formbricks.svg deleted file mode 100644 index bb00eaf4bd3..00000000000 --- a/apps/site/public/customers/stories/formbricks.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/site/public/customers/stories/grover.png b/apps/site/public/customers/stories/grover.png deleted file mode 100644 index 8cb2b0f1b50..00000000000 Binary files a/apps/site/public/customers/stories/grover.png and /dev/null differ diff --git a/apps/site/public/customers/stories/invisible.png b/apps/site/public/customers/stories/invisible.png deleted file mode 100644 index cd45fe65cd9..00000000000 Binary files a/apps/site/public/customers/stories/invisible.png and /dev/null differ diff --git a/apps/site/public/customers/stories/iopool.png b/apps/site/public/customers/stories/iopool.png deleted file mode 100644 index b1fceec474f..00000000000 Binary files a/apps/site/public/customers/stories/iopool.png and /dev/null differ diff --git a/apps/site/public/customers/stories/panther.png b/apps/site/public/customers/stories/panther.png deleted file mode 100644 index 92d3d781fa1..00000000000 Binary files a/apps/site/public/customers/stories/panther.png and /dev/null differ diff --git a/apps/site/public/customers/stories/pearly.png b/apps/site/public/customers/stories/pearly.png deleted file mode 100644 index 67fe5cb037d..00000000000 Binary files a/apps/site/public/customers/stories/pearly.png and /dev/null differ diff --git a/apps/site/public/customers/stories/poppy.png b/apps/site/public/customers/stories/poppy.png deleted file mode 100644 index 0e7a4c93563..00000000000 Binary files a/apps/site/public/customers/stories/poppy.png and /dev/null differ diff --git a/apps/site/public/customers/stories/rapha.png b/apps/site/public/customers/stories/rapha.png deleted file mode 100644 index d59162a53af..00000000000 Binary files a/apps/site/public/customers/stories/rapha.png and /dev/null differ diff --git a/apps/site/public/customers/stories/solin.svg b/apps/site/public/customers/stories/solin.svg deleted file mode 100644 index 1dd80e852c9..00000000000 --- a/apps/site/public/customers/stories/solin.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/site/public/customers/stories/tryg.png b/apps/site/public/customers/stories/tryg.png deleted file mode 100644 index cf254c8523c..00000000000 Binary files a/apps/site/public/customers/stories/tryg.png and /dev/null differ diff --git a/apps/site/public/logos/customers/agvolution.svg b/apps/site/public/logos/customers/agvolution.svg new file mode 100644 index 00000000000..3c38511c849 --- /dev/null +++ b/apps/site/public/logos/customers/agvolution.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/amplication-white.png b/apps/site/public/logos/customers/amplication-white.png new file mode 100644 index 00000000000..119ea4c4f56 Binary files /dev/null and b/apps/site/public/logos/customers/amplication-white.png differ diff --git a/apps/site/public/logos/customers/antstack.svg b/apps/site/public/logos/customers/antstack.svg new file mode 100644 index 00000000000..4368f426060 --- /dev/null +++ b/apps/site/public/logos/customers/antstack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/apideck.svg b/apps/site/public/logos/customers/apideck.svg new file mode 100644 index 00000000000..8ede46ca0e2 --- /dev/null +++ b/apps/site/public/logos/customers/apideck.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/avenue.svg b/apps/site/public/logos/customers/avenue.svg new file mode 100644 index 00000000000..ebb8c487a62 --- /dev/null +++ b/apps/site/public/logos/customers/avenue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/cal.svg b/apps/site/public/logos/customers/cal.svg new file mode 100644 index 00000000000..5f9e0512846 --- /dev/null +++ b/apps/site/public/logos/customers/cal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/cargon.svg b/apps/site/public/logos/customers/cargon.svg new file mode 100644 index 00000000000..41fd2d267dd --- /dev/null +++ b/apps/site/public/logos/customers/cargon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/caribou.png b/apps/site/public/logos/customers/caribou.png new file mode 100644 index 00000000000..6204617ffbb Binary files /dev/null and b/apps/site/public/logos/customers/caribou.png differ diff --git a/apps/site/public/logos/customers/coinrotator.png b/apps/site/public/logos/customers/coinrotator.png new file mode 100644 index 00000000000..2827e045059 Binary files /dev/null and b/apps/site/public/logos/customers/coinrotator.png differ diff --git a/apps/site/public/logos/customers/digitalspeed.svg b/apps/site/public/logos/customers/digitalspeed.svg new file mode 100644 index 00000000000..4162b616a0e --- /dev/null +++ b/apps/site/public/logos/customers/digitalspeed.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/dotworld.webp b/apps/site/public/logos/customers/dotworld.webp new file mode 100644 index 00000000000..07d478e850f Binary files /dev/null and b/apps/site/public/logos/customers/dotworld.webp differ diff --git a/apps/site/public/logos/customers/elsevier.svg b/apps/site/public/logos/customers/elsevier.svg new file mode 100644 index 00000000000..39518e78297 --- /dev/null +++ b/apps/site/public/logos/customers/elsevier.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/escape.svg b/apps/site/public/logos/customers/escape.svg new file mode 100644 index 00000000000..b9850c5a73a --- /dev/null +++ b/apps/site/public/logos/customers/escape.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/everify.svg b/apps/site/public/logos/customers/everify.svg new file mode 100644 index 00000000000..db35ec88e4f --- /dev/null +++ b/apps/site/public/logos/customers/everify.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/expandbymonroe.svg b/apps/site/public/logos/customers/expandbymonroe.svg new file mode 100644 index 00000000000..2c4136078e7 --- /dev/null +++ b/apps/site/public/logos/customers/expandbymonroe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/feather.svg b/apps/site/public/logos/customers/feather.svg new file mode 100644 index 00000000000..e5389c9de3d --- /dev/null +++ b/apps/site/public/logos/customers/feather.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/flux.svg b/apps/site/public/logos/customers/flux.svg new file mode 100644 index 00000000000..9f5a724d13e --- /dev/null +++ b/apps/site/public/logos/customers/flux.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/flycode.svg b/apps/site/public/logos/customers/flycode.svg new file mode 100644 index 00000000000..fc5a929f38f --- /dev/null +++ b/apps/site/public/logos/customers/flycode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/formbricks-white.png b/apps/site/public/logos/customers/formbricks-white.png new file mode 100644 index 00000000000..fa294abcbaa Binary files /dev/null and b/apps/site/public/logos/customers/formbricks-white.png differ diff --git a/apps/site/public/logos/customers/gamma.svg b/apps/site/public/logos/customers/gamma.svg new file mode 100644 index 00000000000..afe8dbd5a59 --- /dev/null +++ b/apps/site/public/logos/customers/gamma.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/garages-near-me.svg b/apps/site/public/logos/customers/garages-near-me.svg new file mode 100644 index 00000000000..2265d0784b6 --- /dev/null +++ b/apps/site/public/logos/customers/garages-near-me.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/grover.svg b/apps/site/public/logos/customers/grover.svg new file mode 100644 index 00000000000..b063ea26365 --- /dev/null +++ b/apps/site/public/logos/customers/grover.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/ihi.svg b/apps/site/public/logos/customers/ihi.svg new file mode 100644 index 00000000000..b233cae9774 --- /dev/null +++ b/apps/site/public/logos/customers/ihi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/insta.svg b/apps/site/public/logos/customers/insta.svg new file mode 100644 index 00000000000..26c6b95d57a --- /dev/null +++ b/apps/site/public/logos/customers/insta.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/instatus.png b/apps/site/public/logos/customers/instatus.png new file mode 100644 index 00000000000..0441d6e4787 Binary files /dev/null and b/apps/site/public/logos/customers/instatus.png differ diff --git a/apps/site/public/logos/customers/invisible.svg b/apps/site/public/logos/customers/invisible.svg new file mode 100644 index 00000000000..654507f4f5c --- /dev/null +++ b/apps/site/public/logos/customers/invisible.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/iopool-white.png b/apps/site/public/logos/customers/iopool-white.png new file mode 100644 index 00000000000..9fb84bcc294 Binary files /dev/null and b/apps/site/public/logos/customers/iopool-white.png differ diff --git a/apps/site/public/logos/customers/jelly.png b/apps/site/public/logos/customers/jelly.png new file mode 100644 index 00000000000..f96a95a8352 Binary files /dev/null and b/apps/site/public/logos/customers/jelly.png differ diff --git a/apps/site/public/logos/customers/krisenchat.svg b/apps/site/public/logos/customers/krisenchat.svg new file mode 100644 index 00000000000..ebaee385f1a --- /dev/null +++ b/apps/site/public/logos/customers/krisenchat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/memberstack.svg b/apps/site/public/logos/customers/memberstack.svg new file mode 100644 index 00000000000..d3d0f52179c --- /dev/null +++ b/apps/site/public/logos/customers/memberstack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/motionbox.svg b/apps/site/public/logos/customers/motionbox.svg new file mode 100644 index 00000000000..cf0e9c4e6e3 --- /dev/null +++ b/apps/site/public/logos/customers/motionbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/nachonacho.svg b/apps/site/public/logos/customers/nachonacho.svg new file mode 100644 index 00000000000..9b064bef58c --- /dev/null +++ b/apps/site/public/logos/customers/nachonacho.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/nuna.png b/apps/site/public/logos/customers/nuna.png new file mode 100644 index 00000000000..8441077fc6f Binary files /dev/null and b/apps/site/public/logos/customers/nuna.png differ diff --git a/apps/site/public/logos/customers/outrider.svg b/apps/site/public/logos/customers/outrider.svg new file mode 100644 index 00000000000..6c30ee1c58e --- /dev/null +++ b/apps/site/public/logos/customers/outrider.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/oxio.svg b/apps/site/public/logos/customers/oxio.svg new file mode 100644 index 00000000000..0056897ab98 --- /dev/null +++ b/apps/site/public/logos/customers/oxio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/panther.svg b/apps/site/public/logos/customers/panther.svg new file mode 100644 index 00000000000..d7b3f768588 --- /dev/null +++ b/apps/site/public/logos/customers/panther.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/pearly-white.webp b/apps/site/public/logos/customers/pearly-white.webp new file mode 100644 index 00000000000..ec4a73f669f Binary files /dev/null and b/apps/site/public/logos/customers/pearly-white.webp differ diff --git a/apps/site/public/logos/customers/pmt.svg b/apps/site/public/logos/customers/pmt.svg new file mode 100644 index 00000000000..a11bd02f3d8 --- /dev/null +++ b/apps/site/public/logos/customers/pmt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/poppy-white.png b/apps/site/public/logos/customers/poppy-white.png new file mode 100644 index 00000000000..b041e4cbd35 Binary files /dev/null and b/apps/site/public/logos/customers/poppy-white.png differ diff --git a/apps/site/public/logos/customers/poppy.svg b/apps/site/public/logos/customers/poppy.svg new file mode 100644 index 00000000000..edc91674a53 --- /dev/null +++ b/apps/site/public/logos/customers/poppy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/prevalentware.png b/apps/site/public/logos/customers/prevalentware.png new file mode 100644 index 00000000000..6d6ab0b9943 Binary files /dev/null and b/apps/site/public/logos/customers/prevalentware.png differ diff --git a/apps/site/public/logos/customers/prosperity.svg b/apps/site/public/logos/customers/prosperity.svg new file mode 100644 index 00000000000..0fb25a7d5cd --- /dev/null +++ b/apps/site/public/logos/customers/prosperity.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/questmate.png b/apps/site/public/logos/customers/questmate.png new file mode 100644 index 00000000000..e3c824f2552 Binary files /dev/null and b/apps/site/public/logos/customers/questmate.png differ diff --git a/apps/site/public/logos/customers/rapha.svg b/apps/site/public/logos/customers/rapha.svg new file mode 100644 index 00000000000..f97a013d9ed --- /dev/null +++ b/apps/site/public/logos/customers/rapha.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/reflag-white.png b/apps/site/public/logos/customers/reflag-white.png new file mode 100644 index 00000000000..84fd1bbd32c Binary files /dev/null and b/apps/site/public/logos/customers/reflag-white.png differ diff --git a/apps/site/public/logos/customers/revere.svg b/apps/site/public/logos/customers/revere.svg new file mode 100644 index 00000000000..544ced03213 --- /dev/null +++ b/apps/site/public/logos/customers/revere.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/solin-white.png b/apps/site/public/logos/customers/solin-white.png new file mode 100644 index 00000000000..e0982bd8acb Binary files /dev/null and b/apps/site/public/logos/customers/solin-white.png differ diff --git a/apps/site/public/logos/customers/southpole.svg b/apps/site/public/logos/customers/southpole.svg new file mode 100644 index 00000000000..59659d85247 --- /dev/null +++ b/apps/site/public/logos/customers/southpole.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/stellate.svg b/apps/site/public/logos/customers/stellate.svg new file mode 100644 index 00000000000..5b5ebf1181a --- /dev/null +++ b/apps/site/public/logos/customers/stellate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/stonegiantstudio.svg b/apps/site/public/logos/customers/stonegiantstudio.svg new file mode 100644 index 00000000000..21dd5ea2b3c --- /dev/null +++ b/apps/site/public/logos/customers/stonegiantstudio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/sunhat.svg b/apps/site/public/logos/customers/sunhat.svg new file mode 100644 index 00000000000..6f4004fb2df --- /dev/null +++ b/apps/site/public/logos/customers/sunhat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/superblog.png b/apps/site/public/logos/customers/superblog.png new file mode 100644 index 00000000000..c1d3eac0ba6 Binary files /dev/null and b/apps/site/public/logos/customers/superblog.png differ diff --git a/apps/site/public/logos/customers/tabya.svg b/apps/site/public/logos/customers/tabya.svg new file mode 100644 index 00000000000..23897afecf1 --- /dev/null +++ b/apps/site/public/logos/customers/tabya.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/timenavi.svg b/apps/site/public/logos/customers/timenavi.svg new file mode 100644 index 00000000000..82b88228b8a --- /dev/null +++ b/apps/site/public/logos/customers/timenavi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/trunk.svg b/apps/site/public/logos/customers/trunk.svg new file mode 100644 index 00000000000..d8e0b129ea4 --- /dev/null +++ b/apps/site/public/logos/customers/trunk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/tryg-white.svg b/apps/site/public/logos/customers/tryg-white.svg new file mode 100644 index 00000000000..d0ae8086d3a --- /dev/null +++ b/apps/site/public/logos/customers/tryg-white.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/site/public/logos/customers/tryg.svg b/apps/site/public/logos/customers/tryg.svg new file mode 100644 index 00000000000..5f7405c788d --- /dev/null +++ b/apps/site/public/logos/customers/tryg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/wasp.svg b/apps/site/public/logos/customers/wasp.svg new file mode 100644 index 00000000000..f46972df239 --- /dev/null +++ b/apps/site/public/logos/customers/wasp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/public/logos/customers/wefindflats.png b/apps/site/public/logos/customers/wefindflats.png new file mode 100644 index 00000000000..a3882fb2f5b Binary files /dev/null and b/apps/site/public/logos/customers/wefindflats.png differ diff --git a/apps/site/public/logos/customers/wingfield.svg b/apps/site/public/logos/customers/wingfield.svg new file mode 100644 index 00000000000..1874abacc19 --- /dev/null +++ b/apps/site/public/logos/customers/wingfield.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/site/src/app/changelog/page.tsx b/apps/site/src/app/changelog/page.tsx index d5979554a88..ca1be5fa80d 100644 --- a/apps/site/src/app/changelog/page.tsx +++ b/apps/site/src/app/changelog/page.tsx @@ -1,9 +1,8 @@ import { createPageMetadata } from "@/lib/page-metadata"; -import Link from "next/link"; -import { ArrowRight } from "@/components/icons/forma"; -import { RoleKicker } from "@/components/brand/role-kicker"; -import { Texture } from "@/components/brand/texture"; -import { extractPreview, formatChangelogDate, getChangelogEntries } from "@/lib/changelog"; +import { ChangelogFeed } from "@/components/sections/changelog-feed"; +import { ChangelogHero } from "@/components/sections/changelog-hero"; +import { CtaBurst } from "@/components/sections/cta-burst"; +import { getChangelogFeedEntries } from "@/lib/changelog"; export const metadata = createPageMetadata({ title: "Changelog", @@ -13,87 +12,25 @@ export const metadata = createPageMetadata({ ogKicker: "Changelog", }); -// Ported from the old site's /changelog (apps/site): same MDX entries, listed -// newest first as a single timeline. +// /changelog — the same MDX release notes as before (content/changelog, also +// served machine-readable at /changelog.md), given the brand front door +// (spectral hero), a product-area filter, and a colour-coded release timeline +// you can scan without reading. The category, tags and highlight flag per +// entry come from the entry's frontmatter via lib/changelog.ts. export default function ChangelogPage() { - const entries = getChangelogEntries(); + const entries = getChangelogFeedEntries(); return ( <> -
-
- -
-
- - Changelog - -

- What's new in Prisma -

-

- New features, improvements, and fixes across Prisma ORM, Prisma Postgres, and the - platform. -

-
-
-
-
- -
-
-
    - {entries.map((entry) => { - const preview = extractPreview(entry.content); - return ( -
  1. - - -

    - - {entry.frontmatter.headline ?? entry.frontmatter.title} - -

    - {preview && ( -

    - {preview} -

    - )} - {/* "Read more" on its own is a generic anchor: the visible - label stays, the hidden half names the entry it opens. */} - - Read more - - : {entry.frontmatter.headline ?? entry.frontmatter.title} - - - -
  2. - ); - })} -
-
-
+ + + ); } diff --git a/apps/site/src/app/customers/[slug]/page.tsx b/apps/site/src/app/customers/[slug]/page.tsx new file mode 100644 index 00000000000..8ed18510d4e --- /dev/null +++ b/apps/site/src/app/customers/[slug]/page.tsx @@ -0,0 +1,42 @@ +import type { Metadata } from "next"; +import { notFound } from "next/navigation"; +import { CustomerStory } from "@/components/sections/customer-story"; +import { CUSTOMER_STORY_DETAILS, getCustomerStory } from "@/data/customer-stories"; +import { createPageMetadata } from "@/lib/page-metadata"; + +type Props = { + params: Promise<{ slug: string }>; +}; + +export function generateStaticParams() { + return CUSTOMER_STORY_DETAILS.map((story) => ({ slug: story.slug })); +} + +export async function generateMetadata({ params }: Props): Promise { + const { slug } = await params; + const story = getCustomerStory(slug); + if (!story) return {}; + + const metadata = createPageMetadata({ + title: story.hero.title, + description: story.hero.lead, + path: `/customers/${slug}`, + ogKicker: "Customers", + }); + + return { + ...metadata, + openGraph: { ...metadata.openGraph, type: "article" }, + }; +} + +// Long-form customer stories, built from the approved copy (Notion: "Customer +// Story Detail Page", batches one and two). The index at /customers links here +// for every story that has an entry in data/customer-stories.ts. +export default async function CustomerStoryPage({ params }: Props) { + const { slug } = await params; + const story = getCustomerStory(slug); + if (!story) notFound(); + + return ; +} diff --git a/apps/site/src/app/customers/page.tsx b/apps/site/src/app/customers/page.tsx index 5a540188a2c..05474408a6c 100644 --- a/apps/site/src/app/customers/page.tsx +++ b/apps/site/src/app/customers/page.tsx @@ -2,25 +2,71 @@ import { createPageMetadata } from "@/lib/page-metadata"; import { CtaBurst } from "@/components/sections/cta-burst"; import { CustomersGrid } from "@/components/sections/customers-grid"; import { CustomersHero } from "@/components/sections/customers-hero"; -import { TestimonialsReveal } from "@/components/sections/testimonials-reveal"; +import { LogoCloud } from "@/components/sections/logo-cloud"; +import { COMMUNITY_LOGOS } from "@/data/customers"; export const metadata = createPageMetadata({ title: "Customers", description: - "See how teams use Prisma ORM, Prisma Postgres, and Prisma Compute in production to ship faster, scale reliably, and improve developer workflows.", + "Real teams, real production stories. See how developers use Prisma to build, deploy, and scale without wrestling their backend at every step.", path: "/customers", ogKicker: "Customers", }); -// Ported from the old site's /case-studies page (apps/site). The nav links -// here as "Customers"; story content lives in src/lib/customers.ts. +// /customers — the redesign of prisma.io/showcase, built from the approved +// copy (Notion: Customers Index — Copy and Design, V1) with the section order +// the copy gives, and the card/logo-band structure of the reference layout +// André supplied. Layout notes and the rulings behind them: +// .context/customers-index-layout.md. +// +// Four sections. The copy asks for logos twice, and the two moments are +// deliberately different jobs: the hero band is twelve recognisable marks +// answering "who", the marquee is all 44 community projects answering "how +// many". If one ever has to go, it's the band — the marquee is the one the copy +// gives an actual heading to. +// +// The closer is CtaBurst with a headline override and nothing else: /contact +// already ships this block with these exact three checks, so the approved copy +// needed no new component. export default function CustomersPage() { return ( <> - - + {/* 180s, not the default 45s. The track is 44 wide tiles (~8600px) against + the homepage's 21 square ones (~2400px), and the keyframe duration is + per-loop, so the default made this strip drift 3.5x faster than the + homepage's. 180s puts it at ~48px/s, a shade calmer than home. */} + + Trusted by 500K+ developers globally + + } + /> + ); } diff --git a/apps/site/src/app/globals.css b/apps/site/src/app/globals.css index cc91b60a008..c19da0d5eab 100644 --- a/apps/site/src/app/globals.css +++ b/apps/site/src/app/globals.css @@ -757,8 +757,14 @@ h6 { pointer-events: none; } +/* Direct parent for the button link variant, where the label span sits inside + the button itself. The `.group` form is for cards that wrap a link: there the + label is nested a level or two down, so the immediate-child selector would + only fire over the text column and not over the card's image. */ :hover > .spectrum-underline::after, -:focus-visible > .spectrum-underline::after { +:focus-visible > .spectrum-underline::after, +.group:hover .spectrum-underline::after, +.group:focus-visible .spectrum-underline::after { opacity: 1; animation: spectrum-slide 2.5s linear infinite; } @@ -766,7 +772,9 @@ h6 { @media (prefers-reduced-motion: reduce) { .spectrum-underline::after, :hover > .spectrum-underline::after, - :focus-visible > .spectrum-underline::after { + :focus-visible > .spectrum-underline::after, + .group:hover .spectrum-underline::after, + .group:focus-visible .spectrum-underline::after { animation: none; } } diff --git a/apps/site/src/app/stack/page.tsx b/apps/site/src/app/stack/page.tsx index 5a4f7c95abd..ddba31b9efd 100644 --- a/apps/site/src/app/stack/page.tsx +++ b/apps/site/src/app/stack/page.tsx @@ -1,43 +1,41 @@ import { createPageMetadata } from "@/lib/page-metadata"; +import { PlatformHero } from "@/components/sections/platform-hero"; +import { PlatformCompare } from "@/components/sections/platform-compare"; +import { PlatformFlow } from "@/components/sections/platform-flow"; +import { PlatformProducts } from "@/components/sections/platform-products"; +import { PlatformOpen } from "@/components/sections/platform-open"; +import { AgentLoop } from "@/components/sections/agent-loop"; +import { TestimonialsReveal } from "@/components/sections/testimonials-reveal"; import { CtaBurst } from "@/components/sections/cta-burst"; -import { StackBento } from "@/components/sections/stack-bento"; -import { RoleKicker } from "@/components/brand/role-kicker"; -import { Texture } from "@/components/brand/texture"; export const metadata = createPageMetadata({ title: "The Prisma Stack | ORM, Postgres, and Compute for TypeScript", + // The approved SEO copy (scripts/seo-page-metadata.test.ts), kept across the + // page redesign. description: "ORM, Postgres, and Compute, one platform for your app and its database, with one shared context across your stack.", path: "/stack", ogKicker: "The Prisma Stack", }); -// Interim /stack page: the header's "Explore the stack" link needs a real -// destination. Reuses the homepage's StackBento until CF designs the full -// stack page (the old site's /stack diagram stays on apps/site meanwhile). +// Narrative order, per the client's structure note: what it is → how it works → +// why it matters. Core value, then the system diagram, then the build/deploy/ +// debug path, then the products, then what you get and what stays replaceable, +// then proof and the ask. +// Section rhythm: the wrapped panels (hero, flow, closing CTA) break up the runs +// of sections that sit on the page's own white. export default function StackPage() { return ( <> -
-
- -
-
- - The Prisma stack - -

- One platform, from schema to production -

-

- ORM, Postgres, and Compute work together natively, one shared context across your - stack, one bill, and nothing to glue together. -

-
-
-
-
- + + + + + + + + {/* the homepage's closing CTA, unchanged — same copy as the copy deck's + closing block, same cloud loop, same prismatic burst on the buttons */} ); diff --git a/apps/site/src/app/use-cases/ai-agents/page.tsx b/apps/site/src/app/use-cases/ai-agents/page.tsx new file mode 100644 index 00000000000..dd020c05270 --- /dev/null +++ b/apps/site/src/app/use-cases/ai-agents/page.tsx @@ -0,0 +1,17 @@ +import { aiAgentsUseCase } from "@/components/use-case/ai-agents/content"; +import { AiAgentsUseCasePage } from "@/components/use-case/ai-agents/ai-agents-page"; +import { createPageMetadata } from "@/lib/page-metadata"; + +// The AI & Agents use case carries a different section set from the segment +// use cases (saas-teams, startups, engineering-teams), so it has its own +// composition rather than running through SegmentPage. +export const metadata = createPageMetadata({ + title: `Prisma for ${aiAgentsUseCase.meta.title}`, + description: aiAgentsUseCase.meta.description, + path: "/use-cases/ai-agents", + ogKicker: "Use cases", +}); + +export default function AiAgentsRoute() { + return ; +} diff --git a/apps/site/src/app/use-cases/engineering-teams/page.tsx b/apps/site/src/app/use-cases/engineering-teams/page.tsx new file mode 100644 index 00000000000..6ba3eae2aff --- /dev/null +++ b/apps/site/src/app/use-cases/engineering-teams/page.tsx @@ -0,0 +1,15 @@ +import { EngineeringIntroVisual } from "@/components/use-case/segment/abstractions/engineering-teams"; +import { engineeringTeamsUseCase } from "@/components/use-case/segment/content/engineering-teams"; +import { SegmentPage } from "@/components/use-case/segment/segment-page"; +import { createPageMetadata } from "@/lib/page-metadata"; + +export const metadata = createPageMetadata({ + title: `Prisma for ${engineeringTeamsUseCase.meta.title}`, + description: engineeringTeamsUseCase.meta.description, + path: "/use-cases/engineering-teams", + ogKicker: "Use cases", +}); + +export default function EngineeringTeamsRoute() { + return } />; +} diff --git a/apps/site/src/app/use-cases/saas-teams/page.tsx b/apps/site/src/app/use-cases/saas-teams/page.tsx new file mode 100644 index 00000000000..2234b05ad48 --- /dev/null +++ b/apps/site/src/app/use-cases/saas-teams/page.tsx @@ -0,0 +1,15 @@ +import { SaasIntroVisual } from "@/components/use-case/segment/abstractions/saas-teams"; +import { saasTeamsUseCase } from "@/components/use-case/segment/content/saas-teams"; +import { SegmentPage } from "@/components/use-case/segment/segment-page"; +import { createPageMetadata } from "@/lib/page-metadata"; + +export const metadata = createPageMetadata({ + title: `Prisma for ${saasTeamsUseCase.meta.title}`, + description: saasTeamsUseCase.meta.description, + path: "/use-cases/saas-teams", + ogKicker: "Use cases", +}); + +export default function SaasTeamsRoute() { + return } />; +} diff --git a/apps/site/src/app/use-cases/startups/page.tsx b/apps/site/src/app/use-cases/startups/page.tsx new file mode 100644 index 00000000000..19a3b5ca3b9 --- /dev/null +++ b/apps/site/src/app/use-cases/startups/page.tsx @@ -0,0 +1,15 @@ +import { StartupsIntroVisual } from "@/components/use-case/segment/abstractions/startups"; +import { startupsUseCase } from "@/components/use-case/segment/content/startups"; +import { SegmentPage } from "@/components/use-case/segment/segment-page"; +import { createPageMetadata } from "@/lib/page-metadata"; + +export const metadata = createPageMetadata({ + title: `Prisma for ${startupsUseCase.meta.title}`, + description: startupsUseCase.meta.description, + path: "/use-cases/startups", + ogKicker: "Use cases", +}); + +export default function StartupsRoute() { + return } />; +} diff --git a/apps/site/src/components/brand/agent-robot.tsx b/apps/site/src/components/brand/agent-robot.tsx index dc6aae0ed75..382681a719a 100644 --- a/apps/site/src/components/brand/agent-robot.tsx +++ b/apps/site/src/components/brand/agent-robot.tsx @@ -6,21 +6,40 @@ import { cn } from "@/lib/utils"; // reading as a character and starts reading as wallpaper. // // The animated WebP looks around; the static PNG stands in under reduced motion. -// The WebP is ~1.9 MB, so it must stay below the fold and lazily loaded. -export function AgentRobot({ className }: { className?: string }) { +// Both are lazily loaded and belong below the fold — `look` is ~1.9 MB. +// +// Source clips arrive on a white backdrop, not with an alpha channel. The +// character is white chrome, so a colour key eats its own highlights — the +// mattes here were cut per frame with macOS Vision's subject segmentation +// instead. Sized to what they actually render at rather than to the source. +const VARIANTS = { + /** Looks around. 324x298, 152 frames. */ + look: { animated: "/brand/robot-look.webp", still: "/brand/robot.png" }, + /** Nods, head dipping down. 288x265, 81 frames at 16fps. */ + nod: { animated: "/brand/robot-nod.webp", still: "/brand/robot-nod.png" }, +} as const; + +export function AgentRobot({ + className, + variant = "look", +}: { + className?: string; + variant?: keyof typeof VARIANTS; +}) { const shared = "drop-shadow-[0_10px_16px_rgba(21,21,21,0.18)]"; + const { animated, still } = VARIANTS[variant]; return ( <> {/* eslint-disable-next-line @next/next/no-img-element */} {/* eslint-disable-next-line @next/next/no-img-element */} +
+ {/* prismatic halo, bled well past the rim */} +
+ + {/* the disc */} +
+
+ +
+
+ + {/* inner ring, so the disc has a floor for the character to stand on */} + + + {/* the comet on the rim. r=192 in a 400 box puts it on the disc's edge; + the dash spans 11% of the circle clockwise from 3 o'clock, so its + head lands at ~40° and the gradient runs that chord, cyan tail to + red head. */} + + + + + + + + + + + + + + + {/* the character, sized to the inner ring and nudged up so its mass — + not its bounding box — sits on the disc's optical centre */} +
+ +
+
+
+ ); +} diff --git a/apps/site/src/components/product/product-features.tsx b/apps/site/src/components/product/product-features.tsx index 53509b379ab..e785b246d36 100644 --- a/apps/site/src/components/product/product-features.tsx +++ b/apps/site/src/components/product/product-features.tsx @@ -1,5 +1,6 @@ import { GlassPrismSpin } from "@/components/brand/glass-prism-spin"; import { LearnMore } from "@/components/brand/learn-more"; +import { PrismButtonOutline } from "@/components/brand/prism-button"; import { Texture } from "@/components/brand/texture"; import { Reveal } from "@/components/motion/reveal"; import { cn } from "@/lib/utils"; @@ -43,7 +44,33 @@ function centreLastPair(count: number, i: number) { // (~2:3): the prism-tinted illustration block fills the card above the // content, the feature's abstraction filling the block, inside the homepage // stack's wrapped panel. -export function ProductFeatures({ features }: Pick) { +export function ProductFeatures({ + features, + cta, + placeholderLabel = "[Feature abstraction]", + mediaHeight = "h-64", + frameIllustration = false, +}: Pick & { + /** Closes the section where the copy carries one — use-case pages do, product pages don't. */ + cta?: { label: string; href: string }; + /** What the reserved illustration slots are waiting for, when there aren't any yet. */ + placeholderLabel?: string; + /** + * Float each illustration in a rounded, bordered, shadowed surface over the + * ray photo, so hero-scale panels (which are otherwise flush) read as one + * consistent card per feature instead of some panels hitting the edges. Used + * by pages that reuse the full-bleed hero panels here (the AI-agents stack). + */ + frameIllustration?: boolean; + /** + * Height of each card's illustration block. The default suits the three-up + * row the cards were proportioned for (~2:3 portrait). At two-up the cards + * are nearly twice as wide, so the same block leaves them landscape and + * squat — pages running four cards can grow it back toward portrait + * (André, 2026-08-13). + */ + mediaHeight?: string; +}) { return (
@@ -108,7 +135,13 @@ export function ProductFeatures({ features }: Pick
{/* illustration and content split the card roughly in half */} -
+
{Illustration ? ( -
+
) : ( @@ -125,8 +164,8 @@ export function ProductFeatures({ features }: Pick -

- [Feature abstraction] +

+ {placeholderLabel}

)} @@ -145,6 +184,14 @@ export function ProductFeatures({ features }: Pick + + {cta ? ( + + + {cta.label} + + + ) : null}
diff --git a/apps/site/src/components/product/product-hero.tsx b/apps/site/src/components/product/product-hero.tsx index b6e81dd1f58..7835553c97f 100644 --- a/apps/site/src/components/product/product-hero.tsx +++ b/apps/site/src/components/product/product-hero.tsx @@ -5,7 +5,7 @@ import { PrismRay } from "@/components/brand/prism-ray"; import { RoleKicker } from "@/components/brand/role-kicker"; import { Texture } from "@/components/brand/texture"; import { cn } from "@/lib/utils"; -import { PLATFORM_PRODUCT_ACCENTS } from "./icons"; +import { PLATFORM_PRODUCT_ACCENTS, type ProductAccentName } from "./icons"; import { PRODUCT_ILLUSTRATIONS } from "./illustrations"; import { ProductTour } from "./product-tour"; import type { ProductPageContent } from "./types"; @@ -43,8 +43,61 @@ export function ProductHero({ name, accent, hero, -}: Pick) { + visual, + placeholderLabel = "[Product abstraction]", + benefitsPlacement = "below-cta", +}: Pick & { + /** + * A Platform product's canonical accent, or a raw `bg-*` class for pages + * that aren't a product — use-case pages carry the same hero but must not + * claim a product identity in the kicker dot. + */ + accent: ProductAccentName | `bg-${string}`; + /** + * A ready-made visual for the right column, rendered ahead of the tour / + * illustration / placeholder fallbacks. Use-case pages carry no product + * tour, so they pass an existing site abstraction here (e.g. the Console + * illustration) rather than reserving a labelled slot. + */ + visual?: React.ReactNode; + /** What the reserved illustration slot is waiting for, when there isn't one yet. */ + placeholderLabel?: string; + /** + * Where the three benefits sit relative to the CTA. Product pages keep + * `below-cta` — the 2026-08-06 review moved them there because they were + * pushing the CTA below the fold on /orm. Use-case pages ask for + * `above-cta`, which is the order their approved copy is written in + * (André, 2026-08-13); their copy is short enough to carry it. + */ + benefitsPlacement?: "above-cta" | "below-cta"; +}) { const Illustration = hero.illustration ? PRODUCT_ILLUSTRATIONS[hero.illustration] : null; + const dotColor = accent.startsWith("bg-") + ? accent + : PLATFORM_PRODUCT_ACCENTS[accent as ProductAccentName]; + const benefitsAbove = benefitsPlacement === "above-cta"; + + // Above the CTA the list is part of the pitch, so it runs straight on from + // the subheadline. Below it, it's supporting detail after the ask, and the + // rule separates the two. + const benefits = ( +
    + {hero.benefits.map((label, i) => ( +
  • + + {label} +
  • + ))} +
+ ); return (
@@ -83,7 +136,7 @@ export function ProductHero({ {/* the site's standard tagline: sentence case, ink at 70%, colour carried by the dot — never uppercase, letter-spaced or grey (documented on /brand) */} - {name} + {name} {/* 16ch is the intended measure, but at the top of the clamp it resolves wider than this column, so the column has to be the hard limit — otherwise a headline with a long nowrap emphasis @@ -91,9 +144,26 @@ export function ProductHero({

-

- {hero.subheadline} -

+ {/* One paragraph on product pages; use-case copy leads with two, + so an array renders a stacked pair at the same measure. */} + {(Array.isArray(hero.subheadline) ? hero.subheadline : [hero.subheadline]).map( + (para, i) => ( +

+ {para} +

+ ), + )} + {/* Benefits support the CTA rather than delaying it — on product + pages they sit below it, so they carry their original weight + without competing. It was the position that pushed the CTA + down, not the type. */} + {benefitsAbove ? benefits : null}
{hero.primaryCta.label} @@ -105,24 +175,7 @@ export function ProductHero({ {hero.microline ? (

{hero.microline}

) : null} - {/* Benefits support the CTA rather than delaying it. They sit - below it now, so they can carry their original weight without - competing — it's the position that was pushing the CTA down, - not the type. */} -
    - {hero.benefits.map((label, i) => ( -
  • - - {label} -
  • - ))} -
+ {benefitsAbove ? null : benefits}
{/* the demo: the ray crosses the panel behind it — light passing @@ -139,7 +192,11 @@ export function ProductHero({ intermediate state while editing content, and it is truthy — it would render a tour whose `% stops.length` is NaN, giving an empty card with no tabs instead of falling through here */} - {hero.tour?.length ? ( + {visual ? ( +
+ {visual} +
+ ) : hero.tour?.length ? ( ) : Illustration ? (
@@ -147,8 +204,8 @@ export function ProductHero({
) : (
-

- [Product abstraction] +

+ {placeholderLabel}

)} diff --git a/apps/site/src/components/product/product-narrative.tsx b/apps/site/src/components/product/product-narrative.tsx index 2ebb07e256b..91d16a953e1 100644 --- a/apps/site/src/components/product/product-narrative.tsx +++ b/apps/site/src/components/product/product-narrative.tsx @@ -9,17 +9,20 @@ export function ProductNarrative({ headline, paragraphs, illustration, + centerText = false, }: { headline: string; paragraphs: string[]; illustration: React.ReactNode; + /** Vertically centre the copy against a full-height (e.g. square) graphic. */ + centerText?: boolean; }) { return (
{/* columns stretch rather than centre, so the graphic reads as a panel the height of the argument beside it instead of a card floating in it */}
-
+

{headline} diff --git a/apps/site/src/components/product/types.ts b/apps/site/src/components/product/types.ts index b64e48a3b2c..83f5faa9910 100644 --- a/apps/site/src/components/product/types.ts +++ b/apps/site/src/components/product/types.ts @@ -54,8 +54,11 @@ export type ProductPageContent = { * two — the second sentence pushed the primary CTA to ~700px, below the * fold on a 1440x800 laptop, and every page's second sentence was already * restated by the section under the hero. + * + * An array renders as stacked paragraphs, for use-case pages whose approved + * copy leads with two. */ - subheadline: string; + subheadline: string | string[]; /** Exactly three specific benefits. Sit under the CTA, not above it. */ benefits: [string, string, string]; primaryCta: Cta; diff --git a/apps/site/src/components/sections/agent-loop.tsx b/apps/site/src/components/sections/agent-loop.tsx index 30007e20e6c..15b939cbb56 100644 --- a/apps/site/src/components/sections/agent-loop.tsx +++ b/apps/site/src/components/sections/agent-loop.tsx @@ -163,7 +163,13 @@ function LoopDiagram() { ); } -export function AgentLoop() { +// `heading` lets /stack re-point the same section at its own narrative while +// the homepage keeps the default. +export function AgentLoop({ + heading = "What changes when your stack is built to work together", +}: { + heading?: string; +} = {}) { return (
@@ -171,7 +177,7 @@ export function AgentLoop() {

- What changes when your stack is built to work together + {heading}

diff --git a/apps/site/src/components/sections/changelog-feed.tsx b/apps/site/src/components/sections/changelog-feed.tsx new file mode 100644 index 00000000000..6dfa2adf128 --- /dev/null +++ b/apps/site/src/components/sections/changelog-feed.tsx @@ -0,0 +1,277 @@ +"use client"; + +import { useMemo, useState } from "react"; +import Link from "next/link"; +import { ArrowRightBold } from "@/components/icons/forma"; +import { Reveal } from "@/components/motion/reveal"; +import { + CATEGORY_META, + CATEGORY_ORDER, + type ChangelogCategory, + type ChangelogEntry, +} from "@/data/changelog"; +import { cn } from "@/lib/utils"; + +// The feed: a sticky category filter over a colour-coded release timeline. +// One client component because the tabs and the list share a single piece of +// filter state — the same call data/blog-browser makes. Twelve entries is far +// too few to debounce or round-trip, so filtering is a plain array filter in +// the client and the state stays out of the URL (no deep links to share yet). +// +// The timeline is the redesign's answer to the live changelog's flat numbered +// list: a vertical spine on desktop with a category-coloured node per entry, +// the date on a rail to its left, and the content in a card whose accent is the +// entry's product area. You can scan the colours down the spine and tell a +// Compute launch from an ORM fix without reading a word. + +const FMT = new Intl.DateTimeFormat("en-US", { + month: "short", + day: "numeric", + year: "numeric", + timeZone: "UTC", +}); + +type Filter = ChangelogCategory | "all"; + +// `all` is every release note in content/changelog, newest first, shaped by +// lib/changelog.ts on the server; this component only filters it. +export function ChangelogFeed({ entries: all }: { entries: ChangelogEntry[] }) { + const [filter, setFilter] = useState("all"); + + // Counts per category — totals over the whole feed that never change with + // the active filter. + const counts = useMemo( + () => + CATEGORY_ORDER.reduce>((acc, id) => { + acc[id] = all.filter((e) => e.category === id).length; + return acc; + }, {}), + [all], + ); + + const entries = useMemo( + () => (filter === "all" ? all : all.filter((e) => e.category === filter)), + [all, filter], + ); + + return ( +
+
+ {/* Sticky filter bar. Sits below the fixed header (top-16) with a + frosted backer so entries scroll cleanly under it. */} +
+
+
+ setFilter("all")} + label="All updates" + count={all.length} + /> + {/* Only categories that actually have entries — a zero-count tab + dead-ends to a blank timeline. Same rule blog-browser applies to + its topic chips. */} + {CATEGORY_ORDER.filter((id) => (counts[id] ?? 0) > 0).map((id) => ( + setFilter(id)} + label={CATEGORY_META[id].label} + count={counts[id] ?? 0} + dot={CATEGORY_META[id].dot} + glow={CATEGORY_META[id].glow} + /> + ))} +
+
+ + {/* Timeline */} +
    + {entries.map((entry, i) => ( + + + + ))} +
+
+
+ ); +} + +function FilterPill({ + active, + onClick, + label, + count, + dot, + glow, +}: { + active: boolean; + onClick: () => void; + label: string; + count: number; + dot?: string; + glow?: string; +}) { + return ( + + ); +} + +function TimelineRow({ entry, isLast }: { entry: ChangelogEntry; isLast: boolean }) { + const meta = CATEGORY_META[entry.category]; + return ( +
  • + {/* Date rail — desktop only, right-aligned onto the spine */} +
    + + {meta.label} +
    + + {/* Content column — the spine is this column's left border; consecutive + rows stack their borders into one continuous line. The node sits on + it, punched through with a background-colored ring. */} +
    + {/* node */} + + {entry.highlight && ( + + )} + + + + +
    +
  • + ); +} + +function EntryCard({ + entry, + meta, +}: { + entry: ChangelogEntry; + meta: (typeof CATEGORY_META)[ChangelogCategory]; +}) { + return ( + + {/* Every card blooms a wash of its category colour up from the bottom on + hover — the panel idiom in miniature. Fades in only on hover so the + resting feed stays clean and the colour reads as a reward for + reaching for the card. */} + + +
    + {/* meta row: category chip + date (date shows here on mobile, where the + rail is hidden) */} +
    + + + {meta.label} + + {entry.highlight && ( + Highlight + )} + +
    + +

    + {entry.title} +

    + +

    + {entry.description} +

    + + {/* tags */} + {entry.tags.length > 0 && ( +
      + {entry.tags.map((tag) => ( +
    • + {tag} +
    • + ))} +
    + )} + + {/* "Read more" on its own is a generic anchor: the visible label + stays, the hidden half names the entry it opens. */} + + Read more + : {entry.title} + + +
    + + ); +} diff --git a/apps/site/src/components/sections/changelog-hero.tsx b/apps/site/src/components/sections/changelog-hero.tsx new file mode 100644 index 00000000000..1d190639936 --- /dev/null +++ b/apps/site/src/components/sections/changelog-hero.tsx @@ -0,0 +1,64 @@ +import { GlassGlide } from "@/components/brand/glass-glide"; +import { Texture } from "@/components/brand/texture"; + +// /changelog hero — the wrapped prismatic panel, centred like customers-hero: +// the page opens on a claim, not a form or product visual, so there's no +// second column to balance. Wash values are product-hero's short-panel +// calibration, not the homepage's, so the beam fan doesn't bloom up behind the +// subhead and cost it contrast. +// +// The live changelog opens on a bare "Changelog / What's new in Prisma" stack +// on white. Same words, given the brand's front door: the spectral wash, the +// grain, and one phrase lit by the glass glide. +export function ChangelogHero() { + return ( +
    +
    + {/* spectral bottom — wash + beam fan dispersing to white above */} +
    +
    +
    +
    +
    +
    +
    + + +
    + {/* top padding = bottom padding + the fixed header's footprint */} +
    +
    + + + + + + What's new in Prisma + + +

    + The Changelog +

    +

    + New features, improvements, and fixes across Prisma ORM, Prisma Postgres, and the + platform — shipped continuously and gathered here. +

    +
    +
    +
    +
    +
    + ); +} diff --git a/apps/site/src/components/sections/comparison.tsx b/apps/site/src/components/sections/comparison.tsx index c863a764a41..c647983b56a 100644 --- a/apps/site/src/components/sections/comparison.tsx +++ b/apps/site/src/components/sections/comparison.tsx @@ -2,6 +2,7 @@ import { CheckBold, X } from "@/components/icons/forma"; import { Pattern } from "@/components/brand/pattern"; import { Reveal } from "@/components/motion/reveal"; import { BrokenCard, LiveCard } from "@/components/sections/comparison-cards"; +import { cn } from "@/lib/utils"; const BEFORE = [ "A database from Neon, an ORM from Drizzle, hosting from Vercel", @@ -19,16 +20,42 @@ const AFTER = [ "Spend limits on every paid tier, so your bill stops where you tell it to", ]; +type ComparisonProps = { + heading?: React.ReactNode; + before?: readonly string[]; + after?: readonly string[]; + /** + * The two mini deploy cards. On by default — they are the section's whole + * point on the homepage. Use-case pages turn them off: the cards illustrate + * *the platform's* broken/live moment, and repeating them on every use case + * would show the same abstraction three or four times across the site + * (André, 2026-08-13). + */ + cards?: boolean; +}; + // Before/after: two columns, each led by a mini deploy card — the same // moment, broken on the left, alive on the right. Card microcopy is // decorative UI illustration (same idiom as ConsoleIllustration). -export function Comparison() { +// +// Defaults are the homepage set; pass copy to reuse the layout elsewhere +// (see use-case/use-case-page.tsx), the same way Faq and CtaBurst are reused. +export function Comparison({ + heading = "The stack your agent has been waiting for", + before = BEFORE, + after = AFTER, + cards = true, +}: ComparisonProps = {}) { + // Without the cards the lists start directly under the column heading, so + // they take the heading's own rhythm instead of the card's clearance. + const listSpacing = cards ? "mt-10" : "mt-8"; + return (

    - The stack your agent has been waiting for + {heading}

    @@ -38,10 +65,10 @@ export function Comparison() {

    Before

    {/* broken deploy card — glass shatters into its current state */} - + {cards ? : null} -
      - {BEFORE.map((item) => ( +
        + {before.map((item) => (
      • After

    {/* live deploy card — a cursor glides in and clicks Deploy preview */} - + {cards ? : null} -
      - {AFTER.map((item) => ( +
        + {after.map((item) => (
      • (null); const inView = useInView(ref, IN_VIEW); @@ -79,7 +83,10 @@ export function ComputeIllustration() {
        {/* ray backdrop — saturation boosted (bold pass) on its own layer so the filter doesn't touch the card content */} @@ -163,7 +170,12 @@ export function ComputeIllustration() {
        {/* the compute chip — glows and sparkles */} -
        +
          - {checks.map(({ label, color }, i) => ( + {checks.map(({ label, color, ownLine }, i) => (
        • {label} diff --git a/apps/site/src/components/sections/customer-story.tsx b/apps/site/src/components/sections/customer-story.tsx new file mode 100644 index 00000000000..b23d3b73247 --- /dev/null +++ b/apps/site/src/components/sections/customer-story.tsx @@ -0,0 +1,580 @@ +import { IconTile } from "@/components/brand/icon-tile"; +import { PrismButton, PrismButtonOutline } from "@/components/brand/prism-button"; +import { PrismRay } from "@/components/brand/prism-ray"; +import { RoleKicker } from "@/components/brand/role-kicker"; +import { Texture } from "@/components/brand/texture"; +import { Star } from "@/components/icons/forma"; +import { Reveal } from "@/components/motion/reveal"; +import { CtaBurst } from "@/components/sections/cta-burst"; +import { PRODUCT_ICONS } from "@/components/product/icons"; +import { PRODUCT_ILLUSTRATIONS } from "@/components/product/illustrations"; +import type { CustomerStoryDetail } from "@/data/customer-stories"; +import { cn } from "@/lib/utils"; + +// /customers/[slug] — the customer story detail template. +// +// Built entirely out of the redesign's existing vocabulary: the wrapped +// prismatic panel (hero-home / product-hero / cta-burst), the split hero with a +// PrismRay crossing behind the visual, IconTile cards (product-problem), a +// product illustration for the "how they use it" visual (product-features), and +// CtaBurst for the closer. Surfaces alternate white ↔ wrapped panel — there is +// deliberately no bg-muted band. Section order follows the approved copy. + +const CHECK_COLORS = ["text-prism-cyan-500", "text-prism-yellow-400", "text-prism-red-500"]; + +export function CustomerStory({ story }: { story: CustomerStoryDetail }) { + return ( + <> + + + + + {story.quote && } + + + ({ + label, + color: CHECK_COLORS[i % CHECK_COLORS.length], + }))} + primaryCta={{ label: "Get started free", href: "https://console.prisma.io" }} + secondaryCta={{ label: "Talk to us", href: "/contact" }} + /> + + ); +} + +// The wrapped prismatic panel used across the site (hero-home, product-hero, +// product-features): a rounded white card with the spectral wash collecting +// along the bottom and dispersing to white above. Extracted here so the hero, +// quote and results panels share one source instead of three copies. +function PrismPanel({ children, className }: { children: React.ReactNode; className?: string }) { + return ( +
          +
          +
          +
          +
          +
          +
          +
          + +
          {children}
          +
          + ); +} + +// The customer mark on a light surface — flattened to solid black (no chip, no +// background). brightness-0 alone forces black while keeping the alpha, so both +// the white marks (logoAsIs) and the dark-ink originals read as one flat mark. +function LogoMark({ story, className }: { story: CustomerStoryDetail; className?: string }) { + return ( + // eslint-disable-next-line @next/next/no-img-element + {story.name} + ); +} + +// Opening quote glyph. Its slice-glitch classes (animate-quote-slice-*) live in +// globals.css — added for the original pricing-hero testimonial, kept when that +// hero was reworked, reused here. +function QuoteGlyph({ className, style }: { className?: string; style?: React.CSSProperties }) { + return ( + + + + + ); +} + +// The testimonial quote-mark with a continuous slice glitch (React Bits +// GlitchText idiom): the ink mark, static chromatic offsets under it, and three +// card-colored layers clipped to a jumping band so slices read as displaced +// through the prism trio. Sits on an OPAQUE card surface — the layers mask with +// bg-card between flickers. +function GlitchQuote({ className }: { className?: string }) { + return ( + + + + + + + + + + + + + + + + ); +} + +// Comma-separated Stack fact → a short list, for the hero plate's footer. Capped +// so a long stack (Amplication runs eight) stays a clean one-or-two-line summary; +// the full stack still shows in the About fact sheet. +function stackFrom(story: CustomerStoryDetail): string[] { + const fact = story.about.facts.find((f) => f.label.toLowerCase() === "stack"); + if (!fact) return []; + return fact.value + .split(",") + .map((s) => s.trim()) + .filter(Boolean) + .slice(0, 5); +} + +// The hero visual: a black "built with" plate that fills its column — the Prisma +// mark up top, the customer's mark large and centred, the stack along the +// bottom — with the brand's prismatic light leaking in at the corners and the +// triple-band ray crossing behind it (story-art.tsx idiom, given room to breathe). +function HeroVisual({ story }: { story: CustomerStoryDetail }) { + const stack = stackFrom(story); + return ( +
          + +
          +
          + + + {/* Prisma, hosting the story */} +
          + {/* eslint-disable-next-line @next/next/no-img-element */} + Prisma + + Customer + +
          + + {/* the customer, given the room */} +
          + {/* eslint-disable-next-line @next/next/no-img-element */} + {story.name} +
          + + {/* the stack, filling the base */} + {stack.length > 0 && ( +
          + {stack.map((tech, i) => ( + + {i > 0 && } + {tech} + + ))} +
          + )} +
          +
          + ); +} + +// Hero — product-hero's wrapped panel in a split: copy left, the "built with" +// plate on the right with the ray crossing behind it. +function StoryHero({ story }: { story: CustomerStoryDetail }) { + return ( +
          + +
          +
          + {/* copy */} +
          + Customer story +

          + {story.hero.title} +

          +
          +

          {story.hero.lead}

          + {story.hero.support &&

          {story.hero.support}

          } +
          +
          + + Get started free + + + Talk to us + +
          +
          + + +
          +
          +
          +
          + ); +} + +// About — narrative on the left, the fact sheet on the right as a card where +// every row carries its own IconTile. +function StoryAbout({ story }: { story: CustomerStoryDetail }) { + return ( +
          +
          + +

          + {story.about.heading} +

          +

          + {story.about.body} +

          +
          + + +
            + {story.about.facts.map((fact) => { + const Icon = PRODUCT_ICONS[fact.icon]; + return ( +
          • + + + +
            +

            {fact.label}

            +

            {fact.value}

            +
            +
          • + ); + })} +
          +
          +
          +
          + ); +} + +// Challenge — a wrapped panel: framing on the left, the constraints as IconTile +// cards on the right, the closing line beneath. +function StoryChallenge({ story }: { story: CustomerStoryDetail }) { + return ( +
          + +
          +
          + +

          + {story.challenge.heading} +

          +
          + {story.challenge.body.map((p, i) => ( +

          {p}

          + ))} +
          + {story.challenge.quote && ( +
          +
          + “{story.challenge.quote.text}” +
          +
          + + {story.challenge.quote.author} + + {", "} + {story.challenge.quote.role} + {story.challenge.quote.company && <>, {story.challenge.quote.company}} +
          +
          + )} +
          + +
          + {story.challenge.points.map((point, i) => { + const Icon = PRODUCT_ICONS[point.icon]; + return ( + +
          + + + +

          + {point.title}{" "} + {point.body} +

          +
          +
          + ); + })} +
          +
          + + {story.challenge.outro && ( + +

          + {story.challenge.outro} +

          +
          + )} +
          +
          +
          + ); +} + +// Why Prisma — the three-up IconTile card row (product-problem idiom). +function StoryReasons({ story }: { story: CustomerStoryDetail }) { + return ( +
          +
          +
          + +

          + {story.reasons.heading} +

          +
          + +

          + {story.reasons.intro} +

          +
          +
          + +
          + {story.reasons.cards.map((card, i) => { + const Icon = PRODUCT_ICONS[card.icon]; + return ( + +
          + + + +

          + {card.title} +

          +

          {card.body}

          +
          +
          + ); + })} +
          +
          +
          + ); +} + +// Pull quote — the Relume "Testimonial4" shape the review asked for, on a +// wrapped panel: rating, a bold centred quote, then an attribution row of +// monogram · name/role · divider · logo. +function StoryQuote({ + quote, + story, +}: { + quote: NonNullable; + story: CustomerStoryDetail; +}) { + const initials = quote.author + .split(" ") + .map((w) => w[0]) + .slice(0, 2) + .join(""); + + return ( +
          + +
          + +
          + {Array.from({ length: 5 }).map((_, i) => ( + + ))} +
          + +
          + “{quote.text}” +
          + +
          + + {initials} + +
          +

          {quote.author}

          +

          + {quote.role} + {quote.company && <>, {quote.company}} +

          +
          + + {quote.companyHref ? ( + + + + ) : ( + + )} +
          +
          +
          +
          +
          + ); +} + +// How they use Prisma — the argument on the left (vertically centred against the +// visual), a schema.prisma abstraction on the right. Reuses the product pages' +// SchemaFile illustration so the visual is real and never repeats the hero plate. +function StoryUsage({ story }: { story: CustomerStoryDetail }) { + const SchemaFile = PRODUCT_ILLUSTRATIONS.schemaFile; + return ( +
          +
          + +

          + {story.usage.heading} +

          +
          + {story.usage.body.map((p, i) => ( +

          {p}

          + ))} +
          +
          + + +
          + +
          +
          +
          +
          + ); +} + +// Results — a wrapped panel split two ways: the outcome heading and the three +// IconTile result cards on the left, the closing quote as a tall card on the +// right (per the review wireframe). Falls back to a single column when a story +// carries no closing quote. +function StoryResults({ story }: { story: CustomerStoryDetail }) { + const quote = story.closingQuote; + return ( +
          + +
          +
          + {/* left: heading, intro, the outcomes */} +
          + +

          + {story.results.heading} +

          +

          + {story.results.intro} +

          +
          + +
          + {story.results.items.map((item, i) => { + const Icon = PRODUCT_ICONS[item.icon]; + return ( + +
          + + + +
          +

          + {item.stat} +

          +

          + {item.detail} +

          +
          +
          +
          + ); + })} +
          +
          + + {/* right: the closing quote as a tall card */} + {quote && ( + +
          + +
          + {quote.text} +
          +
          + + +

          + {quote.author} + {", "} + {quote.role} + {quote.company && <> at {quote.company}} +

          +
          +
          +
          + )} +
          +
          +
          +
          + ); +} diff --git a/apps/site/src/components/sections/customers-grid.tsx b/apps/site/src/components/sections/customers-grid.tsx index 43457c5c0d2..79e0b31f298 100644 --- a/apps/site/src/components/sections/customers-grid.tsx +++ b/apps/site/src/components/sections/customers-grid.tsx @@ -1,22 +1,80 @@ import Link from "next/link"; -import { ArrowRight } from "@/components/icons/forma"; -import { CUSTOMER_STORIES } from "@/lib/customers"; +import { Marker } from "@/components/brand/marker"; +import { ArrowRightBold } from "@/components/icons/forma"; +import { Reveal } from "@/components/motion/reveal"; +import { StoryArt } from "@/components/sections/story-art"; +import { STORY_DETAIL_SLUGS } from "@/data/customer-stories"; +import { CUSTOMER_STORIES, type CustomerStory } from "@/data/customers"; import { cn } from "@/lib/utils"; -// Case-study grid — the 13 production stories ported from the old site's -// /case-studies page. The lead story spans two columns on desktop; everything -// else sits in the uniform 3-up grid. Story art is full-bleed (the old site -// rendered the same assets object-cover) under a soft card wash. +// Display names for the stack ids carried on each story. Anything unmapped +// falls through to the raw id rather than being dropped, so a new technology +// appearing in the data shows up (slightly wrong) instead of silently vanishing. +const STACK_LABELS: Record = { + apollo: "Apollo", + aws: "AWS", + cloudflare: "Cloudflare", + cockroach: "CockroachDB", + cognito: "Cognito", + docker: "Docker", + gcp: "Google Cloud", + graphql: "GraphQL", + kafka: "Kafka", + kubernetes: "Kubernetes", + mongodb: "MongoDB", + mysql: "MySQL", + nest: "NestJS", + next: "Next.js", + nexus: "Nexus", + node: "Node.js", + postgres: "Postgres", + prisma: "Prisma", + react: "React", + redis: "Redis", + trpc: "tRPC", + ts: "TypeScript", + vercel: "Vercel", +}; + +// The three brand colours in the site's standard order, cycled across a story's +// chips so the row carries the spectrum without any one chip claiming meaning. +const DOT_COLORS = ["bg-prism-cyan-400", "bg-prism-yellow-400", "bg-prism-red-400"]; + +// Chips are capped at three. Five is the longest stack in the data, and at card +// width a five-chip row wraps to two lines and starts competing with the title +// for weight — the reference layout runs three. +const MAX_CHIPS = 3; + +// /customers story grid — all thirteen customer stories. +// +// Thirteen doesn't divide into three columns, so the first story runs +// full-width as a lead card and the remaining twelve fill exactly four rows of +// three. That gives the reference layout's featured-card moment without asking +// anyone for new copy: the lead card renders the same fields as every other +// card, just larger and horizontal. +// +// Three columns rather than the reference's two: at max-w-site (1400px) a +// two-column card is ~690px, far wider than a logo plate wants. Three lands at +// ~455px, close to the reference's own ~500px. +// +// Cards link inward to /customers/[slug] where a detail page has been built +// (STORY_DETAIL_SLUGS), and fall back to the live prisma.io blog post for the +// rest until their pages land. export function CustomersGrid() { const [lead, ...rest] = CUSTOMER_STORIES; return ( -
          +
          -
          - - {rest.map((story) => ( - + + + + +
          + {rest.map((story, i) => ( + + + ))}
          @@ -24,59 +82,93 @@ export function CustomersGrid() { ); } -function StoryCard({ - story, - className, - lead = false, -}: { - story: (typeof CUSTOMER_STORIES)[number]; - className?: string; - lead?: boolean; -}) { +function StoryCard({ story, lead = false }: { story: CustomerStory; lead?: boolean }) { + const internal = STORY_DETAIL_SLUGS.has(story.slug); + const href = internal ? `/customers/${story.slug}` : story.href; + // Detail pages are this app's own routes, so they get client-side routing; + // the fallback to a live blog post is another zone and hard-navigates. + const Card = internal ? Link : "a"; + return ( - -
          - {/* eslint-disable-next-line @next/next/no-img-element */} - -
          -
          -

          {story.company}

          -

          + +
          +

          {story.title} -

          -

          +

          + + {story.stack.length > 0 && ( +
            + {story.stack.slice(0, MAX_CHIPS).map((tech, i) => ( +
          • + + {STACK_LABELS[tech] ?? tech} + +
          • + ))} +
          + )} + + {/* No `grow` on the clamped paragraph: line-clamp needs + display:-webkit-box, and letting flex stretch that box makes it tall + enough to paint a fourth line which then gets sliced through the + middle by the clamp. The link takes mt-auto instead, so cards still + bottom-align without the paragraph having to absorb the slack. */} +

          {story.excerpt}

          - - Read the story - + + {/* Grid cards push the link to the bottom edge so it lines up across a + row whatever the excerpt length. The lead card must not: its column + is vertically centred, so mt-auto strands the link at the bottom + with a gap above it — visible as soon as the card was made taller. */} + {/* The underline needs its own box to sit under, so it goes on an inner + span — putting it on the outer one would draw the rule 3px below the + card's bottom padding instead of under the words. */} + + + Read the story + +
          - + ); } diff --git a/apps/site/src/components/sections/customers-hero.tsx b/apps/site/src/components/sections/customers-hero.tsx index e65505ccfc1..92b64409d39 100644 --- a/apps/site/src/components/sections/customers-hero.tsx +++ b/apps/site/src/components/sections/customers-hero.tsx @@ -1,46 +1,130 @@ -import { RoleKicker } from "@/components/brand/role-kicker"; +import { GlassGlide } from "@/components/brand/glass-glide"; +import { PrismButton, PrismButtonOutline } from "@/components/brand/prism-button"; import { Texture } from "@/components/brand/texture"; +import { BAND_LOGOS } from "@/data/customers"; -// Customers hero — wrapped-panel title band (same shell as product-hero, with -// the spectral wash dialed down: this page's color comes from the story grid, -// so the hero stays a quiet paper surface). +// /customers hero — the wrapped prismatic panel, centred rather than split: +// this page opens on a claim and a proof band, not on a form or a product +// visual, so there is no second column to balance (contact-hero.tsx splits at +// lg for exactly that reason). +// +// A split layout with the marks drifting in a constellation beside the copy was +// tried and reverted (André, 2026-08-17). Centred, with the band reading as one +// roster across the full width, is the version that stays. +// +// The "Built with Prisma" logo band lives INSIDE the panel, sitting on the +// spectral wash. The approved copy runs that label straight off the CTAs with +// no heading marker, so it reads as the panel's footer rather than as its own +// section. The second, much wider logo moment (all 44 community marks) is the +// marquee further down, which answers "how many" where this answers "who". +// +// Wash values are product-hero's calibration, not the homepage's, for the same +// reason contact-hero gives: this panel is short, and the homepage's stronger +// values bloom up behind the headline and cost the subhead its contrast. export function CustomersHero() { return (
          + {/* spectral bottom — wash + beam fan dispersing to white above, same + values as product-hero.tsx and contact-hero.tsx */}
          -
          +
          +
          +
          +
          -
          - - Customer stories - -

          - Teams ship faster on Prisma -

          -

          - How engineering teams use Prisma in production to speed up delivery, simplify database - workflows, and scale without slowing down. -

          + {/* top padding = bottom padding + the fixed header's footprint */} +
          +
          +

          + How teams ship faster with Prisma +

          +

          + Real teams, real production stories. See how developers use Prisma to build, deploy, + and scale without wrestling their backend at every step. +

          +
          + + Get started free + + + Talk to us + +
          +
          + + {/* the proof band, on the wash */} +
          +

          + Built with Prisma +

          + {/* Static rather than a marquee: twelve marks are few enough to + show at once, and a fixed roster reads as "these companies" + where a marquee reads as "a crowd". The crowd is the marquee + below. + An explicit grid rather than flex-wrap — twelve items divide + evenly at every breakpoint here (6/4/3/2 per row), where + wrapping left a lopsided 8-then-4 at desktop width. */} +
            + {BAND_LOGOS.map((logo) => ( +
          • + +
          • + ))} +
          +
          ); } + +function BandLogo({ logo }: { logo: (typeof BAND_LOGOS)[number] }) { + const mark = ( + // eslint-disable-next-line @next/next/no-img-element + {logo.name} + ); + + const tile = + "spectrum-border flex h-[4.5rem] w-full items-center justify-center rounded-2xl border border-neutral-200 bg-white/80 px-5 backdrop-blur-sm"; + + // Insta Group is the one mark prisma.io carries without a link. + if (!logo.href) { + return
          {mark}
          ; + } + + return ( + + {mark} + + ); +} diff --git a/apps/site/src/components/sections/logo-cloud.tsx b/apps/site/src/components/sections/logo-cloud.tsx index 28cb8712032..55b08fc1fd3 100644 --- a/apps/site/src/components/sections/logo-cloud.tsx +++ b/apps/site/src/components/sections/logo-cloud.tsx @@ -1,4 +1,5 @@ import { Reveal } from "@/components/motion/reveal"; +import { cn } from "@/lib/utils"; type CompanyLogo = { name: string; @@ -40,7 +41,7 @@ const companies: CompanyLogo[] = [ /* The row is rendered twice per track (and the track twice) so the strip is wider than any viewport and the -50% marquee loop is seamless. */ -function Track({ hidden = false }: { hidden?: boolean }) { +function CompanyTrack({ hidden = false }: { hidden?: boolean }) { return (