diff --git a/apps/claim-db-worker/__tests__/claim.test.tsx b/apps/claim-db-worker/__tests__/claim.test.tsx index 75ed59b..936c7be 100644 --- a/apps/claim-db-worker/__tests__/claim.test.tsx +++ b/apps/claim-db-worker/__tests__/claim.test.tsx @@ -57,7 +57,7 @@ describe("claim page", () => { mockSearchParams.set("projectID", "test-project-123"); render(); - const claimButton = screen.getByText("Claim database"); + const claimButton = screen.getByRole("button", { name: "Claim database" }); expect(claimButton).not.toBeDisabled(); fireEvent.click(claimButton); @@ -102,7 +102,7 @@ describe("claim page", () => { mockSearchParams.set("projectID", "test-project-123"); render(); - const claimButton = screen.getByText("Claim database"); + const claimButton = screen.getByRole("button", { name: "Claim database" }); fireEvent.click(claimButton); await waitFor(() => { @@ -138,7 +138,7 @@ describe("claim page", () => { render(); - const claimButton = screen.getByText("Claim database"); + const claimButton = screen.getByRole("button", { name: "Claim database" }); fireEvent.click(claimButton); diff --git a/apps/claim-db-worker/__tests__/success.test.tsx b/apps/claim-db-worker/__tests__/success.test.tsx index 597b297..2347d61 100644 --- a/apps/claim-db-worker/__tests__/success.test.tsx +++ b/apps/claim-db-worker/__tests__/success.test.tsx @@ -45,7 +45,9 @@ describe("success page", () => { mockSearchParams.set("projectID", "test-project-123"); render(); - expect(screen.getByAltText("Database Success")).toBeInTheDocument(); + expect( + screen.getByAltText("Prismo, the Prisma mascot, smiling") + ).toBeInTheDocument(); }); }); }); diff --git a/apps/claim-db-worker/app/apple-icon.png b/apps/claim-db-worker/app/apple-icon.png new file mode 100644 index 0000000..13b780b Binary files /dev/null and b/apps/claim-db-worker/app/apple-icon.png differ diff --git a/apps/claim-db-worker/app/claim/page.tsx b/apps/claim-db-worker/app/claim/page.tsx index 4b28299..fea7e3a 100644 --- a/apps/claim-db-worker/app/claim/page.tsx +++ b/apps/claim-db-worker/app/claim/page.tsx @@ -1,8 +1,11 @@ "use client"; import { PrismaPostgresLogo } from "@/components/PrismaPostgresLogo"; +import { PrismButton } from "@/components/brand/PrismButton"; +import { Mascot } from "@/components/brand/Brand"; +import { StatePanel } from "@/components/brand/StatePanel"; +import { ArrowRight } from "lucide-react"; import { useSearchParams, useRouter, usePathname } from "next/navigation"; -import Image from "next/image"; import { useEffect, useState, Suspense } from "react"; function ClaimContent() { @@ -56,43 +59,30 @@ function ClaimContent() { }; return ( -
-
- + + + -

- Claim your database - * -

+

+ Claim your database + * +

- - -

- *your database will be deleted 24 hours after creation unless - you claim it -

+ +
-
+ +

+ *Your database will be{" "} + + deleted 24 hours after creation + {" "} + unless you claim it. +

+ ); } diff --git a/apps/claim-db-worker/app/db-unavailable/page.tsx b/apps/claim-db-worker/app/db-unavailable/page.tsx index a86b1d5..a22c346 100644 --- a/apps/claim-db-worker/app/db-unavailable/page.tsx +++ b/apps/claim-db-worker/app/db-unavailable/page.tsx @@ -1,7 +1,9 @@ "use client"; import { cookieUtils } from "@/lib/utils"; -import { Info } from "lucide-react"; +import { Mascot } from "@/components/brand/Brand"; +import { PrismButton } from "@/components/brand/PrismButton"; +import { StatePanel } from "@/components/brand/StatePanel"; const DB_KEY = "temp_db_info"; @@ -11,21 +13,17 @@ export default function DBUnavailablePage() { }; return ( -
-
-

- Database - already claimed or deleted -

-

Please create a new one

- - Create New Database - + + +

+ Database already claimed or deleted +

+

Please create a new one

+
+ + Create new database +
-
+ ); } diff --git a/apps/claim-db-worker/app/error/page.tsx b/apps/claim-db-worker/app/error/page.tsx index 3668306..15dd3b5 100644 --- a/apps/claim-db-worker/app/error/page.tsx +++ b/apps/claim-db-worker/app/error/page.tsx @@ -2,6 +2,8 @@ import { useSearchParams } from "next/navigation"; import { Suspense } from "react"; +import { Mascot } from "@/components/brand/Brand"; +import { StatePanel } from "@/components/brand/StatePanel"; function ErrorContent() { const searchParams = useSearchParams(); @@ -11,57 +13,24 @@ function ErrorContent() { const details = searchParams.get("details"); return ( -
-
-
-
-
- -
-

- {title} -

-
+ + +

+ + Something went wrong +

+

{title}

-

- {message} -

+

+ {message} +

- {details && ( -
-              {details}
-            
- )} -
-
-
+ {details && ( +
+          {details}
+        
+ )} + ); } diff --git a/apps/claim-db-worker/app/favicon.ico b/apps/claim-db-worker/app/favicon.ico index a4cea67..812fb4c 100644 Binary files a/apps/claim-db-worker/app/favicon.ico and b/apps/claim-db-worker/app/favicon.ico differ diff --git a/apps/claim-db-worker/app/globals.css b/apps/claim-db-worker/app/globals.css index dc33b9f..2e4e303 100644 --- a/apps/claim-db-worker/app/globals.css +++ b/apps/claim-db-worker/app/globals.css @@ -1,115 +1,317 @@ @import "tailwindcss"; +/* Brand tokens, ported from the prisma.io site (web/apps/site globals.css and + the /brand-kit page): ink and paper carry the surface, the three prism + colours (cyan / yellow / red) are the only accents. Keep these values in + step with the site rather than tuning them locally. */ :root { - --background: #0a0a0a; - --foreground: #ededed; - --background-dark: #0f1217; - --background-card: #161a20; - --background-code: #13161b; - --background-step: #181b21; - --background-table-header: #1c1f26; - --background-button: #2d3138; - --background-button-hover: #2d3748; - - --brand-surface-main: #161d2b; - --brand-surface-accent: #0d3a38; - --brand-surface-highlight: #71e8df; - - --text-muted: #a0aec0; - --text-dim: #3f4b5b; - --text-accent: #71e8df; - --text-error: #fc8181; - - --border-subtle: #202734; - --border-accent: #71e8df; - - --gradient-start: #71e8df; - --gradient-end: #3f827d; - - --button: #2d3138; - --button-hover: #2d3748; - - --button-blue: #16a394; - --button-blue-hover: #16a395ba; + --background: #ffffff; + --foreground: #3a3b3c; /* brand text primary */ + --ink: #151515; + --paper: #f9faf5; + --muted-foreground: #646567; /* brand text secondary */ + --border: #e5e5e5; + --code: #f6f7f2; } @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); - --font-barlow: var(--font-barlow); - --color-dark: var(--background-dark); - --color-card: var(--background-card); - --color-code: var(--background-code); - --color-step: var(--background-step); - --color-table-header: var(--background-table-header); - --color-button: var(--background-button); + --color-ink: var(--ink); + --color-paper: var(--paper); + --color-muted-foreground: var(--muted-foreground); + --color-border: var(--border); + --color-code: var(--code); - --color-white: var(--text-white); - --color-muted: var(--text-muted); - --color-dim: var(--text-dim); - --color-accent: var(--text-accent); - --color-error: var(--text-error); + --font-sans: var(--font-inter); + --font-heading: var(--font-sora); + /* The site maps mono to Inter; connection strings and commands need real + fixed-width glyphs, so code stays on the system mono stack. */ + --font-mono: + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; - --color-brand-surface-main: var(--brand-surface-main); - --color-brand-surface-accent: var(--brand-surface-accent); - --color-brand-surface-highlight: var(--brand-surface-highlight); + --color-prism-cyan-50: #eeffff; + --color-prism-cyan-100: #dcfdff; + --color-prism-cyan-200: #b3f4f9; + --color-prism-cyan-300: #7be7f0; + --color-prism-cyan-400: #01d7e4; + --color-prism-cyan-500: #00bbcb; + --color-prism-cyan-600: #009aaa; + --color-prism-cyan-700: #007f8d; + --color-prism-cyan-800: #006772; + --color-prism-cyan-900: #00545d; + --color-prism-cyan-950: #00353c; - --color-subtle: var(--border-subtle); - --color-accent: var(--border-accent); + --color-prism-red-50: #ffeeee; + --color-prism-red-100: #ffdfdf; + --color-prism-red-200: #ffc2c4; + --color-prism-red-300: #ff9fa4; + --color-prism-red-400: #ff7682; + --color-prism-red-500: #f34a60; + --color-prism-red-600: #d02344; + --color-prism-red-700: #af0a33; + --color-prism-red-800: #8e0b29; + --color-prism-red-900: #740f22; + --color-prism-red-950: #4b0211; - --color-button: var(--button); - --color-button-hover: var(--button-hover); + --color-prism-yellow-50: #fff7e0; + --color-prism-yellow-100: #fcedc2; + --color-prism-yellow-200: #f8da85; + --color-prism-yellow-300: #f3c306; + --color-prism-yellow-400: #eaa700; + --color-prism-yellow-500: #d48700; + --color-prism-yellow-600: #b46700; + --color-prism-yellow-700: #965100; + --color-prism-yellow-800: #7a4200; + --color-prism-yellow-900: #633700; + --color-prism-yellow-950: #402000; - --color-button-blue: var(--button-blue); - --color-button-blue-hover: var(--button-blue-hover); + --animate-hero-rise: hero-rise 0.9s cubic-bezier(0.22, 1, 0.36, 1) 0.15s both; + @keyframes hero-rise { + from { + opacity: 0; + transform: translateY(16px); + } + to { + opacity: 1; + transform: translateY(0); + } + } +} - --color-gradient-start: var(--gradient-start); - --color-gradient-end: var(--gradient-end); +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground font-sans antialiased; + /* Inter renders optically darker than SF Pro at 400; the site pulls + regular text back to 380 to match. */ + font-weight: 380; + } + button:not(:disabled), + [role="button"]:not([aria-disabled="true"]) { + cursor: pointer; + } } -body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; +/* Brand rule (from the site): headings are always Sora at Medium with natural + tracking, in ink. Unlayered so it beats leftover weight/tracking utilities. */ +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: var(--font-heading); + font-weight: 500; + letter-spacing: normal; +} +@layer base { + h1, + h2, + h3, + h4, + h5, + h6 { + color: var(--ink); + } } -.bg-custom-gradient { - background: - radial-gradient(circle at 30% 30%, rgba(0, 128, 128, 0.2), transparent 50%), - radial-gradient(circle at 70% 70%, rgba(0, 128, 128, 0.2), transparent 50%), - var(--color-dark); +/* The brand spectrum: cyan → yellow → orange → red → magenta. */ +.prism-spectrum { + background-image: linear-gradient( + 85deg, + #01d7e4 0%, + #f3c306 25%, + #f37a03 50%, + #f43531 74%, + #f00e5c 100% + ); +} + +/* Primary CTA — CSS port of the site's PrismButton (components/PrismButton + renders the markup): a black pill with a spectrum glow drifting behind it, + which blooms and fills prismatic on hover. The site version runs on + framer-motion and a WebGL burst; this keeps the look without shipping + either to the worker. The glow and bloom sit in the wrapper, behind the + pill, so only their blurred spill shows around its edge. */ +@keyframes prism-drift { + 0%, + 100% { + transform: translateX(0); + } + 50% { + transform: translateX(-58%); + } +} +.prism-cta { + position: relative; + display: inline-flex; + align-items: center; +} +.prism-cta-glow { + position: absolute; + inset: 0; + overflow: hidden; + border-radius: 9999px; + filter: blur(3.2px); + pointer-events: none; +} +.prism-cta-glow > span { + position: absolute; + inset-block: 0; + left: 0; + width: 240%; + animation: prism-drift 7s ease-in-out infinite; +} +.prism-cta-bloom { + position: absolute; + inset: 0; + border-radius: 9999px; + filter: blur(10px); + opacity: 0; + transition: opacity 0.35s ease; + pointer-events: none; +} +.prism-cta-button { + position: relative; + display: flex; + align-self: stretch; + width: 100%; + align-items: center; + justify-content: center; + overflow: hidden; + border-radius: 9999px; + background: #000; + color: #fff; + font-weight: 600; + line-height: 1.5; + white-space: nowrap; + transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); } -button { - cursor: pointer; +/* The prismatic fill erupting from the centre — a radial reveal standing in + for the site's WebGL burst. */ +.prism-cta-fill { + position: absolute; + inset: 0; + background: radial-gradient( + circle at 50% 50%, + #f00e5c 0%, + #f43531 18%, + #f37a03 34%, + #f3c306 52%, + #01d7e4 78%, + #01d7e4 100% + ); + clip-path: circle(0% at 50% 50%); + transition: clip-path 0.5s cubic-bezier(0.22, 1, 0.36, 1); + pointer-events: none; +} +.prism-cta-label { + position: relative; + z-index: 1; + display: inline-flex; + align-items: center; + gap: 0.5rem; +} +.prism-cta:hover:not(:has(:disabled)) .prism-cta-bloom { + opacity: 0.7; +} +.prism-cta:hover:not(:has(:disabled)) .prism-cta-button { + transform: scale(1.04); +} +.prism-cta:hover:not(:has(:disabled)) .prism-cta-fill { + clip-path: circle(75% at 50% 50%); +} +.prism-cta-button:focus-visible { + outline: 2px solid var(--ink); + outline-offset: 3px; +} +.prism-cta:has(:disabled) { + opacity: 0.5; +} +.prism-cta-button:disabled { + cursor: not-allowed; +} + +/* Secondary pill — thin neutral border that reveals the moving spectrum on + hover (the site's .spectrum-border). */ +@keyframes spectrum-slide { + from { + background-position: 0% 50%; + } + to { + background-position: 200% 50%; + } +} +.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%; + -webkit-mask: + linear-gradient(#fff 0 0) content-box, + linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask: + linear-gradient(#fff 0 0) content-box, + linear-gradient(#fff 0 0); + mask-composite: exclude; + opacity: 0; + transition: opacity 0.5s ease; + pointer-events: none; +} +.spectrum-border:hover::before, +.spectrum-border:focus-visible::before { + opacity: 1; + animation: spectrum-slide 2.5s linear infinite; +} + +@media (prefers-reduced-motion: reduce) { + .prism-cta-button, + .prism-cta-fill, + .prism-cta-bloom { + transition: none; + } + .prism-cta-glow > span, + .spectrum-border:hover::before, + .spectrum-border:focus-visible::before { + animation: none; + } + .prism-cta:hover .prism-cta-button { + transform: none; + } } html { - /* Firefox - Hides track and changes thumb color */ - scrollbar-color: hsla(0, 0%, 46%, 0.5) transparent; + scrollbar-color: rgb(0 0 0 / 0.2) transparent; } -/* Chrome - Hides track and width/height set for vertical/horizontal scrollbars */ ::-webkit-scrollbar { background-color: transparent; width: 7px; height: 7px; } -/* Chrome - Changes thumb color and rounds scrollbar edges, in Chrome any -scrollbar changes disables default styling and rounding so corners appear squared */ ::-webkit-scrollbar-thumb { - background-color: hsla(0, 0%, 46%, 0.5); + background-color: rgb(0 0 0 / 0.2); border-radius: 8px; } -/* Utility class to hide scrollbars */ .scrollbar-hide { - -ms-overflow-style: none; /* Internet Explorer 10+ */ - scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; + scrollbar-width: none; } .scrollbar-hide::-webkit-scrollbar { - display: none; /* Safari and Chrome */ + display: none; } .monaco-editor { diff --git a/apps/claim-db-worker/app/icon.svg b/apps/claim-db-worker/app/icon.svg new file mode 100644 index 0000000..c9d58ef --- /dev/null +++ b/apps/claim-db-worker/app/icon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/apps/claim-db-worker/app/layout.tsx b/apps/claim-db-worker/app/layout.tsx index baffa8b..3567564 100644 --- a/apps/claim-db-worker/app/layout.tsx +++ b/apps/claim-db-worker/app/layout.tsx @@ -1,5 +1,5 @@ import type { Metadata } from "next"; -import { Barlow } from "next/font/google"; +import { Inter, Sora } from "next/font/google"; import Script from "next/script"; import "./globals.css"; import { Navbar } from "../components/Navbar"; @@ -8,10 +8,15 @@ import { DropProvider } from "./contexts/DropContext"; import { Toaster } from "react-hot-toast"; import { PostHogProvider } from "@/components/PostHogProvider"; -const barlow = Barlow({ - weight: ["400", "500", "700", "800", "900"], +// Brand type: Sora for headings, Inter for everything else. +const sora = Sora({ subsets: ["latin"], - variable: "--font-barlow", + variable: "--font-sora", +}); + +const inter = Inter({ + subsets: ["latin"], + variable: "--font-inter", }); export const metadata: Metadata = { @@ -23,7 +28,7 @@ export const metadata: Metadata = { title: "Want a free, instant Prisma Postgres database?", description: "Get a temporary Prisma Postgres database instantly. No account or config needed. Just run npx create-db.", - images: ["/og-image.png"], + images: ["/og-card.png"], type: "website", url: "https://create-db.prisma.io/", }, @@ -32,10 +37,7 @@ export const metadata: Metadata = { title: "Want a free, instant Prisma Postgres database?", description: "Get a temporary Prisma Postgres database instantly. No account or config needed. Just run npx create-db.", - images: ["/og-image.png"], - }, - icons: { - shortcut: "/favicon.png", + images: ["/og-card.png"], }, }; @@ -45,10 +47,12 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - - + +