diff --git a/web/src/globals.css b/web/src/globals.css index 2349341..c770c15 100644 --- a/web/src/globals.css +++ b/web/src/globals.css @@ -369,6 +369,37 @@ CSS animations override inline styles, causing a visible position-flash. Animate the inner content element instead (each component does this itself). */ +/* ── Force dark palette for landing page island ── */ +/* Apply class="force-dark" to a wrapper to lock dark CSS vars regardless of :root.light */ +.force-dark { + --background: 240 6% 6%; + --foreground: 240 5% 93%; + --card: 240 4% 9%; + --card-foreground: 240 5% 93%; + --popover: 240 5% 10%; + --popover-foreground: 240 5% 93%; + --muted: 240 4% 14%; + --muted-foreground: 240 4% 54%; + --primary: 240 5% 93%; + --primary-foreground: 240 6% 6%; + --secondary: 240 4% 13%; + --secondary-foreground: 240 5% 93%; + --accent: 240 4% 14%; + --accent-foreground: 240 5% 93%; + --destructive: 3 74% 61%; + --destructive-foreground: 0 0% 98%; + --border: 240 4% 17%; + --input: 240 4% 11%; + --ring: 240 4% 42%; + --sidebar: 240 7% 7%; + --sidebar-foreground: 240 5% 93%; + --sidebar-border: 240 4% 13%; + --sidebar-accent: 240 4% 12%; + --sidebar-accent-foreground: 240 5% 93%; + --violet: 250 89% 70%; + color-scheme: dark; +} + /* ── Sonner toasts ── */ [data-sonner-toaster] { --toast-bg: hsl(var(--popover)); diff --git a/web/src/routes/index.tsx b/web/src/routes/index.tsx index e686049..96f0173 100644 --- a/web/src/routes/index.tsx +++ b/web/src/routes/index.tsx @@ -17,6 +17,11 @@ import { Activity, Terminal, Sparkles, + Filter, + FileText, + Inbox, + ImagePlay, + LayoutDashboard, } from "lucide-react"; import { LogoIcon } from "@/components/logo-icon"; @@ -39,113 +44,403 @@ function useGitHubStars() { }); } -function FeatureCard({ +// ── Sidebar nav pill ──────────────────────────────────────────────────────── +function SidebarItem({ icon: Icon, - title, - desc, + label, + active, }: { icon: React.ElementType; - title: string; - desc: string; + label: string; + active?: boolean; }) { return ( -
-
-
- -
-

- {title} -

-

{desc}

+
+ +
); } +// ── Rich dashboard mockup ─────────────────────────────────────────────────── function DashboardMockup() { + const stats = [ + { v: "12,481", label: "Contacts" }, + { v: "48,203", label: "Emails Sent" }, + { v: "42.1%", label: "Open Rate" }, + { v: "8.3%", label: "Click Rate" }, + { v: "24", label: "Unsubscribes" }, + ]; + + const events = [ + { type: "open", user: "john@acme.com", time: "2m ago" }, + { type: "click", user: "sarah@stripe.com", time: "4m ago" }, + { type: "open", user: "mark@vercel.com", time: "7m ago" }, + { type: "unsubscribe", user: "test@example.com", time: "12m ago" }, + { type: "open", user: "amy@linear.app", time: "15m ago" }, + { type: "click", user: "ben@figma.com", time: "19m ago" }, + ]; + + const broadcasts = [ + { name: "August Newsletter", sent: "48,203", openRate: "42.1%", status: "sent" }, + { name: "Re-engagement Q3", sent: "8,412", openRate: "31.4%", status: "sent" }, + { name: "Welcome Series", sent: "—", openRate: "—", status: "draft" }, + ]; + return ( -
- {/* Bottom glow */} -
- {/* Outer glow ring */} -
-
- {/* Window chrome */} -
-
-
-
-
-
+
+ {/* Bottom ambient glow */} +
+ {/* Top border highlight */} +
+ + {/* Browser chrome */} +
+ {/* Window titlebar */} +
+
+
+
+
+
+ app.openmail.win -
+
+ {/* App layout */} -
- {/* Sidebar */} -
-
- -
+
+ + {/* ── Sidebar ── */} +
+ {/* Logo + workspace */} +
+ +
+
+ + {/* Search */} +
+
+
+
+ + {/* Nav */} + + + + + + + + + + + {/* Spacer */} +
+ + {/* Bottom: user avatar placeholder */} +
+
+
-
- {["Dashboard", "Contacts", "Broadcasts", "Campaigns", "Templates"].map( - (item, i) => ( -
-
-
-
- ) - )}
- {/* Main content */} -
-
-
-
+ + {/* ── Main content ── */} +
+ {/* Top bar */} +
+
+
+
+
+ {/* Quick action chips */} +
+ {["New Broadcast", "Add Contact"].map((label) => ( +
+
+
+
+ ))} +
- {/* Stats row */} -
- {[ - { v: "12,481", label: "Contacts" }, - { v: "48,203", label: "Sent" }, - { v: "42.1%", label: "Open Rate" }, - { v: "8.3%", label: "Clicks" }, - { v: "24", label: "Unsubs" }, - ].map(({ v, label }) => ( -
-
{label}
-
{v}
+ +
+ {/* Left panel: stats + quick actions + activity */} +
+ + {/* Stat cards */} +
+ {stats.map(({ v, label }) => ( +
+
+ {label} +
+
+ {v} +
+
+ ))}
- ))} -
- {/* Activity feed */} -
-
-
-
-
-
+ + {/* Quick action chips */} +
+ {[ + { icon: Mail, label: "New Broadcast" }, + { icon: Users, label: "Add Contact" }, + { icon: Filter, label: "New Segment" }, + { icon: Zap, label: "Campaign" }, + ].map(({ icon: Icon, label }) => ( +
+ +
+
+ ))} +
+ + {/* Activity feed */} +
+ {/* Feed header */} +
+
+ +
+
+
+ + + + +
+
+
+ + {/* Event rows */} + {events.map(({ type, user, time }, i) => ( +
+ {/* Event type dot */} +
+ {/* Event label */} +
+ {/* User */} +
+ {/* Time */} +
+
+ ))}
- {["Email opened", "Link clicked", "Email opened", "Unsubscribed", "Email opened"].map((ev, i) => ( -
-
-
-
+ + {/* Right panel: recent broadcasts */} +
+ {/* Panel header */} +
+
+ +
+
+
- ))} + + {/* Broadcast rows */} + {broadcasts.map(({ name, sent, openRate, status }, i) => ( +
+ {/* Name */} +
+
+ {/* Status badge */} +
+
+
+
+ {/* Metrics row */} +
+
+
+
+
+
+
+
+
+
+ {/* Send progress bar (for sent broadcasts) */} + {status === "sent" && ( +
+
+
+ )} +
+ ))} + + {/* Segment count card */} +
+
+ +
+
+
+ {[ + { w: 28, c: "rgba(139,92,246,0.35)" }, + { w: 20, c: "rgba(96,165,250,0.3)" }, + { w: 24, c: "rgba(74,222,128,0.3)" }, + ].map(({ w, c }, i) => ( +
+
+
+
+ ))} +
+
+
@@ -154,27 +449,101 @@ function DashboardMockup() { ); } +// ── Feature card ───────────────────────────────────────────────────────────── +function FeatureCard({ + icon: Icon, + title, + desc, +}: { + icon: React.ElementType; + title: string; + desc: string; +}) { + return ( +
+
+
+ +
+

