Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed assets/landing/patterns/dark/numbers-img.webp
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file removed assets/landing/patterns/dark/numbers-right-img.webp
Binary file not shown.
Binary file not shown.
Binary file removed assets/landing/patterns/light/numbers-img.webp
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file not shown.
129 changes: 81 additions & 48 deletions pcweb/components/hosting_banner.py
Original file line number Diff line number Diff line change
@@ -1,72 +1,105 @@
import datetime

import reflex as rx


def glow() -> rx.Component:
return rx.box(
class_name="absolute w-[120rem] h-[23.75rem] flex-shrink-0 rounded-[120rem] left-1/2 -translate-x-1/2 z-[0] top-[-16rem] dark:[background-image:radial-gradient(50%_50%_at_50%_50%,_rgba(58,45,118,1)_0%,_rgba(21,22,24,0.00)_100%)] [background-image:radial-gradient(50%_50%_at_50%_50%,_rgba(235,228,255,0.95)_0%,_rgba(252,252,253,0.00)_100%)]",
class_name="absolute w-[120rem] h-[23.75rem] flex-shrink-0 rounded-[120rem] left-1/2 -translate-x-1/2 z-[0] top-[-16rem] dark:[background-image:radial-gradient(50%_50%_at_50%_50%,_rgba(58,45,118,1)_0%,_rgba(21,22,24,0.00)_100%)] [background-image:radial-gradient(50%_50%_at_50%_50%,_rgba(235,228,255,0.95)_0%,_rgba(252,252,253,0.00)_100%)] saturate-200 dark:saturate-100 group-hover:saturate-300 transition-[saturate] dark:group-hover:saturate-100",
)


POST_LINK = "https://www.linkedin.com/posts/y-combinator_reflex-is-an-ai-app-builder-for-creating-activity-7386793491064008704-sMLz/?utm_source=social_share_send&utm_medium=member_desktop_web&rcm=ACoAAB5u_e0B4BJ2Y79KMlho0J5wpWD5Kz9McKw"
POST_LINK = "https://www.producthunt.com/products/reflex-5?launch=reflex-7"

# October 25, 2025 12:01 AM PDT (UTC-7) = October 25, 2025 07:01 AM UTC
DEADLINE = datetime.datetime(2025, 10, 25, 7, 1, tzinfo=datetime.UTC)
Comment thread
carlosabadia marked this conversation as resolved.


class HostingBannerState(rx.State):
show_banner: bool = True
show_banner: rx.Field[bool] = rx.field(False)
force_hide_banner: rx.Field[bool] = rx.field(False)

@rx.event
def hide_banner(self):
self.show_banner = False
self.force_hide_banner = True

@rx.event
def check_deadline(self):
if datetime.datetime.now(datetime.UTC) < DEADLINE:
self.show_banner = True


def timer():
remove_negative_sign = rx.vars.function.ArgsFunctionOperation.create(
args_names=("t",),
return_expr=rx.vars.sequence.string_replace_operation(
rx.Var("t").to(str), "-", ""
),
)

return rx.el.div(
rx.moment(
date=DEADLINE,
duration_from_now=True,
format="DD[d] HH[h] mm[m] ss[s]",
custom_attrs={"filter": remove_negative_sign},
interval=1000,
class_name="font-medium text-sm",
),
class_name="items-center gap-1 z-[1] bg-orange-4 border border-orange-5 rounded-md px-1.5 py-0.5 text-orange-11 font-medium text-sm md:flex hidden",
)


