diff --git a/.hallmark/log.json b/.hallmark/log.json
new file mode 100644
index 000000000..1a6bfadfc
--- /dev/null
+++ b/.hallmark/log.json
@@ -0,0 +1,20 @@
+[
+ {
+ "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",
+ "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..ab4fe12b8
--- /dev/null
+++ b/.hallmark/preflight.json
@@ -0,0 +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"
+ ]
+}
diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css
new file mode 100644
index 000000000..d112ac2f8
--- /dev/null
+++ b/apps/www/app/(home)/aurora.css
@@ -0,0 +1,2354 @@
+/* Hallmark · genre: atmospheric · macrostructure: Workbench · tone: atmospheric
+ * 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
+ */
+
+@import "../../tokens.css";
+
+/* Load-time rise (open-slide). `backwards` avoids pinning filter after the run. */
+@keyframes home-aurora-rise {
+ from {
+ opacity: 0;
+ transform: translateY(14px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes home-aurora-rise-blur {
+ from {
+ opacity: 0;
+ transform: translateY(16px);
+ filter: blur(8px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ filter: blur(0);
+ }
+}
+
+.home-aurora .rise {
+ animation: home-aurora-rise 900ms var(--ease-out-strong) backwards;
+}
+
+.home-aurora .rise-blur {
+ animation: home-aurora-rise-blur 1s var(--ease-out-strong) backwards;
+}
+
+/* Scroll reveals - ScrollReveal only adds .reveal-hidden below the fold */
+.home-aurora [data-reveal].reveal-hidden {
+ opacity: 0;
+ transform: translateY(18px);
+}
+
+.home-aurora [data-reveal="blur"].reveal-hidden {
+ filter: blur(7px);
+}
+
+.home-aurora [data-reveal="fade"].reveal-hidden {
+ transform: none;
+}
+
+.home-aurora [data-reveal].reveal-shown {
+ transition:
+ opacity var(--dur-reveal) var(--ease-out-strong),
+ transform var(--dur-reveal) var(--ease-out-strong),
+ filter 700ms var(--ease-out-strong);
+ transition-delay: var(--reveal-delay, 0ms);
+}
+
+.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);
+ 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 .rise,
+ .home-aurora .rise-blur {
+ animation: none;
+ }
+
+ .home-aurora__validator-body {
+ animation: none;
+ }
+
+ .home-aurora [data-reveal].reveal-hidden,
+ .home-aurora [data-reveal="blur"].reveal-hidden {
+ opacity: 1;
+ transform: none;
+ filter: none;
+ }
+}
+
+.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;
+ font-size: var(--text-md);
+ letter-spacing: -0.02em;
+ white-space: nowrap;
+}
+
+/* N5 · Floating pill */
+.home-aurora__nav {
+ position: fixed;
+ inset: var(--space-sm) 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);
+}
+
+@media (min-width: 40rem) {
+ .home-aurora__nav {
+ inset-block-start: var(--space-md);
+ }
+}
+
+.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-xs);
+ margin-left: auto;
+}
+
+.home-aurora__nav-utils {
+ display: flex;
+ align-items: center;
+ gap: 0.15rem;
+}
+
+.home-aurora__nav-github {
+ display: none;
+ height: 2rem;
+ padding: 0 0.55rem 0 0.45rem;
+ border-radius: var(--radius-pill);
+ color: var(--color-ink-2);
+ text-decoration: none;
+ gap: 0.4rem;
+}
+
+@media (min-width: 48rem) {
+ .home-aurora__nav-github {
+ display: inline-flex;
+ }
+}
+
+.home-aurora__nav-github:hover {
+ color: var(--color-ink);
+ background: color-mix(in oklch, var(--color-paper-3) 70%, transparent);
+}
+
+.home-aurora__nav-github:focus-visible {
+ outline: 2px solid var(--color-focus);
+ outline-offset: 2px;
+}
+
+.home-aurora__nav-github svg:first-child {
+ width: 1.05rem;
+ height: 1.05rem;
+}
+
+/* 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;
+ align-items: center;
+ justify-content: center;
+ width: 2rem;
+ height: 2rem;
+ border-radius: var(--radius-pill);
+ color: var(--color-ink-2);
+}
+
+.home-aurora__nav [data-search]:hover {
+ color: var(--color-ink);
+ background: color-mix(in oklch, var(--color-paper-3) 70%, transparent);
+}
+
+.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.45rem 0.95rem;
+ border-radius: var(--radius-pill);
+ 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),
+ box-shadow var(--dur-short) var(--ease-out),
+ filter var(--dur-short) var(--ease-out);
+}
+
+@media (min-width: 40rem) {
+ .home-aurora__nav-cta {
+ display: inline-flex;
+ }
+}
+
+.home-aurora__nav-cta:hover {
+ 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 {
+ 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(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);
+}
+
+@media (min-width: 40rem) {
+ .home-aurora__shell {
+ padding-top: calc(var(--space-3xl) + var(--space-sm));
+ }
+}
+
+/* Workbench · hanging intro */
+.home-aurora__intro {
+ display: grid;
+ gap: var(--space-sm);
+ 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);
+ padding-block: var(--space-xl) var(--space-lg);
+ }
+}
+
+.home-aurora__badge {
+ justify-self: start;
+}
+
+.home-aurora__announce {
+ display: inline-flex;
+ align-items: center;
+ 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);
+ background: color-mix(in oklch, var(--color-accent) 10%, transparent);
+ color: var(--color-ink);
+ font-family: var(--font-display);
+ font-size: var(--text-sm);
+ font-weight: 500;
+ text-decoration: none;
+ backdrop-filter: blur(8px);
+ transition:
+ background-color var(--dur-micro) var(--ease-out),
+ border-color var(--dur-micro) var(--ease-out);
+}
+
+.home-aurora__announce:hover {
+ background: color-mix(in oklch, var(--color-accent) 16%, transparent);
+ border-color: color-mix(in oklch, var(--color-accent) 50%, transparent);
+}
+
+.home-aurora__announce:focus-visible {
+ outline: 2px solid var(--color-focus);
+ outline-offset: 2px;
+}
+
+.home-aurora__announce-new {
+ 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);
+ background: color-mix(in oklch, var(--color-accent) 22%, transparent);
+ color: var(--color-accent);
+ font-size: var(--text-xs);
+ font-weight: 600;
+ 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);
+}
+
+.home-aurora__announce-label {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ margin-inline-end: 0.35rem;
+}
+
+.home-aurora__announce-label--solo {
+ margin-inline-start: 0.5rem;
+}
+
+.home-aurora__announce-arrow {
+ width: 1rem;
+ height: 1rem;
+ opacity: 0.45;
+ transition:
+ opacity var(--dur-micro) var(--ease-out),
+ transform var(--dur-micro) var(--ease-out);
+}
+
+.home-aurora__announce:hover .home-aurora__announce-arrow {
+ opacity: 1;
+}
+
+.home-aurora__announce:hover .home-aurora__announce-arrow--right {
+ transform: translateX(0.125rem);
+}
+
+.home-aurora__intro h1,
+.home-aurora__tagline {
+ margin: 0;
+ max-width: 100%;
+ min-width: 0;
+ font-family: var(--font-display);
+ 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: anywhere;
+}
+
+/* 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;
+ letter-spacing: -0.06em;
+ color: inherit;
+}
+
+/* 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: 62rem) {
+ .home-aurora__digital-accent {
+ white-space: nowrap;
+ }
+}
+
+.home-aurora__summary {
+ margin: 0;
+ max-width: min(54ch, 100%);
+ min-width: 0;
+ font-family: var(--font-body);
+ 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.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) 60%, transparent);
+ border: var(--rule-hair) solid var(--color-rule);
+}
+
+.home-aurora__install-row {
+ display: grid;
+ gap: var(--space-sm);
+ margin-top: var(--space-2xs);
+ 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;
+ align-items: flex-end;
+ gap: var(--space-sm);
+}
+
+/* FAQ - end-of-page accordion: +/− spin · panel expand · docs SoT */
+.home-aurora__faq-section {
+ margin-top: var(--space-2xl);
+ display: grid;
+ gap: var(--space-md);
+ scroll-margin-top: 6rem;
+}
+
+.home-aurora__faq-head {
+ display: grid;
+ gap: var(--space-2xs);
+ max-width: 36rem;
+}
+
+.home-aurora__faq-head h2 {
+ margin: 0;
+ font-size: var(--text-display-s);
+ line-height: 1.1;
+ color: var(--color-ink);
+ font-style: normal;
+}
+
+.home-aurora__faq-head p {
+ margin: 0;
+ font-family: var(--font-body);
+ font-size: clamp(1rem, 0.4vw + 0.85rem, 1.15rem);
+ line-height: 1.5;
+ color: var(--color-ink-2);
+}
+
+.home-aurora__faq-head a,
+.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);
+ transition:
+ color var(--dur-micro) var(--ease-out),
+ border-color var(--dur-micro) var(--ease-out);
+}
+
+.home-aurora__faq-head a:hover,
+.home-aurora__faq-more a:hover {
+ color: var(--color-ink);
+ border-color: var(--color-accent);
+}
+
+.home-aurora__faq-head a:focus-visible,
+.home-aurora__faq-more a:focus-visible {
+ outline: 2px solid var(--color-focus);
+ outline-offset: 2px;
+}
+
+.home-aurora__faq {
+ display: grid;
+ gap: 0;
+ width: 100%;
+ max-width: 42rem;
+ border-top: var(--rule-hair) solid var(--color-rule);
+}
+
+.home-aurora__faq-item {
+ border-bottom: var(--rule-hair) solid var(--color-rule);
+ scroll-margin-top: 6rem;
+}
+
+.home-aurora__faq-trigger {
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--space-sm);
+ width: 100%;
+ padding: 1.05rem 0;
+ border: 0;
+ background: transparent;
+ font-family: var(--font-display);
+ font-size: clamp(1rem, 0.4vw + 0.85rem, 1.15rem);
+ font-weight: 500;
+ color: var(--color-ink);
+ text-align: left;
+ transition: color var(--dur-micro) var(--ease-out);
+}
+
+.home-aurora__faq-trigger:hover {
+ color: var(--color-accent);
+}
+
+.home-aurora__faq-trigger:focus-visible {
+ outline: 2px solid var(--color-focus);
+ outline-offset: 2px;
+}
+
+.home-aurora__faq-trigger:active {
+ opacity: 0.92;
+}
+
+/* + morphs to −: 90° spin; hide the bar that would become | after rotate */
+.home-aurora__faq-icon {
+ position: relative;
+ display: block;
+ flex-shrink: 0;
+ width: 0.75rem;
+ height: 0.75rem;
+ color: var(--color-muted);
+ transition:
+ transform var(--dur-short) var(--ease-out),
+ color var(--dur-micro) var(--ease-out);
+}
+
+.home-aurora__faq-icon-h,
+.home-aurora__faq-icon-v {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ border-radius: 1px;
+ background: currentColor;
+ transform: translate(-50%, -50%);
+ transition:
+ opacity var(--dur-short) var(--ease-out),
+ transform var(--dur-short) var(--ease-out);
+}
+
+.home-aurora__faq-icon-h {
+ width: 100%;
+ height: 1.5px;
+}
+
+.home-aurora__faq-icon-v {
+ width: 1.5px;
+ height: 100%;
+}
+
+.home-aurora__faq-item[data-open="true"] .home-aurora__faq-icon {
+ color: var(--color-accent);
+ transform: rotate(90deg);
+}
+
+/* After 90°, the vertical bar is horizontal (= −); drop the old horizontal */
+.home-aurora__faq-item[data-open="true"] .home-aurora__faq-icon-h {
+ opacity: 0;
+ transform: translate(-50%, -50%) scaleX(0);
+}
+
+.home-aurora__faq-panel {
+ display: grid;
+ grid-template-rows: 0fr;
+ opacity: 0;
+ transition:
+ grid-template-rows var(--dur-long) var(--ease-out),
+ opacity var(--dur-short) var(--ease-out);
+}
+
+.home-aurora__faq-item[data-open="true"] .home-aurora__faq-panel {
+ grid-template-rows: 1fr;
+ opacity: 1;
+ transition:
+ grid-template-rows var(--dur-long) var(--ease-out),
+ opacity var(--dur-short) var(--ease-out);
+}
+
+.home-aurora__faq-panel-inner {
+ overflow: hidden;
+ min-height: 0;
+}
+
+.home-aurora__faq-panel-inner p {
+ margin: 0 0 var(--space-sm);
+ 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: color-mix(in oklch, var(--color-ink-2) 80%, var(--color-muted));
+ transform: translateY(-0.35rem);
+ opacity: 0.4;
+ transition:
+ transform var(--dur-long) var(--ease-out),
+ opacity var(--dur-long) var(--ease-out);
+}
+
+.home-aurora__faq-item[data-open="true"] .home-aurora__faq-panel-inner p {
+ transform: none;
+ opacity: 1;
+}
+
+.home-aurora__faq-more {
+ margin-top: calc(var(--space-2xs) * -1);
+}
+
+.home-aurora__faq-panel-inner code {
+ font-family: var(--font-outlier);
+ font-size: 0.92em;
+ color: var(--color-ink);
+ padding: 0.05em 0.3em;
+ border-radius: 0.25rem;
+ background: color-mix(in oklch, var(--color-paper-3) 80%, transparent);
+ border: var(--rule-hair) solid var(--color-rule);
+}
+
+/* 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;
+ gap: var(--space-xs);
+ width: fit-content;
+ max-width: 100%;
+}
+
+@media (min-width: 40rem) {
+ .home-aurora__install {
+ display: grid;
+ }
+}
+
+.home-aurora__install-tabs {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ 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.25rem 0.4rem;
+ border: 0;
+ border-radius: 0;
+ background: transparent;
+ color: var(--color-ink-2);
+ font-family: var(--font-display);
+ font-size: var(--text-sm);
+ font-weight: 600;
+ letter-spacing: 0.01em;
+ cursor: pointer;
+ white-space: nowrap;
+ 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);
+ opacity: 1;
+}
+
+.home-aurora__install-tab[data-active="true"] {
+ background: transparent;
+ color: var(--color-accent);
+ opacity: 1;
+ box-shadow: inset 0 -2px 0 var(--color-accent);
+}
+
+.home-aurora__install-tab:focus-visible {
+ outline: 2px solid var(--color-focus);
+ outline-offset: 2px;
+}
+
+.home-aurora__install-copy {
+ display: flex;
+ align-items: center;
+ gap: var(--space-md);
+ width: max-content;
+ max-width: 100%;
+ min-height: 3rem;
+ 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);
+ 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);
+}
+
+/* Command ↔ Prompt: box stays put; only label/icon crossfade */
+.home-aurora__install-swap {
+ position: relative;
+ display: grid;
+ grid-template-areas: "swap";
+ flex: 0 1 auto;
+ min-width: 0;
+ align-items: center;
+}
+
+.home-aurora__install-swap-item {
+ grid-area: swap;
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ min-width: 0;
+ opacity: 0;
+ transform: translateY(0.25rem);
+ visibility: hidden;
+ pointer-events: none;
+ transition:
+ opacity var(--dur-short) var(--ease-out),
+ transform var(--dur-short) var(--ease-out),
+ visibility var(--dur-short) var(--ease-out);
+}
+
+.home-aurora__install-swap-item[data-active="true"] {
+ opacity: 1;
+ transform: none;
+ visibility: visible;
+ pointer-events: auto;
+}
+
+.home-aurora__install-icon {
+ width: 0.9rem;
+ height: 0.9rem;
+ flex-shrink: 0;
+ color: var(--color-accent);
+ 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(0.8125rem, 0.25vw + 0.8rem, 0.9375rem);
+ line-height: 1.4;
+ 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(0.8125rem, 0.25vw + 0.8rem, 0.9375rem);
+ font-weight: 400;
+ line-height: 1.4;
+ 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-md);
+ 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-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);
+ 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;
+}
+
+/* Pitch sections - code-first proof */
+.home-aurora__pitch {
+ margin-top: var(--space-3xl);
+ display: grid;
+ gap: var(--space-md);
+ scroll-margin-top: 6rem;
+ width: 100%;
+ max-width: 100%;
+ min-width: 0;
+}
+
+.home-aurora__pitch-head {
+ display: grid;
+ gap: var(--space-2xs);
+ max-width: 40rem;
+}
+
+.home-aurora__pitch-label {
+ margin: 0;
+ font-family: var(--font-outlier);
+ font-size: var(--text-xs);
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--color-muted);
+}
+
+.home-aurora__pitch-head h2 {
+ margin: 0;
+ font-size: var(--text-display-s);
+ line-height: 1.1;
+ color: var(--color-ink);
+}
+
+.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);
+ transition:
+ color var(--dur-micro) var(--ease-out),
+ border-color var(--dur-micro) var(--ease-out);
+}
+
+.home-aurora__pitch-head h2 a:hover {
+ border-color: var(--color-accent);
+}
+
+.home-aurora__pitch-head h2 a:focus-visible {
+ outline: 2px solid var(--color-focus);
+ outline-offset: 3px;
+ border-radius: 2px;
+}
+
+.home-aurora__pitch-struck {
+ color: color-mix(in oklch, var(--color-ink) 55%, transparent);
+}
+
+.home-aurora__pitch-struck s {
+ text-decoration-thickness: 1.5px;
+ text-decoration-color: color-mix(in oklch, var(--color-ink) 45%, transparent);
+ color: inherit;
+}
+
+.home-aurora__pitch-head p {
+ margin: 0;
+ color: var(--color-ink-2);
+ font-size: var(--text-md);
+ line-height: 1.5;
+ max-width: 46ch;
+}
+
+.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);
+ transition:
+ color var(--dur-micro) var(--ease-out),
+ border-color var(--dur-micro) var(--ease-out);
+}
+
+.home-aurora__pitch-head p a:hover {
+ color: var(--color-ink);
+ border-color: var(--color-accent);
+}
+
+.home-aurora__pitch-head p a:focus-visible {
+ outline: 2px solid var(--color-focus);
+ outline-offset: 2px;
+}
+
+.home-aurora__compare-stat {
+ margin: var(--space-2xs) 0 0 !important;
+ font-family: var(--font-outlier);
+ font-size: var(--text-sm) !important;
+ letter-spacing: 0.02em;
+ color: var(--color-muted) !important;
+ max-width: none !important;
+}
+
+.home-aurora__compare-stat span {
+ color: var(--color-accent);
+ font-weight: 600;
+ font-variant-numeric: tabular-nums;
+}
+
+.home-aurora__pitch-head code {
+ font-family: var(--font-mono);
+ font-size: 0.92em;
+ font-weight: 500;
+ color: var(--color-ink);
+ padding: 0.1em 0.35em;
+ border-radius: 0.25rem;
+ background: color-mix(in oklch, var(--color-paper-3) 80%, transparent);
+ border: var(--rule-hair) solid var(--color-rule);
+}
+
+/* Before / after compare - window frame + drag reveal */
+.home-aurora__compare-toggle {
+ display: none;
+}
+
+.home-aurora__compare-window {
+ width: 100%;
+ max-width: min(56rem, 100%);
+ min-width: 0;
+ border: var(--rule-hair) solid var(--color-rule);
+ border-radius: var(--radius-frame);
+ background: var(--color-paper-2);
+ overflow: hidden;
+ display: grid;
+ grid-template-rows: auto 1fr;
+}
+
+.home-aurora__compare {
+ --compare: 52%;
+ position: relative;
+ isolation: isolate;
+ width: 100%;
+ min-width: 0;
+ background: var(--color-paper-2);
+ overflow: hidden;
+ user-select: none;
+ touch-action: none;
+}
+
+/* Drives frame height from the longer (old-way) snippet */
+.home-aurora__compare-sizer {
+ visibility: hidden;
+ pointer-events: none;
+ padding: var(--space-md) calc(var(--space-md) + 0.35rem);
+ display: grid;
+ align-content: start;
+ gap: var(--space-xs);
+ min-width: 0;
+}
+
+.home-aurora__compare-pane {
+ position: absolute;
+ inset: 0;
+ padding: var(--space-md) calc(var(--space-md) + 0.35rem);
+ display: grid;
+ align-content: start;
+ gap: var(--space-xs);
+ min-width: 0;
+}
+
+.home-aurora__compare-pane--before {
+ z-index: 2;
+ clip-path: inset(0 calc(100% - var(--compare)) 0 0);
+ background: var(--color-paper);
+}
+
+.home-aurora__compare-pane--after {
+ z-index: 1;
+}
+
+/* Shiki (github-*-high-contrast) — shared with 01 DECLARATIVE */
+.home-aurora__shiki {
+ min-width: 0;
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+}
+
+.home-aurora__shiki .shiki,
+.home-aurora__compare .shiki {
+ margin: 0;
+ padding: var(--space-md);
+ overflow: auto;
+ background: transparent !important;
+ font-family: var(--font-outlier);
+ font-size: clamp(0.78rem, 0.2vw + 0.72rem, 0.9rem);
+ line-height: 1.55;
+ white-space: pre;
+ max-width: 100%;
+ min-width: 0;
+ tab-size: 2;
+}
+
+.home-aurora__compare .shiki {
+ padding: 0;
+}
+
+.home-aurora__shiki .shiki code,
+.home-aurora__compare .shiki code {
+ display: grid;
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+ background: transparent;
+}
+
+.home-aurora__shiki .shiki .line,
+.home-aurora__compare .shiki .line {
+ padding-left: 0;
+ padding-right: 0;
+}
+
+.home-aurora__shiki .shiki .line:empty,
+.home-aurora__compare .shiki .line:empty {
+ height: 1.55em;
+}
+
+/* Hand-rolled tokens aligned to github-dark-high-contrast */
+.home-aurora__tok-kw {
+ color: #ff9492;
+}
+
+.home-aurora__tok-id {
+ color: #91cbff;
+}
+
+.home-aurora__tok-fn {
+ color: #dbb7ff;
+}
+
+.home-aurora__tok-str {
+ color: #addcff;
+}
+
+.home-aurora__tok-punct {
+ color: #f0f3f6;
+}
+
+.home-aurora__tok-muted {
+ color: #bdc4cc;
+}
+
+.home-aurora__tok-ok {
+ color: var(--color-success);
+}
+
+/* Arc-style split: hairline + soft side bloom + thin | grip */
+.home-aurora__compare-handle {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: var(--compare);
+ z-index: 3;
+ width: 1px;
+ background: color-mix(in oklch, var(--color-ink) 35%, var(--color-rule));
+ pointer-events: none;
+ transform: translateX(-50%);
+ box-shadow:
+ -1px 0 0 color-mix(in oklch, var(--color-accent) 28%, transparent),
+ 1px 0 0 color-mix(in oklch, var(--color-accent) 40%, transparent),
+ 0 0 12px color-mix(in oklch, var(--color-accent) 22%, transparent);
+}
+
+.home-aurora__compare-grip {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 3px;
+ height: 1.75rem;
+ border-radius: 999px;
+ background: color-mix(in oklch, var(--color-ink) 72%, var(--color-muted));
+ box-shadow: 0 0 0 1px color-mix(in oklch, var(--color-paper) 55%, transparent);
+ transform: translate(-50%, -50%);
+}
+
+.home-aurora__compare-range {
+ position: absolute;
+ inset: 0;
+ z-index: 4;
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ opacity: 0;
+ cursor: ew-resize;
+ appearance: none;
+ background: transparent;
+}
+
+.home-aurora__compare-range:focus-visible {
+ opacity: 1;
+ outline: 2px solid var(--color-focus);
+ outline-offset: -2px;
+}
+
+@media (max-width: 39.99rem) {
+ /* 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: block;
+ touch-action: auto;
+ }
+
+ .home-aurora__compare-sizer {
+ display: none;
+ }
+
+ .home-aurora__compare-pane {
+ position: relative;
+ inset: auto;
+ clip-path: none;
+ }
+
+ .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 {
+ z-index: auto;
+ background: var(--color-paper);
+ }
+
+ .home-aurora__compare-pane--after {
+ z-index: auto;
+ }
+
+ .home-aurora__compare-handle,
+ .home-aurora__compare-range {
+ display: none;
+ }
+
+ .home-aurora__compare .shiki,
+ .home-aurora__shiki .shiki {
+ white-space: pre-wrap;
+ overflow-wrap: anywhere;
+ overflow: visible;
+ }
+}
+
+/* IDE autocomplete mock - stacked chrome / code / menu (no absolute overlay) */
+.home-aurora__ide {
+ position: relative;
+ display: grid;
+ grid-template-rows: auto auto auto;
+ width: 100%;
+ max-width: min(40rem, 100%);
+ min-width: 0;
+ border: var(--rule-hair) solid var(--color-rule);
+ border-radius: var(--radius-frame);
+ background: var(--color-paper-2);
+ overflow: hidden;
+}
+
+/* Shared window chrome - CLI, IDE, playground */
+.home-aurora__window-chrome {
+ position: relative;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.55rem 0.85rem;
+ border-bottom: var(--rule-hair) solid var(--color-rule);
+ background: color-mix(in oklch, var(--color-paper-2) 78%, transparent);
+ min-width: 0;
+}
+
+.home-aurora__window-traffic {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ flex-shrink: 0;
+ z-index: 1;
+}
+
+.home-aurora__window-traffic span {
+ width: 0.7rem;
+ height: 0.7rem;
+ border-radius: 999px;
+}
+
+.home-aurora__window-traffic [data-tone="close"] {
+ background: var(--color-traffic-close);
+}
+
+.home-aurora__window-traffic [data-tone="min"] {
+ background: var(--color-traffic-min);
+}
+
+.home-aurora__window-traffic [data-tone="max"] {
+ background: var(--color-traffic-max);
+}
+
+.home-aurora__window-title {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding-inline: 0.85rem;
+ min-width: 0;
+ text-align: center;
+ font-family: var(--font-outlier);
+ font-size: var(--text-xs);
+ color: var(--color-muted);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ user-select: none;
+ 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);
+ font-family: var(--font-outlier);
+ font-size: clamp(0.8rem, 0.2vw + 0.75rem, 0.98rem);
+ line-height: 1.6;
+ color: var(--color-ink-2);
+ min-width: 0;
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+}
+
+.home-aurora__tok-cmd,
+.home-aurora__transcript-prompt .home-aurora__tok-cmd {
+ color: var(--color-cmd, oklch(78% 0.19 55));
+}
+
+.home-aurora__tok-caret {
+ display: inline-block;
+ width: 1.5px;
+ height: 1.05em;
+ margin-left: 1px;
+ margin-right: 1px;
+ vertical-align: text-bottom;
+ background: var(--color-ink);
+}
+
+.home-aurora__ide-menu {
+ position: relative;
+ z-index: 1;
+ margin: 0 var(--space-sm) var(--space-sm);
+ padding: 0.35rem;
+ list-style: none;
+ width: auto;
+ max-width: 100%;
+ min-width: 0;
+ border: var(--rule-hair) solid var(--color-rule);
+ border-radius: 0.5rem;
+ background: color-mix(in oklch, var(--color-paper) 94%, transparent);
+ backdrop-filter: blur(10px);
+ box-shadow: var(--shadow-nav);
+ animation: home-aurora-menu-in var(--dur-short) var(--ease-out) both;
+}
+
+.home-aurora__ide-menu li {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ gap: var(--space-xs);
+ padding: 0.4rem 0.55rem;
+ border-radius: 0.3rem;
+ font-family: var(--font-outlier);
+ font-size: var(--text-sm);
+ color: var(--color-ink);
+ min-width: 0;
+}
+
+.home-aurora__ide-menu li[data-active="true"] {
+ background: color-mix(in oklch, var(--color-accent) 18%, transparent);
+}
+
+.home-aurora__ide-name {
+ flex: 0 1 auto;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--color-ink);
+}
+
+.home-aurora__ide-type {
+ flex: 0 1 auto;
+ min-width: 0;
+ max-width: 55%;
+ color: #bdc4cc;
+ font-size: var(--text-xs);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ text-align: right;
+}
+
+@keyframes home-aurora-menu-in {
+ from {
+ opacity: 0;
+ transform: translateY(0.35rem);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* 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;
+ gap: var(--space-sm);
+ width: 100%;
+ max-width: 56rem;
+}
+
+@media (min-width: 48rem) {
+ .home-aurora__agent-split {
+ grid-template-columns: 1fr 1fr;
+ align-items: start;
+ }
+}
+
+/* 04 · Bring your own validator — tabbed schema window */
+.home-aurora__validator {
+ display: grid;
+ gap: var(--space-xs);
+ width: 100%;
+ max-width: min(40rem, 100%);
+ min-width: 0;
+}
+
+.home-aurora__validator-tabs {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--space-xs);
+}
+
+.home-aurora__validator-tab {
+ display: inline-flex;
+ align-items: center;
+ padding: 0.25rem 0.4rem;
+ border: 0;
+ border-radius: 0;
+ background: transparent;
+ color: var(--color-ink-2);
+ font-family: var(--font-display);
+ font-size: var(--text-sm);
+ font-weight: 600;
+ letter-spacing: 0.01em;
+ cursor: pointer;
+ white-space: nowrap;
+ 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);
+ opacity: 1;
+}
+
+.home-aurora__validator-tab[data-active="true"] {
+ background: transparent;
+ color: var(--color-accent);
+ opacity: 1;
+ box-shadow: inset 0 -2px 0 var(--color-accent);
+}
+
+.home-aurora__validator-tab:focus-visible {
+ outline: 2px solid var(--color-focus);
+ outline-offset: 2px;
+}
+
+.home-aurora__validator-tab:active {
+ opacity: 0.9;
+}
+
+.home-aurora__validator-frame {
+ margin: 0;
+ padding: 0;
+ border: var(--rule-hair) solid var(--color-rule);
+ border-radius: var(--radius-frame);
+ background: var(--color-paper-2);
+ display: grid;
+ grid-template-rows: auto 1fr;
+ overflow: hidden;
+ min-width: 0;
+}
+
+.home-aurora__validator-body {
+ min-width: 0;
+ animation: home-aurora-rise 420ms var(--ease-out-strong) both;
+}
+
+.home-aurora__validator-note {
+ margin: 0;
+ font-family: var(--font-body);
+ font-size: var(--text-sm);
+ line-height: 1.45;
+ color: var(--color-ink-2);
+}
+
+.home-aurora__validator-note code {
+ font-family: var(--font-outlier);
+ font-size: 0.92em;
+ color: var(--color-ink);
+}
+
+.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);
+ transition:
+ color var(--dur-micro) var(--ease-out),
+ border-color var(--dur-micro) var(--ease-out);
+}
+
+.home-aurora__validator-note a:hover {
+ color: var(--color-ink);
+ border-color: var(--color-accent);
+}
+
+.home-aurora__validator-note a:focus-visible {
+ outline: 2px solid var(--color-focus);
+ outline-offset: 2px;
+}
+
+/* 05 · Secure — client / server schema boundary */
+.home-aurora__secure {
+ display: grid;
+ gap: var(--space-sm);
+ width: 100%;
+ max-width: 56rem;
+}
+
+@media (min-width: 48rem) {
+ .home-aurora__secure {
+ grid-template-columns: 1fr 1fr;
+ align-items: stretch;
+ }
+}
+
+.home-aurora__secure-pane {
+ margin: 0;
+ 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) 30%, var(--color-rule));
+}
+
+.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: 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;
+}
+
+.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: 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 {
+ margin: 0;
+ padding: 0;
+ border: var(--rule-hair) solid var(--color-rule);
+ border-radius: var(--radius-frame);
+ background: color-mix(in oklch, var(--color-paper) 88%, black);
+ display: grid;
+ grid-template-rows: auto 1fr;
+ overflow: hidden;
+ min-height: 14rem;
+}
+
+.home-aurora__terminal pre {
+ margin: 0;
+ padding: var(--space-md);
+ font-family: var(--font-outlier);
+ font-size: clamp(0.8rem, 0.2vw + 0.75rem, 0.92rem);
+ line-height: 1.55;
+ color: var(--color-ink-2);
+ white-space: pre-wrap;
+}
+
+/* Cursor-style agent transcript - framed prompt / status / open reply */
+.home-aurora__transcript {
+ display: grid;
+ gap: var(--space-xs);
+ min-width: 0;
+ align-content: start;
+ padding: var(--space-md);
+ border: var(--rule-hair) solid var(--color-rule);
+ border-radius: var(--radius-frame);
+ background: var(--color-paper-2);
+ min-height: 14rem;
+}
+
+.home-aurora__transcript-prompt {
+ margin: 0;
+ padding: 0.7rem 0.85rem;
+ border-radius: calc(var(--radius-frame) - 0.15rem);
+ background: var(--color-paper-3);
+ border: var(--rule-hair) solid var(--color-rule);
+ color: var(--color-ink);
+ font-family: var(--font-body);
+ font-size: var(--text-sm);
+ line-height: 1.45;
+}
+
+.home-aurora__transcript-status {
+ margin: 0;
+ padding-inline: 0.15rem;
+ font-family: var(--font-body);
+ font-size: var(--text-sm);
+ color: var(--color-muted);
+ line-height: 1.4;
+}
+
+.home-aurora__transcript-reply {
+ display: grid;
+ gap: var(--space-2xs);
+ padding-inline: 0.15rem;
+ color: var(--color-ink-2);
+ font-family: var(--font-body);
+ font-size: var(--text-sm);
+ line-height: 1.55;
+}
+
+.home-aurora__transcript-reply p {
+ margin: 0;
+}
+
+.home-aurora__transcript-reply code {
+ font-family: var(--font-outlier);
+ font-size: 0.92em;
+ color: var(--color-ink);
+ padding: 0.05em 0.3em;
+ border-radius: 0.25rem;
+ background: color-mix(in oklch, var(--color-paper-3) 80%, transparent);
+ border: var(--rule-hair) solid var(--color-rule);
+}
+
+.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: color-mix(in oklch, var(--color-paper) 70%, black);
+ box-shadow: var(--shadow-frame);
+}
+
+.home-aurora__frame-button {
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ width: 100%;
+ padding: 0;
+ border: 0;
+ background: transparent;
+ cursor: pointer;
+ text-align: left;
+ color: inherit;
+ transition: transform var(--dur-short) var(--ease-out);
+}
+
+.home-aurora__frame-button:hover {
+ transform: scale(1.01);
+}
+
+.home-aurora__frame-chrome {
+ display: none;
+}
+
+@media (min-width: 64rem) {
+ .home-aurora__frame-chrome {
+ display: flex;
+ }
+}
+
+.home-aurora__frame-media {
+ position: relative;
+ flex: 1;
+ min-height: 0;
+ width: 100%;
+}
+
+.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: 1.5rem 0 auto 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);
+ box-shadow: var(--shadow-nav);
+}
+
+/* Bookend · callback to the hero promise + install CTAs (desktop only) */
+.home-aurora__outro {
+ 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 {
+ margin: 0;
+ max-width: none;
+ font-family: var(--font-display);
+ font-size: clamp(1.85rem, 4.5vw + 0.55rem, 3.25rem);
+ line-height: 1.08;
+ letter-spacing: -0.03em;
+ font-weight: 500;
+ font-style: normal;
+ color: var(--color-ink);
+ overflow-wrap: normal;
+}
+
+.home-aurora__outro-docs {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.35rem;
+ width: fit-content;
+ min-height: 3rem;
+ padding: 0.55rem 0.9rem;
+ border: var(--rule-hair) solid transparent;
+ border-radius: var(--radius-frame);
+ background: 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__outro-docs:hover {
+ color: var(--color-ink);
+ border-color: var(--color-rule);
+ background: color-mix(in oklch, var(--color-paper-2) 70%, transparent);
+}
+
+.home-aurora__outro-docs:focus-visible {
+ outline: 2px solid var(--color-focus);
+ outline-offset: 2px;
+}
+
+.home-aurora__outro-docs:active {
+ opacity: 0.9;
+}
+
+/* Ft3 · Index-style footer */
+.home-aurora__footer {
+ margin-top: var(--space-xl);
+ padding: var(--space-xl) 0 0;
+ display: grid;
+ gap: var(--space-lg);
+}
+
+.home-aurora__footer-grid {
+ display: grid;
+ gap: var(--space-lg);
+}
+
+@media (min-width: 40rem) {
+ .home-aurora__footer-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+@media (min-width: 64rem) {
+ .home-aurora__footer-grid {
+ grid-template-columns: 1.4fr repeat(3, minmax(0, 1fr));
+ gap: var(--space-xl);
+ }
+}
+
+.home-aurora__footer-brand {
+ display: grid;
+ gap: var(--space-xs);
+ align-content: start;
+}
+
+.home-aurora__footer-brand p {
+ margin: 0;
+ max-width: 28ch;
+ color: var(--color-ink-2);
+ font-size: var(--text-sm);
+ line-height: 1.45;
+}
+
+.home-aurora__footer-license {
+ color: var(--color-muted) !important;
+ font-family: var(--font-outlier);
+ font-size: var(--text-xs) !important;
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+}
+
+.home-aurora__footer-grid h3 {
+ margin: 0 0 var(--space-xs);
+ font-family: var(--font-outlier);
+ font-size: var(--text-xs);
+ font-weight: 500;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--color-muted);
+}
+
+.home-aurora__footer-grid ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ display: grid;
+ gap: 0.45rem;
+}
+
+.home-aurora__footer-grid a {
+ color: var(--color-ink-2);
+ font-family: var(--font-display);
+ font-size: var(--text-sm);
+ text-decoration: none;
+ transition: color var(--dur-micro) var(--ease-out);
+}
+
+.home-aurora__footer-grid a:hover {
+ color: var(--color-ink);
+}
+
+.home-aurora__footer-grid a:focus-visible {
+ outline: 2px solid var(--color-focus);
+ outline-offset: 2px;
+}
+
+.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__docs-cta,
+ .home-aurora__outro-docs,
+ .home-aurora__nav-cta,
+ .home-aurora__nav-links a,
+ .home-aurora__install-tab,
+ .home-aurora__install-copy,
+ .home-aurora__validator-tab,
+ .home-aurora__ide-menu,
+ .home-aurora__faq-icon,
+ .home-aurora__faq-icon-h,
+ .home-aurora__faq-icon-v,
+ .home-aurora__faq-panel,
+ .home-aurora__faq-panel-inner p,
+ .home-aurora__install-swap-item {
+ transition: opacity 120ms linear;
+ }
+
+ .home-aurora__ide-menu {
+ animation: none;
+ }
+
+ .home-aurora__frame-button:hover .home-aurora__frame-hint {
+ transform: none;
+ }
+
+ .home-aurora__install-copy:active {
+ transform: none;
+ }
+
+ /* No spin → keep horizontal bar, drop vertical (= −) */
+ .home-aurora__faq-item[data-open="true"] .home-aurora__faq-icon {
+ transform: none;
+ }
+
+ .home-aurora__faq-item[data-open="true"] .home-aurora__faq-icon-h {
+ opacity: 1;
+ transform: translate(-50%, -50%);
+ }
+
+ .home-aurora__faq-item[data-open="true"] .home-aurora__faq-icon-v {
+ opacity: 0;
+ transform: translate(-50%, -50%) scaleY(0);
+ }
+
+ .home-aurora__faq-panel-inner p,
+ .home-aurora__faq-item[data-open="true"] .home-aurora__faq-panel-inner p,
+ .home-aurora__install-swap-item,
+ .home-aurora__install-swap-item[data-active="true"] {
+ 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: 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-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 {
+ 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"] {
+ 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;
+ 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..8bd5b4f8e
--- /dev/null
+++ b/apps/www/app/(home)/home-nav.tsx
@@ -0,0 +1,37 @@
+"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..6d092fbf1 100644
--- a/apps/www/app/(home)/layout.tsx
+++ b/apps/www/app/(home)/layout.tsx
@@ -1,49 +1,30 @@
-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 { DotGrid, ScrollReveal } from "~/components/page";
+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 2d4c755b0..64729d0d7 100644
--- a/apps/www/app/(home)/page.tsx
+++ b/apps/www/app/(home)/page.tsx
@@ -1,113 +1,221 @@
-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,
+ AgentNativePitch,
+ BeforeAfterCompare,
+ BringYourOwnValidator,
CompatibilityRails,
+ HeroFaq,
+ InstallPanel,
QuickstartButton,
+ SecureBoundary,
StarUsButton,
+ TypeSafetyShowcase,
VideoDemo,
} from "~/components/page";
+import { Logo } from "~/components/page/logo";
export const metadata: Metadata = {
title: "ArkEnv",
- description: "Environment variable validation from editor to runtime",
+ description: "The simple way to validate environment variables.",
};
export default function HomePage() {
return (
-
- {/* Top gradient overlay for dark mode - SVG version */}
-
-
-
-
-
-
- Next.js, Netlify presets
-
-
-
-
- Better{" "}
-
- typesafe
- {" "}
- than sorry
-
-
- Environment variable validation from editor to runtime
-
-
-
-
-
-
-
-
+
+
+
+
+ Next.js, Netlify presets
+
-
-
+
+ The simple way to validate {" "}
+
+
+ environment variables
+
+
+
+
+ One function validates, parses, and keeps your env vars
+ typesafe. Use ArkType, Zod, or any Standard Schema. Fail fast, ship
+ faster.
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
- {/* Homepage Footer with Fade Gradient and Stylized Separator */}
-
- {/* Background fade gradient */}
+
+
+
+
+ Try ArkEnv now.
+
+ className="home-aurora__install-row home-aurora__install-row--outro"
+ data-reveal
+ style={{ ["--reveal-delay" as string]: "80ms" }}
+ >
+
+
+ Read the docs
+ →
+
+
+
-
);
}
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..2f6a5af7b 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 (
@@ -54,6 +47,7 @@ export default function Layout({ children }: { children: ReactNode }) {
theme={{
enableColorScheme: true,
enableSystem: true,
+ defaultTheme: "dark",
themes: ["system", "light", "dark"],
}}
>
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/app/styles/theme/dark.css b/apps/www/app/styles/theme/dark.css
index d088afb66..8db66cdfd 100644
--- a/apps/www/app/styles/theme/dark.css
+++ b/apps/www/app/styles/theme/dark.css
@@ -1,28 +1,48 @@
.dark {
- --background: #081021;
- --foreground: hsl(0 0% 98%);
- --card: hsl(240 10% 3.9%);
- --card-foreground: hsl(0 0% 98%);
- --popover: hsl(240 10% 3.9%);
- --popover-foreground: hsl(0 0% 98%);
- --primary: rgb(158, 203, 255);
- --primary-foreground: rgb(10, 52, 93);
- --fd-primary: rgb(158, 203, 255);
- --color-fd-primary: rgb(158, 203, 255);
- /** can be rgb(3, 47, 98) */
- --secondary: color-mix(in srgb, var(--background) 80%, transparent);
- --secondary-foreground: hsl(0 0% 98%);
- --muted: hsl(240 3.7% 15.9%);
- --muted-foreground: hsl(240 5% 70%);
- /* WCAG AA compliant: 8.88:1 contrast */
- --accent: hsl(240 3.7% 15.9%);
- --accent-foreground: hsl(0 0% 98%);
- --destructive: hsl(0 62.8% 30.6%);
- --destructive-foreground: hsl(0 0% 98%);
- --border: var(--color-fd-border);
- --input: hsl(240 3.7% 15.9%);
- --ring: hsl(240 4.9% 83.9%);
- --chart-1: hsl(220 70% 50%);
+ /* Surfaces — Aurora paper (hue 200), not ocean navy */
+ --background: oklch(13% 0.018 200);
+ --foreground: oklch(95% 0.01 200);
+ --card: oklch(17% 0.02 200);
+ --card-foreground: oklch(95% 0.01 200);
+ --popover: oklch(17% 0.02 200);
+ --popover-foreground: oklch(95% 0.01 200);
+
+ /* Brand accent */
+ --primary: oklch(74% 0.13 195);
+ --primary-foreground: oklch(14% 0.02 200);
+ --fd-primary: oklch(74% 0.13 195);
+ --color-fd-primary: oklch(74% 0.13 195);
+ --color-fd-primary-foreground: oklch(14% 0.02 200);
+
+ --secondary: oklch(21% 0.022 200);
+ --secondary-foreground: oklch(78% 0.02 200);
+ --muted: oklch(17% 0.02 200);
+ --muted-foreground: oklch(62% 0.025 200);
+ --accent: oklch(21% 0.022 200);
+ --accent-foreground: oklch(95% 0.01 200);
+ --destructive: oklch(65% 0.18 25);
+ --destructive-foreground: oklch(95% 0.01 200);
+ --border: oklch(30% 0.02 200);
+ --input: oklch(21% 0.022 200);
+ --ring: oklch(78% 0.12 195);
+
+ /* fumadocs ocean overrides — search dialog + docs chrome */
+ --color-fd-background: oklch(13% 0.018 200);
+ --color-fd-foreground: oklch(95% 0.01 200);
+ --color-fd-muted: oklch(17% 0.02 200);
+ --color-fd-muted-foreground: oklch(62% 0.025 200);
+ --color-fd-popover: oklch(17% 0.02 200);
+ --color-fd-popover-foreground: oklch(95% 0.01 200);
+ --color-fd-card: oklch(17% 0.02 200);
+ --color-fd-card-foreground: oklch(95% 0.01 200);
+ --color-fd-border: oklch(30% 0.02 200);
+ --color-fd-secondary: oklch(21% 0.022 200);
+ --color-fd-secondary-foreground: oklch(78% 0.02 200);
+ --color-fd-accent: oklch(21% 0.022 200);
+ --color-fd-accent-foreground: oklch(95% 0.01 200);
+ --color-fd-ring: oklch(78% 0.12 195);
+
+ --chart-1: hsl(195 70% 50%);
--chart-2: hsl(160 60% 45%);
--chart-3: hsl(30 80% 55%);
--chart-4: hsl(280 65% 60%);
diff --git a/apps/www/app/styles/theme/light.css b/apps/www/app/styles/theme/light.css
index fd850e53c..f95a734ca 100644
--- a/apps/www/app/styles/theme/light.css
+++ b/apps/www/app/styles/theme/light.css
@@ -1,26 +1,47 @@
:root {
- --background: #fefefe;
- --foreground: hsl(240 10% 3.9%);
- --card: hsl(0 0% 100%);
- --card-foreground: hsl(240 10% 3.9%);
- --popover: hsl(0 0% 100%);
- --popover-foreground: hsl(240 10% 3.9%);
- --primary: #1e40af;
- --primary-foreground: hsl(0 0% 98%);
- --fd-primary: var(--primary);
- --color-fd-primary: var(--primary);
- --secondary: rgb(247, 249, 252);
- --secondary-foreground: hsl(240 5.9% 10%);
- --muted: hsl(240 4.8% 95.9%);
- --muted-foreground: hsl(240 4% 45%);
- --accent: rgb(244, 244, 245);
- --accent-foreground: hsl(240 5.9% 10%);
- --destructive: hsl(0 84.2% 60.2%);
- --destructive-foreground: hsl(0 0% 98%);
- --border: hsl(240 5.9% 90%);
- --input: hsl(240 5.9% 90%);
- --ring: hsl(240 10% 3.9%);
- --chart-1: hsl(12 76% 61%);
+ --background: oklch(99% 0.005 200);
+ --foreground: oklch(18% 0.02 200);
+ --card: oklch(100% 0 0);
+ --card-foreground: oklch(18% 0.02 200);
+ --popover: oklch(100% 0 0);
+ --popover-foreground: oklch(18% 0.02 200);
+
+ /* Aurora accent — darker for light surfaces */
+ --primary: oklch(48% 0.11 195);
+ --primary-foreground: oklch(98% 0.01 195);
+ --fd-primary: oklch(48% 0.11 195);
+ --color-fd-primary: oklch(48% 0.11 195);
+ --color-fd-primary-foreground: oklch(98% 0.01 195);
+
+ --secondary: oklch(96% 0.01 200);
+ --secondary-foreground: oklch(22% 0.02 200);
+ --muted: oklch(96% 0.01 200);
+ --muted-foreground: oklch(45% 0.02 200);
+ --accent: oklch(96% 0.01 200);
+ --accent-foreground: oklch(22% 0.02 200);
+ --destructive: oklch(55% 0.2 25);
+ --destructive-foreground: oklch(98% 0.01 200);
+ --border: oklch(90% 0.015 200);
+ --input: oklch(90% 0.015 200);
+ --ring: oklch(48% 0.11 195);
+
+ /* fumadocs ocean overrides */
+ --color-fd-background: oklch(99% 0.005 200);
+ --color-fd-foreground: oklch(18% 0.02 200);
+ --color-fd-muted: oklch(96% 0.01 200);
+ --color-fd-muted-foreground: oklch(45% 0.02 200);
+ --color-fd-popover: oklch(100% 0 0);
+ --color-fd-popover-foreground: oklch(18% 0.02 200);
+ --color-fd-card: oklch(100% 0 0);
+ --color-fd-card-foreground: oklch(18% 0.02 200);
+ --color-fd-border: oklch(90% 0.015 200);
+ --color-fd-secondary: oklch(96% 0.01 200);
+ --color-fd-secondary-foreground: oklch(22% 0.02 200);
+ --color-fd-accent: oklch(96% 0.01 200);
+ --color-fd-accent-foreground: oklch(22% 0.02 200);
+ --color-fd-ring: oklch(48% 0.11 195);
+
+ --chart-1: hsl(195 70% 40%);
--chart-2: hsl(173 58% 39%);
--chart-3: hsl(197 37% 24%);
--chart-4: hsl(43 74% 66%);
diff --git a/apps/www/components/announcement-badge.tsx b/apps/www/components/announcement-badge.tsx
index 7b371467a..11544b495 100644
--- a/apps/www/components/announcement-badge.tsx
+++ b/apps/www/components/announcement-badge.tsx
@@ -10,7 +10,6 @@ export function AnnouncementBadge({
href,
children,
}: PropsWithChildren<{
- // TODO: we can detect the arrow kind (right facing, or top right facing) based on whether `href` is internal or external
/**
* Show an arrow next to the badge
*/
@@ -25,24 +24,18 @@ export function AnnouncementBadge({
href: ArkenvUrl;
}>) {
return (
-
- {newBadge && (
-
- )}
+
+ {newBadge ?
: null}
{children}
{arrow &&
href &&
(isExternalUrl(href) ? (
-
+
) : (
-
+
))}
diff --git a/apps/www/components/page/agent-native-pitch.tsx b/apps/www/components/page/agent-native-pitch.tsx
new file mode 100644
index 000000000..c781a19fc
--- /dev/null
+++ b/apps/www/components/page/agent-native-pitch.tsx
@@ -0,0 +1,85 @@
+import { WindowChrome } from "./window-chrome";
+
+/**
+ * Agent-native pitch: terminal CLI + Cursor-style agent transcript in a frame.
+ */
+export function AgentNativePitch() {
+ return (
+
+
+
+ 05 / AGENT-READY
+
+
+ For humans.{" "}
+ And their agents.
+
+
+ Run the interactive CLI, or point the{" "}
+
+ agent skill
+ {" "}
+ at your .env.example in Cursor, Claude Code, or Codex.
+
+
+
+
+
+
+
+
+ yam@dev
+ :
+ ~/app
+ $ npx
+ arkenv@latest init{"\n"}
+ ✔ Detected Next.js
+ {"\n"}
+ ✔ Wrote{" "}
+ env.ts
+ {"\n"}
+ ✔ Wired{" "}
+ @arkenv/nextjs
+ {"\n"}
+ yam@dev
+ :
+ ~/app
+ $
+
+
+
+
+
+
+ Set up{" "}
+
+ /arkenv
+ {" "}
+ for my .env.example
+
+
Worked for 2.4s
+
+
+ Running npx arkenv@latest init --agent. Schema
+ matches your .env keys; validation works from editor to runtime.
+
+
+
+
+
+ );
+}
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..19ba2ecc2
--- /dev/null
+++ b/apps/www/components/page/before-after-compare-view.tsx
@@ -0,0 +1,189 @@
+"use client";
+
+import { type CSSProperties, useEffect, useId, useRef, useState } from "react";
+import { WindowChrome } from "./window-chrome";
+
+type BeforeAfterCompareViewProps = {
+ beforeHtml: string;
+ afterHtml: string;
+ reduction: number;
+};
+
+type MobileView = "arkenv" | "old";
+
+const NARROW_MQ = "(max-width: 39.99rem)";
+
+/**
+ * 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,
+ afterHtml,
+ reduction,
+}: 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);
+ 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 / DECLARATIVE
+
+
+
+ Replace your manual process.env checks with one schema.
+ Skip the separate interfaces and error handling.
+
+
+ {reduction}% less code, same checks
+
+
+
+
+
+ Code sample
+
+
setMobileView("arkenv")}
+ >
+ ArkEnv way
+
+
setMobileView("old")}
+ >
+ Old way
+
+
+
+
+
+
+ {/* Invisible sizer so the frame fits the full old-way snippet */}
+
+
setManual(true)}
+ onInput={(event) => {
+ setManual(true);
+ setCompare(Number((event.target as HTMLInputElement).value));
+ }}
+ />
+
+ Reveal ArkEnv versus the old way
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/www/components/page/before-after-compare.tsx b/apps/www/components/page/before-after-compare.tsx
new file mode 100644
index 000000000..1e10dad5e
--- /dev/null
+++ b/apps/www/components/page/before-after-compare.tsx
@@ -0,0 +1,59 @@
+import { BeforeAfterCompareView } from "./before-after-compare-view";
+import { highlightTs } from "./highlight-ts";
+
+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, PORT, NODE_ENV };`;
+
+const AFTER = `// The ArkEnv way
+import arkenv from "@arkenv/core";
+
+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..18a54c9bf
--- /dev/null
+++ b/apps/www/components/page/bring-your-own-validator-view.tsx
@@ -0,0 +1,106 @@
+"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. Either way, you get the same env API.
+
+
+
+
+
+ {examples.map((item) => (
+ setActive(item.id)}
+ >
+ {item.label}
+
+ ))}
+
+
+
+
+
+ {/* 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/compatibility-rails.tsx b/apps/www/components/page/compatibility-rails.tsx
index c46e761d9..e50fc9c60 100644
--- a/apps/www/components/page/compatibility-rails.tsx
+++ b/apps/www/components/page/compatibility-rails.tsx
@@ -105,11 +105,11 @@ function MarqueeRow({
: "animate-marquee";
return (
-
+
{label}
-
+
-
-
+
+
);
}
diff --git a/apps/www/components/page/dot-grid.tsx b/apps/www/components/page/dot-grid.tsx
new file mode 100644
index 000000000..81abd19e0
--- /dev/null
+++ b/apps/www/components/page/dot-grid.tsx
@@ -0,0 +1,266 @@
+"use client";
+
+import { useEffect, useRef } from "react";
+
+/** Physics tuned for a quiet Aurora field - push ripples, then settle. */
+const SPACING = 24;
+const DOT_RADIUS = 1;
+const INFLUENCE = 110;
+const STIFFNESS = 130;
+const DAMPING = 9;
+const PUSH = 1100;
+const DRAG = 1.6;
+const MAX_OFFSET = 14;
+const MAX_POINTER_SPEED = 2600;
+const REST_THRESHOLD = 0.01;
+
+/**
+ * Interactive faded-dot atmosphere for the home hero.
+ * Static CSS dots paint first; the canvas takes over after hydration.
+ */
+export function DotGrid() {
+ const canvasRef = useRef
(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/header-github-link.tsx b/apps/www/components/page/header-github-link.tsx
index 44c05c0bb..802e81000 100644
--- a/apps/www/components/page/header-github-link.tsx
+++ b/apps/www/components/page/header-github-link.tsx
@@ -3,8 +3,12 @@
import { SiGithub } from "@icons-pack/react-simple-icons";
import { Star } from "lucide-react";
import { useEffect, useState } from "react";
+import { cn } from "~/lib/utils";
import { breakDownGithubUrl } from "~/lib/utils/github";
+/**
+ * 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,
)}
+ >
+
+ {starCount !== null ? (
+
+
+ {starCount.toLocaleString()}
+
+ ) : null}
);
}
diff --git a/apps/www/components/page/hero-faq.tsx b/apps/www/components/page/hero-faq.tsx
new file mode 100644
index 000000000..c31ae5432
--- /dev/null
+++ b/apps/www/components/page/hero-faq.tsx
@@ -0,0 +1,194 @@
+"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: readonly {
+ id: string;
+ href: string;
+ question: React.ReactNode;
+ teaser: React.ReactNode;
+}[] = [
+ {
+ id: "why",
+ href: "/docs/arkenv/faq#why-do-i-need-arkenv",
+ 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.
+
+ >
+ ),
+ },
+ {
+ 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. Point the agent skill at your .env.example to
+ intelligently set up validation and guide your assistant through schema
+ maintenance.
+ >
+ ),
+ },
+];
+
+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 (
+
+
+
+
+ {FAQ.map((item) => {
+ const isOpen = openId === item.id;
+ const panelId = `${baseId}-${item.id}-panel`;
+ const buttonId = `${baseId}-${item.id}-button`;
+
+ return (
+
+
setOpenId(isOpen ? null : item.id)}
+ >
+ {item.question}
+
+
+
+
+
+
+
+ );
+ })}
+
+
+ );
+}
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 935e63958..6620aa86e 100644
--- a/apps/www/components/page/index.ts
+++ b/apps/www/components/page/index.ts
@@ -1,7 +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
new file mode 100644
index 000000000..e36810638
--- /dev/null
+++ b/apps/www/components/page/install-panel.tsx
@@ -0,0 +1,129 @@
+"use client";
+
+import { Check, Copy, Sparkles } 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 panelId = `${baseId}-panel`;
+ 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!",
+ });
+
+ const active = tab === "command" ? commandCopy : promptCopy;
+ const ariaLabel =
+ tab === "command"
+ ? active.copied
+ ? "Copied"
+ : "Copy install command"
+ : active.copied
+ ? "Copied"
+ : "Copy prompt";
+
+ return (
+
+
+ setTab("command")}
+ >
+ For you
+
+ setTab("prompt")}
+ >
+ For your agent
+
+
+
+
+
+
+
+
+ $
+
+
+ npx
+ arkenv@latest init
+
+
+
+
+ Copy prompt
+
+
+
+ {active.copied ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+}
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..efca15318 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.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 02212caa9..15d707b3e 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
-
-
-
+
+ Read the docs
+ →
+
);
}
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-view.tsx b/apps/www/components/page/secure-boundary-view.tsx
new file mode 100644
index 000000000..db3aff0f7
--- /dev/null
+++ b/apps/www/components/page/secure-boundary-view.tsx
@@ -0,0 +1,168 @@
+"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 (
+
+
+
+
+ {/* Left Column: Window with integrated tab switcher */}
+
+
+
+
+
+
+
+
+
+ setActiveTab("schema")}
+ >
+ src/env.ts
+
+ setActiveTab("bundle")}
+ >
+ app-client.js
+
+
+
+
+
+
+
+
+ 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
new file mode 100644
index 000000000..e757f73e3
--- /dev/null
+++ b/apps/www/components/page/secure-boundary.tsx
@@ -0,0 +1,27 @@
+import { highlightTs } from "./highlight-ts";
+import { SecureBoundaryView } from "./secure-boundary-view";
+
+const FLAT_ENV_CODE = `import arkenv from "@/generated/env.gen";
+
+export const env = arkenv({
+ NEXT_PUBLIC_API_URL: "string.url",
+ DATABASE_URL: "string.url",
+ STRIPE_SECRET_KEY: "string",
+});`;
+
+const COMPILED_BUNDLE_CODE = `export const env = {
+ NEXT_PUBLIC_API_URL: "https://api.acme.com",
+ // 🔒 Secrets omitted from client bundle!
+};`;
+
+/**
+ * Client / server env boundary pitch showcasing the recommended Flat Layout.
+ */
+export async function SecureBoundary() {
+ const [envHtml, bundleHtml] = await Promise.all([
+ highlightTs(FLAT_ENV_CODE),
+ highlightTs(COMPILED_BUNDLE_CODE),
+ ]);
+
+ return ;
+}
diff --git a/apps/www/components/page/type-safety-showcase.tsx b/apps/www/components/page/type-safety-showcase.tsx
new file mode 100644
index 000000000..5cc0f68db
--- /dev/null
+++ b/apps/www/components/page/type-safety-showcase.tsx
@@ -0,0 +1,104 @@
+/**
+ * Simulated IDE autocomplete + Terminal fail-fast runtime output side-by-side.
+ */
+import { WindowChrome } from "./window-chrome";
+
+export function TypeSafetyShowcase() {
+ return (
+
+
+
+
+ {/* Left Window: IDE Autocomplete */}
+
+
+
+
+ import
+ {" { "}
+ env
+ {" } "}
+ from
+ {` "./env"`}
+ {";\n\n"}
+ const
+ {" db = "}
+ env
+ .
+
+
+
+
+
+ DATABASE_URL
+ string
+
+
+ PORT
+ number
+
+
+ NODE_ENV
+
+ "development" | "production"
+
+
+
+
+
+ {/* Right Window: Terminal Fail-Fast Runtime Output */}
+
+
+
+
+
+ Runtime Error
+
+
+ Process Exited (1)
+
+
+
+ ArkEnvError: Errors found while validating environment variables
+
+
+
+
+ DATABASE_URL
+ {" "}
+ must be a valid URL starting with postgresql:// or mysql:// (was
+ "localhost/db")
+
+
+ PORT must
+ be an integer between 0 and 65535 (was "8080a")
+
+
+
+
+
+
+ );
+}
diff --git a/apps/www/components/page/video-demo.tsx b/apps/www/components/page/video-demo.tsx
index 741889bfd..f557a2e79 100644
--- a/apps/www/components/page/video-demo.tsx
+++ b/apps/www/components/page/video-demo.tsx
@@ -1,12 +1,12 @@
"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";
import demo from "~/videos/demo.mp4";
+import { WindowChrome } from "./window-chrome";
-// Actual video dimensions
const WIDTH = 2048;
const HEIGHT = 1672;
@@ -14,8 +14,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 in a faux window frame (main-branch pattern, Aurora tokens).
+ * Click opens the StackBlitz playground.
*/
export function VideoDemo() {
const [videoError, setVideoError] = useState(false);
@@ -31,58 +31,58 @@ export function VideoDemo() {
};
return (
-
-
- {/* Header / Title Bar */}
-
-
-
- basic - index.ts
-
-
-
- {/* Video Container */}
-
- {videoError ? (
-
- ) : (
-
- )}
- {/* Interactive Overlay Hint */}
-
-
-
Open interactive playground
-
+
+
+
+
+
+ {videoError ? (
+
+ ) : (
+
+ )}
+
+
+ Open interactive playground
+
+
-
-
+
+
+
+
+ View more examples
+
+
);
}
diff --git a/apps/www/components/page/window-chrome.tsx b/apps/www/components/page/window-chrome.tsx
new file mode 100644
index 000000000..8b3dea7f3
--- /dev/null
+++ b/apps/www/components/page/window-chrome.tsx
@@ -0,0 +1,35 @@
+/**
+ * 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 (
+
+
+
+
+
+
+ {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
);
}
diff --git a/apps/www/components/ui/theme-toggle.tsx b/apps/www/components/ui/theme-toggle.tsx
index 7a8f6eb18..1bf827063 100644
--- a/apps/www/components/ui/theme-toggle.tsx
+++ b/apps/www/components/ui/theme-toggle.tsx
@@ -9,6 +9,8 @@ import {
TooltipProvider,
TooltipTrigger,
} from "~/components/ui/tooltip";
+import { useFeatureFlag } from "~/hooks/use-feature-flag";
+import { FeatureFlag } from "~/lib/posthog/feature-flags";
import { cn } from "~/lib/utils";
type Option = {
@@ -22,6 +24,7 @@ let hydrated = false;
let cachedTheme: string | undefined;
export function ThemeToggle({ className }: { className?: string }) {
+ const themeToggleEnabled = useFeatureFlag(FeatureFlag.THEME_TOGGLE);
const { theme, setTheme } = useTheme();
const [mounted, setMounted] = useState(hydrated);
@@ -35,6 +38,10 @@ export function ThemeToggle({ className }: { className?: string }) {
setMounted(true);
}, []);
+ if (themeToggleEnabled !== true) {
+ return null;
+ }
+
const options = [
{ value: "light", icon: Sun, label: "Light" },
{ value: "system", icon: Monitor, label: "System" },
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?
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/hooks/use-feature-flag.test.ts b/apps/www/hooks/use-feature-flag.test.ts
new file mode 100644
index 000000000..d864ca1ec
--- /dev/null
+++ b/apps/www/hooks/use-feature-flag.test.ts
@@ -0,0 +1,48 @@
+import { renderHook, waitFor } from "@testing-library/react";
+import posthog from "posthog-js";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { FeatureFlag } from "~/lib/posthog/feature-flags";
+import { useFeatureFlag } from "./use-feature-flag";
+
+vi.mock("posthog-js", () => ({
+ default: {
+ isFeatureEnabled: vi.fn(),
+ onFeatureFlags: vi.fn(),
+ },
+}));
+
+describe("useFeatureFlag", () => {
+ beforeEach(() => {
+ vi.mocked(posthog.isFeatureEnabled).mockReset();
+ vi.mocked(posthog.onFeatureFlags).mockReset();
+ vi.mocked(posthog.onFeatureFlags).mockReturnValue(() => undefined);
+ });
+
+ it("returns false until the flag is explicitly true", async () => {
+ vi.mocked(posthog.isFeatureEnabled).mockReturnValue(undefined);
+ const { result } = renderHook(() =>
+ useFeatureFlag(FeatureFlag.THEME_TOGGLE),
+ );
+ expect(result.current).toBe(false);
+ });
+
+ it("returns true when PostHog reports the flag enabled", async () => {
+ vi.mocked(posthog.isFeatureEnabled).mockReturnValue(true);
+ const { result } = renderHook(() =>
+ useFeatureFlag(FeatureFlag.THEME_TOGGLE),
+ );
+ await waitFor(() => {
+ expect(result.current).toBe(true);
+ });
+ });
+
+ it("returns false for non-true flag values", async () => {
+ vi.mocked(posthog.isFeatureEnabled).mockReturnValue(false);
+ const { result } = renderHook(() =>
+ useFeatureFlag(FeatureFlag.THEME_TOGGLE),
+ );
+ await waitFor(() => {
+ expect(result.current).toBe(false);
+ });
+ });
+});
diff --git a/apps/www/hooks/use-feature-flag.ts b/apps/www/hooks/use-feature-flag.ts
new file mode 100644
index 000000000..f7c9af5a1
--- /dev/null
+++ b/apps/www/hooks/use-feature-flag.ts
@@ -0,0 +1,24 @@
+"use client";
+
+import posthog from "posthog-js";
+import { useEffect, useState } from "react";
+import type { FeatureFlag } from "~/lib/posthog/feature-flags";
+
+/**
+ * Subscribe to a PostHog boolean feature flag.
+ * Returns true only when the flag is explicitly enabled.
+ */
+export function useFeatureFlag(flag: FeatureFlag): boolean {
+ const [enabled, setEnabled] = useState(false);
+
+ useEffect(() => {
+ const sync = () => {
+ setEnabled(posthog.isFeatureEnabled(flag) === true);
+ };
+
+ sync();
+ return posthog.onFeatureFlags(sync);
+ }, [flag]);
+
+ return enabled;
+}
diff --git a/apps/www/lib/posthog/feature-flags.ts b/apps/www/lib/posthog/feature-flags.ts
new file mode 100644
index 000000000..f6fbb0727
--- /dev/null
+++ b/apps/www/lib/posthog/feature-flags.ts
@@ -0,0 +1,8 @@
+/**
+ * PostHog feature flag keys.
+ * Keep each flag consumed in as few places as possible.
+ */
+export enum FeatureFlag {
+ /** When true, show the light/dark/system theme switcher. Default: off. */
+ THEME_TOGGLE = "theme-toggle",
+}
diff --git a/apps/www/lib/posthog/index.ts b/apps/www/lib/posthog/index.ts
index 5c62e04f5..32f83d228 100644
--- a/apps/www/lib/posthog/index.ts
+++ b/apps/www/lib/posthog/index.ts
@@ -1 +1,2 @@
export * from "./config";
+export * from "./feature-flags";
diff --git a/apps/www/package.json b/apps/www/package.json
index 821184cb1..1f07b3a08 100644
--- a/apps/www/package.json
+++ b/apps/www/package.json
@@ -45,6 +45,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/public/assets/icon.svg b/apps/www/public/assets/icon.svg
index a02e45c3b..dfaaf9a1f 100644
--- a/apps/www/public/assets/icon.svg
+++ b/apps/www/public/assets/icon.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/apps/www/tokens.css b/apps/www/tokens.css
new file mode 100644
index 000000000..5b7295632
--- /dev/null
+++ b/apps/www/tokens.css
@@ -0,0 +1,69 @@
+/* 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);
+ --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;
+ --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);
+ /* 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;
+ --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 510eec1ad..3639995ff 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -677,6 +677,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
@@ -999,7 +1002,7 @@ importers:
devDependencies:
'@fumadocs/ui':
specifier: 16.5.0
- version: 16.5.0(@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.3))(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)
+ version: 16.5.0(@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(@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.3))(next@16.2.6(@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)
'@radix-ui/react-collapsible':
specifier: 1.1.12
version: 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
@@ -1014,10 +1017,10 @@ importers:
version: 19.2.3(@types/react@19.2.14)
fumadocs-core:
specifier: 16.8.7
- version: 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.3)
+ version: 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(@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.3)
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.3))(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)
+ 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(@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.3))(next@16.2.6(@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)
lucide-react:
specifier: 1.14.0
version: 1.14.0(react@19.2.5)
@@ -7709,6 +7712,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==}
@@ -12715,9 +12723,9 @@ snapshots:
'@tailwindcss/oxide': 4.2.4
tailwindcss: 4.2.4
- '@fumadocs/ui@16.5.0(@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.3))(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)':
+ '@fumadocs/ui@16.5.0(@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(@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.3))(next@16.2.6(@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)':
dependencies:
- 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.3)
+ 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(@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.3)
next-themes: 0.4.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
postcss-selector-parser: 7.1.1
react: 19.2.5
@@ -16658,7 +16666,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@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.9.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/expect@4.1.5':
dependencies:
@@ -16720,7 +16728,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@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.9.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/utils@4.1.5':
dependencies:
@@ -18412,7 +18420,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- 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.3):
+ 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(@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.3):
dependencies:
'@orama/orama': 3.1.18
estree-util-value-to-estree: 3.5.0
@@ -18556,7 +18564,7 @@ snapshots:
- '@types/react-dom'
- tailwindcss
- fumadocs-ui@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.3))(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):
+ fumadocs-ui@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(@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.3))(next@16.2.6(@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):
dependencies:
'@fumadocs/tailwind': 0.0.5(@tailwindcss/oxide@4.2.4)(tailwindcss@4.2.4)
'@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
@@ -18570,7 +18578,7 @@ snapshots:
'@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.5)
'@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
class-variance-authority: 0.7.1
- 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.3)
+ 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(@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.3)
lucide-react: 1.14.0(react@19.2.5)
motion: 12.38.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
next-themes: 0.4.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
@@ -18598,6 +18606,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);
+}