From a54a2b69e16ea378416a747ac02204dc462c06a1 Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Wed, 1 Jul 2026 14:13:15 +0200 Subject: [PATCH] www: homepage (#47228) - Update homepage based on new designs from #43455. - Also improve a11y and semantics of homepage as per @czenko's [review](https://github.com/supabase/supabase/pull/43455#pullrequestreview-4548881985) PR breakdown of #43455 Related: #47226 #47227 #47236 --- apps/www/app/(home)/ThemeForcer.tsx | 8 + .../_components/BuiltWithSupabaseSection.tsx | 200 ++++++ .../www/app/(home)/_components/CTASection.tsx | 48 ++ .../(home)/_components/CommunitySectionV2.tsx | 113 ++++ .../_components/CustomerStoriesSection.tsx | 340 ++++++++++ .../_components/DashboardFeaturesSection.tsx | 144 +++++ .../(home)/_components/FrameworksSection.tsx | 350 +++++++++++ .../_components/FrameworksSectionClient.tsx | 140 +++++ apps/www/app/(home)/_components/Hero.tsx | 56 ++ .../app/(home)/_components/HomeContent.tsx | 54 ++ apps/www/app/(home)/_components/LogosGrid.tsx | 119 ++++ .../(home)/_components/OpenSourceSection.tsx | 211 +++++++ .../_components/logos/FrameworkLogos.tsx | 277 ++++++++ .../_components/logos/PublicityLogos.tsx | 591 ++++++++++++++++++ apps/www/app/(home)/layout.tsx | 17 + apps/www/app/(home)/page.tsx | 6 + apps/www/app/blog/BlogHero.tsx | 4 +- apps/www/components/Hero/Hero.tsx | 67 -- apps/www/components/Nav/GitHubButton.tsx | 34 +- apps/www/components/OpenSourceSection.tsx | 58 -- apps/www/components/Products/ProductCard.tsx | 9 +- apps/www/data/home/content.tsx | 13 +- apps/www/pages/index.tsx | 48 -- .../images/customers/logos/chatbase-icon.svg | 5 + .../images/customers/logos/e2b-icon.svg | 3 + .../images/customers/logos/firecrawl-icon.svg | 3 + .../images/customers/logos/hyper-icon.svg | 6 + .../customers/logos/markprompt-icon.svg | 6 + .../images/customers/logos/mobbin-icon.svg | 1 + .../images/customers/logos/rally-icon.svg | 3 + .../images/customers/logos/resend-icon.svg | 3 + 31 files changed, 2734 insertions(+), 203 deletions(-) create mode 100644 apps/www/app/(home)/ThemeForcer.tsx create mode 100644 apps/www/app/(home)/_components/BuiltWithSupabaseSection.tsx create mode 100644 apps/www/app/(home)/_components/CTASection.tsx create mode 100644 apps/www/app/(home)/_components/CommunitySectionV2.tsx create mode 100644 apps/www/app/(home)/_components/CustomerStoriesSection.tsx create mode 100644 apps/www/app/(home)/_components/DashboardFeaturesSection.tsx create mode 100644 apps/www/app/(home)/_components/FrameworksSection.tsx create mode 100644 apps/www/app/(home)/_components/FrameworksSectionClient.tsx create mode 100644 apps/www/app/(home)/_components/Hero.tsx create mode 100644 apps/www/app/(home)/_components/HomeContent.tsx create mode 100644 apps/www/app/(home)/_components/LogosGrid.tsx create mode 100644 apps/www/app/(home)/_components/OpenSourceSection.tsx create mode 100644 apps/www/app/(home)/_components/logos/FrameworkLogos.tsx create mode 100644 apps/www/app/(home)/_components/logos/PublicityLogos.tsx create mode 100644 apps/www/app/(home)/layout.tsx create mode 100644 apps/www/app/(home)/page.tsx delete mode 100644 apps/www/components/Hero/Hero.tsx delete mode 100644 apps/www/components/OpenSourceSection.tsx delete mode 100644 apps/www/pages/index.tsx create mode 100644 apps/www/public/images/customers/logos/chatbase-icon.svg create mode 100644 apps/www/public/images/customers/logos/e2b-icon.svg create mode 100644 apps/www/public/images/customers/logos/firecrawl-icon.svg create mode 100644 apps/www/public/images/customers/logos/hyper-icon.svg create mode 100644 apps/www/public/images/customers/logos/markprompt-icon.svg create mode 100644 apps/www/public/images/customers/logos/mobbin-icon.svg create mode 100644 apps/www/public/images/customers/logos/rally-icon.svg create mode 100644 apps/www/public/images/customers/logos/resend-icon.svg diff --git a/apps/www/app/(home)/ThemeForcer.tsx b/apps/www/app/(home)/ThemeForcer.tsx new file mode 100644 index 00000000000..51679284972 --- /dev/null +++ b/apps/www/app/(home)/ThemeForcer.tsx @@ -0,0 +1,8 @@ +'use client' + +import { useForceDeepDark } from 'lib/theme.utils' + +export function ThemeForcer() { + useForceDeepDark() + return null +} diff --git a/apps/www/app/(home)/_components/BuiltWithSupabaseSection.tsx b/apps/www/app/(home)/_components/BuiltWithSupabaseSection.tsx new file mode 100644 index 00000000000..e9eb6e8f9c6 --- /dev/null +++ b/apps/www/app/(home)/_components/BuiltWithSupabaseSection.tsx @@ -0,0 +1,200 @@ +import Examples from 'data/Examples' +import Link from 'next/link' +import { cn } from 'ui' + +import SectionContainer from '@/components/Layouts/SectionContainer' + +const EXAMPLE_LOGO: Record = { + 'Stripe Subscriptions Starter': { src: '/images/logos/frameworks/stripe.svg' }, + 'Next.js Starter': { src: '/images/logos/frameworks/nextjs.svg', invert: true }, + 'AI Chatbot': { src: '/images/logos/frameworks/openai.svg', invert: true }, + 'LangChain + Next.js Starter': { src: '/images/logos/frameworks/langchain.svg' }, + 'Flutter User Management': { src: '/images/logos/frameworks/flutter.svg' }, + 'Expo React Native Starter': { src: '/images/logos/frameworks/expo.svg', invert: true }, +} + +function SkeletonBar({ className }: { className?: string }) { + return
+} + +function StripeSkeleton() { + return ( +
+
+
+
+ + +
+ + +
+ +
+
+ + +
+ + +
+ +
+
+ + +
+ + +
+
+
+ ) +} + +function NextjsSkeleton() { + return ( +
+ {/* Sidebar */} +
+ +
+ + + + +
+
+ {/* Main */} +
+ + {/* 3x4 data grid */} +
+ {Array.from({ length: 12 }).map((_, i) => ( +
+ ))} +
+
+
+ ) +} + +const SKELETON_CONTENT: Record React.JSX.Element> = { + 'Stripe Subscriptions Starter': StripeSkeleton, + 'Next.js Starter': NextjsSkeleton, +} + +export function BuiltWithSupabaseSection() { + const featuredExamples = Examples.slice(0, 2) + const gridExamples = Examples.slice(2, 6) + + return ( + + {/* Header row */} +
+

+ Kickstart your next project
+ with production ready templates +

+ + View all examples + +
+ +
+ {/* Featured row - 2 large cells */} +
+ {featuredExamples.map((example) => { + const logo = EXAMPLE_LOGO[example.title] + const Skeleton = SKELETON_CONTENT[example.title] + return ( + +
+ {logo && ( + + )} +
+

{example.title}

+

+ {example.description} +

+
+
+
+ {/* Browser frame */} +
+ {/* Title bar */} +
+ + + +
+ {/* Skeleton content */} +
+ {Skeleton ? : null} +
+
+
+ + ) + })} +
+ + {/* 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 new file mode 100644 index 00000000000..346b93b89f2 --- /dev/null +++ b/apps/www/app/(home)/_components/CTASection.tsx @@ -0,0 +1,48 @@ +'use client' + +import { useSendTelemetryEvent } from '~/lib/telemetry' +import Link from 'next/link' +import { Button } from 'ui' + +export function CTASection() { + const sendTelemetryEvent = useSendTelemetryEvent() + + return ( +
+
+

+ Build in a weekend, + scale to millions +

+
+ + +
+
+
+ ) +} diff --git a/apps/www/app/(home)/_components/CommunitySectionV2.tsx b/apps/www/app/(home)/_components/CommunitySectionV2.tsx new file mode 100644 index 00000000000..1025a413d27 --- /dev/null +++ b/apps/www/app/(home)/_components/CommunitySectionV2.tsx @@ -0,0 +1,113 @@ +'use client' + +import { AnimatePresence, motion } from 'framer-motion' +import Link from 'next/link' +import { useEffect, useState } from 'react' +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) + + useEffect(() => { + if (topTweets.length === 0) return + const interval = setInterval(() => { + setPage((p) => p + 1) + }, 10000) + return () => clearInterval(interval) + }, []) + + if (topTweets.length === 0) return null + + const currentTweets = Array.from({ length: TWEETS_PER_PAGE }, (_, i) => { + const idx = (page * TWEETS_PER_PAGE + i) % topTweets.length + return topTweets[idx] + }) + + const columns = distributeToColumns(currentTweets, COLS) + + return ( +
+
+
+

Join the community

+

+ Discover what our community has to say about their Supabase experience. +

+ +
+ + {/* Decorative social proof — hidden from a11y tree, links not keyboard-reachable to prevent focus trap */} + +
+
+ ) +} diff --git a/apps/www/app/(home)/_components/CustomerStoriesSection.tsx b/apps/www/app/(home)/_components/CustomerStoriesSection.tsx new file mode 100644 index 00000000000..380ae364eb3 --- /dev/null +++ b/apps/www/app/(home)/_components/CustomerStoriesSection.tsx @@ -0,0 +1,340 @@ +'use client' + +import { motion } from 'framer-motion' +import Link from 'next/link' +import { useState } from 'react' +import { cn } from 'ui' + +import SectionContainer from '@/components/Layouts/SectionContainer' + +const customerStories = [ + { + name: 'Firecrawl', + logo: '/images/customers/logos/firecrawl.png', + icon: '/images/customers/logos/firecrawl-icon.svg', + tagline: 'Switched from Pinecone to Supabase Vector to boost efficiency and accuracy.', + quote: + "We looked at the alternatives and chose Supabase because it's open source, it's simpler, and for all the ways we need to use it, Supabase has been just as performant — if not more performant — than the other vector databases.", + author: 'Caleb Peffer, CEO, Firecrawl', + authorImg: '/images/blog/avatars/caleb-peffer.jpg', + slug: 'firecrawl', + rawIcon: true, + iconFilter: 'brightness(0) invert(1)', + bgColor: 'color(display-p3 0.980392 0.364706 0.098039)', + bgGradient: + 'linear-gradient(to bottom left, color(display-p3 0.980392 0.364706 0.098039 / 1) 0%, color(display-p3 0.980392 0.364706 0.098039 / 0.9) 100%)', + dimBgColor: 'color(display-p3 0.980392 0.364706 0.098039 / 0.9)', + textColor: 'light' as 'light' | 'dark', + }, + { + name: 'Rally', + logo: '/images/customers/logos/rally.png', + icon: '/images/customers/logos/rally-icon.svg', + rawIcon: true, + tagline: 'From first line of code to fully licensed fintech in three months.', + quote: + "We could not have built this company without Supabase. If I had to go and build all these components myself, we wouldn't even have launched.", + author: 'Thiago Peres, Founder & CTO, Rally', + authorImg: '/images/blog/avatars/thiago-peres-rally.jpeg', + slug: 'rally', + bgColor: 'color(display-p3 0.275 0.306 0.8)', + bgGradient: + 'linear-gradient(to bottom left, color(display-p3 0.275 0.306 0.8 / 1) 0%, color(display-p3 0.118 0.176 0.769 / 1) 100%)', + dimBgColor: 'color(display-p3 0.118 0.176 0.769 / 1)', + textColor: 'light' as 'light' | 'dark', + }, + { + name: 'Hyper', + logo: '/images/customers/logos/hyper.svg', + icon: '/images/customers/logos/hyper-icon.svg', + tagline: 'An AI-native marketing platform with agents that operate across the entire workflow.', + quote: + 'I will get on a podcast and talk about how much I love Supabase. With Supabase we can move fast and build things that delight our customers without having to worry about infrastructure.', + author: 'Elliot Fleck, Co-founder, Hyper', + authorImg: '/images/blog/avatars/elliot-fleck-hyper.jpeg', + slug: 'hyper', + bgColor: '#222222', + bgGradient: 'linear-gradient(to bottom left, #2a2a2a 0%, #181818 100%)', + dimBgColor: '#1e1e1e', + textColor: 'light' as 'light' | 'dark', + }, + { + name: 'E2B', + logo: '/images/customers/logos/e2b.png', + icon: '/images/customers/logos/e2b-icon.svg', + tagline: 'Secure, scalable execution of AI-generated code in the cloud.', + quote: + "Supabase empowers us to focus on innovation rather than infrastructure. It's the backbone of our platform, enabling scalability and seamless developer experiences.", + author: 'Vasek Mlejnsky, CEO, E2B', + authorImg: '/images/blog/avatars/vasek-mlejnsky.jpg', + slug: 'e2b', + rawIcon: true, + iconFilter: 'brightness(0) invert(1)', + bgColor: 'color(display-p3 1 0.533 0)', + bgGradient: + 'linear-gradient(to bottom left, color(display-p3 1 0.533 0 / 1) 0%, color(display-p3 0.7 0.373 0 / 1) 100%)', + dimBgColor: 'color(display-p3 0.7 0.373 0 / 1)', + textColor: 'light' as 'light' | 'dark', + }, + { + name: 'Mobbin', + logo: '/images/customers/logos/mobbin.png', + icon: '/images/customers/logos/mobbin-icon.svg', + tagline: 'Migrated 200,000 users from Firebase for a better authentication experience.', + quote: + 'Migrating to Supabase meant that we could instantly fix our Auth problems and save money. Just being on Supabase alone gives us confidence we can deliver on whatever users need in the future.', + author: 'Jian Jie Liau, Co-founder & CTO, Mobbin', + authorImg: '/images/blog/avatars/jian-mobbin.jpg', + slug: 'mobbin', + bgColor: '#000000', + bgGradient: 'linear-gradient(to bottom left, #0a0a0a 0%, #000000 100%)', + dimBgColor: '#080808', + textColor: 'light' as 'light' | 'dark', + }, +] + +function IconChip({ + story, + size = 'md', +}: { + story: (typeof customerStories)[0] + size?: 'sm' | 'md' +}) { + const isLight = story.textColor === 'light' + const s = story as any + const filter = + s.iconFilter ?? (s.rawIcon ? undefined : isLight ? 'brightness(0) invert(1)' : 'brightness(0)') + return ( + {story.name} + ) +} + +// Change INACTIVE_PAD to adjust closed column width automatically +const INACTIVE_PAD = 22 // px padding on each side of icon in closed columns +const ACTIVE_PAD = 32 // px padding on each side when card is open +const ICON_PX = 32 // w-8 = 32px +const INACTIVE_COL_WIDTH = INACTIVE_PAD * 2 + ICON_PX // = 76px + +export function CustomerStoriesSection() { + const [activeIdx, setActiveIdx] = useState(0) + const active = customerStories[activeIdx] + + return ( +
+ {/* Header row */} + +
+

