From e92c0d5d91e1d3cf9822cf0d4836d837673ca112 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Fri, 31 Jul 2026 01:43:52 +0500 Subject: [PATCH 01/46] Redesign the www landing page with an Aurora workbench layout. Agent-focused hero copy, Command/Prompt install switcher, Geist-only type stack, and a quieter product demo frame. Co-authored-by: Cursor --- .hallmark/log.json | 11 + .hallmark/preflight.json | 10 + apps/www/app/(home)/aurora.css | 690 ++++++++++++++++++ apps/www/app/(home)/home-nav.tsx | 38 + apps/www/app/(home)/layout.tsx | 60 +- apps/www/app/(home)/page.tsx | 141 ++-- apps/www/app/globals.css | 5 + apps/www/app/layout.tsx | 15 +- apps/www/app/styles/base.css | 2 +- apps/www/components/page/index.ts | 1 + apps/www/components/page/install-panel.tsx | 127 ++++ apps/www/components/page/logo.test.tsx | 6 + apps/www/components/page/logo.tsx | 13 +- .../www/components/page/quickstart-button.tsx | 20 +- apps/www/components/page/video-demo.tsx | 40 +- apps/www/hooks/use-copy-command.ts | 9 +- apps/www/package.json | 1 + apps/www/tokens.css | 62 ++ pnpm-lock.yaml | 16 +- tokens.css | 62 ++ 20 files changed, 1137 insertions(+), 192 deletions(-) create mode 100644 .hallmark/log.json create mode 100644 .hallmark/preflight.json create mode 100644 apps/www/app/(home)/aurora.css create mode 100644 apps/www/app/(home)/home-nav.tsx create mode 100644 apps/www/components/page/install-panel.tsx create mode 100644 apps/www/tokens.css create mode 100644 tokens.css diff --git a/.hallmark/log.json b/.hallmark/log.json new file mode 100644 index 000000000..a62e844cb --- /dev/null +++ b/.hallmark/log.json @@ -0,0 +1,11 @@ +[ + { + "date": "2026-07-31", + "macrostructure": "Workbench", + "theme": "Aurora", + "enrichment": "product-demo", + "brief": "ArkEnv landing · Aurora redesign", + "nav": "N5", + "footer": "Ft5" + } +] diff --git a/.hallmark/preflight.json b/.hallmark/preflight.json new file mode 100644 index 000000000..3f7fc2c16 --- /dev/null +++ b/.hallmark/preflight.json @@ -0,0 +1,10 @@ +{ + "scannedAt": "2026-07-31", + "fontStack": "Geist + JetBrains Mono (next/font)", + "palette": "fumadocs ocean + custom light/dark theme CSS", + "motion": "CSS keyframes only (marquee, spin) — no framer-motion", + "spacing": "Tailwind 4 defaults", + "framework": "Next.js app router (apps/www)", + "preserve": ["Geist display", "JetBrains Mono", "fumadocs docs theming", "home route ownership", "product copy"], + "introduce": ["Aurora tokens scoped to home", "Sentient body", "Workbench macrostructure", "N5/Ft5", "microinteraction discipline"] +} diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css new file mode 100644 index 000000000..3251d4a5d --- /dev/null +++ b/apps/www/app/(home)/aurora.css @@ -0,0 +1,690 @@ +/* Hallmark · genre: atmospheric · macrostructure: Workbench · tone: atmospheric + * theme: Aurora · anchor hue: 200 · nav: N5 · footer: Ft5 · enrichment: product-demo + * axes: dark / geist-sans + geist-mono + geist-pixel-grid / cool-cyan + * fonts: Geist · Geist Mono · Geist Pixel Grid + * Pre-emit critique: P4 H4 E4 S4 R4 V5 + */ + +@import "../../tokens.css"; + +.home-aurora { + --background: var(--color-paper); + --foreground: var(--color-ink); + --primary: var(--color-accent); + --primary-foreground: var(--color-accent-ink); + --muted-foreground: var(--color-muted); + --border: var(--color-rule); + --ring: var(--color-focus); + --fd-primary: var(--color-accent); + --color-fd-primary: var(--color-accent); + + position: relative; + isolation: isolate; + min-height: 100dvh; + color: var(--color-ink); + background-color: var(--color-paper); + background-image: + radial-gradient( + ellipse 42% 36% at 12% 18%, + var(--color-bloom-a), + transparent 70% + ), + radial-gradient( + ellipse 38% 32% at 88% 8%, + var(--color-bloom-b), + transparent 72% + ); + background-attachment: fixed; + font-family: var(--font-body); + overflow-x: clip; +} + +.home-aurora :is(h1, h2, h3) { + font-style: normal; + font-weight: 600; + letter-spacing: -0.03em; + font-family: var(--font-display); + overflow-wrap: anywhere; + min-width: 0; +} + +.home-aurora__wordmark { + display: inline-flex; + align-items: center; + flex-shrink: 0; + font-family: var(--font-body); + font-weight: 500; + font-style: normal; + letter-spacing: -0.01em; + color: var(--color-ink); + text-decoration: none; + white-space: nowrap; +} + +.home-aurora__wordmark span { + font-family: var(--font-body); + font-weight: 500; + white-space: nowrap; +} + +/* N5 · Floating pill */ +.home-aurora__nav { + position: fixed; + inset: var(--space-md) auto auto 50%; + transform: translateX(-50%); + z-index: 40; + display: inline-flex; + align-items: center; + gap: var(--space-sm); + max-width: calc(100vw - 2 * var(--page-gutter)); + padding: 0.4rem 0.55rem 0.4rem 0.85rem; + background: color-mix(in oklch, var(--color-paper) 72%, transparent); + backdrop-filter: blur(14px) saturate(120%); + border: var(--rule-hair) solid var(--color-rule); + border-radius: var(--radius-pill); + box-shadow: var(--shadow-nav); +} + +.home-aurora__nav-links { + display: none; + list-style: none; + margin: 0; + padding: 0; + gap: var(--space-xs); +} + +@media (min-width: 40rem) { + .home-aurora__nav-links { + display: flex; + } +} + +.home-aurora__nav-links a { + display: inline-flex; + align-items: center; + padding: 0.35rem 0.65rem; + border-radius: var(--radius-pill); + color: var(--color-ink-2); + font-family: var(--font-display); + font-size: var(--text-sm); + font-weight: 500; + text-decoration: none; + white-space: nowrap; + transition: + color var(--dur-micro) var(--ease-out), + background-color var(--dur-micro) var(--ease-out); +} + +.home-aurora__nav-links a:hover { + color: var(--color-ink); + background: color-mix(in oklch, var(--color-paper-3) 70%, transparent); +} + +.home-aurora__nav-links a:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; +} + +.home-aurora__nav-actions { + display: flex; + align-items: center; + gap: var(--space-2xs); + margin-left: auto; +} + +/* Keep the floating pill content-sized — icon search only */ +.home-aurora__nav [data-search-full] { + display: none !important; +} + +.home-aurora__nav [data-search] { + display: inline-flex !important; + color: var(--color-ink-2); +} + +.home-aurora__nav [data-search]:hover { + color: var(--color-ink); +} + +.home-aurora__nav [data-search]:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; +} + +.home-aurora__nav-cta { + display: none; + align-items: center; + padding: 0.4rem 0.85rem; + border-radius: var(--radius-pill); + background: var(--color-accent); + color: var(--color-accent-ink); + font-family: var(--font-display); + font-size: var(--text-sm); + font-weight: 600; + text-decoration: none; + white-space: nowrap; + transition: + transform var(--dur-micro) var(--ease-out), + opacity var(--dur-micro) var(--ease-out); +} + +@media (min-width: 40rem) { + .home-aurora__nav-cta { + display: inline-flex; + } +} + +.home-aurora__nav-cta:hover { + opacity: 0.92; +} + +.home-aurora__nav-cta:active { + transform: translateY(1px); +} + +.home-aurora__nav-cta:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; +} + +.home-aurora__nav-cta[aria-disabled="true"], +.home-aurora__nav-cta:disabled { + opacity: 0.45; + pointer-events: none; +} + +.home-aurora__shell { + width: min(72rem, 100%); + margin-inline: auto; + padding: calc(var(--space-3xl) + var(--space-sm)) var(--page-gutter) + var(--space-2xl); +} + +/* Workbench · hanging intro */ +.home-aurora__intro { + display: grid; + gap: var(--space-md); + max-width: 48rem; + padding-block: var(--space-xl) var(--space-lg); +} + +.home-aurora__badge { + justify-self: start; +} + +.home-aurora__badge a { + border-color: color-mix(in oklch, var(--color-accent) 35%, transparent) !important; + background: color-mix(in oklch, var(--color-accent) 10%, transparent) !important; + color: var(--color-ink) !important; + box-shadow: none !important; +} + +.home-aurora__intro h1, +.home-aurora__tagline { + margin: 0; + max-width: 15ch; + font-family: var(--font-display); + font-size: clamp(2.75rem, 6.5vw + 1rem, 5rem); + line-height: 1.05; + letter-spacing: -0.035em; + color: var(--color-ink); + font-weight: 500; + overflow-wrap: normal; +} + +.home-aurora__digital { + font-family: var(--font-geist-pixel-grid), "Geist Pixel Grid", ui-monospace, monospace; + font-weight: 500; + letter-spacing: 0.02em; + color: inherit; + white-space: nowrap; +} + +.home-aurora__digital-accent { + color: var(--color-accent); +} + +.home-aurora__summary { + margin: 0; + max-width: 42ch; + font-family: var(--font-body); + font-size: clamp(1.05rem, 1vw + 0.8rem, 1.3rem); + line-height: 1.45; + color: var(--color-ink-2); + font-weight: 400; +} + +.home-aurora__install-row { + display: grid; + gap: var(--space-sm); + margin-top: var(--space-2xs); + width: 100%; + max-width: 42rem; +} + +/* Install · Command / Prompt tabs */ +.home-aurora__install { + display: grid; + gap: var(--space-xs); + width: 100%; +} + +.home-aurora__install-tabs { + display: flex; + align-items: center; + justify-content: flex-end; + gap: var(--space-xs); + width: 100%; + padding: 0; + border: 0; + background: transparent; +} + +.home-aurora__install-tab { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.15rem 0.1rem; + border: 0; + border-radius: 0; + background: transparent; + color: var(--color-muted); + font-family: var(--font-display); + font-size: var(--text-xs); + font-weight: 500; + letter-spacing: 0.02em; + cursor: pointer; + white-space: nowrap; + opacity: 0.75; + transition: + color var(--dur-micro) var(--ease-out), + opacity var(--dur-micro) var(--ease-out); +} + +.home-aurora__install-tab:hover { + color: var(--color-ink-2); + opacity: 1; +} + +.home-aurora__install-tab[data-active="true"] { + background: transparent; + color: var(--color-ink-2); + opacity: 1; + box-shadow: inset 0 -1px 0 color-mix(in oklch, var(--color-accent) 55%, transparent); +} + +.home-aurora__install-tab:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; +} + +.home-aurora__install-panel { + min-width: 0; +} + +.home-aurora__install-copy { + display: flex; + align-items: center; + gap: var(--space-sm); + width: 100%; + min-height: 3.5rem; + padding: 0.85rem 0.85rem 0.85rem 1.1rem; + border: var(--rule-hair) solid var(--color-rule); + border-radius: var(--radius-frame); + background: color-mix(in oklch, var(--color-paper-2) 88%, transparent); + color: var(--color-ink); + text-align: left; + cursor: pointer; + transition: + border-color var(--dur-short) var(--ease-out), + background-color var(--dur-short) var(--ease-out), + transform var(--dur-micro) var(--ease-out); +} + +.home-aurora__install-copy:hover { + border-color: color-mix(in oklch, var(--color-accent) 45%, var(--color-rule)); + background: var(--color-paper-3); +} + +.home-aurora__install-copy:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; +} + +.home-aurora__install-copy:active { + transform: translateY(1px); +} + +.home-aurora__install-copy[data-state="success"] { + border-color: var(--color-success); +} + +.home-aurora__install-icon { + width: 1.25rem; + height: 1.25rem; + flex-shrink: 0; + color: var(--color-accent); + opacity: 0.9; + margin-top: 0.15rem; +} + +.home-aurora__install-code { + flex: 1; + min-width: 0; + font-family: var(--font-outlier); + font-size: clamp(1rem, 0.4vw + 0.95rem, 1.2rem); + line-height: 1.3; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.home-aurora__install-label { + flex: 1; + min-width: 0; + font-family: var(--font-display); + font-size: clamp(1rem, 0.4vw + 0.95rem, 1.2rem); + font-weight: 400; + line-height: 1.3; + white-space: nowrap; +} + +.home-aurora__install-prompt-token { + color: var(--color-accent); +} + +.home-aurora__install-copy-affordance { + display: inline-flex; + align-items: center; + gap: 0.4rem; + flex-shrink: 0; + padding-left: var(--space-sm); + border-left: var(--rule-hair) solid var(--color-rule); + color: var(--color-muted); + align-self: stretch; +} + +/* C3 · Typographic primary CTA (nav / legacy) */ +.home-aurora__cta { + display: inline-flex; + align-items: center; + gap: 0.4rem; + font-family: var(--font-display); + font-size: var(--text-md); + font-weight: 600; + color: var(--color-ink); + text-decoration: none; + border-bottom: var(--rule-hair) solid var(--color-accent); + padding-block: 0.15rem; + transition: + color var(--dur-micro) var(--ease-out), + border-color var(--dur-micro) var(--ease-out), + opacity var(--dur-micro) var(--ease-out); +} + +.home-aurora__cta:hover { + color: var(--color-accent); +} + +.home-aurora__cta:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 3px; + border-radius: 2px; +} + +.home-aurora__cta:active { + opacity: 0.85; +} + +.home-aurora__cta[aria-disabled="true"], +.home-aurora__cta:disabled { + opacity: 0.4; + pointer-events: none; +} + +.home-aurora__cta[data-state="loading"] { + opacity: 0.7; +} + +.home-aurora__cta[data-state="error"] { + border-color: var(--color-danger); + color: var(--color-danger-ink); +} + +.home-aurora__cta[data-state="success"] { + border-color: var(--color-success); + color: var(--color-success); +} + +.home-aurora__bench { + margin-top: var(--space-2xl); + display: grid; + gap: var(--space-md); +} + +.home-aurora__bench-head { + display: grid; + gap: var(--space-2xs); + max-width: 36rem; +} + +.home-aurora__bench-head h2 { + margin: 0; + font-size: var(--text-display-s); + line-height: 1.1; + color: var(--color-ink); +} + +.home-aurora__bench-head p { + margin: 0; + color: var(--color-ink-2); + font-size: var(--text-md); + line-height: 1.5; + max-width: 42ch; +} + +.home-aurora__frame { + position: relative; + width: 100%; + max-width: 56rem; + margin-inline: auto; + border: var(--rule-hair) solid var(--color-rule); + border-radius: var(--radius-frame); + overflow: hidden; + background: var(--color-paper-2); + box-shadow: var(--shadow-frame); +} + +.home-aurora__frame-button { + display: block; + position: relative; + width: 100%; + padding: 0; + border: 0; + background: transparent; + cursor: pointer; + text-align: left; + color: inherit; +} + +.home-aurora__frame-media { + position: absolute; + inset: 0; +} + +.home-aurora__frame-button:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 3px; +} + +.home-aurora__frame-button:hover .home-aurora__frame-hint { + opacity: 1; + transform: translateY(0); +} + +.home-aurora__frame-hint { + position: absolute; + inset: auto 0 1.25rem 0; + display: flex; + justify-content: center; + pointer-events: none; + opacity: 0; + transform: translateY(0.35rem); + transition: + opacity var(--dur-short) var(--ease-out), + transform var(--dur-short) var(--ease-out); +} + +.home-aurora__frame-hint span { + display: inline-flex; + align-items: center; + gap: 0.4rem; + padding: 0.45rem 0.85rem; + border-radius: var(--radius-pill); + background: color-mix(in oklch, var(--color-paper) 88%, transparent); + border: var(--rule-hair) solid var(--color-rule); + color: var(--color-ink); + font-family: var(--font-display); + font-size: var(--text-sm); + font-weight: 500; + backdrop-filter: blur(8px); +} + +.home-aurora__caption { + margin: 0; + padding: var(--space-sm) var(--space-md); + border-top: var(--rule-hair) solid var(--color-rule); + color: var(--color-ink-2); + font-size: var(--text-sm); + line-height: 1.45; +} + +/* Ft5 · Statement */ +.home-aurora__footer { + margin-top: var(--space-3xl); + padding: var(--space-2xl) 0 var(--space-xl); + display: grid; + gap: var(--space-lg); +} + +.home-aurora__footer-line { + margin: 0; + max-width: 28ch; + font-family: var(--font-display); + font-size: clamp(1.75rem, 5vw, 3rem); + font-weight: 600; + font-style: normal; + line-height: 1.05; + letter-spacing: -0.02em; + color: var(--color-ink); +} + +.home-aurora__footer-line a { + color: inherit; + text-decoration: underline; + text-decoration-color: color-mix(in oklch, var(--color-accent) 70%, transparent); + text-underline-offset: 0.18em; + transition: text-decoration-color var(--dur-short) var(--ease-out); +} + +.home-aurora__footer-line a:hover { + text-decoration-color: var(--color-accent); +} + +.home-aurora__footer-line a:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 3px; +} + +.home-aurora__footer-meta { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: baseline; + gap: var(--space-sm); + padding-block-start: var(--space-sm); + border-top: var(--rule-hair) solid var(--color-rule); + color: var(--color-muted); + font-family: var(--font-display); + font-size: var(--text-sm); +} + +@media (prefers-reduced-motion: reduce) { + .home-aurora__frame-hint, + .home-aurora__cta, + .home-aurora__nav-cta, + .home-aurora__nav-links a, + .home-aurora__install-tab, + .home-aurora__install-copy { + transition: opacity 120ms linear; + } + + .home-aurora__frame-button:hover .home-aurora__frame-hint { + transform: none; + } + + .home-aurora__install-copy:active { + transform: none; + } +} + +/* Compatibility rails — restyle existing component under Aurora */ +.home-aurora .home-aurora__rails-host { + display: flex; + flex-direction: column; + gap: var(--space-xs); + width: 100%; + max-width: 42rem; + margin: var(--space-sm) 0 0; + padding: 0; + overflow: hidden; +} + +.home-aurora .home-aurora__rails-host span.text-\[10px\] { + color: var(--color-muted); + font-family: var(--font-outlier); +} + +.home-aurora .home-aurora__rails-host a { + background: color-mix(in oklch, var(--color-paper-2) 80%, transparent); + border-color: var(--color-rule); + color: var(--color-ink-2); +} + +.home-aurora .home-aurora__rails-host a:hover { + color: var(--color-ink); + border-color: color-mix(in oklch, var(--color-accent) 40%, var(--color-rule)); + background: var(--color-paper-3); +} + +/* StarUs mobile — quiet under Aurora */ +.home-aurora .home-aurora__install-row a[href*="github.com"] { + background: color-mix(in oklch, var(--color-paper-2) 90%, transparent) !important; + border: var(--rule-hair) solid var(--color-rule) !important; + color: var(--color-ink) !important; + border-radius: var(--radius-pill) !important; + box-shadow: none !important; +} + +.home-aurora .home-aurora__install-row a[href*="github.com"] .text-yellow-600, +.home-aurora .home-aurora__install-row a[href*="github.com"] .dark\:text-yellow-400, +.home-aurora .home-aurora__install-row a[href*="github.com"] .text-yellow-700, +.home-aurora .home-aurora__install-row a[href*="github.com"] .dark\:text-yellow-300 { + color: var(--color-accent) !important; +} + +/* Logo accent */ +.home-aurora .stroke-blue-500, +.home-aurora .dark\:stroke-blue-400, +.home-aurora .fill-blue-500, +.home-aurora .dark\:fill-blue-400 { + stroke: var(--color-accent); + fill: var(--color-accent); + color: var(--color-accent); +} + +.home-aurora .fill-blue-500, +.home-aurora .dark\:fill-blue-400 { + fill: var(--color-accent); +} + diff --git a/apps/www/app/(home)/home-nav.tsx b/apps/www/app/(home)/home-nav.tsx new file mode 100644 index 000000000..74c277817 --- /dev/null +++ b/apps/www/app/(home)/home-nav.tsx @@ -0,0 +1,38 @@ +"use client"; + +import { HeaderGithubLink } from "~/components/page/header-github-link"; +import { Logo } from "~/components/page/logo"; +import { SearchToggle } from "~/components/ui/search-toggle"; +import { ThemeToggle } from "~/components/ui/theme-toggle"; + +export function HomeNav() { + return ( + + ); +} diff --git a/apps/www/app/(home)/layout.tsx b/apps/www/app/(home)/layout.tsx index e16e90cfa..2285aacea 100644 --- a/apps/www/app/(home)/layout.tsx +++ b/apps/www/app/(home)/layout.tsx @@ -1,49 +1,25 @@ -import { Header } from "@arkenv/fumadocs-ui/components"; +import "./aurora.css"; import { HomeLayout } from "fumadocs-ui/layouts/home"; import type { CSSProperties, ReactNode } from "react"; -import { HeaderGithubLink } from "~/components/page/header-github-link"; -import { Logo } from "~/components/page/logo"; -import { SearchToggle } from "~/components/ui/search-toggle"; -import { ThemeToggle } from "~/components/ui/theme-toggle"; +import { HomeNav } from "./home-nav"; export default function Layout({ children }: { children: ReactNode }) { return ( - } - links={[ - { text: "Documentation", url: "/docs/arkenv" }, - { - text: "Roadmap", - url: "https://github.com/yamcodes/arkenv/issues/683", - }, - ]} - actions={[ - , -
- -
, - , - ]} - menuActions={[]} - menuSocialActions={[ - , - ]} - /> - ), - }} - > - {children} -
+
+ + {children} + +
); } diff --git a/apps/www/app/(home)/page.tsx b/apps/www/app/(home)/page.tsx index 2f9c39dcb..0b7c76e43 100644 --- a/apps/www/app/(home)/page.tsx +++ b/apps/www/app/(home)/page.tsx @@ -1,113 +1,74 @@ import { ExternalLink } from "@arkenv/fumadocs-ui/components"; import type { Metadata } from "next"; -import Image from "next/image"; import { AnnouncementBadge } from "~/components/announcement-badge"; -import { HeroGradientOverlay } from "~/components/hero-gradient-overlay"; -import { HeroVisual } from "~/components/hero-visual"; import { - CLICommand, CompatibilityRails, - QuickstartButton, + InstallPanel, StarUsButton, VideoDemo, } from "~/components/page"; export const metadata: Metadata = { title: "ArkEnv", - description: "Environment variable validation from editor to runtime", + description: "Environment variables toolkit built for agents", }; export default function HomePage() { return ( -
- {/* Top gradient overlay for dark mode - SVG version */} - - -
-
-
- - Flat layout for Next.js - -
-

-
- Better{" "} - - typesafe - {" "} - than sorry -
-
- Environment variable validation from editor to runtime -
-

- -
- - - -
+
+
+
+ + Flat layout for Next.js +
-
- +

+ Environment variables toolkit{" "} + + built for{" "} + agents + + . +

+

+ Validate, parse, and transform environment variables with your + favorite library. Works anywhere. +

+ +
+ +
-
+
-
+
+
+

Try it live

+

Open the playground from the demo below.

+
-
- - {/* Homepage Footer with Fade Gradient and Stylized Separator */} -
- {/* Background fade gradient */} - +
+

+ Proud part of the{" "} + + ArkType ecosystem + + . +

+
+ ArkEnv + + Released under the MIT License · Copyright © 2025-present Yam + Borodetsky + +
+
); } diff --git a/apps/www/app/globals.css b/apps/www/app/globals.css index cb7119a61..2e3173455 100644 --- a/apps/www/app/globals.css +++ b/apps/www/app/globals.css @@ -18,6 +18,11 @@ background-image: none !important; } +html, +body { + overflow-x: clip; +} + @theme { @keyframes spin-slow { from { diff --git a/apps/www/app/layout.tsx b/apps/www/app/layout.tsx index 0f53b0da8..235eb9bd6 100644 --- a/apps/www/app/layout.tsx +++ b/apps/www/app/layout.tsx @@ -2,20 +2,13 @@ import "./globals.css"; import { Analytics } from "@vercel/analytics/next"; import { SpeedInsights } from "@vercel/speed-insights/next"; import { RootProvider } from "fumadocs-ui/provider/next"; +import { GeistMono } from "geist/font/mono"; +import { GeistPixelGrid } from "geist/font/pixel"; +import { GeistSans } from "geist/font/sans"; import type { Metadata } from "next"; -import { Geist, JetBrains_Mono } from "next/font/google"; import type { ReactNode } from "react"; import { Toaster } from "~/components/ui/toaster"; -const geist = Geist({ - subsets: ["latin"], -}); - -const mono = JetBrains_Mono({ - variable: "--font-mono", - subsets: ["latin"], -}); - export const metadata: Metadata = { metadataBase: new URL("https://arkenv.js.org"), icons: { @@ -40,7 +33,7 @@ export default function Layout({ children }: { children: ReactNode }) { return ( diff --git a/apps/www/app/styles/base.css b/apps/www/app/styles/base.css index b1dba246e..8157f568e 100644 --- a/apps/www/app/styles/base.css +++ b/apps/www/app/styles/base.css @@ -11,7 +11,7 @@ code, pre { - font-family: var(--font-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-family: var(--font-geist-mono), var(--font-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } button:not(:disabled) { diff --git a/apps/www/components/page/index.ts b/apps/www/components/page/index.ts index 935e63958..8e24970b4 100644 --- a/apps/www/components/page/index.ts +++ b/apps/www/components/page/index.ts @@ -1,6 +1,7 @@ export * from "./cli-command"; export * from "./compatibility-rails"; export * from "./copy-button"; +export * from "./install-panel"; export * from "./logo"; export * from "./quickstart-button"; export * from "./star-us-button"; diff --git a/apps/www/components/page/install-panel.tsx b/apps/www/components/page/install-panel.tsx new file mode 100644 index 000000000..41ad54bc6 --- /dev/null +++ b/apps/www/components/page/install-panel.tsx @@ -0,0 +1,127 @@ +"use client"; + +import { Check, Copy, Sparkles, Terminal } from "lucide-react"; +import { useId, useState } from "react"; +import { useCopyCommand } from "~/hooks/use-copy-command"; + +const INSTALL_COMMAND = "npx arkenv@latest init"; + +const INSTALL_PROMPT = `Set up ArkEnv with \`npx arkenv@latest init --agent\`. Install any missing dependencies, wire the env schema into the app entry, start the app, and tell me when validation works from editor to runtime.`; + +type Tab = "command" | "prompt"; + +export function InstallPanel() { + const [tab, setTab] = useState("command"); + const baseId = useId(); + const commandPanelId = `${baseId}-command`; + const promptPanelId = `${baseId}-prompt`; + const commandTabId = `${baseId}-tab-command`; + const promptTabId = `${baseId}-tab-prompt`; + + const commandCopy = useCopyCommand(INSTALL_COMMAND, { + successDescription: "Command copied to clipboard!", + }); + const promptCopy = useCopyCommand(INSTALL_PROMPT, { + successDescription: "Prompt copied to clipboard!", + }); + + return ( +
+
+ + +
+ + {tab === "command" ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+ ); +} diff --git a/apps/www/components/page/logo.test.tsx b/apps/www/components/page/logo.test.tsx index 285a9e2a4..1bcb36837 100644 --- a/apps/www/components/page/logo.test.tsx +++ b/apps/www/components/page/logo.test.tsx @@ -14,6 +14,12 @@ describe("Logo", () => { expect(logoText).toBeInTheDocument(); }); + it("hides the wordmark when wordmark is false", () => { + render(); + + expect(screen.queryByText("ArkEnv")).not.toBeInTheDocument(); + }); + it("accepts custom className", () => { render(); diff --git a/apps/www/components/page/logo.tsx b/apps/www/components/page/logo.tsx index 2df64bdcf..dc9753d7d 100644 --- a/apps/www/components/page/logo.tsx +++ b/apps/www/components/page/logo.tsx @@ -4,9 +4,16 @@ import { cn } from "~/lib/utils"; * Logo component * * @param className - Optional className for custom styling + * @param wordmark - When false, renders the icon only (default true) * @returns Logo component */ -export function Logo({ className }: { className?: string }) { +export function Logo({ + className, + wordmark = true, +}: { + className?: string; + wordmark?: boolean; +}) { return (
- ArkEnv + {wordmark ? ( + ArkEnv + ) : null}
); } diff --git a/apps/www/components/page/quickstart-button.tsx b/apps/www/components/page/quickstart-button.tsx index 02212caa9..20ebe03ef 100644 --- a/apps/www/components/page/quickstart-button.tsx +++ b/apps/www/components/page/quickstart-button.tsx @@ -1,22 +1,10 @@ "use client"; -import { Button } from "~/components/ui/button"; - export function QuickstartButton() { return ( - + + Get Started + + ); } diff --git a/apps/www/components/page/video-demo.tsx b/apps/www/components/page/video-demo.tsx index 741889bfd..02bcdfac7 100644 --- a/apps/www/components/page/video-demo.tsx +++ b/apps/www/components/page/video-demo.tsx @@ -6,7 +6,6 @@ import BackgroundVideo from "next-video/background-video"; import { useState } from "react"; import demo from "~/videos/demo.mp4"; -// Actual video dimensions const WIDTH = 2048; const HEIGHT = 1672; @@ -14,8 +13,8 @@ const getAspectRatio = (width: number, height: number) => `${width} / ${height}`; /** - * VideoDemo component displays an interactive video demonstration with a browser-style frame. - * Clicking the video opens the demo in StackBlitz. Falls back to a GIF if video fails to load. + * Product demo framed for the home workbench — no redrawn browser chrome. + * Click opens the StackBlitz playground. */ export function VideoDemo() { const [videoError, setVideoError] = useState(false); @@ -31,34 +30,21 @@ export function VideoDemo() { }; return ( -
+
-
+
+ basic · index.ts — click to open the live playground +
+ ); } diff --git a/apps/www/hooks/use-copy-command.ts b/apps/www/hooks/use-copy-command.ts index 6cd69b919..b180e5ec8 100644 --- a/apps/www/hooks/use-copy-command.ts +++ b/apps/www/hooks/use-copy-command.ts @@ -11,9 +11,14 @@ import { useToast } from "./use-toast"; * @param command - The string command to be copied. * @returns An object containing the `copied` state and the `copy` function. */ -export function useCopyCommand(command: string) { +export function useCopyCommand( + command: string, + options?: { successDescription?: string }, +) { const [copied, setCopied] = useState(false); const { toast } = useToast(); + const successDescription = + options?.successDescription ?? "Command copied to clipboard!"; const copy = async () => { try { @@ -21,7 +26,7 @@ export function useCopyCommand(command: string) { setCopied(true); setTimeout(() => setCopied(false), 2000); toast({ - description: "Command copied to clipboard!", + description: successDescription, duration: 2000, }); } catch (error) { diff --git a/apps/www/package.json b/apps/www/package.json index 3acc691b9..e030e8cea 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -44,6 +44,7 @@ "fumadocs-twoslash": "3.2.0", "fumadocs-typescript": "^5.2.6", "fumadocs-ui": "16.8.7", + "geist": "^1.7.2", "import-in-the-middle": "2.0.6", "lucide-react": "1.14.0", "next": "catalog:", diff --git a/apps/www/tokens.css b/apps/www/tokens.css new file mode 100644 index 000000000..10d7ffd34 --- /dev/null +++ b/apps/www/tokens.css @@ -0,0 +1,62 @@ +/* Hallmark · genre: atmospheric · macrostructure: Workbench · tone: atmospheric + * theme: Aurora · anchor hue: 200 · nav: N5 · footer: Ft5 · enrichment: product-demo + * axes: dark / geist-sans + geist-mono + geist-pixel-grid / cool-cyan + * fonts: Geist · Geist Mono · Geist Pixel Grid + */ + +:root { + --color-paper: oklch(13% 0.018 200); + --color-paper-2: oklch(17% 0.02 200); + --color-paper-3: oklch(21% 0.022 200); + --color-ink: oklch(95% 0.01 200); + --color-ink-2: oklch(78% 0.02 200); + --color-muted: oklch(62% 0.025 200); + --color-rule: oklch(30% 0.02 200); + --color-accent: oklch(74% 0.13 195); + --color-accent-ink: oklch(14% 0.02 200); + --color-focus: oklch(78% 0.12 195); + --color-bloom-a: oklch(52% 0.11 195 / 0.32); + --color-bloom-b: oklch(42% 0.09 220 / 0.22); + --color-success: oklch(72% 0.14 155); + --color-danger: oklch(65% 0.18 25); + --color-danger-ink: oklch(78% 0.14 25); + + --font-display: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; + --font-body: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; + --font-outlier: var(--font-geist-mono), ui-monospace, monospace; + --font-mono: var(--font-geist-mono), ui-monospace, monospace; + /* --font-geist-pixel-grid is provided by geist/font/pixel (GeistPixelGrid) */ + + --space-3xs: 0.25rem; + --space-2xs: 0.5rem; + --space-xs: 0.75rem; + --space-sm: 1rem; + --space-md: 1.5rem; + --space-lg: 2rem; + --space-xl: 3rem; + --space-2xl: 4.5rem; + --space-3xl: 7rem; + --page-gutter: clamp(1rem, 4vw, 2.5rem); + + --text-xs: 0.75rem; + --text-sm: 0.875rem; + --text-md: 1.125rem; + --text-lg: 1.375rem; + --text-xl: 1.75rem; + --text-2xl: 2.25rem; + --text-display-s: clamp(2rem, 4vw + 0.5rem, 3rem); + --text-display: clamp(2.5rem, 5vw + 0.75rem, 4.25rem); + + --ease-out: cubic-bezier(0.16, 1, 0.3, 1); + --ease-in: cubic-bezier(0.7, 0, 0.84, 0); + --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); + --dur-micro: 120ms; + --dur-short: 220ms; + --dur-long: 420ms; + + --rule-hair: 1px; + --radius-pill: 999px; + --radius-frame: 0.75rem; + --shadow-frame: 0 24px 60px -28px oklch(0% 0 0 / 0.55); + --shadow-nav: 0 8px 24px -12px oklch(0% 0 0 / 0.35); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5cab2766..e33becbf2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -652,6 +652,9 @@ importers: fumadocs-ui: specifier: 16.8.7 version: 16.8.7(@tailwindcss/oxide@4.2.4)(@types/mdx@2.0.13)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.8.7(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.14.0(react@19.2.5))(next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.4.1))(next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.4) + geist: + specifier: ^1.7.2 + version: 1.7.2(next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) import-in-the-middle: specifier: 2.0.6 version: 2.0.6 @@ -7664,6 +7667,11 @@ packages: fzf@0.5.2: resolution: {integrity: sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==} + geist@1.7.2: + resolution: {integrity: sha512-Gu5lDFa3pLRyoBlBPf0QIFHVdWAnpco7fS1bJm41jyLPFoguBgiubseUN2oLXMgqZ7uxAxDoXcHMhCY/fOTTgg==} + peerDependencies: + next: '>=13.2.0' + gemoji@8.1.0: resolution: {integrity: sha512-HA4Gx59dw2+tn+UAa7XEV4ufUKI4fH1KgcbenVA9YKSj1QJTT0xh5Mwv5HMFNN3l2OtUe3ZIfuRwSyZS5pLIWw==} @@ -16627,7 +16635,7 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.1(@noble/hashes@1.8.0))(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.1(@noble/hashes@1.8.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4)) '@vitest/expect@4.1.5': dependencies: @@ -16689,7 +16697,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.1(@noble/hashes@1.8.0))(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.1(@noble/hashes@1.8.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4)) '@vitest/utils@4.1.5': dependencies: @@ -18567,6 +18575,10 @@ snapshots: fzf@0.5.2: {} + geist@1.7.2(next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)): + dependencies: + next: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.59.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + gemoji@8.1.0: {} gensync@1.0.0-beta.2: {} diff --git a/tokens.css b/tokens.css new file mode 100644 index 000000000..10d7ffd34 --- /dev/null +++ b/tokens.css @@ -0,0 +1,62 @@ +/* Hallmark · genre: atmospheric · macrostructure: Workbench · tone: atmospheric + * theme: Aurora · anchor hue: 200 · nav: N5 · footer: Ft5 · enrichment: product-demo + * axes: dark / geist-sans + geist-mono + geist-pixel-grid / cool-cyan + * fonts: Geist · Geist Mono · Geist Pixel Grid + */ + +:root { + --color-paper: oklch(13% 0.018 200); + --color-paper-2: oklch(17% 0.02 200); + --color-paper-3: oklch(21% 0.022 200); + --color-ink: oklch(95% 0.01 200); + --color-ink-2: oklch(78% 0.02 200); + --color-muted: oklch(62% 0.025 200); + --color-rule: oklch(30% 0.02 200); + --color-accent: oklch(74% 0.13 195); + --color-accent-ink: oklch(14% 0.02 200); + --color-focus: oklch(78% 0.12 195); + --color-bloom-a: oklch(52% 0.11 195 / 0.32); + --color-bloom-b: oklch(42% 0.09 220 / 0.22); + --color-success: oklch(72% 0.14 155); + --color-danger: oklch(65% 0.18 25); + --color-danger-ink: oklch(78% 0.14 25); + + --font-display: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; + --font-body: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; + --font-outlier: var(--font-geist-mono), ui-monospace, monospace; + --font-mono: var(--font-geist-mono), ui-monospace, monospace; + /* --font-geist-pixel-grid is provided by geist/font/pixel (GeistPixelGrid) */ + + --space-3xs: 0.25rem; + --space-2xs: 0.5rem; + --space-xs: 0.75rem; + --space-sm: 1rem; + --space-md: 1.5rem; + --space-lg: 2rem; + --space-xl: 3rem; + --space-2xl: 4.5rem; + --space-3xl: 7rem; + --page-gutter: clamp(1rem, 4vw, 2.5rem); + + --text-xs: 0.75rem; + --text-sm: 0.875rem; + --text-md: 1.125rem; + --text-lg: 1.375rem; + --text-xl: 1.75rem; + --text-2xl: 2.25rem; + --text-display-s: clamp(2rem, 4vw + 0.5rem, 3rem); + --text-display: clamp(2.5rem, 5vw + 0.75rem, 4.25rem); + + --ease-out: cubic-bezier(0.16, 1, 0.3, 1); + --ease-in: cubic-bezier(0.7, 0, 0.84, 0); + --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); + --dur-micro: 120ms; + --dur-short: 220ms; + --dur-long: 420ms; + + --rule-hair: 1px; + --radius-pill: 999px; + --radius-frame: 0.75rem; + --shadow-frame: 0 24px 60px -28px oklch(0% 0 0 / 0.55); + --shadow-nav: 0 8px 24px -12px oklch(0% 0 0 / 0.35); +} From 962d0a793e4a0d6cec31de9a69962b7fd6cc47c6 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 20:48:37 +0000 Subject: [PATCH 02/46] [autofix.ci] apply automated fixes --- .hallmark/log.json | 18 +++++++------- .hallmark/preflight.json | 28 +++++++++++++++------- apps/www/app/(home)/aurora.css | 16 ++----------- apps/www/components/page/install-panel.tsx | 7 +++--- 4 files changed, 34 insertions(+), 35 deletions(-) diff --git a/.hallmark/log.json b/.hallmark/log.json index a62e844cb..9516ccef3 100644 --- a/.hallmark/log.json +++ b/.hallmark/log.json @@ -1,11 +1,11 @@ [ - { - "date": "2026-07-31", - "macrostructure": "Workbench", - "theme": "Aurora", - "enrichment": "product-demo", - "brief": "ArkEnv landing · Aurora redesign", - "nav": "N5", - "footer": "Ft5" - } + { + "date": "2026-07-31", + "macrostructure": "Workbench", + "theme": "Aurora", + "enrichment": "product-demo", + "brief": "ArkEnv landing · Aurora redesign", + "nav": "N5", + "footer": "Ft5" + } ] diff --git a/.hallmark/preflight.json b/.hallmark/preflight.json index 3f7fc2c16..ab4fe12b8 100644 --- a/.hallmark/preflight.json +++ b/.hallmark/preflight.json @@ -1,10 +1,22 @@ { - "scannedAt": "2026-07-31", - "fontStack": "Geist + JetBrains Mono (next/font)", - "palette": "fumadocs ocean + custom light/dark theme CSS", - "motion": "CSS keyframes only (marquee, spin) — no framer-motion", - "spacing": "Tailwind 4 defaults", - "framework": "Next.js app router (apps/www)", - "preserve": ["Geist display", "JetBrains Mono", "fumadocs docs theming", "home route ownership", "product copy"], - "introduce": ["Aurora tokens scoped to home", "Sentient body", "Workbench macrostructure", "N5/Ft5", "microinteraction discipline"] + "scannedAt": "2026-07-31", + "fontStack": "Geist + JetBrains Mono (next/font)", + "palette": "fumadocs ocean + custom light/dark theme CSS", + "motion": "CSS keyframes only (marquee, spin) — no framer-motion", + "spacing": "Tailwind 4 defaults", + "framework": "Next.js app router (apps/www)", + "preserve": [ + "Geist display", + "JetBrains Mono", + "fumadocs docs theming", + "home route ownership", + "product copy" + ], + "introduce": [ + "Aurora tokens scoped to home", + "Sentient body", + "Workbench macrostructure", + "N5/Ft5", + "microinteraction discipline" + ] } diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css index 3251d4a5d..279a119af 100644 --- a/apps/www/app/(home)/aurora.css +++ b/apps/www/app/(home)/aurora.css @@ -23,17 +23,7 @@ min-height: 100dvh; color: var(--color-ink); background-color: var(--color-paper); - background-image: - radial-gradient( - ellipse 42% 36% at 12% 18%, - var(--color-bloom-a), - transparent 70% - ), - radial-gradient( - ellipse 38% 32% at 88% 8%, - var(--color-bloom-b), - transparent 72% - ); + background-image: radial-gradient(ellipse 42% 36% at 12% 18%, var(--color-bloom-a), transparent 70%), radial-gradient(ellipse 38% 32% at 88% 8%, var(--color-bloom-b), transparent 72%); background-attachment: fixed; font-family: var(--font-body); overflow-x: clip; @@ -196,8 +186,7 @@ .home-aurora__shell { width: min(72rem, 100%); margin-inline: auto; - padding: calc(var(--space-3xl) + var(--space-sm)) var(--page-gutter) - var(--space-2xl); + padding: calc(var(--space-3xl) + var(--space-sm)) var(--page-gutter) var(--space-2xl); } /* Workbench · hanging intro */ @@ -687,4 +676,3 @@ .home-aurora .dark\:fill-blue-400 { fill: var(--color-accent); } - diff --git a/apps/www/components/page/install-panel.tsx b/apps/www/components/page/install-panel.tsx index 41ad54bc6..3901471a8 100644 --- a/apps/www/components/page/install-panel.tsx +++ b/apps/www/components/page/install-panel.tsx @@ -6,7 +6,8 @@ import { useCopyCommand } from "~/hooks/use-copy-command"; const INSTALL_COMMAND = "npx arkenv@latest init"; -const INSTALL_PROMPT = `Set up ArkEnv with \`npx arkenv@latest init --agent\`. Install any missing dependencies, wire the env schema into the app entry, start the app, and tell me when validation works from editor to runtime.`; +const INSTALL_PROMPT = + "Set up ArkEnv with `npx arkenv@latest init --agent`. Install any missing dependencies, wire the env schema into the app entry, start the app, and tell me when validation works from editor to runtime."; type Tab = "command" | "prompt"; @@ -69,9 +70,7 @@ export function InstallPanel() { >

- Environment variables toolkit{" "} - - built for{" "} - agents + The simple way to validate{" "} + + environment variables - .

- Validate, parse, and transform environment variables with your - favorite library. Works anywhere. + One function for validated, parsed, and typesafe env vars. + Works with ArkType, Zod, Valibot, or any Standard Schema. Ready for + your agent, managed from the CLI.

diff --git a/apps/www/components/page/compatibility-rails.tsx b/apps/www/components/page/compatibility-rails.tsx index c46e761d9..b37327a8a 100644 --- a/apps/www/components/page/compatibility-rails.tsx +++ b/apps/www/components/page/compatibility-rails.tsx @@ -143,8 +143,8 @@ export function CompatibilityRails({ className }: { className?: string }) { className, )} > - - + +
); } From 81b8a6cd675437fe0c5125a5bfec0754fccb7f7f Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 04:39:40 +0000 Subject: [PATCH 04/46] [autofix.ci] apply automated fixes --- apps/www/app/(home)/page.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/www/app/(home)/page.tsx b/apps/www/app/(home)/page.tsx index 883ba961f..8f7b3f4fa 100644 --- a/apps/www/app/(home)/page.tsx +++ b/apps/www/app/(home)/page.tsx @@ -29,9 +29,9 @@ export default function HomePage() {

- One function for validated, parsed, and typesafe env vars. - Works with ArkType, Zod, Valibot, or any Standard Schema. Ready for - your agent, managed from the CLI. + One function for validated, parsed, and typesafe env{" "} + vars. Works with ArkType, Zod, Valibot, or any Standard Schema. Ready + for your agent, managed from the CLI.

From 3427ac77a08522b06367450d355b8aaa36089a85 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Fri, 31 Jul 2026 11:03:54 +0500 Subject: [PATCH 05/46] Widen the landing hero so the lead line stays unbroken. Co-authored-by: Cursor --- apps/www/app/(home)/aurora.css | 16 ++++++++++++---- apps/www/app/(home)/page.tsx | 4 +++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css index f538ff5d3..59063f3ca 100644 --- a/apps/www/app/(home)/aurora.css +++ b/apps/www/app/(home)/aurora.css @@ -184,16 +184,17 @@ } .home-aurora__shell { - width: min(72rem, 100%); + width: min(90rem, 100%); margin-inline: auto; - padding: calc(var(--space-3xl) + var(--space-sm)) var(--page-gutter) var(--space-2xl); + padding: calc(var(--space-3xl) + var(--space-sm)) + clamp(0.75rem, 2.5vw, 1.75rem) var(--space-2xl); } /* Workbench · hanging intro */ .home-aurora__intro { display: grid; gap: var(--space-md); - max-width: 48rem; + max-width: none; padding-block: var(--space-xl) var(--space-lg); } @@ -211,7 +212,7 @@ .home-aurora__intro h1, .home-aurora__tagline { margin: 0; - max-width: 20ch; + max-width: none; font-family: var(--font-display); font-size: clamp(2.75rem, 6.5vw + 1rem, 5rem); line-height: 1.05; @@ -221,6 +222,13 @@ overflow-wrap: normal; } +/* Keep the Geist lead on one line once the viewport can host it */ +@media (min-width: 75rem) { + .home-aurora__lead { + white-space: nowrap; + } +} + .home-aurora__digital { font-family: var(--font-geist-pixel-grid), "Geist Pixel Grid", ui-monospace, monospace; font-weight: 500; diff --git a/apps/www/app/(home)/page.tsx b/apps/www/app/(home)/page.tsx index 8f7b3f4fa..6b72c6f5b 100644 --- a/apps/www/app/(home)/page.tsx +++ b/apps/www/app/(home)/page.tsx @@ -23,7 +23,9 @@ export default function HomePage() {

- The simple way to validate{" "} + + The simple way to validate + {" "} environment variables From 51e280ffdb013859d0ecd9d56017f44352edcec7 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 06:04:54 +0000 Subject: [PATCH 06/46] [autofix.ci] apply automated fixes --- apps/www/app/(home)/aurora.css | 3 +-- apps/www/app/(home)/page.tsx | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css index 59063f3ca..66487f936 100644 --- a/apps/www/app/(home)/aurora.css +++ b/apps/www/app/(home)/aurora.css @@ -186,8 +186,7 @@ .home-aurora__shell { width: min(90rem, 100%); margin-inline: auto; - padding: calc(var(--space-3xl) + var(--space-sm)) - clamp(0.75rem, 2.5vw, 1.75rem) var(--space-2xl); + padding: calc(var(--space-3xl) + var(--space-sm)) clamp(0.75rem, 2.5vw, 1.75rem) var(--space-2xl); } /* Workbench · hanging intro */ diff --git a/apps/www/app/(home)/page.tsx b/apps/www/app/(home)/page.tsx index 6b72c6f5b..4d638800e 100644 --- a/apps/www/app/(home)/page.tsx +++ b/apps/www/app/(home)/page.tsx @@ -23,9 +23,7 @@ export default function HomePage() {

- - The simple way to validate - {" "} + The simple way to validate{" "} environment variables From d8df5d81d391de989d7a5dbb80ace2e7512715b9 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Fri, 31 Jul 2026 11:25:10 +0500 Subject: [PATCH 07/46] Add a header-local interactive dot field to the landing atmosphere. Co-authored-by: Cursor --- .hallmark/log.json | 9 + apps/www/app/(home)/aurora.css | 62 +++++- apps/www/app/(home)/layout.tsx | 4 + apps/www/components/page/dot-grid.tsx | 273 ++++++++++++++++++++++++++ apps/www/components/page/index.ts | 1 + 5 files changed, 346 insertions(+), 3 deletions(-) create mode 100644 apps/www/components/page/dot-grid.tsx diff --git a/.hallmark/log.json b/.hallmark/log.json index 9516ccef3..1a6bfadfc 100644 --- a/.hallmark/log.json +++ b/.hallmark/log.json @@ -1,4 +1,13 @@ [ + { + "date": "2026-07-31", + "macrostructure": "Workbench", + "theme": "Aurora", + "enrichment": "product-demo + interactive-dot-field", + "brief": "ArkEnv landing · interactive faded dots (open-slide DNA)", + "nav": "N5", + "footer": "Ft5" + }, { "date": "2026-07-31", "macrostructure": "Workbench", diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css index 66487f936..d44a54f6c 100644 --- a/apps/www/app/(home)/aurora.css +++ b/apps/www/app/(home)/aurora.css @@ -1,5 +1,6 @@ /* Hallmark · genre: atmospheric · macrostructure: Workbench · tone: atmospheric - * theme: Aurora · anchor hue: 200 · nav: N5 · footer: Ft5 · enrichment: product-demo + * theme: Aurora · anchor hue: 200 · nav: N5 · footer: Ft5 + * enrichment: product-demo + interactive-dot-field (open-slide DNA) * axes: dark / geist-sans + geist-mono + geist-pixel-grid / cool-cyan * fonts: Geist · Geist Mono · Geist Pixel Grid * Pre-emit critique: P4 H4 E4 S4 R4 V5 @@ -23,12 +24,67 @@ min-height: 100dvh; color: var(--color-ink); background-color: var(--color-paper); - background-image: radial-gradient(ellipse 42% 36% at 12% 18%, var(--color-bloom-a), transparent 70%), radial-gradient(ellipse 38% 32% at 88% 8%, var(--color-bloom-b), transparent 72%); - background-attachment: fixed; font-family: var(--font-body); overflow-x: clip; } +.home-aurora__atmosphere { + position: absolute; + inset: 0 0 auto; + height: min(48rem, 85vh); + z-index: 0; + pointer-events: none; + overflow: hidden; + background-image: + radial-gradient( + ellipse 42% 36% at 12% 18%, + var(--color-bloom-a), + transparent 70% + ), + radial-gradient( + ellipse 38% 32% at 88% 8%, + var(--color-bloom-b), + transparent 72% + ); + /* Strong near the header, gone by the workbench */ + mask-image: linear-gradient( + to bottom, + #000 0%, + #000 28%, + rgb(0 0 0 / 0.55) 48%, + transparent 100% + ); + -webkit-mask-image: linear-gradient( + to bottom, + #000 0%, + #000 28%, + rgb(0 0 0 / 0.55) 48%, + transparent 100% + ); +} + +.home-aurora > :not(.home-aurora__atmosphere) { + position: relative; + z-index: 1; +} + +/* Static CSS dots until the canvas hydrates and clears the image */ +.home-aurora__dot-grid { + display: block; + width: 100%; + height: 100%; + color: color-mix(in oklch, var(--color-ink) 14%, transparent); + background-image: radial-gradient(currentColor 1px, transparent 1.5px); + background-size: 24px 24px; +} + +@media (prefers-reduced-motion: reduce) { + .home-aurora__dot-grid { + /* Keep the static field; physics stays off in the component */ + opacity: 0.85; + } +} + .home-aurora :is(h1, h2, h3) { font-style: normal; font-weight: 600; diff --git a/apps/www/app/(home)/layout.tsx b/apps/www/app/(home)/layout.tsx index 2285aacea..a2039e77c 100644 --- a/apps/www/app/(home)/layout.tsx +++ b/apps/www/app/(home)/layout.tsx @@ -1,11 +1,15 @@ import "./aurora.css"; import { HomeLayout } from "fumadocs-ui/layouts/home"; import type { CSSProperties, ReactNode } from "react"; +import { DotGrid } from "~/components/page"; import { HomeNav } from "./home-nav"; export default function Layout({ children }: { children: ReactNode }) { return (
+ (null); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + let width = 0; + let height = 0; + let cols = 0; + let rows = 0; + // ox, oy, vx, vy per dot + let dots = new Float32Array(0); + let color = ""; + let raf = 0; + let running = false; + let lastTime = 0; + const pointer = { + x: 0, + y: 0, + prevX: 0, + prevY: 0, + vx: 0, + vy: 0, + active: false, + }; + + const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); + + const resolveColor = () => { + color = getComputedStyle(canvas).color; + }; + + const draw = () => { + ctx.clearRect(0, 0, width, height); + ctx.fillStyle = color; + ctx.beginPath(); + for (let row = 0; row < rows; row++) { + for (let col = 0; col < cols; col++) { + const idx = (row * cols + col) * 4; + const x = SPACING / 2 + col * SPACING + dots[idx]; + const y = SPACING / 2 + row * SPACING + dots[idx + 1]; + ctx.moveTo(x + DOT_RADIUS, y); + ctx.arc(x, y, DOT_RADIUS, 0, Math.PI * 2); + } + } + ctx.fill(); + }; + + const step = (dt: number) => { + const influenceSq = INFLUENCE * INFLUENCE; + let moving = false; + for (let row = 0; row < rows; row++) { + const baseY = SPACING / 2 + row * SPACING; + for (let col = 0; col < cols; col++) { + const idx = (row * cols + col) * 4; + const baseX = SPACING / 2 + col * SPACING; + let ox = dots[idx]; + let oy = dots[idx + 1]; + let vx = dots[idx + 2]; + let vy = dots[idx + 3]; + + let ax = -STIFFNESS * ox - DAMPING * vx; + let ay = -STIFFNESS * oy - DAMPING * vy; + + if (pointer.active) { + const dx = baseX + ox - pointer.x; + const dy = baseY + oy - pointer.y; + const distSq = dx * dx + dy * dy; + if (distSq < influenceSq) { + const dist = Math.sqrt(distSq) || 1; + const falloff = 1 - dist / INFLUENCE; + ax += ((dx / dist) * PUSH + pointer.vx * DRAG) * falloff; + ay += ((dy / dist) * PUSH + pointer.vy * DRAG) * falloff; + } + } + + vx += ax * dt; + vy += ay * dt; + ox += vx * dt; + oy += vy * dt; + + const offsetSq = ox * ox + oy * oy; + if (offsetSq > MAX_OFFSET * MAX_OFFSET) { + const scale = MAX_OFFSET / Math.sqrt(offsetSq); + ox *= scale; + oy *= scale; + } + + if ( + offsetSq > REST_THRESHOLD || + vx * vx + vy * vy > REST_THRESHOLD + ) { + moving = true; + } else { + ox = 0; + oy = 0; + vx = 0; + vy = 0; + } + + dots[idx] = ox; + dots[idx + 1] = oy; + dots[idx + 2] = vx; + dots[idx + 3] = vy; + } + } + return moving; + }; + + const frame = (time: number) => { + const dt = Math.min(Math.max((time - lastTime) / 1000, 0.001), 1 / 30); + lastTime = time; + + if (pointer.active) { + const clamp = (v: number) => + Math.max(-MAX_POINTER_SPEED, Math.min(MAX_POINTER_SPEED, v)); + pointer.vx = clamp((pointer.x - pointer.prevX) / dt); + pointer.vy = clamp((pointer.y - pointer.prevY) / dt); + pointer.prevX = pointer.x; + pointer.prevY = pointer.y; + } + + const moving = step(dt); + draw(); + + if (moving || pointer.active) { + raf = requestAnimationFrame(frame); + } else { + running = false; + } + }; + + const wake = () => { + if (running || reduceMotion.matches) return; + running = true; + lastTime = performance.now(); + raf = requestAnimationFrame(frame); + }; + + const sleep = () => { + cancelAnimationFrame(raf); + running = false; + }; + + const resize = () => { + const rect = canvas.getBoundingClientRect(); + width = rect.width; + height = rect.height; + const dpr = Math.min(window.devicePixelRatio || 1, 2); + canvas.width = Math.round(width * dpr); + canvas.height = Math.round(height * dpr); + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + cols = Math.ceil(width / SPACING); + rows = Math.ceil(height / SPACING); + dots = new Float32Array(cols * rows * 4); + draw(); + }; + + const deactivatePointer = () => { + pointer.active = false; + pointer.vx = 0; + pointer.vy = 0; + }; + + const onPointerMove = (event: PointerEvent) => { + if (reduceMotion.matches) return; + const rect = canvas.getBoundingClientRect(); + const x = event.clientX - rect.left; + const y = event.clientY - rect.top; + const inRange = + x > -INFLUENCE && + y > -INFLUENCE && + x < rect.width + INFLUENCE && + y < rect.height + INFLUENCE; + if (!inRange) { + deactivatePointer(); + return; + } + if (!pointer.active) { + pointer.prevX = x; + pointer.prevY = y; + } + pointer.active = true; + pointer.x = x; + pointer.y = y; + wake(); + }; + + const onPointerEnd = (event: PointerEvent) => { + if (event.pointerType !== "mouse") deactivatePointer(); + }; + + const onWindowOut = (event: PointerEvent) => { + if (!event.relatedTarget) deactivatePointer(); + }; + + resolveColor(); + resize(); + canvas.style.backgroundImage = "none"; + + const resizeObserver = new ResizeObserver(resize); + resizeObserver.observe(canvas); + + const themeObserver = new MutationObserver(() => { + resolveColor(); + draw(); + }); + themeObserver.observe(document.documentElement, { + attributes: true, + attributeFilter: ["class", "data-theme", "style"], + }); + + const onMotionPreferenceChange = () => { + if (!reduceMotion.matches) return; + sleep(); + deactivatePointer(); + dots.fill(0); + draw(); + }; + + window.addEventListener("pointermove", onPointerMove, { passive: true }); + window.addEventListener("pointerup", onPointerEnd, { passive: true }); + window.addEventListener("pointercancel", onPointerEnd, { passive: true }); + window.addEventListener("pointerout", onWindowOut, { passive: true }); + window.addEventListener("blur", deactivatePointer); + window.addEventListener("scroll", deactivatePointer, { passive: true }); + reduceMotion.addEventListener("change", onMotionPreferenceChange); + + return () => { + sleep(); + resizeObserver.disconnect(); + themeObserver.disconnect(); + window.removeEventListener("pointermove", onPointerMove); + window.removeEventListener("pointerup", onPointerEnd); + window.removeEventListener("pointercancel", onPointerEnd); + window.removeEventListener("pointerout", onWindowOut); + window.removeEventListener("blur", deactivatePointer); + window.removeEventListener("scroll", deactivatePointer); + reduceMotion.removeEventListener("change", onMotionPreferenceChange); + }; + }, []); + + return ( + + ); +} diff --git a/apps/www/components/page/index.ts b/apps/www/components/page/index.ts index 8e24970b4..fe4f6773b 100644 --- a/apps/www/components/page/index.ts +++ b/apps/www/components/page/index.ts @@ -1,6 +1,7 @@ export * from "./cli-command"; export * from "./compatibility-rails"; export * from "./copy-button"; +export * from "./dot-grid"; export * from "./install-panel"; export * from "./logo"; export * from "./quickstart-button"; From 6a406c4ecd62f6b120b74cc545cc6a68269dfb36 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 06:26:08 +0000 Subject: [PATCH 08/46] [autofix.ci] apply automated fixes --- apps/www/app/(home)/aurora.css | 28 +++------------------------ apps/www/components/page/dot-grid.tsx | 11 ++--------- 2 files changed, 5 insertions(+), 34 deletions(-) diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css index d44a54f6c..1caad2c9a 100644 --- a/apps/www/app/(home)/aurora.css +++ b/apps/www/app/(home)/aurora.css @@ -35,32 +35,10 @@ z-index: 0; pointer-events: none; overflow: hidden; - background-image: - radial-gradient( - ellipse 42% 36% at 12% 18%, - var(--color-bloom-a), - transparent 70% - ), - radial-gradient( - ellipse 38% 32% at 88% 8%, - var(--color-bloom-b), - transparent 72% - ); + background-image: radial-gradient(ellipse 42% 36% at 12% 18%, var(--color-bloom-a), transparent 70%), radial-gradient(ellipse 38% 32% at 88% 8%, var(--color-bloom-b), transparent 72%); /* Strong near the header, gone by the workbench */ - mask-image: linear-gradient( - to bottom, - #000 0%, - #000 28%, - rgb(0 0 0 / 0.55) 48%, - transparent 100% - ); - -webkit-mask-image: linear-gradient( - to bottom, - #000 0%, - #000 28%, - rgb(0 0 0 / 0.55) 48%, - transparent 100% - ); + mask-image: linear-gradient(to bottom, #000 0%, #000 28%, rgb(0 0 0 / 0.55) 48%, transparent 100%); + -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 28%, rgb(0 0 0 / 0.55) 48%, transparent 100%); } .home-aurora > :not(.home-aurora__atmosphere) { diff --git a/apps/www/components/page/dot-grid.tsx b/apps/www/components/page/dot-grid.tsx index 2acea2f56..ec281b788 100644 --- a/apps/www/components/page/dot-grid.tsx +++ b/apps/www/components/page/dot-grid.tsx @@ -109,10 +109,7 @@ export function DotGrid() { oy *= scale; } - if ( - offsetSq > REST_THRESHOLD || - vx * vx + vy * vy > REST_THRESHOLD - ) { + if (offsetSq > REST_THRESHOLD || vx * vx + vy * vy > REST_THRESHOLD) { moving = true; } else { ox = 0; @@ -264,10 +261,6 @@ export function DotGrid() { }, []); return ( - + ); } From 4658d8718741b72dba8bfcf4b89ec71f5320d724 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 31 Jul 2026 07:47:28 +0000 Subject: [PATCH 09/46] docs(www): update home hero summary copy Align the homepage hero summary with the v1 redesign wording. Co-authored-by: Yam Borodetsky --- apps/www/app/(home)/page.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/www/app/(home)/page.tsx b/apps/www/app/(home)/page.tsx index 4d638800e..2f970933d 100644 --- a/apps/www/app/(home)/page.tsx +++ b/apps/www/app/(home)/page.tsx @@ -29,9 +29,9 @@ export default function HomePage() {

- One function for validated, parsed, and typesafe env{" "} - vars. Works with ArkType, Zod, Valibot, or any Standard Schema. Ready - for your agent, managed from the CLI. + One function for validated, parsed, and typesafe env vars. Works with + ArkType, Zod, Valibot, or any Standard Schema. Start from the CLI, or + let your agent handle it.

From 5b98e5aba68e5ce70b58d62bc044e4a47a567780 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 31 Jul 2026 07:48:43 +0000 Subject: [PATCH 10/46] docs(www): hide install Command/Prompt panel on mobile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep the hero tabbed install UI desktop/tablet-only (≥40rem). Co-authored-by: Yam Borodetsky --- apps/www/app/(home)/aurora.css | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css index 1caad2c9a..efd1fab4c 100644 --- a/apps/www/app/(home)/aurora.css +++ b/apps/www/app/(home)/aurora.css @@ -303,13 +303,19 @@ max-width: 42rem; } -/* Install · Command / Prompt tabs */ +/* Install · Command / Prompt tabs — desktop/tablet only */ .home-aurora__install { - display: grid; + display: none; gap: var(--space-xs); width: 100%; } +@media (min-width: 40rem) { + .home-aurora__install { + display: grid; + } +} + .home-aurora__install-tabs { display: flex; align-items: center; From 8c664d8b0a275d4ef263681133b2330f04527498 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 31 Jul 2026 07:50:18 +0000 Subject: [PATCH 11/46] docs(www): add mobile Read the docs CTA beside Star us Surface docs on small screens where nav links and install tabs are hidden. Co-authored-by: Yam Borodetsky --- apps/www/app/(home)/aurora.css | 45 +++++++++++++++++++ apps/www/app/(home)/page.tsx | 2 + .../page/quickstart-button.test.tsx | 8 ++-- .../www/components/page/quickstart-button.tsx | 4 +- 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css index efd1fab4c..a99418201 100644 --- a/apps/www/app/(home)/aurora.css +++ b/apps/www/app/(home)/aurora.css @@ -303,6 +303,50 @@ max-width: 42rem; } +/* Mobile secondary CTA — below Star us; hidden with install panel on ≥40rem */ +.home-aurora__docs-cta { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.4rem; + width: 100%; + min-height: 3rem; + padding: 0.75rem 1.1rem; + border: var(--rule-hair) solid var(--color-rule); + border-radius: var(--radius-pill); + background: color-mix(in oklch, var(--color-paper-2) 90%, transparent); + color: var(--color-ink-2); + font-family: var(--font-display); + font-size: var(--text-sm); + font-weight: 500; + text-decoration: none; + transition: + color var(--dur-micro) var(--ease-out), + border-color var(--dur-micro) var(--ease-out), + background-color var(--dur-micro) var(--ease-out); +} + +.home-aurora__docs-cta:hover { + color: var(--color-ink); + border-color: color-mix(in oklch, var(--color-accent) 40%, var(--color-rule)); + background: var(--color-paper-3); +} + +.home-aurora__docs-cta:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; +} + +.home-aurora__docs-cta:active { + opacity: 0.9; +} + +@media (min-width: 40rem) { + .home-aurora__docs-cta { + display: none; + } +} + /* Install · Command / Prompt tabs — desktop/tablet only */ .home-aurora__install { display: none; @@ -659,6 +703,7 @@ @media (prefers-reduced-motion: reduce) { .home-aurora__frame-hint, .home-aurora__cta, + .home-aurora__docs-cta, .home-aurora__nav-cta, .home-aurora__nav-links a, .home-aurora__install-tab, diff --git a/apps/www/app/(home)/page.tsx b/apps/www/app/(home)/page.tsx index 2f970933d..e9efb696f 100644 --- a/apps/www/app/(home)/page.tsx +++ b/apps/www/app/(home)/page.tsx @@ -4,6 +4,7 @@ import { AnnouncementBadge } from "~/components/announcement-badge"; import { CompatibilityRails, InstallPanel, + QuickstartButton, StarUsButton, VideoDemo, } from "~/components/page"; @@ -37,6 +38,7 @@ export default function HomePage() {
+
diff --git a/apps/www/components/page/quickstart-button.test.tsx b/apps/www/components/page/quickstart-button.test.tsx index 47852505c..859606fcb 100644 --- a/apps/www/components/page/quickstart-button.test.tsx +++ b/apps/www/components/page/quickstart-button.test.tsx @@ -3,14 +3,14 @@ import { describe, expect, it } from "vitest"; import { QuickstartButton } from "./quickstart-button"; describe("QuickstartButton", () => { - it("renders quickstart button with correct text", () => { + it("renders read the docs button with correct text", () => { render(); - expect(screen.getByText("Get Started")).toBeInTheDocument(); + expect(screen.getByText("Read the docs")).toBeInTheDocument(); }); - it("renders as a link to docs/quickstart", () => { + it("renders as a link to docs", () => { render(); const link = screen.getByRole("link"); - expect(link).toHaveAttribute("href", "/docs/arkenv/quickstart"); + expect(link).toHaveAttribute("href", "/docs/arkenv"); }); }); diff --git a/apps/www/components/page/quickstart-button.tsx b/apps/www/components/page/quickstart-button.tsx index 20ebe03ef..15d707b3e 100644 --- a/apps/www/components/page/quickstart-button.tsx +++ b/apps/www/components/page/quickstart-button.tsx @@ -2,8 +2,8 @@ export function QuickstartButton() { return ( - - Get Started + + Read the docs ); From 9c8428329f5193c2b120bae88433fb532b8679bb Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 31 Jul 2026 07:55:47 +0000 Subject: [PATCH 12/46] docs(www): restore header ArkEnv wordmark in Sentient Show the brand name beside the logo again, set in self-hosted Sentient (Aurora wordmark serif) for the nav and footer mark. Co-authored-by: Yam Borodetsky --- apps/www/app/(home)/aurora.css | 6 ++++-- apps/www/app/(home)/home-nav.tsx | 2 +- apps/www/app/fonts/Sentient-Variable.woff2 | Bin 0 -> 50628 bytes apps/www/app/fonts/sentient.ts | 13 +++++++++++++ apps/www/app/layout.tsx | 3 ++- apps/www/tokens.css | 6 ++++-- 6 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 apps/www/app/fonts/Sentient-Variable.woff2 create mode 100644 apps/www/app/fonts/sentient.ts diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css index a99418201..dd35acb69 100644 --- a/apps/www/app/(home)/aurora.css +++ b/apps/www/app/(home)/aurora.css @@ -76,7 +76,7 @@ display: inline-flex; align-items: center; flex-shrink: 0; - font-family: var(--font-body); + font-family: var(--font-wordmark); font-weight: 500; font-style: normal; letter-spacing: -0.01em; @@ -86,8 +86,10 @@ } .home-aurora__wordmark span { - font-family: var(--font-body); + font-family: var(--font-wordmark); font-weight: 500; + font-size: var(--text-md); + letter-spacing: -0.02em; white-space: nowrap; } diff --git a/apps/www/app/(home)/home-nav.tsx b/apps/www/app/(home)/home-nav.tsx index 74c277817..8d9fd66f7 100644 --- a/apps/www/app/(home)/home-nav.tsx +++ b/apps/www/app/(home)/home-nav.tsx @@ -9,7 +9,7 @@ export function HomeNav() { return ( diff --git a/apps/www/app/(home)/layout.tsx b/apps/www/app/(home)/layout.tsx index a2039e77c..6d092fbf1 100644 --- a/apps/www/app/(home)/layout.tsx +++ b/apps/www/app/(home)/layout.tsx @@ -1,7 +1,7 @@ import "./aurora.css"; import { HomeLayout } from "fumadocs-ui/layouts/home"; import type { CSSProperties, ReactNode } from "react"; -import { DotGrid } from "~/components/page"; +import { DotGrid, ScrollReveal } from "~/components/page"; import { HomeNav } from "./home-nav"; export default function Layout({ children }: { children: ReactNode }) { @@ -10,6 +10,7 @@ export default function Layout({ children }: { children: ReactNode }) { +
-
+
Next.js, Netlify presets
-

+

The happy path for{" "} environment{" "} variables + .

-

+

One function to validate, parse, and keep your env vars - typesafe. Works with ArkType, Zod, or any Standard Schema. Start from - the CLI, or let your agent handle it. + typesafe. Use ArkType, Zod, or any Standard Schema. Run the CLI, or + let your agent handle the setup.

- -
+
+ +
+
@@ -48,17 +67,47 @@ export default function HomePage() { + +
-

Try it live

-

Open the playground from the demo below.

+

+ Try it live! +

+

+ Click the demo to open a fully interactive playground. +

- +
+ +
+
+ + + +
+

+ Try ArkEnv now. +

+
-
); diff --git a/apps/www/components/page/before-after-compare-view.tsx b/apps/www/components/page/before-after-compare-view.tsx new file mode 100644 index 000000000..4f8fdab4b --- /dev/null +++ b/apps/www/components/page/before-after-compare-view.tsx @@ -0,0 +1,145 @@ +"use client"; + +import { + useEffect, + useId, + useRef, + useState, + type CSSProperties, +} from "react"; +import { WindowChrome } from "./window-chrome"; + +type BeforeAfterCompareViewProps = { + beforeHtml: string; + afterHtml: string; + reduction: number; +}; + +/** + * Scroll-driven before/after reveal. Drag still works as a manual override. + * Full 0–100% travel; pane padding keeps labels readable at the edges. + */ +export function BeforeAfterCompareView({ + beforeHtml, + afterHtml, + reduction, +}: BeforeAfterCompareViewProps) { + const [compare, setCompare] = useState(100); + const [manual, setManual] = useState(false); + const compareRef = useRef(null); + const labelId = useId(); + + useEffect(() => { + if (manual) return; + + const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); + if (reduceMotion.matches) { + setCompare(50); + return; + } + + const updateFromScroll = () => { + const el = compareRef.current; + if (!el) return; + const rect = el.getBoundingClientRect(); + const view = window.innerHeight || 1; + const start = view * 0.85; + const end = view * 0.2; + const raw = (start - rect.top) / (start - end); + const progress = Math.min(1, Math.max(0, raw)); + setCompare(Math.round((1 - progress) * 100)); + }; + + updateFromScroll(); + window.addEventListener("scroll", updateFromScroll, { passive: true }); + window.addEventListener("resize", updateFromScroll, { passive: true }); + return () => { + window.removeEventListener("scroll", updateFromScroll); + window.removeEventListener("resize", updateFromScroll); + }; + }, [manual]); + + return ( +
+
+

+ 01 - FAIL-FAST +

+

+ Catch bad configs before you boot. +

+

+ ArkEnv replaces your manual process.env checks with one + schema that validates at startup. Drop the separate interfaces, consts, + and error handling, and guarantee your app never crashes at runtime due + to a missing key. +

+

+ {reduction}% less code. 100% runtime safety. +

+
+ +
+ +
+ {/* Invisible sizer so the frame fits the full old-way snippet */} +
+ ); +} diff --git a/apps/www/components/page/before-after-compare.tsx b/apps/www/components/page/before-after-compare.tsx index 95b69eab6..ef08086c2 100644 --- a/apps/www/components/page/before-after-compare.tsx +++ b/apps/www/components/page/before-after-compare.tsx @@ -1,75 +1,59 @@ -"use client"; +import { BeforeAfterCompareView } from "./before-after-compare-view"; +import { highlightTs } from "./highlight-ts"; -/** - * Before / after code compare with a draggable vertical reveal. - * Pure CSS clip + range input — no card chrome. - */ -export function BeforeAfterCompare() { - return ( -
-
-

01 — Friction

-

Delete the boilerplate

-

- Manual process.env checks, throws, and{" "} - parseInt coercion — or one schema. -

-
- -
- { - const value = `${(event.target as HTMLInputElement).value}%`; - event.currentTarget.parentElement?.style.setProperty( - "--compare", - value, - ); - }} - /> -
-

The ArkEnv way

-
-						{`import arkenv from "arkenv";
-
-export const env = arkenv({
-  DATABASE_URL: "string.url",
-  PORT: "0 <= number.integer <= 65535 = 3000",
-  NODE_ENV: "'development' | 'production'",
-});
-
-// env.PORT is number — validated at boot`}
-					
-
-
-

The old way

-
-						{`const url = process.env.DATABASE_URL;
-if (!url) throw new Error("DATABASE_URL missing");
+const BEFORE = `// The old way
+const DATABASE_URL = process.env.DATABASE_URL;
+if (!DATABASE_URL) throw new Error("DATABASE_URL missing");
+if (!/^https?:\\/\\//.test(DATABASE_URL)) {
+  throw new Error("DATABASE_URL must be http(s)");
+}
 
 const portRaw = process.env.PORT ?? "3000";
 const PORT = Number.parseInt(portRaw, 10);
 if (Number.isNaN(PORT)) throw new Error("PORT invalid");
+if (PORT < 0 || PORT > 65535) {
+  throw new Error("PORT out of range");
+}
 
 const NODE_ENV = process.env.NODE_ENV ?? "development";
 if (!["development", "production"].includes(NODE_ENV)) {
   throw new Error("NODE_ENV invalid");
 }
 
-export const env = { DATABASE_URL: url, PORT, NODE_ENV };`}
-					
-
- -
+export const env = { DATABASE_URL, PORT, NODE_ENV };`; + +const AFTER = `// The ArkEnv way +import arkenv from "arkenv"; + +export const env = arkenv({ + DATABASE_URL: "string.url", + PORT: "0 <= number.integer <= 65535 = 3000", + NODE_ENV: "'development' | 'production'", +});`; + +function countLines(source: string) { + return source.trimEnd().split("\n").length; +} + +/** + * Server entry: Shiki-highlights the snippets, then hands off to the + * interactive client reveal. + */ +export async function BeforeAfterCompare() { + const [beforeHtml, afterHtml] = await Promise.all([ + highlightTs(BEFORE), + highlightTs(AFTER), + ]); + + const reduction = Math.round( + (1 - countLines(AFTER) / countLines(BEFORE)) * 100, + ); + + return ( + ); } diff --git a/apps/www/components/page/bring-your-own-validator-view.tsx b/apps/www/components/page/bring-your-own-validator-view.tsx new file mode 100644 index 000000000..2f0fb3c7c --- /dev/null +++ b/apps/www/components/page/bring-your-own-validator-view.tsx @@ -0,0 +1,104 @@ +"use client"; + +import { useId, useState } from "react"; +import { WindowChrome } from "./window-chrome"; + +type ValidatorExample = { + id: "arktype" | "zod" | "valibot"; + label: string; + importLine: string; + html: string; +}; + +type BringYourOwnValidatorViewProps = { + examples: ValidatorExample[]; +}; + +/** + * Client tabs over server-highlighted validator snippets. + */ +export function BringYourOwnValidatorView({ + examples, +}: BringYourOwnValidatorViewProps) { + const [active, setActive] = useState("arktype"); + const baseId = useId(); + const example = examples.find((item) => item.id === active) ?? examples[0]; + const panelId = `${baseId}-panel`; + + return ( +
+
+

+ 04 - MODULAR +

+

+ Bring your own validator. +

+

+ Use ArkType, Zod, Valibot, or any{" "} + Standard Schema{" "} + library. You keep the same env API. +

+
+ +
+
+ {examples.map((item) => ( + + ))} +
+ +
+ +
+ {/* biome-ignore lint/security/noDangerouslySetInnerHtml: static Shiki HTML from server */} +
+
+
+ +

+ via{" "} + {example.importLine} + {" · "} + + See Standard Schema docs → + +

+
+
+ ); +} diff --git a/apps/www/components/page/bring-your-own-validator.tsx b/apps/www/components/page/bring-your-own-validator.tsx new file mode 100644 index 000000000..09d309f41 --- /dev/null +++ b/apps/www/components/page/bring-your-own-validator.tsx @@ -0,0 +1,56 @@ +import { BringYourOwnValidatorView } from "./bring-your-own-validator-view"; +import { highlightTs } from "./highlight-ts"; + +const EXAMPLES = [ + { + id: "arktype" as const, + label: "ArkType", + importLine: "@arkenv/core", + code: `import arkenv from "@arkenv/core"; + +export const env = arkenv({ + DATABASE_URL: "string.url", + PORT: "number.port", +});`, + }, + { + id: "zod" as const, + label: "Zod", + importLine: "@arkenv/standard", + code: `import arkenv from "@arkenv/standard"; +import { z } from "zod"; + +export const env = arkenv({ + DATABASE_URL: z.url(), + PORT: z.coerce.number().int().min(0).max(65535), +});`, + }, + { + id: "valibot" as const, + label: "Valibot", + importLine: "@arkenv/standard", + code: `import arkenv from "@arkenv/standard"; +import * as v from "valibot"; + +export const env = arkenv({ + DATABASE_URL: v.pipe(v.string(), v.url()), + PORT: v.pipe(v.string(), v.toNumber(), v.integer(), v.minValue(0), v.maxValue(65535)), +});`, + }, +]; + +/** + * Pitch: same ArkEnv API across ArkType / Zod / Valibot via Standard Schema. + */ +export async function BringYourOwnValidator() { + const examples = await Promise.all( + EXAMPLES.map(async (example) => ({ + id: example.id, + label: example.label, + importLine: example.importLine, + html: await highlightTs(example.code), + })), + ); + + return ; +} diff --git a/apps/www/components/page/dot-grid.tsx b/apps/www/components/page/dot-grid.tsx index ec281b788..81abd19e0 100644 --- a/apps/www/components/page/dot-grid.tsx +++ b/apps/www/components/page/dot-grid.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef } from "react"; -/** Physics tuned for a quiet Aurora field — push ripples, then settle. */ +/** Physics tuned for a quiet Aurora field - push ripples, then settle. */ const SPACING = 24; const DOT_RADIUS = 1; const INFLUENCE = 110; diff --git a/apps/www/components/page/header-github-link.tsx b/apps/www/components/page/header-github-link.tsx index 44c05c0bb..f3dfa89d6 100644 --- a/apps/www/components/page/header-github-link.tsx +++ b/apps/www/components/page/header-github-link.tsx @@ -4,7 +4,11 @@ import { SiGithub } from "@icons-pack/react-simple-icons"; import { Star } from "lucide-react"; import { useEffect, useState } from "react"; import { breakDownGithubUrl } from "~/lib/utils/github"; +import { cn } from "~/lib/utils"; +/** + * Single GitHub badge: icon + star count in one hit target. + */ export function HeaderGithubLink({ className, iconClassName, @@ -34,21 +38,32 @@ export function HeaderGithubLink({ fetchStarCount(); }, []); + const label = + starCount !== null + ? `GitHub · ${starCount.toLocaleString()} stars` + : "GitHub"; + return ( - - {starCount !== null && ( -
- - {starCount.toLocaleString()} -
+ aria-label={label} + className={cn( + "inline-flex items-center justify-center gap-1.5 transition-colors", + className, )} + > +
); } diff --git a/apps/www/components/page/hero-faq.tsx b/apps/www/components/page/hero-faq.tsx new file mode 100644 index 000000000..7b798bc8a --- /dev/null +++ b/apps/www/components/page/hero-faq.tsx @@ -0,0 +1,165 @@ +"use client"; + +import { useCallback, useEffect, useId, useState } from "react"; + +/** + * Homepage FAQ picks - questions + anchors match `/docs/arkenv/faq` (source of truth). + * Keep answers as short teasers; link out for the full write-up. + */ +const FAQ = [ + { + id: "why", + href: "/docs/arkenv/faq#why-do-i-need-arkenv", + question: "Why do I need ArkEnv?", + teaser: + 'Env vars go missing in CI, turn "false" into a truthy string, and fail at runtime. ArkEnv validates at boot so your process matches the schema before app code runs.', + }, + { + id: "env-files", + href: "/docs/arkenv/faq#does-arkenv-load-my-env-files", + question: "Does ArkEnv load my `.env` files?", + teaser: + "No. ArkEnv validates the env your framework already loaded. It does not read `.env` files itself.", + }, + { + id: "arktype", + href: "/docs/arkenv/faq#do-i-have-to-use-arktype", + question: "Do I have to use ArkType?", + teaser: + "No. Use any Standard Schema validator: Zod, Valibot, Typia, and others.", + }, + { + id: "zod", + href: "/docs/arkenv/faq#does-arkenv-work-with-zod", + question: "Does ArkEnv work with Zod?", + teaser: + "Yes. Zod has a first-class example, and the CLI can scaffold ArkEnv + Zod for you.", + }, + { + id: "agents", + href: "/docs/arkenv/faq#does-arkenv-support-ai-development-tools-like-claude-code-or-cursor", + question: + "Does ArkEnv support AI development tools like Claude Code or Cursor?", + teaser: + "Yes. Grab the agent skill and your assistant keeps env schemas in sync as the code changes.", + }, +] as const; + +const DOCS_FAQ_HREF = "/docs/arkenv/faq"; + +/** + * Landing FAQ - select questions from the docs FAQ, with links back to SoT. + * `#why` expands the first item and scrolls to the section. + */ +export function HeroFaq() { + const [openId, setOpenId] = useState(null); + const baseId = useId(); + + const openWhy = useCallback(() => { + if (window.location.hash !== "#why" && window.location.hash !== "#faq") { + return; + } + if (window.location.hash === "#why") { + setOpenId("why"); + } + requestAnimationFrame(() => { + const target = + window.location.hash === "#why" + ? document.getElementById("why") + : document.getElementById("faq"); + target?.scrollIntoView({ + behavior: "smooth", + block: "start", + }); + }); + }, []); + + useEffect(() => { + openWhy(); + window.addEventListener("hashchange", openWhy); + + const onClick = (event: MouseEvent) => { + const target = event.target; + if (!(target instanceof Element)) return; + const link = target.closest( + 'a[href="/#why"], a[href="#why"], a[href="/#faq"], a[href="#faq"]', + ); + if (!link) return; + requestAnimationFrame(openWhy); + }; + document.addEventListener("click", onClick); + + return () => { + window.removeEventListener("hashchange", openWhy); + document.removeEventListener("click", onClick); + }; + }, [openWhy]); + + return ( +
+
+

+ Frequently asked questions. +

+

+ Picked from the docs.{" "} + Read the full FAQ → +

+
+ +
+ {FAQ.map((item) => { + const isOpen = openId === item.id; + const panelId = `${baseId}-${item.id}-panel`; + const buttonId = `${baseId}-${item.id}-button`; + + return ( +
+ +
+
+

{item.teaser}

+

+ Read full answer → +

+
+
+
+ ); + })} +
+
+ ); +} diff --git a/apps/www/components/page/highlight-ts.ts b/apps/www/components/page/highlight-ts.ts new file mode 100644 index 000000000..4f465f388 --- /dev/null +++ b/apps/www/components/page/highlight-ts.ts @@ -0,0 +1,16 @@ +import { codeToHtml } from "shiki"; + +/** + * Shared landing-page highlighter — matches docs + 01 DECLARATIVE + * (github-*-high-contrast dual theme). + */ +export async function highlightTs(code: string) { + return codeToHtml(code, { + lang: "ts", + themes: { + light: "github-light-high-contrast", + dark: "github-dark-high-contrast", + }, + defaultColor: false, + }); +} diff --git a/apps/www/components/page/index.ts b/apps/www/components/page/index.ts index bf124699f..6620aa86e 100644 --- a/apps/www/components/page/index.ts +++ b/apps/www/components/page/index.ts @@ -1,12 +1,17 @@ export * from "./agent-native-pitch"; export * from "./before-after-compare"; +export * from "./bring-your-own-validator"; export * from "./cli-command"; export * from "./compatibility-rails"; export * from "./copy-button"; export * from "./dot-grid"; +export * from "./hero-faq"; export * from "./install-panel"; export * from "./logo"; export * from "./quickstart-button"; +export * from "./scroll-reveal"; +export * from "./secure-boundary"; export * from "./star-us-button"; export * from "./type-safety-showcase"; export * from "./video-demo"; +export * from "./window-chrome"; diff --git a/apps/www/components/page/install-panel.tsx b/apps/www/components/page/install-panel.tsx index 3901471a8..5313738ec 100644 --- a/apps/www/components/page/install-panel.tsx +++ b/apps/www/components/page/install-panel.tsx @@ -14,8 +14,7 @@ type Tab = "command" | "prompt"; export function InstallPanel() { const [tab, setTab] = useState("command"); const baseId = useId(); - const commandPanelId = `${baseId}-command`; - const promptPanelId = `${baseId}-prompt`; + const panelId = `${baseId}-panel`; const commandTabId = `${baseId}-tab-command`; const promptTabId = `${baseId}-tab-prompt`; @@ -26,6 +25,16 @@ export function InstallPanel() { successDescription: "Prompt copied to clipboard!", }); + const active = tab === "command" ? commandCopy : promptCopy; + const ariaLabel = + tab === "command" + ? active.copied + ? "Copied" + : "Copy install command" + : active.copied + ? "Copied" + : "Copy prompt"; + return (
- {tab === "command" ? ( -
+ -
- ) : ( -
- -
- )} + + + +
); } diff --git a/apps/www/components/page/scroll-reveal.tsx b/apps/www/components/page/scroll-reveal.tsx new file mode 100644 index 000000000..8499b5ea7 --- /dev/null +++ b/apps/www/components/page/scroll-reveal.tsx @@ -0,0 +1,48 @@ +"use client"; + +import { useEffect } from "react"; + +/** + * Open-slide–style scroll reveals: only hide `[data-reveal]` nodes still + * below the fold, then clear blur/opacity via IntersectionObserver. + * No-JS and above-the-fold content stay visible (no flash). + */ +export function ScrollReveal() { + useEffect(() => { + if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { + return; + } + + const els = Array.from( + document.querySelectorAll("[data-reveal]"), + ); + const foldLine = window.innerHeight * 0.92; + const pending = els.filter( + (el) => el.getBoundingClientRect().top > foldLine, + ); + if (pending.length === 0) return; + + for (const el of pending) el.classList.add("reveal-hidden"); + + const observer = new IntersectionObserver( + (entries) => { + for (const entry of entries) { + if (!entry.isIntersecting) continue; + entry.target.classList.add("reveal-shown"); + entry.target.classList.remove("reveal-hidden"); + observer.unobserve(entry.target); + } + }, + { rootMargin: "0px 0px -8% 0px" }, + ); + + for (const el of pending) observer.observe(el); + + return () => { + observer.disconnect(); + for (const el of pending) el.classList.remove("reveal-hidden"); + }; + }, []); + + return null; +} diff --git a/apps/www/components/page/secure-boundary.tsx b/apps/www/components/page/secure-boundary.tsx new file mode 100644 index 000000000..b91b3d89e --- /dev/null +++ b/apps/www/components/page/secure-boundary.tsx @@ -0,0 +1,68 @@ +import { WindowChrome } from "./window-chrome"; +import { highlightTs } from "./highlight-ts"; + +const CLIENT = `export const env = arkenv({ + NEXT_PUBLIC_API_URL: "string", +});`; + +const SERVER = `export const env = arkenv({ + DATABASE_URL: "string", +});`; + +/** + * Strict-layout pitch: client vs server env boundary. + * Examples mirror /docs/nextjs/layouts/strict. + */ +export async function SecureBoundary() { + const [clientHtml, serverHtml] = await Promise.all([ + highlightTs(CLIENT), + highlightTs(SERVER), + ]); + + return ( +
+
+

+ 03 - SECURE +

+

+ Keep secrets off the client. +

+

+ Strict layout splits client + and server schemas. Server keys never enter the client module graph. +

+
+ +
+
+ +

Browser

+ {/* biome-ignore lint/security/noDangerouslySetInnerHtml: static Shiki HTML from server */} +
+
+ +
+ +

Server only

+ {/* biome-ignore lint/security/noDangerouslySetInnerHtml: static Shiki HTML from server */} +
+
+
+
+ ); +} diff --git a/apps/www/components/page/type-safety-showcase.tsx b/apps/www/components/page/type-safety-showcase.tsx index 9f4bb4c01..bf4712c79 100644 --- a/apps/www/components/page/type-safety-showcase.tsx +++ b/apps/www/components/page/type-safety-showcase.tsx @@ -1,15 +1,21 @@ /** - * Simulated IDE autocomplete — proves typesafe env access without a video. + * Simulated IDE autocomplete - proves typesafe env access without a video. */ +import { WindowChrome } from "./window-chrome"; + export function TypeSafetyShowcase() { return (
-

02 — Typesafety

-

Stop guessing your env vars

-

- Full IDE autocomplete and type inference across your stack — from - editor to runtime. +

+ 02 - TYPESAFE +

+

+ No more guessing env vars. +

+

+ Get autocomplete and types in your editor, then the same guarantees at + runtime, and even build time.

@@ -17,19 +23,19 @@ export function TypeSafetyShowcase() { className="home-aurora__ide" role="img" aria-label="IDE showing env autocomplete for DATABASE_URL and PORT" + data-reveal + style={{ ["--reveal-delay" as string]: "140ms" }} > -
- - - - app.ts -
+
 					
 						import
-						{" { env } "}
+						{" { "}
+						env
+						{" } "}
 						from
-						{` "./env";\n\n`}
+						{` "./env"`}
+						{`;\n\n`}
 						const
 						{" db = "}
 						env
diff --git a/apps/www/components/page/video-demo.tsx b/apps/www/components/page/video-demo.tsx
index 02bcdfac7..8aee1c33c 100644
--- a/apps/www/components/page/video-demo.tsx
+++ b/apps/www/components/page/video-demo.tsx
@@ -5,6 +5,7 @@ import Image from "next/image";
 import BackgroundVideo from "next-video/background-video";
 import { useState } from "react";
 import demo from "~/videos/demo.mp4";
+import { WindowChrome } from "./window-chrome";
 
 const WIDTH = 2048;
 const HEIGHT = 1672;
@@ -13,7 +14,7 @@ const getAspectRatio = (width: number, height: number) =>
 	`${width} / ${height}`;
 
 /**
- * Product demo framed for the home workbench — no redrawn browser chrome.
+ * Product demo in a faux window frame (main-branch pattern, Aurora tokens).
  * Click opens the StackBlitz playground.
  */
 export function VideoDemo() {
@@ -38,6 +39,10 @@ export function VideoDemo() {
 				onClick={handleVideoClick}
 				aria-label="Open interactive demo in a new tab"
 			>
+				
 				
{videoError ? (
-
- basic · index.ts — click to open the live playground -
); } diff --git a/apps/www/components/page/window-chrome.tsx b/apps/www/components/page/window-chrome.tsx new file mode 100644 index 000000000..4f081c423 --- /dev/null +++ b/apps/www/components/page/window-chrome.tsx @@ -0,0 +1,29 @@ +/** + * Shared faux-window traffic lights + optional title. + * Used by CLI, IDE mock, before/after compare, and playground frame. + */ +export function WindowChrome({ + title, + className, +}: { + title?: string; + className?: string; +}) { + return ( + + ); +} diff --git a/apps/www/tokens.css b/apps/www/tokens.css index 10d7ffd34..5b7295632 100644 --- a/apps/www/tokens.css +++ b/apps/www/tokens.css @@ -20,6 +20,10 @@ --color-success: oklch(72% 0.14 155); --color-danger: oklch(65% 0.18 25); --color-danger-ink: oklch(78% 0.14 25); + --color-cmd: oklch(78% 0.19 55); + --color-traffic-close: oklch(68% 0.18 25); + --color-traffic-min: oklch(78% 0.14 85); + --color-traffic-max: oklch(72% 0.16 145); --font-display: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; --font-body: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; @@ -50,9 +54,12 @@ --ease-out: cubic-bezier(0.16, 1, 0.3, 1); --ease-in: cubic-bezier(0.7, 0, 0.84, 0); --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); + /* open-slide rise / blur-reveal */ + --ease-out-strong: cubic-bezier(0.23, 1, 0.32, 1); --dur-micro: 120ms; --dur-short: 220ms; --dur-long: 420ms; + --dur-reveal: 800ms; --rule-hair: 1px; --radius-pill: 999px; From 7633cbc8bd89f111e3743743f24e20389b142de4 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Sat, 1 Aug 2026 01:01:01 +0500 Subject: [PATCH 24/46] Tighten Declarative, Typesafe, and Secure pitch copy. Restore the declarative before/after framing, fold fail-fast into typesafe without conflating startup and request time, and sell the client/server boundary without naming layouts. Co-authored-by: Cursor --- .../page/before-after-compare-view.tsx | 21 ++++++++++++------- apps/www/components/page/secure-boundary.tsx | 6 +++--- .../components/page/type-safety-showcase.tsx | 7 ++++--- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/apps/www/components/page/before-after-compare-view.tsx b/apps/www/components/page/before-after-compare-view.tsx index 4f8fdab4b..c8962ec0a 100644 --- a/apps/www/components/page/before-after-compare-view.tsx +++ b/apps/www/components/page/before-after-compare-view.tsx @@ -63,27 +63,34 @@ export function BeforeAfterCompareView({

- 01 - FAIL-FAST + 01 - DECLARATIVE

- Catch bad configs before you boot. + + + Simplify, simplify, + {" "} + simplify. +

ArkEnv replaces your manual process.env checks with one - schema that validates at startup. Drop the separate interfaces, consts, - and error handling, and guarantee your app never crashes at runtime due - to a missing key. + schema. Drop the separate interfaces, consts, and error handling.

- {reduction}% less code. 100% runtime safety. + {reduction}% less code for the same guarantees

diff --git a/apps/www/components/page/secure-boundary.tsx b/apps/www/components/page/secure-boundary.tsx index b91b3d89e..7e3f91331 100644 --- a/apps/www/components/page/secure-boundary.tsx +++ b/apps/www/components/page/secure-boundary.tsx @@ -10,7 +10,7 @@ const SERVER = `export const env = arkenv({ });`; /** - * Strict-layout pitch: client vs server env boundary. + * Client / server env boundary pitch. * Examples mirror /docs/nextjs/layouts/strict. */ export async function SecureBoundary() { @@ -33,8 +33,8 @@ export async function SecureBoundary() { Keep secrets off the client.

- Strict layout splits client - and server schemas. Server keys never enter the client module graph. + ArkEnv isolates your frontend and backend variables. Server schemas + stay walled off, so private keys never enter the browser bundle.

diff --git a/apps/www/components/page/type-safety-showcase.tsx b/apps/www/components/page/type-safety-showcase.tsx index bf4712c79..0fa66a237 100644 --- a/apps/www/components/page/type-safety-showcase.tsx +++ b/apps/www/components/page/type-safety-showcase.tsx @@ -11,11 +11,12 @@ export function TypeSafetyShowcase() { 02 - TYPESAFE

- No more guessing env vars. + Editor autocomplete. Runtime fail-fast.

- Get autocomplete and types in your editor, then the same guarantees at - runtime, and even build time. + Get types in your IDE. Whether you validate at build time or inject + variables at startup, ArkEnv catches missing keys immediately, long + before they cause a crash deep in production.

From 41403f8e2c33d15108ec42ffaa1bebf3715b189a Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Sat, 1 Aug 2026 01:04:53 +0500 Subject: [PATCH 25/46] Strip AI-slop phrasing from landing pitch copy. Co-authored-by: Cursor --- apps/www/app/(home)/page.tsx | 8 ++++---- apps/www/components/page/agent-native-pitch.tsx | 9 ++++----- apps/www/components/page/before-after-compare-view.tsx | 6 +++--- apps/www/components/page/hero-faq.tsx | 2 +- apps/www/components/page/secure-boundary.tsx | 4 ++-- apps/www/components/page/type-safety-showcase.tsx | 7 +++---- 6 files changed, 17 insertions(+), 19 deletions(-) diff --git a/apps/www/app/(home)/page.tsx b/apps/www/app/(home)/page.tsx index 369ff2525..074cd8ba4 100644 --- a/apps/www/app/(home)/page.tsx +++ b/apps/www/app/(home)/page.tsx @@ -48,9 +48,9 @@ export default function HomePage() { className="home-aurora__summary rise-blur" style={{ animationDelay: "240ms" }} > - One function to validate, parse, and keep your env vars - typesafe. Use ArkType, Zod, or any Standard Schema. Run the CLI, or - let your agent handle the setup. + One function validates and types your env vars. Use + ArkType, Zod, or any Standard Schema. Run the CLI, or hand setup to + your agent.

@@ -77,7 +77,7 @@ export default function HomePage() { Try it live!

- Click the demo to open a fully interactive playground. + Click the demo to open the playground.

diff --git a/apps/www/components/page/agent-native-pitch.tsx b/apps/www/components/page/agent-native-pitch.tsx index b20085e30..26af780ce 100644 --- a/apps/www/components/page/agent-native-pitch.tsx +++ b/apps/www/components/page/agent-native-pitch.tsx @@ -15,13 +15,12 @@ export function AgentNativePitch() { 05 - AGENTIC

- Built for humans.{" "} - And their agents. + For humans and{" "} + their agents.

- Run the interactive CLI, or supercharge Cursor, Claude Code, or Codex - with our agent skill to set up your schema intelligently from your - existing .env file. + Run the interactive CLI, or point the agent skill at your{" "} + .env in Cursor, Claude Code, or Codex.

diff --git a/apps/www/components/page/before-after-compare-view.tsx b/apps/www/components/page/before-after-compare-view.tsx index c8962ec0a..a8cd48711 100644 --- a/apps/www/components/page/before-after-compare-view.tsx +++ b/apps/www/components/page/before-after-compare-view.tsx @@ -82,15 +82,15 @@ export function BeforeAfterCompareView({

- ArkEnv replaces your manual process.env checks with one - schema. Drop the separate interfaces, consts, and error handling. + Replace your manual process.env checks with one schema. + Skip the separate interfaces and error handling.

- {reduction}% less code for the same guarantees + {reduction}% less code, same checks

diff --git a/apps/www/components/page/hero-faq.tsx b/apps/www/components/page/hero-faq.tsx index 7b798bc8a..9acc68ebe 100644 --- a/apps/www/components/page/hero-faq.tsx +++ b/apps/www/components/page/hero-faq.tsx @@ -41,7 +41,7 @@ const FAQ = [ question: "Does ArkEnv support AI development tools like Claude Code or Cursor?", teaser: - "Yes. Grab the agent skill and your assistant keeps env schemas in sync as the code changes.", + "Yes. Install the agent skill and your assistant updates env schemas as the code changes.", }, ] as const; diff --git a/apps/www/components/page/secure-boundary.tsx b/apps/www/components/page/secure-boundary.tsx index 7e3f91331..003430add 100644 --- a/apps/www/components/page/secure-boundary.tsx +++ b/apps/www/components/page/secure-boundary.tsx @@ -33,8 +33,8 @@ export async function SecureBoundary() { Keep secrets off the client.

- ArkEnv isolates your frontend and backend variables. Server schemas - stay walled off, so private keys never enter the browser bundle. + ArkEnv isolates frontend and backend variables. You keep private keys + out of the browser bundle.

diff --git a/apps/www/components/page/type-safety-showcase.tsx b/apps/www/components/page/type-safety-showcase.tsx index 0fa66a237..d54cdd6d3 100644 --- a/apps/www/components/page/type-safety-showcase.tsx +++ b/apps/www/components/page/type-safety-showcase.tsx @@ -11,12 +11,11 @@ export function TypeSafetyShowcase() { 02 - TYPESAFE

- Editor autocomplete. Runtime fail-fast. + Editor autocomplete with fail-fast at runtime.

- Get types in your IDE. Whether you validate at build time or inject - variables at startup, ArkEnv catches missing keys immediately, long - before they cause a crash deep in production. + Your editor gets the types. ArkEnv rejects missing keys at build or + startup, before they reach production.

From 4b608a0e4f65fb9163c9eaeb1e6b2bfa95e24b30 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Sat, 1 Aug 2026 01:11:41 +0500 Subject: [PATCH 26/46] Apply formatter cleanup on landing pitch files. Co-authored-by: Cursor --- apps/www/app/(home)/aurora.css | 27 ++++++------------- apps/www/app/(home)/page.tsx | 25 ++++++++--------- apps/www/components/announcement-badge.tsx | 10 ++----- .../components/page/agent-native-pitch.tsx | 4 +-- .../page/before-after-compare-view.tsx | 8 +----- .../page/bring-your-own-validator-view.tsx | 4 ++- .../components/page/header-github-link.tsx | 2 +- apps/www/components/page/hero-faq.tsx | 3 +-- apps/www/components/page/secure-boundary.tsx | 2 +- .../components/page/type-safety-showcase.tsx | 2 +- 10 files changed, 33 insertions(+), 54 deletions(-) diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css index 7a97b4076..fcb0b620f 100644 --- a/apps/www/app/(home)/aurora.css +++ b/apps/www/app/(home)/aurora.css @@ -343,8 +343,7 @@ width: min(90rem, 100%); margin-inline: auto; /* Mobile: clear the floating pill without a tall empty band */ - padding: calc(3.75rem + var(--space-xs)) clamp(0.75rem, 2.5vw, 1.75rem) - var(--space-md); + padding: calc(3.75rem + var(--space-xs)) clamp(0.75rem, 2.5vw, 1.75rem) var(--space-md); } @media (min-width: 40rem) { @@ -378,8 +377,7 @@ gap: 0.4rem; padding: 0.25rem 0.25rem 0.25rem 0.25rem; border-radius: var(--radius-pill); - border: var(--rule-hair) solid - color-mix(in oklch, var(--color-accent) 35%, transparent); + border: var(--rule-hair) solid color-mix(in oklch, var(--color-accent) 35%, transparent); background: color-mix(in oklch, var(--color-accent) 10%, transparent); color: var(--color-ink); font-family: var(--font-display); @@ -406,8 +404,7 @@ height: 1.25rem; padding-inline: 0.45rem; border-radius: var(--radius-pill); - border: var(--rule-hair) solid - color-mix(in oklch, var(--color-accent) 30%, transparent); + border: var(--rule-hair) solid color-mix(in oklch, var(--color-accent) 30%, transparent); background: color-mix(in oklch, var(--color-accent) 22%, transparent); color: var(--color-accent); font-size: var(--text-xs); @@ -557,8 +554,7 @@ .home-aurora__faq-more a { color: var(--color-accent); text-decoration: none; - border-bottom: var(--rule-hair) solid - color-mix(in oklch, var(--color-accent) 45%, transparent); + border-bottom: var(--rule-hair) solid color-mix(in oklch, var(--color-accent) 45%, transparent); transition: color var(--dur-micro) var(--ease-out), border-color var(--dur-micro) var(--ease-out); @@ -1059,8 +1055,7 @@ .home-aurora__pitch-head h2 a { color: inherit; text-decoration: none; - border-bottom: var(--rule-hair) solid - color-mix(in oklch, var(--color-ink) 28%, transparent); + border-bottom: var(--rule-hair) solid color-mix(in oklch, var(--color-ink) 28%, transparent); transition: color var(--dur-micro) var(--ease-out), border-color var(--dur-micro) var(--ease-out); @@ -1097,8 +1092,7 @@ .home-aurora__pitch-head p a { color: var(--color-accent); text-decoration: none; - border-bottom: var(--rule-hair) solid - color-mix(in oklch, var(--color-accent) 45%, transparent); + border-bottom: var(--rule-hair) solid color-mix(in oklch, var(--color-accent) 45%, transparent); transition: color var(--dur-micro) var(--ease-out), border-color var(--dur-micro) var(--ease-out); @@ -1634,8 +1628,7 @@ .home-aurora__validator-note a { color: var(--color-accent); text-decoration: none; - border-bottom: var(--rule-hair) solid - color-mix(in oklch, var(--color-accent) 45%, transparent); + border-bottom: var(--rule-hair) solid color-mix(in oklch, var(--color-accent) 45%, transparent); transition: color var(--dur-micro) var(--ease-out), border-color var(--dur-micro) var(--ease-out); @@ -1696,11 +1689,7 @@ .home-aurora__secure-pane[data-side="server"] .home-aurora__secure-badge { color: var(--color-success); - border-bottom-color: color-mix( - in oklch, - var(--color-success) 30%, - var(--color-rule) - ); + border-bottom-color: color-mix(in oklch, var(--color-success) 30%, var(--color-rule)); } .home-aurora__terminal { diff --git a/apps/www/app/(home)/page.tsx b/apps/www/app/(home)/page.tsx index 074cd8ba4..797fe6b0c 100644 --- a/apps/www/app/(home)/page.tsx +++ b/apps/www/app/(home)/page.tsx @@ -88,7 +88,11 @@ export default function HomePage() {
-

+

Try ArkEnv now.

- + Read the docs @@ -194,13 +195,13 @@ export default function HomePage() {
  • - - @yamcodes - + + @yamcodes +
  • ) { return ( - - {newBadge ? ( - - ) : null} + + {newBadge ? : null} diff --git a/apps/www/components/page/agent-native-pitch.tsx b/apps/www/components/page/agent-native-pitch.tsx index 26af780ce..714bcff81 100644 --- a/apps/www/components/page/agent-native-pitch.tsx +++ b/apps/www/components/page/agent-native-pitch.tsx @@ -56,8 +56,8 @@ export function AgentNativePitch() {

    - Set up{" "} - /arkenv for my .env + Set up /arkenv for my + .env

    Worked for 2.4s

    diff --git a/apps/www/components/page/before-after-compare-view.tsx b/apps/www/components/page/before-after-compare-view.tsx index a8cd48711..b986408e9 100644 --- a/apps/www/components/page/before-after-compare-view.tsx +++ b/apps/www/components/page/before-after-compare-view.tsx @@ -1,12 +1,6 @@ "use client"; -import { - useEffect, - useId, - useRef, - useState, - type CSSProperties, -} from "react"; +import { type CSSProperties, useEffect, useId, useRef, useState } from "react"; import { WindowChrome } from "./window-chrome"; type BeforeAfterCompareViewProps = { diff --git a/apps/www/components/page/bring-your-own-validator-view.tsx b/apps/www/components/page/bring-your-own-validator-view.tsx index 2f0fb3c7c..25acac0b8 100644 --- a/apps/www/components/page/bring-your-own-validator-view.tsx +++ b/apps/www/components/page/bring-your-own-validator-view.tsx @@ -40,7 +40,9 @@ export function BringYourOwnValidatorView({

    Use ArkType, Zod, Valibot, or any{" "} - Standard Schema{" "} + + Standard Schema + {" "} library. You keep the same env API.

    diff --git a/apps/www/components/page/header-github-link.tsx b/apps/www/components/page/header-github-link.tsx index f3dfa89d6..802e81000 100644 --- a/apps/www/components/page/header-github-link.tsx +++ b/apps/www/components/page/header-github-link.tsx @@ -3,8 +3,8 @@ import { SiGithub } from "@icons-pack/react-simple-icons"; import { Star } from "lucide-react"; import { useEffect, useState } from "react"; -import { breakDownGithubUrl } from "~/lib/utils/github"; import { cn } from "~/lib/utils"; +import { breakDownGithubUrl } from "~/lib/utils/github"; /** * Single GitHub badge: icon + star count in one hit target. diff --git a/apps/www/components/page/hero-faq.tsx b/apps/www/components/page/hero-faq.tsx index 9acc68ebe..a06b837f9 100644 --- a/apps/www/components/page/hero-faq.tsx +++ b/apps/www/components/page/hero-faq.tsx @@ -106,8 +106,7 @@ export function HeroFaq() { Frequently asked questions.

    - Picked from the docs.{" "} - Read the full FAQ → + Picked from the docs. Read the full FAQ →

    diff --git a/apps/www/components/page/secure-boundary.tsx b/apps/www/components/page/secure-boundary.tsx index 003430add..7c9b52c85 100644 --- a/apps/www/components/page/secure-boundary.tsx +++ b/apps/www/components/page/secure-boundary.tsx @@ -1,5 +1,5 @@ -import { WindowChrome } from "./window-chrome"; import { highlightTs } from "./highlight-ts"; +import { WindowChrome } from "./window-chrome"; const CLIENT = `export const env = arkenv({ NEXT_PUBLIC_API_URL: "string", diff --git a/apps/www/components/page/type-safety-showcase.tsx b/apps/www/components/page/type-safety-showcase.tsx index d54cdd6d3..31228cf55 100644 --- a/apps/www/components/page/type-safety-showcase.tsx +++ b/apps/www/components/page/type-safety-showcase.tsx @@ -35,7 +35,7 @@ export function TypeSafetyShowcase() { {" } "} from {` "./env"`} - {`;\n\n`} + {";\n\n"} const {" db = "} env From 588ed5e850445a37522ac0811f335b2645bcf58c Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Sat, 1 Aug 2026 10:23:30 +0500 Subject: [PATCH 27/46] Hide the landing outro CTA on mobile. Co-authored-by: Cursor --- apps/www/app/(home)/aurora.css | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css index fcb0b620f..a60024ef8 100644 --- a/apps/www/app/(home)/aurora.css +++ b/apps/www/app/(home)/aurora.css @@ -1858,13 +1858,19 @@ box-shadow: var(--shadow-nav); } -/* Bookend · callback to the hero promise + install CTAs */ +/* Bookend · callback to the hero promise + install CTAs (desktop only) */ .home-aurora__outro { - margin-top: var(--space-2xl); - padding-block: var(--space-xl) var(--space-md); - border-top: var(--rule-hair) solid var(--color-rule); - display: grid; - gap: var(--space-sm); + display: none; +} + +@media (min-width: 40rem) { + .home-aurora__outro { + margin-top: var(--space-2xl); + padding-block: var(--space-xl) var(--space-md); + border-top: var(--rule-hair) solid var(--color-rule); + display: grid; + gap: var(--space-sm); + } } .home-aurora__outro-title { From a8b10429ac5938e4b9a744d999b7b0ee9d8c5dc9 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 6 Aug 2026 01:54:59 +0500 Subject: [PATCH 28/46] docs(www): refine homepage hero, section pitch copy, and flat layout security visuals --- apps/www/app/(home)/aurora.css | 343 ++++++++++++++---- apps/www/app/(home)/home-nav.tsx | 2 +- apps/www/app/(home)/page.tsx | 38 +- .../components/page/agent-native-pitch.tsx | 26 +- .../page/before-after-compare-view.tsx | 53 ++- .../components/page/before-after-compare.tsx | 2 +- .../page/bring-your-own-validator-view.tsx | 4 +- .../components/page/compatibility-rails.tsx | 6 +- apps/www/components/page/install-panel.tsx | 14 +- apps/www/components/page/secure-boundary.tsx | 121 ++++-- .../components/page/type-safety-showcase.tsx | 2 +- apps/www/components/page/video-demo.tsx | 96 ++--- apps/www/components/page/window-chrome.tsx | 10 +- apps/www/components/ui/new-badge.tsx | 7 +- 14 files changed, 528 insertions(+), 196 deletions(-) diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css index a60024ef8..8f622b163 100644 --- a/apps/www/app/(home)/aurora.css +++ b/apps/www/app/(home)/aurora.css @@ -300,18 +300,22 @@ .home-aurora__nav-cta { display: none; align-items: center; - padding: 0.4rem 0.85rem; + padding: 0.45rem 0.95rem; border-radius: var(--radius-pill); - background: var(--color-accent); - color: var(--color-accent-ink); + background: linear-gradient(135deg, oklch(82% 0.14 190) 0%, oklch(70% 0.15 205) 100%); + color: oklch(12% 0.02 200); font-family: var(--font-display); font-size: var(--text-sm); font-weight: 600; text-decoration: none; white-space: nowrap; + box-shadow: + 0 0 0 1px oklch(90% 0.1 195 / 0.3) inset, + 0 2px 10px oklch(74% 0.13 195 / 0.25); transition: transform var(--dur-micro) var(--ease-out), - opacity var(--dur-micro) var(--ease-out); + box-shadow var(--dur-short) var(--ease-out), + filter var(--dur-short) var(--ease-out); } @media (min-width: 40rem) { @@ -321,7 +325,21 @@ } .home-aurora__nav-cta:hover { - opacity: 0.92; + filter: brightness(1.08); + box-shadow: + 0 0 0 1px oklch(95% 0.1 195 / 0.5) inset, + 0 4px 18px oklch(74% 0.16 195 / 0.45); + transform: translateY(-1px); +} + +.home-aurora__nav-cta:hover span { + transform: translateX(3px); +} + +.home-aurora__nav-cta span { + display: inline-block; + margin-left: 0.2rem; + transition: transform var(--dur-short) var(--ease-out); } .home-aurora__nav-cta:active { @@ -341,6 +359,8 @@ .home-aurora__shell { width: min(90rem, 100%); + max-width: 100%; + min-width: 0; margin-inline: auto; /* Mobile: clear the floating pill without a tall empty band */ padding: calc(3.75rem + var(--space-xs)) clamp(0.75rem, 2.5vw, 1.75rem) var(--space-md); @@ -356,10 +376,18 @@ .home-aurora__intro { display: grid; gap: var(--space-sm); - max-width: none; + width: 100%; + max-width: 100%; + min-width: 0; padding-block: var(--space-2xs) var(--space-md); } +/* Marquee rails (and other wide kids) must not inflate the grid track */ +.home-aurora__intro > * { + min-width: 0; + max-width: 100%; +} + @media (min-width: 40rem) { .home-aurora__intro { gap: var(--space-md); @@ -412,6 +440,24 @@ box-shadow: none; } +@media (max-width: 40rem) { + .home-aurora__announce-new { + width: 0.5rem; + height: 0.5rem; + min-width: 0.5rem; + padding: 0; + margin-inline-start: 0.4rem; + border: none; + background: var(--color-accent); + border-radius: 50%; + box-shadow: 0 0 8px color-mix(in oklch, var(--color-accent) 60%, transparent); + } + + .home-aurora__announce-label { + margin-inline-start: 0.25rem; + } +} + .home-aurora__announce-new:hover { background: color-mix(in oklch, var(--color-accent) 30%, transparent); } @@ -448,13 +494,14 @@ .home-aurora__tagline { margin: 0; max-width: 100%; + min-width: 0; font-family: var(--font-display); - font-size: clamp(2.25rem, 7vw + 0.65rem, 5rem); + font-size: clamp(2.5rem, 9.5vw + 0.5rem, 5.85rem); line-height: 1.05; letter-spacing: -0.035em; color: var(--color-ink); font-weight: 500; - overflow-wrap: normal; + overflow-wrap: anywhere; } /* Keep the Geist lead on one line once the viewport can host it */ @@ -486,22 +533,24 @@ .home-aurora__summary { margin: 0; - max-width: 52ch; + max-width: min(54ch, 100%); + min-width: 0; font-family: var(--font-body); - font-size: clamp(1.05rem, 1vw + 0.8rem, 1.3rem); - line-height: 1.45; - color: var(--color-ink-2); + font-size: clamp(1.05rem, 1vw + 0.8rem, 1.25rem); + line-height: 1.5; + color: oklch(72% 0.02 200); font-weight: 400; + overflow-wrap: anywhere; } .home-aurora__summary code { font-family: var(--font-mono); - font-size: 0.92em; - font-weight: 500; - color: var(--color-ink); + font-size: 0.9em; + font-weight: 400; + color: var(--color-ink-2); padding: 0.1em 0.35em; border-radius: 0.25rem; - background: color-mix(in oklch, var(--color-paper-3) 80%, transparent); + background: color-mix(in oklch, var(--color-paper-3) 60%, transparent); border: var(--rule-hair) solid var(--color-rule); } @@ -509,10 +558,16 @@ display: grid; gap: var(--space-sm); margin-top: var(--space-2xs); - width: fit-content; + width: 100%; max-width: 100%; } +@media (min-width: 40rem) { + .home-aurora__install-row { + width: fit-content; + } +} + .home-aurora__install-row--outro { display: flex; flex-wrap: wrap; @@ -791,33 +846,33 @@ display: inline-flex; align-items: center; gap: 0.25rem; - padding: 0.15rem 0.1rem; + padding: 0.25rem 0.4rem; border: 0; border-radius: 0; background: transparent; - color: var(--color-muted); + color: var(--color-ink-2); font-family: var(--font-display); - font-size: var(--text-xs); - font-weight: 500; - letter-spacing: 0.02em; + font-size: var(--text-sm); + font-weight: 600; + letter-spacing: 0.01em; cursor: pointer; white-space: nowrap; - opacity: 0.75; + opacity: 0.7; transition: color var(--dur-micro) var(--ease-out), opacity var(--dur-micro) var(--ease-out); } .home-aurora__install-tab:hover { - color: var(--color-ink-2); + color: var(--color-ink); opacity: 1; } .home-aurora__install-tab[data-active="true"] { background: transparent; - color: var(--color-ink-2); + color: var(--color-accent); opacity: 1; - box-shadow: inset 0 -1px 0 color-mix(in oklch, var(--color-accent) 55%, transparent); + box-shadow: inset 0 -2px 0 var(--color-accent); } .home-aurora__install-tab:focus-visible { @@ -828,11 +883,11 @@ .home-aurora__install-copy { display: flex; align-items: center; - gap: var(--space-sm); + gap: var(--space-md); width: max-content; max-width: 100%; min-height: 3rem; - padding: 0.65rem 0.75rem 0.65rem 0.95rem; + padding: 0.65rem 1.15rem 0.65rem 1.25rem; border: var(--rule-hair) solid var(--color-rule); border-radius: var(--radius-frame); background: color-mix(in oklch, var(--color-paper-2) 88%, transparent); @@ -897,19 +952,31 @@ } .home-aurora__install-icon { - width: 1.25rem; - height: 1.25rem; + width: 0.9rem; + height: 0.9rem; flex-shrink: 0; color: var(--color-accent); - opacity: 0.9; + opacity: 0.75; + stroke-width: 1.5px; +} + +.home-aurora__install-prompt-symbol { + font-family: var(--font-outlier); + font-size: 0.9rem; + font-weight: 400; + line-height: 1; + flex-shrink: 0; + color: var(--color-accent); + opacity: 0.75; + user-select: none; } .home-aurora__install-code { flex: 1; min-width: 0; font-family: var(--font-outlier); - font-size: clamp(1rem, 0.4vw + 0.95rem, 1.2rem); - line-height: 1.3; + font-size: clamp(0.8125rem, 0.25vw + 0.8rem, 0.9375rem); + line-height: 1.4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -919,9 +986,9 @@ flex: 1; min-width: 0; font-family: var(--font-display); - font-size: clamp(1rem, 0.4vw + 0.95rem, 1.2rem); + font-size: clamp(0.8125rem, 0.25vw + 0.8rem, 0.9375rem); font-weight: 400; - line-height: 1.3; + line-height: 1.4; white-space: nowrap; } @@ -934,7 +1001,7 @@ align-items: center; gap: 0.4rem; flex-shrink: 0; - padding-left: var(--space-sm); + padding-left: var(--space-md); border-left: var(--rule-hair) solid var(--color-rule); color: var(--color-muted); align-self: stretch; @@ -993,11 +1060,17 @@ } .home-aurora__bench { - margin-top: var(--space-2xl); + margin-top: var(--space-xl); display: grid; gap: var(--space-md); } +@media (min-width: 40rem) { + .home-aurora__bench { + margin-top: var(--space-2xl); + } +} + .home-aurora__bench-head { display: grid; gap: var(--space-2xs); @@ -1135,6 +1208,10 @@ } /* Before / after compare - window frame + drag reveal */ +.home-aurora__compare-toggle { + display: none; +} + .home-aurora__compare-window { width: 100%; max-width: min(56rem, 100%); @@ -1314,12 +1391,55 @@ } @media (max-width: 39.99rem) { - /* Stack both panes - clip slider cuts mid-token on narrow screens */ + /* One snippet at a time - toggle instead of stacked panes / clip slider */ + .home-aurora__compare-toggle { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--space-xs); + margin-top: calc(var(--space-2xs) * -1); + } + + .home-aurora__compare-toggle-btn { + display: inline-flex; + align-items: center; + padding: 0.35rem 0.15rem; + border: 0; + border-radius: 0; + background: transparent; + color: var(--color-muted); + font-family: var(--font-display); + font-size: var(--text-xs); + font-weight: 500; + letter-spacing: 0.02em; + cursor: pointer; + white-space: nowrap; + opacity: 0.75; + transition: + color var(--dur-micro) var(--ease-out), + opacity var(--dur-micro) var(--ease-out); + } + + .home-aurora__compare-toggle-btn:hover { + color: var(--color-ink-2); + opacity: 1; + } + + .home-aurora__compare-toggle-btn[data-active="true"] { + color: var(--color-ink-2); + opacity: 1; + box-shadow: inset 0 -1px 0 color-mix(in oklch, var(--color-accent) 55%, transparent); + } + + .home-aurora__compare-toggle-btn:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; + } + .home-aurora__compare { --compare: 100%; min-height: 0; - display: flex; - flex-direction: column; + display: block; touch-action: auto; } @@ -1331,18 +1451,19 @@ position: relative; inset: auto; clip-path: none; - flex: 0 0 auto; + } + + .home-aurora__compare[data-mobile-view="arkenv"] .home-aurora__compare-pane--before, + .home-aurora__compare[data-mobile-view="old"] .home-aurora__compare-pane--after { + display: none; } .home-aurora__compare-pane--before { - order: 1; z-index: auto; - border-bottom: var(--rule-hair) solid var(--color-rule); background: var(--color-paper); } .home-aurora__compare-pane--after { - order: 2; z-index: auto; } @@ -1430,6 +1551,28 @@ pointer-events: none; } +.home-aurora__window-url-bar { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + margin-left: 0.5rem; + padding: 0.2rem 0.75rem; + border-radius: var(--radius-pill); + background: color-mix(in oklch, var(--color-paper-3) 80%, transparent); + border: var(--rule-hair) solid var(--color-rule); + min-width: 0; +} + +.home-aurora__window-url { + font-family: var(--font-mono); + font-size: 0.75rem; + color: var(--color-ink-2); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .home-aurora__ide-body { margin: 0; padding: var(--space-sm) var(--space-md); @@ -1557,32 +1700,33 @@ .home-aurora__validator-tab { display: inline-flex; align-items: center; - padding: 0.2rem 0.1rem; + padding: 0.25rem 0.4rem; border: 0; border-radius: 0; background: transparent; - color: var(--color-muted); + color: var(--color-ink-2); font-family: var(--font-display); - font-size: var(--text-xs); - font-weight: 500; - letter-spacing: 0.02em; + font-size: var(--text-sm); + font-weight: 600; + letter-spacing: 0.01em; cursor: pointer; white-space: nowrap; - opacity: 0.75; + opacity: 0.7; transition: color var(--dur-micro) var(--ease-out), opacity var(--dur-micro) var(--ease-out); } .home-aurora__validator-tab:hover { - color: var(--color-ink-2); + color: var(--color-ink); opacity: 1; } .home-aurora__validator-tab[data-active="true"] { - color: var(--color-ink-2); + background: transparent; + color: var(--color-accent); opacity: 1; - box-shadow: inset 0 -1px 0 color-mix(in oklch, var(--color-accent) 55%, transparent); + box-shadow: inset 0 -2px 0 var(--color-accent); } .home-aurora__validator-tab:focus-visible { @@ -1661,35 +1805,82 @@ .home-aurora__secure-pane { margin: 0; - display: grid; - grid-template-rows: auto auto 1fr; + display: flex; + flex-direction: column; border: var(--rule-hair) solid var(--color-rule); border-radius: var(--radius-frame); background: color-mix(in oklch, var(--color-paper) 88%, black); overflow: hidden; min-width: 0; + transition: border-color var(--dur-short) var(--ease-out); } .home-aurora__secure-pane[data-side="server"] { - border-color: color-mix(in oklch, var(--color-success) 35%, var(--color-rule)); + border-color: color-mix(in oklch, var(--color-success) 30%, var(--color-rule)); } -.home-aurora__secure-badge { - margin: 0; - padding: 0.4rem 0.85rem; - font-family: var(--font-outlier); +.home-aurora__secure-pane-head { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.5rem 0.85rem; + border-bottom: var(--rule-hair) solid var(--color-rule); + background: color-mix(in oklch, var(--color-paper-2) 60%, transparent); +} + +.home-aurora__secure-pane-title { + display: flex; + align-items: center; + gap: 0.5rem; + font-family: var(--font-display); font-size: var(--text-xs); - font-weight: 500; - letter-spacing: 0.08em; + font-weight: 600; + color: var(--color-ink); +} + +.home-aurora__secure-chip { + padding: 0.15rem 0.5rem; + border-radius: var(--radius-pill); + font-family: var(--font-outlier); + font-size: 0.7rem; + font-weight: 600; + letter-spacing: 0.04em; text-transform: uppercase; - color: var(--color-muted); - border-bottom: var(--rule-hair) solid var(--color-rule); - background: color-mix(in oklch, var(--color-paper-2) 70%, transparent); } -.home-aurora__secure-pane[data-side="server"] .home-aurora__secure-badge { +.home-aurora__secure-chip--public { + background: color-mix(in oklch, var(--color-accent) 15%, transparent); + color: var(--color-accent); + border: var(--rule-hair) solid color-mix(in oklch, var(--color-accent) 30%, transparent); +} + +.home-aurora__secure-chip--private { + background: color-mix(in oklch, var(--color-success) 15%, transparent); color: var(--color-success); - border-bottom-color: color-mix(in oklch, var(--color-success) 30%, var(--color-rule)); + border: var(--rule-hair) solid color-mix(in oklch, var(--color-success) 30%, transparent); +} + +.home-aurora__secure-chip--error { + background: color-mix(in oklch, var(--color-danger) 15%, transparent); + color: var(--color-danger-ink); + border: var(--rule-hair) solid color-mix(in oklch, var(--color-danger) 30%, transparent); +} + +.home-aurora__secure-vault-footer { + margin-top: auto; + padding: 0.55rem 0.85rem; + border-top: var(--rule-hair) solid var(--color-rule); + background: color-mix(in oklch, var(--color-paper-2) 40%, transparent); +} + +.home-aurora__secure-status { + display: flex; + align-items: center; + gap: 0.45rem; + font-family: var(--font-display); + font-size: var(--text-xs); + font-weight: 500; + color: var(--color-ink-2); } .home-aurora__terminal { @@ -2080,15 +2271,25 @@ flex-direction: column; gap: var(--space-xs); width: 100%; - max-width: 42rem; + max-width: min(42rem, 100%); + min-width: 0; margin: var(--space-sm) 0 0; padding: 0; overflow: hidden; } +.home-aurora .home-aurora__rails-host > div { + min-width: 0; + max-width: 100%; +} + .home-aurora .home-aurora__rails-host span.text-\[10px\] { - color: var(--color-muted); - font-family: var(--font-outlier); + color: var(--color-ink-2); + font-family: var(--font-display); + font-size: var(--text-xs); + font-weight: 600; + letter-spacing: 0.05em; + opacity: 0.9; } .home-aurora .home-aurora__rails-host a { @@ -2105,6 +2306,8 @@ /* StarUs mobile - quiet under Aurora */ .home-aurora .home-aurora__install-row a[href*="github.com"] { + width: 100% !important; + justify-content: center !important; background: color-mix(in oklch, var(--color-paper-2) 90%, transparent) !important; border: var(--rule-hair) solid var(--color-rule) !important; color: var(--color-ink) !important; diff --git a/apps/www/app/(home)/home-nav.tsx b/apps/www/app/(home)/home-nav.tsx index d99d6c2c6..8bd5b4f8e 100644 --- a/apps/www/app/(home)/home-nav.tsx +++ b/apps/www/app/(home)/home-nav.tsx @@ -29,7 +29,7 @@ export function HomeNav() {
    - Get started → + Get started
    diff --git a/apps/www/app/(home)/page.tsx b/apps/www/app/(home)/page.tsx index 797fe6b0c..64729d0d7 100644 --- a/apps/www/app/(home)/page.tsx +++ b/apps/www/app/(home)/page.tsx @@ -17,7 +17,7 @@ import { Logo } from "~/components/page/logo"; export const metadata: Metadata = { title: "ArkEnv", - description: "The happy path for environment variables.", + description: "The simple way to validate environment variables.", }; export default function HomePage() { @@ -37,20 +37,20 @@ export default function HomePage() { className="home-aurora__tagline rise-blur" style={{ animationDelay: "120ms" }} > - The happy path for{" "} + The simple way to validate{" "} - environment{" "} - variables + + environment variables + - .

    - One function validates and types your env vars. Use - ArkType, Zod, or any Standard Schema. Run the CLI, or hand setup to - your agent. + One function validates, parses, and keeps your env vars + typesafe. Use ArkType, Zod, or any Standard Schema. Fail fast, ship + faster.

    @@ -65,26 +65,18 @@ export default function HomePage() {
  • +
    +
    + +
    +
    + -
    -
    -

    - Try it live! -

    -

    - Click the demo to open the playground. -

    -
    -
    - -
    -
    -
    @@ -118,7 +110,7 @@ export default function HomePage() { > -

    The happy path for environment variables.

    +

    The simple way to validate environment variables.

    MIT License

    diff --git a/apps/www/components/page/agent-native-pitch.tsx b/apps/www/components/page/agent-native-pitch.tsx index 714bcff81..c781a19fc 100644 --- a/apps/www/components/page/agent-native-pitch.tsx +++ b/apps/www/components/page/agent-native-pitch.tsx @@ -12,15 +12,21 @@ export function AgentNativePitch() { >

    - 05 - AGENTIC + 05 / AGENT-READY

    - For humans and{" "} - their agents. + For humans.{" "} + And their agents.

    - Run the interactive CLI, or point the agent skill at your{" "} - .env in Cursor, Claude Code, or Codex. + Run the interactive CLI, or point the{" "} + + agent skill + {" "} + at your .env.example in Cursor, Claude Code, or Codex.

    @@ -56,8 +62,14 @@ export function AgentNativePitch() {

    - Set up /arkenv for my - .env + Set up{" "} + + /arkenv + {" "} + for my .env.example

    Worked for 2.4s

    diff --git a/apps/www/components/page/before-after-compare-view.tsx b/apps/www/components/page/before-after-compare-view.tsx index b986408e9..19ba2ecc2 100644 --- a/apps/www/components/page/before-after-compare-view.tsx +++ b/apps/www/components/page/before-after-compare-view.tsx @@ -9,9 +9,13 @@ type BeforeAfterCompareViewProps = { reduction: number; }; +type MobileView = "arkenv" | "old"; + +const NARROW_MQ = "(max-width: 39.99rem)"; + /** - * Scroll-driven before/after reveal. Drag still works as a manual override. - * Full 0–100% travel; pane padding keeps labels readable at the edges. + * Desktop: scroll-driven before/after reveal (drag still works as override). + * Mobile: ArkEnv snippet by default, with a toggle for the old way. */ export function BeforeAfterCompareView({ beforeHtml, @@ -20,12 +24,17 @@ export function BeforeAfterCompareView({ }: BeforeAfterCompareViewProps) { const [compare, setCompare] = useState(100); const [manual, setManual] = useState(false); + const [mobileView, setMobileView] = useState("arkenv"); const compareRef = useRef(null); const labelId = useId(); + const toggleId = useId(); useEffect(() => { if (manual) return; + const narrow = window.matchMedia(NARROW_MQ); + if (narrow.matches) return; + const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); if (reduceMotion.matches) { setCompare(50); @@ -61,7 +70,7 @@ export function BeforeAfterCompareView({ >

    - 01 - DECLARATIVE + 01 / DECLARATIVE

    +
    +

    + Code sample +

    + + +
    +
    - +
    {/* Invisible sizer so the frame fits the full old-way snippet */}
    diff --git a/apps/www/components/page/type-safety-showcase.tsx b/apps/www/components/page/type-safety-showcase.tsx index 31228cf55..468a2a292 100644 --- a/apps/www/components/page/type-safety-showcase.tsx +++ b/apps/www/components/page/type-safety-showcase.tsx @@ -8,7 +8,7 @@ export function TypeSafetyShowcase() {

    - 02 - TYPESAFE + 02 / TYPESAFE

    Editor autocomplete with fail-fast at runtime. diff --git a/apps/www/components/page/video-demo.tsx b/apps/www/components/page/video-demo.tsx index 8aee1c33c..f557a2e79 100644 --- a/apps/www/components/page/video-demo.tsx +++ b/apps/www/components/page/video-demo.tsx @@ -1,6 +1,6 @@ "use client"; -import { ArrowUpRight } from "lucide-react"; +import { ArrowRight, ArrowUpRight } from "lucide-react"; import Image from "next/image"; import BackgroundVideo from "next-video/background-video"; import { useState } from "react"; @@ -31,48 +31,58 @@ export function VideoDemo() { }; return ( -
    -

    - - + + + + + View more examples + + +
    ); } diff --git a/apps/www/components/page/window-chrome.tsx b/apps/www/components/page/window-chrome.tsx index 4f081c423..8b3dea7f3 100644 --- a/apps/www/components/page/window-chrome.tsx +++ b/apps/www/components/page/window-chrome.tsx @@ -1,12 +1,14 @@ /** - * Shared faux-window traffic lights + optional title. + * Shared faux-window traffic lights + optional title or URL address bar. * Used by CLI, IDE mock, before/after compare, and playground frame. */ export function WindowChrome({ title, + url, className, }: { title?: string; + url?: string; className?: string; }) { return ( @@ -21,7 +23,11 @@ export function WindowChrome({ - {title ? ( + {url ? ( +
    + {url} +
    + ) : title ? ( {title} ) : null} diff --git a/apps/www/components/ui/new-badge.tsx b/apps/www/components/ui/new-badge.tsx index 326e0b2bf..b3cf9f5d3 100644 --- a/apps/www/components/ui/new-badge.tsx +++ b/apps/www/components/ui/new-badge.tsx @@ -4,9 +4,12 @@ import { Badge } from "./badge"; export function NewBadge({ className }: { className?: string }) { return ( - New + New ); } From 5717d368671ca18fcdda4f8bd0d1ba128c312c06 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 6 Aug 2026 01:55:26 +0500 Subject: [PATCH 29/46] docs(www): update section 02 typesafe pitch with side-by-side IDE autocomplete and terminal runtime error --- apps/www/app/(home)/aurora.css | 15 +++ .../components/page/type-safety-showcase.tsx | 117 ++++++++++++------ 2 files changed, 93 insertions(+), 39 deletions(-) diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css index 8f622b163..69ff07319 100644 --- a/apps/www/app/(home)/aurora.css +++ b/apps/www/app/(home)/aurora.css @@ -1666,6 +1666,21 @@ } } +/* Typesafe split (IDE Autocomplete + Terminal Error) */ +.home-aurora__typesafe-split { + display: grid; + gap: var(--space-sm); + width: 100%; + max-width: 56rem; +} + +@media (min-width: 48rem) { + .home-aurora__typesafe-split { + grid-template-columns: 1fr 1fr; + align-items: stretch; + } +} + /* Agent split */ .home-aurora__agent-split { display: grid; diff --git a/apps/www/components/page/type-safety-showcase.tsx b/apps/www/components/page/type-safety-showcase.tsx index 468a2a292..90652b1e2 100644 --- a/apps/www/components/page/type-safety-showcase.tsx +++ b/apps/www/components/page/type-safety-showcase.tsx @@ -1,5 +1,5 @@ /** - * Simulated IDE autocomplete - proves typesafe env access without a video. + * Simulated IDE autocomplete + Terminal fail-fast runtime output side-by-side. */ import { WindowChrome } from "./window-chrome"; @@ -14,51 +14,90 @@ export function TypeSafetyShowcase() { Editor autocomplete with fail-fast at runtime.

    - Your editor gets the types. ArkEnv rejects missing keys at build or - startup, before they reach production. + Your editor gets perfect types. ArkEnv rejects missing or invalid keys + at build or startup, before they reach production.

    - -
    -					
    -						import
    -						{" { "}
    -						env
    -						{" } "}
    -						from
    -						{` "./env"`}
    -						{";\n\n"}
    -						const
    -						{" db = "}
    -						env
    -						.
    -						
    -				
    - + {/* Left Window: IDE Autocomplete */} +
    + +
    +						
    +							import
    +							{" { "}
    +							env
    +							{" } "}
    +							from
    +							{` "./env"`}
    +							{";\n\n"}
    +							const
    +							{" db = "}
    +							env
    +							.
    +							
    +					
    + +
    + + {/* Right Window: Terminal Fail-Fast Runtime Output */} +
    + +
    +
    + + Runtime Error + + + Process Exited (1) + +
    +
    + [ArkEnvError] Invalid environment variables: +
    +
    +
    + •{" "} + + DATABASE_URL + + : Expected string.url (got missing) +
    +
    + • PORT: + Expected integer <= 65535 (got "invalid") +
    +
    +
    +
    ); From f1f9e19318d656e459f1d5da1e0f3baaf33cb0f1 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 6 Aug 2026 01:58:16 +0500 Subject: [PATCH 30/46] docs(www): tabbed layout for section 03 left column (src/env.ts <-> app-client.js) --- .../components/page/secure-boundary-view.tsx | 163 ++++++++++++++++++ apps/www/components/page/secure-boundary.tsx | 106 +----------- 2 files changed, 165 insertions(+), 104 deletions(-) create mode 100644 apps/www/components/page/secure-boundary-view.tsx diff --git a/apps/www/components/page/secure-boundary-view.tsx b/apps/www/components/page/secure-boundary-view.tsx new file mode 100644 index 000000000..80106e164 --- /dev/null +++ b/apps/www/components/page/secure-boundary-view.tsx @@ -0,0 +1,163 @@ +"use client"; + +import { useId, useState } from "react"; +import { WindowChrome } from "./window-chrome"; + +type SecureBoundaryViewProps = { + envHtml: string; + bundleHtml: string; +}; + +type Tab = "schema" | "bundle"; + +export function SecureBoundaryView({ + envHtml, + bundleHtml, +}: SecureBoundaryViewProps) { + const [activeTab, setActiveTab] = useState("schema"); + const baseId = useId(); + + return ( +
    +
    +

    + 03 / SECURE +

    +

    + Keep secrets off the client. +

    +

    + Define a single{" "} + + flat schema + + . Secrets are stripped at build time; accidental client access throws + during SSR. +

    +
    + +
    + {/* Left Column: Tabbed Switcher (src/env.ts <-> .next/static/chunks/app-client.js) */} +
    +
    + + +
    + +
    + +
    +
    + +

    + {activeTab === "schema" ? ( + <> + Define server & client variables in one flat schema. Non- + NEXT_PUBLIC_ keys are automatically identified. + + ) : ( + <> + Secret values like DATABASE_URL{" "} + ("postgresql://db...") are stripped at build time. + Only public values reach the client. + + )} +

    +
    + + {/* Right Column: Anchored SSR Error Overlay */} +
    + +
    +
    + + Unhandled Runtime Error + + SSR Exception +
    +
    +
    + Uncaught Error: +
    +
    + ArkEnvError: Cannot access server-only key + "DATABASE_URL" on client. +
    +
    +
    +
    + Call Stack +
    +
    + at{" "} + + Proxy.<anonymous> + {" "} + (src/env.ts:14) +
    +
    + at Header{" "} + (app/components/header.tsx:5) +
    +
    +
    +
    +
    +
    + ); +} diff --git a/apps/www/components/page/secure-boundary.tsx b/apps/www/components/page/secure-boundary.tsx index e09595234..e757f73e3 100644 --- a/apps/www/components/page/secure-boundary.tsx +++ b/apps/www/components/page/secure-boundary.tsx @@ -1,5 +1,5 @@ import { highlightTs } from "./highlight-ts"; -import { WindowChrome } from "./window-chrome"; +import { SecureBoundaryView } from "./secure-boundary-view"; const FLAT_ENV_CODE = `import arkenv from "@/generated/env.gen"; @@ -23,107 +23,5 @@ export async function SecureBoundary() { highlightTs(COMPILED_BUNDLE_CODE), ]); - return ( -
    -
    -

    - 03 / SECURE -

    -

    - Keep secrets off the client. -

    -

    - Define a single{" "} - - flat schema - - . Secrets are stripped at build time; accidental client access throws - during SSR. -

    -
    - -
    - {/* Left Column: Stacked src/env.ts & Compiled JS Chunk */} -
    - {/* src/env.ts */} -
    - -
    -
    - - {/* .next/static/chunks/app-client.js */} -
    - -
    -
    - -

    - Secret values like DATABASE_URL{" "} - ("postgresql://db...") are stripped at build time. Only - public values reach the client. -

    -
    - - {/* Right Column: Runtime Proxy Error Overlay */} -
    - -
    -
    - - Unhandled Runtime Error - - SSR Exception -
    -
    -
    - Uncaught Error: -
    -
    - ArkEnvError: Cannot access server-only key - "DATABASE_URL" on client. -
    -
    -
    -
    - Call Stack -
    -
    - at{" "} - - Proxy.<anonymous> - {" "} - (src/env.ts:14) -
    -
    - at Header{" "} - (app/components/header.tsx:5) -
    -
    -
    -
    -
    -
    - ); + return ; } From 2df6e03ca06bb3b3367dd916461229a5bcffaab3 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 6 Aug 2026 01:59:31 +0500 Subject: [PATCH 31/46] docs(www): embed tab switcher into WindowChrome header for perfect top alignment in Section 03 --- .../components/page/secure-boundary-view.tsx | 77 +++++++++---------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/apps/www/components/page/secure-boundary-view.tsx b/apps/www/components/page/secure-boundary-view.tsx index 80106e164..ca0ae8546 100644 --- a/apps/www/components/page/secure-boundary-view.tsx +++ b/apps/www/components/page/secure-boundary-view.tsx @@ -48,50 +48,49 @@ export function SecureBoundaryView({ data-reveal style={{ ["--reveal-delay" as string]: "140ms" }} > - {/* Left Column: Tabbed Switcher (src/env.ts <-> .next/static/chunks/app-client.js) */} + {/* Left Column: Window with integrated tab switcher */}
    -
    - - -
    -
    - +
    Date: Thu, 6 Aug 2026 01:59:56 +0500 Subject: [PATCH 32/46] docs(www): stabilize window code container height in Section 03 tab switcher to eliminate vertical layout shift --- apps/www/components/page/secure-boundary-view.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/www/components/page/secure-boundary-view.tsx b/apps/www/components/page/secure-boundary-view.tsx index ca0ae8546..130938c55 100644 --- a/apps/www/components/page/secure-boundary-view.tsx +++ b/apps/www/components/page/secure-boundary-view.tsx @@ -92,7 +92,7 @@ export function SecureBoundaryView({
    -

    +

    {activeTab === "schema" ? ( <> Define server & client variables in one flat schema. Non- From 7116f734ede02531f9ab13a53b2c0e84e82664ff Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 6 Aug 2026 02:01:08 +0500 Subject: [PATCH 33/46] docs(www): overlay both code panes and subtext paragraphs with opacity crossfade to eliminate height jumps --- .../components/page/secure-boundary-view.tsx | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/apps/www/components/page/secure-boundary-view.tsx b/apps/www/components/page/secure-boundary-view.tsx index 130938c55..21607039b 100644 --- a/apps/www/components/page/secure-boundary-view.tsx +++ b/apps/www/components/page/secure-boundary-view.tsx @@ -91,29 +91,35 @@ export function SecureBoundaryView({

    -
    +
    +
    +
    +
    -

    - {activeTab === "schema" ? ( - <> - Define server & client variables in one flat schema. Non- - NEXT_PUBLIC_ keys are automatically identified. - - ) : ( - <> - Secret values like DATABASE_URL{" "} - ("postgresql://db...") are stripped at build time. - Only public values reach the client. - - )} -

    +
    +

    + Define server & client variables in one flat schema. Non- + NEXT_PUBLIC_ keys are automatically identified. +

    +

    + Secret values like DATABASE_URL{" "} + ("postgresql://db...") are stripped at build time. Only + public values reach the client. +

    +
    {/* Right Column: Anchored SSR Error Overlay */} From 9e73c4813d14d0cd980069bae16dccacf46ade29 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 6 Aug 2026 02:07:27 +0500 Subject: [PATCH 34/46] docs(www): anchor left window height to larger schema content so size remains strictly constant when toggling tabs --- apps/www/components/page/secure-boundary-view.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/www/components/page/secure-boundary-view.tsx b/apps/www/components/page/secure-boundary-view.tsx index 21607039b..db3aff0f7 100644 --- a/apps/www/components/page/secure-boundary-view.tsx +++ b/apps/www/components/page/secure-boundary-view.tsx @@ -91,29 +91,29 @@ export function SecureBoundaryView({
    -
    +
    -
    +

    Define server & client variables in one flat schema. Non- NEXT_PUBLIC_ keys are automatically identified.

    Secret values like DATABASE_URL{" "} ("postgresql://db...") are stripped at build time. Only From 4254188e7b2d83b96bbb312a64610e110b692b31 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 6 Aug 2026 02:08:48 +0500 Subject: [PATCH 35/46] docs(www): update section 02 terminal error output to show realistic developer typos (missing protocol & typo in port) --- apps/www/components/page/type-safety-showcase.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/www/components/page/type-safety-showcase.tsx b/apps/www/components/page/type-safety-showcase.tsx index 90652b1e2..edd4d6f98 100644 --- a/apps/www/components/page/type-safety-showcase.tsx +++ b/apps/www/components/page/type-safety-showcase.tsx @@ -83,17 +83,18 @@ export function TypeSafetyShowcase() {

    [ArkEnvError] Invalid environment variables:
    -
    +
    •{" "} DATABASE_URL - : Expected string.url (got missing) + : Must be a valid URL starting with postgresql:// or mysql:// + (got "localhost/db")
    PORT: - Expected integer <= 65535 (got "invalid") + Expected integer between 0 and 65535 (got "8080a")
    From 5d2eb7f3b6a93ac0f2e8c9b1173a9fb3f33fd7fc Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 6 Aug 2026 02:11:19 +0500 Subject: [PATCH 36/46] docs(www): align section 02 terminal error format with core ArkEnvError output (Errors found while validating environment variables & path (was ...)) --- apps/www/app/(home)/aurora.css | 2 +- .../components/page/type-safety-showcase.tsx | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css index 69ff07319..6208244ff 100644 --- a/apps/www/app/(home)/aurora.css +++ b/apps/www/app/(home)/aurora.css @@ -600,7 +600,7 @@ .home-aurora__faq-head p { margin: 0; font-family: var(--font-body); - font-size: var(--text-sm); + font-size: clamp(1rem, 0.4vw + 0.85rem, 1.15rem); line-height: 1.5; color: var(--color-ink-2); } diff --git a/apps/www/components/page/type-safety-showcase.tsx b/apps/www/components/page/type-safety-showcase.tsx index edd4d6f98..5cc0f68db 100644 --- a/apps/www/components/page/type-safety-showcase.tsx +++ b/apps/www/components/page/type-safety-showcase.tsx @@ -81,20 +81,19 @@ export function TypeSafetyShowcase() {
    - [ArkEnvError] Invalid environment variables: + ArkEnvError: Errors found while validating environment variables
    -
    +
    - •{" "} - + DATABASE_URL - - : Must be a valid URL starting with postgresql:// or mysql:// - (got "localhost/db") + {" "} + must be a valid URL starting with postgresql:// or mysql:// (was + "localhost/db")
    - • PORT: - Expected integer between 0 and 65535 (got "8080a") + PORT must + be an integer between 0 and 65535 (was "8080a")
    From 01cc5cafd92d0a1df10ed01fb19f3881aacb5ba5 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 6 Aug 2026 02:11:44 +0500 Subject: [PATCH 37/46] docs(www): increase FAQ question and answer font sizes for improved readability --- apps/www/app/(home)/aurora.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css index 6208244ff..05aa75c63 100644 --- a/apps/www/app/(home)/aurora.css +++ b/apps/www/app/(home)/aurora.css @@ -647,11 +647,11 @@ justify-content: space-between; gap: var(--space-sm); width: 100%; - padding: 0.85rem 0; + padding: 1.05rem 0; border: 0; background: transparent; font-family: var(--font-display); - font-size: var(--text-sm); + font-size: clamp(1rem, 0.4vw + 0.85rem, 1.15rem); font-weight: 500; color: var(--color-ink); text-align: left; @@ -744,7 +744,7 @@ margin: 0 0 var(--space-sm); max-width: 48ch; font-family: var(--font-body); - font-size: var(--text-sm); + font-size: clamp(0.95rem, 0.35vw + 0.8rem, 1.05rem); line-height: 1.55; color: var(--color-ink-2); transform: translateY(-0.35rem); From a825f355761953bee2cd117cd1f2d2703a691dcc Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 6 Aug 2026 02:15:39 +0500 Subject: [PATCH 38/46] docs(www): format .env and .env.example with code tags in FAQ questions and answers --- apps/www/components/page/hero-faq.tsx | 32 +++++++++++++++++++++------ 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/www/components/page/hero-faq.tsx b/apps/www/components/page/hero-faq.tsx index a06b837f9..76ca9a1d6 100644 --- a/apps/www/components/page/hero-faq.tsx +++ b/apps/www/components/page/hero-faq.tsx @@ -6,7 +6,12 @@ import { useCallback, useEffect, useId, useState } from "react"; * Homepage FAQ picks - questions + anchors match `/docs/arkenv/faq` (source of truth). * Keep answers as short teasers; link out for the full write-up. */ -const FAQ = [ +const FAQ: readonly { + id: string; + href: string; + question: React.ReactNode; + teaser: React.ReactNode; +}[] = [ { id: "why", href: "/docs/arkenv/faq#why-do-i-need-arkenv", @@ -17,9 +22,17 @@ const FAQ = [ { id: "env-files", href: "/docs/arkenv/faq#does-arkenv-load-my-env-files", - question: "Does ArkEnv load my `.env` files?", - teaser: - "No. ArkEnv validates the env your framework already loaded. It does not read `.env` files itself.", + question: ( + <> + Does ArkEnv load my .env files? + + ), + teaser: ( + <> + No. ArkEnv validates the env your framework already loaded. It does not + read .env files itself. + + ), }, { id: "arktype", @@ -40,10 +53,15 @@ const FAQ = [ href: "/docs/arkenv/faq#does-arkenv-support-ai-development-tools-like-claude-code-or-cursor", question: "Does ArkEnv support AI development tools like Claude Code or Cursor?", - teaser: - "Yes. Install the agent skill and your assistant updates env schemas as the code changes.", + teaser: ( + <> + Yes. Point the agent skill at your .env.example to + intelligently set up validation and guide your assistant through schema + maintenance. + + ), }, -] as const; +]; const DOCS_FAQ_HREF = "/docs/arkenv/faq"; From 469c565ca379702e14526f24a6274cc9297ce4f1 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 6 Aug 2026 02:16:35 +0500 Subject: [PATCH 39/46] docs(www): link AI Agent Skill to /docs/arkenv/integrations/ai/skills in FAQ documentation --- apps/www/content/docs/arkenv/faq.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/content/docs/arkenv/faq.mdx b/apps/www/content/docs/arkenv/faq.mdx index e54926c16..7030831da 100644 --- a/apps/www/content/docs/arkenv/faq.mdx +++ b/apps/www/content/docs/arkenv/faq.mdx @@ -149,7 +149,7 @@ Read the [CLI reference docs](/docs/cli) for a complete list of flags and capabi ## Does ArkEnv support AI development tools like Claude Code or Cursor? -Yes. ArkEnv is optimized for modern development workflows and features a **dedicated AI Agent Skill**. Providing this context to LLM-driven tools (like Claude Code, Cursor, Codex or Antigravity) allows your AI assistant to **automatically maintain, update, and tighten environment schemas** as your application code evolves, matching schema changes to code changes seamlessly. +Yes. ArkEnv is optimized for modern development workflows and features a dedicated [**AI Agent Skill**](/docs/arkenv/integrations/ai/skills). Providing this context to LLM-driven tools (like Claude Code, Cursor, Codex or Antigravity) allows your AI assistant to **automatically maintain, update, and tighten environment schemas** as your application code evolves, matching schema changes to code changes seamlessly. ## Is it safe to use in production? From bb66d87657db736db1377cbfc7b2806434514f79 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 6 Aug 2026 02:19:47 +0500 Subject: [PATCH 40/46] docs(www): update logo icon SVG and favicon to cyan/aqua accent color, and adjust digital-accent hero line wrap breakpoint --- apps/www/app/(home)/aurora.css | 4 ++-- apps/www/components/page/logo.tsx | 4 ++-- apps/www/public/assets/icon.svg | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css index 05aa75c63..9012bb3de 100644 --- a/apps/www/app/(home)/aurora.css +++ b/apps/www/app/(home)/aurora.css @@ -518,14 +518,14 @@ color: inherit; } -/* Prefer one line; wrap between the two words when the viewport is too narrow */ +/* Allow wrapping between the two words when the viewport is too narrow for single line */ .home-aurora__digital-accent { color: var(--color-accent); display: inline; white-space: normal; } -@media (min-width: 36rem) { +@media (min-width: 62rem) { .home-aurora__digital-accent { white-space: nowrap; } diff --git a/apps/www/components/page/logo.tsx b/apps/www/components/page/logo.tsx index dc9753d7d..efca15318 100644 --- a/apps/www/components/page/logo.tsx +++ b/apps/www/components/page/logo.tsx @@ -25,7 +25,7 @@ export function Logo({ className="size-7" > \ No newline at end of file + \ No newline at end of file From f807e35c502bf04206e6b1ff450611481b56543f Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 6 Aug 2026 02:22:17 +0500 Subject: [PATCH 41/46] docs(www): refine Why do I need ArkEnv FAQ teaser to eliminate slop and deliver punchy single-paragraph summary --- apps/www/components/page/hero-faq.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/www/components/page/hero-faq.tsx b/apps/www/components/page/hero-faq.tsx index 76ca9a1d6..491ea3f3e 100644 --- a/apps/www/components/page/hero-faq.tsx +++ b/apps/www/components/page/hero-faq.tsx @@ -16,8 +16,15 @@ const FAQ: readonly { id: "why", href: "/docs/arkenv/faq#why-do-i-need-arkenv", question: "Why do I need ArkEnv?", - teaser: - 'Env vars go missing in CI, turn "false" into a truthy string, and fail at runtime. ArkEnv validates at boot so your process matches the schema before app code runs.', + teaser: ( + <> + Unvalidated env vars fail silently: missing keys break deployments, + unparsed booleans ("false") evaluate as truthy, + and unannounced schema changes trigger untraceable production bugs. + ArkEnv enforces your configuration schema at boot before application + code runs. + + ), }, { id: "env-files", From 9ffc05fba5adca7536d79103526876c05e8a6dbe Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 6 Aug 2026 02:24:49 +0500 Subject: [PATCH 42/46] docs(www): apply stop-slop pass to Why do I need ArkEnv FAQ teaser --- apps/www/components/page/hero-faq.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/www/components/page/hero-faq.tsx b/apps/www/components/page/hero-faq.tsx index 491ea3f3e..3127ac109 100644 --- a/apps/www/components/page/hero-faq.tsx +++ b/apps/www/components/page/hero-faq.tsx @@ -18,11 +18,11 @@ const FAQ: readonly { question: "Why do I need ArkEnv?", teaser: ( <> - Unvalidated env vars fail silently: missing keys break deployments, - unparsed booleans ("false") evaluate as truthy, - and unannounced schema changes trigger untraceable production bugs. - ArkEnv enforces your configuration schema at boot before application - code runs. + Environment variables fail silently when you least expect it: CI + deployments miss required keys, "false" evaluates + as a truthy string, and unannounced schema updates break downstream + code. ArkEnv checks your process against your schema at boot, halting + broken builds before your application code ever executes. ), }, From 3836b64a91250de44c7bd6436fbeb89f8d591bf4 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 6 Aug 2026 02:26:36 +0500 Subject: [PATCH 43/46] docs(www): soften FAQ answer font-weight and trim Why do I need ArkEnv teaser length --- apps/www/app/(home)/aurora.css | 3 ++- apps/www/components/page/hero-faq.tsx | 8 +++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css index 9012bb3de..d112ac2f8 100644 --- a/apps/www/app/(home)/aurora.css +++ b/apps/www/app/(home)/aurora.css @@ -745,8 +745,9 @@ max-width: 48ch; font-family: var(--font-body); font-size: clamp(0.95rem, 0.35vw + 0.8rem, 1.05rem); + font-weight: 400; line-height: 1.55; - color: var(--color-ink-2); + color: color-mix(in oklch, var(--color-ink-2) 80%, var(--color-muted)); transform: translateY(-0.35rem); opacity: 0.4; transition: diff --git a/apps/www/components/page/hero-faq.tsx b/apps/www/components/page/hero-faq.tsx index 3127ac109..61d933e49 100644 --- a/apps/www/components/page/hero-faq.tsx +++ b/apps/www/components/page/hero-faq.tsx @@ -18,11 +18,9 @@ const FAQ: readonly { question: "Why do I need ArkEnv?", teaser: ( <> - Environment variables fail silently when you least expect it: CI - deployments miss required keys, "false" evaluates - as a truthy string, and unannounced schema updates break downstream - code. ArkEnv checks your process against your schema at boot, halting - broken builds before your application code ever executes. + Missing CI keys, unparsed "false" booleans, and + silent schema drift trigger hard-to-trace bugs. ArkEnv validates your + env at boot before application code runs. ), }, From 4cbecf85fd73bfddb73292ee5e3912457a42c9ee Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 6 Aug 2026 02:27:03 +0500 Subject: [PATCH 44/46] docs(www): restore previous comprehensive prose for Why do I need ArkEnv FAQ teaser --- apps/www/components/page/hero-faq.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/www/components/page/hero-faq.tsx b/apps/www/components/page/hero-faq.tsx index 61d933e49..3127ac109 100644 --- a/apps/www/components/page/hero-faq.tsx +++ b/apps/www/components/page/hero-faq.tsx @@ -18,9 +18,11 @@ const FAQ: readonly { question: "Why do I need ArkEnv?", teaser: ( <> - Missing CI keys, unparsed "false" booleans, and - silent schema drift trigger hard-to-trace bugs. ArkEnv validates your - env at boot before application code runs. + Environment variables fail silently when you least expect it: CI + deployments miss required keys, "false" evaluates + as a truthy string, and unannounced schema updates break downstream + code. ArkEnv checks your process against your schema at boot, halting + broken builds before your application code ever executes. ), }, From a7ef74d8c82c0d249694ce6e2a6da297849ab375 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 6 Aug 2026 02:36:03 +0500 Subject: [PATCH 45/46] docs(www): set final copy for Why do I need ArkEnv FAQ teaser --- apps/www/components/page/hero-faq.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/www/components/page/hero-faq.tsx b/apps/www/components/page/hero-faq.tsx index 3127ac109..cd12b2425 100644 --- a/apps/www/components/page/hero-faq.tsx +++ b/apps/www/components/page/hero-faq.tsx @@ -18,11 +18,12 @@ const FAQ: readonly { question: "Why do I need ArkEnv?", teaser: ( <> - Environment variables fail silently when you least expect it: CI - deployments miss required keys, "false" evaluates - as a truthy string, and unannounced schema updates break downstream - code. ArkEnv checks your process against your schema at boot, halting - broken builds before your application code ever executes. + When environment variables fail, the results can be silent, unexpected, + and catastrophic. CI deployments miss required keys, stringified + booleans like "false" evaluate as truthy, and + schema updates break downstream code. ArkEnv protects against this by + validating the environment against your schema before a single line of + code runs. ), }, From e7939e0550807c64cad7828006b0430d9a26316c Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 6 Aug 2026 02:37:21 +0500 Subject: [PATCH 46/46] docs(www): format Why do I need ArkEnv teaser into two distinct paragraphs --- apps/www/components/page/hero-faq.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/www/components/page/hero-faq.tsx b/apps/www/components/page/hero-faq.tsx index cd12b2425..c31ae5432 100644 --- a/apps/www/components/page/hero-faq.tsx +++ b/apps/www/components/page/hero-faq.tsx @@ -18,12 +18,16 @@ const FAQ: readonly { question: "Why do I need ArkEnv?", teaser: ( <> - When environment variables fail, the results can be silent, unexpected, - and catastrophic. CI deployments miss required keys, stringified - booleans like "false" evaluate as truthy, and - schema updates break downstream code. ArkEnv protects against this by - validating the environment against your schema before a single line of - code runs. +

    + When environment variables fail, the results can be silent, + unexpected, and catastrophic. CI deployments miss required keys, + stringified booleans like "false" evaluate as + truthy, and schema updates break downstream code. +

    +

    + ArkEnv protects against this by validating the environment against + your schema before a single line of code runs. +

    ), },