From 120bc07f6645ac8f5ca3ba0d73cfe31af42fa377 Mon Sep 17 00:00:00 2001 From: Maxim Date: Thu, 23 Apr 2026 16:50:35 +0300 Subject: [PATCH 01/25] feat: upgrade DocBlocks with new components and icon support --- src/components/proton/DocBlocks.tsx | 147 ++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 src/components/proton/DocBlocks.tsx diff --git a/src/components/proton/DocBlocks.tsx b/src/components/proton/DocBlocks.tsx new file mode 100644 index 000000000..5033539ba --- /dev/null +++ b/src/components/proton/DocBlocks.tsx @@ -0,0 +1,147 @@ +import Link from "next/link"; +import type { LucideIcon } from "lucide-react"; +import type { ReactNode } from "react"; + +export function DocH2({ + children, + icon: Icon, +}: { + children: ReactNode; + icon?: LucideIcon; +}) { + return ( +

+ {Icon ? ( + + ) : null} + {children} +

+ ); +} + +export function DocH3({ children }: { children: ReactNode }) { + return ( +

{children}

+ ); +} + +export function DocUl({ items }: { items: string[] }) { + return ( + + ); +} + +export function DocCallout({ + title, + children, +}: { + title?: string; + children: ReactNode; +}) { + return ( +
+ {title ? ( +

+ {title} +

+ ) : null} +
{children}
+
+ ); +} + +export function DocInlineLink({ + href, + children, +}: { + href: string; + children: ReactNode; +}) { + return ( + + {children} + + ); +} + +/** Pill badge used above DocH2 to label a section. */ +export function DocSectionLabel({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} + +/** 2-column responsive glass-card grid replacing DocUl on feature-heavy sections. */ +export function DocFeatureGrid({ + items, +}: { + items: { icon?: LucideIcon; text: string }[]; +}) { + return ( +
+ {items.map((item, i) => { + const Icon = item.icon; + return ( +
+ {Icon ? ( + + ) : ( + + )} + + {item.text} + +
+ ); + })} +
+ ); +} + +/** Horizontal stat highlight strip. */ +export function DocStat({ + items, +}: { + items: { value: string; label: string }[]; +}) { + return ( +
+ {items.map(({ value, label }) => ( +
+ + {value} + + + {label} + +
+ ))} +
+ ); +} From a9511b9826e343861ed04b8f6eb202d2471d10ab Mon Sep 17 00:00:00 2001 From: Maxim Date: Thu, 23 Apr 2026 16:52:22 +0300 Subject: [PATCH 02/25] fix: use item.text as stable key in DocFeatureGrid --- src/components/proton/DocBlocks.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/proton/DocBlocks.tsx b/src/components/proton/DocBlocks.tsx index 5033539ba..7839da2be 100644 --- a/src/components/proton/DocBlocks.tsx +++ b/src/components/proton/DocBlocks.tsx @@ -92,11 +92,11 @@ export function DocFeatureGrid({ }) { return (
- {items.map((item, i) => { + {items.map((item) => { const Icon = item.icon; return (
{Icon ? ( From 4fb3aaa42b98085ee3f90140263ffcf4e07556d7 Mon Sep 17 00:00:00 2001 From: Maxim Date: Thu, 23 Apr 2026 16:55:27 +0300 Subject: [PATCH 03/25] fix: reset blog/sign-in/download to dark OLED emerald theme --- src/app/(site)/blog/page.tsx | 91 ++++++++++++ src/app/(site)/download/page.tsx | 237 +++++++++++++++++++++++++++++++ src/app/(site)/sign-in/page.tsx | 90 ++++++++++++ 3 files changed, 418 insertions(+) create mode 100644 src/app/(site)/blog/page.tsx create mode 100644 src/app/(site)/download/page.tsx create mode 100644 src/app/(site)/sign-in/page.tsx diff --git a/src/app/(site)/blog/page.tsx b/src/app/(site)/blog/page.tsx new file mode 100644 index 000000000..5b12e5d2f --- /dev/null +++ b/src/app/(site)/blog/page.tsx @@ -0,0 +1,91 @@ +import { DocCallout, DocInlineLink } from "@/components/proton/DocBlocks"; +import { docMetadata } from "@/lib/page-meta"; +import { BLOG_POSTS } from "@/lib/blog-posts"; +import { ROUTES } from "@/lib/site-routes"; +import { syne } from "@/lib/proton-fonts"; +import { cn } from "@/lib/utils"; +import { ArrowLeft } from "lucide-react"; +import Link from "next/link"; + +export const metadata = docMetadata( + "Blog", + "/blog", + "Security guides, audits, and product deep dives.", +); + +export default function BlogIndexPage() { + const entries = Object.entries(BLOG_POSTS); + return ( +
+
+ + + +

+ Blog +

+

+ Longer articles live on their own URLs inside this demo. Topics cover + audits, open source, browser extensions, mobile privacy, and kill + switch behaviour — all scoped to the VPN product line. +

+ + +

+ Replace or extend posts in{" "} + + src/lib/blog-posts.ts + + . For MDX later, swap the dynamic route to read from the filesystem. +

+
+ +
    + {entries.map(([slug, post]) => ( +
  • + +

    + {post.title} +

    +

    + {post.excerpt} +

    + + Read article → + + +
  • + ))} +
+ +

+ More to explore:{" "} + Support + {" · "} + Features +

+
+ ); +} diff --git a/src/app/(site)/download/page.tsx b/src/app/(site)/download/page.tsx new file mode 100644 index 000000000..99f1649a1 --- /dev/null +++ b/src/app/(site)/download/page.tsx @@ -0,0 +1,237 @@ +import { docMetadata } from "@/lib/page-meta"; +import { ROUTES } from "@/lib/site-routes"; +import { syne } from "@/lib/proton-fonts"; +import { cn } from "@/lib/utils"; +import Link from "next/link"; +import type { LucideIcon } from "lucide-react"; +import { ArrowLeft, Flame, Globe, Laptop, Monitor, Smartphone, Tv } from "lucide-react"; + +const PRIMARY_PLATFORMS: { + id: string; + label: string; + Icon: LucideIcon; + blurb: string; +}[] = [ + { + id: "windows", + label: "Windows", + Icon: Monitor, + blurb: + "Full GUI for Windows 10 and 11 with tray controls, split tunneling, and kill switch. Ideal for laptops moving between office, home, and public Wi‑Fi.", + }, + { + id: "macos", + label: "macOS", + Icon: Laptop, + blurb: + "Universal build for Apple silicon and Intel. Integrates with menu bar, supports per-app exclusions, and follows macOS network extensions guidelines.", + }, + { + id: "android", + label: "Android", + Icon: Smartphone, + blurb: + "Google Play build plus optional APK for sideloading. Supports always-on VPN and per-app split tunneling on recent Android versions.", + }, + { + id: "ios", + label: "iPhone / iPad", + Icon: Smartphone, + blurb: + "Distributed via the App Store with on-demand rules compatible with iOS network extensions. Use the Shortcuts widget for quick connect.", + }, +]; + +const SECONDARY_PLATFORMS: { + id: string; + label: string; + Icon: LucideIcon; + blurb: string; +}[] = [ + { + id: "linux", + label: "Linux", + Icon: Laptop, + blurb: + "CLI for servers and headless boxes; graphical builds exist for major desktops. Package formats vary by distro — .deb, .rpm, or community packages.", + }, + { + id: "chrome", + label: "Chrome", + Icon: Globe, + blurb: + "Lightweight extension for Chromium — proxies browser tabs only. Pair with the desktop app for system-wide coverage.", + }, + { + id: "firefox", + label: "Firefox", + Icon: Globe, + blurb: + "Signed WebExtension with the same scope model as Chrome: web traffic inside Firefox uses the tunnel; other apps do not.", + }, + { + id: "chromebook", + label: "Chromebook", + Icon: Laptop, + blurb: + "Many Chromebooks run Android VPN clients; Linux (Crostini) has separate networking — confirm which mode matches your policy.", + }, + { + id: "apple-tv", + label: "Apple TV", + Icon: Tv, + blurb: + "tvOS clients focus on streaming reliability. Use Ethernet where possible for 4K throughput through the tunnel.", + }, + { + id: "android-tv", + label: "Android TV", + Icon: Tv, + blurb: + "Navigate with the D-pad, pin the app to the launcher, and set automatic connect for guest networks.", + }, + { + id: "fire-tv", + label: "Fire TV", + Icon: Flame, + blurb: + "Amazon Fire OS build from the Appstore. Side-loaded APKs skip automatic updates — prefer the store listing when available.", + }, +]; + +export const metadata = docMetadata( + "Download", + "/download", + "Download VPN apps — demo hub with in-app links.", +); + +export default function DownloadPage() { + return ( +
+
+ + + +

+ Download AuraVPN +

+
+

+ Pick your platform below. URLs use in-app anchors so the mega menu + can deep-link ( + + /download#windows + + ,{" "} + + #macos + + , etc.). +

+

+ After install, open the app to get your anonymous token. If you need + feature comparisons first, open{" "} + + Features + {" "} + or{" "} + + Pricing + + . +

+
+ + {/* Primary platforms */} +
+ {PRIMARY_PLATFORMS.map((p) => ( +
+
+
+ +
+

+ {p.label} +

+
+

+ {p.blurb} +

+

+ Production builds would live here. For now, return to{" "} + + Home + {" "} + or see{" "} + + Pricing + + . +

+
+ ))} +
+ + {/* Divider */} +
+
+ + More platforms + +
+
+ + {/* Secondary platforms */} +
+ {SECONDARY_PLATFORMS.map((p) => ( +
+
+
+ +
+

+ {p.label} +

+
+

+ {p.blurb} +

+
+ ))} +
+
+ ); +} diff --git a/src/app/(site)/sign-in/page.tsx b/src/app/(site)/sign-in/page.tsx new file mode 100644 index 000000000..46ddcbccb --- /dev/null +++ b/src/app/(site)/sign-in/page.tsx @@ -0,0 +1,90 @@ +import { docMetadata } from "@/lib/page-meta"; +import { syne } from "@/lib/proton-fonts"; +import { cn } from "@/lib/utils"; +import { Shield, KeyRound, ArrowRight } from "lucide-react"; + +export const metadata = docMetadata( + "Enter Token", + "/sign-in", + "Access your anonymous VPN session via token.", +); + +export default function SignInPage() { + return ( +
+
+ +
+
+
+ +
+
+ +

+ Anonymous Access +

+

+ No email, no password. Just enter the unique token generated by your + VPN app to manage your subscription. +

+ +
+
+
+ +
+
+ +
+ +
+

+ You can find your token in the app settings after installation. +

+
+ + +
+
+ +
+ Don't have a token yet?{" "} + + Download the app + {" "} + first. +
+
+
+ ); +} From 952bc9ddefce311096013e8d79527b60dbe29cb5 Mon Sep 17 00:00:00 2001 From: Maxim Date: Thu, 23 Apr 2026 16:58:27 +0300 Subject: [PATCH 04/25] fix: sign-in button type submit --- src/app/(site)/sign-in/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(site)/sign-in/page.tsx b/src/app/(site)/sign-in/page.tsx index 46ddcbccb..905b8d672 100644 --- a/src/app/(site)/sign-in/page.tsx +++ b/src/app/(site)/sign-in/page.tsx @@ -65,7 +65,7 @@ export default function SignInPage() {
+ +
+

+ {copied + ? "Command copied — follow these steps" + : "Click Install below, then follow these steps"} +

+

+ {cfg.title} +

+

+ {cfg.desc} +

+
+ +
+ {meta.steps.map((step, i) => ( + + ))} +
+ +
+ + +
+
+
+
+ + ); +} + +/* ═══════════════════════════════════════════════════════════ + STEP ROW — key caps like the reference +═══════════════════════════════════════════════════════════ */ +function StepRow({ + step, + index, + active, +}: { + step: StepSpec; + index: number; + active: boolean; +}) { + return ( +
+
+ {step.keys.map((key, i) => ( +
+ {i > 0 && ( + + + + + )} + 1}> + {typeof key === "string" ? ( + + {key} + + ) : ( + + )} + +
+ ))} +
+ +
+

+ {step.label} +

+

+ {step.desc} +

+
+ +
+ {index + 1} +
+
+ ); +} + +function WinLogo({ active }: { active: boolean }) { + return ( + + + + ); +} + +function KeyCap({ + children, + accent, + wide, +}: { + children: ReactNode; + accent?: boolean; + wide?: boolean; +}) { + return ( + + {children} + + ); +} + +/* ═══════════════════════════════════════════════════════════ + LEFT PANEL — preview + OS switcher +═══════════════════════════════════════════════════════════ */ +function LeftPanel({ + cfg, + meta, + copied, + platform, + onPick, +}: { + cfg: (typeof VARIANT_CONFIG)[CtaVariant]; + meta: PlatformMeta; + copied: boolean; + platform: Platform; + onPick: (p: Platform) => void; +}) { + const PlatIcon = meta.Icon; + + return ( +