@@ -171,7 +182,7 @@ export function CustomerStoriesSection() {
}`,
- lang: 'js' as const,
+ lang: 'javascript' as const,
},
{
name: 'Next.js',
@@ -142,7 +94,7 @@ export default async function Page() {
return
}`,
- lang: 'js' as const,
+ lang: 'javascript' as const,
},
{
name: 'Flutter',
@@ -183,7 +135,7 @@ final data = await supabase
todos = data
}
`,
- lang: 'xml' as const,
+ lang: 'svelte' as const,
},
{
name: 'Vue',
@@ -205,7 +157,7 @@ onMounted(async () => {
todos.value = data
})
`,
- lang: 'xml' as const,
+ lang: 'vue' as const,
},
{
name: 'Nuxt',
@@ -227,105 +179,26 @@ const { data: todos } = await useAsyncData(
`,
- lang: 'xml' as const,
+ lang: 'vue' as const,
},
]
-export function FrameworksSection() {
- const { resolvedTheme } = useTheme()
- const isDark = resolvedTheme?.includes('dark') ?? false
- const [activeIdx, setActiveIdx] = useState(0)
- const [mounted, setMounted] = useState(false)
- const active = frameworksList[activeIdx]
+// ── Server component: pre-highlights all code ───────────────────────────────
- useEffect(() => setMounted(true), [])
+export async function FrameworksSection() {
+ const hl = await createHighlighter({
+ themes: [supabaseDark, supabaseLight],
+ langs: ['javascript', 'dart', 'svelte', 'vue'],
+ })
- return (
-
-
-
- {/* Left: title */}
-
-
- Use Supabase with{' '}
-
-
-
- {active.name}
-
-
-
-
-
+ const frameworks = frameworksList.map((fw) => ({
+ name: fw.name,
+ icon: fw.icon,
+ darkHtml: hl.codeToHtml(fw.code, { lang: fw.lang, theme: 'supabase-dark' }),
+ lightHtml: hl.codeToHtml(fw.code, { lang: fw.lang, theme: 'supabase-light' }),
+ }))
- {/* Right: icon tabs + code */}
-
- {/* 6-col icon row */}
-
- {frameworksList.map((framework, index) => (
-
setActiveIdx(index)}
- className={cn(
- 'flex items-center justify-center py-4 border-r border-border last:border-r-0 transition-colors',
- index === activeIdx
- ? 'bg-surface-75 text-foreground'
- : 'text-foreground-muted hover:text-foreground-light hover:bg-surface-75/50'
- )}
- >
-
-
-
-
- ))}
-
+ hl.dispose()
- {/* Code area */}
-
- {mounted && (
-
-
- {/* @ts-ignore */}
-
- {active.code}
-
-
-
- )}
-
-
-
-
-
- )
+ 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..ec547269f60
--- /dev/null
+++ b/apps/www/app/(home)/_components/FrameworksSectionClient.tsx
@@ -0,0 +1,94 @@
+'use client'
+
+import { AnimatePresence, motion } from 'framer-motion'
+import { useState } from 'react'
+import { cn } from 'ui'
+
+type Framework = {
+ name: string
+ icon: string
+ darkHtml: string
+ lightHtml: string
+}
+
+export function FrameworksSectionClient({ frameworks }: { frameworks: Framework[] }) {
+ const [activeIdx, setActiveIdx] = useState(0)
+ const active = frameworks[activeIdx]
+
+ return (
+
+
+
+ {/* Left: title */}
+
+
+ Use Supabase with{' '}
+
+
+
+ {active.name}
+
+
+
+
+
+
+ {/* Right: icon tabs + code */}
+
+ {/* 6-col icon row */}
+
+ {frameworks.map((framework, index) => (
+
setActiveIdx(index)}
+ className={cn(
+ 'flex items-center justify-center py-4 border-r border-border last:border-r-0 transition-colors',
+ index === activeIdx
+ ? 'bg-surface-75 text-foreground'
+ : 'text-foreground-muted hover:text-foreground-light hover:bg-surface-75/50'
+ )}
+ >
+
+
+
+
+ ))}
+
+
+ {/* Code area */}
+
+
+
+
+
+ )
+}
diff --git a/apps/www/app/(home)/_components/HomeContent.tsx b/apps/www/app/(home)/_components/HomeContent.tsx
new file mode 100644
index 00000000000..b722cf49cbc
--- /dev/null
+++ b/apps/www/app/(home)/_components/HomeContent.tsx
@@ -0,0 +1,57 @@
+'use client'
+
+import dynamic from 'next/dynamic'
+import getContent from '~/data/home/content'
+import { BuiltWithSupabaseSection } from './BuiltWithSupabaseSection'
+import { CommunitySection } from './CommunitySection'
+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'))
+
+function SectionBlock({ children, className }: { children: React.ReactNode; className?: string }) {
+ return
+}
+
+export function HomeContent({ frameworksSlot }: { frameworksSlot: React.ReactNode }) {
+ const content = getContent()
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/www/app/(home)/_components/LogosGrid.tsx b/apps/www/app/(home)/_components/LogosGrid.tsx
index c2270fad973..ccea5bfcf8a 100644
--- a/apps/www/app/(home)/_components/LogosGrid.tsx
+++ b/apps/www/app/(home)/_components/LogosGrid.tsx
@@ -1,6 +1,5 @@
-'use client'
-
import { ComponentProps } from 'react'
+
import {
BetasharesLogo,
BoltLogo,
@@ -68,7 +67,10 @@ export function LogosGrid() {
{gridLogos.map(({ name, Logo }) => (
-
+
))}
diff --git a/apps/www/app/(home)/_components/OpenSourceSection.tsx b/apps/www/app/(home)/_components/OpenSourceSection.tsx
index ccff983a1f5..a6e40bf1df0 100644
--- a/apps/www/app/(home)/_components/OpenSourceSection.tsx
+++ b/apps/www/app/(home)/_components/OpenSourceSection.tsx
@@ -1,10 +1,10 @@
'use client'
-import { animate, motion, useInView } from 'framer-motion'
-import Link from 'next/link'
-import { useEffect, useRef, useState } from 'react'
-import { Button } from 'ui'
import { useSendTelemetryEvent } from '~/lib/telemetry'
+import { motion, useInView } from 'framer-motion'
+import Link from 'next/link'
+import { useRef } from 'react'
+import { Button } from 'ui'
// ── Contribution graph ──────────────────────────────────────────────────────
@@ -85,23 +85,14 @@ export function OpenSourceSection() {
const inView = useInView(panelRef, { once: true, amount: 0.4 })
const sendTelemetryEvent = useSendTelemetryEvent()
- const [stars, setStars] = useState('0.0K')
- useEffect(() => {
- if (!inView) return
- const controls = animate(0, 98400, {
- duration: 2.2,
- ease: 'easeOut',
- onUpdate: (v: number) => setStars(`${(v / 1000).toFixed(1)}K`),
- })
- return controls.stop
- }, [inView])
+ const stars = '98.4K'
return (
{/* Main content — spans 2 cols */}
-
+
Open source from day one
diff --git a/apps/www/app/(home)/page.tsx b/apps/www/app/(home)/page.tsx
index 05ceb769633..6fd479bb6b8 100644
--- a/apps/www/app/(home)/page.tsx
+++ b/apps/www/app/(home)/page.tsx
@@ -1,60 +1,6 @@
-'use client'
-
-import dynamic from 'next/dynamic'
-import getContent from '~/data/home/content'
-import { BuiltWithSupabaseSection } from './_components/BuiltWithSupabaseSection'
-import { CommunitySection } from './_components/CommunitySection'
-import { CTASection } from './_components/CTASection'
-import { CustomerStoriesSection } from './_components/CustomerStoriesSection'
-import { DashboardFeaturesSection } from './_components/DashboardFeaturesSection'
import { FrameworksSection } from './_components/FrameworksSection'
-import { Hero } from './_components/Hero'
-import { LogosGrid } from './_components/LogosGrid'
-import { OpenSourceSection } from './_components/OpenSourceSection'
-
-const Products = dynamic(() => import('~/components/Products/index'))
-
-function SectionBlock({ children, className }: { children: React.ReactNode; className?: string }) {
- return
-}
+import { HomeContent } from './_components/HomeContent'
export default function HomePage() {
- const content = getContent()
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
+ return
} />
}
diff --git a/apps/www/components/Panel/Panel.tsx b/apps/www/components/Panel/Panel.tsx
index 95b320ade41..787a151c5cd 100644
--- a/apps/www/components/Panel/Panel.tsx
+++ b/apps/www/components/Panel/Panel.tsx
@@ -1,7 +1,9 @@
-import React, { PropsWithChildren, useEffect, useRef } from 'react'
-import { motion } from 'framer-motion'
-import { cn } from 'ui'
+'use client'
+
import { detectBrowser, isBrowser } from 'common'
+import { motion } from 'framer-motion'
+import React, { PropsWithChildren, useEffect, useRef } from 'react'
+import { cn } from 'ui'
interface Props {
outerClassName?: string
diff --git a/apps/www/components/Products/index.tsx b/apps/www/components/Products/index.tsx
index d2a23e446a9..8fc5f1bc5b8 100644
--- a/apps/www/components/Products/index.tsx
+++ b/apps/www/components/Products/index.tsx
@@ -1,3 +1,5 @@
+'use client'
+
import SectionContainer from '~/components/Layouts/SectionContainer'
import type { ProductType } from '~/data/MainProducts'
import { useSendTelemetryEvent } from '~/lib/telemetry'
diff --git a/apps/www/package.json b/apps/www/package.json
index 6df69f53cf0..ef089bd4f73 100644
--- a/apps/www/package.json
+++ b/apps/www/package.json
@@ -83,6 +83,7 @@
"remark": "^15.0.1",
"remark-gfm": "^4.0.1",
"shared-data": "workspace:*",
+ "shiki": "^4.0.1",
"swiper": "^12.1.2",
"typed.js": "^2.0.16",
"typescript": "catalog:",
diff --git a/apps/www/public/images/customers/logos/chatbase-icon.svg b/apps/www/public/images/customers/logos/chatbase-icon.svg
new file mode 100644
index 00000000000..e772486c2a6
--- /dev/null
+++ b/apps/www/public/images/customers/logos/chatbase-icon.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/apps/www/public/images/customers/logos/hyper-icon.svg b/apps/www/public/images/customers/logos/hyper-icon.svg
new file mode 100644
index 00000000000..790af453eec
--- /dev/null
+++ b/apps/www/public/images/customers/logos/hyper-icon.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/www/public/images/customers/logos/markprompt-icon.svg b/apps/www/public/images/customers/logos/markprompt-icon.svg
new file mode 100644
index 00000000000..0e288cb1d8e
--- /dev/null
+++ b/apps/www/public/images/customers/logos/markprompt-icon.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a3b275f3579..c5668a8e752 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1751,6 +1751,9 @@ importers:
shared-data:
specifier: workspace:*
version: link:../../packages/shared-data
+ shiki:
+ specifier: ^4.0.1
+ version: 4.0.1
swiper:
specifier: ^12.1.2
version: 12.1.2
@@ -7937,24 +7940,52 @@ packages:
'@shikijs/core@3.13.0':
resolution: {integrity: sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA==}
+ '@shikijs/core@4.0.1':
+ resolution: {integrity: sha512-vWvqi9JNgz1dRL9Nvog5wtx7RuNkf7MEPl2mU/cyUUxJeH1CAr3t+81h8zO8zs7DK6cKLMoU9TvukWIDjP4Lzg==}
+ engines: {node: '>=20'}
+
'@shikijs/engine-javascript@3.13.0':
resolution: {integrity: sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg==}
+ '@shikijs/engine-javascript@4.0.1':
+ resolution: {integrity: sha512-DJK9NiwtGYqMuKCRO4Ip0FKNDQpmaiS+K5bFjJ7DWFn4zHueDWgaUG8kAofkrnXF6zPPYYQY7J5FYVW9MbZyBg==}
+ engines: {node: '>=20'}
+
'@shikijs/engine-oniguruma@3.13.0':
resolution: {integrity: sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==}
+ '@shikijs/engine-oniguruma@4.0.1':
+ resolution: {integrity: sha512-oCWdCTDch3J8Kc0OZJ98KuUPC02O1VqIE3W/e2uvrHqTxYRR21RGEJMtchrgrxhsoJJCzmIciKsqG+q/yD+Cxg==}
+ engines: {node: '>=20'}
+
'@shikijs/langs@3.13.0':
resolution: {integrity: sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==}
+ '@shikijs/langs@4.0.1':
+ resolution: {integrity: sha512-v/mluaybWdnGJR4GqAR6zh8qAZohW9k+cGYT28Y7M8+jLbC0l4yG085O1A+WkseHTn+awd+P3UBymb2+MXFc8w==}
+ engines: {node: '>=20'}
+
+ '@shikijs/primitive@4.0.1':
+ resolution: {integrity: sha512-ns0hHZc5eWZuvuIEJz2pTx3Qecz0aRVYumVQJ8JgWY2tq/dH8WxdcVM49Fc2NsHEILNIT6vfdW9MF26RANWiTA==}
+ engines: {node: '>=20'}
+
'@shikijs/themes@3.13.0':
resolution: {integrity: sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==}
+ '@shikijs/themes@4.0.1':
+ resolution: {integrity: sha512-FW41C/D6j/yKQkzVdjrRPiJCtgeDaYRJFEyCKFCINuRJRj9WcmubhP4KQHPZ4+9eT87jruSrYPyoblNRyDFzvA==}
+ engines: {node: '>=20'}
+
'@shikijs/transformers@1.6.0':
resolution: {integrity: sha512-qGfHe1ECiqfE2STPWvfogIj/9Q0SK+MCRJdoITkW7AmFuB7DmbFnBT2US84+zklJOB51MzNO8RUXZiauWssJlQ==}
'@shikijs/types@3.13.0':
resolution: {integrity: sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==}
+ '@shikijs/types@4.0.1':
+ resolution: {integrity: sha512-EaygPEn57+jJ76mw+nTLvIpJMAcMPokFbrF8lufsZP7Ukk+ToJYEcswN1G0e49nUZAq7aCQtoeW219A8HK1ZOw==}
+ engines: {node: '>=20'}
+
'@shikijs/vscode-textmate@10.0.2':
resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
@@ -14880,6 +14911,9 @@ packages:
oniguruma-to-es@4.3.3:
resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==}
+ oniguruma-to-es@4.3.4:
+ resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==}
+
oo-ascii-tree@1.98.0:
resolution: {integrity: sha512-+GE7ywhtS6MctbfcO+vZzqIxcFzucZCwmawcwCVo89DxQDakV1JFfFViTXG4A90UzTAsU4tQteGmwDtwOlOXLw==}
engines: {node: '>= 14.17.0'}
@@ -16726,6 +16760,10 @@ packages:
shiki@3.13.0:
resolution: {integrity: sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g==}
+ shiki@4.0.1:
+ resolution: {integrity: sha512-EkAEhDTN5WhpoQFXFw79OHIrSAfHhlImeCdSyg4u4XvrpxKEmdo/9x/HWSowujAnUrFsGOwWiE58a6GVentMnQ==}
+ engines: {node: '>=20'}
+
should-equal@2.0.0:
resolution: {integrity: sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==}
@@ -25119,25 +25157,58 @@ snapshots:
'@types/hast': 3.0.4
hast-util-to-html: 9.0.5
+ '@shikijs/core@4.0.1':
+ dependencies:
+ '@shikijs/primitive': 4.0.1
+ '@shikijs/types': 4.0.1
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+ hast-util-to-html: 9.0.5
+
'@shikijs/engine-javascript@3.13.0':
dependencies:
'@shikijs/types': 3.13.0
'@shikijs/vscode-textmate': 10.0.2
oniguruma-to-es: 4.3.3
+ '@shikijs/engine-javascript@4.0.1':
+ dependencies:
+ '@shikijs/types': 4.0.1
+ '@shikijs/vscode-textmate': 10.0.2
+ oniguruma-to-es: 4.3.4
+
'@shikijs/engine-oniguruma@3.13.0':
dependencies:
'@shikijs/types': 3.13.0
'@shikijs/vscode-textmate': 10.0.2
+ '@shikijs/engine-oniguruma@4.0.1':
+ dependencies:
+ '@shikijs/types': 4.0.1
+ '@shikijs/vscode-textmate': 10.0.2
+
'@shikijs/langs@3.13.0':
dependencies:
'@shikijs/types': 3.13.0
+ '@shikijs/langs@4.0.1':
+ dependencies:
+ '@shikijs/types': 4.0.1
+
+ '@shikijs/primitive@4.0.1':
+ dependencies:
+ '@shikijs/types': 4.0.1
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+
'@shikijs/themes@3.13.0':
dependencies:
'@shikijs/types': 3.13.0
+ '@shikijs/themes@4.0.1':
+ dependencies:
+ '@shikijs/types': 4.0.1
+
'@shikijs/transformers@1.6.0':
dependencies:
shiki: 1.6.0
@@ -25147,6 +25218,11 @@ snapshots:
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
+ '@shikijs/types@4.0.1':
+ dependencies:
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+
'@shikijs/vscode-textmate@10.0.2': {}
'@shuding/opentype.js@1.4.0-beta.0':
@@ -33806,6 +33882,12 @@ snapshots:
regex: 6.0.1
regex-recursion: 6.0.2
+ oniguruma-to-es@4.3.4:
+ dependencies:
+ oniguruma-parser: 0.12.1
+ regex: 6.0.1
+ regex-recursion: 6.0.2
+
oo-ascii-tree@1.98.0: {}
open@10.2.0:
@@ -36052,6 +36134,17 @@ snapshots:
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
+ shiki@4.0.1:
+ dependencies:
+ '@shikijs/core': 4.0.1
+ '@shikijs/engine-javascript': 4.0.1
+ '@shikijs/engine-oniguruma': 4.0.1
+ '@shikijs/langs': 4.0.1
+ '@shikijs/themes': 4.0.1
+ '@shikijs/types': 4.0.1
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+
should-equal@2.0.0:
dependencies:
should-type: 1.4.0