diff --git a/assets/customers/light/autodesk/autodesk_small.svg b/assets/customers/light/autodesk/autodesk_small.svg index 2fceb4f19d..8c29155c98 100644 --- a/assets/customers/light/autodesk/autodesk_small.svg +++ b/assets/customers/light/autodesk/autodesk_small.svg @@ -3,7 +3,7 @@ - + \ No newline at end of file diff --git a/assets/customers/light/bayesline/bayesline_small.svg b/assets/customers/light/bayesline/bayesline_small.svg index bf576b1dfd..2e4b51f1a6 100644 --- a/assets/customers/light/bayesline/bayesline_small.svg +++ b/assets/customers/light/bayesline/bayesline_small.svg @@ -3,7 +3,7 @@ - + \ No newline at end of file diff --git a/assets/landing/ai_bento/dark/bento1.webp b/assets/landing/ai_bento/dark/bento1.webp new file mode 100644 index 0000000000..4f56f84773 Binary files /dev/null and b/assets/landing/ai_bento/dark/bento1.webp differ diff --git a/assets/landing/ai_bento/dark/bento2.webp b/assets/landing/ai_bento/dark/bento2.webp new file mode 100644 index 0000000000..005ccf9c5d Binary files /dev/null and b/assets/landing/ai_bento/dark/bento2.webp differ diff --git a/assets/landing/ai_bento/dark/bento3.webp b/assets/landing/ai_bento/dark/bento3.webp new file mode 100644 index 0000000000..cda33cdc3c Binary files /dev/null and b/assets/landing/ai_bento/dark/bento3.webp differ diff --git a/assets/landing/ai_bento/dark/bento4.webp b/assets/landing/ai_bento/dark/bento4.webp new file mode 100644 index 0000000000..1b95bbf33c Binary files /dev/null and b/assets/landing/ai_bento/dark/bento4.webp differ diff --git a/assets/landing/ai_bento/dark/bento5.webp b/assets/landing/ai_bento/dark/bento5.webp new file mode 100644 index 0000000000..43a0385c04 Binary files /dev/null and b/assets/landing/ai_bento/dark/bento5.webp differ diff --git a/assets/landing/ai_bento/light/bento1.webp b/assets/landing/ai_bento/light/bento1.webp new file mode 100644 index 0000000000..dff47e1d08 Binary files /dev/null and b/assets/landing/ai_bento/light/bento1.webp differ diff --git a/assets/landing/ai_bento/light/bento2.webp b/assets/landing/ai_bento/light/bento2.webp new file mode 100644 index 0000000000..f784b10947 Binary files /dev/null and b/assets/landing/ai_bento/light/bento2.webp differ diff --git a/assets/landing/ai_bento/light/bento3.webp b/assets/landing/ai_bento/light/bento3.webp new file mode 100644 index 0000000000..e44fa6be7e Binary files /dev/null and b/assets/landing/ai_bento/light/bento3.webp differ diff --git a/assets/landing/ai_bento/light/bento4.webp b/assets/landing/ai_bento/light/bento4.webp new file mode 100644 index 0000000000..b64157b7b6 Binary files /dev/null and b/assets/landing/ai_bento/light/bento4.webp differ diff --git a/assets/landing/ai_bento/light/bento5.webp b/assets/landing/ai_bento/light/bento5.webp new file mode 100644 index 0000000000..f62f50d9f8 Binary files /dev/null and b/assets/landing/ai_bento/light/bento5.webp differ diff --git a/assets/landing/app_build/user.webp b/assets/landing/app_build/user.webp new file mode 100644 index 0000000000..df584c74bc Binary files /dev/null and b/assets/landing/app_build/user.webp differ diff --git a/assets/landing/deploy/dark/deploy_visual.webp b/assets/landing/deploy/dark/deploy_visual.webp new file mode 100644 index 0000000000..cc34810039 Binary files /dev/null and b/assets/landing/deploy/dark/deploy_visual.webp differ diff --git a/assets/landing/deploy/light/deploy_visual.webp b/assets/landing/deploy/light/deploy_visual.webp new file mode 100644 index 0000000000..648cb7cfe6 Binary files /dev/null and b/assets/landing/deploy/light/deploy_visual.webp differ diff --git a/assets/landing/hosting_features/light/card.webp b/assets/landing/hosting_features/light/card.webp index 33d36edaab..871b546612 100644 Binary files a/assets/landing/hosting_features/light/card.webp and b/assets/landing/hosting_features/light/card.webp differ diff --git a/assets/landing/integrations/dark/anthropic.svg b/assets/landing/integrations/dark/anthropic.svg new file mode 100644 index 0000000000..2d728362d7 --- /dev/null +++ b/assets/landing/integrations/dark/anthropic.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/landing/integrations/dark/aws.svg b/assets/landing/integrations/dark/aws.svg new file mode 100644 index 0000000000..62d95183da --- /dev/null +++ b/assets/landing/integrations/dark/aws.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/landing/integrations/dark/azure.svg b/assets/landing/integrations/dark/azure.svg new file mode 100644 index 0000000000..bf46a5d9a1 --- /dev/null +++ b/assets/landing/integrations/dark/azure.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/landing/integrations/dark/databricks.svg b/assets/landing/integrations/dark/databricks.svg new file mode 100644 index 0000000000..6371580628 --- /dev/null +++ b/assets/landing/integrations/dark/databricks.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/landing/integrations/dark/gcp.svg b/assets/landing/integrations/dark/gcp.svg new file mode 100644 index 0000000000..71de948c3d --- /dev/null +++ b/assets/landing/integrations/dark/gcp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/landing/integrations/dark/langchain.svg b/assets/landing/integrations/dark/langchain.svg new file mode 100644 index 0000000000..34ec9b3045 --- /dev/null +++ b/assets/landing/integrations/dark/langchain.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/landing/integrations/dark/okta.svg b/assets/landing/integrations/dark/okta.svg new file mode 100644 index 0000000000..4fd24eaefc --- /dev/null +++ b/assets/landing/integrations/dark/okta.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/landing/integrations/dark/openai.svg b/assets/landing/integrations/dark/openai.svg new file mode 100644 index 0000000000..d05f8d864c --- /dev/null +++ b/assets/landing/integrations/dark/openai.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/assets/landing/integrations/dark/oracle.svg b/assets/landing/integrations/dark/oracle.svg new file mode 100644 index 0000000000..35a29ee38d --- /dev/null +++ b/assets/landing/integrations/dark/oracle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/landing/integrations/dark/reflex.svg b/assets/landing/integrations/dark/reflex.svg new file mode 100644 index 0000000000..46b0b281e9 --- /dev/null +++ b/assets/landing/integrations/dark/reflex.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/landing/integrations/dark/stripe.svg b/assets/landing/integrations/dark/stripe.svg new file mode 100644 index 0000000000..36e2dac9ff --- /dev/null +++ b/assets/landing/integrations/dark/stripe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/landing/integrations/dark/supabase.svg b/assets/landing/integrations/dark/supabase.svg new file mode 100644 index 0000000000..d3a062f8c0 --- /dev/null +++ b/assets/landing/integrations/dark/supabase.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/assets/landing/integrations/light/anthropic.svg b/assets/landing/integrations/light/anthropic.svg new file mode 100644 index 0000000000..db115bbce1 --- /dev/null +++ b/assets/landing/integrations/light/anthropic.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/landing/integrations/light/aws.svg b/assets/landing/integrations/light/aws.svg new file mode 100644 index 0000000000..b2d9005714 --- /dev/null +++ b/assets/landing/integrations/light/aws.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/landing/integrations/light/azure.svg b/assets/landing/integrations/light/azure.svg new file mode 100644 index 0000000000..bf46a5d9a1 --- /dev/null +++ b/assets/landing/integrations/light/azure.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/landing/integrations/light/databricks.svg b/assets/landing/integrations/light/databricks.svg new file mode 100644 index 0000000000..6371580628 --- /dev/null +++ b/assets/landing/integrations/light/databricks.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/landing/integrations/light/gcp.svg b/assets/landing/integrations/light/gcp.svg new file mode 100644 index 0000000000..71de948c3d --- /dev/null +++ b/assets/landing/integrations/light/gcp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/landing/integrations/light/langchain.svg b/assets/landing/integrations/light/langchain.svg new file mode 100644 index 0000000000..746c553f73 --- /dev/null +++ b/assets/landing/integrations/light/langchain.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/landing/integrations/light/okta.svg b/assets/landing/integrations/light/okta.svg new file mode 100644 index 0000000000..3ef8b56236 --- /dev/null +++ b/assets/landing/integrations/light/okta.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/landing/integrations/light/openai.svg b/assets/landing/integrations/light/openai.svg new file mode 100644 index 0000000000..2ebab679f6 --- /dev/null +++ b/assets/landing/integrations/light/openai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/landing/integrations/light/oracle.svg b/assets/landing/integrations/light/oracle.svg new file mode 100644 index 0000000000..35a29ee38d --- /dev/null +++ b/assets/landing/integrations/light/oracle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/landing/integrations/light/r_logo.svg b/assets/landing/integrations/light/r_logo.svg new file mode 100644 index 0000000000..2a658ba262 --- /dev/null +++ b/assets/landing/integrations/light/r_logo.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/landing/integrations/light/reflex.svg b/assets/landing/integrations/light/reflex.svg new file mode 100644 index 0000000000..46b0b281e9 --- /dev/null +++ b/assets/landing/integrations/light/reflex.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/landing/integrations/light/slack.svg b/assets/landing/integrations/light/slack.svg new file mode 100644 index 0000000000..6d14c726ca --- /dev/null +++ b/assets/landing/integrations/light/slack.svg @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/assets/landing/integrations/light/stripe.svg b/assets/landing/integrations/light/stripe.svg new file mode 100644 index 0000000000..36e2dac9ff --- /dev/null +++ b/assets/landing/integrations/light/stripe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/landing/integrations/light/supabase.svg b/assets/landing/integrations/light/supabase.svg new file mode 100644 index 0000000000..d3a062f8c0 --- /dev/null +++ b/assets/landing/integrations/light/supabase.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/assets/landing/os_bento/light/bento1.webp b/assets/landing/os_bento/light/bento1.webp new file mode 100644 index 0000000000..4dda7558af Binary files /dev/null and b/assets/landing/os_bento/light/bento1.webp differ diff --git a/assets/landing/os_bento/light/bento2.webp b/assets/landing/os_bento/light/bento2.webp new file mode 100644 index 0000000000..5df567ab63 Binary files /dev/null and b/assets/landing/os_bento/light/bento2.webp differ diff --git a/assets/landing/os_bento/light/bento3.webp b/assets/landing/os_bento/light/bento3.webp new file mode 100644 index 0000000000..10ab075830 Binary files /dev/null and b/assets/landing/os_bento/light/bento3.webp differ diff --git a/assets/landing/os_bento/light/bento4.webp b/assets/landing/os_bento/light/bento4.webp new file mode 100644 index 0000000000..a8471fd7a0 Binary files /dev/null and b/assets/landing/os_bento/light/bento4.webp differ diff --git a/assets/landing/os_bento/light/bento5.webp b/assets/landing/os_bento/light/bento5.webp new file mode 100644 index 0000000000..efbe010b17 Binary files /dev/null and b/assets/landing/os_bento/light/bento5.webp differ diff --git a/assets/landing/os_bento/light/bento6.webp b/assets/landing/os_bento/light/bento6.webp new file mode 100644 index 0000000000..e4e5f71ab6 Binary files /dev/null and b/assets/landing/os_bento/light/bento6.webp differ diff --git a/assets/landing/patterns/dark/numbers-img.webp b/assets/landing/patterns/dark/numbers-img.webp new file mode 100644 index 0000000000..7976e7831d Binary files /dev/null and b/assets/landing/patterns/dark/numbers-img.webp differ diff --git a/assets/landing/patterns/dark/numbers-reversed-img.webp b/assets/landing/patterns/dark/numbers-reversed-img.webp new file mode 100644 index 0000000000..6e469e3054 Binary files /dev/null and b/assets/landing/patterns/dark/numbers-reversed-img.webp differ diff --git a/assets/landing/patterns/dark/numbers-right-img.webp b/assets/landing/patterns/dark/numbers-right-img.webp new file mode 100644 index 0000000000..94afd02cd2 Binary files /dev/null and b/assets/landing/patterns/dark/numbers-right-img.webp differ diff --git a/assets/landing/patterns/dark/numbers-right-reversed-img.webp b/assets/landing/patterns/dark/numbers-right-reversed-img.webp new file mode 100644 index 0000000000..2e467e7521 Binary files /dev/null and b/assets/landing/patterns/dark/numbers-right-reversed-img.webp differ diff --git a/assets/landing/patterns/light/numbers-img.webp b/assets/landing/patterns/light/numbers-img.webp new file mode 100644 index 0000000000..e7f10bc1fd Binary files /dev/null and b/assets/landing/patterns/light/numbers-img.webp differ diff --git a/assets/landing/patterns/light/numbers-reversed-img.webp b/assets/landing/patterns/light/numbers-reversed-img.webp new file mode 100644 index 0000000000..0cbcbfeaf5 Binary files /dev/null and b/assets/landing/patterns/light/numbers-reversed-img.webp differ diff --git a/assets/landing/patterns/light/numbers-right-img.webp b/assets/landing/patterns/light/numbers-right-img.webp new file mode 100644 index 0000000000..ecaafe9b49 Binary files /dev/null and b/assets/landing/patterns/light/numbers-right-img.webp differ diff --git a/assets/landing/patterns/light/numbers-right-reversed-img.webp b/assets/landing/patterns/light/numbers-right-reversed-img.webp new file mode 100644 index 0000000000..7e7e8d4ff2 Binary files /dev/null and b/assets/landing/patterns/light/numbers-right-reversed-img.webp differ diff --git a/assets/tailwind-theme.css b/assets/tailwind-theme.css index fb84218190..7e2b702229 100644 --- a/assets/tailwind-theme.css +++ b/assets/tailwind-theme.css @@ -1121,7 +1121,20 @@ --animate-spin: spin 1s linear infinite; --animate-blur-in: blur-in 0.15s ease forwards; --animate-border: border 3s linear infinite; - --animate-fade-in-scale: fade-in-scale 0.3s ease-out; + --animate-slide-in-right: slide-in-right both; + --animate-slide-in-left: slide-in-left both; + --animate-slide-in-up: slide-in-up both; + --animate-slide-in-down: slide-in-down both; + --animate-scale-rotate-in: scale-rotate-in both; + --animate-scale-in-top-right: scale-in-top-right both; + --animate-slide-down: slide-down 2.4s linear infinite; + --animate-slide-down-full: slide-down-full 2.4s linear infinite; + --animate-blink: blink 1.25s step-end infinite; + --animate-ellipse-1: ellipse-1 2400ms ease-out infinite; + --animate-ellipse-2: ellipse-2 2400ms ease-out infinite; + --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; /* Radius */ --radius-ui-xxs: calc(var(--radius) - 0.25rem); --radius-ui-xs: calc(var(--radius) - 0.125rem); @@ -1189,6 +1202,342 @@ } } + @keyframes slide-in-right { + from { + transform: translateX(100%); + } + + to { + transform: translateX(0); + } + } + + @keyframes slide-in-left { + from { + transform: translateX(-100%); + } + + to { + transform: translateX(0); + } + } + + @keyframes slide-in-up { + from { + transform: translateY(100%); + } + + to { + transform: translateY(0); + } + } + + @keyframes slide-in-down { + from { + transform: translateY(-100%); + } + + to { + transform: translateY(0); + } + } + + @keyframes scale-rotate-in { + 0% { + transform: scale(0) rotate(-100deg); + } + + 100% { + transform: scale(1) rotate(0deg); + } + } + + @keyframes scale-in-top-right { + from { + transform: scale(0); + transform-origin: top right; + } + + to { + transform: scale(1); + transform-origin: top right; + } + } + + @keyframes slide-down-full { + 0% { + transform: translateY(-100%); + } + + 100% { + transform: translateY(calc(100vh + 5rem)); + } + + } + + @keyframes blink { + 50% { + opacity: 0; + } + } + + @keyframes slide-center-to-left { + + 0%, + 100% { + transform: translateX(0) translateY(-50%); + } + + 50% { + transform: translateX(-200%) translateY(-50%); + } + } + + @keyframes slide-center-to-right { + + 0%, + 100% { + transform: translateX(0) translateY(-50%); + } + + 50% { + transform: translateX(200%) translateY(-50%); + } + } + + @keyframes fade-scale-out { + 0% { + opacity: 1; + transform: scale(1); + } + + 7.5%, + 50% { + opacity: 0; + transform: scale(0.55); + } + + 56.5%, + 100% { + opacity: 1; + transform: scale(1); + } + } + + @keyframes fade-scale-in { + 0% { + opacity: 0; + transform: scale(0.55); + } + + 7.5%, + 50% { + opacity: 1; + transform: scale(1); + } + + 56.5%, + 100% { + opacity: 0; + transform: scale(0.55); + } + } + + @keyframes ellipse-slide-left { + 0% { + left: -13.75rem; + } + + 7.5% { + left: 17rem; + } + + 20% { + left: 17rem; + } + + 22% { + left: 14rem; + } + + 24% { + left: 10rem; + } + + 27.5% { + left: 4rem; + } + + 30% { + left: -2rem; + } + + 34.5% { + left: -13.75rem; + } + + 50% { + left: -13.75rem; + } + + 57.5% { + left: 17rem; + } + + 70% { + left: 17rem; + } + + 72% { + left: 14rem; + } + + 74% { + left: 10rem; + } + + 77.5% { + left: 4rem; + } + + 80% { + left: -2rem; + } + + 84.5% { + left: -13.75rem; + } + + 100% { + left: -13.75rem; + } + } + + @keyframes ellipse-slide-right { + 0% { + right: -13.75rem; + } + + 7.5% { + right: 17rem; + } + + 20% { + right: 17rem; + } + + 22% { + right: 14rem; + } + + 24% { + right: 10rem; + } + + 27.5% { + right: 4rem; + } + + 30% { + right: -2rem; + } + + 34.5% { + right: -13.75rem; + } + + 50% { + right: -13.75rem; + } + + 57.5% { + right: 17rem; + } + + 70% { + right: 17rem; + } + + 72% { + right: 14rem; + } + + 74% { + right: 10rem; + } + + 77.5% { + right: 4rem; + } + + 80% { + right: -2rem; + } + + 84.5% { + right: -13.75rem; + } + + 100% { + right: -13.75rem; + } + } + + @keyframes prompt-box-line { + 0% { + filter: blur(8px); + opacity: 0; + transform: scale(0.25); + } + + 100% { + filter: blur(0); + opacity: 1; + transform: scale(1); + } + } + + @keyframes ellipse-1 { + 0% { + top: calc(-100% - 16.81rem); + } + + 100% { + top: calc(100% + 2rem); + } + } + + @keyframes ellipse-2 { + 0% { + top: calc(-100% - 10.12rem); + } + + 100% { + top: calc(100% + 3.06rem); + } + } + + @keyframes ellipse-3 { + 0% { + top: calc(-100% - 10rem); + } + + 100% { + top: calc(100% + 11.69rem); + } + } + + @keyframes ellipse-4 { + 0% { + top: calc(-100% - 2rem); + } + + 100% { + top: calc(100% + 19.31rem); + } + } + + } @layer base { diff --git a/pcweb/components/docpage/navbar/buttons/discord.py b/pcweb/components/docpage/navbar/buttons/discord.py index 12b88fa0d1..85af131dba 100644 --- a/pcweb/components/docpage/navbar/buttons/discord.py +++ b/pcweb/components/docpage/navbar/buttons/discord.py @@ -1,14 +1,18 @@ import reflex as rx +import reflex_ui as ui from pcweb.components.icons.icons import get_icon from pcweb.constants import DISCORD_URL def discord() -> rx.Component: - return rx.link( - get_icon(icon="discord_navbar", class_name="shrink-0 !text-slate-9"), - custom_attrs={"aria-label": "Discord link"}, - class_name="hover:bg-slate-3 size-8 text-slate-9 flex justify-center items-center rounded-[10px] border border-solid border-slate-5 bg-slate-1 transition-bg cursor-pointer py-0.5 px-3 hover:!text-slate-9", - underline="none", - href=DISCORD_URL, + return ui.link( + render_=ui.button( + get_icon(icon="discord_navbar", class_name="shrink-0 text-secondary-11"), + custom_attrs={"aria-label": "Discord link"}, + size="icon-sm", + variant="outline", + class_name="text-secondary-11", + ), + to=DISCORD_URL, ) diff --git a/pcweb/components/docpage/navbar/buttons/github.py b/pcweb/components/docpage/navbar/buttons/github.py index 9a3c1b619a..0cd911fb56 100644 --- a/pcweb/components/docpage/navbar/buttons/github.py +++ b/pcweb/components/docpage/navbar/buttons/github.py @@ -1,20 +1,19 @@ import reflex as rx +import reflex_ui as ui from pcweb.components.icons.icons import get_icon -from pcweb.constants import GITHUB_URL -from pcweb.github import GithubStarState +from pcweb.constants import GITHUB_STARS, GITHUB_URL def github() -> rx.Component: - return rx.link( - rx.flex( - get_icon(icon="github_navbar", class_name="shrink-0 !text-slate-9"), - rx.text( - GithubStarState.stars_short, - class_name="font-small", - ), - class_name="text-slate-9 flex-row gap-2 hover:bg-slate-3 flex justify-center rounded-[10px] border border-slate-5 bg-slate-1 transition-bg cursor-pointer py-0.5 px-3 items-center h-8", + return ui.link( + render_=ui.button( + get_icon(icon="github_navbar", class_name="shrink-0 text-secondary-11"), + f"{GITHUB_STARS // 1000}K", + custom_attrs={"aria-label": "Github link"}, + size="sm", + variant="outline", + class_name="text-secondary-11", ), - href=GITHUB_URL, - underline="none", + to=GITHUB_URL, ) diff --git a/pcweb/components/docpage/navbar/navbar.py b/pcweb/components/docpage/navbar/navbar.py index 07a6512c68..6032352ef3 100644 --- a/pcweb/components/docpage/navbar/navbar.py +++ b/pcweb/components/docpage/navbar/navbar.py @@ -1,10 +1,9 @@ """UI and logic for the navbar component.""" import reflex as rx +import reflex_ui as ui from reflex_ui.blocks.lemcal import lemcal_dialog -from pcweb.components.button import button -from pcweb.components.docpage.navbar.navmenu.navmenu import nav_menu from pcweb.components.hosting_banner import hosting_banner from pcweb.constants import REFLEX_BUILD_URL, REFLEX_CLOUD_URL from pcweb.pages.blog import blogs @@ -25,25 +24,23 @@ def resource_item(text: str, url: str, icon: str, index): return rx.el.li( - rx.link( + rx.el.a( rx.box( - rx.icon(icon, size=16, class_name="flex-shrink-0 text-slate-9"), - rx.spacer(), + ui.icon(icon, size=16, class_name="flex-shrink-0 text-slate-9"), rx.text( text, class_name="font-small text-slate-9 truncate text-start w-[150px]", ), - rx.spacer(), rx.icon( tag="chevron_right", size=14, - class_name="flex-shrink-0 text-slate-12", + stroke_width=2, + class_name="flex-shrink-0 text-slate-8 ml-auto", ), - class_name="flex flex-row flex-nowrap items-center gap-3 hover:bg-slate-3 px-[1.125rem] py-2 rounded-md w-full transition-bg justify-between", + class_name="flex flex-row flex-nowrap items-center gap-4 hover:bg-secondary-3 px-[1.125rem] py-2 rounded-md w-full transition-colors", ), - class_name="w-full text-slate-9 hover:!text-slate-9", - underline="none", - href=url, + class_name="w-full text-slate-9 hover:text-slate-9", + to=url, on_click=SidebarState.set_sidebar_index(index), ), class_name="w-full", @@ -92,20 +89,13 @@ def link_item(name: str, url: str, active_str: str = ""): else: active = False - common_cn = "transition-color p-[1.406rem_0px] font-small xl:flex hidden items-center justify-center " - active_cn = "shadow-[inset_0_-1px_0_0_var(--c-violet-9)] text-violet-9" - unactive_cn = "shadow-none text-slate-9" + common_cn = "transition-color p-[1.406rem_0px] font-small xl:flex hidden items-center justify-center hover:text-secondary-12 " + active_cn = "shadow-[inset_0_-0.5px_0_0_var(--c-violet-9)] text-violet-9 hover:text-violet-9" + unactive_cn = "shadow-none text-secondary-11" - return rx.link( + return rx.el.a( name, - href=url, - underline="none", - _hover={"color": rx.cond(active, "var(--c-violet-9)", "var(--c-slate-11)")}, - style={ - ":hover": { - "color": rx.cond(active, "var(--c-violet-9)", "var(--c-slate-11)") - } - }, + to=url, class_name=common_cn + rx.cond(active, active_cn, unactive_cn), on_click=SidebarState.set_sidebar_index(0), ) @@ -136,7 +126,7 @@ def blog_section_item(date: str, title: str, url: str) -> rx.Component: def blog_section() -> rx.Component: - return nav_menu.content( + return ui.navigation_menu.content( rx.box( rx.link( rx.moment( @@ -213,12 +203,12 @@ def blog_section() -> rx.Component: def link_button(label: str, url: str) -> rx.Component: - return rx.link( + return rx.el.a( resources_button( label, size="md", variant="transparent", class_name="justify-start w-full" ), - href=url, - is_external=True, + to=url, + target="_blank", underline="none", class_name="!w-full", ) @@ -227,12 +217,12 @@ def link_button(label: str, url: str) -> rx.Component: def grid_card( title: str, description: str, url: str, image: str, image_style: str ) -> rx.Component: - return rx.link( + return rx.el.a( rx.box( rx.text(title, class_name="text-slate-12 text-base font-semibold"), rx.el.button( rx.icon("chevron-right", class_name="text-slate-9 size-4"), - class_name="size-6 group-hover:bg-slate-3 transition-bg rounded-md flex items-center justify-center", + class_name="size-6 group-hover:bg-secondary-3 transition-colors rounded-md flex items-center justify-center", ), class_name="flex flex-row items-center gap-2 justify-between", ), @@ -241,9 +231,7 @@ def grid_card( src=image, class_name=image_style, ), - href=url, - is_external=False, - underline="none", + to=url, class_name="w-[14.5rem] rounded-md shadow-small bg-white-1 border border-slate-4 flex flex-col gap-3 p-5 relative border-solid !h-[16.5625rem] overflow-hidden group", ) @@ -273,50 +261,48 @@ def new_resource_section(): { "label": "Newsletter", "url": "https://reflex.dev/open-source/#newsletter", - "icon": "mails", + "icon": "MailAtSign01Icon", }, - {"label": "Blog", "url": "/blog", "icon": "library-big"}, - {"label": "Affiliates", "url": "/affiliates", "icon": "network"}, - {"label": "Use Cases", "url": use_cases_page.path, "icon": "list-checks"}, + {"label": "Blog", "url": "/blog", "icon": "RightToLeftListDashIcon"}, + {"label": "Affiliates", "url": "/affiliates", "icon": "AddTeamIcon"}, + {"label": "Use Cases", "url": use_cases_page.path, "icon": "CheckListIcon"}, ] _open_source_items = [ - {"label": "Templates", "url": "/templates", "icon": "layout-panel-top"}, + {"label": "Templates", "url": "/templates", "icon": "SidebarTopIcon"}, { "label": "Changelog", "url": "https://github.com/reflex-dev/reflex/releases", - "icon": "history", + "icon": "Clock02Icon", }, { "label": "Contributing", "url": "https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md", - "icon": "handshake", + "icon": "SourceCodeIcon", }, { "label": "Discussions", "url": "https://github.com/orgs/reflex-dev/discussions", - "icon": "message-square-text", + "icon": "Message01Icon", }, { "label": "FAQ", "url": faq.path, - "icon": "table-of-contents", + "icon": "HelpCircleIcon", }, ] def _link_button(label: str, url: str, icon: str) -> rx.Component: - return rx.link( + return rx.el.a( resources_button( - rx.icon(icon, class_name="size-4"), + ui.icon(icon, size=16, class_name="flex-shrink-0"), label, size="md", variant="transparent", - class_name="justify-start w-full items-center", + class_name="justify-start w-full items-center gap-3", ), - href=url, - is_external=False, - underline="none", - class_name="!w-full", + to=url, + class_name="w-full", ) def _resource_section_column( @@ -326,18 +312,18 @@ def _resource_section_column( rx.box( rx.text( section_title, - class_name="text-sm text-slate-10 font-semibold px-2.5 py-1", - ), - rx.foreach( - resource_item, - lambda item: _link_button(item["label"], item["url"], item["icon"]), + class_name="text-sm text-slate-12 font-semibold px-2.5 py-1 pb-2", ), + *[ + _link_button(item["label"], item["url"], item["icon"]) + for item in resource_item + ], class_name="flex flex-col w-full p-2", ), class_name="flex flex-col w-full max-w-[9.1875rem]", ) - return nav_menu.content( + return ui.navigation_menu.content( _resource_section_column("Open Source", _open_source_items), _resource_section_column("Company", _company_items), # Grid cards @@ -354,7 +340,11 @@ def _resource_section_column( ), class_name="grid grid-cols-2 gap-3 p-3 bg-slate-1", ), - class_name="flex flex-row shadow-large rounded-xl bg-slate-2 border border-slate-5 w-[34.55rem] font-sans overflow-hidden", + unstyled=True, + class_name=ui.cn( + ui.navigation_menu.class_names.CONTENT, + "flex flex-row rounded-xl w-[34.55rem] font-sans overflow-hidden p-1.5", + ), ) @@ -362,25 +352,26 @@ def new_menu_trigger( title: str, url: str | None = None, active_str: str = "" ) -> rx.Component: if url: - return nav_menu.trigger(link_item(title, url, active_str)) - return nav_menu.trigger( + return ui.navigation_menu.trigger(link_item(title, url, active_str)) + return ui.navigation_menu.trigger( rx.box( rx.text( title, - class_name="p-[1.406rem_0px] font-small text-slate-9 group-hover:text-slate-11 transition-colors", + class_name="p-[1.406rem_0px] font-medium text-sm text-secondary-11 group-hover:text-secondary-12 transition-colors", ), rx.icon( "chevron-down", - class_name="chevron size-5 !text-slate-9 group-hover:!text-slate-11 py-1 mr-0 transition-all ease-out", + class_name="chevron size-5 !text-secondary-11 group-hover:!text-secondary-12 py-1 mr-0 transition-all ease-out", ), class_name="flex-row items-center gap-x-1 group user-select-none cursor-pointer xl:flex hidden", on_click=rx.stop_propagation, ), style={ - "&[data-state='open'] .chevron": { + "&[data-popup-open] .chevron": { "transform": "rotate(180deg)", }, }, + unstyled=True, ) @@ -406,21 +397,26 @@ def logo() -> rx.Component: def doc_section(): from pcweb.pages.docs import hosting as hosting_page - return nav_menu.content( - rx.el.ul( - resource_item( - "AI Builder Docs", - ai_builder.overview.best_practices.path, - "bot", - 0, - ), - resource_item( - "Open Source Docs", getting_started.introduction.path, "frame", 0 - ), - resource_item( - "Cloud Docs", hosting_page.deploy_quick_start.path, "server", 0 - ), - class_name="items-start gap-1.5 gap-x-1.5 grid grid-cols-1 m-0 p-1.5 w-[280px] min-w-max", + return ui.navigation_menu.content( + resource_item( + "AI Builder Docs", + ai_builder.overview.best_practices.path, + "MagicWand01Icon", + 0, + ), + resource_item( + "Open Source Docs", + getting_started.introduction.path, + "SourceCodeCircleIcon", + 0, + ), + resource_item( + "Cloud Docs", hosting_page.deploy_quick_start.path, "CloudServerIcon", 0 + ), + unstyled=True, + class_name=ui.cn( + ui.navigation_menu.class_names.CONTENT, + "items-start gap-1.5 gap-x-1.5 grid grid-cols-1 m-0 p-1.5 w-[280px] min-w-max", ), ) @@ -429,9 +425,9 @@ def new_component_section() -> rx.Component: from pcweb.pages.docs import ai_builder as ai_builder_pages from pcweb.pages.docs import hosting as hosting_page - return nav_menu.root( - nav_menu.list( - nav_menu.item( + return ui.navigation_menu.root( + ui.navigation_menu.list( + ui.navigation_menu.item( rx.box( logo(), rx.badge( @@ -449,6 +445,7 @@ def new_component_section() -> rx.Component: ), ), class_name="flex flex-row gap-x-0 items-center", + unstyled=True, ), ), rx.cond( @@ -456,47 +453,53 @@ def new_component_section() -> rx.Component: | rx.State.router.page.path.contains("ai-builder") | rx.State.router.page.path.contains("cloud"), rx.el.div( - nav_menu.item( - link_item( + ui.navigation_menu.item( + render_=link_item( "AI Builder", ai_builder_pages.overview.best_practices.path, "builder", ), + unstyled=True, ), - nav_menu.item( - link_item( + ui.navigation_menu.item( + render_=link_item( "Open Source", getting_started.introduction.path, "framework", ), + unstyled=True, class_name="whitespace-nowrap", ), - nav_menu.item( - link_item( + ui.navigation_menu.item( + render_=link_item( "Cloud", hosting_page.deploy_quick_start.path, "hosting" ), + unstyled=True, ), class_name="xl:flex hidden flex-row items-center gap-0 lg:gap-5 2xl:gap-7 m-0 h-full list-none", ), rx.el.div( - nav_menu.item( - link_item( + ui.navigation_menu.item( + render_=link_item( "AI Builder", REFLEX_BUILD_URL, "builder", ), + unstyled=True, ), - nav_menu.item( - link_item("Open Source", framework.path, "framework"), + ui.navigation_menu.item( + render_=link_item("Open Source", framework.path, "framework"), class_name="whitespace-nowrap", + unstyled=True, ), - nav_menu.item( - link_item("Cloud", hosting_landing.path, "hosting"), + ui.navigation_menu.item( + render_=link_item("Cloud", hosting_landing.path, "hosting"), + unstyled=True, ), class_name="xl:flex hidden flex-row items-center gap-0 lg:gap-5 2xl:gap-7 m-0 h-full list-none", ), ), - nav_menu.item( + ui.navigation_menu.item( new_menu_trigger("Docs"), doc_section(), display=rx.cond( @@ -507,28 +510,39 @@ def new_component_section() -> rx.Component: "block", ), class_name="cursor-pointer", + unstyled=True, ), - nav_menu.item( + ui.navigation_menu.item( new_menu_trigger("Resources"), new_resource_section(), class_name="cursor-pointer", + unstyled=True, ), - nav_menu.item( - new_menu_trigger("Pricing", "/pricing", "pricing"), + ui.navigation_menu.item( + ui.navigation_menu.item( + render_=link_item( + "Pricing", + "/pricing", + "pricing", + ), + unstyled=True, + ), class_name="xl:flex hidden", + unstyled=True, ), class_name="flex flex-row items-center gap-0 lg:gap-5 2xl:gap-7 m-0 h-full list-none", ), - nav_menu.list( - nav_menu.item(search_bar()), - nav_menu.item(github()), - nav_menu.item(discord(), class_name="xl:flex hidden"), - nav_menu.item( + ui.navigation_menu.list( + ui.navigation_menu.item(search_bar()), + ui.navigation_menu.item(github()), + ui.navigation_menu.item(discord(), class_name="xl:flex hidden"), + ui.navigation_menu.item( rx.link( - button( + ui.button( "Sign In", + size="sm", variant="secondary", - class_name="!h-8 !font-small-smbold !rounded-[0.625rem] whitespace-nowrap", + class_name="font-semibold text-secondary-11 whitespace-nowrap", ), underline="none", is_external=True, @@ -536,22 +550,35 @@ def new_component_section() -> rx.Component: ), class_name="desktop-only", ), - nav_menu.item( - lemcal_dialog( - button( + ui.navigation_menu.item( + render_=lemcal_dialog( + ui.button( "Book a Demo", - class_name="!h-8 !font-small-smbold !rounded-[0.625rem] whitespace-nowrap", + size="sm", + variant="primary", + class_name="font-semibold whitespace-nowrap", ), ), + unstyled=True, class_name="xl:flex hidden", ), - nav_menu.item(navbar_sidebar_button(), class_name="xl:hidden flex"), + ui.navigation_menu.item( + navbar_sidebar_button(), + class_name="xl:hidden flex", + unstyled=True, + ), class_name="flex flex-row gap-2 m-0 h-full list-none items-center", ), - rx.box( - nav_menu.viewport(), - class_name="top-[80%] left-[250px] absolute flex justify-start w-full", + ui.navigation_menu.portal( + ui.navigation_menu.positioner( + ui.navigation_menu.popup( + ui.navigation_menu.viewport(), + ), + side_offset=-10, + ), ), + unstyled=True, + class_name="relative flex w-full items-center h-full justify-between gap-6 mx-auto z-[9999] flex-row max-w-[94.5rem]", ) @@ -561,7 +588,7 @@ def navbar() -> rx.Component: hosting_banner(), rx.el.header( new_component_section(), - class_name="flex flex-row items-center gap-12 bg-slate-1 shadow-[inset_0_-1px_0_0_var(--c-slate-3)] px-4 lg:px-6 w-screen h-[48px] lg:h-[65px]", + class_name="flex flex-row items-center gap-12 bg-slate-1 shadow-[inset_0_-0.5px_0_0_var(--c-slate-3)] px-4 lg:px-6 w-screen h-[48px] lg:h-[65px]", ), - class_name="flex flex-col w-full top-0 z-[9999] fixed text-slate-12", + class_name="flex flex-col w-full top-0 z-[9999] fixed text-secondary-12", ) diff --git a/pcweb/components/docpage/navbar/navmenu/navmenu.py b/pcweb/components/docpage/navbar/navmenu/navmenu.py deleted file mode 100644 index 1bccd8eba2..0000000000 --- a/pcweb/components/docpage/navbar/navmenu/navmenu.py +++ /dev/null @@ -1,116 +0,0 @@ -"""Reflex custom component NavMenu.""" - -from types import SimpleNamespace -from typing import Any, Dict, Literal - -import reflex as rx -from reflex.vars import Var - -LiteralMenuComponentDir = Literal["ltr", "rtl"] -LiteralMenuComponentOrientation = Literal["vertical", "horizontal"] - - -class NavMenu(rx.Component): - """NavMenu component.""" - - library = "@radix-ui/react-navigation-menu@^1.2.0" - - -class NavMenuRoot(NavMenu): - """Navigation menu root component.""" - - tag = "Root" - alias = "RadixNavigationMenuRoot" - - value: Var[str] - default_value: Var[str] - delay_duration: Var[int] = 200 - skip_delay_duration: Var[int] = 300 - orientation: Var[LiteralMenuComponentOrientation] = "horizontal" - dir: Var[LiteralMenuComponentDir] - - class_name: Var[str] | str = ( - "relative flex w-full items-center h-full justify-between gap-6 mx-auto z-[9999] flex-row max-w-[94.5rem]" - ) - - def get_event_triggers(self) -> Dict[str, Any]: - return { - **super().get_event_triggers(), - "on_value_change": lambda e0: [e0], - } - - -class NavMenuList(NavMenu): - """Contains the top level menu items.""" - - tag = "List" - alias = "RadixNavigationMenuList" - - as_child: Var[bool] = False - - -class NavMenuItem(NavMenu): - """A top level menu item, contains a link or trigger with content combination.""" - - tag = "Item" - alias = "RadixNavigationMenuItem" - - as_child: Var[bool] = False - value: Var[str] - - -class NavMenuTrigger(NavMenu): - """The button that toggles the content.""" - - tag = "Trigger" - alias = "RadixNavigationMenuTrigger" - - as_child: Var[bool] = False - - class_name: Var[str] | str = "outline-none user-select-none" - - -class NavMenuContent(NavMenu): - """Contains the content associated with each trigger.""" - - tag = "Content" - alias = "RadixNavigationMenuContent" - - as_child: Var[bool] = False - force_mount: Var[bool] - - on_escape_key_down: rx.EventHandler[lambda e: [e]] = None - on_pointer_down_outside: rx.EventHandler[lambda e: [e]] = None - on_focus_outside: rx.EventHandler[lambda e: [e]] = None - on_interact_outside: rx.EventHandler[lambda e: [e]] = None - class_name: Var[str] | str = ( - "data-[motion=from-start]:animate-enterFromLeft data-[motion=from-end]:animate-enterFromRight data-[motion=to-start]:animate-exitToLeft data-[motion=to-end]:animate-exitToRight absolute top-0 left-0 w-full sm:w-auto", - ) - - -class NavMenuViewport(NavMenu): - """An optional viewport element that is used to render active content outside of the list.""" - - tag = "Viewport" - alias = "RadixNavigationMenuViewport" - - as_child: Var[bool] = False - force_mount: Var[bool] - - class_name: Var[str] | str = ( - "data-[state=open]:animate-scaleIn data-[state=closed]:animate-scaleOut relative h-[var(--radix-navigation-menu-viewport-height)] w-full origin-[top_left] overflow-hidden bg-slate-2 transition-[width,_height] duration-300 sm:w-[var(--radix-navigation-menu-viewport-width)] border border-slate-5 shadow-large rounded-xl" - ) - - -class NavMenu(SimpleNamespace): - """NavMenu component.""" - - root = __call__ = staticmethod(NavMenuRoot.create) - list = staticmethod(NavMenuList.create) - item = staticmethod(NavMenuItem.create) - trigger = staticmethod(NavMenuTrigger.create) - content = staticmethod(NavMenuContent.create) - viewport = staticmethod(NavMenuViewport.create) - - -nav_menu = NavMenu() diff --git a/pcweb/components/docpage/navbar/typesense.py b/pcweb/components/docpage/navbar/typesense.py index c86aad61d2..7271a2e568 100644 --- a/pcweb/components/docpage/navbar/typesense.py +++ b/pcweb/components/docpage/navbar/typesense.py @@ -316,21 +316,23 @@ def keyboard_shortcut_script() -> rx.Component: def search_trigger() -> rx.Component: """Render the search trigger button.""" - return rx.box( - ui.icon( - "Search01Icon", - class_name="md:absolute md:left-2 md:top-1/2 md:transform md:-translate-y-1/2 text-md w-4 h-4 flex-shrink-0 !text-slate-9", + return ui.button( + rx.html( + """ + +""" ), - rx.text( - "⌘K", - class_name="absolute right-2 top-1/2 transform -translate-y-1/2 text-sm bg-slate-3 rounded-md text-sm !text-slate-9 px-[5px] py-[2px] hidden md:inline", + rx.el.span( + "Search", + class_name="hidden md:block text-sm font-medium", ), - rx.el.input( - placeholder="Search", - read_only=True, - class_name="bg-transparent border-none outline-none focus:outline-none pl-4 cursor-pointer hidden md:block font-medium", + rx.el.span( + "⌘ K", + class_name="px-2 bg-slate-4 rounded-sm ml-0.5 hidden md:block h-5 font-medium", ), - class_name="py-[6px] md:px-[12px] w-8 md:w-full hover:bg-slate-3 cursor-pointer flex items-center justify-center h-8 border border-slate-5 rounded-[10px] bg-slate-1 transition-bg relative", + variant="outline", + size="sm", + class_name="md:w-full text-secondary-11", ) diff --git a/pcweb/components/hosting_banner.py b/pcweb/components/hosting_banner.py index 370f88b648..3d7682561e 100644 --- a/pcweb/components/hosting_banner.py +++ b/pcweb/components/hosting_banner.py @@ -8,7 +8,7 @@ def glow() -> rx.Component: class HostingBannerState(rx.State): - show_banner: bool = True + show_banner: bool = False def hide_banner(self): self.show_banner = False diff --git a/pcweb/components/icons/icons.py b/pcweb/components/icons/icons.py index a33d215fa2..e99c1bb90e 100644 --- a/pcweb/components/icons/icons.py +++ b/pcweb/components/icons/icons.py @@ -191,6 +191,8 @@ """ +feather_unstyled = """""" + cloud = """ @@ -265,8 +267,8 @@ """ -discord_navbar = """ - +discord_navbar = """ + """ @@ -367,6 +369,20 @@ """ +db = """ + + +""" + +api = """ + +""" + +doc = """ + + +""" + package = """ @@ -449,6 +465,27 @@ cancel_circle = """ """ +arrow_fill_down = """ + + +""" + +alert = """ + + +""" + +browser = """ + + + +""" + +checkmark = """ + + +""" + ICONS = { # Socials "github": github, @@ -512,6 +549,14 @@ "play_video": play_video, "zap": zap, "cancel-circle": cancel_circle, + "arrow-fill-down": arrow_fill_down, + "alert": alert, + "browser": browser, + "checkmark": checkmark, + "feather_unstyled": feather_unstyled, + "db": db, + "api": api, + "doc": doc, } LiteralIcon = Literal[ @@ -575,6 +620,12 @@ "play_video", "zap", "cancel-circle", + "arrow-fill-down", + "alert", + "browser", + "checkmark", + "feather_unstyled", + "db", ] diff --git a/pcweb/components/link_button.py b/pcweb/components/link_button.py index cbf5764b4a..16d1dac633 100644 --- a/pcweb/components/link_button.py +++ b/pcweb/components/link_button.py @@ -41,9 +41,9 @@ def get_variant_bg_cn(variant: str) -> str: "variant": { "primary": f"{get_variant_bg_cn('violet')} text-[#FCFCFD] font-semibold", "secondary": "bg-slate-4 hover:bg-slate-5 text-slate-11 font-semibold", - "transparent": "bg-transparent hover:bg-slate-3 text-slate-9 font-medium", + "transparent": "bg-transparent hover:bg-secondary-3 text-slate-9 font-medium", "destructive": f"{get_variant_bg_cn('red')} text-[#FCFCFD] font-semibold", - "outline": "bg-slate-1 hover:bg-slate-3 text-slate-9 font-medium border border-slate-5", + "outline": "bg-slate-1 hover:bg-slate-3 text-secondary-11 font-medium border border-slate-5", }, } diff --git a/pcweb/components/marquee.py b/pcweb/components/marquee.py new file mode 100644 index 0000000000..b8283bb73b --- /dev/null +++ b/pcweb/components/marquee.py @@ -0,0 +1,31 @@ +from typing import Literal, Union + +import reflex as rx + + +class Marquee(rx.NoSSRComponent): + """Marquee component.""" + + library = "react-fast-marquee@1.6.5" + tag = "Marquee" + is_default = True + + # Behavior props + auto_fill: rx.Var[bool] = rx.Var.create(True) + play: rx.Var[bool] = rx.Var.create(True) + pause_on_hover: rx.Var[bool] = rx.Var.create(True) + pause_on_click: rx.Var[bool] = rx.Var.create(False) + direction: rx.Var[Literal["left", "right", "up", "down"]] = rx.Var.create("left") + + # Animation props + speed: rx.Var[int] = rx.Var.create(35) + delay: rx.Var[int] = rx.Var.create(0) + loop: rx.Var[int] = rx.Var.create(0) + + # Gradient props + gradient: rx.Var[bool] = rx.Var.create(True) + gradient_color: rx.Var[str] = rx.Var.create("var(--c-slate-1)") + gradient_width: rx.Var[Union[int, str]] = rx.Var.create(250) + + +marquee = Marquee.create diff --git a/pcweb/components/numbers_pattern.py b/pcweb/components/numbers_pattern.py new file mode 100644 index 0000000000..cd1756253d --- /dev/null +++ b/pcweb/components/numbers_pattern.py @@ -0,0 +1,100 @@ +import reflex as rx +import reflex_ui as ui + + +def ellipses(side: str = "left"): + direction = "right" if side == "right" else "left" + common_class = "absolute bg-violet-11 dark:bg-[#534a87] blur-[10px]" + return rx.el.div( + # Ellipse 1 + rx.el.div( + class_name=f"w-[32px] h-[88px] {direction}-[0.5rem] {common_class} animate-ellipse-1" + ), + # Ellipse 2 + rx.el.div( + class_name=f"w-[32px] h-[178px] {direction}-[9.5rem] {common_class} animate-ellipse-2" + ), + # Ellipse 3 + rx.el.div( + class_name=f"w-[16px] h-[42px] {direction}-[4.19rem] {common_class} animate-ellipse-3" + ), + # Ellipse 4 + rx.el.div( + class_name=f"w-[32px] h-[48px] {direction}-[0.44rem] {common_class} animate-ellipse-4" + ), + ) + + +def ellipses_reversed(side: str = "left"): + direction = "right" if side == "right" else "left" + common_class = f"absolute bg-violet-11 dark:bg-[#534a87] blur-[10px] [animation-direction:reverse] {direction}" + return rx.el.div( + # Ellipse 1 + rx.el.div( + class_name=f"w-[32px] h-[88px] {direction}-[0.5rem] {common_class} animate-ellipse-1" + ), + # Ellipse 2 + rx.el.div( + class_name=f"w-[32px] h-[178px] {direction}-[9.5rem] {common_class} animate-ellipse-2" + ), + # Ellipse 3 + rx.el.div( + class_name=f"w-[16px] h-[42px] {direction}-[4.19rem] {common_class} animate-ellipse-3" + ), + # Ellipse 4 + rx.el.div( + class_name=f"w-[32px] h-[48px] {direction}-[0.44rem] {common_class} animate-ellipse-4" + ), + ) + + +def numbers_pattern( + side: str = "left", reversed: bool = False, class_name: str = "" +) -> rx.Component: + """Numbers pattern with static background and masked animated ellipses.""" + position_class = "left-0" if side == "left" else "right-0" + + light_dark_path = rx.color_mode_cond("light", "dark") + + image_sources = { + ("left", False): f"/landing/patterns/{light_dark_path}/numbers-img.webp", + ( + "left", + True, + ): f"/landing/patterns/{light_dark_path}/numbers-reversed-img.webp", + ("right", False): f"/landing/patterns/{light_dark_path}/numbers-right-img.webp", + ( + "right", + True, + ): f"/landing/patterns/{light_dark_path}/numbers-right-reversed-img.webp", + } + src = image_sources.get( + (side, reversed), f"/landing/patterns/{light_dark_path}/numbers-img.webp" + ) + + mask_style = { + "mask_image": f"url({src})", + "mask_size": "100% 100%", + "mask_repeat": "no-repeat", + "webkit_mask_image": f"url({src})", + "webkit_mask_size": "100% 100%", + "webkit_mask_repeat": "no-repeat", + } + + return rx.el.div( + rx.el.div( + # Static background pattern (always visible) + rx.image(src=src, class_name="pointer-events-none"), + # Masked layer with ellipses + rx.el.div( + ellipses(side=side) if not reversed else ellipses_reversed(side=side), + class_name="absolute inset-0 w-full h-full", + style=mask_style, + ), + class_name="relative size-full", + ), + class_name=ui.cn( + f"absolute {position_class} pointer-events-none overflow-hidden z-[-1] lg:w-[234px] w-[180px] h-auto", + class_name, + ), + ) diff --git a/pcweb/components/progressive_blur.py b/pcweb/components/progressive_blur.py new file mode 100644 index 0000000000..a85f66400a --- /dev/null +++ b/pcweb/components/progressive_blur.py @@ -0,0 +1,19 @@ +from typing import Literal + +import reflex as rx + + +class ProgressiveBlur(rx.Component): + """ProgressiveBlur component.""" + + library = "progressive-blur" + tag = "LinearBlur" + + steps: rx.Var[int] = rx.Var.create(8) + strength: rx.Var[int] = rx.Var.create(64) + falloff_percentage: rx.Var[int] = rx.Var.create(100) + tint: rx.Var[str] = rx.Var.create("") + side: rx.Var[Literal["top", "bottom", "left", "right"]] = rx.Var.create("bottom") + + +progressive_blur = ProgressiveBlur.create diff --git a/pcweb/constants.py b/pcweb/constants.py index 27ea36f392..78705c0d79 100644 --- a/pcweb/constants.py +++ b/pcweb/constants.py @@ -71,9 +71,9 @@ RX_BUILD_BACKEND = os.getenv("RX_BUILD_BACKEND", "https://build-backend.reflex.dev/") # Stats -GITHUB_STARS = 25000 +GITHUB_STARS = 27000 DISCORD_USERS = 7500 -CONTRIBUTORS = 180 +CONTRIBUTORS = 200 MAX_FILE_SIZE_MB = 5 MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024 diff --git a/pcweb/github.py b/pcweb/github.py deleted file mode 100644 index fb26088f3f..0000000000 --- a/pcweb/github.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Github stars count for the reflex repository.""" - -import reflex as rx - -REFLEX_STAR_COUNT = 25000 - - -class GithubStarState(rx.State): - @rx.var(cache=True, interval=60) - def stars(self) -> str: - return f"{REFLEX_STAR_COUNT}" - - @rx.var(cache=True, interval=60) - def stars_short(self) -> str: - return f"{round(REFLEX_STAR_COUNT / 1000)}K" diff --git a/pcweb/pages/__init__.py b/pcweb/pages/__init__.py index 47b0f4bb4d..23d9876fbc 100644 --- a/pcweb/pages/__init__.py +++ b/pcweb/pages/__init__.py @@ -18,7 +18,7 @@ from .page404 import page404 as page404 from .pricing.pricing import pricing as pricing from .sales import sales as sales -from .security.security import security as security +from .security.security import security_page as security_page from .to_be_booked import to_be_booked as to_be_booked from .use_cases.use_cases import use_cases_page as use_cases_page diff --git a/pcweb/pages/framework/views/frontend_features.py b/pcweb/pages/framework/views/frontend_features.py index 9972663af6..37ca617385 100644 --- a/pcweb/pages/framework/views/frontend_features.py +++ b/pcweb/pages/framework/views/frontend_features.py @@ -104,7 +104,7 @@ def frontend_features() -> rx.Component: class_name="flex flex-col lg:border-r border-slate-3 lg:p-[5rem_6.5rem_5rem_2.5rem] lg:text-nowrap text-center lg:text-start pb-12 lg:pb-0 mt-12 lg:mt-0", ), rx.box( - stat(stat="300K+", text="Apps built with Reflex"), + stat(stat="1 Million+", text="Apps built with Reflex"), class_name="lg:px-10 lg:py-[5.5rem] py-12 w-auto border-b border-slate-3 lg:border-b-0", ), class_name="flex flex-col-reverse lg:flex-row lg:border-t border-slate-3 max-w-[64.19rem] justify-center lg:border-x w-full", diff --git a/pcweb/pages/framework/views/get_started.py b/pcweb/pages/framework/views/get_started.py index 9f0a63d0f6..2ffe0b7099 100644 --- a/pcweb/pages/framework/views/get_started.py +++ b/pcweb/pages/framework/views/get_started.py @@ -1,6 +1,6 @@ import reflex as rx +import reflex_ui as ui -from pcweb.components.button import button from pcweb.components.hint import hint from pcweb.components.icons.icons import get_icon from pcweb.pages.docs import getting_started @@ -27,7 +27,7 @@ def code_block() -> rx.Component: rx.el.p("$ pip install reflex"), rx.el.p("$ reflex init"), rx.el.p("$ reflex run"), - class_name="font-['JetBrains_Mono'] !font-bold text-[0.8125rem] text-slate-12 leading-6 tracking-[-0.01219rem]", + class_name="font-['JetBrains_Mono'] !font-medium text-[0.8125rem] text-slate-12 leading-6 tracking-[-0.01219rem]", ), hint( text="Copy", @@ -47,24 +47,24 @@ def code_block() -> rx.Component: class_name="flex flex-row justify-between", ), rx.box( - rx.link( + rx.el.a( "Need help? Learn how to use Reflex.", - href=getting_started.introduction.path, - underline="none", + to=getting_started.introduction.path, class_name="font-small text-slate-9 hover:!text-slate-11 transition-color", ), - rx.link( - button( + ui.link( + render_=ui.button( "Docs", - class_name="!h-10 !py-2 !px-[1.125rem] !rounded-[0.875rem] !w-full lg:!w-auto", + size="md", + class_name="font-semibold", ), - href=getting_started.introduction.path, - class_name="w-full lg:!w-auto", + to=getting_started.introduction.path, + target="_blank", ), - class_name="flex flex-col lg:flex-row justify-between items-center gap-4 lg:gap-2", + class_name="flex flex-row justify-between items-center gap-4 lg:gap-2", ), box_shadow="0px 24px 12px 0px light-dark(rgba(28, 32, 36, 0.02), rgba(0, 0, 0, 0.00)), 0px 8px 8px 0px light-dark(rgba(28, 32, 36, 0.02), rgba(0, 0, 0, 0.00)), 0px 2px 6px 0px light-dark(rgba(28, 32, 36, 0.02), rgba(0, 0, 0, 0.00))", - class_name="relative flex flex-col gap-4 border-slate-3 bg-[rgba(249,249,251,0.48)] dark:bg-[rgba(26,27,29,0.48)] backdrop-filter backdrop-blur-[6px] p-4 border rounded-2xl max-w-[24.5rem] self-center w-full", + class_name="relative flex flex-col gap-4 border-slate-3 bg-[rgba(249,249,251,0.48)] dark:bg-[rgba(26,27,29,0.48)] backdrop-filter backdrop-blur-[6px] p-6 border rounded-2xl max-w-[24.5rem] self-center w-full", ) diff --git a/pcweb/pages/framework/views/stats.py b/pcweb/pages/framework/views/stats.py index 3f2852c413..f63cb2d07a 100644 --- a/pcweb/pages/framework/views/stats.py +++ b/pcweb/pages/framework/views/stats.py @@ -1,8 +1,7 @@ import reflex as rx from pcweb.components.icons import get_icon -from pcweb.constants import CONTRIBUTORS, DISCORD_USERS -from pcweb.github import GithubStarState +from pcweb.constants import CONTRIBUTORS, DISCORD_USERS, GITHUB_STARS def stat_card(stat: str, text: str, icon: str, class_name: str = "") -> rx.Component: @@ -22,7 +21,7 @@ def stat_card(stat: str, text: str, icon: str, class_name: str = "") -> rx.Compo def stats_grid() -> rx.Component: return rx.box( stat_card( - stat=f"{GithubStarState.stars:,}", + stat=f"{GITHUB_STARS:,}", text="Stars", icon="star", class_name="lg:!border-l !border-slate-3", diff --git a/pcweb/pages/landing/landing.py b/pcweb/pages/landing/landing.py index c2aa4b473b..c9accd5498 100644 --- a/pcweb/pages/landing/landing.py +++ b/pcweb/pages/landing/landing.py @@ -1,27 +1,43 @@ import reflex as rx from pcweb.meta.meta import meta_tags -from pcweb.pages.landing.views.ai_section import ai_section +from pcweb.pages.landing.views.ai_bento import ai_bento +from pcweb.pages.landing.views.app_build import app_build from pcweb.pages.landing.views.companies import companies -from pcweb.pages.landing.views.framework_section import framework_section +from pcweb.pages.landing.views.connect_section import connect_section +from pcweb.pages.landing.views.deploy_section import deploy_section +from pcweb.pages.landing.views.enterprise_social import enterprise_social +from pcweb.pages.landing.views.final_cta import final_cta from pcweb.pages.landing.views.hero import hero -from pcweb.pages.landing.views.hosting_section import hosting_section -from pcweb.pages.landing.views.outcomes_section import outcomes_section +from pcweb.pages.landing.views.integrations import integrations +from pcweb.pages.landing.views.os_bento import os_bento +from pcweb.pages.landing.views.os_stats import os_stats from pcweb.pages.landing.views.products import products -from pcweb.pages.landing.views.security import security +from pcweb.pages.landing.views.social_marquee import social_marquee +from pcweb.pages.landing.views.social_stats import social_stats +from pcweb.pages.landing.views.use_cases import use_cases_section +from pcweb.pages.landing.views.video import video from pcweb.templates.mainpage import mainpage @mainpage(path="/", title="Reflex · Web apps in Pure Python", meta=meta_tags) def landing() -> rx.Component: - return rx.box( + return rx.el.div( hero(), + app_build(), + social_stats(), products(), + integrations(), + video(), companies(), - ai_section(), - framework_section(), - hosting_section(), - outcomes_section(), - security(), + ai_bento(), + connect_section(), + enterprise_social(), + social_marquee(), + use_cases_section(), + os_bento(), + os_stats(), + deploy_section(), + final_cta(), class_name="flex flex-col size-full justify-center items-center", ) diff --git a/pcweb/pages/landing/views/ai_bento.py b/pcweb/pages/landing/views/ai_bento.py new file mode 100644 index 0000000000..35247a7eed --- /dev/null +++ b/pcweb/pages/landing/views/ai_bento.py @@ -0,0 +1,114 @@ +import reflex as rx +import reflex_ui as ui + +from pcweb.components.numbers_pattern import numbers_pattern + + +def header() -> rx.Component: + return rx.el.div( + rx.el.div( + ui.icon("Layers01Icon", class_name="shrink-0"), + rx.el.span("Features", class_name="text-sm font-semibold"), + class_name="flex flex-row gap-2 items-center text-primary-9", + ), + rx.el.h2( + "Generate your app with AI", + class_name="max-w-full w-full lg:text-3xl text-2xl text-center text-slate-12 font-semibold text-balance word-wrap break-words md:whitespace-pre", + ), + rx.el.p( + "Build production-ready web apps in seconds with AI-powered code generation.", + class_name="text-slate-9 text-sm font-medium text-center text-balance word-wrap break-words md:whitespace-pre", + ), + class_name="flex flex-col gap-4 items-center mx-auto w-full relative overflow-hidden", + ) + + +def frontend_card( + title: str, + description: str, + image: str, + height: str = "auto", + cols: str = "1", +) -> rx.Component: + return rx.box( + rx.el.div( + rx.box( + rx.el.h2( + title, class_name="text-2xl font-semibold text-slate-12 z-[1]" + ), + rx.el.p( + description, + class_name="text-base font-medium text-slate-9 z-[1] text-pretty", + ), + class_name="flex flex-col gap-2 px-10 pt-10", + ), + rx.image( + src=f"/landing/ai_bento/light/{image}", + class_name="dark:hidden w-full shrink-0", + height=height, + loading="lazy", + alt=title + " image", + ), + rx.image( + src=f"/landing/ai_bento/dark/{image}", + class_name="dark:block hidden w-full shrink-0", + height=height, + loading="lazy", + alt=title + " image", + ), + class_name="flex flex-col gap-8", + ), + class_name=f"lg:col-span-{cols} col-span-1 h-96 rounded-[1.125rem] bg-white-1 border border-slate-4 overflow-hidden relative lg:shadow-small max-h-full pointer-events-none", + ) + + +def bento_cards() -> rx.Component: + return rx.el.div( + frontend_card( + title="Work With UI and Backend in 100% Python", + description=( + "Generate and refine UI and backend in 100% Python.\n" + "No JavaScript or frontend frameworks required" + ), + image="bento1.webp", + cols="2", + ), + frontend_card( + title="Integrate With Tools", + description="Over 15 integrations are available.", + image="bento2.webp", + ), + frontend_card( + title="Research Web", + description="Reflex AI finds the best way to implement new features and fix your code.", + image="bento3.webp", + ), + frontend_card( + title="Build with Visual References", + description="Pass it screenshots, and even video, Figma design", + image="bento4.webp", + cols="2", + ), + frontend_card( + title="Test Any App", + description=( + "Reflex simulates real users interaction and debugs the app for you." + ), + image="bento5.webp", + cols="2", + ), + class_name="grid grid-cols-1 lg:grid-cols-4 gap-4 grid-rows-2 max-w-[84.5rem]", + ) + + +def ai_bento() -> rx.Component: + return rx.el.section( + rx.el.div( + numbers_pattern(side="left", reversed=True, class_name="left-0 top-0"), + numbers_pattern(side="right", reversed=True, class_name="right-0 top-0"), + header(), + class_name="flex flex-col items-center mx-auto w-full max-w-[64.19rem] lg:border-x border-slate-3 relative overflow-hidden py-20", + ), + bento_cards(), + class_name="flex flex-col items-center mx-auto w-full", + ) diff --git a/pcweb/pages/landing/views/app_build.py b/pcweb/pages/landing/views/app_build.py new file mode 100644 index 0000000000..4917914c6c --- /dev/null +++ b/pcweb/pages/landing/views/app_build.py @@ -0,0 +1,364 @@ +import reflex as rx +import reflex_ui as ui + +from pcweb.components.icons.icons import get_icon +from pcweb.components.progressive_blur import progressive_blur + +# Animation delays (in ms) +DELAY_USER_PROFILE = 250 +DURATION_USER_PROFILE = 900 +DELAY_OKTA = 1050 +DURATION_OKTA = 300 +DELAY_DIVIDER = 1200 +DURATION_DIVIDER = 200 +DELAY_GRAPH_OVERVIEW = 1350 +DURATION_GRAPH_OVERVIEW = 700 +DELAY_GRAPH_Y_AXIS = 1550 +DURATION_GRAPH_Y_AXIS = 700 +DELAY_DATABRICKS = 2100 +DURATION_DATABRICKS = 300 +DELAY_GRAPH_BARS = 1950 +DURATION_GRAPH_BARS = 300 +DELAY_METRICS_HEADER = 2250 +DURATION_METRICS_HEADER = 800 +DELAY_METRICS_BADGE = 2500 +DURATION_METRICS_BADGE = 700 +DELAY_METRICS_TABS = 2350 +DURATION_METRICS_TABS = 700 +DELAY_METRICS_CONTENT = 2450 +DURATION_METRICS_CONTENT = 600 +DELAY_SLACK = 3050 +DURATION_SLACK = 300 + + +def integration_card(icon: str, class_name: str = ""): + return rx.el.div( + rx.image( + src=f"/landing/integrations/light/{icon}.svg", + class_name="size-7 pointer-events-none shrink-0", + ), + class_name=ui.cn( + "z-100 flex justify-center items-center size-12 bg-white-1/88 dark:bg-black/20 backdrop-blur-[6px] border border-slate-6 dark:border-[#1C2024] shadow-large", + class_name, + ), + ) + + +def integration_card_light_dark(icon: str, class_name: str = ""): + return rx.el.div( + rx.image( + src=f"/landing/integrations/light/{icon}.svg", + class_name="size-7 pointer-events-none shrink-0 dark:hidden", + ), + rx.image( + src=f"/landing/integrations/dark/{icon}.svg", + class_name="size-7 pointer-events-none shrink-0 hidden dark:block", + ), + class_name=ui.cn( + "z-100 flex justify-center items-center size-12 bg-white-1/88 dark:bg-black/20 backdrop-blur-[6px] border border-slate-6 dark:border-[#1C2024] shadow-large", + class_name, + ), + ) + + +def okta_card(): + return integration_card_light_dark( + "okta", + class_name=f"rounded-t-[14px] rounded-bl-[14px] rounded-br-[4px] absolute top-2 left-2 animate-scale-rotate-in animate-duration-{DURATION_OKTA} animate-ease-out animate-delay-{DELAY_OKTA}", + ) + + +def databricks_card(): + return integration_card( + "databricks", + class_name=f"rounded-t-[14px] rounded-br-[14px] rounded-bl-[4px] absolute top-[5.25rem] right-[11rem] animate-scale-rotate-in animate-duration-{DURATION_DATABRICKS} animate-ease-out animate-delay-{DELAY_DATABRICKS}", + ) + + +def user_profile(): + return rx.el.div( + rx.el.div( + rx.image( + src="/landing/app_build/user.webp", + class_name="object-cover pointer-events-none", + ), + class_name="size-10 bg-violet-5 dark:bg-[#E1D9FF] rounded-full border border-violet-7 overflow-hidden", + ), + rx.el.span( + "Amelia Wong", + class_name="text-slate-12 text-sm font-semibold", + ), + get_icon("arrow-fill-down", class_name="size-4 text-slate-9 -ml-2"), + class_name=f"flex flex-row items-center gap-4 animate-slide-in-left animate-duration-{DURATION_USER_PROFILE} animate-ease-out animate-delay-{DELAY_USER_PROFILE}", + ) + + +def slack_alert(): + return rx.el.div( + rx.el.div( + rx.image( + src="/landing/integrations/light/slack.svg", + class_name="size-full pointer-events-none shrink-0 object-cover", + ), + class_name="size-9 bg-white-1 dark:bg-white rounded-[8px] border-[0.5px] border-slate-6 dark:border-[#1C2024] overflow-hidden flex justify-center items-center p-1 shadow-small shrink-0", + ), + rx.el.div( + rx.el.p( + "New message from Reflex", + class_name="text-slate-12 text-sm font-semibold", + ), + rx.el.p( + "Metrics reached critical level of 93%", + rx.el.br(), + "Please, check it.", + class_name="text-slate-11 text-xs font-medium word-wrap", + ), + class_name="flex flex-col", + ), + rx.el.span( + "now", + class_name="absolute top-3 right-2 text-xs text-slate-7 dark:text-slate-9 font-medium", + ), + style={ + "box-shadow": "0 25px 7px 0 rgba(0, 0, 0, 0.00), 0 16px 6px 0 rgba(0, 0, 0, 0.01), 0 9px 5px 0 rgba(0, 0, 0, 0.03), 0 4px 4px 0 rgba(0, 0, 0, 0.04), 0 1px 2px 0 rgba(0, 0, 0, 0.05)" + }, + class_name=f"flex flex-row items-center gap-3.5 border border-slate-6 dark:border-[#1C2024] h-[4.5rem] w-[20.375rem] rounded-[0.875rem] px-3 absolute top-2 right-2 z-[10] bg-white/88 dark:bg-black/20 backdrop-blur-[6px] animate-scale-in-top-right animate-duration-{DURATION_SLACK} animate-ease-out animate-delay-{DELAY_SLACK}", + ) + + +def metrics_tabs(): + return rx.el.div( + rx.el.div( + rx.el.span( + "All", + class_name="text-slate-10 text-xs font-medium size-full text-center bg-slate-2 overflow-hidden flex items-center justify-center", + ), + rx.el.span( + "Critical", + class_name="text-slate-10 text-xs font-medium size-full text-center overflow-hidden flex items-center justify-center", + ), + rx.el.span( + "Normal", + class_name="text-slate-10 text-xs font-medium size-full text-center overflow-hidden flex items-center justify-center", + ), + class_name="flex flex-row items-center w-[246px] h-[32px] rounded-[8px] bg-white-1 dark:bg-[#151618] border border-slate-4 dark:border-[#1C2024] divide-x divide-slate-4 dark:divide-[#1C2024] overflow-hidden", + ), + class_name=f"w-full h-[32px] animate-slide-in-right animate-duration-{DURATION_METRICS_TABS} animate-ease-out animate-delay-{DELAY_METRICS_TABS} pl-6", + ) + + +def metrics_header(): + return rx.el.div( + rx.el.div( + rx.html( + """ + +
+ + +
+""", + class_name="block dark:hidden", + ), + rx.html( + """ + +
+ + +
+""", + class_name="hidden dark:block", + ), + # Text + rx.el.div( + "93%", + class_name="absolute size-16 left-0 top-0 flex items-center justify-center text-center text-red-10 text-base font-[565] leading-[21px] font-mono z-10", + ), + class_name="relative size-16 flex-none", + ), + rx.el.div( + rx.el.span( + "Metrics", + class_name="text-slate-12 text-lg font-semibold", + ), + rx.el.div( + get_icon("alert"), + "Critical Level", + class_name=f"text-red-10 dark:text-white bg-red-2 dark:bg-[#641723] border-red-6 dark:border-[#641723] border flex flex-row items-center gap-1.5 text-xs font-medium rounded-md px-1.5 h-[24px] animate-delay-{DELAY_METRICS_BADGE} animate-duration-{DURATION_METRICS_BADGE} animate-slide-in-right animate-ease-out", + ), + class_name="flex flex-col gap-2", + ), + class_name=f"flex flex-row gap-6 w-full justify-start relative animate-slide-in-right animate-duration-{DURATION_METRICS_HEADER} animate-ease-out animate-delay-{DELAY_METRICS_HEADER} pl-6", + ) + + +def metrics_content(): + def metrics_row(name: str, value: str, time: str): + return rx.el.div( + rx.el.span(name, class_name="text-slate-9 text-sm font-medium w-[72px]"), + rx.el.span(value, class_name="text-slate-9 text-sm font-medium w-[46px]"), + rx.el.span( + time, class_name="text-slate-9 text-sm font-medium text-end ml-auto" + ), + class_name="flex flex-row items-center h-[48px] px-6 py-4 gap-4", + ) + + return rx.el.div( + metrics_row("Critical", "93%", "Now"), + metrics_row("Normal", "32%", "12:42"), + metrics_row("Critical", "89%", "09:41"), + metrics_row("Normal", "34%", "08:14"), + metrics_row("Normal", "12%", "05:36"), + class_name=f"flex flex-col w-[294px] divide-y divide-slate-4 border-t border-slate-3 animate-slide-in-up animate-delay-{DELAY_METRICS_CONTENT} animate-ease-out animate-duration-{DURATION_METRICS_CONTENT}", + ) + + +def metrics_card(): + return rx.el.div( + rx.el.div( + progressive_blur( + steps=16, + strength=6, + side="right", + fall_of_percentage=100, + class_name="w-[1.45rem] inset-y-0 absolute right-0 z-50 pointer-events-none", + ), + metrics_header(), + metrics_tabs(), + class_name="flex flex-col gap-6 relative items-center w-full justify-start", + ), + metrics_content(), + class_name="flex flex-col gap-6 justify-start items-center min-w-[18.375rem] pt-6 overflow-hidden", + ) + + +def graph_y_axis(): + return rx.el.div( + *[ + rx.el.div( + rx.el.span( + f"{i}", + class_name="text-slate-9 text-xs font-medium", + ), + rx.el.div( + class_name="h-[0.5px] bg-slate-3 w-[524px] absolute left-10 top-1/2 -translate-y-1/2", + ), + class_name="relative", + ) + for i in [64, 48, 32, 24, 16, 8, 4] + ], + class_name=f"flex flex-col items-end gap-7 w-4 pt-9 animate-slide-in-up animate-duration-{DURATION_GRAPH_Y_AXIS} animate-ease-out animate-delay-{DELAY_GRAPH_Y_AXIS}", + ) + + +def stacked_bar_chart(primary_height: int = 8, secondary_height: int = 16): + return rx.el.div( + rx.el.div( + height=f"{primary_height}rem", + class_name="w-4 rounded-t-sm bg-violet-8 absolute bottom-0 left-0 z-20", + ), + rx.el.div( + height=f"{secondary_height}rem", + class_name="w-4 rounded-t-sm bg-violet-3 dark:bg-[#151618] absolute bottom-0 left-0 z-10", + ), + class_name="relative h-full w-4", + ) + + +def graph_chart(): + return rx.el.div( + graph_y_axis(), + rx.el.div( + stacked_bar_chart(primary_height=8.4375, secondary_height=16), + stacked_bar_chart(primary_height=13.25, secondary_height=16), + stacked_bar_chart(primary_height=14.75, secondary_height=16), + stacked_bar_chart(primary_height=11.25, secondary_height=15.0625), + stacked_bar_chart(primary_height=12.9375, secondary_height=16), + stacked_bar_chart(primary_height=14.375, secondary_height=16), + stacked_bar_chart(primary_height=8.625, secondary_height=12.3125), + stacked_bar_chart(primary_height=10.75, secondary_height=13.3125), + stacked_bar_chart(primary_height=7.3125, secondary_height=12.3125), + stacked_bar_chart(primary_height=13.25, secondary_height=16), + stacked_bar_chart(primary_height=9.875, secondary_height=15.0625), + stacked_bar_chart(primary_height=6.75, secondary_height=16), + class_name=f"flex flex-row justify-between w-full animate-slide-in-up animate-duration-{DURATION_GRAPH_BARS} animate-ease-out animate-delay-{DELAY_GRAPH_BARS}", + ), + class_name="flex flex-row gap-6 w-full px-8 pt-8 border border-slate-4 dark:border-[#1C2024] rounded-t-lg overflow-hidden h-[408px]", + ) + + +def graph_overview(): + return rx.el.div( + databricks_card(), + rx.el.div( + rx.el.div( + rx.el.span( + "Overview", + class_name="text-slate-12 text-2xl font-semibold", + ), + rx.el.div( + rx.el.div( + rx.el.span(class_name="size-4 rounded-sm bg-violet-8"), + "Peak Performance", + class_name="flex flex-row items-center gap-2 text-xs font-medium text-slate-10", + ), + rx.el.div( + rx.el.span( + class_name="size-4 rounded-sm bg-violet-4 dark:bg-[#151618]" + ), + "Capacity Per Day", + class_name="flex flex-row items-center gap-2 text-xs font-medium text-slate-10", + ), + class_name="flex flex-row items-center gap-4", + ), + class_name="flex flex-col gap-6", + ), + rx.el.div( + "24 June ", + rx.el.span(" - ", class_name="text-slate-8 ml-0.5"), + " Today", + class_name="text-slate-10 dark:text-slate-9 text-sm font-medium rounded-lg px-3.5 h-8 border border-slate-4 dark:border-[#1C2024] bg-white-1 flex items-center", + ), + class_name="flex flex-row justify-between items-baseline w-full", + ), + graph_chart(), + class_name=f"flex flex-col gap-6 size-full border-r border-slate-4 dark:border-[#1C2024] px-6 pt-6 animate-slide-in-up animate-duration-{DURATION_GRAPH_OVERVIEW} animate-ease-out animate-delay-{DELAY_GRAPH_OVERVIEW} h-[408px] relative", + ) + + +def app_build(): + return rx.el.section( + slack_alert(), + okta_card(), + rx.el.div( + rx.el.div( + rx.el.div( + rx.el.div( + class_name="w-[0.95rem] inset-y-0 absolute left-0 z-50 pointer-events-none backdrop-blur-[1px]", + ), + user_profile(), + class_name="h-[4.5rem] shrink-0 w-full", + ), + class_name="w-full h-[4.5rem] shrink-0 relative p-4 overflow-hidden rounded-t-[32px]", + ), + rx.el.div( + height="0.5px", + class_name=f"w-full bg-slate-4 dark:bg-[#1C2024] animate-fade animate-duration-{DURATION_DIVIDER} animate-ease-out animate-delay-{DELAY_DIVIDER} shrink-0", + ), + rx.el.div( + graph_overview(), + metrics_card(), + class_name="flex flex-row size-full", + ), + progressive_blur( + steps=16, + strength=6, + side="bottom", + fall_of_percentage=100, + class_name="h-[9rem] inset-x-0 absolute bottom-0 z-50 pointer-events-none -mb-1", + ), + class_name="border-t border-x border-slate-4 dark:border-[#1C2024] rounded-t-[32px] bg-white-1 relative size-full shadow-[0px_2px_16px_0px_rgba(28,32,36,0.04)] flex flex-col", + ), + class_name="flex flex-col justify-center items-center mx-auto w-full max-w-[64.19rem] lg:border-x border-slate-3 relative overflow-hidden border-t px-6 pt-6 h-[504px] max-lg:hidden", + ) diff --git a/pcweb/pages/landing/views/companies.py b/pcweb/pages/landing/views/companies.py index 753e0d753d..b4053a2f44 100644 --- a/pcweb/pages/landing/views/companies.py +++ b/pcweb/pages/landing/views/companies.py @@ -1,4 +1,5 @@ import reflex as rx +import reflex_ui as ui from reflex.experimental import ClientStateVar from pcweb.components.icons.icons import get_icon @@ -78,7 +79,7 @@ def stat(stat: str, text: str) -> rx.Component: return rx.el.section( - get_icon("feather"), + get_icon("feather_unstyled", class_name="text-primary-9"), rx.box( rx.text( stat, @@ -87,7 +88,7 @@ def stat(stat: str, text: str) -> rx.Component: rx.text(text, class_name="text-sm text-slate-9 font-medium"), class_name="flex flex-col justify-center items-center text-center text-nowrap", ), - get_icon("feather", class_name="scale-x-[-1]"), + get_icon("feather_unstyled", class_name="scale-x-[-1] text-primary-9"), class_name="flex flex-row items-center gap-4 justify-center", ) @@ -97,24 +98,24 @@ def quote_box(company: str) -> rx.Component: return rx.fragment( rx.text( f"“{case_study['quote']}”", - class_name="text-xs text-slate-12 italic font-medium animate-fade animate-duration-[750ms] animate-fill-both", + class_name="text-xs text-slate-12 font-medium animate-fade animate-duration-[505ms] animate-ease-out animate-fill-both", ), rx.box( - rx.image( - src=case_study["picture"], + ui.gradient_profile( + seed=case_study["person"], class_name="size-6 rounded-full", ), rx.box( rx.text( f"{case_study['person']}", - class_name="text-xs text-slate-9 font-medium", + class_name="text-xs text-slate-10 font-medium", ), rx.text( case_study["role"], class_name="text-xs text-slate-9 font-medium", ), ), - class_name="flex flex-row items-center gap-2 w-full animate-fade animate-duration-[750ms] animate-fill-both", + class_name="flex flex-row items-center gap-2 w-full animate-fade animate-duration-[550ms] animate-ease-out animate-fill-both", ), ) @@ -123,18 +124,18 @@ def case_study_badge() -> rx.Component: return rx.box( rx.text("Case Study", class_name="text-xs text-violet-9 font-semibold"), get_icon( - "arrow_top_right", - class_name="size-3 group-hover:rotate-0 transition-transform rotate-45", + "chevron_right", + class_name="size-3", ), - class_name="absolute bottom-4 right-4 bg-violet-3 border border-violet-6 text-violet-9 group-hover:bg-violet-4 group-hover:border-violet-7 text-xs font-semibold px-2 py-1 rounded-full transition-colors flex flex-row items-center gap-1 scale-[0.85] pointer-events-none", + class_name="absolute bottom-4 right-4 bg-violet-3 text-violet-9 group-hover:bg-violet-4 group-hover:border-violet-7 text-xs font-semibold px-2 py-1 rounded-full transition-colors flex flex-row items-center gap-1 scale-[0.85] pointer-events-none", ) def quote_badge() -> rx.Component: return rx.box( rx.text("Quote", class_name="text-xs text-violet-9 font-semibold"), - get_icon("quote", class_name="size-3"), - class_name="absolute bottom-4 right-4 bg-violet-3 border border-violet-6 text-violet-9 group-hover:bg-violet-4 group-hover:border-violet-7 text-xs font-semibold px-2 py-1 rounded-full transition-colors flex flex-row items-center gap-1 scale-[0.85] pointer-events-none", + # ui.icon("QuoteDownIcon", class_name="size-3"), + class_name="absolute bottom-4 right-4 bg-violet-3 text-violet-9 group-hover:bg-violet-4 group-hover:border-violet-7 text-xs font-semibold px-2 py-1 rounded-full transition-colors flex flex-row items-center gap-1 scale-[0.85] pointer-events-none", ) @@ -157,8 +158,8 @@ def company_card(name: str, id: str) -> rx.Component: companies_case_studies_var.contains(company_cs.value), quote_box(company_cs.value), rx.box( - stat(stat="300K+", text="Apps built with Reflex"), - class_name="animate-fade flex justify-center items-center size-full animate-duration-[750ms] animate-fill-both", + stat(stat="1 Million+", text="Apps built with Reflex"), + class_name="animate-fade flex justify-center items-center size-full animate-duration-[550ms] animate-ease-out animate-fill-both", ), ), class_name="flex flex-col gap-2.5 w-full h-[10.75rem] justify-between bg-slate-1 border-box p-4 overflow-hidden", @@ -228,7 +229,7 @@ def company_card(name: str, id: str) -> rx.Component: ], id=id, class_name=( - "relative w-full after:content[''] after:absolute after:z-[1] after:bg-slate-3 after:left-0 after:top-[-1px] after:w-full after:h-[1px] before:content[''] before:absolute before:z-[1] before:bg-slate-3 before:top-0 before:left-[-1px] before:h-full before:w-[1px] group", + "relative w-full group border-b border-r border-slate-3", rx.cond( name == "STATS", "col-span-2", @@ -246,7 +247,6 @@ def company_card(name: str, id: str) -> rx.Component: def companies() -> rx.Component: return rx.el.section( - rx.box(class_name="h-[3rem] w-full border-b border-slate-3 lg:flex hidden"), rx.box( *[ company_card( @@ -261,5 +261,5 @@ def companies() -> rx.Component: index_companies(), class_name="lg:hidden flex border-t border-slate-3 lg:border-t-0", ), - class_name="flex flex-col justify-center items-center mx-auto w-full max-w-[64.19rem] border-slate-3 relative z-[1] overflow-hidden isolate lg:border-b lg:border-x", + class_name="z-0 flex flex-col justify-center items-center mx-auto w-full max-w-[64.19rem] border-slate-3 relative overflow-hidden isolate lg:border-l", ) diff --git a/pcweb/pages/landing/views/connect_section.py b/pcweb/pages/landing/views/connect_section.py new file mode 100644 index 0000000000..7b3fc6fdbd --- /dev/null +++ b/pcweb/pages/landing/views/connect_section.py @@ -0,0 +1,74 @@ +import reflex as rx +import reflex_ui as ui + +from pcweb.components.icons.icons import get_icon +from pcweb.components.numbers_pattern import numbers_pattern + + +def header() -> rx.Component: + return rx.el.div( + rx.el.div( + ui.icon("DatabaseAddIcon", class_name="shrink-0"), + rx.el.span("Data", class_name="text-sm font-semibold"), + class_name="flex flex-row gap-2 items-center text-primary-9", + ), + rx.el.h2( + "Connect to Any Data Source", + class_name="max-w-full w-full lg:text-3xl text-2xl text-center text-slate-12 font-semibold text-balance word-wrap break-words md:whitespace-pre", + ), + rx.el.p( + "Build powerful data-driven apps with seamless integrations to APIs, databases, Python libraries, and file formats.", + class_name="text-slate-9 text-sm font-medium text-center text-balance word-wrap break-words md:whitespace-pre", + ), + class_name="flex flex-col gap-4 items-center mx-auto w-full relative overflow-hidden", + ) + + +def connect_card(icon: str, title: str, description: str) -> rx.Component: + return rx.el.div( + rx.el.div( + get_icon(icon, class_name="shrink-0 !text-slate-9 size-5"), + rx.el.span(title, class_name="text-sm font-medium text-slate-10"), + class_name="flex flex-row gap-2 items-center", + ), + rx.el.p( + description, + class_name="text-slate-12 text-base font-medium", + ), + class_name="flex flex-col gap-4 p-10 border-r border-slate-3 border-b", + ) + + +def connect_section() -> rx.Component: + return rx.el.section( + rx.el.div( + numbers_pattern(side="left", class_name="left-0 top-0"), + numbers_pattern(side="right", class_name="right-0 top-0"), + header(), + class_name="flex flex-col items-center mx-auto w-full relative overflow-hidden py-20 border-b border-slate-3 lg:border-x", + ), + rx.el.div( + connect_card( + "api", + "API", + "Integrate with any REST or GraphQL API to fetch and sync data in real-time", + ), + connect_card( + "python", + "Python Library/SDK", + "Import any Python package to extend functionality with data tools and more", + ), + connect_card( + "db", + "Database", + "Connect to PostgreSQL, MySQL, MongoDB, or any database to power your app", + ), + connect_card( + "doc", + "File Types", + "Process and display CSV, Excel, PDF, images, and other file formats seamlessly", + ), + class_name="w-full grid grid-cols-1 lg:grid-cols-2 lg:border-l border-slate-3", + ), + class_name="flex flex-col items-center mx-auto w-full max-w-[64.19rem] relative overflow-hidden", + ) diff --git a/pcweb/pages/landing/views/deploy_section.py b/pcweb/pages/landing/views/deploy_section.py new file mode 100644 index 0000000000..531ec2a286 --- /dev/null +++ b/pcweb/pages/landing/views/deploy_section.py @@ -0,0 +1,247 @@ +import reflex as rx +import reflex_ui as ui + +from pcweb.components.icons.icons import get_icon +from pcweb.components.numbers_pattern import numbers_pattern +from pcweb.constants import REFLEX_CLOUD_URL +from pcweb.pages.databricks.databricks import databricks_page +from pcweb.pages.hosting.views.deploy_animation import animated_box +from pcweb.pages.security.security import security_page + + +def header() -> rx.Component: + return rx.el.div( + rx.el.div( + ui.icon("CloudServerIcon", class_name="shrink-0"), + rx.el.span( + "Hosting", + class_name="text-sm font-semibold", + ), + class_name="flex flex-row gap-2 items-center text-primary-9", + ), + rx.el.h2( + "Deploy, manage, and scale.", + class_name="max-w-full w-full lg:text-3xl text-2xl text-center text-slate-12 font-semibold text-balance word-wrap break-words md:whitespace-pre", + ), + rx.el.p( + "A complete infrastructure for your apps.", + class_name="text-slate-9 text-sm font-medium text-center text-balance word-wrap break-words md:whitespace-pre", + ), + class_name="flex flex-col gap-4 items-center mx-auto w-full relative overflow-hidden", + ) + + +def deploy_card() -> rx.Component: + return rx.el.div( + rx.el.div( + rx.html( + """ + + + + + + + + + + +""", + class_name="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 object-cover pointer-events-none z-[0] dark:block hidden", + ), + rx.html( + """ + + + + + + + + + + +""", + class_name="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 object-cover pointer-events-none z-[0] dark:hidden", + ), + rx.image( + f"/landing/deploy/{rx.color_mode_cond('light', 'dark')}/deploy_visual.webp", + class_name="h-[8.5rem] w-auto ml-[6rem] z-[1] block", + ), + class_name="relative flex flex-col max-lg:hidden", + ), + rx.el.div( + rx.el.span( + "Deploy your app with a single command.", + class_name="text-slate-12 lg:text-3xl text-2xl font-semibold", + ), + rx.el.span( + "Performant, secure, and scalable.", + class_name="text-slate-9 lg:text-3xl text-2xl font-semibold", + ), + rx.el.div( + rx.image( + src="/hosting_graphing.svg", + class_name="w-auto h-[1.95rem]", + ), + ui.link( + render_=ui.button( + "Start deploying", + variant="primary", + size="lg", + class_name="font-semibold h-10 w-fit", + ), + to=REFLEX_CLOUD_URL, + target="_blank", + ), + class_name="flex flex-col lg:flex-row lg:gap-8 gap-6 items-center mt-8 max-lg:w-full max-lg:justify-start max-lg:items-start", + ), + class_name="flex flex-col items-end", + ), + class_name="max-w-[71.125rem] w-full rounded-[1.125rem] border border-slate-3 bg-white-1 pt-[5rem] pb-[5.5rem] px-10 h-[20.5625rem] flex flex-row items-center gap-[10.5rem] z-10 shadow-small", + ) + + +def feature_card(icon: str, title: str, description: str) -> rx.Component: + return rx.box( + rx.box( + get_icon(icon, class_name="!text-slate-9 !size-5"), + rx.text(title, class_name="text-slate-12 text-base font-semibold"), + class_name="flex flex-row gap-2 items-center", + ), + rx.text(description, class_name="text-slate-9 font-medium text-sm text-start"), + class_name="flex flex-col gap-2 w-full lg:w-[22.05rem] h-[8rem] px-8 py-5", + ) + + +def badge_card(component: rx.Component, link: str) -> rx.Component: + return rx.el.a( + rx.el.div( + component, + class_name="size-18 rounded-lg border border-slate-5 dark:border-[#1C2024] shadow-large bg-white-1 group-hover:bg-slate-2 transition-colors flex items-center justify-center z-[2]", + ), + href=link, + target="_blank", + class_name="size-21 rounded-xl border border-slate-3 dark:border-[#1C2024] shadow-large bg-white/76 dark:bg-slate-1 z-[2] group relative cursor-pointer flex items-center justify-center", + ) + + +def security_badges() -> rx.Component: + return rx.box( + rx.box( + badge_card( + rx.el.div( + rx.el.span( + "AICPA", + class_name="text-violet-9 text-base font-semibold", + ), + rx.el.span("SOC 2", class_name="text-slate-9 text-sm font-medium"), + class_name="flex flex-col items-center justify-center", + ), + security_page.path, + ), + badge_card( + rx.el.div( + rx.image( + src="/landing/integrations/light/databricks.svg", + class_name="h-[24px] w-auto pb-0.5", + ), + rx.el.span( + "Partner", class_name="text-slate-9 text-sm font-medium" + ), + class_name="flex flex-col items-center justify-center", + ), + databricks_page.path, + ), + class_name="flex flex-row gap-4 items-center justify-center", + ), + class_name="p-6 flex items-center justify-center", + ) + + +def security() -> rx.Component: + return rx.el.div( + rx.box( + rx.box( + rx.el.h2( + "Secure by default", + class_name="text-slate-12 text-xl lg:text-2xl font-semibold lg:text-start text-center", + ), + rx.el.h3( + "SOC 2 compliant with enterprise-grade security and flexible deployment options.", + class_name="text-slate-9 text-lg lg:text-2xl font-semibold lg:text-start text-center text-balance", + ), + class_name="flex flex-col lg:col-span-2 p-10 max-lg:border-b border-slate-3", + ), + security_badges(), + class_name="grid grid-cols-1 lg:grid-cols-3 lg:divide-x divide-slate-4 w-full", + ), + class_name="flex flex-col gap-4 mx-auto w-full max-w-[64.19rem] lg:border-x justify-center items-center relative overflow-hidden border-slate-3 border-b", + ) + + +def deploy_content() -> rx.Component: + return rx.box( + rx.box( + feature_card( + "backend_db", + "Build and deploy", + "Deploy and scale your Reflex app with a single command.", + ), + feature_card( + "backend_auth", + "Add team members", + "Invite team members to your Reflex app and manage their permissions.", + ), + feature_card( + "infinity", + "Integrate with CI/CD", + "Deploy via GitHub Actions, GitLab CI, or your own custom pipeline.", + ), + feature_card( + "globe", + "Scale to multiple regions", + "Deploy your app to multiple regions for high availability and low latency.", + ), + feature_card( + "analytics", + "Get alerts and metrics", + "Get alerts and metrics for your Reflex app to help you monitor and optimize your app.", + ), + class_name="flex flex-col max-w-full lg:max-w-full divide-y divide-slate-3", + ), + rx.box( + rx.image( + src="/landing/hosting_features/light/card.webp", + class_name="absolute top-0 left-0 w-full h-full object-cover pointer-events-none dark:hidden", + ), + rx.image( + src="/landing/hosting_features/dark/card.webp", + class_name="absolute top-0 left-0 w-full h-full object-cover pointer-events-none dark:block hidden", + ), + class_name="justify-center items-center relative overflow-hidden w-full lg:flex hidden", + ), + class_name="flex flex-col lg:flex-row mx-auto w-full border-x-0 divide-x-0 lg:divide-x divide-slate-3 border-t border-slate-3", + ) + + +def deploy_section() -> rx.Component: + return rx.el.section( + rx.el.div( + rx.el.div( + numbers_pattern(side="left", class_name="left-0 top-0"), + numbers_pattern(side="right", class_name="right-0 top-0"), + header(), + rx.el.div( + animated_box(), + class_name="flex justify-center items-center pt-[3.75rem] w-full", + ), + class_name="flex flex-col items-center mx-auto w-full max-w-[64.19rem] relative overflow-hidden pt-20 border-t border-slate-3", + ), + deploy_content(), + class_name="flex flex-col items-center mx-auto w-full max-w-[64.19rem] lg:border-x border-slate-3", + ), + deploy_card(), + security(), + class_name="flex flex-col items-center mx-auto w-full", + ) diff --git a/pcweb/pages/landing/views/enterprise_social.py b/pcweb/pages/landing/views/enterprise_social.py new file mode 100644 index 0000000000..f1acb6dea9 --- /dev/null +++ b/pcweb/pages/landing/views/enterprise_social.py @@ -0,0 +1,103 @@ +import reflex as rx +import reflex_ui as ui + +from pcweb.components.numbers_pattern import numbers_pattern + + +def header() -> rx.Component: + return rx.el.div( + rx.el.div( + ui.icon("UserGroupIcon", class_name="shrink-0"), + rx.el.span("Teams", class_name="text-sm font-semibold"), + class_name="flex flex-row gap-2 items-center text-primary-9", + ), + rx.el.h2( + "Hear from the teams that use Reflex", + class_name="max-w-full w-full lg:text-3xl text-2xl text-center text-slate-12 font-semibold text-balance word-wrap break-words md:whitespace-pre", + ), + rx.el.p( + "Companies of all sizes trust Reflex to build internal tools and customer-facing apps.", + class_name="text-slate-9 text-sm font-medium text-center text-balance word-wrap break-words md:whitespace-pre", + ), + class_name="flex flex-col gap-4 items-center mx-auto w-full relative overflow-hidden", + ) + + +def enterprise_card(image: str, name: str, stat: str, text: str) -> rx.Component: + first_word = text.split(" ")[0] + rest_of_text = " ".join(text.split(" ")[1:]) + return rx.el.a( + rx.el.div( + rx.image( + src=f"/customers/{rx.color_mode_cond('light', 'dark')}{image}", + class_name="w-auto h-[1.875rem]", + ), + rx.el.span( + name, + class_name="text-slate-12 text-3xl font-bold", + ), + class_name="flex flex-row gap-3 items-center justify-start mr-auto", + ), + rx.el.div( + rx.el.h3( + stat, + class_name="text-4xl font-semibold text-slate-12", + ), + rx.el.div( + rx.el.span( + first_word, class_name="text-slate-12 text-base font-semibold" + ), + rx.el.span( + f" {rest_of_text}", + class_name="text-slate-10 text-base font-medium", + ), + class_name="inline-block", + ), + class_name="flex flex-col gap-2", + ), + ui.icon( + "ArrowUpRight01Icon", + class_name="group-hover:opacity-100 opacity-0 scale-50 group-hover:scale-100 transition-all duration-100 absolute top-4 right-4 size-5 text-secondary-11 origin-top-right ease-in-out z-100", + ), + to=f"/customers/{name.lower().replace(' ', '-')}", + class_name="flex flex-col gap-10 p-10 shadow-small border border-slate-3 hover:border-slate-4 rounded-2xl bg-white-1 hover:bg-slate-2 transition-colors h-[15.875rem] cursor-pointer group relative", + ) + + +def enterprise_social() -> rx.Component: + return rx.el.section( + rx.el.div( + numbers_pattern(side="left", reversed=True, class_name="left-0 top-0"), + numbers_pattern(side="right", reversed=True, class_name="right-0 top-0"), + header(), + class_name="flex flex-col items-center mx-auto w-full max-w-[64.19rem] lg:border-x border-slate-3 relative overflow-hidden py-20", + ), + rx.el.div( + enterprise_card( + "/autodesk/autodesk_small.svg", + "Autodesk", + "25%", + "Time saved on each project with Reflex.", + ), + enterprise_card( + "/bayesline/bayesline_small.svg", + "Bayesline", + "30%", + "Production codebase smaller than Dash.", + ), + enterprise_card( + "/ansa/ansa_small.svg", + "Ansa", + "100+", + "Hours of manual work saved a month.", + ), + enterprise_card( + "/sellerx/sellerx_small.svg", + "SellerX", + "10x", + "Faster than developing with React and FastAPI.", + ), + class_name="w-full grid grid-cols-1 lg:grid-cols-4 gap-2", + ), + class_name="flex flex-col items-center mx-auto w-full max-w-[84.5rem]", + ) diff --git a/pcweb/pages/landing/views/final_cta.py b/pcweb/pages/landing/views/final_cta.py new file mode 100644 index 0000000000..a3829012b6 --- /dev/null +++ b/pcweb/pages/landing/views/final_cta.py @@ -0,0 +1,37 @@ +import reflex as rx +import reflex_ui as ui + +from pcweb.components.numbers_pattern import numbers_pattern +from pcweb.constants import REFLEX_BUILD_URL + + +def content() -> rx.Component: + return rx.el.div( + rx.el.h2( + "Start building with Reflex.", + class_name="text-slate-12 lg:text-4xl text-3xl font-semibold text-center", + ), + rx.el.h3( + "All in one platform in Python", + class_name="text-slate-9 lg:text-4xl text-3xl font-semibold text-center", + ), + ui.link( + render_=ui.button( + "Get Started with Reflex", + size="lg", + class_name="font-semibold mt-8 h-10", + ), + to=REFLEX_BUILD_URL, + target="_blank", + ), + class_name="flex flex-col items-center mx-auto w-full justify-center", + ) + + +def final_cta() -> rx.Component: + return rx.el.section( + numbers_pattern(side="left", reversed=True, class_name="left-0 top-0"), + numbers_pattern(side="right", reversed=True, class_name="right-0 top-0"), + content(), + class_name="flex flex-col items-center mx-auto w-full max-w-[64.19rem] lg:border-x border-slate-3 relative overflow-hidden py-20", + ) diff --git a/pcweb/pages/landing/views/framework_section.py b/pcweb/pages/landing/views/framework_section.py deleted file mode 100644 index 15d7978fbd..0000000000 --- a/pcweb/pages/landing/views/framework_section.py +++ /dev/null @@ -1,37 +0,0 @@ -import reflex as rx - -from pcweb.components.icons.hugeicons import hi -from pcweb.pages.framework.demos.demos import demo_section - - -def header() -> rx.Component: - return rx.box( - rx.image( - src="/landing/patterns/light/pattern_framework.webp", - class_name="absolute top-0 left-0 w-full h-full object-cover pointer-events-none", - ), - rx.box( - hi("source-code-circle", class_name="shrink-0"), - rx.el.span("Reflex Framework", class_name="text-sm font-semibold"), - class_name="flex flex-row gap-2 items-center text-jade-10", - ), - rx.el.h2( - """Write full-stack apps in pure Python. -No JavaScript, no limits""", - class_name="max-w-full w-full lg:text-3xl text-2xl text-center text-slate-12 font-semibold text-balance word-wrap break-words md:whitespace-pre", - ), - rx.el.p( - """Write, test, and refine your full-stack app using just Python—no need to manage -complex frameworks or switch between languages.""", - class_name="text-slate-11 text-sm font-medium text-center text-balance word-wrap break-words md:whitespace-pre", - ), - class_name="flex flex-col gap-4 mx-auto w-full max-w-[64.19rem] lg:border-x border-slate-3 p-10 justify-center items-center relative overflow-hidden h-[22.75rem]", - ) - - -def framework_section() -> rx.Component: - return rx.el.section( - header(), - demo_section(color="jade"), - class_name="flex flex-col mx-auto w-full max-w-[84.19rem] justify-center items-center", - ) diff --git a/pcweb/pages/landing/views/hero.py b/pcweb/pages/landing/views/hero.py index 81ef903f60..86f28fbc5b 100644 --- a/pcweb/pages/landing/views/hero.py +++ b/pcweb/pages/landing/views/hero.py @@ -4,10 +4,10 @@ import httpx import reflex as rx +import reflex_ui as ui from reflex.experimental import ClientStateVar -from pcweb.components.icons.hugeicons import hi -from pcweb.components.icons.icons import get_icon_var +from pcweb.components.numbers_pattern import numbers_pattern from pcweb.constants import ( MAX_FILE_SIZE_BYTES, MAX_FILE_SIZE_MB, @@ -17,8 +17,6 @@ RX_BUILD_BACKEND, ) -from .video_demo import watch_preview - def is_content_type_valid(content_type: str) -> bool: """Check if the content type is valid.""" @@ -28,6 +26,9 @@ def is_content_type_valid(content_type: str) -> bool: textarea_x_pos = ClientStateVar.create(var_name="textarea_x_pos", default=0) textarea_y_pos = ClientStateVar.create(var_name="textarea_y_pos", default=0) textarea_opacity = ClientStateVar.create(var_name="textarea_opacity", default=0) +show_default_prompt = ClientStateVar.create( + var_name="show_default_prompt", default=True +) class ImageData(TypedDict): @@ -37,7 +38,8 @@ class ImageData(TypedDict): class SubmitPromptState(rx.State): _images: list[ImageData] | None = None - is_uploading: bool = False + is_uploading: rx.Field[bool] = rx.field(False) + is_processing: rx.Field[bool] = rx.field(False) @rx.event(background=True, temporal=True) async def redirect_to_ai_builder(self, form_data: dict): @@ -58,6 +60,7 @@ async def redirect_to_ai_builder(self, form_data: dict): ) async with self: self.reset() + self.is_processing = True return ( rx.redirect("/") if not response.is_success @@ -132,359 +135,208 @@ def image_data_uris(self) -> list[str]: ] -@rx.memo -def preset_cards(text: str, id: str, icon: str) -> rx.Component: - textarea_x_pos = ClientStateVar.create( - var_name="textarea_x_pos", default=0, global_ref=False - ) - textarea_y_pos = ClientStateVar.create( - var_name="textarea_y_pos", default=0, global_ref=False - ) - textarea_opacity = ClientStateVar.create( - var_name="textarea_opacity", default=0, global_ref=False - ) - return rx.box( - rx.el.button( - get_icon_var(icon, class_name="shrink-0"), - text, - on_click=SubmitPromptState.redirect_to_ai_builder({"prompt": text}), - on_mouse_enter=rx.call_function(textarea_opacity.set_value(1)), - on_mouse_leave=rx.call_function(textarea_opacity.set_value(0)), - id=id, - on_mouse_move=[ - rx.call_function( - textarea_x_pos.set_value( - rx.Var( - f"event.clientX - document.getElementById({id}).getBoundingClientRect().left" - ) - ) - ), - rx.call_function( - textarea_y_pos.set_value( - rx.Var( - f"event.clientY - document.getElementById({id}).getBoundingClientRect().top" - ) - ) - ), - ], - class_name="flex flex-row gap-2.5 p-2.5 rounded-[0.625rem] transition-bg cursor-pointer flex-1 text-sm font-medium text-slate-9 border-[rgba(190,_190,_210,_0.40)] dark:border-[rgba(255,_255,_255,_0.06)] hover:bg-[#fdfdfd78] dark:hover:bg-[#15161863] border-[1.5px] items-center justify-start w-full", +def integration_text(text: str, integration: str) -> rx.Component: + return rx.el.span( + rx.image( + src=f"/landing/integrations/light/{integration}.svg", + class_name="size-7 pointer-events-none shrink-0 inline-block align-text-bottom", ), - rx.box( - aria_hidden=True, - style={ - "border": "2px solid var(--c-violet-6)", - "opacity": textarea_opacity.value, - "WebkitMaskImage": f"radial-gradient(50% 40px at {textarea_x_pos.value}px {textarea_y_pos.value}px, black 45%, transparent)", - }, - class_name="pointer-events-none absolute left-0 top-0 z-10 h-full w-full rounded-[0.625rem] border-[1.5px] bg-[transparent] opacity-0 transition-opacity duration-500 box-border", + rx.el.span( + text, + class_name="text-slate-12 text-xl leading-[2.5rem] font-semibold", ), - class_name="relative w-full", + class_name="inline-flex items-center gap-2 align-bottom mx-1", ) -@rx.memo -def preset_image_card(text: str, id: str): - textarea_x_pos = ClientStateVar.create( - var_name="textarea_x_pos", default=0, global_ref=False - ) - textarea_y_pos = ClientStateVar.create( - var_name="textarea_y_pos", default=0, global_ref=False - ) - textarea_opacity = ClientStateVar.create( - var_name="textarea_opacity", default=0, global_ref=False - ) - return rx.upload.root( - rx.box( - rx.el.button( - get_icon_var("image-03", class_name="shrink-0"), - text, - on_click=rx.set_value("prompt", text), - class_name="flex flex-row gap-2 p-2.5 rounded-xl transition-bg cursor-pointer flex-1 text-sm font-medium text-slate-9 text-start border-[rgba(190,_190,_210,_0.40)] dark:border-[rgba(255,_255,_255,_0.06)] hover:bg-[#fdfdfd78] dark:hover:bg-[#15161863] border-[1.5px] w-full items-center", - on_mouse_enter=textarea_opacity.set_value(1), - on_mouse_leave=textarea_opacity.set_value(0), - id=id, - on_mouse_move=[ - rx.call_function( - textarea_x_pos.set_value( - rx.Var( - f"event.clientX - document.getElementById({id}).getBoundingClientRect().left" - ) - ) - ), - rx.call_function( - textarea_y_pos.set_value( - rx.Var( - f"event.clientY - document.getElementById({id}).getBoundingClientRect().top" - ) - ) - ), - ], - type="button", - ), - rx.box( - aria_hidden=True, - style={ - "border": "2px solid var(--c-violet-6)", - "opacity": textarea_opacity.value, - "WebkitMaskImage": f"radial-gradient(50% 40px at {textarea_x_pos.value}px {textarea_y_pos.value}px, black 45%, transparent)", - }, - class_name="pointer-events-none absolute left-0 top-0 z-10 h-full w-full rounded-xl border bg-[transparent] opacity-0 transition-opacity duration-500 box-border", - ), - id=id, - class_name="relative w-full", +def integration_text_light_dark(text: str, integration: str) -> rx.Component: + return rx.el.span( + rx.image( + src=f"/landing/integrations/light/{integration}.svg", + class_name="size-7 pointer-events-none shrink-0 inline-block align-text-bottom dark:hidden", ), - on_drop=SubmitPromptState.handle_upload( - rx.upload_files( - upload_id="upload-image-button", - ) + rx.image( + src=f"/landing/integrations/dark/{integration}.svg", + class_name="size-7 pointer-events-none shrink-0 align-text-bottom hidden dark:inline-block", + ), + rx.el.span( + text, + class_name="text-slate-12 text-xl leading-[2.5rem] font-semibold", ), - accept={ - "image/png": [".png"], - "image/jpeg": [".jpg", ".jpeg"], - "image/webp": [".webp"], - }, - multiple=True, - id="upload-image-button", + class_name="inline-flex items-center gap-2 align-bottom mx-1", ) @rx.memo -def one_upload_image_display(image_data_uri: str, index: int): - return rx.hover_card.root( - rx.hover_card.trigger( - rx.box( - rx.el.button( - hi( - "multiplication-sign", - size=11, - stroke_width=3.5, - ), - type="button", - on_click=SubmitPromptState.cancel_upload(index), - class_name="absolute top-1 right-1 -translate-y-1/2 translate-x-1/2 rounded-full transition-colors dark:border-token-main-surface-secondary border-[3px] border-slate-2 bg-slate-12 p-[2px] text-slate-1 flex justify-center items-center", - ), - rx.image( - src=image_data_uri, - class_name="rounded-lg object-cover h-full w-full aspect-square", - ), - class_name="flex items-center gap-2 relative size-12", +def one_upload_file_display(file_data_uri: str, index: int) -> rx.Component: + return ui.preview_card( + trigger=rx.box( + rx.el.button( + rx.html(""" + + +"""), + type="button", + on_click=SubmitPromptState.cancel_upload(index), + class_name="absolute top-1 right-1 rounded-full transition-colors flex justify-center items-center size-[0.89581rem]", ), - ), - rx.hover_card.content( rx.image( - src=image_data_uri, - class_name="rounded-lg object-cover h-full w-full", + src=file_data_uri, + class_name="rounded-lg object-cover h-full w-full aspect-square border border-slate-3", ), - side="top", - align="center", - class_name="bg-slate-1 p-2 rounded-xl shadow-large border border-slate-5", + class_name="flex items-center gap-2 relative size-12 shrink-0", + ), + content=rx.image( + src=file_data_uri, + class_name="rounded-lg object-cover h-full max-h-[10rem] w-auto", ), ) -def uploaded_image_display(): +def uploaded_file_display() -> rx.Component: return rx.foreach( SubmitPromptState.image_data_uris, - lambda image_data_uri, index: one_upload_image_display( - image_data_uri=image_data_uri, + lambda file_data_uri, index: one_upload_file_display( + file_data_uri=file_data_uri, index=index, ), ) -def uploading_image_display(): - return rx.box( - rx.el.button( - hi( - "multiplication-sign", - size=11, - stroke_width=3.5, - ), - type="button", - on_click=SubmitPromptState.cancel_upload(-1), - class_name="absolute top-1 right-1 -translate-y-1/2 translate-x-1/2 rounded-full transition-colors dark:border-token-main-surface-secondary border-[3px] border-slate-2 bg-slate-12 p-[2px] text-slate-1 flex justify-center items-center", - ), - rx.skeleton( - class_name="rounded-lg object-cover h-full w-full aspect-square", - ), - class_name="flex items-center gap-2 relative size-12", - ) - - def prompt_box() -> rx.Component: - return rx.box( - rx.el.form( + return rx.el.form( + rx.el.div( rx.cond( - SubmitPromptState.image_data_uris | SubmitPromptState.is_uploading, - rx.box( - rx.cond( - SubmitPromptState.is_uploading, - uploading_image_display(), - uploaded_image_display(), + show_default_prompt.value, + rx.el.span( + rx.el.span( + "Build a dashboard with ", + integration_text( + "Databricks", + "databricks", + ), + "metrics,", + class_name="animate-[prompt-box-line] animate-duration-[200ms] animate-ease-out origin-left absolute top-3 left-5 h-10 pointer-events-none", ), - class_name="h-auto flex flex-row items-center gap-2 overflow-x-auto scrollbar-thin scrollbar-thumb-slate-4 scrollbar-track-transparent overflow-y-visible pt-2", - ), - ), - rx.box( - rx.upload.root( - rx.box( - rx.el.input( - placeholder="What do you want to build?", - id="prompt", - class_name="font-medium text-slate-12 text-base placeholder:text-slate-9 outline-none focus:outline-none caret-slate-12 w-full bg-transparent", + rx.el.span( + "use ", + integration_text_light_dark( + "Okta", + "okta", ), - rx.box( - rx.upload.root( - rx.el.button( - hi("image-add-02"), - type="button", - title="Upload images", - class_name="w-full [background:linear-gradient(45deg,var(--c-violet-2),var(--c-violet-2)_50%,var(--c-violet-2))_padding-box,conic-gradient(from_var(--border-angle),#9a79fd7a_60%,#8b5cf680_70%,#745dd1db_85%,#8b5cf680_90%,#9a79fd7a)_border-box] dark:[background:linear-gradient(45deg,var(--c-violet-3),var(--c-violet-3)_50%,var(--c-violet-3))_padding-box,conic-gradient(from_var(--border-angle),#5F43D0CC_60%,#7c57f8e3_72%,#926bfee6_85%,#7c57f8e3_95%,#5F43D0CC)_border-box] border-[1.5px] border-transparent animate-border text-violet-9 text-sm cursor-pointer inline-flex items-center justify-center relative transition-bg shrink-0 font-sans disabled:cursor-not-allowed disabled:border disabled:border-slate-5 disabled:!bg-slate-3 disabled:!text-slate-8 transition-bg outline-none peer-placeholder-shown:!bg-slate-3 peer-placeholder-shown:!bg-none peer-placeholder-shown:cursor-not-allowed peer-placeholder-shown:border peer-placeholder-shown:border-slate-5 peer-placeholder-shown:!text-slate-8 text-nowrap px-1.5 h-7 rounded-md gap-1.5 bg-transparent hover:bg-slate-3 font-medium", - ), - on_drop=SubmitPromptState.handle_upload( - rx.upload_files( - upload_id="upload-image-button", - ) - ), - accept={ - "image/png": [".png"], - "image/jpeg": [".jpg", ".jpeg"], - "image/webp": [".webp"], - }, - no_drag=True, - multiple=True, - max_files=MAX_IMAGES_COUNT, - id="upload-image-button", - ), - rx.el.button( - rx.html( - """ - -""" - ), - class_name="bg-[#6E56CF] hover:bg-[#654DC4] text-white rounded-full size-6 shrink-0 flex items-center justify-center cursor-pointer", - ), - class_name="flex flex-row gap-2.5 items-center", + "for auth, ping me on ", + integration_text( + "Slack", + "slack", ), - on_mouse_enter=textarea_opacity.set_value(1), - on_mouse_leave=textarea_opacity.set_value(0), - on_mouse_move=[ - rx.call_function( - textarea_x_pos.set_value( - rx.Var( - "event.clientX - document.getElementById('landing-input-box').getBoundingClientRect().left" - ) - ) - ), - rx.call_function( - textarea_y_pos.set_value( - rx.Var( - "event.clientY - document.getElementById('landing-input-box').getBoundingClientRect().top" - ) - ) - ), - ], - id="landing-input-box", - class_name="bg-[#fdfdfd78] dark:bg-[#16161ac2] border-[rgba(190,_190,_210,_0.40)] dark:border-[rgba(255,_255,_255,_0.06)] overflow-hidden border-[1.5px] rounded-xl px-3 py-2.5 w-full flex flex-row items-center gap-3 focus-within:border-violet-6", + class_name="animate-[prompt-box-line] animate-duration-[200ms] animate-ease-out origin-left absolute top-13 left-5 h-10 animate-delay-200 animate-fill-both pointer-events-none", ), - accept={ - "image/png": [".png"], - "image/jpeg": [".jpg", ".jpeg"], - "image/webp": [".webp"], - }, - on_drop=SubmitPromptState.handle_upload( - rx.upload_files( # pyright: ignore [reportArgumentType] - upload_id="upload-landing-box", - ) + rx.el.span( + "when critical metrics", + # Cursor + rx.el.span( + class_name="w-0.5 h-8 bg-slate-12 animate-blink inline-block align-middle animate-fill-both animate-delay-450", + ), + class_name="animate-[prompt-box-line] animate-duration-[200ms] animate-ease-out origin-left absolute top-23 left-5 h-10 animate-delay-400 animate-fill-both pointer-events-none", ), - no_click=True, - multiple=True, - max_files=MAX_IMAGES_COUNT, - id="upload-landing-box", - drag_active_style=rx.Style( - style_dict={ - "& #landing-input-box": { - "border": "1.5px dashed var(--c-slate-9)" - } - } + class_name="text-slate-11 dark:text-slate-9 text-xl leading-[2.5rem] font-medium cursor-text", + ), + rx.el.div( + rx.el.textarea( + placeholder="What do you want to build?", + auto_focus=True, + id="prompt", + custom_attrs={ + "autoComplete": "off", + "autoCapitalize": "none", + "autoCorrect": "off", + "spellCheck": "false", + }, + auto_height=True, + enter_key_submit=True, + class_name="text-slate-12 text-xl font-medium size-full placeholder:text-slate-9 border-none focus:border-none focus:outline-none outline-none resize-none caret-slate-12 mt-2 resize-none w-full [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-track]:bg-grayA-3 [&::-webkit-scrollbar-thumb]:bg-slate-7 [&::-webkit-scrollbar-track]:rounded-full [&::-webkit-scrollbar-thumb]:rounded-full bg-transparent min-h-[2.5rem] max-h-[10.5rem]", + ), + rx.el.div( + uploaded_file_display(), + class_name="flex flex-row gap-2 items-center overflow-x-auto mb-2", ), + class_name="flex flex-col gap-2", ), - rx.box( - aria_hidden=True, - style={ - "border": "2px solid var(--c-violet-6)", - "opacity": textarea_opacity.value, - "WebkitMaskImage": f"radial-gradient(45% 40px at {textarea_x_pos.value}px {textarea_y_pos.value}px, black 45%, transparent)", - }, - class_name="pointer-events-none absolute left-0 top-0 z-10 h-full w-full rounded-xl border bg-[transparent] opacity-0 transition-opacity duration-500 box-border", + ), + rx.cond( + show_default_prompt.value, + rx.el.span( + class_name="absolute inset-0 cursor-text z-10", + on_click=show_default_prompt.set_value(False), ), - class_name="relative w-full", + None, ), - rx.box( - preset_image_card(text="Use an Image", id="upload-image-box"), - preset_cards(text="Chat App", id="chat-app", icon="ai-chat-02"), - preset_cards( - text="Live Dashboard", id="live-dashboard", icon="webpage" + on_click=rx.set_focus("prompt"), + class_name="min-h-[9rem] h-full lg:w-[29rem] max-w-[29rem] max-lg:w-full rounded-2xl bg-white-1 dark:bg-[#151618] border border-slate-4 px-5 py-3 relative overflow-hidden dark:!shadow-none", + style={ + "box-shadow": "0 2px 0 0 #FFF inset, 0 2px 6px 0 light-dark(rgba(28, 32, 36, 0.08), rgba(0, 0, 0, 0)) inset, 0 1px 5px 0 light-dark(rgba(28, 32, 36, 0.03), rgba(0, 0, 0, 0)) inset;", + }, + ), + rx.el.div( + rx.upload.root( + ui.button( + ui.icon(icon="AttachmentIcon"), + "Attach", + size="lg", + type="button", + variant="ghost", + on_click=show_default_prompt.set_value(False), + class_name="rounded-[10px] font-semibold text-slate-10 dark:text-slate-9", + ), + on_drop=SubmitPromptState.handle_upload( + rx.upload_files( + upload_id="upload-image-button", + ) + ), + accept={ + "image/png": [".png"], + "image/jpeg": [".jpg", ".jpeg"], + "image/webp": [".webp"], + }, + on_drop_rejected=rx.toast.error( + f"Unsupported file type or file too large (Max {MAX_FILE_SIZE_MB}MB, up to {MAX_IMAGES_COUNT} files)." ), - class_name="grid grid-cols-1 lg:grid-cols-3 gap-2", + max_files=MAX_IMAGES_COUNT, + max_size=MAX_FILE_SIZE_BYTES, + multiple=True, + id="upload-image-button", ), - class_name="flex flex-col gap-4 w-full", - on_submit=SubmitPromptState.redirect_to_ai_builder, + ui.button( + "Build Your App", + size="lg", + variant="primary", + loading=SubmitPromptState.is_processing, + on_click=show_default_prompt.set_value(False), + class_name="rounded-[10px] font-semibold", + ), + class_name="flex flex-row items-center gap-2 justify-between", + ), + on_mount=rx.call_script( + f""" + if (window.innerWidth < 1024) {{ + {show_default_prompt.set}(false); + }} + """ ), - class_name="max-w-[34.125rem] rounded-[1.75rem] p-4 flex flex-col gap-4 w-full mt-[1.5rem] bg-[linear-gradient(180deg,_rgba(252,_252,_253,_0.82)_0%,_rgba(252,_252,_253,_0.80)_88%)] shadow-[0px_0px_0px_1px_rgba(190,_190,_210,_0.40)] dark:bg-[linear-gradient(180deg,_rgba(21,_22,_24,_0.82)_0%,_rgba(21,_22,_24,_0.80)_88%)] dark:shadow-[0px_0px_0px_1px_rgba(255,_255,_255,_0.06)] backdrop-blur-[6px] z-[1]", + on_submit=SubmitPromptState.redirect_to_ai_builder, + class_name="flex flex-col gap-4 mt-6 max-w-[29rem] w-full", ) def hero() -> rx.Component: return rx.el.section( - # Dark Waves - rx.image( - src="/landing/patterns/dark/wave.webp", - class_name="absolute lg:top-[65px] top-[45px] lg:right-0 right-[-150px] z-[-1] pointer-events-none hidden dark:lg:block w-[514px] lg:h-[406px] h-[506px] dark:block", - ), - rx.image( - src="/landing/patterns/dark/wave.webp", - class_name="absolute lg:top-[65px] top-[45px] lg:left-0 left-[-150px] z-[-1] pointer-events-none hidden dark:lg:block scale-x-[-1] w-[514px] lg:h-[406px] h-[506px] dark:block", - ), - # Light Waves - rx.image( - src="/landing/patterns/light/wave.webp", - class_name="absolute lg:top-[65px] top-[45px] lg:right-0 right-[-150px] z-[-1] pointer-events-none hidden lg:block w-[514px] lg:h-[406px] h-[506px] dark:hidden dark:lg:hidden", - ), - rx.image( - src="/landing/patterns/light/wave.webp", - class_name="absolute lg:top-[65px] top-[45px] lg:left-0 left-[-150px] z-[-1] pointer-events-none hidden lg:block scale-x-[-1] w-[514px] lg:h-[406px] h-[506px] dark:hidden dark:lg:hidden", - ), - # Triangle - rx.box( - rx.image( - src="/landing/patterns/dark/triangle.webp", - class_name="size-full bg-transparent", - ), - class_name="absolute top-[232px] left-1/2 transform -translate-x-1/2 -translate-y-1/2 object-cover z-[1] pointer-events-none w-[31rem] h-[20.875rem] hidden lg:block bg-transparent mix-blend-overlay", - ), - # Small gradient - rx.box( - class_name="z-[-1] blur-[16px] absolute rounded-[64.25rem] bg-[radial-gradient(50%_50%_at_50%_50%,_light-dark(#D4CAFE,#4329AC)_0%,_rgba(21,_22,_24,_0)_100%)] w-[37rem] lg:h-[9.5rem] h-[6.5rem] overflow-hidden pointer-events-none shrink-0 left-1/2 transform -translate-x-1/2 -translate-y-1/2 lg:top-[22.5rem] top-[25.5rem] saturate-[1.25] lg:-mx-0 -mx-8 opacity-80 md:opacity-100 max-lg:opacity-50" - ), - # Big gradient - rx.box( - class_name="z-[-1] blur-[28px] absolute rounded-[64.25rem] bg-[radial-gradient(50%_50%_at_50%_50%,_#D4CAFE_0%,_rgba(21,_22,_24,_0)_100%)] dark:bg-[radial-gradient(50%_50%_at_50%_50%,_#4329AC_0%,_rgba(21,_22,_24,_0)_100%)] w-[45rem] lg:h-[10.25rem] h-[18.5rem] overflow-hidden pointer-events-none shrink-0 left-1/2 transform -translate-x-1/2 -translate-y-1/2 top-[26.25rem] saturate-[1.5] lg:-mx-0 -mx-8 opacity-80 md:opacity-100 max-lg:hidden" - ), - # New Ellipse gradient - rx.box( - class_name="absolute w-[1076px] h-[676px] lg:top-[-369px] top-[-26rem] bg-[radial-gradient(37.87%_37.87%_at_50%_50%,_#F4F0FE_0%,_rgba(21,_22,_24,_0)_100%)] dark:bg-[radial-gradient(37.87%_37.87%_at_50%_50%,_#261958_0%,_rgba(21,_22,_24,_0)_100%)] z-[-1] pointer-events-none saturate-[1.5] lg:-mx-0 -mx-8 opacity-80 md:opacity-100" - ), - # Headings + numbers_pattern(side="left", class_name="lg:top-[65px] top-[45px]"), + numbers_pattern(side="right", class_name="lg:top-[65px] top-[45px]"), rx.el.h1( - "Prompt to production app, in seconds", - class_name="max-w-full inline-block bg-clip-text bg-gradient-to-r from-slate-12 to-slate-11 w-full text-5xl lg:text-6xl font-semibold text-center text-transparent text-balance mx-auto break-words z-[1]", - ), - rx.el.h2( - "A unified platform to build and deploy all in Python.", - class_name="max-w-full w-full text-lg lg:text-xl text-center text-slate-9 font-medium mx-auto text-balance word-wrap break-words md:whitespace-pre z-[1]", + """Build From Prompt to + Production App, In Seconds""", + class_name="text-slate-12 lg:text-4xl text-3xl font-semibold text-center lg:max-w-[576px] word-wrap break-words lg:whitespace-pre", ), prompt_box(), - watch_preview(), - class_name="flex flex-col justify-center items-center gap-4 mx-auto w-full max-w-[64.19rem] lg:border-x border-slate-3 pb-[6.31rem] pt-28 lg:pt-[10rem] relative lg:overflow-hidden overflow-visible z-[1] bg-transparent lg:bg-slate-1 lg:px-4", + class_name="flex flex-col justify-center items-center gap-4 mx-auto w-full max-w-[64.19rem] lg:border-x border-slate-3 pb-[3rem] pt-28 lg:pt-[8rem] relative lg:overflow-hidden overflow-hidden z-[1] bg-transparent lg:bg-slate-1 lg:px-4", ) diff --git a/pcweb/pages/landing/views/integrations.py b/pcweb/pages/landing/views/integrations.py new file mode 100644 index 0000000000..2a52a05e9c --- /dev/null +++ b/pcweb/pages/landing/views/integrations.py @@ -0,0 +1,161 @@ +import reflex as rx +import reflex_ui as ui + +from pcweb.components.numbers_pattern import numbers_pattern + + +def header() -> rx.Component: + return rx.el.div( + rx.el.div( + ui.icon("WorkflowSquare08Icon", class_name="shrink-0"), + rx.el.span("Integrations", class_name="text-sm font-semibold"), + class_name="flex flex-row gap-2 items-center text-primary-9", + ), + rx.el.h2( + "Integrate With Your Platforms", + class_name="max-w-full w-full lg:text-3xl text-2xl text-center text-slate-12 font-semibold text-balance word-wrap break-words md:whitespace-pre", + ), + rx.el.p( + "Build entire app flow using powerful integrations.", + class_name="text-slate-9 text-sm font-medium text-center text-balance word-wrap break-words md:whitespace-pre", + ), + class_name="flex flex-col gap-4 items-center mx-auto w-full relative overflow-hidden", + ) + + +def intregation_card( + first_integration: str, + second_integration: str, + class_name: str = "", +): + first_light_dark_path = rx.color_mode_cond( + f"/landing/integrations/light/{first_integration}.svg", + f"/landing/integrations/dark/{first_integration}.svg", + ) + second_light_dark_path = rx.color_mode_cond( + f"/landing/integrations/light/{second_integration}.svg", + f"/landing/integrations/dark/{second_integration}.svg", + ) + return rx.el.div( + rx.el.div( + rx.image( + src=first_light_dark_path, + class_name="size-7 pointer-events-none shrink-0", + ), + class_name=ui.cn( + "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 size-14 rounded-[0.625rem] border border-slate-5 dark:border-[#1C2024] shadow-medium bg-white-1 z-[3] flex justify-center items-center", + "animate-[fade-scale-out] animate-duration-[8000ms] animate-ease-out animate-infinite", + class_name, + ), + ), + rx.el.div( + rx.image( + src=second_light_dark_path, + class_name="size-7 pointer-events-none shrink-0", + ), + class_name=ui.cn( + "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 size-14 rounded-[0.625rem] border border-slate-5 dark:border-[#1C2024] shadow-medium bg-white-1 z-[3] flex justify-center items-center", + "animate-[fade-scale-in] animate-duration-[8000ms] animate-ease-out animate-infinite", + class_name, + ), + ), + class_name="relative size-14", + ) + + +def line_svg(class_name: str = "") -> rx.Component: + return rx.html( + """ + + + + + + + + + +""", + class_name=f"absolute z-[0] opacity-50 dark:opacity-100 {class_name}", + ) + + +def ellipse_svg(class_name: str = "") -> rx.Component: + return rx.html( + """ + + + + + + + + + + +""", + class_name=f"absolute z-[1] {class_name}", + ) + + +def r_logo_card() -> rx.Component: + return rx.el.div( + rx.el.div( + rx.image( + src="/landing/integrations/light/r_logo.svg", + class_name="h-8 w-autopointer-events-none shrink-0 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2", + ), + class_name="size-15 rounded-lg border border-slate-5 dark:border-[#1C2024] shadow-large bg-white-1 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-[2]", + ), + class_name="size-18 rounded-xl border border-slate-3 dark:border-[#1C2024] shadow-large bg-white/76 dark:bg-slate-1 relative z-[2]", + ) + + +def lines() -> rx.Component: + return rx.el.div( + rx.el.div( + line_svg(class_name="left-[12rem] top-1/2 -translate-y-1/2"), + # ellipse_svg( + # class_name="top-1/2 -translate-y-1/2 mix-blend-darken z-[0] animate-[ellipse-slide-left] animate-duration-[8000ms] animate-ease-in-out animate-infinite" + # ), + class_name="relative overflow-hidden", + ), + rx.el.div( + # ellipse_svg( + # class_name="top-1/2 -translate-y-1/2 mix-blend-darken z-[0] animate-[ellipse-slide-right] animate-duration-[8000ms] animate-ease-in-out animate-infinite" + # ), + line_svg(class_name="right-[12rem] top-1/2 -translate-y-1/2 scale-x-[-1]"), + class_name="relative overflow-hidden", + ), + class_name="grid grid-cols-2 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 lg:w-[43.5rem] w-[36rem] z-[0] overflow-hidden h-[20rem]", + ) + + +def integrations_row() -> rx.Component: + return rx.el.div( + lines(), + rx.el.div( + intregation_card("supabase", "langchain"), + intregation_card("openai", "databricks"), + intregation_card("stripe", "anthropic"), + class_name="flex flex-col gap-10", + ), + r_logo_card(), + rx.el.div( + intregation_card("aws", "gcp"), + intregation_card("azure", "oracle"), + intregation_card("databricks", "reflex"), + class_name="flex flex-col gap-10", + ), + class_name="flex flex-row items-center lg:gap-[7.5rem] gap-14 mt-10 relative max-lg:w-full justify-center", + ) + + +def integrations() -> rx.Component: + return rx.el.section( + numbers_pattern(side="left", class_name="left-0 top-0"), + numbers_pattern(side="right", class_name="right-0 top-0"), + header(), + integrations_row(), + class_name="flex flex-col items-center mx-auto w-full max-w-[64.19rem] lg:border-x border-slate-3 relative overflow-hidden lg:border-t lg:pb-26 pb-20 pt-20", + ) diff --git a/pcweb/pages/landing/views/os_bento.py b/pcweb/pages/landing/views/os_bento.py new file mode 100644 index 0000000000..75e507b6a7 --- /dev/null +++ b/pcweb/pages/landing/views/os_bento.py @@ -0,0 +1,40 @@ +import reflex as rx +import reflex_ui as ui + +from pcweb.components.numbers_pattern import numbers_pattern +from pcweb.pages.framework.views.frontend_features import frontend_grid + + +def header() -> rx.Component: + return rx.el.div( + rx.el.div( + ui.icon("Layers01Icon", class_name="shrink-0"), + rx.el.span( + "Open Source", + class_name="text-sm font-semibold", + ), + class_name="flex flex-row gap-2 items-center text-primary-9", + ), + rx.el.h2( + "Built on our Open Source Python Framework", + class_name="max-w-full w-full lg:text-3xl text-2xl text-center text-slate-12 font-semibold text-balance word-wrap break-words md:whitespace-pre", + ), + rx.el.p( + "Reflex is the only solution that gives you full flexibility while staying in the language your team knows - Python.", + class_name="text-slate-9 text-sm font-medium text-center text-balance word-wrap break-words md:whitespace-pre", + ), + class_name="flex flex-col gap-4 items-center mx-auto w-full relative overflow-hidden", + ) + + +def os_bento() -> rx.Component: + return rx.el.section( + rx.el.div( + numbers_pattern(side="left", reversed=True, class_name="left-0 top-0"), + numbers_pattern(side="right", reversed=True, class_name="right-0 top-0"), + header(), + class_name="flex flex-col items-center mx-auto w-full max-w-[64.19rem] lg:border-x border-slate-3 relative overflow-hidden py-20", + ), + frontend_grid(), + class_name="flex flex-col items-center mx-auto w-full", + ) diff --git a/pcweb/pages/landing/views/os_stats.py b/pcweb/pages/landing/views/os_stats.py new file mode 100644 index 0000000000..bb6ea7c477 --- /dev/null +++ b/pcweb/pages/landing/views/os_stats.py @@ -0,0 +1,53 @@ +import reflex as rx +import reflex_ui as ui + +from pcweb.components.icons import get_icon +from pcweb.constants import CONTRIBUTORS, DISCORD_USERS, GITHUB_STARS + + +def stat_card( + stat: str, text: str, icon: str, class_name: str = "", color: str = "!text-slate-9" +) -> rx.Component: + return rx.box( + rx.box( + get_icon(icon, class_name=color), + rx.text(text, class_name="font-base text-slate-9"), + class_name="flex flex-row gap-2 items-center", + ), + rx.text(stat, class_name="font-x-large text-slate-12"), + class_name=ui.cn( + "flex flex-col gap-2 w-full p-10 items-center", + class_name, + ), + ) + + +def stats_grid() -> rx.Component: + return rx.box( + stat_card( + stat=f"{GITHUB_STARS:,}+", + text="Stars", + icon="star", + ), + stat_card( + stat=f"{CONTRIBUTORS:,}+", + text="Contributors", + icon="fork", + ), + stat_card( + stat=f"{DISCORD_USERS:,}+", + text="Discord", + icon="discord_navbar", + ), + class_name="grid grid-cols-1 lg:grid-cols-3 gap-0 grid-rows-1 w-full divide-slate-3 lg:divide-x max-lg:divide-y", + ) + + +def os_stats() -> rx.Component: + return rx.el.section( + rx.box( + stats_grid(), + class_name="flex flex-row max-w-[64.19rem] justify-center w-full lg:border-x border-slate-3", + ), + class_name="flex flex-col justify-center items-center w-full", + ) diff --git a/pcweb/pages/landing/views/products.py b/pcweb/pages/landing/views/products.py index c850dab7c3..157d77e75a 100644 --- a/pcweb/pages/landing/views/products.py +++ b/pcweb/pages/landing/views/products.py @@ -18,29 +18,7 @@ def product_card( rx.el.div( rx.el.span(title, class_name="text-slate-12 text-xl font-semibold"), rx.el.span(description, class_name="text-slate-9 text-sm font-medium"), - rx.link( - rx.el.span( - link, - class_name="text-sm font-medium text-slate-12 underline-none hover:text-slate-12", - ), - get_icon( - "chevron_right", - class_name="size-4 text-slate-9 group-hover:text-slate-12 group-hover:translate-x-1 transition-all duration-300", - ), - href=url, - underline="none", - class_name="flex flex-row gap-2 items-center gap-[0.375rem] mt-4 group", - ), - class_name="flex flex-col gap-2 p-10", - ), - rx.el.div( - rx.el.span(number), - rx.el.span(name), - rx.image( - src=f"/landing/lines/light/lines_{color[0]}.webp", - class_name="h-6 w-auto pointer-events-none", - ), - class_name=f"flex flex-row gap-5 items-center px-3.5 h-10 font-mono text-xs font-medium text-{color[0]}-{color[1]} overflow-hidden", + class_name="flex flex-col gap-2 px-10 pt-10", ), rx.el.div( rx.image( @@ -51,8 +29,21 @@ def product_card( src=f"/landing/products/dark/product_{graphic}.webp", class_name="w-auto pointer-events-none hidden dark:block", ), + class_name="w-full h-[17.25rem]", + ), + rx.el.a( + rx.el.span( + link, + class_name="text-sm font-medium text-slate-12 underline-none hover:text-slate-12", + ), + get_icon( + "chevron_right", + class_name="size-4 text-slate-9 group-hover:text-slate-12 group-hover:translate-x-1 transition-all duration-300", + ), + to=url, + class_name="flex flex-row items-center gap-2 justify-between group h-[4rem] px-10 hover:bg-slate-2 transition-colors border-t max-lg:border-b border-slate-3", ), - class_name="flex flex-col divide-y divide-slate-3", + class_name="flex flex-col", ) @@ -83,10 +74,10 @@ def products() -> rx.Component: "Hosting Platform", "Deploy, Host, and Scale", "Deploy through Databricks, Snowflake, self-host on AWS, GCP, Azure, or use Reflex Cloud.", - "Explore Databricks Integration", - "/databricks", + "Explore Hosting Options", + "/hosting", ("amber", "11"), "hosting", ), - class_name="grid grid-cols-1 md:grid-cols-3 mx-auto w-full max-w-[64.19rem] lg:border-x border-slate-3 lg:divide-x divide-slate-3 lg:border-y", + class_name="grid grid-cols-1 md:grid-cols-3 mx-auto w-full max-w-[64.19rem] lg:border-x border-slate-3 lg:divide-x divide-slate-3 lg:border-t", ) diff --git a/pcweb/pages/landing/views/social_marquee.py b/pcweb/pages/landing/views/social_marquee.py new file mode 100644 index 0000000000..860b42b999 --- /dev/null +++ b/pcweb/pages/landing/views/social_marquee.py @@ -0,0 +1,130 @@ +from dataclasses import dataclass + +import reflex as rx +import reflex_ui as ui + +from pcweb.components.marquee import marquee + + +def get_highlight(text: str) -> rx.Component: + return rx.el.span(text, class_name="text-primary-11") + + +def get_normal_text(*children) -> rx.Component: + return rx.el.span( + *children, class_name="text-secondary-12 text-sm font-medium text-wrap flex-1" + ) + + +@dataclass +class Social: + name: str + role: str + text: str | rx.Component + + +SOCIALS_1 = [ + Social( + name="vishnudeva", + role="Reddit User", + text=get_normal_text( + "Been a lurker on Hacker News for years but I created an account just so I could say how excited I am! Love the effort you're putting into ", + get_highlight("Reflex"), + ". Streamlit felt really painful to use whenever you want to do anything slightly out of the main path.", + ), + ), + Social( + name="vikinghckr", + role="Reddit User", + text=get_normal_text( + "I'm not exaggerating but this might just be the highest impact library I've seen. As a backend developer who has lots of great project ideas but bail at the thought of having to use JavaScript and HTML, ", + get_highlight("Reflex"), + " is a godsend!", + ), + ), + Social( + name="Alex", + role="OpenSea Co-founder", + text=get_normal_text( + "Have been playing with ", + get_highlight("Reflex"), + " since Jan and realized I should just say, from a fellow YC member: love the architecture decisions you guys are making! ❤️", + ), + ), +] + +SOCIALS_2 = [ + Social( + name="PoshoDev", + role="Discord User", + text=get_normal_text( + "I'm experimenting with ", + get_highlight("Reflex"), + " for the first time and I have to say that I really love the experience so far. Not needing to create frontend and backend individually for small web projects is a huge advantage. 😊", + ), + ), + Social( + name="Andrew", + role="Discord User", + text=get_normal_text( + "I've recently started using ", + get_highlight("Reflex"), + " and love it. My developer productivity is through the roof. Built a full-stack web app with stripe integration, firebase user authentication all built and looking quite nice and all done in about 2 nights of work.", + ), + ), + Social( + name="Chaumy", + role="Discord User", + text=get_normal_text( + "Finally managed to work through the docs - ", + get_highlight("Reflex"), + " looks like an awesome framework to build webapps with - I just can't get used to the whole javascript ecosystem", + ), + ), +] + + +def social_card(social: Social) -> rx.Component: + return rx.el.div( + social.text, + rx.el.div( + ui.gradient_profile( + seed=social.name, + class_name="size-9 rounded-full", + ), + rx.el.div( + rx.el.span( + social.name, class_name="text-secondary-12 font-medium text-sm" + ), + rx.el.span( + social.role, class_name="text-secondary-11 text-sm font-medium" + ), + class_name="flex flex-col", + ), + class_name="flex flex-row gap-4 mt-auto", + ), + # rx.el.a( + # to=social.url, + # target="_blank", + # class_name="absolute inset-0", + # ), + # ui.icon( + # "ArrowUpRight01Icon", + # class_name="group-hover:opacity-100 opacity-0 scale-50 group-hover:scale-100 transition-all duration-100 absolute bottom-4 right-4 size-5 text-primary-11 origin-bottom-left ease-in-out", + # ), + class_name="flex flex-col gap-4 bg-slate-1 hover:bg-slate-2 transition-colors relative w-[22.5rem] h-[15rem] flex-shrink-0 p-6 group border-slate-4 py-10", + ) + + +def social_marquee() -> rx.Component: + return rx.el.section( + marquee( + *[social_card(social) for social in SOCIALS_1], + direction="left", + ), + marquee( + *[social_card(social) for social in SOCIALS_2], + direction="right", + ), + class_name="flex flex-col mx-auto w-full max-w-[64.19rem] lg:border-x justify-center items-center relative overflow-hidden border-slate-3 border-b", + ) diff --git a/pcweb/pages/landing/views/social_stats.py b/pcweb/pages/landing/views/social_stats.py new file mode 100644 index 0000000000..752d9ca1a0 --- /dev/null +++ b/pcweb/pages/landing/views/social_stats.py @@ -0,0 +1,24 @@ +import reflex as rx + +from pcweb.components.icons.icons import get_icon +from pcweb.components.numbers_pattern import numbers_pattern +from pcweb.constants import GITHUB_STARS + + +def stat(icon: str, text: str) -> rx.Component: + return rx.el.section( + get_icon(icon, class_name="text-primary-9"), + rx.el.span(text, class_name="font-medium text-sm text-slate-12"), + class_name="flex flex-row items-center gap-2", + ) + + +def social_stats(): + return rx.el.section( + numbers_pattern(side="left", reversed=True, class_name="left-0 top-0"), + numbers_pattern(side="right", reversed=True, class_name="right-0 top-0"), + stat("browser", "1M+ Apps Built"), + stat("checkmark", "Used by 25% of Fortune 500"), + stat("github_navbar", f"{GITHUB_STARS // 1000}K GitHub Stars"), + class_name="flex flex-col justify-center items-center mx-auto w-full max-w-[64.19rem] lg:border-x border-slate-3 relative overflow-hidden border-t gap-4 lg:py-[5rem] py-[3.5rem] max-lg:border-b", + ) diff --git a/pcweb/pages/landing/views/use_cases.py b/pcweb/pages/landing/views/use_cases.py new file mode 100644 index 0000000000..a491d152b9 --- /dev/null +++ b/pcweb/pages/landing/views/use_cases.py @@ -0,0 +1,132 @@ +import reflex as rx +import reflex_ui as ui +from reflex.experimental.client_state import ClientStateVar + +from pcweb.components.numbers_pattern import numbers_pattern + +items = [ + ("Analytics", "Analytics01Icon"), + ("Finance", "CreditCardPosIcon"), + ("E-commerce", "ShoppingBasket03Icon"), + ("DevOps", "CloudServerIcon"), + ("Databases", "Database02Icon"), + ("AI Workflows", "MagicWand01Icon"), +] + +selected_industry = ClientStateVar.create( + var_name="selected_industry", default=items[0][0] +) + + +def header() -> rx.Component: + return rx.el.div( + rx.el.div( + ui.icon("BrowserIcon", class_name="shrink-0"), + rx.el.span("Use Cases", class_name="text-sm font-semibold"), + class_name="flex flex-row gap-2 items-center text-primary-9", + ), + rx.el.h2( + "Use Cases by Industry", + class_name="max-w-full w-full lg:text-3xl text-2xl text-center text-slate-12 font-semibold text-balance word-wrap break-words md:whitespace-pre", + ), + rx.el.p( + "See what’s built with Reflex.", + class_name="text-slate-9 text-sm font-medium text-center text-balance word-wrap break-words md:whitespace-pre", + ), + class_name="flex flex-col gap-4 items-center mx-auto w-full relative overflow-hidden", + ) + + +def pill_item(name: str, icon: str) -> rx.Component: + active_cn = "bg-slate-2 shadow-large text-slate-12" + is_active = selected_industry.value == name + return rx.el.div( + ui.icon(icon), + rx.el.span(name), + class_name=ui.cn( + "h-8 flex flex-row gap-2 items-center px-3 text-slate-9 font-medium text-sm cursor-pointer hover:bg-slate-2 transition-colors text-nowrap", + rx.cond(is_active, active_cn, ""), + ), + on_click=selected_industry.set_value(name), + ) + + +def pills() -> rx.Component: + return rx.el.div( + *[pill_item(name, icon) for name, icon in items], + class_name="flex flex-row items-center justify-start border border-slate-4 rounded-lg shadow-small bg-white-1 divide-x divide-slate-4 h-8 mt-10 flex-nowrap overflow-x-auto max-lg:w-full lg:justify-center max-lg:overflow-y-hidden", + ) + + +def gradients() -> rx.Component: + return rx.fragment( + rx.html( + """ + + + + + + + + + + + +""", + class_name="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none z-1 dark:hidden block", + ), + rx.html( + """ + + + + + + + + + + + +""", + class_name="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none z-2 hidden dark:block", + ), + ) + + +def app_card() -> rx.Component: + return rx.el.div( + gradients(), + rx.image( + src=rx.match( + selected_industry.value, + ("Analytics", "/case_studies/analytics_dashboard.webp"), + ("Finance", "/case_studies/bayesline_app.webp"), + ("E-commerce", "/case_studies/sellerx_app.webp"), + ("DevOps", "/case_studies/devops_app.webp"), + ("Databases", "/case_studies/admin_app.webp"), + ("AI Workflows", "/case_studies/ai_workflow.webp"), + "/case_studies/analytics_dashboard.webp", + ), + class_name="w-full lg:h-[33.05038rem] h-[24rem] object-cover rounded-2xl border border-slate-4 z-5 lg:p-4 bg-slate-1 object-top", + ), + class_name="w-full rounded-4xl lg:border border-slate-4 lg:backdrop-blur-[6px] lg:bg-slate-2/48 lg:p-4 flex relative z-1", + ) + + +def use_cases_section() -> rx.Component: + return rx.el.section( + rx.el.div( + rx.el.div( + numbers_pattern(side="left", class_name="left-0 top-0"), + numbers_pattern(side="right", class_name="right-0 top-0"), + header(), + pills(), + class_name="max-w-[64.19rem] w-full lg:border-x border-slate-3 flex flex-col items-center mx-auto pt-20 pb-10 relative overflow-hidden", + ), + app_card(), + class_name="relative max-w-[71.125rem] mx-auto flex flex-col items-center justify-center w-full", + ), + class_name="flex flex-col items-center mx-auto w-full max-w-[84.5rem]", + ) diff --git a/pcweb/pages/landing/views/video.py b/pcweb/pages/landing/views/video.py new file mode 100644 index 0000000000..88f0e256f1 --- /dev/null +++ b/pcweb/pages/landing/views/video.py @@ -0,0 +1,109 @@ +import reflex as rx +import reflex_ui as ui + +from pcweb.components.dialog import dialog +from pcweb.constants import DEMO_VIDEO_URL, REFLEX_BUILD_URL + + +def video_demo() -> rx.Component: + return rx.el.div( + dialog( + trigger=rx.el.div( + rx.el.div( + ui.icon("PlayIcon", class_name="text-slate-1 fill-slate-1 size-5"), + class_name="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 scale-100 z-[2] group-hover:scale-110 transition-transform duration-300 backdrop rounded-full bg-slate-10 size-10 flex items-center justify-center", + ), + rx.image( + "/landing/video/dark/video_demo_dark.webp", + class_name="object-cover size-full dark:block hidden scale-110", + ), + rx.image( + "/landing/video/light/video_demo_light.webp", + class_name="object-cover size-full dark:hidden block scale-110", + ), + rx.el.span( + class_name="inset-0 size-full absolute z-[1] bg-[#00000008] backdrop-blur-[0.1px] rounded-lg", + ), + class_name="shadow-small aspect-video rounded-xl overflow-hidden cursor-pointer relative isolate group border border-slate-4", + ), + content=rx.el.div( + rx.image( + "/logo.jpg", + class_name="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-10 h-10 z-[-1] rounded-md", + ), + rx.video( + src=DEMO_VIDEO_URL, + playing=True, + controls=False, + class_name="size-full z-[1]", + ), + class_name="relative isolate aspect-video bg-slate-1 rounded-2xl overflow-hidden", + ), + class_name="!max-w-[70rem] !p-0 !bg-transparent overflow-hidden", + ), + class_name="lg:p-10 p-8", + ) + + +def text() -> rx.Component: + return rx.el.div( + rx.el.h2( + "Build With Reflex. ", + rx.el.span( + "A Single Platform to Build With AI And Iterate in Python", + class_name="text-slate-10 lg:text-3xl text-2xl font-semibold", + ), + class_name="text-slate-12 lg:text-3xl text-2xl font-semibold max-w-[57rem]", + ), + ui.link( + render_=ui.button( + "Get Started with Reflex", + size="lg", + class_name="w-fit font-semibold mr-auto rounded-[0.625rem]", + ), + to=REFLEX_BUILD_URL, + target="_blank", + ), + class_name="flex flex-col gap-6 items-start justify-center lg:py-20 py-8 px-10", + ) + + +def video() -> rx.Component: + return rx.el.section( + rx.html( + """ + + + + + + + + + + + """, + class_name="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-1 pointer-events-none dark:hidden block", + ), + rx.html( + """ + + + + + + + + + + +""", + class_name="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none hidden dark:block", + ), + rx.el.div( + text(), + video_demo(), + class_name="grid grid-cols-1 lg:grid-cols-2 w-full rounded-2xl border border-slate-4 bg-white-1 z-5", + ), + class_name="mx-auto w-full max-w-[71.125rem] relative rounded-4xl border border-slate-4 backdrop-blur-[6px] bg-slate-2/48 p-4 flex z-1 max-lg:mb-6", + ) diff --git a/pcweb/pages/security/security.py b/pcweb/pages/security/security.py index 1e7405e4e8..9dcff517d3 100644 --- a/pcweb/pages/security/security.py +++ b/pcweb/pages/security/security.py @@ -8,7 +8,7 @@ @mainpage(path="/security", title="Security - Reflex") -def security(): +def security_page() -> rx.Component: """Main security page with modular sections.""" return rx.box( rx.box( diff --git a/pcweb/telemetry/pixels.py b/pcweb/telemetry/pixels.py index 2390fc9b0a..a6ca14c591 100644 --- a/pcweb/telemetry/pixels.py +++ b/pcweb/telemetry/pixels.py @@ -5,7 +5,6 @@ get_clearbit_trackers, get_common_room_trackers, get_google_analytics_trackers, - get_koala_trackers, get_posthog_trackers, get_rb2b_trackers, ) @@ -15,9 +14,6 @@ def get_pixel_website_trackers() -> list[rx.Component]: """Get the pixel trackers for the website.""" return [ get_common_room_trackers(site_id="b608b3c3-5dea-4365-b685-6b6635c7fda5"), - get_koala_trackers( - public_api_key="pk_733c6bff981543743bd2d53b4d7e95cc9b3f", - ), *get_google_analytics_trackers(tracking_id="G-4T7C8ZD9TR"), get_clearbit_trackers(public_key="pk_3d711a6e26de5ddb47443d8db170d506"), get_posthog_trackers( diff --git a/pcweb/views/bottom_section/get_started.py b/pcweb/views/bottom_section/get_started.py index 02837906ba..e7f0b7db53 100644 --- a/pcweb/views/bottom_section/get_started.py +++ b/pcweb/views/bottom_section/get_started.py @@ -1,6 +1,6 @@ import reflex as rx +import reflex_ui as ui -from pcweb.components.button import button from pcweb.components.hint import hint from pcweb.components.icons.icons import get_icon from pcweb.pages.docs import getting_started @@ -51,12 +51,14 @@ def code_block() -> rx.Component: "Need help? Learn how to use Reflex.", class_name="font-small text-slate-9", ), - rx.link( - button( + ui.link( + render_=ui.button( "Docs", - class_name="!h-10 !py-2 !px-[1.125rem] !rounded-[0.875rem]", + size="lg", + class_name="font-semibold text-lg", ), - href=getting_started.introduction.path, + to=getting_started.introduction.path, + target="_blank", ), class_name="flex flex-row justify-between items-center gap-2", ), diff --git a/rxconfig.py b/rxconfig.py index b827e18978..b786cde2e5 100644 --- a/rxconfig.py +++ b/rxconfig.py @@ -7,7 +7,6 @@ deploy_url="https://reflex.dev", frontend_packages=[ "chakra-react-select", - "@radix-ui/react-navigation-menu", "tailwindcss-animated", ], show_build_with_reflex=True, diff --git a/uv.lock b/uv.lock index 756ccd9c1f..62338f2a98 100644 --- a/uv.lock +++ b/uv.lock @@ -2344,7 +2344,7 @@ wheels = [ [[package]] name = "reflex-ui" version = "0.0.1" -source = { git = "https://github.com/reflex-dev/reflex-ui?rev=main#d22c986f16b80851d899ae91260d4f145f11131c" } +source = { git = "https://github.com/reflex-dev/reflex-ui?rev=main#58d3ec1fa0716e695981d78a96617480dde38e2c" } dependencies = [ { name = "reflex" }, ]