feat(ui): redesign landing page and update styling
Some checks failed
CI / Format (push) Successful in 22s
CI / Security Audit (push) Successful in 1m32s
CI / Tests (push) Successful in 3m32s
CI / Clippy (push) Successful in 2m29s
CI / Deploy (push) Successful in 2s
CI / E2E Tests (push) Failing after 31s

Overhaul landing page design with updated CSS, Tailwind config, and
i18n translations across all supported languages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-03-02 08:34:58 +01:00
parent c9c5970971
commit ca5da3c232
24 changed files with 5679 additions and 739 deletions

View File

@@ -49,10 +49,10 @@ const MAIN_CSS: Asset = asset!("/assets/main.css");
const TAILWIND_CSS: Asset = asset!("/assets/tailwind.css");
const MANIFEST: Asset = asset!("/assets/manifest.json");
/// Google Fonts URL for Inter (body) and Space Grotesk (headings).
/// Google Fonts URL for Literata (body) and Sora (headings).
const GOOGLE_FONTS: &str = "https://fonts.googleapis.com/css2?\
family=Inter:wght@400;500;600&\
family=Space+Grotesk:wght@500;600;700&\
family=Sora:wght@300;400;500;600;700;800&\
family=Literata:ital,opsz,wght@0,7..72,400;0,7..72,500;0,7..72,600;0,7..72,700;1,7..72,400&\
display=swap";
/// Root application component. Loads global assets and mounts the router.
@@ -85,14 +85,14 @@ pub fn App() -> Element {
src: "https://seggwat.com/static/widgets/v1/seggwat-feedback.js",
r#defer: true,
"data-project-key": "a04b8cf1-9177-42ce-8a7b-084f38b99799",
"data-button-color": "#6d85c6",
"data-button-color": "#8b5cf6",
"data-button-position": "right-side",
"data-enable-screenshots": "true",
}
document::Link { rel: "icon", href: FAVICON }
document::Link { rel: "manifest", href: MANIFEST }
document::Meta { name: "theme-color", content: "#4B3FE0" }
document::Meta { name: "theme-color", content: "#0c0a1d" }
document::Meta { name: "apple-mobile-web-app-capable", content: "yes" }
document::Meta {
name: "apple-mobile-web-app-status-bar-style",

View File

@@ -1,8 +1,5 @@
use dioxus::prelude::*;
use dioxus_free_icons::icons::bs_icons::{
BsArrowRight, BsGlobe2, BsKey, BsRobot, BsServer, BsShieldCheck,
};
use dioxus_free_icons::icons::fa_solid_icons::FaCubes;
use dioxus_free_icons::icons::bs_icons::{BsArrowRight, BsShieldCheck};
use dioxus_free_icons::Icon;
use crate::i18n::{t, Locale};
@@ -12,14 +9,15 @@ use crate::Route;
///
/// Displays a marketing-oriented page with hero section, feature grid,
/// how-it-works steps, and call-to-action banners. This page is accessible
/// without authentication.
/// without authentication. Uses the Glass Aurora design with glassmorphic
/// effects, aurora gradients, and centered hero layout.
#[component]
pub fn LandingPage() -> Element {
rsx! {
div { class: "landing",
LandingNav {}
HeroSection {}
SocialProof {}
TrustBar {}
FeaturesGrid {}
HowItWorks {}
CtaBanner {}
@@ -29,6 +27,7 @@ pub fn LandingPage() -> Element {
}
/// Sticky top navigation bar with logo, nav links, and CTA buttons.
/// Uses Glass Aurora glassmorphic nav with backdrop-filter blur.
#[component]
fn LandingNav() -> Element {
let locale = use_context::<Signal<Locale>>();
@@ -69,7 +68,8 @@ fn LandingNav() -> Element {
}
}
/// Hero section with headline, subtitle, and CTA buttons.
/// Hero section with pill badges, headline, subtitle, CTA buttons, and
/// a glass-preview stat panel. Centered layout per Glass Aurora design.
#[component]
fn HeroSection() -> Element {
let locale = use_context::<Signal<Locale>>();
@@ -78,7 +78,11 @@ fn HeroSection() -> Element {
rsx! {
section { class: "hero-section",
div { class: "hero-content",
div { class: "hero-badge badge badge-outline", {t(l, "landing.badge")} }
div { class: "hero-pills",
span { class: "pill accent", {t(l, "landing.pill_gdpr")} }
span { class: "pill", {t(l, "landing.pill_self_hosted")} }
span { class: "pill", {t(l, "landing.pill_eu")} }
}
h1 { class: "hero-title",
{t(l, "landing.hero_title_1")}
br {}
@@ -100,176 +104,26 @@ fn HeroSection() -> Element {
{t(l, "landing.learn_more")}
}
}
}
div { class: "hero-graphic",
// Abstract shield/network SVG motif
svg {
view_box: "0 0 400 400",
fill: "none",
width: "100%",
height: "100%",
// Gradient definitions
defs {
linearGradient {
id: "grad1",
x1: "0%",
y1: "0%",
x2: "100%",
y2: "100%",
stop { offset: "0%", stop_color: "#91a4d2" }
stop { offset: "100%", stop_color: "#6d85c6" }
}
linearGradient {
id: "grad2",
x1: "0%",
y1: "100%",
x2: "100%",
y2: "0%",
stop { offset: "0%", stop_color: "#f97066" }
stop { offset: "100%", stop_color: "#f9a066" }
}
radialGradient {
id: "glow",
cx: "50%",
cy: "50%",
r: "50%",
stop {
offset: "0%",
stop_color: "rgba(145,164,210,0.3)",
}
stop {
offset: "100%",
stop_color: "rgba(145,164,210,0)",
div { class: "preview-container",
div { class: "glass-preview",
div { class: "preview-stat",
div { class: "preview-stat-value", "5" }
div { class: "preview-stat-label",
{t(l, "landing.preview_models")}
}
}
div { class: "preview-stat",
div { class: "preview-stat-value", "847K" }
div { class: "preview-stat-label",
{t(l, "landing.preview_tokens")}
}
}
div { class: "preview-stat",
div { class: "preview-stat-value", "$47.82" }
div { class: "preview-stat-label",
{t(l, "landing.preview_spend")}
}
}
}
// Background glow
circle {
cx: "200",
cy: "200",
r: "180",
fill: "url(#glow)",
}
// Shield outline
path {
d: "M200 40 L340 110 L340 230 C340 300 270 360 200 380 \
C130 360 60 300 60 230 L60 110 Z",
stroke: "url(#grad1)",
stroke_width: "2",
fill: "none",
opacity: "0.6",
}
// Inner shield
path {
d: "M200 80 L310 135 L310 225 C310 280 255 330 200 345 \
C145 330 90 280 90 225 L90 135 Z",
stroke: "url(#grad1)",
stroke_width: "1.5",
fill: "rgba(145,164,210,0.05)",
opacity: "0.8",
}
// Network nodes
circle {
cx: "200",
cy: "180",
r: "8",
fill: "url(#grad1)",
}
circle {
cx: "150",
cy: "230",
r: "6",
fill: "url(#grad2)",
}
circle {
cx: "250",
cy: "230",
r: "6",
fill: "url(#grad2)",
}
circle {
cx: "200",
cy: "280",
r: "6",
fill: "url(#grad1)",
}
circle {
cx: "130",
cy: "170",
r: "4",
fill: "#91a4d2",
opacity: "0.6",
}
circle {
cx: "270",
cy: "170",
r: "4",
fill: "#91a4d2",
opacity: "0.6",
}
// Network connections
line {
x1: "200",
y1: "180",
x2: "150",
y2: "230",
stroke: "#91a4d2",
stroke_width: "1",
opacity: "0.4",
}
line {
x1: "200",
y1: "180",
x2: "250",
y2: "230",
stroke: "#91a4d2",
stroke_width: "1",
opacity: "0.4",
}
line {
x1: "150",
y1: "230",
x2: "200",
y2: "280",
stroke: "#91a4d2",
stroke_width: "1",
opacity: "0.4",
}
line {
x1: "250",
y1: "230",
x2: "200",
y2: "280",
stroke: "#91a4d2",
stroke_width: "1",
opacity: "0.4",
}
line {
x1: "200",
y1: "180",
x2: "130",
y2: "170",
stroke: "#91a4d2",
stroke_width: "1",
opacity: "0.3",
}
line {
x1: "200",
y1: "180",
x2: "270",
y2: "170",
stroke: "#91a4d2",
stroke_width: "1",
opacity: "0.3",
}
// Checkmark inside shield center
path {
d: "M180 200 L195 215 L225 185",
stroke: "url(#grad1)",
stroke_width: "3",
stroke_linecap: "round",
stroke_linejoin: "round",
fill: "none",
}
}
}
@@ -277,44 +131,36 @@ fn HeroSection() -> Element {
}
}
/// Social proof / trust indicator strip.
/// Trust bar with aurora dot indicators and stat labels.
/// Replaces the previous text-based social proof section.
#[component]
fn SocialProof() -> Element {
fn TrustBar() -> Element {
let locale = use_context::<Signal<Locale>>();
let l = *locale.read();
rsx! {
section { class: "social-proof",
p { class: "social-proof-text",
{t(l, "landing.social_proof")}
span { class: "social-proof-highlight", {t(l, "landing.data_sovereignty")} }
section { class: "trust-bar",
div { class: "trust-item",
div { class: "trust-dot" }
span { "100% " {t(l, "landing.on_premise")} }
}
div { class: "social-proof-stats",
div { class: "proof-stat",
span { class: "proof-stat-value", "100%" }
span { class: "proof-stat-label", {t(l, "landing.on_premise")} }
}
div { class: "proof-divider" }
div { class: "proof-stat",
span { class: "proof-stat-value", "GDPR" }
span { class: "proof-stat-label", {t(l, "landing.compliant")} }
}
div { class: "proof-divider" }
div { class: "proof-stat",
span { class: "proof-stat-value", "EU" }
span { class: "proof-stat-label", {t(l, "landing.data_residency")} }
}
div { class: "proof-divider" }
div { class: "proof-stat",
span { class: "proof-stat-value", "Zero" }
span { class: "proof-stat-label", {t(l, "landing.third_party")} }
}
div { class: "trust-item",
div { class: "trust-dot" }
span { "GDPR " {t(l, "landing.compliant")} }
}
div { class: "trust-item",
div { class: "trust-dot" }
span { "EU " {t(l, "landing.data_residency")} }
}
div { class: "trust-item",
div { class: "trust-dot" }
span { "Zero " {t(l, "landing.third_party")} }
}
}
}
}
/// Feature cards grid section.
/// Feature cards grid section. Uses gradient icon bars instead of SVG icons.
#[component]
fn FeaturesGrid() -> Element {
let locale = use_context::<Signal<Locale>>();
@@ -328,44 +174,26 @@ fn FeaturesGrid() -> Element {
}
div { class: "features-grid",
FeatureCard {
icon: rsx! {
Icon { icon: BsServer, width: 28, height: 28 }
},
title: t(l, "landing.feat_infra_title"),
description: t(l, "landing.feat_infra_desc"),
}
FeatureCard {
icon: rsx! {
Icon { icon: BsShieldCheck, width: 28, height: 28 }
},
title: t(l, "landing.feat_gdpr_title"),
description: t(l, "landing.feat_gdpr_desc"),
}
FeatureCard {
icon: rsx! {
Icon { icon: FaCubes, width: 28, height: 28 }
},
title: t(l, "landing.feat_llm_title"),
description: t(l, "landing.feat_llm_desc"),
}
FeatureCard {
icon: rsx! {
Icon { icon: BsRobot, width: 28, height: 28 }
},
title: t(l, "landing.feat_agent_title"),
description: t(l, "landing.feat_agent_desc"),
}
FeatureCard {
icon: rsx! {
Icon { icon: BsGlobe2, width: 28, height: 28 }
},
title: t(l, "landing.feat_mcp_title"),
description: t(l, "landing.feat_mcp_desc"),
}
FeatureCard {
icon: rsx! {
Icon { icon: BsKey, width: 28, height: 28 }
},
title: t(l, "landing.feat_api_title"),
description: t(l, "landing.feat_api_desc"),
}
@@ -374,18 +202,17 @@ fn FeaturesGrid() -> Element {
}
}
/// Individual feature card.
/// Individual feature card with a gradient icon bar accent.
///
/// # Arguments
///
/// * `icon` - The icon element to display
/// * `title` - Feature title (owned String from translation lookup)
/// * `description` - Feature description text (owned String from translation lookup)
#[component]
fn FeatureCard(icon: Element, title: String, description: String) -> Element {
fn FeatureCard(title: String, description: String) -> Element {
rsx! {
div { class: "card feature-card",
div { class: "feature-card-icon", {icon} }
div { class: "feature-icon-bar" }
h3 { class: "feature-card-title", "{title}" }
p { class: "feature-card-desc", "{description}" }
}
@@ -441,33 +268,35 @@ fn StepCard(number: &'static str, title: String, description: String) -> Element
}
}
/// Call-to-action banner before the footer.
/// Call-to-action banner wrapped in a glass box with aurora top border.
#[component]
fn CtaBanner() -> Element {
let locale = use_context::<Signal<Locale>>();
let l = *locale.read();
rsx! {
section { class: "cta-banner",
h2 { class: "cta-title", {t(l, "landing.cta_title")} }
p { class: "cta-subtitle",
{t(l, "landing.cta_subtitle")}
}
div { class: "cta-actions",
Link {
to: Route::Login {
redirect_url: "/dashboard".into(),
},
class: "btn btn-primary btn-lg",
{t(l, "landing.get_started_free")}
Icon { icon: BsArrowRight, width: 18, height: 18 }
section { class: "cta-section",
div { class: "cta-box",
h2 { class: "cta-title", {t(l, "landing.cta_title")} }
p { class: "cta-subtitle",
{t(l, "landing.cta_subtitle")}
}
Link {
to: Route::Login {
redirect_url: "/dashboard".into(),
},
class: "btn btn-outline btn-lg",
{t(l, "common.log_in")}
div { class: "cta-actions",
Link {
to: Route::Login {
redirect_url: "/dashboard".into(),
},
class: "btn btn-primary btn-lg",
{t(l, "landing.get_started_free")}
Icon { icon: BsArrowRight, width: 18, height: 18 }
}
Link {
to: Route::Login {
redirect_url: "/dashboard".into(),
},
class: "btn btn-outline btn-lg",
{t(l, "common.log_in")}
}
}
}
}
@@ -475,6 +304,7 @@ fn CtaBanner() -> Element {
}
/// Landing page footer with links and copyright.
/// Uses glass border-top styling per Glass Aurora design.
#[component]
fn LandingFooter() -> Element {
let locale = use_context::<Signal<Locale>>();