@@ -6,13 +6,247 @@ import {
66 SUBSCRIPTION_DISPLAY_NAME ,
77 type SubscriptionTierPrice ,
88} from '@codebuff/common/constants/subscription-plans'
9- import { Gift , Shield , Sparkles , Zap , Brain } from 'lucide-react'
10- import Link from 'next/link'
9+ import { env } from '@codebuff/common/env'
10+ import { loadStripe } from '@stripe/stripe-js'
11+ import { motion } from 'framer-motion'
12+ import { Gift , Shield , Loader2 } from 'lucide-react'
13+ import { useRouter } from 'next/navigation'
1114import { useSession } from 'next-auth/react'
15+ import { useState } from 'react'
1216
1317import { BlockColor } from '@/components/ui/decorative-blocks'
1418import { SECTION_THEMES } from '@/components/ui/landing/constants'
1519import { FeatureSection } from '@/components/ui/landing/feature'
20+ import { toast } from '@/components/ui/use-toast'
21+ import { cn } from '@/lib/utils'
22+
23+ const USAGE_MULTIPLIER : Record < number , string > = {
24+ 100 : '1×' ,
25+ 200 : '3×' ,
26+ 500 : '8×' ,
27+ }
28+
29+ function SubscribeButton ( {
30+ className,
31+ tier,
32+ } : {
33+ className ?: string
34+ tier ?: number
35+ } ) {
36+ const { status } = useSession ( )
37+ const router = useRouter ( )
38+ const [ isLoading , setIsLoading ] = useState ( false )
39+
40+ const handleSubscribe = async ( ) => {
41+ if ( status !== 'authenticated' ) {
42+ router . push ( '/login?callbackUrl=/pricing' )
43+ return
44+ }
45+
46+ setIsLoading ( true )
47+ try {
48+ const res = await fetch ( '/api/stripe/create-subscription' , {
49+ method : 'POST' ,
50+ headers : { 'Content-Type' : 'application/json' } ,
51+ body : JSON . stringify ( { tier } ) ,
52+ } )
53+ if ( ! res . ok ) {
54+ const err = await res . json ( ) . catch ( ( ) => ( { } ) )
55+ throw new Error ( err . error || 'Failed to start checkout' )
56+ }
57+ const { sessionId } = await res . json ( )
58+ const stripe = await loadStripe ( env . NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY )
59+ if ( ! stripe ) throw new Error ( 'Stripe failed to load' )
60+ const { error } = await stripe . redirectToCheckout ( { sessionId } )
61+ if ( error ) throw new Error ( error . message )
62+ } catch ( err ) {
63+ toast ( {
64+ title : 'Error' ,
65+ description :
66+ err instanceof Error ? err . message : 'Something went wrong' ,
67+ variant : 'destructive' ,
68+ } )
69+ } finally {
70+ setIsLoading ( false )
71+ }
72+ }
73+
74+ return (
75+ < button
76+ onClick = { handleSubscribe }
77+ disabled = { isLoading }
78+ className = { cn (
79+ 'inline-flex items-center justify-center gap-2 rounded-lg px-3 py-2 sm:px-10 sm:py-3.5 text-xs sm:text-base font-semibold transition-all duration-200' ,
80+ 'bg-acid-green text-black hover:bg-acid-green/90 shadow-[0_0_30px_rgba(0,255,149,0.2)] hover:shadow-[0_0_50px_rgba(0,255,149,0.3)]' ,
81+ 'disabled:opacity-60 disabled:cursor-not-allowed' ,
82+ className ,
83+ ) }
84+ >
85+ { isLoading ? (
86+ < Loader2 className = "h-5 w-5 animate-spin" />
87+ ) : (
88+ < > Subscribe</ >
89+ ) }
90+ </ button >
91+ )
92+ }
93+
94+ function StrongHeroSection ( ) {
95+ return (
96+ < div className = "min-h-[calc(100vh-64px)] bg-black flex flex-col items-center justify-center relative overflow-hidden px-4 py-12" >
97+ { /* Subtle radial glow behind content */ }
98+ < div
99+ className = "absolute inset-0 pointer-events-none"
100+ style = { {
101+ background :
102+ 'radial-gradient(ellipse 60% 50% at 50% 40%, rgba(0,255,149,0.06) 0%, transparent 70%)' ,
103+ } }
104+ />
105+
106+ { /* Animated gradient blobs */ }
107+ < div className = "absolute inset-0 pointer-events-none" aria-hidden = "true" >
108+ < motion . div
109+ className = "absolute -inset-[200px] opacity-70"
110+ style = { {
111+ background :
112+ 'radial-gradient(circle at 30% 40%, rgba(0,255,149,0.1) 0%, transparent 50%)' ,
113+ filter : 'blur(40px)' ,
114+ } }
115+ animate = { {
116+ x : [ 0 , 100 , - 50 , 0 ] ,
117+ y : [ 0 , - 80 , 60 , 0 ] ,
118+ scale : [ 1 , 1.1 , 0.95 , 1 ] ,
119+ } }
120+ transition = { { duration : 18 , repeat : Infinity , ease : 'easeInOut' } }
121+ />
122+ < motion . div
123+ className = "absolute -inset-[200px] opacity-70"
124+ style = { {
125+ background :
126+ 'radial-gradient(circle at 70% 60%, rgba(0,255,149,0.07) 0%, transparent 50%)' ,
127+ filter : 'blur(40px)' ,
128+ } }
129+ animate = { {
130+ x : [ 0 , - 80 , 60 , 0 ] ,
131+ y : [ 0 , 50 , - 70 , 0 ] ,
132+ scale : [ 1 , 0.95 , 1.1 , 1 ] ,
133+ } }
134+ transition = { { duration : 22 , repeat : Infinity , ease : 'easeInOut' } }
135+ />
136+ </ div >
137+
138+ { /* Giant background text */ }
139+ < motion . div
140+ className = "absolute inset-0 flex items-center justify-center select-none pointer-events-none"
141+ aria-hidden = "true"
142+ style = { {
143+ fontSize : 'clamp(6rem, 22vw, 20rem)' ,
144+ fontWeight : 900 ,
145+ letterSpacing : '-0.02em' ,
146+ lineHeight : 1 ,
147+ color : 'transparent' ,
148+ WebkitTextStroke : '1.5px rgba(0,255,149,0.11)' ,
149+ background :
150+ 'linear-gradient(180deg, rgba(0,255,149,0.14) 0%, rgba(0,255,149,0.02) 100%)' ,
151+ WebkitBackgroundClip : 'text' ,
152+ backgroundClip : 'text' ,
153+ } }
154+ initial = { { opacity : 0 , scale : 0.95 } }
155+ animate = { { opacity : 1 , scale : 1 } }
156+ transition = { { duration : 2 , ease : [ 0.16 , 1 , 0.3 , 1 ] } }
157+ >
158+ { SUBSCRIPTION_DISPLAY_NAME . toUpperCase ( ) }
159+ </ motion . div >
160+
161+ { /* Foreground content */ }
162+ < div className = "relative z-10 flex flex-col items-center text-center max-w-4xl" >
163+ < div className = "max-w-2xl" >
164+ < motion . p
165+ className = "font-mono text-xs sm:text-sm tracking-[0.3em] text-acid-green/50 uppercase mb-8"
166+ initial = { { opacity : 0 } }
167+ animate = { { opacity : 1 } }
168+ transition = { { duration : 0.8 , delay : 0.5 } }
169+ >
170+ codebuff
171+ </ motion . p >
172+
173+ < motion . h1
174+ className = "text-4xl sm:text-5xl md:text-5xl font-bold text-white mb-3 tracking-tight"
175+ initial = { { opacity : 0 , y : 20 } }
176+ animate = { { opacity : 1 , y : 0 } }
177+ transition = { { duration : 0.7 , delay : 0.7 } }
178+ >
179+ The strongest coding agent
180+ </ motion . h1 >
181+
182+ < motion . p
183+ className = "text-base sm:text-lg text-white/50 mb-12 font-light"
184+ initial = { { opacity : 0 , y : 20 } }
185+ animate = { { opacity : 1 , y : 0 } }
186+ transition = { { duration : 0.7 , delay : 0.9 } }
187+ >
188+ Deep thinking. Multi-agent orchestration. Ship faster.
189+ </ motion . p >
190+ </ div >
191+
192+ { /* Pricing cards grid */ }
193+ < motion . div
194+ className = "grid grid-cols-3 gap-2 sm:gap-5 mb-10 w-full"
195+ initial = { { opacity : 0 , y : 20 } }
196+ animate = { { opacity : 1 , y : 0 } }
197+ transition = { { duration : 0.7 , delay : 1.1 } }
198+ >
199+ { Object . entries ( SUBSCRIPTION_TIERS ) . map ( ( [ key , tier ] ) => {
200+ const price = Number ( key ) as SubscriptionTierPrice
201+ const isHighlighted = price === 200
202+
203+ return (
204+ < div
205+ key = { price }
206+ className = { cn (
207+ 'rounded-xl p-3 sm:p-8 backdrop-blur-sm border flex flex-col items-center transition-all duration-300' ,
208+ 'hover:scale-[1.02]' ,
209+ isHighlighted
210+ ? 'border-acid-green/30 bg-acid-green/[0.04] shadow-[0_0_40px_rgba(0,255,149,0.08)] hover:shadow-[0_0_60px_rgba(0,255,149,0.15)]'
211+ : 'border-white/10 bg-white/[0.02] hover:border-white/20 hover:bg-white/[0.04]' ,
212+ ) }
213+ >
214+ < div className = "flex items-baseline justify-center gap-1 mb-1" >
215+ < span className = "text-xl sm:text-5xl font-bold text-white tracking-tight" >
216+ ${ tier . monthlyPrice }
217+ </ span >
218+ < span className = "text-xs sm:text-sm text-white/30" > /mo</ span >
219+ </ div >
220+
221+ < p className = "text-xs sm:text-sm text-white/40 mb-3 sm:mb-6" >
222+ { USAGE_MULTIPLIER [ price ] } usage
223+ </ p >
224+
225+ < SubscribeButton
226+ tier = { price }
227+ className = { cn (
228+ 'w-full' ,
229+ ! isHighlighted &&
230+ 'bg-white/10 text-white hover:bg-white/20 shadow-none hover:shadow-none' ,
231+ ) }
232+ />
233+ </ div >
234+ )
235+ } ) }
236+ </ motion . div >
237+
238+ < motion . p
239+ className = "text-xs text-white/30 tracking-wide"
240+ initial = { { opacity : 0 } }
241+ animate = { { opacity : 1 } }
242+ transition = { { duration : 0.8 , delay : 1.6 } }
243+ >
244+ Cancel anytime · Tax not included · Usage amounts subject to change
245+ </ motion . p >
246+ </ div >
247+ </ div >
248+ )
249+ }
16250
17251function CreditVisual ( ) {
18252 return (
@@ -68,97 +302,6 @@ function PricingCard() {
68302 )
69303}
70304
71- const USAGE_MULTIPLIER : Record < number , string > = {
72- 100 : '1×' ,
73- 200 : '3×' ,
74- 500 : '8×' ,
75- }
76-
77- function StrongSubscriptionIllustration ( ) {
78- return (
79- < div className = "flex flex-col items-center text-center pt-6" >
80- < div className = "bg-black rounded-2xl border border-white/10 shadow-[0_0_60px_rgba(0,255,149,0.08)] p-6 w-full max-w-md" >
81- < div className = "flex flex-col items-center space-y-5" >
82- { /* Header with Strong branding */ }
83- < div className = "flex flex-col items-center gap-1" >
84- < div className = "flex items-center gap-3" >
85- < div className = "p-2 rounded-full bg-acid-green/10 border border-acid-green/20" >
86- < Sparkles className = "h-5 w-5 text-acid-green" />
87- </ div >
88- < div className = "text-xl font-bold text-white" >
89- { SUBSCRIPTION_DISPLAY_NAME } Subscription
90- </ div >
91- </ div >
92- < div className = "text-xs text-white/40" > Monthly plans</ div >
93- </ div >
94-
95- { /* Benefits */ }
96- < div className = "grid grid-cols-1 gap-2 w-full" >
97- < div className = "flex items-center gap-3 bg-white/5 rounded-lg p-3 border border-white/5" >
98- < div className = "p-1.5 rounded-full bg-acid-green/10" >
99- < Brain className = "h-4 w-4 text-acid-green" />
100- </ div >
101- < div className = "text-left" >
102- < div className = "text-sm font-medium text-white" > Deep thinking</ div >
103- < div className = "text-xs text-white/40" >
104- Multi-agent orchestration for complex tasks
105- </ div >
106- </ div >
107- </ div >
108-
109- < div className = "flex items-center gap-3 bg-white/5 rounded-lg p-3 border border-white/5" >
110- < div className = "p-1.5 rounded-full bg-acid-green/10" >
111- < Zap className = "h-4 w-4 text-acid-green" />
112- </ div >
113- < div className = "text-left" >
114- < div className = "text-sm font-medium text-white" > Save on credits</ div >
115- < div className = "text-xs text-white/40" >
116- Get more usage compared to pay-as-you-go
117- </ div >
118- </ div >
119- </ div >
120- </ div >
121-
122- { /* Pricing tiers */ }
123- < div className = "grid grid-cols-3 gap-2 w-full" >
124- { Object . entries ( SUBSCRIPTION_TIERS ) . map ( ( [ key , tier ] ) => {
125- const price = Number ( key ) as SubscriptionTierPrice
126- const isHighlighted = price === 200
127-
128- return (
129- < div
130- key = { price }
131- className = { `rounded-lg p-3 border flex flex-col items-center ${
132- isHighlighted
133- ? 'border-acid-green/30 bg-acid-green/[0.08]'
134- : 'border-white/10 bg-white/5'
135- } `}
136- >
137- < div className = "text-lg font-bold text-white" >
138- ${ tier . monthlyPrice }
139- </ div >
140- < div className = "text-xs text-white/40" > /mo</ div >
141- < div className = "text-xs text-white/50 mt-1" >
142- { USAGE_MULTIPLIER [ price ] } usage
143- </ div >
144- </ div >
145- )
146- } ) }
147- </ div >
148-
149- { /* CTA */ }
150- < Link
151- href = "/strong"
152- className = "w-full bg-white hover:bg-white/90 text-black font-semibold py-3 px-6 rounded-lg transition-colors text-center"
153- >
154- Learn More
155- </ Link >
156- </ div >
157- </ div >
158- </ div >
159- )
160- }
161-
162305function TeamPlanIllustration ( ) {
163306 return (
164307 < div className = "grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6 w-full max-w-screen-lg mx-auto" >
@@ -247,20 +390,7 @@ export default function PricingClient() {
247390
248391 return (
249392 < >
250- < FeatureSection
251- title = { < span > Codebuff { SUBSCRIPTION_DISPLAY_NAME } </ span > }
252- description = "Deep thinking, multi-agent orchestration, and unlimited potential. Subscribe to save credits with plans starting at $100/mo."
253- backdropColor = { BlockColor . DarkForestGreen }
254- decorativeColors = { [ BlockColor . DarkForestGreen , BlockColor . AcidMatrix ] }
255- textColor = "text-white"
256- tagline = "POWER SUBSCRIPTION"
257- highlightText = "The strongest coding agent subscription"
258- highlightIcon = "💪"
259- illustration = { < StrongSubscriptionIllustration /> }
260- learnMoreText = "Learn More"
261- learnMoreLink = "/strong"
262- imagePosition = "left"
263- />
393+ < StrongHeroSection />
264394
265395 < FeatureSection
266396 title = { < span > Simple, Usage-Based Pricing</ span > }
0 commit comments