+ How industry leaders
{' '} + are building with Supabase +

+ + More customer stories + +
+
+ + {/* Cards row */} + + {/* Mobile: stacked cards */} +
+ {customerStories.map((story, index) => { + const isActive = index === activeIdx + const isDark = story.textColor === 'dark' + return ( +
setActiveIdx(index)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + setActiveIdx(index) + } + }} + className="text-left rounded-lg p-5 flex flex-col gap-4 overflow-hidden transition-opacity cursor-pointer" + style={{ background: isActive ? story.bgGradient : story.dimBgColor }} + > + + {isActive && ( +
+
+

+ {story.name} +

+

+ {story.tagline} +

+
+

+ {story.quote} +

+
+ {story.author} +

+ {story.author} +

+
+ + Read the story → + +
+ )} +
+ ) + })} +
+ + {/* Desktop: animated accordion grid */} +
(i === activeIdx ? '1fr' : `${INACTIVE_COL_WIDTH}px`)) + .join(' '), + }} + > + {customerStories.map((story, index) => { + const isActive = index === activeIdx + const isDark = story.textColor === 'dark' + return ( + setActiveIdx(index)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + setActiveIdx(index) + } + }} + className="text-left flex flex-col items-start gap-8 overflow-hidden cursor-pointer" + style={{ + background: story.bgGradient, + borderRadius: 8, + padding: isActive ? ACTIVE_PAD : `${ACTIVE_PAD}px ${INACTIVE_PAD}px`, + boxShadow: isDark + ? 'inset 0 0 0 1px rgba(0,0,0,0.07)' + : 'inset 0 0 0 1px rgba(255,255,255,0.12)', + }} + transition={{ type: 'spring', duration: 0.4, bounce: 0 }} + > + + + + + + {/* Top: company name + tagline */} + + + {story.name} + + + {story.tagline} + + + + {/* Bottom: quote + author */} + + + {story.quote} + + + {story.author} + + {story.author} + + + + Read the story → + + + + + ) + })} +
+
+
+ ) +} diff --git a/apps/www/app/(home)/_components/DashboardFeaturesSection.tsx b/apps/www/app/(home)/_components/DashboardFeaturesSection.tsx new file mode 100644 index 00000000000..0bafcc2e08e --- /dev/null +++ b/apps/www/app/(home)/_components/DashboardFeaturesSection.tsx @@ -0,0 +1,144 @@ +'use client' + +import BrowserFrame from '~/components/BrowserFrame' +import { AnimatePresence, motion, useInView } from 'framer-motion' +import { useTheme } from 'next-themes' +import Link from 'next/link' +import React, { useRef, useState } from 'react' +import { cn } from 'ui' + +import SectionContainer from '@/components/Layouts/SectionContainer' + +const dashboardStories = [ + { + logo: '/images/customers/logos/hyper-icon.svg', + name: 'Hyper', + quote: + 'We store embeddings in a PostgreSQL database, hosted by Supabase, to perform a similarity search to identify the most relevant sections within the MDN.', + author: 'Hermina Condei, Director at MDN, Mozilla', + slug: 'mozilla-mdn', + }, + { + logo: '/images/customers/logos/chatbase-icon.svg', + name: 'Chatbase', + quote: + 'The SQL Editor in Supabase is my favorite feature — it lets us run complex queries, debug issues, and iterate on our schema without ever leaving the browser.', + author: 'Yasser Elsaid, Founder of Chatbase', + slug: 'chatbase', + }, + { + logo: '/images/customers/logos/markprompt-icon.svg', + name: 'Markprompt', + quote: + 'Row Level Security in Supabase allows us to set granular access policies directly in Postgres, so our multi-tenant architecture stays secure by default.', + author: 'Michael Fester, Co-Founder of Markprompt', + slug: 'markprompt', + }, +] + +export function DashboardFeaturesSection({ + title, + tabs, +}: { + title: React.ReactNode + tabs: { + label: string + panel: React.FC<{ isDark: boolean }> + highlights: { label: string; link?: string }[] + }[] +}) { + const { resolvedTheme } = useTheme() + const [activeTabIdx, setActiveTabIdx] = useState(0) + const sectionRef = useRef(null) + const isInView = useInView(sectionRef, { once: true }) + + if (tabs.length === 0) return null + + const Panel = tabs[activeTabIdx].panel + const story = dashboardStories[activeTabIdx] ?? dashboardStories[0] + + return ( + <> + +
+

{title}

+
+ {tabs.map((tab, index) => ( + + ))} +
+
+ + + {isInView && ( + + + + + + )} + +
+ + {/* Customer story banner */} +
+ + + + {story.name} +
+

+ {story.name} + {' — '} + {story.quote} {story.author} +

+
+ + + View more about {story.name} + +
+
+
+
+ + ) +} diff --git a/apps/www/app/(home)/_components/FrameworksSection.tsx b/apps/www/app/(home)/_components/FrameworksSection.tsx new file mode 100644 index 00000000000..a4dc4668478 --- /dev/null +++ b/apps/www/app/(home)/_components/FrameworksSection.tsx @@ -0,0 +1,350 @@ +import { createHighlighter, type ThemeRegistration } from 'shiki' + +import { FrameworksSectionClient } from './FrameworksSectionClient' + +// ── Shiki themes ──────────────────────────────────────────────────────────── + +const supabaseDark: ThemeRegistration = { + name: 'supabase-dark', + type: 'dark', + colors: { + 'editor.background': '#00000000', + 'editor.foreground': '#ffffff', + }, + tokenColors: [ + { + scope: ['keyword', 'storage', 'storage.type', 'storage.modifier'], + settings: { foreground: '#bda4ff' }, + }, + { + scope: [ + 'entity.name.function', + 'support.function', + 'entity.name.tag', + 'support.class.component', + ], + settings: { foreground: '#3ecf8e' }, + }, + { + scope: ['constant', 'variable.other.constant', 'support.constant'], + settings: { foreground: '#3ecf8e' }, + }, + { + scope: [ + 'variable.other.property', + 'support.type.property-name', + 'meta.object-literal.key', + 'entity.other.attribute-name', + ], + settings: { foreground: '#3ecf8e' }, + }, + { scope: ['string', 'string.quoted'], settings: { foreground: '#ffcda1' } }, + { scope: ['comment', 'punctuation.definition.comment'], settings: { foreground: '#7e7e7e' } }, + { scope: ['variable.parameter'], settings: { foreground: '#ffffff' } }, + { scope: ['punctuation'], settings: { foreground: '#ffffff' } }, + { scope: ['constant.numeric'], settings: { foreground: '#ededed' } }, + { scope: ['markup.underline.link'], settings: { foreground: '#ffffff' } }, + { scope: ['markup.inserted'], settings: { foreground: '#3ecf8e' } }, + { scope: ['markup.deleted'], settings: { foreground: '#F06A50' } }, + ], +} + +const supabaseLight: ThemeRegistration = { + name: 'supabase-light', + type: 'light', + colors: { + 'editor.background': '#00000000', + 'editor.foreground': '#525252', + }, + tokenColors: [ + { + scope: ['keyword', 'storage', 'storage.type', 'storage.modifier'], + settings: { foreground: '#6b35dc' }, + }, + { + scope: [ + 'entity.name.function', + 'support.function', + 'entity.name.tag', + 'support.class.component', + ], + settings: { foreground: '#15593b' }, + }, + { + scope: ['constant', 'variable.other.constant', 'support.constant'], + settings: { foreground: '#15593b' }, + }, + { + scope: [ + 'variable.other.property', + 'support.type.property-name', + 'meta.object-literal.key', + 'entity.other.attribute-name', + ], + settings: { foreground: '#15593b' }, + }, + { scope: ['string', 'string.quoted'], settings: { foreground: '#b45309' } }, + { scope: ['comment', 'punctuation.definition.comment'], settings: { foreground: '#7e7e7e' } }, + { scope: ['variable.parameter'], settings: { foreground: '#525252' } }, + { scope: ['punctuation'], settings: { foreground: '#a0a0a0' } }, + { scope: ['constant.numeric'], settings: { foreground: '#525252' } }, + { scope: ['markup.underline.link'], settings: { foreground: '#525252' } }, + ], +} + +// ── Framework data ────────────────────────────────────────────────────────── + +const frameworksList: { + name: string + icon: string + code: string + lang: 'javascript' | 'dart' | 'svelte' | 'vue' + docsUrl: string +}[] = [ + { + name: 'React', + icon: 'M45.74 23.6983C45.2739 23.5379 44.7909 23.3861 44.2937 23.2426C44.3754 22.909 44.4504 22.5798 44.5171 22.2561C45.6119 16.9418 44.8961 12.6605 42.4518 11.2509C40.1079 9.89927 36.2748 11.3085 32.4035 14.6776C32.0313 15.0016 31.6579 15.3446 31.2848 15.704C31.0362 15.4662 30.7879 15.2364 30.5403 15.0165C26.4831 11.4141 22.4164 9.89599 19.9744 11.3097C17.6329 12.6652 16.9394 16.69 17.9249 21.7265C18.0201 22.2129 18.1313 22.7097 18.2571 23.2148C17.6816 23.3782 17.1259 23.5524 16.5943 23.7377C11.8376 25.3961 8.7998 27.9952 8.7998 30.6911C8.7998 33.4755 12.0609 36.2683 17.0153 37.9617C17.4063 38.0953 17.812 38.2217 18.2301 38.3416C18.0944 38.8879 17.9763 39.4232 17.8773 39.9454C16.9376 44.8944 17.6714 48.8242 20.0068 50.1711C22.4189 51.5622 26.4673 50.1324 30.4093 46.6865C30.7209 46.4141 31.0336 46.1253 31.3469 45.8225C31.7529 46.2135 32.1582 46.5835 32.5615 46.9306C36.3798 50.2164 40.151 51.5432 42.4842 50.1925C44.894 48.7975 45.6772 44.576 44.6604 39.4399C44.5828 39.0476 44.4924 38.6469 44.3909 38.239C44.6752 38.155 44.9543 38.0682 45.2265 37.978C50.3771 36.2715 53.7282 33.5127 53.7282 30.6911C53.7282 27.9854 50.5924 25.3688 45.74 23.6983ZM44.6228 36.1561C44.3772 36.2375 44.1251 36.3161 43.8682 36.3923C43.2996 34.5922 42.5322 32.6781 41.5931 30.7005C42.4893 28.7699 43.227 26.8803 43.7797 25.0919C44.2393 25.2249 44.6854 25.3651 45.1152 25.5132C49.2728 26.9444 51.8089 29.0605 51.8089 30.6911C51.8089 32.4279 49.07 34.6826 44.6228 36.1561ZM42.7776 39.8126C43.2272 42.0837 43.2914 44.1371 42.9936 45.7423C42.726 47.1847 42.1878 48.1463 41.5225 48.5315C40.1066 49.351 37.0787 48.2857 33.8132 45.4757C33.4388 45.1535 33.0618 44.8096 32.6835 44.4455C33.9495 43.061 35.2147 41.4514 36.4495 39.6638C38.6215 39.4711 40.6735 39.156 42.5344 38.7258C42.626 39.0955 42.7074 39.4581 42.7776 39.8126ZM24.1169 48.3898C22.7336 48.8784 21.6318 48.8924 20.9658 48.5084C19.5486 47.691 18.9594 44.5358 19.7631 40.3033C19.8551 39.8186 19.9647 39.3207 20.091 38.8118C21.9314 39.2187 23.9684 39.5116 26.1456 39.6881C27.3887 41.4373 28.6905 43.0452 30.0024 44.453C29.7157 44.7297 29.4302 44.9931 29.1463 45.2413C27.4032 46.7651 25.6564 47.8461 24.1169 48.3898ZM17.6361 36.1455C15.4453 35.3967 13.6361 34.4235 12.396 33.3616C11.2817 32.4073 10.7191 31.4599 10.7191 30.6911C10.7191 29.0551 13.1581 26.9684 17.226 25.5501C17.7196 25.378 18.2363 25.2158 18.7725 25.0635C19.3347 26.8923 20.0722 28.8043 20.9623 30.7378C20.0607 32.7 19.3128 34.6425 18.745 36.4927C18.3628 36.3829 17.9924 36.2672 17.6361 36.1455ZM19.8085 21.3579C18.9642 17.0428 19.5249 13.7876 20.936 12.9708C22.4391 12.1006 25.7628 13.3413 29.2659 16.4518C29.4898 16.6506 29.7146 16.8587 29.9401 17.074C28.6347 18.4756 27.3448 20.0714 26.1127 21.8103C23.9997 22.0061 21.977 22.3208 20.1174 22.742C20.0004 22.2717 19.8969 21.8097 19.8085 21.3579ZM39.1886 26.1433C38.744 25.3754 38.2876 24.6257 37.8223 23.8964C39.2558 24.0777 40.6293 24.3182 41.9191 24.6126C41.5318 25.8536 41.0492 27.1511 40.4811 28.4813C40.0735 27.7076 39.6425 26.9275 39.1886 26.1433ZM31.2854 18.4456C32.1707 19.4047 33.0573 20.4756 33.9293 21.6374C33.0506 21.5959 32.161 21.5743 31.264 21.5743C30.3755 21.5743 29.4925 21.5954 28.6192 21.6362C29.4921 20.4852 30.3863 19.4158 31.2854 18.4456ZM23.3317 26.1566C22.8876 26.9267 22.4645 27.7025 22.0634 28.4799C21.5045 27.1543 21.0263 25.8509 20.6357 24.5923C21.9176 24.3054 23.2846 24.0709 24.7089 23.8931C24.2371 24.6291 23.7769 25.3843 23.3317 26.1564V26.1566ZM24.75 37.626C23.2783 37.4618 21.8908 37.2394 20.6093 36.9604C21.0061 35.6793 21.4948 34.3481 22.0655 32.994C22.4677 33.7707 22.8925 34.5469 23.3393 35.3187H23.3393C23.7945 36.1049 24.266 36.875 24.75 37.626ZM31.3385 43.0719C30.4289 42.0904 29.5215 41.0047 28.6353 39.8368C29.4956 39.8706 30.3726 39.8879 31.264 39.8879C32.1798 39.8879 33.085 39.8672 33.9761 39.8276C33.1012 41.0164 32.2178 42.1038 31.3385 43.0719ZM40.4994 32.9249C41.0999 34.2937 41.6061 35.618 42.0081 36.8772C40.7054 37.1744 39.2989 37.4138 37.8171 37.5916C38.2835 36.8525 38.7439 36.0899 39.1963 35.3055C39.6539 34.5118 40.0885 33.717 40.4994 32.9249ZM37.5337 34.3466C36.8314 35.5643 36.1104 36.7268 35.3784 37.8241C34.0452 37.9194 32.6678 37.9685 31.264 37.9685C29.8659 37.9685 28.5058 37.9251 27.1962 37.8401C26.4347 36.7284 25.698 35.5625 25.0002 34.3571H25.0004C24.3044 33.155 23.6638 31.9427 23.0834 30.7372C23.6636 29.5289 24.3025 28.3152 24.9945 27.1152L24.9944 27.1155C25.6882 25.9123 26.4184 24.7521 27.1729 23.6473C28.509 23.5463 29.8792 23.4936 31.2639 23.4936H31.264C32.655 23.4936 34.0269 23.5467 35.3626 23.6486C36.1056 24.7453 36.8308 25.9017 37.5274 27.1051C38.2319 28.3219 38.879 29.5275 39.4642 30.7099C38.8808 31.9126 38.2351 33.1303 37.5337 34.3466ZM41.4931 12.9137C42.9976 13.7813 43.5826 17.2804 42.6374 21.8688C42.5771 22.1615 42.5092 22.4597 42.4354 22.762C40.5715 22.3319 38.5474 22.0118 36.4282 21.813C35.1937 20.055 33.9143 18.4567 32.6302 17.0731C32.9755 16.741 33.3202 16.4243 33.6636 16.1254C36.9805 13.2388 40.0806 12.0991 41.4931 12.9137ZM31.264 26.6791C33.4797 26.6791 35.276 28.4753 35.276 30.6911C35.276 32.9068 33.4797 34.703 31.264 34.703C29.0483 34.703 27.252 32.9068 27.252 30.6911C27.252 28.4753 29.0483 26.6791 31.264 26.6791Z', + code: `import { createClient } from '@supabase/supabase-js' + +const supabase = createClient( + process.env.SUPABASE_URL, + process.env.SUPABASE_ANON_KEY +) + +export default function App() { + const [todos, setTodos] = useState([]) + + useEffect(() => { + supabase.from('todos').select('*') + .then(({ data }) => setTodos(data)) + }, []) + + return +}`, + lang: 'javascript' as const, + docsUrl: '/docs/guides/getting-started/quickstarts/reactjs', + }, + { + name: 'Next.js', + icon: 'M42.3148 48.6796C38.9009 50.9525 34.8014 52.2771 30.3924 52.2771C18.4957 52.2771 8.85156 42.6329 8.85156 30.7362C8.85156 18.8395 18.4957 9.19531 30.3924 9.19531C42.2891 9.19531 51.9333 18.8395 51.9333 30.7362C51.9333 37.1564 49.1245 42.9207 44.6688 46.8671L39.5552 40.2803V21.8278H36.584V36.4531L25.2299 21.8278H21.4808V39.6473H24.4801V25.6368L42.3148 48.6796Z', + code: `import { createClient } from '@supabase/supabase-js' + +const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY +) + +export default async function Page() { + const { data: todos } = await supabase + .from('todos') + .select('*') + + return +}`, + lang: 'javascript' as const, + docsUrl: '/docs/guides/getting-started/quickstarts/nextjs', + }, + { + name: 'Flutter', + icon: 'M46.5067 10.3828L34.3509 10.3962L14.75 29.9971L20.7974 36.0519L26.1125 30.7666L46.5067 10.3828Z M34.6996 28.4653C34.5272 28.4573 34.3493 28.4491 34.2378 28.5965L23.7856 39.0471L29.7894 45.0142L29.7825 45.021L34.079 49.3212C34.1072 49.3462 34.1352 49.3741 34.1637 49.4026C34.2813 49.5201 34.4074 49.6462 34.5895 49.6055C36.5743 49.601 38.5591 49.6017 40.544 49.6025C42.529 49.6032 44.5142 49.604 46.4998 49.5995L35.9333 39.0234L46.4963 28.467L34.906 28.464C34.8415 28.4719 34.7711 28.4686 34.6996 28.4653Z', + code: `import 'package:supabase_flutter/supabase_flutter.dart'; + +Future main() async { + await Supabase.initialize( + url: 'YOUR_SUPABASE_URL', + anonKey: 'YOUR_SUPABASE_ANON_KEY', + ); + runApp(MyApp()); +} + +final supabase = Supabase.instance.client; + +final data = await supabase + .from('todos') + .select('*');`, + lang: 'dart' as const, + docsUrl: '/docs/guides/getting-started/quickstarts/flutter', + }, + { + name: 'Svelte', + icon: 'M29.9094 11.2292C35.956 7.37668 44.3187 9.17299 48.553 15.2334H48.5532C50.5831 18.0746 51.3826 21.614 50.771 25.0519C50.4778 26.677 49.8581 28.2259 48.9493 29.6047C50.2752 32.1335 50.7201 35.0322 50.2136 37.8422C49.6086 41.2154 47.6106 44.1777 44.7096 46.0024L34.0903 52.7707C28.0445 56.623 19.6818 54.8274 15.4466 48.7665C13.4171 45.9251 12.6176 42.3859 13.2288 38.948C13.5223 37.3227 14.1422 35.7738 15.0512 34.3949C13.7247 31.8665 13.2794 28.9677 13.786 26.1577C14.3913 22.7845 16.3893 19.8223 19.29 17.9974L29.9094 11.2292ZM19.8146 45.9861C21.8311 48.8931 25.4469 50.2333 28.8709 49.343H28.8708C29.6345 49.139 30.3624 48.8192 31.0293 48.3946L41.6512 41.6252C43.396 40.5274 44.5979 38.7455 44.9622 36.7164C45.33 34.6483 44.8489 32.5192 43.6278 30.8101C41.6113 27.9032 37.9955 26.5629 34.5715 27.4531C33.8084 27.6571 33.081 27.9768 32.4147 28.4012L28.3617 30.9842C28.1601 31.1125 27.9401 31.2092 27.7093 31.271C26.6776 31.5384 25.5887 31.1342 24.9815 30.2584C24.614 29.7429 24.4693 29.1012 24.5801 28.4779C24.6899 27.8669 25.0519 27.3302 25.5774 26.9996L36.2002 20.2298C36.4017 20.1015 36.6218 20.0048 36.8526 19.9431C37.8838 19.6754 38.9725 20.0795 39.5793 20.9551C39.9039 21.4146 40.0556 21.974 40.0078 22.5345L39.9714 22.9285L40.3662 23.0484C41.8596 23.4989 43.265 24.2014 44.5218 25.1254L45.0657 25.5245L45.2658 24.9145C45.3729 24.59 45.4577 24.2586 45.5196 23.9225C45.8873 21.8544 45.4063 19.7254 44.1852 18.0162C42.1687 15.1093 38.553 13.7691 35.129 14.6593C34.3653 14.8633 33.6374 15.1832 32.9705 15.6077L22.3487 22.3777C20.6036 23.475 19.4016 25.2568 19.0376 27.2858C18.6699 29.3539 19.1509 31.4829 20.372 33.192C22.3885 36.099 26.0043 37.4392 29.4283 36.549C30.1914 36.345 30.9188 36.0256 31.5853 35.6017L35.6389 33.0177C35.8402 32.8895 36.06 32.7929 36.2905 32.7311C37.3221 32.4637 38.4111 32.868 39.0183 33.7438C39.3857 34.2592 39.5306 34.9007 39.4205 35.524C39.3102 36.1352 38.948 36.6718 38.4224 37.0028L27.7996 43.7722C27.5981 43.9006 27.378 43.9973 27.1471 44.059C26.116 44.3266 25.0273 43.9225 24.4204 43.0469C24.0955 42.5876 23.9438 42.0281 23.992 41.4675L24.0284 41.0735L23.6336 40.9537C22.1404 40.5032 20.7351 39.8011 19.4783 38.8776L18.934 38.4778L18.734 39.0878C18.6266 39.4122 18.5418 39.7437 18.4801 40.0798C18.1125 42.1479 18.5935 44.277 19.8146 45.9861Z', + code: ``, + lang: 'svelte' as const, + docsUrl: '/docs/guides/getting-started/quickstarts/sveltekit', + }, + { + name: 'Vue', + icon: 'M43.0532 13.4531H50.1147L30.2756 47.8158L10.4365 13.4531H17.4978L30.2755 35.5845L43.0532 13.4531ZM42.1764 13.4531L30.2755 34.0659L18.3746 13.4531L25.6939 13.4531L30.2756 21.3888L34.8572 13.4531L42.1764 13.4531Z', + code: ``, + lang: 'vue' as const, + docsUrl: '/docs/guides/getting-started/quickstarts/vuejs', + }, + { + name: 'Nuxt', + icon: 'M32.5784 45.4741H50.2199C50.7802 45.4741 51.3307 45.3325 51.8159 45.0634C52.3012 44.7943 52.7041 44.4072 52.9842 43.9409C53.2642 43.4748 53.4115 42.946 53.4113 42.4078C53.4111 41.8696 53.2633 41.3409 52.9828 40.875L41.1352 21.164C40.8552 20.6979 40.4524 20.3109 39.9672 20.0418C39.4821 19.7727 38.9317 19.631 38.3715 19.631C37.8113 19.631 37.261 19.7727 36.7758 20.0418C36.2906 20.3109 35.8878 20.6979 35.6078 21.164L32.5784 26.2073L26.6555 16.3452C26.3753 15.8792 25.9723 15.4922 25.487 15.2232C25.0017 14.9541 24.4513 14.8125 23.8909 14.8125C23.3306 14.8125 22.7802 14.9541 22.2949 15.2232C21.8096 14.9541 21.4066 15.8792 21.1263 16.3452L6.38358 40.875C6.10311 41.3409 5.95532 41.8696 5.95508 42.4078C5.95483 42.946 6.10214 43.4748 6.38219 43.9409C6.66224 44.4072 7.06515 44.7943 7.5504 45.0634C8.03564 45.3325 8.58612 45.4741 9.14645 45.4741H20.2203C24.6079 45.4741 27.8436 43.6229 30.07 40.0113L38.3706 26.2073L47.0599 40.6619H35.4754L32.5784 45.4741Z', + code: ` + +`, + lang: 'vue' as const, + docsUrl: '/docs/guides/getting-started/quickstarts/nuxtjs', + }, +] + +// ── Example links per framework ────────────────────────────────────────────── + +const frameworkExamples: Record< + string, + { title: string; description: string; url: string; icon: string }[] +> = { + React: [ + { + title: 'Todo List', + description: 'CRUD app with real-time and RLS.', + url: '/docs/guides/getting-started/tutorials/with-react', + icon: 'ListChecks', + }, + { + title: 'Auth & User Management', + description: 'Email, social login, and user profiles.', + url: '/docs/guides/auth/quickstarts/react', + icon: 'Users', + }, + ], + 'Next.js': [ + { + title: 'App Router Quickstart', + description: 'Server Components with SSR auth.', + url: '/docs/guides/getting-started/quickstarts/nextjs', + icon: 'Rocket', + }, + { + title: 'Auth with PKCE', + description: 'Secure auth flow with middleware.', + url: '/docs/guides/auth/server-side/nextjs', + icon: 'KeyRound', + }, + ], + Flutter: [ + { + title: 'User Management', + description: 'Auth and profile management.', + url: '/docs/guides/getting-started/tutorials/with-flutter', + icon: 'Users', + }, + { + title: 'Deep Linking', + description: 'OAuth and magic link for mobile.', + url: '/docs/guides/auth/native-mobile-deep-linking', + icon: 'Link2', + }, + ], + Svelte: [ + { + title: 'Todo List', + description: 'Server-side auth and real-time data.', + url: '/docs/guides/getting-started/tutorials/with-svelte', + icon: 'ListChecks', + }, + { + title: 'Auth Helpers', + description: 'Sessions and protected routes.', + url: '/docs/guides/auth/server-side/sveltekit', + icon: 'ShieldCheck', + }, + ], + Vue: [ + { + title: 'Todo List', + description: 'CRUD with Vue 3 Composition API.', + url: '/docs/guides/getting-started/tutorials/with-vue-3', + icon: 'ListChecks', + }, + { + title: 'Auth with Vue', + description: 'Email and OAuth authentication.', + url: '/docs/guides/getting-started/quickstarts/vuejs', + icon: 'KeyRound', + }, + ], + Nuxt: [ + { + title: 'Nuxt Module', + description: 'Auto-configured auth and client.', + url: '/docs/guides/getting-started/quickstarts/nuxtjs', + icon: 'Blocks', + }, + { + title: 'Server Routes', + description: 'Service role access from server.', + url: '/docs/guides/auth/server-side/nuxt', + icon: 'Server', + }, + ], +} + +// ── Server component: pre-highlights all code ─────────────────────────────── + +export async function FrameworksSection() { + const hl = await createHighlighter({ + themes: [supabaseDark, supabaseLight], + langs: ['javascript', 'dart', 'svelte', 'vue'], + }) + + const frameworks = frameworksList.map((fw) => ({ + name: fw.name, + icon: fw.icon, + 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() + + return +} diff --git a/apps/www/app/(home)/_components/FrameworksSectionClient.tsx b/apps/www/app/(home)/_components/FrameworksSectionClient.tsx new file mode 100644 index 00000000000..2c3c3230f35 --- /dev/null +++ b/apps/www/app/(home)/_components/FrameworksSectionClient.tsx @@ -0,0 +1,140 @@ +'use client' + +import { AnimatePresence, motion } from 'framer-motion' +import Link from 'next/link' +import { useState } from 'react' +import { cn } from 'ui' + +type Framework = { + name: string + icon: string + docsUrl: string + darkHtml: string + lightHtml: string + examples: { title: string; description: string; url: string; icon: string }[] +} + +export function FrameworksSectionClient({ frameworks }: { frameworks: Framework[] }) { + const [activeIdx, setActiveIdx] = useState(0) + const active = frameworks[activeIdx] + + const handleTabChange = (index: number) => { + setActiveIdx(index) + } + + return ( +
+
+
+ {/* Left: title */} +
+

