From ece51b4d2fdeb05db33b8e1e261970b3fe267fa5 Mon Sep 17 00:00:00 2001 From: Alan Daniel Date: Tue, 17 Mar 2026 13:32:45 -0400 Subject: [PATCH] new layout --- .../_components/BuiltWithSupabaseSection.tsx | 92 +++++----- .../www/app/(home)/_components/CTASection.tsx | 4 +- .../(home)/_components/CommunitySectionV2.tsx | 63 ++++--- .../_components/CustomerStoriesSection.tsx | 25 ++- .../_components/DashboardFeaturesSection.tsx | 4 +- .../(home)/_components/FrameworksSection.tsx | 30 ++++ .../_components/FrameworksSectionClient.tsx | 63 ++++++- .../(home)/_components/OpenSourceSection.tsx | 138 ++++++++++----- apps/www/app/layout.tsx | 4 +- apps/www/app/providers.tsx | 4 +- apps/www/components/FontDevtools.tsx | 161 ++++++++++++++++++ apps/www/lib/fonts.ts | 7 + apps/www/pages/_app.tsx | 8 +- apps/www/public/rss.xml | 2 +- apps/www/styles/index.css | 9 +- packages/config/tailwind.config.js | 6 +- packages/ui-patterns/src/TweetCard/index.tsx | 5 - 17 files changed, 477 insertions(+), 148 deletions(-) create mode 100644 apps/www/components/FontDevtools.tsx diff --git a/apps/www/app/(home)/_components/BuiltWithSupabaseSection.tsx b/apps/www/app/(home)/_components/BuiltWithSupabaseSection.tsx index 255fc32fd55..5312aa7ac3d 100644 --- a/apps/www/app/(home)/_components/BuiltWithSupabaseSection.tsx +++ b/apps/www/app/(home)/_components/BuiltWithSupabaseSection.tsx @@ -99,28 +99,26 @@ export function BuiltWithSupabaseSection() { const gridExamples = Examples.slice(2, 6) return ( -
+
{/* Header row */} -
-
-
-

- Kickstart your next project
- with production ready templates -

- - View all examples - -
+
+
+

+ Kickstart your next project
+ with production ready templates +

+ + View all examples +
- {/* Featured row - 2 large cells */} -
-
+
+ {/* Featured row - 2 large cells */} +
{featuredExamples.map((example) => { const logo = EXAMPLE_LOGO[example.title] @@ -167,36 +165,36 @@ export function BuiltWithSupabaseSection() { })}
-
- {/* Grid row - 4 smaller cells */} -
-
- {gridExamples.map((example) => { - const logo = EXAMPLE_LOGO[example.title] - return ( - -
- {logo && ( - - )} -
-

{example.title}

-

- {example.description} -

- - ) - })} + {/* Grid row - 4 smaller cells */} +
+
+ {gridExamples.map((example) => { + const logo = EXAMPLE_LOGO[example.title] + return ( + +
+ {logo && ( + + )} +
+

{example.title}

+

+ {example.description} +

+ + ) + })} +
diff --git a/apps/www/app/(home)/_components/CTASection.tsx b/apps/www/app/(home)/_components/CTASection.tsx index 706294a11a3..79a70992eea 100644 --- a/apps/www/app/(home)/_components/CTASection.tsx +++ b/apps/www/app/(home)/_components/CTASection.tsx @@ -152,8 +152,8 @@ export function CTASection() { const contributors = useContributors() return ( -
- +
+ {/**/}

Build in a weekend, diff --git a/apps/www/app/(home)/_components/CommunitySectionV2.tsx b/apps/www/app/(home)/_components/CommunitySectionV2.tsx index 10bf60d8d9f..50d00cd2f6f 100644 --- a/apps/www/app/(home)/_components/CommunitySectionV2.tsx +++ b/apps/www/app/(home)/_components/CommunitySectionV2.tsx @@ -7,8 +7,18 @@ import { topTweets } from 'shared-data/tweets' import { Button } from 'ui' import { TweetCard } from 'ui-patterns/TweetCard' +const COLS = 5 const TWEETS_PER_PAGE = 15 +// V-shape offsets: outer columns high, center lowest +const COL_OFFSETS = [0, 60, 100, 60, 0] + +function distributeToColumns(tweets: typeof topTweets, cols: number) { + const columns: (typeof topTweets)[] = Array.from({ length: cols }, () => []) + tweets.forEach((tweet, i) => columns[i % cols].push(tweet)) + return columns +} + export function CommunitySectionV2() { const [page, setPage] = useState(0) @@ -24,9 +34,11 @@ export function CommunitySectionV2() { return topTweets[idx] }) + const columns = distributeToColumns(currentTweets, COLS) + return (
-
+

Join the community

@@ -53,29 +65,36 @@ export function CommunitySectionV2() { - {currentTweets.map((tweet, i) => ( - ( +

- - - - + {col.map((tweet, i) => ( + + + + + + ))} +
))} diff --git a/apps/www/app/(home)/_components/CustomerStoriesSection.tsx b/apps/www/app/(home)/_components/CustomerStoriesSection.tsx index df630a65f8a..84eba382a0c 100644 --- a/apps/www/app/(home)/_components/CustomerStoriesSection.tsx +++ b/apps/www/app/(home)/_components/CustomerStoriesSection.tsx @@ -52,8 +52,7 @@ const aiBuilderStories = [ { name: 'Bolt', logo: '/images/logos/publicity/bolt.svg', - description: - 'We store embeddings in a PostgreSQL database, hosted by Supabase, to perform a similarity search to identify', + description: 'We store embeddings in a PostgreSQL database, hosted by Supabase.', slug: 'bolt', gradient: 'linear-gradient(to top, #0a1a3a 0%, #1a3f6f 30%, #2563eb 65%, #60a5fa 100%)', }, @@ -61,7 +60,7 @@ const aiBuilderStories = [ name: 'Lovable', logo: '/images/logos/publicity/lovable.svg', description: - 'We store embeddings in a PostgreSQL database, hosted by Supabase, to perform a similarity search to identify', + 'We store embeddings in a PostgreSQL database. Supabase is a great partner for us as we scale.', slug: 'lovable', gradient: 'linear-gradient(to bottom, #c4b5fd 0%, #a78bfa 20%, #ec4899 50%, #fb7185 75%, #ef4444 100%)', @@ -73,11 +72,11 @@ export function CustomerStoriesSection() { const active = customerStories[activeIdx] return ( -
+
{/* Header row */} -
-
-
+
+
+

How industry leaders
{' '} are building with Supabase @@ -93,8 +92,8 @@ export function CustomerStoriesSection() {

{/* Cards row */} -
-
+
+
{/* AI Builder stories */} -
-
-
+
+
+
-

+

Powering the next wave
of AI builders diff --git a/apps/www/app/(home)/_components/DashboardFeaturesSection.tsx b/apps/www/app/(home)/_components/DashboardFeaturesSection.tsx index fcd00213745..533d068c93d 100644 --- a/apps/www/app/(home)/_components/DashboardFeaturesSection.tsx +++ b/apps/www/app/(home)/_components/DashboardFeaturesSection.tsx @@ -57,7 +57,7 @@ export function DashboardFeaturesSection({

{title}

@@ -105,7 +105,7 @@ export function DashboardFeaturesSection({ {/* Customer story banner */}
-
+
= { + React: [ + { title: 'Todo List', description: 'CRUD app with real-time and RLS.', url: '/docs/guides/getting-started/tutorials/with-react' }, + { title: 'Auth & User Management', description: 'Email, social login, and user profiles.', url: '/docs/guides/auth/quickstarts/react' }, + ], + 'Next.js': [ + { title: 'App Router Quickstart', description: 'Server Components with SSR auth.', url: '/docs/guides/getting-started/quickstarts/nextjs' }, + { title: 'Auth with PKCE', description: 'Secure auth flow with middleware.', url: '/docs/guides/auth/server-side/nextjs' }, + ], + Flutter: [ + { title: 'User Management', description: 'Auth and profile management.', url: '/docs/guides/getting-started/tutorials/with-flutter' }, + { title: 'Deep Linking', description: 'OAuth and magic link for mobile.', url: '/docs/guides/auth/native-mobile-deep-linking' }, + ], + Svelte: [ + { title: 'Todo List', description: 'Server-side auth and real-time data.', url: '/docs/guides/getting-started/tutorials/with-svelte' }, + { title: 'Auth Helpers', description: 'Sessions and protected routes.', url: '/docs/guides/auth/server-side/sveltekit' }, + ], + Vue: [ + { title: 'Todo List', description: 'CRUD with Vue 3 Composition API.', url: '/docs/guides/getting-started/tutorials/with-vue-3' }, + { title: 'Auth with Vue', description: 'Email and OAuth authentication.', url: '/docs/guides/getting-started/quickstarts/vuejs' }, + ], + Nuxt: [ + { title: 'Nuxt Module', description: 'Auto-configured auth and client.', url: '/docs/guides/getting-started/quickstarts/nuxtjs' }, + { title: 'Server Routes', description: 'Service role access from server.', url: '/docs/guides/auth/server-side/nuxt' }, + ], +} + // ── Server component: pre-highlights all code ─────────────────────────────── export async function FrameworksSection() { @@ -204,6 +233,7 @@ export async function FrameworksSection() { docsUrl: fw.docsUrl, darkHtml: hl.codeToHtml(fw.code, { lang: fw.lang, theme: 'supabase-dark' }), lightHtml: hl.codeToHtml(fw.code, { lang: fw.lang, theme: 'supabase-light' }), + examples: frameworkExamples[fw.name] ?? [], })) hl.dispose() diff --git a/apps/www/app/(home)/_components/FrameworksSectionClient.tsx b/apps/www/app/(home)/_components/FrameworksSectionClient.tsx index 76349389daa..a26fd23cc9c 100644 --- a/apps/www/app/(home)/_components/FrameworksSectionClient.tsx +++ b/apps/www/app/(home)/_components/FrameworksSectionClient.tsx @@ -2,7 +2,7 @@ import { AnimatePresence, motion } from 'framer-motion' import Link from 'next/link' -import { useState } from 'react' +import { useRef, useState } from 'react' import { cn } from 'ui' type Framework = { @@ -11,18 +11,26 @@ type Framework = { docsUrl: string darkHtml: string lightHtml: string + examples: { title: string; description: string; url: string }[] } export function FrameworksSectionClient({ frameworks }: { frameworks: Framework[] }) { const [activeIdx, setActiveIdx] = useState(0) + const prevIdx = useRef(0) + const direction = activeIdx > prevIdx.current ? -1 : 1 const active = frameworks[activeIdx] + const handleTabChange = (index: number) => { + prevIdx.current = activeIdx + setActiveIdx(index) + } + return ( -
-
+
+
- {/* Left: title */} -
+ {/* Left: title + example cards */} +
Use Supabase with{' '} @@ -39,16 +47,57 @@ export function FrameworksSectionClient({ frameworks }: { frameworks: Framework[
+ + {active.examples.length > 0 && ( + + ({ opacity: 0, x: -28 * d, filter: 'blur(2px)' }), + center: { + opacity: 1, + x: 0, + filter: 'blur(0px)', + transition: { duration: 0.2, delay: 0.05 }, + }, + exit: (d: number) => ({ + opacity: 0, + x: 28 * d, + filter: 'blur(2px)', + transition: { duration: 0.1 }, + }), + }} + className="flex flex-col gap-2.5 max-w-sm" + > + {active.examples.map((example) => ( + + {example.title} + + {example.description} + + + ))} + + + )}
{/* Right: icon tabs + code */} -
+
{/* 6-col icon row */}
{frameworks.map((framework, index) => (
- -
- -
- - GitHub Stars - - - {stars} - - - Top 100 GitHub repos - -
diff --git a/apps/www/app/layout.tsx b/apps/www/app/layout.tsx index bd90908ee0c..98990119e37 100644 --- a/apps/www/app/layout.tsx +++ b/apps/www/app/layout.tsx @@ -3,7 +3,7 @@ import 'config/code-hike.scss' import '../styles/index.css' import { APP_NAME, DEFAULT_META_DESCRIPTION } from '~/lib/constants' -import { ktfPrima } from '~/lib/fonts' +import { geistMono, ktfPrima } from '~/lib/fonts' import { Agentation } from 'agentation' import { Metadata } from 'next' import type { Viewport } from 'next' @@ -47,7 +47,7 @@ export const viewport: Viewport = { export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - + {children} {process.env.NODE_ENV === 'development' && } diff --git a/apps/www/app/providers.tsx b/apps/www/app/providers.tsx index 663b4d9c206..b19d8159f48 100644 --- a/apps/www/app/providers.tsx +++ b/apps/www/app/providers.tsx @@ -11,8 +11,9 @@ import { useThemeSandbox, } from 'common' import { WwwCommandMenu } from 'components/CommandMenu' +import { FontDevtools } from 'components/FontDevtools' import { DevToolbar, DevToolbarProvider } from 'dev-tools' -import { API_URL } from 'lib/constants' +import { API_URL, IS_PROD } from 'lib/constants' import { themes, TooltipProvider, SonnerToaster } from 'ui' import { CommandProvider } from 'ui-patterns/CommandMenu' import { useConsentToast } from 'ui-patterns/consent' @@ -44,6 +45,7 @@ function Providers({ children }: { children: React.ReactNode }) { enabled={IS_PLATFORM} /> + {!IS_PROD && } diff --git a/apps/www/components/FontDevtools.tsx b/apps/www/components/FontDevtools.tsx new file mode 100644 index 00000000000..aa9222f5c63 --- /dev/null +++ b/apps/www/components/FontDevtools.tsx @@ -0,0 +1,161 @@ +'use client' + +import { useCallback, useEffect, useState } from 'react' +import { + Button, + Label_Shadcn_ as Label, + Popover_Shadcn_ as Popover, + PopoverContent_Shadcn_ as PopoverContent, + PopoverTrigger_Shadcn_ as PopoverTrigger, + Select_Shadcn_ as Select, + SelectContent_Shadcn_ as SelectContent, + SelectItem_Shadcn_ as SelectItem, + SelectTrigger_Shadcn_ as SelectTrigger, + SelectValue_Shadcn_ as SelectValue, +} from 'ui' + +const FONT_OPTIONS = { + sans: [ + { label: 'KTF Prima', value: 'var(--font-ktf-prima), ui-sans-serif, system-ui, sans-serif' }, + { label: 'System UI', value: 'ui-sans-serif, system-ui, sans-serif' }, + { label: 'Inter', value: 'Inter, ui-sans-serif, system-ui, sans-serif' }, + { + label: 'Helvetica Neue', + value: '"Helvetica Neue", Helvetica, Arial, sans-serif', + }, + ], + mono: [ + { label: 'Geist Mono', value: 'var(--font-geist-mono), ui-monospace, Menlo, monospace' }, + { label: 'System Mono', value: 'ui-monospace, Menlo, Monaco, monospace' }, + { + label: 'JetBrains Mono', + value: '"JetBrains Mono", ui-monospace, monospace', + }, + ], +} as const + +const STORAGE_KEY = 'www-font-devtools' +const STYLE_ID = 'font-devtools-override' + +function getStored(): { sans?: string; mono?: string } { + try { + return JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}') + } catch { + return {} + } +} + +function applyOverrides(sans?: string, mono?: string) { + let el = document.getElementById(STYLE_ID) as HTMLStyleElement | null + const rules: string[] = [] + + if (sans) { + rules.push(`:root { --font-sans: ${sans} !important; }`) + } + if (mono) { + rules.push(`:root { --font-mono: ${mono} !important; }`) + } + + if (rules.length === 0) { + el?.remove() + return + } + + if (!el) { + el = document.createElement('style') + el.id = STYLE_ID + document.head.appendChild(el) + } + el.textContent = rules.join('\n') +} + +export function FontDevtools() { + const [sans, setSans] = useState('') + const [mono, setMono] = useState('') + + useEffect(() => { + const stored = getStored() + if (stored.sans) setSans(stored.sans) + if (stored.mono) setMono(stored.mono) + if (stored.sans || stored.mono) { + applyOverrides(stored.sans, stored.mono) + } + }, []) + + const update = useCallback( + (type: 'sans' | 'mono', value: string) => { + const nextSans = type === 'sans' ? value : sans + const nextMono = type === 'mono' ? value : mono + setSans(nextSans) + setMono(nextMono) + + applyOverrides(nextSans || undefined, nextMono || undefined) + + const stored: Record = {} + if (nextSans) stored.sans = nextSans + if (nextMono) stored.mono = nextMono + localStorage.setItem(STORAGE_KEY, JSON.stringify(stored)) + }, + [sans, mono] + ) + + const reset = useCallback(() => { + setSans('') + setMono('') + applyOverrides() + localStorage.removeItem(STORAGE_KEY) + }, []) + + return ( +
+ + + + + +

+ Font Devtools +

+ +
+ + +
+ +
+ + +
+ + +
+
+
+ ) +} diff --git a/apps/www/lib/fonts.ts b/apps/www/lib/fonts.ts index a2104e5292e..761a4161047 100644 --- a/apps/www/lib/fonts.ts +++ b/apps/www/lib/fonts.ts @@ -1,5 +1,12 @@ +import { Geist_Mono } from 'next/font/google' import localFont from 'next/font/local' +export const geistMono = Geist_Mono({ + subsets: ['latin'], + variable: '--font-geist-mono', + display: 'swap', +}) + export const ktfPrima = localFont({ src: [ { diff --git a/apps/www/pages/_app.tsx b/apps/www/pages/_app.tsx index 30c4b7aa58b..003c24e61d5 100644 --- a/apps/www/pages/_app.tsx +++ b/apps/www/pages/_app.tsx @@ -15,7 +15,7 @@ import { DevToolbar, DevToolbarProvider } from 'dev-tools' import { DefaultSeo } from 'next-seo' import type { AppProps } from 'next/app' import Head from 'next/head' -import { ktfPrima } from '~/lib/fonts' +import { geistMono, ktfPrima } from '~/lib/fonts' import { useRouter } from 'next/router' import { SonnerToaster, themes, TooltipProvider } from 'ui' import { CommandProvider } from 'ui-patterns/CommandMenu' @@ -26,7 +26,8 @@ import MetaFaviconsPagesRouter, { DEFAULT_FAVICON_THEME_COLOR, } from 'common/MetaFavicons/pages-router' import { WwwCommandMenu } from '~/components/CommandMenu' -import { API_URL, APP_NAME, DEFAULT_META_DESCRIPTION } from '~/lib/constants' +import { FontDevtools } from '~/components/FontDevtools' +import { API_URL, APP_NAME, DEFAULT_META_DESCRIPTION, IS_PROD } from '~/lib/constants' import useDarkLaunchWeeks from '../hooks/useDarkLaunchWeeks' import { useWwwCommandMenuTelemetry } from '../hooks/useWwwCommandMenuTelemetry' @@ -54,7 +55,7 @@ export default function App({ Component, pageProps }: AppProps) { } return ( -
+
@@ -110,6 +111,7 @@ export default function App({ Component, pageProps }: AppProps) { enabled={IS_PLATFORM} /> + {!IS_PROD && } diff --git a/apps/www/public/rss.xml b/apps/www/public/rss.xml index b30fc5ae253..18f8f9cccd0 100644 --- a/apps/www/public/rss.xml +++ b/apps/www/public/rss.xml @@ -2766,4 +2766,4 @@ - + \ No newline at end of file diff --git a/apps/www/styles/index.css b/apps/www/styles/index.css index c7a65d64565..8c2b8f3445c 100644 --- a/apps/www/styles/index.css +++ b/apps/www/styles/index.css @@ -24,6 +24,8 @@ } :root { + --font-sans: var(--font-ktf-prima), ui-sans-serif, system-ui, sans-serif; + --font-mono: var(--font-geist-mono), ui-monospace, Menlo, monospace; --heading-tracking: -0.02em; --body-tracking: -0.01em; --heading-leading: 1.1; @@ -36,7 +38,6 @@ html { body { @apply bg text-foreground; - font-family: var(--font-ktf-prima), ui-sans-serif, system-ui, sans-serif; letter-spacing: var(--body-tracking); line-height: var(--body-leading); -webkit-font-smoothing: antialiased; @@ -44,11 +45,15 @@ body { } h1, h2, h3, h4, h5, h6 { - font-family: var(--font-ktf-prima), ui-sans-serif, system-ui, sans-serif; letter-spacing: var(--heading-tracking); line-height: var(--heading-leading); } +pre, code, kbd, samp, +pre *, code *, kbd *, samp * { + font-family: var(--font-mono) !important; +} + /* h1, h2, h3, diff --git a/packages/config/tailwind.config.js b/packages/config/tailwind.config.js index c9469032a5f..cb38d1c94e3 100644 --- a/packages/config/tailwind.config.js +++ b/packages/config/tailwind.config.js @@ -273,7 +273,7 @@ const uiConfig = ui({ }, figcaption: { color: 'hsl(var(--foreground-muted))', - fontFamily: 'Office Code Pro, monospace', + fontFamily: 'var(--font-mono)', }, 'figure.quote-figure p:first-child': { marginTop: '0 !important', @@ -374,8 +374,8 @@ const uiConfig = ui({ xs: '480px', }, fontFamily: { - sans: 'var(--font-ktf-prima), ui-sans-serif, system-ui, sans-serif', - mono: 'var(--font-source-code-pro), Source Code Pro, Office Code Pro, Menlo, monospace', + sans: 'var(--font-sans)', + mono: 'var(--font-mono)', }, // shadcn defaults START diff --git a/packages/ui-patterns/src/TweetCard/index.tsx b/packages/ui-patterns/src/TweetCard/index.tsx index bcaa26c49a1..cf10646fd2d 100644 --- a/packages/ui-patterns/src/TweetCard/index.tsx +++ b/packages/ui-patterns/src/TweetCard/index.tsx @@ -34,11 +34,6 @@ export function TweetCard(props: TweetCard) {
)}

{props.handle}

-
- -