diff --git a/apps/site/src/app/support/page.tsx b/apps/site/src/app/support/page.tsx
index 7a22cd9f4e2..d6d4be73376 100644
--- a/apps/site/src/app/support/page.tsx
+++ b/apps/site/src/app/support/page.tsx
@@ -1,156 +1,333 @@
+import type { ReactNode } from "react";
import { createPageMetadata } from "@/lib/page-metadata";
-import { ArrowRight } from "@/components/icons/forma";
-import { RoleKicker } from "@/components/brand/role-kicker";
+import { cn } from "@/lib/utils";
import { Texture } from "@/components/brand/texture";
+import { SupportSearch } from "@/components/support/support-search";
+import { Reveal } from "@/components/support/reveal";
+import {
+ ArrowRight,
+ CheckCircle,
+ ConsoleCode,
+ Discord,
+ DocWindow,
+ Github,
+ Mail,
+ PolicyTable,
+ Shield,
+} from "@/components/support/support-icons";
+import "./support.css";
export const metadata = createPageMetadata({
title: "Prisma Support | Get Help, Report Bugs, and Request Features",
description:
- "Get help with Prisma. Search for answers, report bugs, request features, or contact the Prisma support team.",
+ "Find an answer in the docs, ask the community, or reach our support team. Where you go depends on your plan and what you need.",
path: "/support",
ogKicker: "Support",
});
-// Ported from the old site's /support page (apps/site + data/support.json),
-// rebuilt in the redesign language: one card per support path, accent bars
-// instead of the old gradient washes.
+// Ported from the approved support design. Visual styling is reproduced
+// faithfully; link targets use the live site's real routes.
-type SupportCard = {
- title: string;
- description: string;
- accent: string;
- links: { label: string; url: string }[];
-};
+const RAINBOW =
+ "linear-gradient(85deg, #01d7e4 0%, #f3c306 25%, #f37a03 50%, #f43531 74%, #f00e5c 100%)";
-const SUPPORT_CARDS: SupportCard[] = [
+const HERO_GLOW =
+ "radial-gradient(52% 40% at 30% 100%, color-mix(in srgb, var(--color-prism-cyan-400) 34%, transparent), transparent 68%),radial-gradient(44% 36% at 52% 100%, color-mix(in srgb, var(--color-prism-yellow-300) 26%, transparent), transparent 66%),radial-gradient(42% 30% at 74% 100%, color-mix(in srgb, var(--color-prism-red-400) 28%, transparent), transparent 68%)";
+
+const PANEL_GLOW =
+ "radial-gradient(52% 44% at 28% 100%, color-mix(in srgb, var(--color-prism-cyan-400) 32%, transparent), transparent 70%),radial-gradient(44% 38% at 52% 100%, color-mix(in srgb, var(--color-prism-yellow-300) 24%, transparent), transparent 66%),radial-gradient(48% 40% at 74% 100%, color-mix(in srgb, var(--color-prism-red-400) 28%, transparent), transparent 70%)";
+
+const ICON_GLOW =
+ "radial-gradient(80% 55% at 20% 100%, color-mix(in srgb, var(--color-prism-cyan-300) 45%, transparent), transparent 70%),radial-gradient(70% 50% at 52% 100%, color-mix(in srgb, var(--color-prism-yellow-300) 40%, transparent), transparent 68%),radial-gradient(75% 52% at 84% 100%, color-mix(in srgb, var(--color-prism-red-300) 42%, transparent), transparent 70%)";
+
+const outlineButton =
+ "spectrum-border flex items-center justify-center rounded-full border border-[#646567] cursor-pointer transition-colors duration-500 hover:border-transparent px-[22px] py-[11px] text-[16px]";
+const outlineButtonLabel = "whitespace-nowrap font-semibold leading-[1.5] text-foreground";
+
+function GlowIcon({ className, children }: { className?: string; children: ReactNode }) {
+ return (
+
+
+ {children}
+
+ );
+}
+
+// Solid black pill button with a soft rainbow bloom behind it.
+function PrimaryButton({ href, children }: { href: string; children: ReactNode }) {
+ return (
+
+
+
+
+
+
+ {children}
+
+
+
+ );
+}
+
+const SUPPORT_CARDS = [
{
- title: "Issues & feature requests",
- description: "Found a bug, or want to request something new? Let us know.",
- accent: "bg-prism-cyan-400/70",
- links: [
- {
- label: "Report a bug",
- url: "https://github.com/prisma/orm/issues/new?assignees=&labels=&template=bug_report.md",
- },
- {
- label: "Request a new feature",
- url: "https://github.com/prisma/orm/issues/new?assignees=&labels=&template=feature_request.md",
- },
- ],
+ title: "Community support",
+ description:
+ "Ask questions and share what you're building with thousands of developers on our Discord. Available on every plan, including the free tier.",
+ icon: ,
+ cta: { label: "Join our Discord", url: "https://pris.ly/discord" },
},
{
- title: "Starter plan",
+ title: "Direct support",
description:
- "Support for customers on our Starter plan is provided through our community channels.",
- accent: "bg-prism-yellow-300",
- links: [{ label: "Join our Discord channel", url: "https://pris.ly/discord" }],
+ "On a Pro or Business plan? Submit a ticket from your Console, and our team will help.",
+ icon: ,
+ cta: { label: "Submit a ticket", url: "https://console.prisma.io" },
},
{
- title: "Pro & Business plans",
+ title: "Enterprise support",
description:
- "Support for customers on our Pro or Business plan is provided through the Platform Console.",
- accent: "bg-prism-red-400/80",
- links: [{ label: "Submit a ticket", url: "https://console.prisma.io" }],
+ "Need higher-touch support with guaranteed response times? Explore our enterprise options.",
+ icon: ,
+ cta: { label: "Discover Enterprise support", url: "/enterprise" },
},
];
-const MORE_LINKS = [
- { label: "Documentation", url: "/docs" },
- { label: "Prisma examples", url: "https://github.com/prisma/prisma-examples" },
- { label: "Support policy", url: "/support-policy" },
- { label: "Enterprise support", url: "/enterprise" },
+const RESOURCES = [
+ {
+ title: "Documentation",
+ description: "Guides, references, and API docs for every part of Prisma.",
+ url: "/docs",
+ cta: "Read the docs",
+ icon: ,
+ },
+ {
+ title: "Examples",
+ description: "Ready-to-run example projects on GitHub.",
+ url: "https://github.com/prisma/prisma-examples",
+ cta: "Browse examples",
+ icon: ,
+ },
+ {
+ title: "Platform status",
+ description: "Check current status and past incidents.",
+ url: "https://www.prisma-status.com",
+ cta: "View status",
+ icon: ,
+ },
+ {
+ title: "Support policy",
+ description: "What each plan includes and how support works.",
+ url: "/support-policy",
+ cta: "Read the policy",
+ icon: ,
+ },
];
export default function SupportPage() {
return (
- <>
+
+ {/* Hero */}
+
-
-
- Support
-
-
- How can we help?
+
+
+ How can we{" "}
+
+ help
+
+
+
+
+
+
+ ?
-
- Search the docs, report bugs, request features, or reach the Prisma support team.
+
+ Find an answer in the docs, ask the community, or reach our support team. Where you
+ go depends on your plan and what you need.
+
-
-
-
+ {/* Ways to get support */}
+
+
+
+
+ Ways to get support
+
+
+
{SUPPORT_CARDS.map((card) => (
-
-
-
{card.title}
-
{card.description}
-
+
))}
+
+
+ Response times depend on your subscription plan and current volume.
+
+
+
+
-
-
);
}
diff --git a/apps/site/src/app/support/support.css b/apps/site/src/app/support/support.css
new file mode 100644
index 00000000000..c88f6c2b881
--- /dev/null
+++ b/apps/site/src/app/support/support.css
@@ -0,0 +1,106 @@
+/* Support page motion + spectrum border — ported from the approved design. */
+
+/* Animated rainbow border, revealed on hover / focus-within. */
+.spectrum-border {
+ position: relative;
+}
+.spectrum-border::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ border-radius: inherit;
+ padding: 1px;
+ background-image: linear-gradient(85deg, #01d7e4, #f3c306, #f37a03, #f43531, #f00e5c, #01d7e4);
+ background-size: 200% 100%;
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 0.5s;
+ animation: 2.5s linear infinite spectrum-slide;
+ -webkit-mask-image: linear-gradient(#fff 0 0), linear-gradient(#fff 0 0);
+ mask-image: linear-gradient(#fff 0 0), linear-gradient(#fff 0 0);
+ -webkit-mask-clip: content-box, border-box;
+ mask-clip: content-box, border-box;
+ -webkit-mask-composite: xor;
+ mask-composite: exclude;
+}
+.spectrum-border:hover::before,
+.spectrum-border-on::before {
+ opacity: 1;
+}
+.spectrum-border-focus:focus-within::before {
+ opacity: 1;
+}
+
+@keyframes spectrum-slide {
+ 0% {
+ background-position: 0%;
+ }
+ 100% {
+ background-position: 200%;
+ }
+}
+
+/* Hero entrance + glass glide sweep. */
+.animate-hero-rise {
+ animation: 0.9s cubic-bezier(0.22, 1, 0.36, 1) 0.45s both hero-rise;
+}
+.animate-glass-glide {
+ animation: 9s 1.8s infinite both glass-glide;
+}
+
+@keyframes hero-rise {
+ 0% {
+ opacity: 0;
+ transform: translateY(16px);
+ }
+ 100% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+@keyframes glass-glide {
+ 0% {
+ opacity: 0;
+ animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1);
+ left: -20%;
+ }
+ 5% {
+ opacity: 1;
+ }
+ 25% {
+ opacity: 1;
+ }
+ 30%,
+ 100% {
+ opacity: 0;
+ left: 80%;
+ }
+}
+
+/* Scroll-reveal (opacity + lift), toggled via data-shown by the Reveal component. */
+.reveal {
+ opacity: 0;
+ transform: translateY(44px);
+ transition:
+ opacity 0.7s cubic-bezier(0.22, 1, 0.36, 1),
+ transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
+}
+.reveal[data-shown="true"] {
+ opacity: 1;
+ transform: none;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .spectrum-border::before,
+ .animate-hero-rise,
+ .animate-glass-glide {
+ animation: none;
+ }
+ /* Honor reduced motion: show content in place, no lift or transition. */
+ .reveal,
+ .reveal[data-shown="true"] {
+ opacity: 1;
+ transform: none;
+ transition: none;
+ }
+}
diff --git a/apps/site/src/components/support/reveal.tsx b/apps/site/src/components/support/reveal.tsx
new file mode 100644
index 00000000000..d735adcefed
--- /dev/null
+++ b/apps/site/src/components/support/reveal.tsx
@@ -0,0 +1,36 @@
+"use client";
+
+import { useEffect, useRef, useState, type ReactNode } from "react";
+
+// Scroll-reveal wrapper (opacity + translateY) matching the approved design.
+// Falls back to visible when IntersectionObserver is unavailable.
+export function Reveal({ children, className }: { children: ReactNode; className?: string }) {
+ const ref = useRef
(null);
+ const [shown, setShown] = useState(false);
+
+ useEffect(() => {
+ const el = ref.current;
+ if (!el) return;
+ if (typeof IntersectionObserver === "undefined") {
+ setShown(true);
+ return;
+ }
+ const observer = new IntersectionObserver(
+ ([entry]) => {
+ if (entry.isIntersecting) {
+ setShown(true);
+ observer.disconnect();
+ }
+ },
+ { rootMargin: "0px 0px -10% 0px", threshold: 0.1 },
+ );
+ observer.observe(el);
+ return () => observer.disconnect();
+ }, []);
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/apps/site/src/components/support/support-icons.tsx b/apps/site/src/components/support/support-icons.tsx
new file mode 100644
index 00000000000..0015b8f7dff
--- /dev/null
+++ b/apps/site/src/components/support/support-icons.tsx
@@ -0,0 +1,86 @@
+// Support page icon set — ported verbatim from the approved support design.
+// 24x24 glyphs; color via currentColor, size/color via the className prop.
+import type { SVGProps } from "react";
+
+type IconProps = SVGProps;
+
+export function SearchGlass({ className, ...props }: IconProps) {
+ return (
+
+ );
+}
+
+export function Discord({ className, ...props }: IconProps) {
+ return (
+
+ );
+}
+
+export function ConsoleCode({ className, ...props }: IconProps) {
+ return (
+
+ );
+}
+
+export function Shield({ className, ...props }: IconProps) {
+ return (
+
+ );
+}
+
+export function Github({ className, ...props }: IconProps) {
+ return (
+
+ );
+}
+
+export function DocWindow({ className, ...props }: IconProps) {
+ return (
+
+ );
+}
+
+export function ArrowRight({ className, ...props }: IconProps) {
+ return (
+
+ );
+}
+
+export function CheckCircle({ className, ...props }: IconProps) {
+ return (
+
+ );
+}
+
+export function PolicyTable({ className, ...props }: IconProps) {
+ return (
+
+ );
+}
+
+export function Mail({ className, ...props }: IconProps) {
+ return (
+
+ );
+}
+
diff --git a/apps/site/src/components/support/support-search.tsx b/apps/site/src/components/support/support-search.tsx
new file mode 100644
index 00000000000..93c16b6e6e7
--- /dev/null
+++ b/apps/site/src/components/support/support-search.tsx
@@ -0,0 +1,73 @@
+"use client";
+
+import { useSearchContext } from "fumadocs-ui/contexts/search";
+import { SearchGlass } from "@/components/support/support-icons";
+
+// Hero search box, styled per the approved design, wired to the site-wide
+// unified search dialog (the same context the header trigger uses).
+export function SupportSearch() {
+ const { setOpenSearch } = useSearchContext();
+ const open = () => setOpenSearch(true);
+
+ return (
+
+
+
+ );
+}