+ Use Supabase with{' '} + + + + {active.name} + + + +

+
+ + {/* Right: icon tabs + code */} +
+ {/* 6-col icon row */} +
+ {frameworks.map((framework, index) => ( + + ))} +
+ + {/* Code area */} +
+ + +
+
+ + + + {`Read docs for ${active.name}`} + + + + +
+
+
+
+
+ ) +} diff --git a/apps/www/app/(home)/_components/Hero.tsx b/apps/www/app/(home)/_components/Hero.tsx new file mode 100644 index 00000000000..a994a09a8f2 --- /dev/null +++ b/apps/www/app/(home)/_components/Hero.tsx @@ -0,0 +1,56 @@ +'use client' + +import { useSendTelemetryEvent } from '~/lib/telemetry' +import Link from 'next/link' +import { Button } from 'ui' + +import SectionContainer from '@/components/Layouts/SectionContainer' + +export function Hero() { + const sendTelemetryEvent = useSendTelemetryEvent() + + return ( + +
+
+

+ Build in a weekend + Scale to millions +

+

+ Start your project with a Postgres database. Add Authentication, Data APIs, Edge + Functions, Realtime Data, Storage, and Vector embeddings. +

+
+
+ + +
+
+
+ ) +} diff --git a/apps/www/app/(home)/_components/HomeContent.tsx b/apps/www/app/(home)/_components/HomeContent.tsx new file mode 100644 index 00000000000..10a9ca9fae7 --- /dev/null +++ b/apps/www/app/(home)/_components/HomeContent.tsx @@ -0,0 +1,54 @@ +'use client' + +import getContent from '~/data/home/content' +import dynamic from 'next/dynamic' + +import { BuiltWithSupabaseSection } from './BuiltWithSupabaseSection' +import { CommunitySectionV2 as CommunitySection } from './CommunitySectionV2' +import { CTASection } from './CTASection' +import { CustomerStoriesSection } from './CustomerStoriesSection' +import { DashboardFeaturesSection } from './DashboardFeaturesSection' +import { Hero } from './Hero' +import { LogosGrid } from './LogosGrid' +import { OpenSourceSection } from './OpenSourceSection' + +const Products = dynamic(() => import('~/components/Products/index')) + +export function HomeContent({ frameworksSlot }: { frameworksSlot: React.ReactNode }) { + const content = getContent() + + return ( + <> +
+ +
+
+
+ +
+
+
+ +
+
+ +
+
{frameworksSlot}
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + ) +} diff --git a/apps/www/app/(home)/_components/LogosGrid.tsx b/apps/www/app/(home)/_components/LogosGrid.tsx new file mode 100644 index 00000000000..ae94535c54c --- /dev/null +++ b/apps/www/app/(home)/_components/LogosGrid.tsx @@ -0,0 +1,119 @@ +'use client' + +import { AnimatePresence, motion } from 'framer-motion' +import { ComponentProps, useEffect, useState } from 'react' + +import { + BetasharesLogo, + BoltLogo, + ChatbaseLogo, + FigmaLogo, + GithubLogo, + GoodtapeLogo, + GopuffLogo, + GumloopLogo, + HappyteamsLogo, + HumataLogo, + LangchainLogo, + LoopsLogo, + LovableLogo, + MarkpromptLogo, + MdnLogo, + MobbinLogo, + MozillaLogo, + PikaLogo, + PwcLogo, + ResendLogo, + SoshiLogo, + SubmagicLogo, + TempoLogo, + V0Logo, +} from './logos/PublicityLogos' +import SectionContainer from '@/components/Layouts/SectionContainer' + +const gridLogos: { name: string; Logo: (props: ComponentProps<'svg'>) => React.JSX.Element }[] = [ + { name: 'betashares', Logo: BetasharesLogo }, + { name: 'bolt', Logo: BoltLogo }, + { name: 'chatbase', Logo: ChatbaseLogo }, + { name: 'figma', Logo: FigmaLogo }, + { name: 'github', Logo: GithubLogo }, + { name: 'goodtape', Logo: GoodtapeLogo }, + { name: 'gopuff', Logo: GopuffLogo }, + { name: 'gumloop', Logo: GumloopLogo }, + { name: 'happyteams', Logo: HappyteamsLogo }, + { name: 'humata', Logo: HumataLogo }, + { name: 'langchain', Logo: LangchainLogo }, + { name: 'loops', Logo: LoopsLogo }, + { name: 'lovable', Logo: LovableLogo }, + { name: 'markprompt', Logo: MarkpromptLogo }, + { name: 'mdn', Logo: MdnLogo }, + { name: 'mobbin', Logo: MobbinLogo }, + { name: 'mozilla', Logo: MozillaLogo }, + { name: 'pika', Logo: PikaLogo }, + { name: 'pwc', Logo: PwcLogo }, + { name: 'resend', Logo: ResendLogo }, + { name: 'soshi', Logo: SoshiLogo }, + { name: 'submagic', Logo: SubmagicLogo }, + { name: 'tempo', Logo: TempoLogo }, + { name: 'v0', Logo: V0Logo }, +] + +const LOGOS_PER_PAGE = 12 + +export function LogosGrid() { + const [page, setPage] = useState(0) + const totalPages = Math.ceil(gridLogos.length / LOGOS_PER_PAGE) + + useEffect(() => { + const interval = setInterval(() => { + setPage((p) => (p + 1) % totalPages) + }, 10000) + return () => clearInterval(interval) + }, [totalPages]) + + const start = page * LOGOS_PER_PAGE + const currentLogos = gridLogos.slice(start, start + LOGOS_PER_PAGE) + + return ( +
+ +

+ Trusted by fast-growing companies worldwide +

+
+ +
+ + + + {currentLogos.map(({ name, Logo }, i) => ( + + + + ))} + + + +
+
+ ) +} diff --git a/apps/www/app/(home)/_components/OpenSourceSection.tsx b/apps/www/app/(home)/_components/OpenSourceSection.tsx new file mode 100644 index 00000000000..be815f3c132 --- /dev/null +++ b/apps/www/app/(home)/_components/OpenSourceSection.tsx @@ -0,0 +1,211 @@ +'use client' + +import staticContent from '.generated/staticContent/_index.json' +import { useSendTelemetryEvent } from '~/lib/telemetry' +import { useInView } from 'framer-motion' +import Link from 'next/link' +import { useRef } from 'react' +import { Button } from 'ui' + +import { kFormatter } from '../../../lib/helpers' +import SectionContainer from '@/components/Layouts/SectionContainer' + +// ── Pixel font (5×7 per glyph) ───────────────────────────────────────────── + +const GLYPHS: Record = { + '0': ['01110', '10001', '10011', '10101', '11001', '10001', '01110'], + '1': ['00100', '01100', '00100', '00100', '00100', '00100', '01110'], + '2': ['01110', '10001', '00001', '00110', '01000', '10000', '11111'], + '3': ['01110', '10001', '00001', '00110', '00001', '10001', '01110'], + '4': ['00010', '00110', '01010', '10010', '11111', '00010', '00010'], + '5': ['11111', '10000', '11110', '00001', '00001', '10001', '01110'], + '6': ['01110', '10001', '10000', '11110', '10001', '10001', '01110'], + '7': ['11111', '00001', '00010', '00100', '01000', '01000', '01000'], + '8': ['01110', '10001', '10001', '01110', '10001', '10001', '01110'], + '9': ['01110', '10001', '10001', '01111', '00001', '10001', '01110'], + '.': ['00000', '00000', '00000', '00000', '00000', '00000', '00100'], + K: ['10001', '10010', '10100', '11000', '10100', '10010', '10001'], + ' ': ['00000', '00000', '00000', '00000', '00000', '00000', '00000'], +} + +function textToPixelMask(text: string): Set { + const mask = new Set() + let cursorX = 0 + for (const ch of text) { + const glyph = GLYPHS[ch] + if (!glyph) { + cursorX += 3 + continue + } + for (let row = 0; row < glyph.length; row++) { + for (let col = 0; col < glyph[row].length; col++) { + if (glyph[row][col] === '1') mask.add(`${cursorX + col},${row}`) + } + } + cursorX += 6 // 5 wide + 1 gap + } + return mask +} + +// ── Contribution graph ────────────────────────────────────────────────────── + +const CELL = 3 +const GAP = 0.5 +const COLS = 72 +const ROWS = 40 + +const githubStars = staticContent.githubStars +const TEXT = kFormatter(githubStars) +const TEXT_MASK = textToPixelMask(TEXT) +const TEXT_W = TEXT.length * 6 - 1 +const TEXT_H = 7 +const TEXT_OFFSET_X = Math.floor((COLS - TEXT_W) / 2) +const TEXT_OFFSET_Y = Math.floor((ROWS - TEXT_H) / 2) + +const LEVELS_BG = [ + 'hsl(var(--background-surface-300) / 0.2)', + 'hsl(var(--brand-400) / 0.08)', + 'hsl(var(--brand-500) / 0.1)', + 'hsl(var(--brand-600) / 0.12)', + 'hsl(var(--brand-default) / 0.12)', +] + +const LEVELS_TEXT = [ + 'hsl(var(--brand-600))', + 'hsl(var(--brand-default))', + 'hsl(var(--brand-default))', + 'hsl(var(--brand-600))', + 'hsl(var(--brand-500))', +] + +function makeGrid(): { level: number; isText: boolean }[][] { + let seed = 0x9e3779b9 + const rand = () => { + seed ^= seed << 13 + seed ^= seed >> 17 + seed ^= seed << 5 + return (seed >>> 0) / 0xffffffff + } + return Array.from({ length: COLS }, (_, c) => + Array.from({ length: ROWS }, (_, r) => { + const v = rand() + const level = v < 0.45 ? 0 : v < 0.65 ? 1 : v < 0.8 ? 2 : v < 0.92 ? 3 : 4 + const localX = c - TEXT_OFFSET_X + const localY = r - TEXT_OFFSET_Y + const isText = TEXT_MASK.has(`${localX},${localY}`) + return { level, isText } + }) + ) +} + +const GRID = makeGrid() +const SVG_W = COLS * (CELL + GAP) - GAP +const SVG_H = ROWS * (CELL + GAP) - GAP + +function ContribGraph() { + const ref = useRef(null) + const isInView = useInView(ref, { once: true, margin: '-50px' }) + + return ( + + {GRID.map((col, c) => + col.map(({ level, isText }, r) => ( + + )) + )} + + ) +} + +// ── Component ─────────────────────────────────────────────────────────────── + +export function OpenSourceSection() { + const sendTelemetryEvent = useSendTelemetryEvent() + + return ( +
+ {/* Contrib graph — right half, full-height bleed (desktop) */} +
+ +
+ + +
+
+
+

