diff --git a/apps/www/components/Enterprise/Performance.tsx b/apps/www/components/Enterprise/Performance.tsx index 73a32399be834..abffa0336dc80 100644 --- a/apps/www/components/Enterprise/Performance.tsx +++ b/apps/www/components/Enterprise/Performance.tsx @@ -1,7 +1,7 @@ import React, { FC } from 'react' -import { cn, Badge } from 'ui' import SectionContainer from '~/components/Layouts/SectionContainer' +import UsersGrowthChart from '~/components/UsersGrowthChart' interface Props { id: string @@ -30,71 +30,11 @@ const Performance: FC = (props) => { ))} -
- - - - - - - - - - - -
-
+ ) } -const GraphLabel: FC<{ className?: string }> = ({ className }) => ( -
-
- Users -
- 930,550 - - +13.4% - -
-
-
-
-) - interface HighlightItemProps { highlight: Highlight } diff --git a/apps/www/components/Solutions/ResultsSection.tsx b/apps/www/components/Solutions/ResultsSection.tsx index 16b9c6d7cdcde..1681cf7506237 100644 --- a/apps/www/components/Solutions/ResultsSection.tsx +++ b/apps/www/components/Solutions/ResultsSection.tsx @@ -1,8 +1,7 @@ import React, { FC } from 'react' -import { cn, Badge, AnimatedCounter } from 'ui' + import SectionContainer from '~/components/Layouts/SectionContainer' -import { motion } from 'framer-motion' -import { useMedia } from 'react-use' +import UsersGrowthChart from '~/components/UsersGrowthChart' export interface ResultsSectionProps { id: string @@ -31,127 +30,11 @@ const ResultsSection: FC = (props) => { ))}
-
- - - - - - - - - - - - - -
-
+ ) } -const GraphLabel: FC<{ className?: string }> = ({ className }) => { - const isMobileOrTablet = useMedia('(max-width: 1280px)') - - const motionProps = !isMobileOrTablet - ? { - initial: { offsetDistance: '0%', rotate: '0deg', opacity: 0 }, - whileInView: { offsetDistance: '80%', rotate: '30deg', opacity: 1 }, - viewport: { once: true }, - transition: { type: 'spring', duration: 2.68, bounce: 0, delay: 0.25 }, - style: { - offsetPath: 'path("M0 493.132C285.852 493.132 896.213 411.11 1401.8 1")', - }, - } - : undefined - - const Component = isMobileOrTablet ? 'div' : motion.div - - return ( - -
- Users -
- - {isMobileOrTablet ? ( - '5,230,550' - ) : ( - - )} - - - {isMobileOrTablet ? ( - '+28.3%' - ) : ( - - )} - -
-
-
- - ) -} - -const GraphPath: FC<{ className?: string }> = ({ className }) => ( - - - -) - interface HighlightItemProps { highlight: Highlight } diff --git a/apps/www/components/Supasquad/PerfectTiming.tsx b/apps/www/components/Supasquad/PerfectTiming.tsx index 644f2af6e20ce..5f2ebba74c0c7 100644 --- a/apps/www/components/Supasquad/PerfectTiming.tsx +++ b/apps/www/components/Supasquad/PerfectTiming.tsx @@ -1,7 +1,7 @@ import React, { FC } from 'react' -import { cn, Badge } from 'ui' + import SectionContainer from '~/components/Layouts/SectionContainer' -import { companyStats } from '~/data/company-stats' +import UsersGrowthChart from '~/components/UsersGrowthChart' export interface PerfectTimingProps { id: string @@ -30,71 +30,11 @@ const PerfectTiming = (props: PerfectTimingProps) => { ))}
-
- - - - - - - - - - - -
-
+ ) } -const GraphLabel: FC<{ className?: string }> = ({ className }) => ( -
-
- Developers -
- {companyStats.developersRegistered} - - {companyStats.developersRegisteredChange} - -
-
-
-
-) - interface HighlightItemProps { highlight: Highlight } diff --git a/apps/www/components/UsersGrowthChart.tsx b/apps/www/components/UsersGrowthChart.tsx new file mode 100644 index 0000000000000..a6c10a72b535b --- /dev/null +++ b/apps/www/components/UsersGrowthChart.tsx @@ -0,0 +1,150 @@ +import React, { FC, useMemo, useEffect, useState } from 'react' +import { cn, Badge, AnimatedCounter } from 'ui' +import { motion } from 'framer-motion' +import { useMedia } from 'react-use' + +import { companyStats } from '~/data/company-stats' + +const UsersGrowthChart: FC = () => { + return ( +
+ + + + + + + + + + + + + +
+
+ ) +} + +const GraphLabel: FC<{ className?: string }> = ({ className }) => { + const isMobileOrTablet = useMedia('(max-width: 1280px)') + const [isClient, setIsClient] = useState(false) + + useEffect(() => { + setIsClient(true) + }, []) + + // Use client-side flag to prevent hydration mismatch + const shouldShowAnimated = useMemo(() => { + // Always show animated version until client-side hydration is complete + if (!isClient) return true + return !isMobileOrTablet + }, [isClient, isMobileOrTablet]) + + // Use the same server-safe logic for motion props + const motionProps = shouldShowAnimated + ? { + initial: { offsetDistance: '0%', rotate: '0deg', opacity: 0 }, + whileInView: { offsetDistance: '62%', rotate: '23.4deg', opacity: 1 }, + viewport: { once: true }, + transition: { type: 'spring', duration: 2.68, bounce: 0, delay: 0.25 }, + style: { + offsetPath: 'path("M0 493.132C285.852 493.132 896.213 411.11 1401.8 1")', + }, + } + : undefined + + // Use the same server-safe logic for component selection + const Component = shouldShowAnimated ? motion.div : 'div' + + return ( + +
+ + {companyStats.developersRegistered.label} + +
+ + {shouldShowAnimated ? ( + + ) : ( + companyStats.developersRegistered.text + )} + + + {shouldShowAnimated ? ( + + ) : ( + companyStats.developersRegisteredChange.text + )} + +
+
+
+ + ) +} + +const GraphPath: FC<{ className?: string }> = ({ className }) => ( + + + +) + +export default UsersGrowthChart diff --git a/apps/www/data/company-stats.ts b/apps/www/data/company-stats.ts index 9c7337873463f..d078c071ac85b 100644 --- a/apps/www/data/company-stats.ts +++ b/apps/www/data/company-stats.ts @@ -1,6 +1,25 @@ +// For each stat we provide text and number +// Text is used for labels and static components +// Number is used for animated components (eg. AnimatedCounter) export const companyStats = { - databasesManaged: '6,500,000+', - databasesLaunchedDaily: '35,000+', - developersRegistered: '2,500,000+', - developersRegisteredChange: '+13.4%', + databasesManaged: { + number: 6_500_000, + text: '6,500,000+', + label: 'Databases managed', + }, + databasesLaunchedDaily: { + number: 35_000, + text: '35,000+', + label: 'Databases launched daily', + }, + developersRegistered: { + number: 2_500_000, + text: '2,500,000+', + label: 'Users', + }, + developersRegisteredChange: { + number: 13.4, + text: '+13.4%', + label: 'Users growth', + }, } diff --git a/apps/www/data/enterprise.tsx b/apps/www/data/enterprise.tsx index c1453958d41d6..746a8478e4452 100644 --- a/apps/www/data/enterprise.tsx +++ b/apps/www/data/enterprise.tsx @@ -139,12 +139,12 @@ export default { "Supabase ensures optimal database performance at any scale, so you can focus on innovating and growing without worrying about infrastructure limitations—whether you're handling high-traffic applications, complex queries, or massive data volumes.", highlights: [ { - heading: 'Databases managed', - subheading: companyStats.databasesManaged, + heading: companyStats.databasesManaged.label, + subheading: companyStats.databasesManaged.text, }, { - heading: 'Databases launched daily', - subheading: companyStats.databasesLaunchedDaily, + heading: companyStats.databasesLaunchedDaily.label, + subheading: companyStats.databasesLaunchedDaily.text, }, ], }, diff --git a/apps/www/data/ga.tsx b/apps/www/data/ga.tsx index 016bda3d1e34c..95fbbaf7c4368 100644 --- a/apps/www/data/ga.tsx +++ b/apps/www/data/ga.tsx @@ -20,11 +20,11 @@ export const data = (isDark: boolean) => ({ highlightsSection: { highlights: [ { - number: companyStats.databasesManaged, + number: companyStats.databasesManaged.text, text: 'databases managed', }, { - number: companyStats.databasesLaunchedDaily, + number: companyStats.databasesLaunchedDaily.text, text: 'databases launched daily', }, { diff --git a/apps/www/data/open-source/contributing/supasquad.tsx b/apps/www/data/open-source/contributing/supasquad.tsx index 89c0f669637c3..7897c9dd98c9f 100644 --- a/apps/www/data/open-source/contributing/supasquad.tsx +++ b/apps/www/data/open-source/contributing/supasquad.tsx @@ -138,12 +138,12 @@ export const data = { "Supabase's explosive growth means more builders need help. There are more opportunities to contribute, and more ways to make your mark. Join SupaSquad and help us support this thriving ecosystem of builders.", highlights: [ { - heading: 'databases managed', - subheading: companyStats.databasesManaged, + heading: companyStats.databasesManaged.label, + subheading: companyStats.databasesManaged.text, }, { - heading: 'databases launched daily', - subheading: companyStats.databasesLaunchedDaily, + heading: companyStats.databasesLaunchedDaily.label, + subheading: companyStats.databasesLaunchedDaily.text, }, ], }, diff --git a/apps/www/data/solutions/developers.tsx b/apps/www/data/solutions/developers.tsx index c07be712c534a..31a440f5da5f3 100644 --- a/apps/www/data/solutions/developers.tsx +++ b/apps/www/data/solutions/developers.tsx @@ -595,12 +595,12 @@ const data: () => { "Supabase ensures optimal database performance at any scale, so you can focus on innovating and growing without worrying about infrastructure limitations — whether you're handling high-traffic applications, complex queries, or massive data volumes.", highlights: [ { - heading: 'databases managed', - subheading: companyStats.databasesManaged, + heading: companyStats.databasesManaged.label, + subheading: companyStats.databasesManaged.text, }, { - heading: 'databases launched daily', - subheading: companyStats.databasesLaunchedDaily, + heading: companyStats.databasesLaunchedDaily.label, + subheading: companyStats.databasesLaunchedDaily.text, }, ], }, diff --git a/apps/www/data/solutions/firebase.tsx b/apps/www/data/solutions/firebase.tsx index f420b6a6413b1..d0d6d89c84a82 100644 --- a/apps/www/data/solutions/firebase.tsx +++ b/apps/www/data/solutions/firebase.tsx @@ -548,12 +548,12 @@ const data = { "Supabase ensures optimal database performance at any scale, so you can focus on innovating and growing without worrying about infrastructure limitations — whether you're handling high-traffic applications, complex queries, or massive data volumes.", highlights: [ { - heading: 'databases managed', - subheading: companyStats.databasesManaged, + heading: companyStats.databasesManaged.label, + subheading: companyStats.databasesManaged.text, }, { - heading: 'databases launched daily', - subheading: companyStats.databasesLaunchedDaily, + heading: companyStats.databasesLaunchedDaily.label, + subheading: companyStats.databasesLaunchedDaily.text, }, ], }, diff --git a/apps/www/data/solutions/innovation-teams.tsx b/apps/www/data/solutions/innovation-teams.tsx index 34554605104c6..553025183096d 100644 --- a/apps/www/data/solutions/innovation-teams.tsx +++ b/apps/www/data/solutions/innovation-teams.tsx @@ -596,12 +596,12 @@ const data: () => { "Supabase ensures optimal database performance at any scale, so you can focus on innovating and growing without worrying about infrastructure limitations — whether you're handling high-traffic applications, complex queries, or massive data volumes.", highlights: [ { - heading: 'databases managed', - subheading: companyStats.databasesManaged, + heading: companyStats.databasesManaged.label, + subheading: companyStats.databasesManaged.text, }, { - heading: 'databases launched daily', - subheading: companyStats.databasesLaunchedDaily, + heading: companyStats.databasesLaunchedDaily.label, + subheading: companyStats.databasesLaunchedDaily.text, }, ], }, diff --git a/apps/www/data/solutions/neon.tsx b/apps/www/data/solutions/neon.tsx index a2b1e3ed61842..95c99d95ae61e 100644 --- a/apps/www/data/solutions/neon.tsx +++ b/apps/www/data/solutions/neon.tsx @@ -540,12 +540,12 @@ const data = { "Supabase ensures optimal database performance at any scale, so you can focus on innovating and growing without worrying about infrastructure limitations — whether you're handling high-traffic applications, complex queries, or massive data volumes.", highlights: [ { - heading: 'databases managed', - subheading: companyStats.databasesManaged, + heading: companyStats.databasesManaged.label, + subheading: companyStats.databasesManaged.text, }, { - heading: 'databases launched daily', - subheading: companyStats.databasesLaunchedDaily, + heading: companyStats.databasesLaunchedDaily.label, + subheading: companyStats.databasesLaunchedDaily.text, }, ], }, diff --git a/apps/www/data/solutions/postgres-developers.tsx b/apps/www/data/solutions/postgres-developers.tsx index 13584b2386650..7c682450d9c7a 100644 --- a/apps/www/data/solutions/postgres-developers.tsx +++ b/apps/www/data/solutions/postgres-developers.tsx @@ -695,12 +695,12 @@ const data: () => { "Supabase ensures optimal database performance at any scale, so you can focus on innovating and growing without worrying about infrastructure limitations — whether you're handling high-traffic applications, complex queries, or massive data volumes.", highlights: [ { - heading: 'databases managed', - subheading: companyStats.databasesManaged, + heading: companyStats.databasesManaged.label, + subheading: companyStats.databasesManaged.text, }, { - heading: 'databases launched daily', - subheading: companyStats.databasesLaunchedDaily, + heading: companyStats.databasesLaunchedDaily.label, + subheading: companyStats.databasesLaunchedDaily.text, }, ], }, diff --git a/apps/www/data/solutions/startups.tsx b/apps/www/data/solutions/startups.tsx index 1b59fdfdb2a4f..8ef98a86132d6 100644 --- a/apps/www/data/solutions/startups.tsx +++ b/apps/www/data/solutions/startups.tsx @@ -584,12 +584,12 @@ const data: () => { "Supabase ensures optimal database performance at any scale, so you can focus on innovating and growing without worrying about infrastructure limitations — whether you're handling high-traffic applications, complex queries, or massive data volumes.", highlights: [ { - heading: 'databases managed', - subheading: companyStats.databasesManaged, + heading: companyStats.databasesManaged.label, + subheading: companyStats.databasesManaged.text, }, { - heading: 'databases launched daily', - subheading: companyStats.databasesLaunchedDaily, + heading: companyStats.databasesLaunchedDaily.label, + subheading: companyStats.databasesLaunchedDaily.text, }, ], }, diff --git a/packages/ui/src/components/AnimatedCounter/AnimatedCounter.tsx b/packages/ui/src/components/AnimatedCounter/AnimatedCounter.tsx index f47274aff1e4f..b3bd56aa0390f 100644 --- a/packages/ui/src/components/AnimatedCounter/AnimatedCounter.tsx +++ b/packages/ui/src/components/AnimatedCounter/AnimatedCounter.tsx @@ -61,7 +61,7 @@ export interface AnimatedCounterProps { *