@@ -30,6 +30,7 @@ def radial_circle(violet: bool = False) -> rx.Component:
3030
3131
3232def glow () -> rx .Component :
33+ """Radial gradient glow effect for popular card."""
3334 return rx .html (
3435 """<svg xmlns="http://www.w3.org/2000/svg" width="502" height="580" viewBox="0 0 502 580" fill="none">
3536 <path d="M0 290C0 450.163 112.377 580 251 580C389.623 580 502 450.163 502 290C502 129.837 389.623 0 251 0C112.377 0 0 129.837 0 290Z" fill="url(#paint0_radial_13685_26666)"/>
@@ -45,6 +46,7 @@ def glow() -> rx.Component:
4546
4647
4748def grid () -> rx .Component :
49+ """Animated grid background for popular card."""
4850 return rx .html (
4951 """<svg width="326" height="472" viewBox="0 0 326 472" fill="none" xmlns="http://www.w3.org/2000/svg">
5052<g clip-path="url(#clip0_13685_24040)">
@@ -137,6 +139,99 @@ def grid() -> rx.Component:
137139 )
138140
139141
142+ def _get_price_label (title : str ) -> str :
143+ """Get the appropriate price label for each plan."""
144+ if title == "Hobby" :
145+ return "Free"
146+ elif title == "Enterprise" :
147+ return "" # No label for Enterprise (Custom pricing)
148+ else :
149+ return "From"
150+
151+
152+ def _render_price_display (price : str , title : str ) -> rx .Component :
153+ """Render the price display section with proper formatting."""
154+ if "user" in price :
155+ # Handle user-based pricing (e.g., "$49 user/month")
156+ parts = price .split (" " , 1 )
157+ return rx .el .div (
158+ rx .el .span (parts [0 ], class_name = "text-4xl font-bold text-slate-12" ),
159+ rx .el .span (f" { parts [1 ]} " , class_name = "text-sm text-slate-9 ml-2" ),
160+ class_name = "flex items-baseline"
161+ )
162+ else :
163+ # Handle regular pricing (e.g., "$25/month")
164+ main_price = price .split ("/" )[0 ] if "/" in price else price
165+ period = f" / { price .split ('/' )[1 ]} " if "/" in price else ""
166+ return rx .el .div (
167+ rx .el .span (main_price , class_name = "text-4xl font-bold text-slate-12" ),
168+ rx .el .span (period , class_name = "text-sm text-slate-9" ),
169+ class_name = "flex items-baseline"
170+ )
171+
172+
173+ def _render_messaging_section (title : str ) -> rx .Component :
174+ """Render the messaging/features section for each plan."""
175+ messaging_config = {
176+ "Hobby" : {
177+ "main" : "Reflex build 5 msgs/day" ,
178+ "sub" : rx .link ("Monthly cap 30 messages" , href = "#reflex-build" ,
179+ class_name = "text-xs text-slate-9 hover:text-slate-11 underline" )
180+ },
181+ "Pro" : {
182+ "main" : "Reflex build 100 msgs/month" ,
183+ "sub" : rx .link ("Upgrade to Team for more messages" , href = "#reflex-build" ,
184+ class_name = "text-xs text-slate-9 hover:text-slate-11 underline" )
185+ },
186+ "Enterprise" : {
187+ "main" : "Reflex build 500+ msgs/month" ,
188+ "sub" : rx .link ("More messages available on request" , href = "#reflex-build" ,
189+ class_name = "text-xs text-slate-9 hover:text-slate-11 underline" )
190+ }
191+ }
192+
193+ if title in messaging_config :
194+ config = messaging_config [title ]
195+ return rx .el .div (
196+ rx .el .p (config ["main" ], class_name = "text-sm font-medium text-slate-12 mt-4" ),
197+ rx .el .p (config ["sub" ]) if title == "Hobby" else config ["sub" ],
198+ class_name = "mt-4"
199+ )
200+ else :
201+ # Default spacing for plans without messaging section
202+ return rx .el .div (class_name = "h-[3.5rem]" )
203+
204+
205+ def _get_features_header (title : str ) -> str :
206+ """Get the appropriate features section header for each plan."""
207+ headers = {
208+ "Hobby" : "Get started with:" ,
209+ "Pro" : "Everything in the Free Plan, plus:" ,
210+ "Team" : "Everything in the Pro Plan, plus:" ,
211+ "Enterprise" : "Everything in Team, plus:"
212+ }
213+ return headers .get (title , "Features:" )
214+
215+
216+ def _render_feature_list (features : list [tuple [str , str ]]) -> rx .Component :
217+ """Render the feature list with consistent styling."""
218+ return rx .el .ul (
219+ * [
220+ rx .el .li (
221+ rx .icon ("check" , class_name = "!text-green-500" , size = 16 ),
222+ feature [1 ],
223+ rx .tooltip (
224+ rx .icon ("info" , class_name = "!text-slate-9" , size = 12 ),
225+ content = feature [2 ],
226+ ) if len (feature ) == 3 else "" ,
227+ class_name = "text-sm font-medium text-slate-11 flex items-center gap-3 mb-2" ,
228+ )
229+ for feature in features
230+ ],
231+ class_name = "flex flex-col" ,
232+ )
233+
234+
140235def card (
141236 title : str ,
142237 description : str ,
@@ -145,56 +240,43 @@ def card(
145240 price : str = None ,
146241 redirect_url : str = None ,
147242) -> rx .Component :
243+ """Standard pricing card component."""
148244 return rx .box (
149- rx .el .div (
150- rx .el .h3 (title , class_name = "font-semibold text-slate-12 text-2xl" ),
151- (
152- rx .badge (
153- price ,
154- color_scheme = "gray" ,
155- size = "3" ,
156- class_name = "font-medium 2xl:text-lg text-base w-fit" ,
157- )
158- if price
159- else rx .fragment ()
160- ),
161- class_name = "flex 2xl:items-center mb-2 2xl:gap-4 gap-2 2xl:flex-row flex-col" ,
162- ),
163- rx .el .p (
164- description , class_name = "text-sm font-medium text-slate-9 mb-8 text-pretty"
165- ),
166- rx .el .ul (
167- * [
168- rx .el .li (
169- rx .icon (feature [0 ], class_name = "!text-slate-9" , size = 16 ),
170- feature [1 ],
171- (
172- rx .tooltip (
173- rx .icon ("info" , class_name = "!text-slate-9" , size = 12 ),
174- content = feature [2 ],
175- )
176- if len (feature ) == 3
177- else ""
178- ),
179- class_name = "text-sm font-medium text-slate-11 flex items-center gap-3" ,
180- )
181- for feature in features
182- ],
183- class_name = "flex flex-col gap-2" ,
184- ),
185- rx .box (class_name = "flex-1" ),
245+ # Header
246+ rx .el .h3 (title , class_name = "font-semibold text-slate-12 text-2xl mb-4" ),
247+ rx .el .p (description , class_name = "text-sm font-medium text-slate-9 mb-6 text-pretty" ),
248+
249+ # CTA Button
186250 rx .link (
187251 button (
188252 button_text ,
189253 variant = "secondary" ,
190254 size = "lg" ,
191- class_name = "w-full" ,
255+ class_name = "w-full mb-6 " ,
192256 ),
193257 href = redirect_url ,
194258 is_external = True ,
195259 underline = "none" ,
196260 ),
197- class_name = "flex flex-col p-8 border border-slate-4 rounded-[1.125rem] shadow-small bg-slate-2 w-full min-w-0 h-[33.5rem] overflow-hidden" ,
261+
262+ # Pricing Section
263+ rx .el .div (
264+ rx .el .span (_get_price_label (title ), class_name = "text-sm text-slate-9 block mb-1" ),
265+ _render_price_display (price , title ),
266+ _render_messaging_section (title ),
267+ class_name = "mb-6"
268+ ),
269+
270+ # Divider
271+ rx .el .hr (class_name = "border-slate-3 mb-6" ),
272+
273+ # Features Section
274+ rx .el .div (
275+ rx .el .p (_get_features_header (title ), class_name = "text-sm font-medium text-slate-9 mb-4" ),
276+ _render_feature_list (features ),
277+ ),
278+
279+ class_name = "flex flex-col p-6 border border-slate-4 rounded-lg shadow-small bg-slate-2 w-full min-w-0 overflow-hidden h-[42rem]" ,
198280 )
199281
200282
@@ -205,62 +287,60 @@ def popular_card(
205287 button_text : str ,
206288 price : str = None ,
207289) -> rx .Component :
290+ """Popular pricing card component with special styling and effects."""
208291 return rx .box (
292+ # Popular Badge
209293 rx .box (
210- "Most popular " ,
294+ "Most Popular " ,
211295 class_name = "absolute top-[-0.75rem] left-8 rounded-md bg-[--violet-9] h-[1.5rem] text-sm font-medium text-center px-2 flex items-center justify-center text-[#FCFCFD] z-[10]" ,
212296 ),
297+
298+ # Card Content with Background Effects
213299 rx .box (
214300 glow (),
215301 grid (),
216- rx .el .div (
217- rx .el .h3 (title , class_name = "font-semibold text-slate-12 text-2xl" ),
218- (
219- rx .badge (
220- price ,
221- color_scheme = "violet" ,
222- size = "3" ,
223- class_name = "font-medium 2xl:text-lg text-base w-fit" ,
224- )
225- if price
226- else rx .fragment ()
227- ),
228- class_name = "flex 2xl:items-center mb-2 2xl:gap-4 gap-2 2xl:flex-row flex-col" ,
229- ),
230- rx .el .p (description , class_name = "text-sm font-medium text-slate-9 mb-8" ),
231- rx .el .ul (
232- * [
233- rx .el .li (
234- rx .icon (feature [0 ], class_name = "!text-violet-9" , size = 16 ),
235- feature [1 ],
236- (
237- rx .tooltip (
238- rx .icon ("info" , class_name = "!text-slate-9" , size = 12 ),
239- content = feature [2 ],
240- )
241- if len (feature ) == 3
242- else ""
243- ),
244- class_name = "text-sm font-medium text-slate-11 flex items-center gap-3" ,
245- )
246- for feature in features
247- ],
248- class_name = "flex flex-col gap-2" ,
249- ),
250- rx .box (class_name = "flex-1" ),
302+
303+ # Header
304+ rx .el .h3 (title , class_name = "font-semibold text-slate-12 text-2xl mb-4" ),
305+ rx .el .p (description , class_name = "text-sm font-medium text-slate-9 mb-6 text-pretty" ),
306+
307+ # CTA Button
251308 rx .link (
252309 button (
253310 button_text ,
254311 variant = "primary" ,
255312 size = "lg" ,
256- class_name = "w-full !text-sm !font-semibold" ,
313+ class_name = "w-full mb-6 !text-sm !font-semibold" ,
257314 ),
258315 href = f"{ REFLEX_CLOUD_URL } /?redirect_url={ REFLEX_CLOUD_URL } /billing/" ,
259316 is_external = True ,
260317 underline = "none" ,
261318 ),
262- class_name = "flex flex-col p-8 border border-[--violet-9] rounded-[1.125rem] w-full min-w-0 h-[33.5rem] relative z-[1] backdrop-blur-[6px] bg-[rgba(249,_249,_251,_0.48)] dark:bg-[rgba(26,_27,_29,_0.48)] shadow-[0px_2px_5px_0px_rgba(28_32_36_0.03)] overflow-hidden" ,
319+
320+ # Pricing Section
321+ rx .el .div (
322+ rx .el .span ("From" , class_name = "text-sm text-slate-9 block mb-1" ),
323+ _render_price_display (price , title ),
324+ rx .el .div (
325+ rx .el .p ("Reflex build 250 msgs/month" , class_name = "text-sm font-medium text-slate-12 mt-4" ),
326+ rx .link ("More messages available on request" , href = "#reflex-build" , class_name = "text-xs text-slate-9 hover:text-slate-11 underline" ),
327+ class_name = "mt-4"
328+ ),
329+ class_name = "mb-6"
330+ ),
331+
332+ # Divider
333+ rx .el .hr (class_name = "border-slate-3 mb-6" ),
334+
335+ # Features Section
336+ rx .el .div (
337+ rx .el .p ("Everything in the Pro Plan, plus:" , class_name = "text-sm font-medium text-slate-9 mb-4" ),
338+ _render_feature_list (features ),
339+ ),
340+
341+ class_name = "flex flex-col p-6 border-2 border-[--violet-9] rounded-lg w-full min-w-0 relative z-[1] backdrop-blur-[6px] bg-[rgba(249,_249,_251,_0.48)] dark:bg-[rgba(26,_27,_29,_0.48)] shadow-[0px_2px_5px_0px_rgba(28_32_36_0.03)] overflow-hidden h-[42rem]" ,
263342 ),
343+
264344 class_name = "relative" ,
265345 )
266346
@@ -271,69 +351,65 @@ def plan_cards() -> rx.Component:
271351 "Hobby" ,
272352 "Everything you need to get started." ,
273353 [
274- ("frame" , "Open Source Framework" ),
275- ("brain" , "AI App Builder (Limited Access)" ),
276354 (
277355 "app-window" ,
278- "Cloud Unlimited Apps" ,
356+ "Cloud Limited Apps" ,
279357 "Free users are limited to 20 hours of 1 vCPU, 1 GB RAM machines per month." ,
280358 ),
281- ("code" , "Reflex Open Source" ),
282359 ("heart-handshake" , "Discord/Github Support" ),
283360 ("building" , rx .link ("Reflex Enterprise" , href = "https://reflex.dev/docs/enterprise/overview/" , class_name = "!text-slate-11" ), "Free-tier users can access Reflex Enterprise features, with a required 'Built with Reflex' badge displayed on their apps." ),
361+ ("frame" , "Open Source Framework" ),
284362 ],
285- "Start building for free " ,
286- price = "Free " ,
363+ "Start for Free " ,
364+ price = "$0/month " ,
287365 redirect_url = REFLEX_DOCS_URL ,
288366 ),
289- popular_card (
367+ card (
290368 "Pro" ,
291369 "For professional projects and startups." ,
292370 [
293- ("brain" , "AI App Builder (Free $20 credits / month)" ),
294- ("credit-card" , "Cloud Compute (Free $10 credits / month)" ),
371+ ("credit-card" , "Cloud Credits $10/month included" ),
295372 ("brush" , "Custom domains" ),
296373 ("building" , rx .link ("Reflex Enterprise" , href = "https://reflex.dev/docs/enterprise/overview/" , class_name = "!text-slate-11" ), "Pro-tier users can access Reflex Enterprise features without the 'Built with Reflex' badge when hosting their apps on Reflex Cloud" ),
297- ("circle-plus" , "Everything in Hobby" ),
298374 ],
299- "Start with Pro plan" ,
300- price = "$20/mo" ,
375+ "Upgrade now" ,
376+ price = "$20/month" ,
377+ redirect_url = f"{ REFLEX_CLOUD_URL } /?redirect_url={ REFLEX_CLOUD_URL } /billing/" ,
301378 ),
302- card (
379+ popular_card (
303380 "Team" ,
304381 "For teams looking to scale their applications." ,
305- [
306- ("users" , "Invite your team mates" ),
382+ [
383+ ("credit-card" , "Cloud Compute $20/mo included" ),
384+ ("users" , "Invite your teammates" ),
307385 (
308386 "cable" ,
309- "Connect AI Builder to your Data " ,
310- "Integrations include Databricks, Snowflake, etc." ,
387+ "Reflex Build Integrations " ,
388+ "Databricks, Snowflake, etc." ,
311389 ),
312- ("credit-card" , "Cloud Compute (Free $20 credits / user / month)" ),
313- ("lock-keyhole" , "One Click Auth" ),
314390 ("file-badge" , "AG Grid with no Reflex Branding" ),
315391 ("mail" , "Email support" ),
316392 ("building" , rx .link ("Reflex Enterprise" , href = "https://reflex.dev/docs/enterprise/overview/" , class_name = "!text-slate-11" ), "Team-tier users can access Reflex Enterprise features without the 'Built with Reflex' badge when self-hosting their apps." ),
317- ("circle-plus" , "Everything in Pro" ),
318393 ],
319- "Start with Team plan" ,
320- redirect_url = f"{ REFLEX_CLOUD_URL } /?redirect_url={ REFLEX_CLOUD_URL } /billing/" ,
321- price = "$49/user/mo" ,
394+ "Upgrade now" ,
395+ price = "$49 user/month" ,
322396 ),
323397 card (
324398 "Enterprise" ,
325399 "Get a plan tailored to your business needs." ,
326400 [
401+ ("credit-card" , "Cloud Compute $100/mo included" ),
402+ ("hard-drive" , "On Premise Deployment" , "Option to self-host your apps on your own infrastructure." ),
327403 ("hand-helping" , "White Glove Onboarding" ),
328404 ("user-round-plus" , "Personalized integration help" ),
329- ("hard-drive" , "On Premise Deployment" ),
330405 ("key" , "Bring your own AI API keys" ),
331406 ("headset" , "Dedicated Support Channel" ),
332407 ("git-pull-request" , "Influence Reflex Roadmap" ),
333408 ("circle-plus" , "Everything in Team" ),
334409 ],
335- "Contact sales" ,
410+ "Contact Us" ,
411+ price = "Custom" ,
336412 redirect_url = REFLEX_DEV_WEB_LANDING_FORM_URL_GET_DEMO ,
337413 ),
338- class_name = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 lg:gap- 6"
414+ class_name = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"
339415 )
0 commit comments