diff --git a/docs/app/assets/tailwind-theme.css b/docs/app/assets/tailwind-theme.css index 14806139e37..4a555afa94c 100644 --- a/docs/app/assets/tailwind-theme.css +++ b/docs/app/assets/tailwind-theme.css @@ -1226,6 +1226,8 @@ --animate-ellipse-3: ellipse-3 2400ms ease-out infinite; --animate-ellipse-4: ellipse-4 2400ms ease-out infinite; --animate-ellipse-reversed: ellipse-reversed 2400ms ease-out infinite; + --arrow-sweep-duration: 4s; + --animate-arrow-sweep: arrow-sweep var(--arrow-sweep-duration) linear infinite backwards; /* Radius */ --radius-ui-xxs: calc(var(--radius) - 0.25rem); --radius-ui-xs: calc(var(--radius) - 0.125rem); @@ -1237,6 +1239,7 @@ /* Width */ --layout-max-width: 81rem; --docs-layout-max-width: 69rem; + --landing-layout-max-width: 78.5rem; @keyframes accordion-down { from { @@ -1631,6 +1634,20 @@ } } + @keyframes arrow-sweep { + 0% { + transform: translateX(-100%); + } + + 25% { + transform: translateX(200%); + } + + 100% { + transform: translateX(200%); + } + } + } @@ -2184,6 +2201,6 @@ } body { - @apply isolate bg-slate-1 font-sans antialiased; + @apply isolate bg-secondary-1 font-sans antialiased; } -} +} \ No newline at end of file diff --git a/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py b/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py index f45e3c13632..52bb83c635e 100644 --- a/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py +++ b/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py @@ -183,7 +183,7 @@ def ai_builder_section() -> rx.Component: ), class_name="grid grid-cols-1 lg:grid-cols-3 gap-12", ), - class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--docs-layout-max-width) mx-auto", + class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--landing-layout-max-width) mx-auto", ), class_name="bg-gradient-to-b from-white-1 to-m-slate-1 dark:from-m-slate-11 dark:to-m-slate-12 w-full lg:pt-24 lg:pb-24 pb-10 max-xl:px-6 max-lg:pt-10", ) diff --git a/docs/app/reflex_docs/pages/docs_landing/views/divider.py b/docs/app/reflex_docs/pages/docs_landing/views/divider.py index 0468cc4bc3f..1796075ba6f 100644 --- a/docs/app/reflex_docs/pages/docs_landing/views/divider.py +++ b/docs/app/reflex_docs/pages/docs_landing/views/divider.py @@ -11,7 +11,7 @@ def divider(class_name: str = "") -> rx.Component: class_name="absolute top-0 -left-24 w-24 h-px bg-gradient-to-r from-transparent to-current text-m-slate-4 dark:text-m-slate-10" ), class_name=ui.cn( - "w-full h-[1px] bg-m-slate-4 dark:bg-m-slate-10 relative max-w-(--docs-layout-max-width) mx-auto", + "w-full h-[1px] bg-m-slate-4 dark:bg-m-slate-10 relative max-w-(--landing-layout-max-width) mx-auto", class_name, ), ) diff --git a/docs/app/reflex_docs/pages/docs_landing/views/enterprise.py b/docs/app/reflex_docs/pages/docs_landing/views/enterprise.py index 419fce7237c..3ed024c832b 100644 --- a/docs/app/reflex_docs/pages/docs_landing/views/enterprise.py +++ b/docs/app/reflex_docs/pages/docs_landing/views/enterprise.py @@ -34,5 +34,5 @@ def enterprise_section() -> rx.Component: ), class_name="grid grid-cols-1 lg:grid-cols-2 border-t border-secondary-4 relative", ), - class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--docs-layout-max-width) mx-auto w-full justify-start lg:mb-24 mb-10 max-xl:px-6 overflow-hidden", + class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--landing-layout-max-width) mx-auto w-full justify-start lg:mb-24 mb-10 max-xl:px-6 overflow-hidden", ) diff --git a/docs/app/reflex_docs/pages/docs_landing/views/framework.py b/docs/app/reflex_docs/pages/docs_landing/views/framework.py index aad79932cff..e7d46eba0f5 100644 --- a/docs/app/reflex_docs/pages/docs_landing/views/framework.py +++ b/docs/app/reflex_docs/pages/docs_landing/views/framework.py @@ -187,5 +187,5 @@ def framework() -> rx.Component: components_section(), class_name="flex flex-col lg:flex-row relative", ), - class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--docs-layout-max-width) mx-auto w-full justify-start lg:mb-24 mb-10 max-xl:px-6 overflow-hidden", + class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--landing-layout-max-width) mx-auto w-full justify-start lg:mb-24 mb-10 max-xl:px-6 overflow-hidden", ) diff --git a/docs/app/reflex_docs/pages/docs_landing/views/hero.py b/docs/app/reflex_docs/pages/docs_landing/views/hero.py index 70b440b6e53..42d9fba2c01 100644 --- a/docs/app/reflex_docs/pages/docs_landing/views/hero.py +++ b/docs/app/reflex_docs/pages/docs_landing/views/hero.py @@ -61,6 +61,6 @@ def hero() -> rx.Component: ), ), class_name=ui.cn( - "flex lg:flex-row flex-col max-w-(--docs-layout-max-width) mx-auto w-full max-lg:pb-10 max-xl:px-6", + "flex lg:flex-row flex-col max-w-(--landing-layout-max-width) mx-auto w-full max-lg:pb-10 max-xl:px-6", ), ) diff --git a/docs/app/reflex_docs/pages/docs_landing/views/hosting.py b/docs/app/reflex_docs/pages/docs_landing/views/hosting.py index 4c7bfba9d5d..4851decde7a 100644 --- a/docs/app/reflex_docs/pages/docs_landing/views/hosting.py +++ b/docs/app/reflex_docs/pages/docs_landing/views/hosting.py @@ -47,5 +47,5 @@ def hosting_section() -> rx.Component: ), class_name="grid grid-cols-1 lg:grid-cols-2 border-t border-m-slate-4 dark:border-m-slate-10 relative", ), - class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--docs-layout-max-width) mx-auto w-full justify-start max-xl:px-6 lg:mb-24 overflow-hidden", + class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--landing-layout-max-width) mx-auto w-full justify-start max-xl:px-6 lg:mb-24 overflow-hidden", ) diff --git a/docs/app/reflex_docs/pages/docs_landing/views/other.py b/docs/app/reflex_docs/pages/docs_landing/views/other.py index 3a9172779c5..f3945d82c7d 100644 --- a/docs/app/reflex_docs/pages/docs_landing/views/other.py +++ b/docs/app/reflex_docs/pages/docs_landing/views/other.py @@ -35,5 +35,5 @@ def other_section() -> rx.Component: faded_borders(), class_name="grid grid-cols-1 lg:grid-cols-2 border-t border-secondary-4 relative", ), - class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--docs-layout-max-width) mx-auto w-full justify-start lg:mb-24 mb-10 max-xl:px-6 overflow-hidden", + class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--landing-layout-max-width) mx-auto w-full justify-start lg:mb-24 mb-10 max-xl:px-6 overflow-hidden", ) diff --git a/docs/app/reflex_docs/pages/docs_landing/views/self_hosting.py b/docs/app/reflex_docs/pages/docs_landing/views/self_hosting.py index da8ddba27e4..6f94f974f6e 100644 --- a/docs/app/reflex_docs/pages/docs_landing/views/self_hosting.py +++ b/docs/app/reflex_docs/pages/docs_landing/views/self_hosting.py @@ -34,5 +34,5 @@ def self_hosting_section() -> rx.Component: ), class_name="grid grid-cols-1 lg:grid-cols-2 border-t border-secondary-4 relative", ), - class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--docs-layout-max-width) mx-auto w-full justify-start lg:mb-24 mb-10 max-xl:px-6 overflow-hidden max-lg:pt-10", + class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--landing-layout-max-width) mx-auto w-full justify-start lg:mb-24 mb-10 max-xl:px-6 overflow-hidden max-lg:pt-10", ) diff --git a/docs/app/reflex_docs/templates/docpage/docpage.py b/docs/app/reflex_docs/templates/docpage/docpage.py index 96dfe4b5d6b..46880da6d8f 100644 --- a/docs/app/reflex_docs/templates/docpage/docpage.py +++ b/docs/app/reflex_docs/templates/docpage/docpage.py @@ -55,6 +55,40 @@ def footer_link_flex(heading: str, links): ) +def social_menu_item(icon: str, url: str, name: str) -> rx.Component: + return rx.el.elements.a( + marketing_button( + get_icon(icon, class_name="shrink-0"), + variant="ghost", + size="icon-sm", + class_name="text-m-slate-7 dark:text-m-slate-6", + native_button=False, + ), + href=url, + custom_attrs={"aria-label": "Social link for " + name}, + target="_blank", + ) + + +def menu_socials() -> rx.Component: + from reflex_site_shared.constants import ( + DISCORD_URL, + FORUM_URL, + GITHUB_URL, + LINKEDIN_URL, + TWITTER_URL, + ) + + return rx.box( + social_menu_item("twitter_footer", TWITTER_URL, "Twitter"), + social_menu_item("github_navbar", GITHUB_URL, "Github"), + social_menu_item("discord_navbar", DISCORD_URL, "Discord"), + social_menu_item("linkedin_footer", LINKEDIN_URL, "LinkedIn"), + social_menu_item("forum_footer", FORUM_URL, "Forum"), + class_name="flex flex-row items-center gap-2", + ) + + def thumb_card(score: int, icon: str, label: str) -> rx.Component: return rx.el.button( ui.icon( @@ -238,7 +272,6 @@ def link_pill(text: str, href: str) -> rx.Component: @rx.memo def docpage_footer(path: str): from reflex_site_shared.constants import FORUM_URL, ROADMAP_URL - from reflex_site_shared.views.footer import menu_socials return rx.el.footer( rx.box( diff --git a/packages/reflex-site-shared/src/reflex_site_shared/components/icons.py b/packages/reflex-site-shared/src/reflex_site_shared/components/icons.py index 0c4312787f1..316c40042ee 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/components/icons.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/components/icons.py @@ -572,6 +572,28 @@ circle = """ """ + +grok = """ +""" + +claude = """""" + +openai = """ +""" + +gemini = """ +""" + +cursor = """""" + +arrow_turn = """""" + +reflex_small = """""" + +docs = """""" + +curved_line = """""" + ICONS = { # Socials "github": github, @@ -663,6 +685,15 @@ "sun_footer": sun_footer, "computer_footer": computer_footer, "circle": circle, + "grok": grok, + "claude": claude, + "openai": openai, + "gemini": gemini, + "arrow_turn": arrow_turn, + "reflex_small": reflex_small, + "docs": docs, + "cursor": cursor, + "curved_line": curved_line, } diff --git a/packages/reflex-site-shared/src/reflex_site_shared/constants.py b/packages/reflex-site-shared/src/reflex_site_shared/constants.py index 850ec53b1e0..1c2379b1b86 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/constants.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/constants.py @@ -34,7 +34,7 @@ "REFLEX_DEV_WEB_GENERAL_FORM_FEEDBACK_WEBHOOK_URL", "" ) RECENT_BLOGS_API_URL: str = os.environ.get( - "RECENT_BLOGS_API_URL", "https://reflex.dev/api/v1/recent-blogs" + "RECENT_BLOGS_API_URL", "https://reflex.dev/blog-api/api/v1/recent-blogs" ) diff --git a/packages/reflex-site-shared/src/reflex_site_shared/pages/page404.py b/packages/reflex-site-shared/src/reflex_site_shared/pages/page404.py index 6f84c6211c7..a685adf4da3 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/pages/page404.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/pages/page404.py @@ -4,12 +4,6 @@ from reflex_site_shared.components.blocks.flexdown import markdown_with_shiki from reflex_site_shared.templates.webpage import webpage -contents = f""" -# Page Not Found - -The page at `{rx.State.router.page.raw_path}` doesn't exist. -""" - @webpage(path="/404", title="Page Not Found · Reflex.dev", add_as_page=False) def page404(): @@ -19,6 +13,11 @@ def page404(): The component. """ return rx.box( - markdown_with_shiki(contents), + markdown_with_shiki("# Page Not Found"), + rx.el.p( + "The page at ", + rx.code(rx.State.router.page.raw_path, class_name="code-style"), + " doesn't exist.", + ), class_name="h-[80vh] w-full flex flex-col items-center justify-center", ) diff --git a/packages/reflex-site-shared/src/reflex_site_shared/views/cta_card.py b/packages/reflex-site-shared/src/reflex_site_shared/views/cta_card.py index 32e5f3e3288..16803ae025e 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/views/cta_card.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/views/cta_card.py @@ -18,7 +18,7 @@ def cta_card(): return rx.el.div( rx.el.div( rx.el.span( - "The Unified Platform to Build and Scale Enterprise Apps", + "The Platform to Build and Scale Enterprise Apps", class_name="text-slate-12 lg:text-3xl text-2xl font-[575]", ), rx.el.span( @@ -43,13 +43,13 @@ def cta_card(): ), class_name="flex flex-row gap-4 items-center", ), - class_name="flex flex-col gap-6 justify-center max-w-[24.5rem]", + class_name="flex flex-col gap-6 justify-center max-w-[29.25rem]", ), rx.image( - f"{REFLEX_ASSETS_CDN}common/{rx.color_mode_cond('light', 'dark')}/cta.svg", + f"{REFLEX_ASSETS_CDN}common/{rx.color_mode_cond('light', 'dark')}/cta_gray_lines_2.svg", class_name="w-auto h-full pointer-events-none", loading="lazy", alt="CTA Card", ), - class_name="flex flex-row justify-between max-w-(--docs-layout-max-width) mx-auto w-full bg-white/96 dark:bg-m-slate-11 backdrop-blur-[16px] rounded-xl relative overflow-hidden shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_12px_24px_0_rgba(0,0,0,0.08),0_1px_1px_0_rgba(0,0,0,0.01),0_4px_8px_0_rgba(0,0,0,0.03)] dark:shadow-none dark:border dark:border-m-slate-9 pl-16 max-lg:hidden mb-12 mt-24", + class_name="flex flex-row justify-between max-w-(--landing-layout-max-width) mx-auto w-full bg-white/96 dark:bg-m-slate-11 backdrop-blur-[16px] rounded-xl relative overflow-hidden shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_12px_24px_0_rgba(0,0,0,0.08),0_1px_1px_0_rgba(0,0,0,0.01),0_4px_8px_0_rgba(0,0,0,0.03)] dark:shadow-none dark:border dark:border-m-slate-9 pl-16 max-lg:hidden mb-12 mt-24", ) diff --git a/packages/reflex-site-shared/src/reflex_site_shared/views/footer.py b/packages/reflex-site-shared/src/reflex_site_shared/views/footer.py index 834361dbf51..2c8b4c7905a 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/views/footer.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/views/footer.py @@ -1,4 +1,4 @@ -"""Footer module.""" +"""Site footer layout and sections for marketing pages.""" from datetime import datetime @@ -19,35 +19,12 @@ LINKEDIN_URL, REFLEX_ASSETS_CDN, REFLEX_BUILD_URL, - ROADMAP_URL, TWITTER_URL, ) -def ph_1() -> rx.Component: - """Ph 1. - - Returns: - The component. - """ - return rx.fragment( - rx.image( - src=f"{REFLEX_ASSETS_CDN}logos/dark/ph_1.svg", - class_name="hidden dark:block h-[36px] w-fit", - alt="1st product of the day logo", - loading="lazy", - ), - rx.image( - src=f"{REFLEX_ASSETS_CDN}logos/light/ph_1.svg", - class_name="dark:hidden block h-[36px] w-fit", - alt="1st product of the day logo", - loading="lazy", - ), - ) - - def logo() -> rx.Component: - """Logo. + """Homepage logo link for the footer. Returns: The component. @@ -69,13 +46,13 @@ def logo() -> rx.Component: def tab_item(mode: str, icon: str) -> rx.Component: - """Tab item. + """Single color-mode toggle tab (system, light, or dark). Returns: The component. """ - active_cn = " shadow-[0_-1px_0_0_rgba(0,0,0,0.08)_inset,0_0_0_1px_rgba(0,0,0,0.08)_inset,0_1px_2px_0_rgba(0,0,0,0.02),0_1px_4px_0_rgba(0,0,0,0.02)] dark:shadow-[0_1px_0_0_rgba(255,255,255,0.16)_inset] bg-white dark:bg-m-slate-10 hover:bg-m-slate-2 dark:hover:bg-m-slate-9 text-m-slate-12 dark:text-m-slate-3" - unactive_cn = " hover:text-m-slate-12 dark:hover:text-m-slate-3 text-m-slate-7 dark:text-m-slate-6" + active_cn = " shadow-[0_-1px_0_0_rgba(0,0,0,0.08)_inset,0_0_0_1px_rgba(0,0,0,0.08)_inset,0_1px_2px_0_rgba(0,0,0,0.02),0_1px_4px_0_rgba(0,0,0,0.02)] dark:shadow-[0_1px_0_0_rgba(255,255,255,0.16)_inset] bg-white-1 hover:bg-secondary-2 text-secondary-12" + unactive_cn = " hover:text-secondary-12 text-secondary-11" return rx.el.button( get_icon(icon, class_name="shrink-0"), on_click=set_color_mode(mode), # type: ignore[reportArgumentType] @@ -88,7 +65,7 @@ def tab_item(mode: str, icon: str) -> rx.Component: def dark_mode_toggle() -> rx.Component: - """Dark mode toggle. + """Footer control group for switching color mode. Returns: The component. @@ -97,12 +74,12 @@ def dark_mode_toggle() -> rx.Component: tab_item("system", "computer_footer"), tab_item("light", "sun_footer"), tab_item("dark", "moon_footer"), - class_name="flex flex-row gap-0.5 items-center p-0.5 [box-shadow:0_1px_0_0_rgba(0,_0,_0,_0.08),_0_0_0_1px_rgba(0,_0,_0,_0.08),_0_1px_2px_0_rgba(0,_0,_0,_0.02),_0_1px_4px_0_rgba(0,_0,_0,_0.02)] w-fit mt-auto bg-m-slate-1 dark:bg-m-slate-12 rounded-[0.625rem] dark:border dark:border-m-slate-9 border border-transparent lg:ml-auto mr-px", + class_name="flex flex-row gap-0.5 items-center p-0.5 [box-shadow:0_1px_0_0_rgba(0,_0,_0,_0.08),_0_0_0_1px_rgba(0,_0,_0,_0.08),_0_1px_2px_0_rgba(0,_0,_0,_0.02),_0_1px_4px_0_rgba(0,_0,_0,_0.02)] w-fit mt-auto bg-secondary-1 rounded-[0.625rem] dark:border dark:border-secondary-4 border border-transparent", ) def footer_link(text: str, href: str) -> rx.Component: - """Footer link. + """Link styled for footer link columns. Returns: The component. @@ -115,15 +92,15 @@ def footer_link(text: str, href: str) -> rx.Component: class_name="shrink-0 lg:hidden flex", ), href=href, - target="_blank", - class_name="font-[525] text-m-slate-7 hover:text-m-slate-8 dark:hover:text-m-slate-5 dark:text-m-slate-6 text-sm transition-color w-full lg:w-fit flex flex-row justify-between items-center", + target="_blank" if not href.startswith("/") else "", + class_name="font-[525] text-secondary-11 hover:text-secondary-12 text-sm transition-color w-full lg:w-fit flex flex-row justify-between items-center min-h-[24px]", ) def footer_link_flex( heading: str, links: list[rx.Component], class_name: str = "" ) -> rx.Component: - """Footer link flex. + """Column with a heading and stacked footer links. Returns: The component. @@ -131,51 +108,52 @@ def footer_link_flex( return rx.el.div( rx.el.h3( heading, - class_name="text-xs text-m-slate-12 dark:text-m-slate-3 font-semibold w-fit mb-3", + class_name="text-xs text-secondary-12 font-[525] w-fit mb-2", ), *links, class_name=ui.cn("flex flex-col gap-2", class_name), ) -def social_menu_item(icon: str, url: str, name: str) -> rx.Component: - """Social menu item. +def social_menu_item( + icon: str, url: str, name: str, class_name: str = "" +) -> rx.Component: + """Icon link to a social profile or community. Returns: The component. """ return rx.el.elements.a( - marketing_button( - get_icon(icon, class_name="shrink-0"), - variant="ghost", - size="icon-sm", - class_name="text-m-slate-7 dark:text-m-slate-6", - native_button=False, - ), + get_icon(icon, class_name="size-5 shrink-0"), href=url, custom_attrs={"aria-label": "Social link for " + name}, target="_blank", + class_name=ui.cn( + "text-secondary-11 hover:text-secondary-12 transition-colors flex items-center justify-center size-7 rounded-md", + "lg:size-auto lg:h-full lg:w-full lg:rounded-none", + class_name, + ), ) def menu_socials() -> rx.Component: - """Menu socials. + """Row of major social and community links. Returns: The component. """ - return rx.box( + return rx.el.div( social_menu_item("twitter_footer", TWITTER_URL, "Twitter"), social_menu_item("github_navbar", GITHUB_URL, "Github"), social_menu_item("discord_navbar", DISCORD_URL, "Discord"), social_menu_item("linkedin_footer", LINKEDIN_URL, "LinkedIn"), social_menu_item("forum_footer", FORUM_URL, "Forum"), - class_name="flex flex-row items-center gap-2", + class_name="flex flex-row items-center gap-2 lg:gap-0 lg:grid lg:grid-cols-5 lg:border-y lg:border-secondary-4 lg:divide-x lg:divide-secondary-4 lg:w-full lg:h-16", ) def newsletter_input() -> rx.Component: - """Newsletter input. + """Email signup form or post-subscribe confirmation. Returns: The component. @@ -188,11 +166,11 @@ def newsletter_input() -> rx.Component: rx.icon( tag="circle-check", size=16, - class_name="!text-violet-9", + class_name="!text-primary-9", ), rx.text( "Thanks for subscribing!", - class_name="text-xs text-m-slate-7 dark:text-m-slate-6 font-semibold", + class_name="text-xs font-[525] text-secondary-12", ), class_name="flex flex-row items-center gap-2", ), @@ -205,17 +183,18 @@ def newsletter_input() -> rx.Component: class_name="flex flex-col flex-wrap gap-2", ), rx.form( - rx.el.input( + ui.input( placeholder="Email", name="input_email", type="email", required=True, - class_name="relative [box-shadow:0_-1px_0_0_rgba(0,_0,_0,_0.08)_inset,_0_0_0_1px_rgba(0,_0,_0,_0.08)_inset,_0_1px_2px_0_rgba(0,_0,_0,_0.02),_0_1px_4px_0_rgba(0,_0,_0,_0.02)] rounded-lg h-8 px-2 py-1.5 w-[12rem] text-sm placeholder:text-m-slate-7 dark:placeholder:text-m-slate-6 font-[525] focus:outline-none outline-none dark:border dark:border-m-slate-9 dark:bg-m-slate-11", + size="sm", + class_name="w-[195px]", ), - marketing_button( + ui.button( "Subscribe", type="submit", - variant="outline", + variant="outline-shadow", size="sm", class_name="w-fit max-w-full", ), @@ -228,96 +207,149 @@ def newsletter_input() -> rx.Component: def newsletter() -> rx.Component: - """Newsletter. + """Newsletter call-to-action block with heading. Returns: The component. """ return rx.el.div( - rx.text( - "Get updates", - class_name="text-xs text-m-slate-7 dark:text-m-slate-6 font-semibold", - ), newsletter_input(), - class_name="flex flex-col items-start gap-4 self-stretch", + class_name="flex flex-col items-start gap-4 self-stretch mt-6", ) @rx.memo def footer_index(class_name: str = "", grid_class_name: str = "") -> rx.Component: - """Footer index. + """Full marketing footer: logo, newsletter, links, and legal. Returns: The component. """ return rx.el.footer( rx.el.div( - logo(), rx.el.div( - footer_link_flex( - "Product", - [ - footer_link("AI Builder", REFLEX_BUILD_URL), - footer_link("Framework", "/framework"), - footer_link("Cloud", "/cloud"), - ], - ), - footer_link_flex( - "Solutions", - [ - footer_link("Enterprise", "/use-cases"), - footer_link("Finance", "/use-cases/finance"), - footer_link("Healthcare", "/use-cases/healthcare"), - footer_link("Consulting", "/use-cases/consulting"), - footer_link("Government", "/use-cases/government"), - ], - ), - footer_link_flex( - "Resources", - [ - footer_link("Documentation", "/docs"), - footer_link("FAQ", "/faq"), - footer_link("Common Errors", "/errors"), - footer_link("Roadmap", ROADMAP_URL), - footer_link("Changelog", CHANGELOG_URL), - ], - ), rx.el.div( - class_name="absolute -top-24 -right-px w-px h-24 bg-gradient-to-b from-transparent to-current text-m-slate-4 dark:text-m-slate-10 max-lg:hidden" + rx.el.div( + rx.el.div( + class_name="absolute -right-px -top-24 h-24 w-px bg-linear-to-b from-transparent to-current text-secondary-4 max-lg:hidden" + ), + logo(), + newsletter(), + rx.el.div( + menu_socials(), + class_name="mt-6 w-full lg:-mx-8 lg:w-[calc(100%+4rem)]", + ), + rx.el.div( + dark_mode_toggle(), + rx.el.span( + f"Copyright © {datetime.now().year} Pynecone, Inc.", + class_name="text-xs font-[525] text-secondary-11", + ), + rx.el.div( + server_status(StatusState.status), class_name="-ml-2.5" + ), + class_name="mt-auto justify-start flex flex-col items-start gap-6 pt-8", + ), + class_name="flex flex-col lg:pr-8 lg:pl-8 lg:pb-8 min-w-[337px] lg:border-r border-secondary-4 shrink-0 relative pt-16", + ), + rx.el.div( + footer_link_flex( + "Product", + [ + footer_link("AI Builder", REFLEX_BUILD_URL), + footer_link( + "Agent Toolkit", + "/docs/ai/integrations/overview/", + ), + footer_link( + "Enterprise", + "/docs/enterprise/overview/", + ), + footer_link("App Management", "/hosting"), + footer_link("Pricing", "/pricing"), + ], + ), + footer_link_flex( + "Solutions", + [ + footer_link("Enterprise", "/use-cases"), + footer_link("Finance", "/use-cases/finance"), + footer_link("Healthcare", "/use-cases/healthcare"), + footer_link("Consulting", "/use-cases/consulting"), + footer_link("Government", "/use-cases/government"), + ], + ), + footer_link_flex( + "Resources", + [ + footer_link("Blog", "/blog"), + footer_link("Templates", "/templates"), + footer_link( + "Integrations", + "/docs/ai/integrations/overview/", + ), + ], + ), + footer_link_flex( + "Migration", + [ + footer_link("From No-Code", "/migration/no-code/"), + footer_link("From Low-Code", "/migration/low-code/"), + footer_link( + "From Other Frameworks", + "/migration/other-frameworks/", + ), + footer_link( + "From Other AI Tools", + "/migration/other-ai-tools/", + ), + ], + ), + footer_link_flex( + "Developers", + [ + footer_link("Documentation", "/docs"), + footer_link("Changelog", CHANGELOG_URL), + footer_link("Common Errors", "/errors"), + ], + ), + footer_link_flex( + "Company", + [ + footer_link("About", "/about"), + footer_link( + "Careers", + "https://www.ycombinator.com/companies/reflex/jobs", + ), + footer_link( + "Privacy Policy", + "https://build.reflex.dev/privacy-policy", + ), + footer_link( + "Terms of Service", + "https://build.reflex.dev/terms-of-use", + ), + ], + ), + class_name=ui.cn( + "grid grid-cols-1 lg:grid-cols-3 gap-x-12 gap-y-12 w-full relative lg:px-8 pb-8 pt-16", + grid_class_name, + ), + ), + class_name="flex lg:flex-row flex-col max-lg:gap-6 w-full", ), - class_name=ui.cn( - "grid grid-cols-1 lg:grid-cols-3 gap-12 w-full lg:pr-12 pb-8 lg:border-r border-m-slate-4 dark:border-m-slate-10 xl:ml-auto relative", - grid_class_name, + rx.el.div( + class_name="absolute -top-px -right-24 w-24 h-px bg-linear-to-l from-transparent to-current text-secondary-4 max-lg:hidden" ), - ), - rx.el.div( - newsletter(), - ph_1(), - dark_mode_toggle(), - class_name="flex flex-col gap-6 lg:pl-12 pb-8 max-lg:justify-start", - ), - class_name="flex flex-col max-lg:gap-6 lg:flex-row w-full", - ), - rx.el.div( - server_status(StatusState.status), - rx.el.div( - rx.el.span( - f"Copyright © {datetime.now().year} Pynecone, Inc.", - class_name="text-xs text-m-slate-7 dark:text-m-slate-6 font-medium", + rx.el.div( + class_name="absolute -top-px -left-24 w-24 h-px bg-linear-to-r from-transparent to-current text-secondary-4 max-lg:hidden" ), - menu_socials(), - class_name="flex flex-row items-center gap-6", - ), - rx.el.div( - class_name="absolute -top-px -right-24 w-24 h-px bg-gradient-to-l from-transparent to-current text-m-slate-4 dark:text-m-slate-10 max-lg:hidden" - ), - rx.el.div( - class_name="absolute -top-px -left-24 w-24 h-px bg-gradient-to-r from-transparent to-current text-m-slate-4 dark:text-m-slate-10 max-lg:hidden" + class_name="relative flex flex-col w-full lg:border-x border-secondary-4 border-t", ), - class_name="flex flex-row items-center justify-between py-6 gap-4 w-full border-t border-m-slate-4 dark:border-m-slate-10 relative", + class_name="w-full min-w-0 lg:px-2", ), class_name=ui.cn( - "flex flex-col max-w-(--docs-layout-max-width) justify-center items-center w-full mx-auto mt-24 max-lg:px-4 overflow-hidden", + "flex flex-col w-full min-w-0 max-w-(--landing-layout-max-width) items-stretch mx-auto max-lg:px-4 lg:border-x border-secondary-4 relative", class_name, ), ) diff --git a/packages/reflex-site-shared/src/reflex_site_shared/views/hosting_banner.py b/packages/reflex-site-shared/src/reflex_site_shared/views/hosting_banner.py index 7f00ba51ad1..97a5806c28e 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/views/hosting_banner.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/views/hosting_banner.py @@ -121,10 +121,10 @@ def hosting_banner() -> rx.Component: ui.button( "Learn more", ui.icon("ArrowRight01Icon"), - variant="ghost", + variant="ghost-highlight", size="xs", aria_label="Learn more", - class_name="text-m-slate-3 dark:hover:text-m-slate-5 max-lg:hidden", + class_name="max-lg:hidden text-white hover:text-primary-10", ), class_name="flex flex-row items-center md:gap-4 gap-2", ), diff --git a/packages/reflex-site-shared/src/reflex_site_shared/views/marketing_navbar.py b/packages/reflex-site-shared/src/reflex_site_shared/views/marketing_navbar.py index d50a5a62aca..dc812a53e15 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/views/marketing_navbar.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/views/marketing_navbar.py @@ -7,65 +7,19 @@ from reflex_site_shared.backend.get_blogs import BlogPostDict, RecentBlogsState from reflex_site_shared.components.icons import get_icon from reflex_site_shared.components.marketing_button import button as marketing_button -from reflex_site_shared.components.marquee import marquee from reflex_site_shared.constants import ( CHANGELOG_URL, - CONTRIBUTING_URL, - DISCUSSIONS_URL, + DISCORD_URL, GITHUB_STARS, GITHUB_URL, - JOBS_BOARD_URL, REFLEX_ASSETS_CDN, REFLEX_BUILD_URL, ) from reflex_site_shared.views.sidebar import navbar_sidebar_button - - -def social_proof_card(image: str) -> rx.Component: - """Social proof card. - - Returns: - The component. - """ - return rx.el.div( - rx.image( - f"{REFLEX_ASSETS_CDN}companies/{rx.color_mode_cond('light', 'dark')}/{image}_small.svg", - loading="lazy", - alt=f"{image} logo", - class_name="w-auto h-fit pointer-events-none", - ), - class_name="flex justify-center items-center px-3", - ) - - -def logos_carousel() -> rx.Component: - """Logos carousel. - - Returns: - The component. - """ - logos = [ - "agricole", - "man", - "shell", - "red_hat", - "accenture", - "dell", - "microsoft", - "world", - "ford", - "unicef", - "nike", - ] - return marquee( - *[social_proof_card(logo) for logo in logos], - direction="left", - gradient_color="light-dark(var(--c-white-1), var(--c-m-slate-11))", - class_name="h-[1.625rem] w-full overflow-hidden mt-auto", - gradient_width=65, - speed=25, - pause_on_hover=False, - ) +from reflex_site_shared.views.workflow_stage import ( + workflow_stage_image, + workflow_stage_row, +) def github() -> rx.Component: @@ -152,7 +106,7 @@ def menu_trigger(title: str, content: rx.Component) -> rx.Component: def menu_content(content: rx.Component, class_name: str = "") -> rx.Component: - """Menu content. + """Styled dropdown panel wrapper for navigation menu items. Returns: The component. @@ -169,36 +123,150 @@ def menu_content(content: rx.Component, class_name: str = "") -> rx.Component: ) -def platform_item(image: str, title: str, description: str, href: str) -> rx.Component: - """Platform item. +def products_column_gutter() -> rx.Component: + """Vertical separator between product mega-menu columns. Returns: The component. """ return rx.el.div( - rx.image( - src=f"{REFLEX_ASSETS_CDN}common/{rx.color_mode_cond('light', 'dark')}/{image}", - alt=title, - class_name="size-18", + rx.el.div( + class_name="pointer-events-none absolute top-0 left-0 h-12 w-full border-b border-secondary-4", ), + role="presentation", + class_name="w-8 shrink-0 bg-secondary-1 border-secondary-4 border-x relative", + ) + + +def products_iterate_column_body() -> rx.Component: + """Iterate stage column: framework pitch and GitHub link. + + Returns: + The component. + """ + return rx.el.div( + rx.el.elements.a( + rx.el.div( + rx.el.span( + "Framework", + class_name="text-base font-[525] text-secondary-12 group-hover:text-primary-10 dark:group-hover:text-primary-9 transition-colors", + ), + badge("Open source"), + class_name="flex flex-row items-center gap-2.5", + ), + rx.el.p( + "The full-stack Python framework, optimized to build with AI agents—apps that can be used by humans and agents alike.", + class_name="text-secondary-11 text-sm font-[475]", + ), + href="/open-source/", + class_name="group flex flex-col gap-2 items-center text-center", + ), + rx.el.elements.a( + get_icon( + "github_navbar", + class_name="size-[18px] shrink-0 text-secondary-12 group-hover:text-primary-10 dark:group-hover:text-primary-9", + ), + "View on GitHub", + ui.icon("ArrowUpRight03Icon", class_name="size-3 shrink-0 -ml-1.75"), + href=GITHUB_URL, + target="_blank", + class_name="flex flex-row items-center gap-2 text-sm font-medium text-secondary-12 hover:text-primary-10 dark:hover:text-primary-9 group mt-auto", + ), + class_name="flex flex-col p-6 text-center justify-center items-center min-h-[252px] h-full", + ) + + +def products_ship_column_body() -> rx.Component: + """Ship stage column: deploy messaging and illustration. + + Returns: + The component. + """ + return rx.el.elements.a( rx.el.div( rx.el.span( - title, - class_name="dark:text-m-slate-3 text-m-slate-12 text-sm font-[525]", + "Deploy, monitor & scale", + class_name="text-base font-[525] text-secondary-12 group-hover:text-primary-10 dark:group-hover:text-primary-9 transition-colors", + ), + class_name="flex flex-row items-center gap-2.5 px-6 pt-6", + ), + rx.el.p( + "One click to enterprise-grade infrastructure. Track, version, and grow across teams.", + class_name="text-secondary-11 text-sm font-[475] px-6", + ), + rx.el.div( + rx.image( + src=f"{REFLEX_ASSETS_CDN}landing/features/{rx.color_mode_cond('light', 'dark')}/ship_navbar_3.svg", + alt="Deploy, monitor & scale", + loading="lazy", + class_name="h-auto w-full max-w-full object-cover", + ), + class_name="flex w-full mt-auto", + ), + href="/hosting/", + class_name="group flex flex-col gap-2 text-center justify-center items-center min-h-[252px]", + ) + + +def products_build_column_body() -> rx.Component: + """Build stage column: AI builder and agent toolkit teaser. + + Returns: + The component. + """ + return rx.el.div( + rx.el.elements.a( + rx.el.div( + rx.el.span( + "AI app builder", + class_name="text-base font-[525] text-secondary-12 group-hover:text-primary-10 dark:group-hover:text-primary-9 transition-colors", + ), + badge("New"), + class_name="flex flex-row items-center gap-2.5 px-6 pt-6", + ), + rx.el.p( + "Describe it, Reflex builds it.", + class_name="text-secondary-11 text-sm font-[475] px-6 pb-6", + ), + href=REFLEX_BUILD_URL, + target="_blank", + class_name="group flex flex-col gap-2 items-center text-center", + ), + rx.el.div( + get_icon( + "arrow_turn", class_name="shrink-0 text-secondary-10 -translate-y-0.75" ), rx.el.span( - description, - class_name="dark:text-m-slate-6 text-m-slate-7 text-sm font-[475]", + "OR", + class_name="px-2 font-mono text-xs font-[415] uppercase text-secondary-12", ), - class_name="flex flex-col", + get_icon( + "arrow_turn", + class_name="shrink-0 text-secondary-10 rotate-180 translate-y-0.75", + ), + class_name="flex w-full shrink-0 items-center justify-center py-3 border-y border-secondary-4 px-6 bg-secondary-1", + ), + rx.el.elements.a( + rx.el.div( + rx.el.span( + "Agent Toolkit", + class_name="text-base font-[525] text-secondary-12 group-hover:text-primary-10 dark:group-hover:text-primary-9 transition-colors", + ), + class_name="flex flex-row items-center gap-2.5 px-6 pt-6", + ), + rx.el.p( + "Get started with our MCP and Skills.", + class_name="text-secondary-11 text-sm font-[475] px-6 pb-6", + ), + href="/docs/ai/integrations/ai-onboarding/", + class_name="group flex flex-col gap-2 items-center text-center", ), - rx.el.elements.a(class_name="absolute inset-0", href=href), - class_name="p-4 flex flex-row gap-6 relative cursor-pointer rounded-sm hover-card-shadow", + class_name="flex flex-col text-center justify-center items-center min-h-[252px]", ) -def platform_content() -> rx.Component: - """Platform content. +def products_content() -> rx.Component: + """Mega-menu body for Products: build, iterate, ship columns. Returns: The component. @@ -207,67 +275,41 @@ def platform_content() -> rx.Component: rx.el.div( rx.el.div( rx.el.div( - rx.el.div( - rx.el.span( - "AI Builder", - class_name="dark:text-m-slate-3 text-m-slate-12 text-lg font-semibold mb-2", - ), - rx.el.span( - "Build production-ready web apps for your team in seconds with AI-powered code generation.", - class_name="dark:text-m-slate-6 text-m-slate-7 text-sm font-medium", - ), - class_name="p-4 flex flex-col relative hover-card-shadow rounded-md", + workflow_stage_row( + "Build", right=workflow_stage_image(sweep_index=0) ), - rx.image( - src=f"{REFLEX_ASSETS_CDN}common/{rx.color_mode_cond('light', 'dark')}/ai_builder_pattern.svg", - alt="AI Builder Navbar Pattern", - class_name="pointer-events-none", - ), - rx.el.elements.a( - class_name="absolute inset-0", - href=REFLEX_BUILD_URL, - target="_blank", - ), - class_name="relative flex flex-col hover-card-shadow rounded-md", - ), - class_name="p-4 flex flex-col rounded-xl bg-white-1 dark:bg-m-slate-11 h-full shadow-card dark:shadow-card-dark dark:border-r dark:border-m-slate-9", - ), - rx.el.div( - platform_item( - "framework_pixel.svg", - "Reflex Framework", - "Iterate on full-stack apps in pure Python. No JavaScript required.", - "/open-source/", - ), - platform_item( - "cloud_pixel.svg", - "Cloud Hosting", - "Deploy your app with a single command to Reflex Cloud.", - "/hosting/", + products_build_column_body(), + class_name="flex min-w-0 flex-1 shrink-0 flex-col", ), + products_column_gutter(), rx.el.div( - rx.el.span( - "Reflex Is The Operating System ", - rx.el.br(), - " for Enterprise Apps", - class_name="dark:text-m-slate-6 text-m-slate-7 font-mono font-[415] text-[0.75rem] leading-4.5 uppercase", + workflow_stage_row( + "Iterate", + left=workflow_stage_image(size="small", sweep_index=1), + right=workflow_stage_image(size="small", sweep_index=2), ), - rx.image( - src=f"{REFLEX_ASSETS_CDN}common/{rx.color_mode_cond('light', 'dark')}/squares_navbar.svg", - alt="Squares Navbar", - class_name="absolute bottom-4 right-4 pointer-events-none", + products_iterate_column_body(), + class_name="flex min-w-0 flex-1 shrink-0 flex-col", + ), + products_column_gutter(), + rx.el.div( + workflow_stage_row( + "Ship", left=workflow_stage_image(sweep_index=3) ), - class_name="relative p-4", + products_ship_column_body(), + class_name="flex min-w-0 flex-1 shrink-0 flex-col", ), - class_name="p-4 flex flex-col h-full", + class_name="flex min-h-0 w-full flex-row bg-white-1", ), - class_name="w-[46.5rem] grid grid-cols-2", + products_menu_footer(), + class_name="flex max-w-[min(100vw-2rem,1240px)] w-[1240px] flex-col overflow-hidden rounded-xl bg-secondary-1 dark:shadow-card-dark", ), + class_name="p-0", ) def solutions_item(title: str, icon: str, href: str) -> rx.Component: - """Solutions item. + """Solutions submenu row with icon and title. Returns: The component. @@ -275,16 +317,51 @@ def solutions_item(title: str, icon: str, href: str) -> rx.Component: return rx.el.elements.a( ui.icon( icon, - class_name="shrink-0 text-m-slate-7 dark:text-m-slate-6 size-4.5", + stroke_width=1.5, + class_name="shrink-0 text-secondary-11 size-5", ), title, href=href, - class_name="flex flex-row px-4 py-2 rounded-sm text-sm font-[525] text-m-slate-12 dark:text-m-slate-3 gap-3 items-center justify-start cursor-pointer hover-card-shadow", + class_name="flex flex-row px-4 py-2 rounded-sm text-sm font-[525] text-secondary-12 gap-3 items-center justify-start cursor-pointer hover-card-shadow text-nowrap", + ) + + +def solutions_row( + title: str, + icon: str, + href: str, + *, + trailing_pill: str | None = None, +) -> rx.Component: + """Full-width solutions link row with optional badge. + + Returns: + The component. + """ + link_children: list[rx.Component] = [ + ui.icon( + icon, + stroke_width=1.5, + class_name="shrink-0 text-secondary-11 size-5", + ), + rx.el.span( + title, + class_name="text-sm font-[525] text-secondary-12 text-nowrap", + ), + ] + if trailing_pill: + link_children.append( + badge(trailing_pill), + ) + return rx.el.elements.a( + *link_children, + href=href, + class_name="flex flex-row px-4 py-2 rounded-sm gap-3 items-center w-full cursor-pointer hover-card-shadow text-nowrap", ) def solutions_column(title: str, items: list[tuple[str, str, str]]) -> rx.Component: - """Solutions column. + """Grouped solutions links under a section heading. Returns: The component. @@ -293,7 +370,7 @@ def solutions_column(title: str, items: list[tuple[str, str, str]]) -> rx.Compon rx.el.div( rx.el.span( title, - class_name="font-mono font-[415] text-[0.75rem] leading-4 uppercase pb-4 border-b border-dashed dark:border-m-slate-8 border-m-slate-6 dark:text-m-slate-6 text-m-slate-7", + class_name="font-mono font-[415] text-[0.75rem] leading-4 uppercase pb-4 border-b border-dashed border-secondary-6 text-secondary-11", ), class_name="px-4 pt-4 flex flex-col", ), @@ -306,7 +383,7 @@ def solutions_column(title: str, items: list[tuple[str, str, str]]) -> rx.Compon def blog_item(post: BlogPostDict) -> rx.Component: - """Blog item. + """Compact preview card for a recent blog post. Returns: The component. @@ -316,7 +393,7 @@ def blog_item(post: BlogPostDict) -> rx.Component: rx.moment( post["date"], format="MMM DD YYYY", - class_name="text-m-slate-7 dark:text-m-slate-6 text-xs font-[415] font-mono uppercase text-nowrap", + class_name="text-secondary-11 text-xs font-[415] font-mono uppercase text-nowrap", ), rx.image( src=f"{REFLEX_ASSETS_CDN}common/{rx.color_mode_cond('light', 'dark')}/squares_blog.svg", @@ -327,7 +404,7 @@ def blog_item(post: BlogPostDict) -> rx.Component: ), rx.el.span( post["title"], - class_name="dark:text-m-slate-3 text-m-slate-12 text-sm font-[525] group-hover:text-primary-10 dark:group-hover:text-primary-9 line-clamp-3", + class_name="text-secondary-12 text-base font-[525] group-hover:text-primary-10 line-clamp-3", ), rx.el.elements.a( href=post["url"], @@ -337,30 +414,20 @@ def blog_item(post: BlogPostDict) -> rx.Component: ) -def blog_column() -> rx.Component: - """Blog column. +def badge(text: str) -> rx.Component: + """Small pill label for nav highlights (e.g. New, Enterprise). Returns: The component. """ return rx.el.div( - rx.foreach( - RecentBlogsState.posts[:2], - blog_item, - ), - rx.el.elements.a( - "Read All in Blog", - ui.icon("ArrowRight01Icon", class_name="ml-auto"), - href="/blog", - class_name="dark:text-m-slate-3 text-m-slate-12 text-sm font-[525] h-10 flex items-center justify-start gap-2 hover:text-primary-10 dark:hover:text-primary-9 mt-auto", - ), - on_mount=RecentBlogsState.fetch_recent_blogs, - class_name="flex flex-col gap-6 p-4 h-full", + text, + class_name="text-secondary-11 text-xs font-[475] bg-secondary-1 px-1.5 h-5 rounded-md flex items-center justify-center border border-secondary-4", ) -def customers_column() -> rx.Component: - """Customers column. +def case_studies_column() -> rx.Component: + """Case study teaser and book-demo CTA for Solutions menu. Returns: The component. @@ -369,32 +436,55 @@ def customers_column() -> rx.Component: rx.el.div( rx.el.div( rx.el.span( - "Customers", - class_name="font-mono font-[415] text-[0.75rem] leading-4 uppercase pb-4 border-b border-dashed dark:border-m-slate-8 border-m-slate-6 dark:text-m-slate-6 text-m-slate-7", + "Case Studies", + class_name="font-mono font-[415] text-[0.75rem] leading-4 uppercase pb-4 border-b border-dashed border-secondary-6 text-secondary-11", ), class_name="px-4 pt-4 flex flex-col", ), rx.el.div( - rx.el.span( - "Read Stories How Teams Use Reflex", - class_name="text-m-slate-12 dark:text-m-slate-3 text-lg font-[575]", + rx.el.elements.a( + "How Autodesk saved 25% of their development time", + href="/customers/autodesk/", + class_name="text-secondary-12 text-lg font-[525] hover:text-primary-10 dark:hover:text-primary-9 mt-auto", ), - rx.el.span( - "Discover how companies build internal tools, AI apps, and production dashboards in pure Python.", - class_name="text-m-slate-7 dark:text-m-slate-6 text-sm font-[475]", + rx.el.div( + badge("Enterprise"), + badge("AI"), + class_name="flex flex-row gap-2", + ), + class_name="flex flex-col gap-2 px-4 pb-4", + ), + rx.el.div( + class_name="h-px w-[calc(100%+2rem)] -mx-4 shrink-0 bg-secondary-4", + ), + rx.el.div( + demo_form_dialog( + trigger=rx.el.div( + rx.el.div( + "Book a Demo", + ui.icon( + "ArrowRight01Icon", + class_name="size-5", + stroke_width=1.5, + ), + class_name="flex flex-row items-center justify-between text-sm font-[525] text-secondary-12", + ), + rx.el.span( + "Reflex in action with your team.", + class_name="text-secondary-11 text-sm font-[475]", + ), + class_name="flex flex-col px-4 py-2 rounded-sm hover-card-shadow cursor-pointer ", + ), ), - logos_carousel(), - class_name="flex flex-col gap-2 px-4 pb-4 h-full", ), - rx.el.elements.a(class_name="absolute inset-0", href="/customers/"), - class_name="flex flex-col gap-6 hover-card-shadow rounded-lg relative h-full hover:[--m-slate-11:var(--m-slate-10)] hover:shadow-card dark:hover:shadow-card-dark", + class_name="flex flex-col relative h-full justify-between", ), - class_name="p-4 block rounded-lg shadow-card dark:shadow-card-dark z-[1] bg-white-1 dark:bg-m-slate-11 dark:border-x dark:border-m-slate-9", + class_name="p-4 block z-[1] bg-secondary-1 dark:border-x dark:border-secondary-4 w-[296px]", ) def solutions_content() -> rx.Component: - """Solutions content. + """Mega-menu body for Solutions: industries, features, case studies. Returns: The component. @@ -404,111 +494,180 @@ def solutions_content() -> rx.Component: rx.el.div( rx.el.div( solutions_column( - "Who's It For", + "Industries", [ - ("Executives", "LocationUser01Icon", "/use-cases/"), - ("Developers", "SourceCodeSquareIcon", "/use-cases/"), - ("Data Teams", "DatabaseIcon", "/use-cases/"), + ("AI", "OfficeIcon", "/use-cases/"), + ("Finance", "Wallet05Icon", "/use-cases/finance/"), + ("Healthcare", "HealthIcon", "/use-cases/healthcare/"), ( - "Non Technical", - "CursorCircleSelection02Icon", - "/use-cases/", + "Tech / SaaS", + "SourceCodeCircleIcon", + "/use-cases/consulting/", ), + ("Government", "BankIcon", "/use-cases/government/"), ], ), solutions_column( - "Industries", + "Features", [ - ("Enterprise", "OfficeIcon", "/use-cases/"), - ("Finance", "Wallet05Icon", "/use-cases/finance/"), - ("Healthcare", "HealthIcon", "/use-cases/healthcare/"), + ("Security", "ShieldKeyIcon", "/docs/enterprise/overview/"), ( - "Consulting", - "DocumentValidationIcon", - "/use-cases/consulting/", + "Auth", + "LoginMethodIcon", + "/docs/authentication/authentication-overview/", + ), + ( + "Role-based access", + "UserUnlock01Icon", + "/docs/hosting/adding-members/", + ), + ( + "On-prem & VPC Deploy", + "ServerStack01Icon", + "/blog/on-premises-deployment/", ), ( - "Government", - "BankIcon", - "/use-cases/government/", + "Integrations", + "PlugSocketIcon", + "/docs/ai/integrations/overview/", ), ], ), class_name="grid grid-cols-2", ), - class_name="p-4 flex flex-col rounded-xl bg-white-1 dark:bg-m-slate-11 h-full w-[28rem] shadow-card dark:shadow-card-dark dark:border-r dark:border-m-slate-9", + class_name="p-4 flex flex-col bg-white-1 h-full w-[28rem] shadow-card dark:shadow-card-dark border-r border-secondary-4", ), - rx.el.div( - solutions_column( - "Migration", - [ - ( - "Switch from No Code", - "WebDesign01Icon", - "/migration/no-code/", - ), - ( - "Switch from Low Code", - "SourceCodeSquareIcon", - "/migration/low-code/", - ), - ( - "Switch from Other Frameworks", - "CodeIcon", - "/migration/other-frameworks/", - ), - ( - "Switch from Other AI tools", - "ArtificialIntelligence04Icon", - "/migration/other-ai-tools/", - ), - ], + case_studies_column(), + class_name="flex flex-row", + ), + ) + + +def resources_agent_column() -> rx.Component: + """Agent onboarding links (agents, MCP, skills). + + Returns: + The component. + """ + return rx.el.div( + rx.el.div( + rx.el.span( + "Agent onboarding", + class_name="font-mono font-[415] text-[0.75rem] leading-4 uppercase pb-4 border-b border-dashed border-secondary-6 text-secondary-11", + ), + class_name="px-4 pt-4 flex flex-col", + ), + rx.el.div( + solutions_row( + "Agents", + "RoboticIcon", + "/docs/ai/integrations/mcp-installation/", + trailing_pill="Overview", + ), + solutions_row( + "MCP", "McpServerIcon", "/docs/ai/integrations/mcp-overview/" + ), + solutions_row("Skills", "ToolsIcon", "/docs/ai/integrations/skills/"), + class_name="flex flex-col", + ), + class_name="flex flex-col gap-4", + ) + + +def resources_blog_column() -> rx.Component: + """Recent posts list with link to the blog index. + + Returns: + The component. + """ + return rx.el.div( + rx.foreach( + RecentBlogsState.posts[:2], + blog_item, + ), + rx.el.elements.a( + "Read All in Blog", + ui.icon("ArrowRight01Icon", class_name="size-4 shrink-0"), + href="/blog", + class_name="text-secondary-12 text-sm font-[525] flex items-center gap-1.5 hover:text-primary-10 dark:hover:text-primary-9 mt-auto pt-3", + ), + on_mount=RecentBlogsState.fetch_recent_blogs, + class_name="flex flex-col gap-4 p-8 h-full justify-between border-l border-secondary-4", + ) + + +def products_menu_footer() -> rx.Component: + """Footer links inside the Products mega-menu. + + Returns: + The component. + """ + return rx.el.div( + rx.el.div( + rx.el.elements.a( + get_icon( + "docs", + class_name="size-[18px] shrink-0 text-secondary-12 group-hover:text-primary-10 dark:group-hover:text-primary-9", ), - class_name="p-4 flex flex-col h-full", + "View All Docs", + ui.icon("ArrowUpRight03Icon", class_name="size-3 shrink-0 -ml-1.75"), + href="/docs/", + target="_blank", + class_name="flex flex-row items-center gap-2 text-sm font-medium text-secondary-12 hover:text-primary-10 dark:hover:text-primary-9 group", ), - class_name="flex flex-row", + rx.el.elements.a( + get_icon( + "reflex_small", + class_name="size-[18px] shrink-0 text-secondary-12 group-hover:text-primary-10 dark:group-hover:text-primary-9", + ), + "Get started for free", + ui.icon("ArrowUpRight03Icon", class_name="size-3 shrink-0 -ml-1.75"), + href=REFLEX_BUILD_URL, + target="_blank", + class_name="flex flex-row items-center gap-2 text-sm font-medium text-secondary-12 hover:text-primary-10 dark:hover:text-primary-9 group", + ), + class_name="flex flex-row items-center gap-8 px-6 py-3", ), + class_name="flex flex-col w-full shrink-0 bg-white-1 border-t border-secondary-4", ) -def resources_content() -> rx.Component: - """Resources content. +def resources_menu_footer() -> rx.Component: + """Community links footer inside the Resources mega-menu. Returns: The component. """ - return menu_content( + return rx.el.div( rx.el.div( - rx.el.div( - solutions_column( - "Developers", - [ - ("Templates", "Layout02Icon", "/templates/"), - ( - "Integrations", - "PlugSocketIcon", - "/docs/ai-builder/integrations/overview/", - ), - ("Changelog", "Clock02Icon", CHANGELOG_URL), - ("Contributing", "GitCommitIcon", CONTRIBUTING_URL), - ("Discussion", "BubbleChatIcon", DISCUSSIONS_URL), - ("FAQ", "HelpSquareIcon", "/faq/"), - ], + rx.el.elements.a( + get_icon( + "github_navbar", + class_name="size-[18px] shrink-0 text-secondary-12 group-hover:text-primary-10 dark:group-hover:text-primary-9", ), - class_name="p-4 flex flex-col rounded-xl bg-m-slate-1 dark:bg-m-slate-12 h-full", + "GitHub", + href=GITHUB_URL, + target="_blank", + class_name="flex flex-row items-center gap-2 text-sm font-medium text-secondary-12 hover:text-primary-10 dark:hover:text-primary-9 group", ), - customers_column(), - rx.el.div( - blog_column(), - class_name="p-4 flex flex-col h-full bg-m-slate-1 dark:bg-m-slate-12", + rx.el.elements.a( + get_icon( + "discord_navbar", + class_name="size-[18px] shrink-0 text-secondary-12 group-hover:text-primary-10 dark:group-hover:text-primary-9", + ), + "Discord", + href=DISCORD_URL, + target="_blank", + class_name="flex flex-row items-center gap-2 text-sm font-medium text-secondary-12 hover:text-primary-10 dark:hover:text-primary-9 group", ), - class_name="w-[52.5rem] grid grid-cols-3", + class_name="flex flex-row items-center gap-8 px-6 py-3", ), + class_name="flex flex-col w-full shrink-0 bg-white-1 border-t border-secondary-4", ) -def about_content() -> rx.Component: - """About content. +def resources_content() -> rx.Component: + """Mega-menu body for Resources: learn, agents, blog. Returns: The component. @@ -516,26 +675,56 @@ def about_content() -> rx.Component: return menu_content( rx.el.div( rx.el.div( - solutions_item("Company", "Profile02Icon", "/about/"), - solutions_item("Careers", "WorkIcon", JOBS_BOARD_URL), - class_name="p-4 flex flex-col rounded-xl bg-white-1 h-full dark:shadow-none dark:border dark:border-m-slate-9 dark:bg-m-slate-11 shadow-card", + rx.el.div( + rx.el.div( + rx.el.div( + solutions_column( + "Learn", + [ + ("Documentation", "File02Icon", "/docs"), + ("Templates", "Layout02Icon", "/templates"), + ("Changelog", "Clock02Icon", CHANGELOG_URL), + ], + ), + resources_agent_column(), + class_name="grid grid-cols-2 gap-8 min-w-0 p-5 bg-white-1 flex-1", + ), + resources_menu_footer(), + class_name="flex flex-col min-w-0 h-full", + ), + resources_blog_column(), + class_name="grid grid-cols-1 min-[480px]:grid-cols-[minmax(0,1fr)_min(17.5rem,40%)] h-full", + ), + class_name="flex flex-col w-[728px] max-w-full overflow-hidden rounded-xl bg-secondary-1 dark:shadow-card-dark", ), - class_name="w-[12.5rem]", + class_name="p-0", ), ) def navigation_menu() -> rx.Component: - """Navigation menu. + """Desktop navigation: mega-menus, CTAs, and GitHub. Returns: The component. """ return ui.navigation_menu.root( ui.navigation_menu.list( - menu_trigger("Platform", platform_content()), - menu_trigger("Solutions", solutions_content()), + menu_trigger("Products", products_content()), menu_trigger("Resources", resources_content()), + menu_trigger("Solutions", solutions_content()), + ui.navigation_menu.item( + rx.el.elements.a( + marketing_button( + "Enterprise", + size="sm", + variant="ghost", + ), + href="/docs/enterprise/overview/", + ), + class_name="xl:flex hidden px-1", + custom_attrs={"role": "menuitem"}, + ), ui.navigation_menu.item( rx.el.elements.a( marketing_button( @@ -561,7 +750,6 @@ def navigation_menu() -> rx.Component: class_name="xl:flex hidden px-1", custom_attrs={"role": "menuitem"}, ), - menu_trigger("About", about_content()), class_name="flex flex-row items-center m-0 h-full list-none", custom_attrs={"role": "menubar"}, ), @@ -615,13 +803,13 @@ def navigation_menu() -> rx.Component: class_name="relative h-full w-full overflow-hidden rounded-[inherit]", ), unstyled=True, - class_name="relative h-[var(--popup-height)] w-[var(--popup-width)] origin-[var(--transform-origin)] rounded-xl bg-m-slate-1 dark:bg-m-slate-12 navbar-shadow transition-[opacity,transform,width,height,scale,translate] duration-150 ease-[cubic-bezier(0.22,1,0.36,1)] data-[ending-style]:ease-[ease] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[ending-style]:duration-150 data-[starting-style]:scale-90 data-[starting-style]:opacity-0", + class_name="relative h-[var(--popup-height)] w-[var(--popup-width)] origin-[var(--transform-origin)] rounded-xl bg-secondary-1 navbar-shadow transition-[opacity,transform,width,height,scale,translate] duration-150 ease-[cubic-bezier(0.22,1,0.36,1)] data-[ending-style]:ease-[ease] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[ending-style]:duration-150 data-[starting-style]:scale-90 data-[starting-style]:opacity-0", ), unstyled=True, class_name="safari-nav-positioner box-border h-[var(--positioner-height)] w-[var(--positioner-width)] max-w-[var(--available-width)] transition-[top,left,right,bottom] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] data-[instant]:transition-none", side_offset=30, align="start", - align_offset=-20, + align_offset=-109, position_method="fixed", ), ), @@ -632,7 +820,7 @@ def navigation_menu() -> rx.Component: @rx.memo def marketing_navbar() -> rx.Component: - """Marketing navbar. + """Fixed header: hosting banner plus logo and full navigation. Returns: The component. @@ -644,7 +832,7 @@ def marketing_navbar() -> rx.Component: rx.el.header( logo(), navigation_menu(), - class_name="w-full max-w-[71.5rem] h-[4.5rem] mx-auto flex flex-row items-center p-5 rounded-b-xl backdrop-blur-[16px] shadow-[0_-2px_2px_1px_rgba(0,0,0,0.02),0_1px_1px_0_rgba(0,0,0,0.08),0_4px_8px_0_rgba(0,0,0,0.03),0_0_0_1px_#FFF_inset] dark:shadow-none dark:border-x dark:border-b dark:border-m-slate-10 bg-gradient-to-b from-white to-m-slate-1 dark:from-m-slate-11 dark:to-m-slate-12", + class_name="w-full max-w-[81rem] h-[4.5rem] mx-auto flex flex-row items-center p-5 rounded-b-xl backdrop-blur-[16px] shadow-[0_-2px_2px_1px_rgba(0,0,0,0.02),0_1px_1px_0_rgba(0,0,0,0.08),0_4px_8px_0_rgba(0,0,0,0.03),0_0_0_1px_#FFF_inset] dark:shadow-none dark:border-x dark:border-b dark:border-m-slate-10 bg-gradient-to-b from-white to-m-slate-1 dark:from-m-slate-11 dark:to-m-slate-12", ), class_name="flex flex-col w-full top-0 z-[9999] fixed self-center", ) diff --git a/packages/reflex-site-shared/src/reflex_site_shared/views/sidebar/__init__.py b/packages/reflex-site-shared/src/reflex_site_shared/views/sidebar/__init__.py index 3b78a151be1..22984658324 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/views/sidebar/__init__.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/views/sidebar/__init__.py @@ -194,7 +194,7 @@ def navbar_sidebar_button() -> rx.Component: }, ), size="icon-sm", - variant="outline", + variant="outline-shadow", custom_attrs={"aria-label": "Open sidebar"}, native_button=False, ), diff --git a/packages/reflex-site-shared/src/reflex_site_shared/views/workflow_stage.py b/packages/reflex-site-shared/src/reflex_site_shared/views/workflow_stage.py new file mode 100644 index 00000000000..d5337f06de5 --- /dev/null +++ b/packages/reflex-site-shared/src/reflex_site_shared/views/workflow_stage.py @@ -0,0 +1,126 @@ +"""Workflow stage visuals (animated arrows) used in marketing mega-menus.""" + +from typing import Literal +from urllib.parse import quote + +import reflex_components_internal as ui + +import reflex as rx + +ArrowSize = Literal["large", "small"] + +_ARROW_VARIANTS: dict[ArrowSize, tuple[int, int]] = { + "large": (26, 136), + "small": (24, 126), +} + +# Total animated groups; delay per group = duration / _ARROW_SWEEP_GROUPS so +# the four sweeps chain end-to-end across one animation cycle. +_ARROW_SWEEP_GROUPS = 4 + + +def _arrow_paths(count: int) -> str: + return "".join( + f'' + for i in range(count) + ) + + +def _arrow_row_svg(count: int, width: int, *, inline_style: bool = False) -> str: + style_attr = ' style="display:block;height:100%;width:auto"' if inline_style else "" + return ( + f'' + f"{_arrow_paths(count)}" + ) + + +def _arrow_mask_data_url(count: int, width: int) -> str: + # Percent-encode the SVG body so Firefox/Safari accept the data URL + # in mask-image; raw `<`, `>`, and spaces are not reliably parsed. + encoded = quote(_arrow_row_svg(count, width), safe="") + return f'url("data:image/svg+xml,{encoded}")' + + +def workflow_stage_image( + wrapper_class_name: str = "", + size: ArrowSize = "large", + sweep_index: int = 0, +) -> rx.Component: + """Animated arrow strip with a phased sweep highlight. + + Returns: + The component. + """ + count, width = _ARROW_VARIANTS[size] + mask_url = _arrow_mask_data_url(count, width) + + return rx.el.div( + rx.html( + _arrow_row_svg(count, width, inline_style=True), + class_name="block h-full w-auto text-secondary-7", + ), + rx.el.div( + rx.el.div( + class_name="absolute -top-[7px] left-0 h-[24px] w-1/2 bg-primary-9 blur-[8px] animate-arrow-sweep", + style={ + "animationDelay": ( + f"calc(var(--arrow-sweep-duration) / {_ARROW_SWEEP_GROUPS} " + f"* {sweep_index})" + ), + }, + ), + class_name=ui.cn( + "absolute inset-0 pointer-events-none overflow-hidden", + "[mask-repeat:no-repeat] [-webkit-mask-repeat:no-repeat]", + "[mask-size:100%_100%] [-webkit-mask-size:100%_100%]", + "[mask-position:left_center] [-webkit-mask-position:left_center]", + ), + style={ + "maskImage": mask_url, + "WebkitMaskImage": mask_url, + }, + ), + class_name=ui.cn( + "relative flex h-[10px] w-auto shrink-0 items-center justify-start overflow-hidden max-lg:hidden", + wrapper_class_name, + ), + custom_attrs={"aria-hidden": "true"}, + ) + + +def workflow_stage_row( + title: str, + *, + left: rx.Component | None = None, + right: rx.Component | None = None, +) -> rx.Component: + """Three-column row: optional left graphic, centered title, optional right graphic. + + Returns: + The component. + """ + left_cell = ( + rx.el.div( + left, + class_name="pointer-events-none flex min-w-0 items-center justify-self-start px-2", + ) + if left is not None + else rx.el.div(class_name="min-w-0 justify-self-start") + ) + right_cell = ( + rx.el.div( + right, + class_name="pointer-events-none flex min-w-0 items-center justify-self-end px-2", + ) + if right is not None + else rx.el.div(class_name="min-w-0 justify-self-end") + ) + return rx.el.div( + left_cell, + rx.el.span(title, class_name="shrink-0 justify-self-center text-center"), + right_cell, + class_name="grid h-12 w-full shrink-0 grid-cols-[1fr_auto_1fr] items-center bg-secondary-1 font-mono text-xs font-[415] uppercase text-secondary-12 lg:border-b border-secondary-4", + )