+ {title} +

+

{desc}

+
+ ); +} + +// ── Main landing page ───────────────────────────────────────────────────────── function LandingPage() { const { data: stars } = useGitHubStars(); return ( -
- +
{/* ── Ambient background glows ── */}
-
-
-
+
+
+
+ {/* ── Dot grid overlay ── */} +
+ {/* ── Nav ── */} -
+
- {/* Logo */}
- - + + OpenMail
@@ -184,15 +553,13 @@ function LandingPage() { {[ { label: "Features", href: "#features" }, { label: "AI Agents", href: "#ai" }, - { label: "Pricing", href: "#pricing" }, - { label: "Docs", href: "/docs" }, + { label: "Pricing", href: "#pricing" }, + { label: "Docs", href: "/docs" }, ].map(({ label, href }) => ( {label} @@ -205,9 +572,10 @@ function LandingPage() { href={GITHUB_REPO} target="_blank" rel="noreferrer" - className="hidden items-center gap-1.5 rounded-lg border border-white/[0.09] bg-white/[0.04] px-3 py-1.5 text-xs font-medium text-white/55 transition-colors duration-150 hover:bg-white/[0.08] hover:text-white/80 sm:flex tabular-nums" + className="hidden items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium text-white/45 transition-all duration-150 hover:text-white/75 sm:flex tabular-nums cursor-pointer" + style={{ border: "1px solid rgba(255,255,255,0.08)", background: "rgba(255,255,255,0.03)" }} > - + {stars != null ? stars.toLocaleString() : "Star"} {/* ── Hero ── */} -
- {/* Dot grid */} -
- +
{/* Badge */} -
- - +
+ + Open source · Free to self-host
{/* Headline */}

The open‑source{" "} -

+

Lifecycle email marketing built for product teams. Automate onboarding, retention, and re-engagement — without the enterprise price tag.

@@ -268,7 +635,8 @@ function LandingPage() {
Get started free @@ -277,13 +645,34 @@ function LandingPage() { href={GITHUB_REPO} target="_blank" rel="noreferrer" - className="flex items-center gap-2 rounded-xl border border-white/[0.1] bg-white/[0.04] px-6 py-2.5 text-[13px] font-semibold text-white/60 transition-all duration-150 hover:bg-white/[0.08] hover:text-white/85" + className="flex items-center gap-2 rounded-xl px-6 py-2.5 text-[13px] font-semibold transition-all duration-150 cursor-pointer" + style={{ + border: "1px solid rgba(255,255,255,0.09)", + background: "rgba(255,255,255,0.04)", + color: "rgba(255,255,255,0.55)", + }} > View on GitHub
+ {/* Trust signals */} +
+ {[ + { label: "ELv2 licensed" }, + { label: "No per-seat fees" }, + { label: "Self-hostable" }, + ].map(({ label }) => ( +
+ + + {label} + +
+ ))} +
+ {/* Dashboard mockup */}
@@ -291,41 +680,73 @@ function LandingPage() {

{/* ── Comparison ── */} -
+
-

+

Why OpenMail

Everything Customer.io has. At a fraction of the cost.

-
-
- Feature - OpenMail - Customer.io +
+ {/* Header row */} +
+ + Feature + + + OpenMail + + + Customer.io +
+ {[ - { feature: "Self-hosted option", us: true, them: false }, - { feature: "Full API access", us: true, them: "Limited" }, - { feature: "AI agent integration", us: true, them: false }, - { feature: "Real-time dashboards", us: true, them: false }, - { feature: "Per-seat pricing", us: "Never", them: "$1k–$10k+/mo" }, - { feature: "You own your data", us: true, them: false }, - { feature: "Open source", us: true, them: false }, + { feature: "Self-hosted option", us: true, them: false }, + { feature: "Full API access", us: true, them: "Limited" }, + { feature: "AI agent integration", us: true, them: false }, + { feature: "Real-time dashboards", us: true, them: false }, + { feature: "Per-seat pricing", us: "Never", them: "$1k–$10k+/mo" }, + { feature: "You own your data", us: true, them: false }, + { feature: "Open source", us: true, them: false }, ].map(({ feature, us, them }) => (
- {feature} + + {feature} +
{typeof us === "boolean" ? ( -
+
) : ( @@ -335,12 +756,14 @@ function LandingPage() {
{typeof them === "boolean" ? ( them ? ( - + ) : ( - + ) ) : ( - {them} + + {them} + )}
@@ -350,37 +773,90 @@ function LandingPage() {
{/* ── Feature grid ── */} -
-
-

+

+
+

Platform

Everything you need to run email at scale

- - - - - - - - - + + + + + + + + +
{/* ── AI section ── */} -
-
-
- +
+
+
-
+
AI-Native
@@ -390,7 +866,7 @@ function LandingPage() { > Let your AI agents run email -

+

OpenMail connects directly to Claude, GPT, Cursor, and any agent that supports the Model Context Protocol. Your AI can create campaigns, enroll contacts, send broadcasts, and pull @@ -403,81 +879,103 @@ function LandingPage() { "\"What's the open rate on our onboarding sequence?\"", ].map((q) => (

- - {q} + + › + + + {q} +
))}
-
+
- Connect in 30 seconds + Connect in 30 seconds
-
+
-                  {"{"}
+                  {"{"}
                   {"\n  "}
-                  "mcpServers"
-                  {": {"}
+                  "mcpServers"
+                  {": {"}
                   {"\n    "}
-                  "openmail"
-                  {": {"}
+                  "openmail"
+                  {": {"}
                   {"\n      "}
-                  "url"
-                  {": "}
-                  "https://mcp.openmail.win/mcp"
-                  {","}
+                  "url"
+                  {": "}
+                  "https://mcp.openmail.win/mcp"
+                  {","}
                   {"\n      "}
-                  "headers"
-                  {": {"}
+                  "headers"
+                  {": {"}
                   {"\n        "}
-                  "Authorization"
-                  {": "}
-                  "Bearer <your-api-key>"
+                  "Authorization"
+                  {": "}
+                  "Bearer <your-api-key>"
                   {"\n      "}
-                  {"}"}
+                  {"}"}
                   {"\n    "}
-                  {"}"}
+                  {"}"}
                   {"\n  "}
-                  {"}"}
+                  {"}"}
                   {"\n"}
-                  {"}"}
+                  {"}"}
                 
-

+

Works with Claude Desktop, Cursor, and any MCP-compatible agent.

-
{/* ── Pricing ── */} -
-

+

+

Pricing

Simple. Honest. No surprises.

-

+

No per-seat fees. No contact limits on self-hosted. No lock-in.

{/* Self-hosted */} -
-

+

+

Self-hosted

-

Free

-

Forever. No credit card required.

+

+ Free +

+

+ Forever. No credit card required. +

    {[ "Unlimited contacts", @@ -486,7 +984,7 @@ function LandingPage() { "Your infrastructure, your data", "Community support", ].map((f) => ( -
  • +
  • {f}
  • @@ -496,7 +994,11 @@ function LandingPage() { href={GITHUB_REPO} target="_blank" rel="noreferrer" - className="flex items-center justify-center gap-2 rounded-xl border border-white/[0.09] py-2.5 text-[13px] font-semibold text-white/55 transition-all duration-150 hover:bg-white/[0.05] hover:text-white/80 cursor-pointer" + className="flex items-center justify-center gap-2 rounded-xl py-2.5 text-[13px] font-semibold transition-all duration-150 cursor-pointer" + style={{ + border: "1px solid rgba(255,255,255,0.08)", + color: "rgba(255,255,255,0.5)", + }} > Clone on GitHub @@ -504,12 +1006,28 @@ function LandingPage() {
{/* Enterprise */} -
-

+