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 = """
"""
-discord_navbar = """
"""
+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" },
]