Open source from day one

+

+ Supabase is built in the open because we believe great developer tools should be + transparent, inspectable, and owned by the community. Read, contribute, self-host. + You're never locked in, and always in control. +

+
+ +
+ +
+
+ + {/* Contrib graph — stacked below the text on mobile */} +
+ +
+
+
+
+ ) +} diff --git a/apps/www/app/(home)/_components/logos/FrameworkLogos.tsx b/apps/www/app/(home)/_components/logos/FrameworkLogos.tsx new file mode 100644 index 00000000000..be93adc90b7 --- /dev/null +++ b/apps/www/app/(home)/_components/logos/FrameworkLogos.tsx @@ -0,0 +1,277 @@ +import { ComponentProps } from 'react' + +export function DartLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function ExpoLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function FlutterLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function FlutterflowLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function KotlinLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function LangchainLogo(props: ComponentProps<'svg'>) { + return ( + + + + + + ) +} + +export function NestjsLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function NextjsLogo(props: ComponentProps<'svg'>) { + return ( + + + + + + + + + + + ) +} + +export function NuxtjsLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function OpenaiLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function ReactLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function ReactiveXLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function RedwoodLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function RemixLogo(props: ComponentProps<'svg'>) { + return ( + + + + + ) +} + +export function SolidjsLogo(props: ComponentProps<'svg'>) { + return ( + + + + + + + ) +} + +export function StripeLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function SvelteLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function TurbopackLogo(props: ComponentProps<'svg'>) { + return ( + + + + + ) +} + +export function VercelLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function VueLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} diff --git a/apps/www/app/(home)/_components/logos/PublicityLogos.tsx b/apps/www/app/(home)/_components/logos/PublicityLogos.tsx new file mode 100644 index 00000000000..b262cda4193 --- /dev/null +++ b/apps/www/app/(home)/_components/logos/PublicityLogos.tsx @@ -0,0 +1,591 @@ +import { ComponentProps } from 'react' + +export function OnePasswordLogo(props: ComponentProps<'svg'>) { + return ( + + + + + ) +} + +export function BetasharesLogo(props: ComponentProps<'svg'>) { + return ( + + + + + + + + + + + ) +} + +export function BoltLogo(props: ComponentProps<'svg'>) { + return ( + + + + + + ) +} + +export function BrthrsLogo(props: ComponentProps<'svg'>) { + return ( + + brthrs-logotype-color + + + + + + + + + + + + + ) +} + +export function ChatbaseLogo(props: ComponentProps<'svg'>) { + return ( + + + + + + + + + + + + ) +} + +export function CoComLogo(props: ComponentProps<'svg'>) { + return ( + + + + + + + + + + + ) +} + +export function FigmaLogo(props: ComponentProps<'svg'>) { + return ( + + + + + + + + + ) +} + +export function GithubLogo(props: ComponentProps<'svg'>) { + return ( + + + + + ) +} + +export function GoodtapeLogo(props: ComponentProps<'svg'>) { + return ( + + + + + + + + ) +} + +export function GopuffLogo(props: ComponentProps<'svg'>) { + return ( + + + + + + + + + + + ) +} + +export function GumloopLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function HappyteamsLogo(props: ComponentProps<'svg'>) { + return ( + + + + + + ) +} + +export function HumataLogo(props: ComponentProps<'svg'>) { + return ( + + + + + ) +} + +export function KikiLogo(props: ComponentProps<'svg'>) { + return ( + + + + + ) +} + +export function LangchainLogo(props: ComponentProps<'svg'>) { + return ( + + + + + + + + + + + + + ) +} + +export function LoopsLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function LovableLogo(props: ComponentProps<'svg'>) { + return ( + + + + + ) +} + +export function MarkpromptLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function MdnLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function MobbinLogo(props: ComponentProps<'svg'>) { + return ( + + + + + ) +} + +export function MozillaLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function NextdoorlendingLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function PebblelyLogo(props: ComponentProps<'svg'>) { + return ( + + + + + + + ) +} + +export function PikaLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function PwcLogo(props: ComponentProps<'svg'>) { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +export function QuivrLogo(props: ComponentProps<'svg'>) { + return ( + + + + + + + ) +} + +export function ResendLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function ShotgunLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function SoshiLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function SubmagicLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function TempoLogo(props: ComponentProps<'svg'>) { + return ( + + + + + + + + + + + + ) +} + +export function UdioLogo(props: ComponentProps<'svg'>) { + return ( + + + + ) +} + +export function V0Logo(props: ComponentProps<'svg'>) { + return ( + + + + + + + + + + + ) +} diff --git a/apps/www/app/(home)/layout.tsx b/apps/www/app/(home)/layout.tsx new file mode 100644 index 00000000000..61677b56677 --- /dev/null +++ b/apps/www/app/(home)/layout.tsx @@ -0,0 +1,17 @@ +import Footer from '~/components/Footer' +import Nav from '~/components/Nav' + +import { ThemeForcer } from './ThemeForcer' + +export default function HomeLayout({ children }: { children: React.ReactNode }) { + return ( + <> + +