def hosting_banner() -> rx.Component:
return rx.cond(
HostingBannerState.show_banner,
rx.hstack(
rx.link(
rx.box(
return rx.el.div(
rx.cond(
HostingBannerState.show_banner & ~HostingBannerState.force_hide_banner,
rx.hstack(
rx.el.a(
rx.box(
# Header text with responsive spans
rx.el.span(
"New",
class_name="inline-flex items-center font-medium px-1.5 h-5 rounded-md text-xs bg-violet-9 text-slate-1 z-[1]",
),
rx.text(
"Reflex Build – ", # noqa: RUF001
# Descriptive text: hidden on small, inline on md+
rx.box(
# Header text with responsive spans
rx.el.span(
"The first AI agent to build internal Python enterprise apps",
class_name="hidden md:inline-block text-slate-12 font-medium text-sm",
"Launch",
class_name="items-center font-medium px-1.5 h-5 rounded-md text-xs bg-violet-9 text-slate-1 z-[1] inline-flex",
),
# Mobile CTA: inline on small, hidden on md+
rx.el.span(
"Early Access",
class_name="inline-block md:hidden text-slate-12 font-medium text-sm",
rx.text(
rx.el.span(
"We're live on Product Hunt - ",
class_name="inline-block text-slate-12 font-semibold text-sm",
),
# Mobile CTA: inline on small, hidden on md+
rx.el.span(
" 50% ",
class_name="text-slate-12 font-semibold text-sm underline decoration-slate-11",
),
rx.el.span(
" launch",
class_name="text-slate-12 font-semibold text-sm underline decoration-slate-11 hidden md:inline-block",
),
rx.el.span(
" discount!",
class_name="text-slate-12 font-semibold text-sm underline decoration-slate-11",
),
class_name="text-slate-12 font-semibold text-sm z-[1]",
),
class_name="text-slate-12 font-semibold text-sm z-[1]",
),
# Standalone CTA button: hidden on small, inline on md+
rx.el.button(
"Get Access",
class_name=(
"hidden md:inline-block "
"text-green-11 h-[1.65rem] rounded-md bg-green-4 "
"px-1.5 text-sm font-semibold z-[1] items-center "
"justify-center shrink-0 border border-green-5 hover:bg-green-5 transition-colors"
),
),
class_name="flex items-center gap-4",
)
# Standalone CTA button: hidden on small, inline on md+
timer(),
class_name="flex items-center md:gap-3.5 gap-2",
)
),
glow(),
to=POST_LINK,
target="_blank",
),
glow(),
href=POST_LINK,
underline="none",
is_external=True,
),
rx.icon(
"x",
on_click=HostingBannerState.hide_banner,
size=16,
class_name="cursor-pointer hover:!text-slate-11 transition-color !text-slate-9 absolute right-4 z-10",
rx.icon(
"x",
on_click=HostingBannerState.hide_banner,
size=16,
class_name="cursor-pointer hover:!text-slate-11 transition-color !text-slate-9 absolute right-4 z-10",
),
class_name="px-4 lg:px-6 w-screen h-auto lg:h-[3.5rem] shadow-[inset_0_-1px_0_0_var(--c-slate-3)] flex items-center justify-between md:justify-center bg-slate-1 flex-row gap-4 overflow-hidden relative lg:py-0 py-2 max-w-full group",
),
class_name="px-4 lg:px-6 w-screen h-auto lg:h-[3.5rem] shadow-[inset_0_-1px_0_0_var(--c-slate-3)] flex items-center justify-between md:justify-center bg-slate-1 flex-row gap-4 overflow-hidden relative lg:py-0 py-2 max-w-full",
),
on_mount=HostingBannerState.check_deadline,
)
118 changes: 61 additions & 57 deletions pcweb/components/numbers_pattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,99 +2,103 @@
import reflex_ui as ui


def ellipses(side: str = "left"):
def _ellipses(side: str, reverse_animation: bool = False) -> rx.Component:
"""Create animated ellipses for the pattern effect."""
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"
),
animation_class = "[animation-direction:reverse]" if reverse_animation else ""
common_class = (
f"absolute bg-violet-11 dark:bg-[#534a87] blur-[10px] {animation_class}"
)


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 = ""
side: str = "left", reverse: 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"
"""Numbers pattern with static background and masked animated ellipses.

Matches Figma design structure with layered gradients.

Args:
side: Position side ("left" or "right")
reverse: Reverse the ellipse animation direction
class_name: Additional CSS classes
"""
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 = f"landing/patterns/{light_dark_path}/numbers-pattern.webp"
Comment thread
carlosabadia marked this conversation as resolved.

# Determine if we need to flip: right side XOR reverse
# - right side normally flips
# - reverse inverts the flip behavior
is_flipped = (side == "right") != reverse

# Background image style
image_style = {"opacity": rx.color_mode_cond("1", "0.3")}
if is_flipped:
image_style |= {"transform": "scaleX(-1)"}

# Gradient masks
vertical_gradient = "linear-gradient(360deg, rgba(0, 0, 0, 0) 0%, #000000 12%, #000000 88%, rgba(0, 0, 0, 0) 100%)"
# Angled gradient - angle changes based on reverse
gradient_angle = "105deg" if reverse else "280deg"
angled_gradient = f"linear-gradient({gradient_angle}, rgba(0, 0, 0, 0) 18.13%, rgba(0, 0, 0, 0.88) 66.72%, rgba(0, 0, 0, 0) 85.62%)"

# Container mask combining both gradients
container_mask_style = {
"mask_image": f"{angled_gradient}, {vertical_gradient}",
"webkit_mask_image": f"{angled_gradient}, {vertical_gradient}",
"mask_composite": "intersect",
"webkit_mask_composite": "intersect",
}
src = image_sources.get(
(side, reversed), f"/landing/patterns/{light_dark_path}/numbers-img.webp"
)
if is_flipped:
container_mask_style["transform"] = "scaleX(-1)"

mask_style = {
# Image mask style for the ellipses layer
ellipses_mask_style = {
"mask_image": f"url({src})",
"mask_size": "100% 100%",
"mask_size": "cover",
"mask_repeat": "no-repeat",
"mask_position": "center",
"webkit_mask_image": f"url({src})",
"webkit_mask_size": "100% 100%",
"webkit_mask_size": "cover",
"webkit_mask_repeat": "no-repeat",
"webkit_mask_position": "center",
}
if is_flipped:
ellipses_mask_style["transform"] = "scaleX(-1)"

return rx.el.div(
# Layer 1: Background pattern image
rx.image(
src=src,
class_name="pointer-events-none w-full h-full absolute inset-0 object-cover",
style=image_style,
),
# Layer 2: Masked animated ellipses
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",
_ellipses(side=side, reverse_animation=reverse),
class_name="absolute inset-0 w-full h-full",
style=ellipses_mask_style,
),
class_name=ui.cn(
f"absolute {position_class} pointer-events-none overflow-hidden z-[-1] lg:w-[234px] w-[180px] h-auto",
f"absolute {position_class} pointer-events-none z-[-1] lg:w-[234px] w-[180px] h-full bottom-0",
class_name,
),
style=container_mask_style,
)
4 changes: 2 additions & 2 deletions pcweb/pages/landing/views/ai_bento.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ def bento_cards() -> rx.Component:
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"),
numbers_pattern(side="left", reverse=True, class_name="left-0 top-0"),
numbers_pattern(side="right", reverse=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",
),
Expand Down
4 changes: 2 additions & 2 deletions pcweb/pages/landing/views/enterprise_social.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ def enterprise_card(image: str, name: str, stat: str, text: str) -> rx.Component
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"),
numbers_pattern(side="left", reverse=True, class_name="left-0 top-0"),
numbers_pattern(side="right", reverse=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",
),
Expand Down
4 changes: 2 additions & 2 deletions pcweb/pages/landing/views/final_cta.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ def content() -> rx.Component:

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"),
numbers_pattern(side="left", reverse=True, class_name="left-0 top-0"),
numbers_pattern(side="right", reverse=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",
)
10 changes: 8 additions & 2 deletions pcweb/pages/landing/views/hero.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,14 @@ def prompt_box() -> rx.Component:

def hero() -> rx.Component:
return rx.el.section(
numbers_pattern(side="left", class_name="lg:top-[65px] top-[45px]"),
numbers_pattern(side="right", class_name="lg:top-[65px] top-[45px]"),
numbers_pattern(
side="left",
class_name="lg:top-[65px] top-[45px] lg:h-[calc(100%-65px)] h-[calc(100%-45px)]",
),
numbers_pattern(
side="right",
class_name="lg:top-[65px] top-[45px] lg:h-[calc(100%-65px)] h-[calc(100%-45px)]",
),
rx.el.h1(
"""Build From Prompt to
Production App, In Seconds""",
Expand Down
4 changes: 2 additions & 2 deletions pcweb/pages/landing/views/os_bento.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ def header() -> rx.Component:
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"),
numbers_pattern(side="left", reverse=True, class_name="left-0 top-0"),
numbers_pattern(side="right", reverse=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",
),
Expand Down
4 changes: 2 additions & 2 deletions pcweb/pages/landing/views/social_stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ def stat(icon: str, text: str) -> rx.Component:

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"),
numbers_pattern(side="left", reverse=True, class_name="left-0 top-0"),
numbers_pattern(side="right", reverse=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"),
Expand Down