Discovered before you run.
Source generators turn your tests into executable code at build time, removing the need for runtime reflection to discover them.
Native AOT supportFrom 3ec8a84904d5632c38831ccca196482505884cac Mon Sep 17 00:00:00 2001
From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com>
Date: Sun, 4 Oct 2026 00:54:16 +0100
Subject: [PATCH 1/5] docs: give the site a test-run identity
Rework the docs theme and homepage around the vocabulary of a test run:
.NET violet as the brand colour, pass green and fail red reserved for
results, Schibsted Grotesk with JetBrains Mono for code.
- Homepage hero shows an animated parallel test run across worker lanes
- Write / Build / Run sections, including an analyzer squiggle demo
- Sidebar tree guide rail, editor-tab code titles, refreshed favicon
- Ask AI widget switched to the slate preset
---
docs/docusaurus.config.ts | 2 +-
docs/src/css/custom.css | 282 ++++++++++++-------
docs/src/pages/index.module.css | 337 ++++++++++------------
docs/src/pages/index.tsx | 454 ++++++++++++++++++++----------
docs/static/img/tunit-favicon.svg | 6 +-
5 files changed, 625 insertions(+), 456 deletions(-)
diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts
index 6c9d7fe0902..ea00ecb9bc6 100644
--- a/docs/docusaurus.config.ts
+++ b/docs/docusaurus.config.ts
@@ -42,7 +42,7 @@ const config: Config = {
innerHTML: `window.tlumaConfig = {
source: "thomhurst/tunit",
theme: "auto",
- brandColor: "green",
+ brandColor: "slate",
button: "bottom-right",
welcomePulse: true,
edgePadding: "1rem",
diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css
index 4fd525f5712..243ed69ae27 100644
--- a/docs/src/css/custom.css
+++ b/docs/src/css/custom.css
@@ -1,108 +1,123 @@
@import url('./benchmark-charts.css');
-@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap');
+@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Schibsted+Grotesk:ital,wght@0,400..900;1,400..900&display=swap');
-/* Shared with the homepage: forest ink, mint surfaces, and plum accents. */
+/*
+ * TUnit borrows the vocabulary of a test run: .NET violet for the brand,
+ * pass green and fail red reserved for results, and cool lab-white paper.
+ */
:root {
- --tunit-ink: #214b3c;
- --tunit-body: #365747;
- --tunit-muted: #50695d;
- --tunit-paper: #f8fbf9;
- --tunit-surface: #d5f4e5;
- --tunit-border: #b5d0c1;
- --tunit-accent: #654b87;
- --tunit-accent-surface: #e4def3;
- --tunit-code: #edf6f0;
- --tunit-on-primary: #f8fbf9;
- --tunit-warning: #624915;
- --tunit-warning-surface: #f6e9c6;
- --tunit-danger: #7f3431;
- --tunit-danger-surface: #f5dfdc;
- --tunit-chart-primary: #365e4b;
- --tunit-chart-secondary: #654b87;
- --tunit-chart-comparison: #50695d;
- --tunit-chart-label: #f8fbf9;
+ --tunit-ink: #1a1633;
+ --tunit-body: #36325a;
+ --tunit-muted: #5c5880;
+ --tunit-paper: #f6f7fb;
+ --tunit-surface: #eceef7;
+ --tunit-border: #d6d8e8;
+ --tunit-accent: #512bd4;
+ --tunit-accent-surface: #ebe5ff;
+ --tunit-code: #eff0f8;
+ --tunit-on-primary: #ffffff;
+ --tunit-pass: #127a43;
+ --tunit-pass-fill: #22b866;
+ --tunit-fail: #c3352b;
+ --tunit-warning: #6b4a00;
+ --tunit-warning-surface: #fbefcc;
+ --tunit-danger: #9a2a22;
+ --tunit-danger-surface: #fbe3e0;
+ --tunit-chart-primary: #512bd4;
+ --tunit-chart-secondary: #22b866;
+ --tunit-chart-comparison: #8f8bb0;
+ --tunit-chart-label: #ffffff;
- --ifm-color-primary: #365e4b;
- --ifm-color-primary-dark: #305443;
- --ifm-color-primary-darker: #2b4b3c;
- --ifm-color-primary-darkest: #214b3c;
- --ifm-color-primary-light: #3b6652;
- --ifm-color-primary-lighter: #406e58;
- --ifm-color-primary-lightest: #45775f;
- --ifm-color-emphasis-100: #edf6f0;
- --ifm-color-emphasis-200: #dce8df;
- --ifm-color-emphasis-300: #b5d0c1;
- --ifm-color-emphasis-400: #91b09f;
- --ifm-color-emphasis-500: #708e7d;
- --ifm-color-emphasis-600: #50695d;
- --ifm-color-emphasis-700: #365747;
- --ifm-color-emphasis-800: #294f3f;
- --ifm-color-emphasis-900: #214b3c;
- --syntax-keyword: #654b87;
- --syntax-string: #286044;
- --syntax-function: #704472;
- --syntax-class: #296462;
- --syntax-number: #795320;
- --syntax-comment: #607568;
+ --ifm-color-primary: #512bd4;
+ --ifm-color-primary-dark: #4926bf;
+ --ifm-color-primary-darker: #4524b4;
+ --ifm-color-primary-darkest: #381e94;
+ --ifm-color-primary-light: #623ed9;
+ --ifm-color-primary-lighter: #6a48db;
+ --ifm-color-primary-lightest: #8466e2;
+ --ifm-color-emphasis-100: #eff0f8;
+ --ifm-color-emphasis-200: #e2e4f0;
+ --ifm-color-emphasis-300: #d6d8e8;
+ --ifm-color-emphasis-400: #aeb0c9;
+ --ifm-color-emphasis-500: #8f8bb0;
+ --ifm-color-emphasis-600: #5c5880;
+ --ifm-color-emphasis-700: #36325a;
+ --ifm-color-emphasis-800: #272348;
+ --ifm-color-emphasis-900: #1a1633;
+ --syntax-keyword: #512bd4;
+ --syntax-string: #a3461b;
+ --syntax-function: #7a3e9d;
+ --syntax-class: #0f6e78;
+ --syntax-number: #127a43;
+ --syntax-comment: #6f6c8e;
}
html[data-theme='dark'] {
- --tunit-ink: #d5f4e5;
- --tunit-body: #c1d9cc;
- --tunit-muted: #acc5b8;
- --tunit-paper: #172f27;
- --tunit-surface: #203e32;
- --tunit-border: #426351;
- --tunit-accent: #d2bafa;
- --tunit-accent-surface: #3e3450;
- --tunit-code: #203e32;
- --tunit-on-primary: #172f27;
- --tunit-warning: #eed39a;
- --tunit-warning-surface: #423a27;
- --tunit-danger: #f4bab5;
- --tunit-danger-surface: #452c2d;
+ --tunit-ink: #eeecfa;
+ --tunit-body: #cfcce6;
+ --tunit-muted: #a6a2c6;
+ --tunit-paper: #16132b;
+ --tunit-surface: #1f1b3a;
+ --tunit-border: #37325c;
+ --tunit-accent: #ae98ff;
+ --tunit-accent-surface: #2f2660;
+ --tunit-code: #1d1937;
+ --tunit-on-primary: #16132b;
+ --tunit-pass: #5ad993;
+ --tunit-pass-fill: #3ccf80;
+ --tunit-fail: #ff8a80;
+ --tunit-warning: #f3d58e;
+ --tunit-warning-surface: #3b3220;
+ --tunit-danger: #ffb4ad;
+ --tunit-danger-surface: #43222a;
+ --tunit-chart-primary: #ae98ff;
+ --tunit-chart-secondary: #3ccf80;
+ --tunit-chart-comparison: #6f6b95;
+ --tunit-chart-label: #16132b;
- --ifm-color-primary: #b6dbc6;
- --ifm-color-primary-dark: #a2d1b7;
- --ifm-color-primary-darker: #8ec7a8;
- --ifm-color-primary-darkest: #7abd99;
- --ifm-color-primary-light: #c6e7d5;
- --ifm-color-primary-lighter: #d5f4e5;
- --ifm-color-primary-lightest: #e5f9ee;
- --ifm-color-emphasis-100: #203e32;
- --ifm-color-emphasis-200: #2e4e3f;
- --ifm-color-emphasis-300: #426351;
- --ifm-color-emphasis-400: #628570;
- --ifm-color-emphasis-500: #86a694;
- --ifm-color-emphasis-600: #acc5b8;
- --ifm-color-emphasis-700: #c1d9cc;
- --ifm-color-emphasis-800: #cbe7d8;
- --ifm-color-emphasis-900: #d5f4e5;
- --syntax-keyword: #d2bafa;
- --syntax-string: #b6dbc6;
- --syntax-function: #e0bddb;
- --syntax-class: #a6d9d2;
- --syntax-number: #eed39a;
- --syntax-comment: #acc5b8;
+ --ifm-color-primary: #ae98ff;
+ --ifm-color-primary-dark: #9c82ff;
+ --ifm-color-primary-darker: #9377ff;
+ --ifm-color-primary-darkest: #7856ff;
+ --ifm-color-primary-light: #c0aeff;
+ --ifm-color-primary-lighter: #c9b9ff;
+ --ifm-color-primary-lightest: #e1d9ff;
+ --ifm-color-emphasis-100: #1f1b3a;
+ --ifm-color-emphasis-200: #2a2549;
+ --ifm-color-emphasis-300: #37325c;
+ --ifm-color-emphasis-400: #504a7a;
+ --ifm-color-emphasis-500: #6f6b95;
+ --ifm-color-emphasis-600: #a6a2c6;
+ --ifm-color-emphasis-700: #cfcce6;
+ --ifm-color-emphasis-800: #e0def2;
+ --ifm-color-emphasis-900: #eeecfa;
+ --syntax-keyword: #b9a6ff;
+ --syntax-string: #f3b58a;
+ --syntax-function: #e3b3f5;
+ --syntax-class: #7fd8de;
+ --syntax-number: #7be3a8;
+ --syntax-comment: #8f8bb0;
}
/* Define aliases in both themes so inherited values resolve against that theme. */
:root, html[data-theme='dark'] {
- --ifm-font-family-base: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+ --ifm-font-family-base: 'Schibsted Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+ --ifm-font-family-monospace: 'JetBrains Mono', 'Cascadia Code', Consolas, monospace;
+ --ifm-line-height-base: 1.7;
--ifm-heading-font-family: var(--ifm-font-family-base);
- --ifm-heading-font-weight: 600;
+ --ifm-heading-font-weight: 700;
--ifm-font-color-base: var(--tunit-body);
--ifm-heading-color: var(--tunit-ink);
--ifm-background-color: var(--tunit-paper);
--ifm-background-surface-color: var(--tunit-surface);
- --ifm-navbar-background-color: var(--tunit-surface);
+ --ifm-navbar-background-color: var(--tunit-paper);
--ifm-navbar-link-color: var(--tunit-ink);
--ifm-navbar-link-hover-color: var(--tunit-accent);
--ifm-link-color: var(--tunit-accent);
--ifm-link-hover-color: var(--tunit-ink);
--ifm-menu-color: var(--tunit-muted);
--ifm-menu-color-active: var(--tunit-ink);
- --ifm-menu-color-background-active: var(--tunit-surface);
+ --ifm-menu-color-background-active: transparent;
--ifm-menu-color-background-hover: var(--tunit-code);
--ifm-breadcrumb-color-active: var(--tunit-accent);
--ifm-breadcrumb-item-background-active: var(--tunit-accent-surface);
@@ -111,10 +126,10 @@ html[data-theme='dark'] {
--ifm-color-content-secondary: var(--tunit-muted);
--ifm-card-background-color: var(--tunit-code);
--ifm-code-background: var(--tunit-code);
- --ifm-code-font-size: 95%;
+ --ifm-code-font-size: 88%;
--ifm-table-border-color: var(--tunit-border);
--ifm-table-head-background: var(--tunit-surface);
- --ifm-table-stripe-background: var(--tunit-code);
+ --ifm-table-stripe-background: transparent;
--ifm-hr-background-color: var(--tunit-border);
--docusaurus-highlighted-code-line-bg: var(--tunit-accent-surface);
--text-primary: var(--tunit-ink);
@@ -129,6 +144,8 @@ html[data-theme='dark'] {
}
body { margin: 0; }
+/* JetBrains Mono would draw `--` as a dash, which misleads in CLI commands. */
+code, pre, kbd { font-variant-ligatures: none; }
a { text-underline-offset: 3px; }
:where(a, button, input, select, summary, [tabindex]):focus-visible {
outline: 3px solid var(--tunit-accent);
@@ -140,17 +157,18 @@ a { text-underline-offset: 3px; }
box-shadow: none;
}
.navbar__brand { gap: 0.65rem; }
-.navbar__title { color: var(--tunit-ink); font-size: 1.15rem; letter-spacing: -0.04em; }
-.navbar__link { font-size: 0.85rem; font-weight: 600; }
-.navbar__logo { height: 1.85rem; }
+.navbar__title { color: var(--tunit-ink); font-size: 1.3rem; font-weight: 800; letter-spacing: -0.045em; }
+.navbar__link { font-size: 0.9rem; font-weight: 500; }
+.navbar__link--active { color: var(--tunit-accent); }
+.navbar__logo { height: 1.75rem; }
.navbar__toggle, .navbar-sidebar__close { color: var(--tunit-ink); }
.navbar-sidebar { background: var(--tunit-paper); }
.navbar-sidebar__brand { background: var(--tunit-surface); border-bottom: 1px solid var(--tunit-border); }
.navbar-sidebar__back { color: var(--tunit-ink); background: var(--tunit-code); }
-.navbar-sidebar .menu__link--active { color: var(--tunit-ink); background: var(--tunit-surface); }
+.navbar-sidebar .menu__link--active { color: var(--tunit-accent); }
.footer, .footer--dark {
- --ifm-footer-background-color: var(--tunit-surface);
+ --ifm-footer-background-color: var(--tunit-paper);
--ifm-footer-color: var(--tunit-muted);
--ifm-footer-title-color: var(--tunit-ink);
--ifm-footer-link-color: var(--tunit-body);
@@ -163,7 +181,7 @@ html[data-theme] {
--docsearch-primary-color: var(--tunit-accent);
--docsearch-text-color: var(--tunit-ink);
--docsearch-muted-color: var(--tunit-muted);
- --docsearch-container-background: rgb(23 47 39 / 65%);
+ --docsearch-container-background: rgb(26 22 51 / 60%);
--docsearch-modal-background: var(--tunit-paper);
--docsearch-searchbox-background: var(--tunit-code);
--docsearch-searchbox-focus-background: var(--tunit-paper);
@@ -179,39 +197,87 @@ html[data-theme] {
--docsearch-footer-shadow: 0 -1px 0 var(--tunit-border);
--docsearch-logo-color: var(--tunit-muted);
}
-.DocSearch-Button { background: var(--tunit-paper); border: 1px solid var(--tunit-border); color: var(--tunit-muted); border-radius: 6px; }
+.DocSearch-Button { background: var(--tunit-surface); border: 1px solid transparent; color: var(--tunit-muted); border-radius: 8px; }
.DocSearch-Button:hover { background: var(--tunit-code); color: var(--tunit-ink); box-shadow: inset 0 0 0 1px var(--tunit-accent); }
.DocSearch-Button-Key { background: var(--tunit-code); border: 1px solid var(--tunit-border); color: var(--tunit-muted); box-shadow: none; }
.DocSearch-Hit[aria-selected='true'] a { background: var(--tunit-accent-surface); }
.DocSearch-Hit[aria-selected='true'] :is(.DocSearch-Hit-title, .DocSearch-Hit-path, .DocSearch-Hit-icon, mark) { color: var(--tunit-ink) !important; }
.theme-doc-sidebar-container { background: var(--tunit-paper); border-color: var(--tunit-border) !important; }
-.menu__link { border-radius: 5px; }
-.menu__link--active { font-weight: 600; }
-.table-of-contents__link--active, .table-of-contents__link:hover { color: var(--tunit-accent); }
-.theme-doc-markdown h1 { letter-spacing: -0.04em; }
-.theme-doc-markdown h2 { letter-spacing: -0.03em; }
+.theme-doc-sidebar-menu { font-size: 0.92rem; }
+.menu { padding-top: 1.25rem; }
+.menu__link { border-radius: 6px; }
+.menu__link:hover { color: var(--tunit-ink); }
+.menu__list-item-collapsible:hover { background: var(--tunit-surface); }
+:is(.theme-doc-sidebar-item-category-level-1 > .menu__list-item-collapsible, .theme-doc-sidebar-item-link-level-1) > .menu__link { color: var(--tunit-ink); font-weight: 650; }
+
+/* Nested sidebar levels read like a test explorer tree: a guide rail, with the open page marked on it. */
+.theme-doc-sidebar-menu .menu__list .menu__list {
+ margin-left: 0.85rem;
+ padding-left: 0.35rem;
+ border-left: 1px solid var(--tunit-border);
+}
+.theme-doc-sidebar-menu .menu__link--active:not(.menu__link--sublist) {
+ position: relative;
+ color: var(--tunit-accent);
+ font-weight: 650;
+ background: var(--tunit-accent-surface);
+}
+.theme-doc-sidebar-menu .menu__list .menu__list .menu__link--active:not(.menu__link--sublist)::before {
+ content: '';
+ position: absolute;
+ left: calc(-0.35rem - 2px);
+ top: 0.35rem;
+ bottom: 0.35rem;
+ width: 3px;
+ border-radius: 2px;
+ background: var(--tunit-accent);
+}
+
+.table-of-contents { font-size: 0.82rem; }
+.table-of-contents__link--active { color: var(--tunit-accent); font-weight: 600; }
+.table-of-contents__link:hover { color: var(--tunit-accent); }
+.theme-doc-markdown { --ifm-h1-font-size: 2.9rem; --ifm-h2-font-size: 1.85rem; --ifm-h3-font-size: 1.3rem; }
+.theme-doc-markdown h1 { letter-spacing: -0.045em; line-height: 1.05; font-weight: 800; }
+.theme-doc-markdown h2 { letter-spacing: -0.03em; margin-top: 3rem; }
+.theme-doc-markdown h3 { letter-spacing: -0.015em; margin-top: 2rem; }
.theme-doc-markdown :is(h2, h3) { scroll-margin-top: calc(var(--ifm-navbar-height) + 1rem); }
-.pagination-nav__link { background: var(--tunit-code); border-color: var(--tunit-border); border-radius: 6px; }
-.pagination-nav__link:hover { border-color: var(--tunit-accent); }
+.theme-doc-markdown > p, .theme-doc-markdown > ul, .theme-doc-markdown > ol { max-width: 72ch; }
+.breadcrumbs__link { font-size: 0.8rem; }
+.pagination-nav__link { background: transparent; border-color: var(--tunit-border); border-radius: 10px; }
+.pagination-nav__link:hover { border-color: var(--tunit-accent); background: var(--tunit-surface); }
.pagination-nav__label { color: var(--tunit-ink); }
.pagination-nav__sublabel { color: var(--tunit-muted); }
-code { color: var(--tunit-ink); border-color: var(--tunit-border); }
-.theme-code-block { background: var(--tunit-code); border: 1px solid var(--tunit-border); box-shadow: none; }
-.theme-code-block pre { background: var(--tunit-code) !important; color: var(--tunit-ink) !important; }
+:not(pre) > code { color: var(--tunit-ink); background: var(--tunit-accent-surface); border: 0; padding: 0.1em 0.35em; border-radius: 4px; }
+.theme-code-block { --prism-background-color: var(--tunit-code); background: var(--tunit-code); border: 1px solid var(--tunit-border); border-radius: 10px; box-shadow: none; }
+.theme-code-block pre { background: var(--tunit-code) !important; color: var(--tunit-ink) !important; font-size: 0.84rem; line-height: 1.7; }
.theme-code-block pre code { background: transparent; color: inherit; border: 0; }
-.theme-code-block [class*='codeBlockTitle'] { background: var(--tunit-surface); border-color: var(--tunit-border); color: var(--tunit-ink); }
+/* Titled code blocks look like an editor tab, since the title is almost always a file name. */
+.theme-code-block [class*='codeBlockTitle'] {
+ display: inline-block;
+ margin: 0.5rem 0 0 0.75rem;
+ padding: 0.35rem 0.8rem;
+ background: var(--tunit-paper);
+ border: 1px solid var(--tunit-border);
+ border-bottom: 0;
+ border-radius: 7px 7px 0 0;
+ box-shadow: inset 0 2px 0 var(--tunit-accent);
+ color: var(--tunit-ink);
+ font-family: var(--ifm-font-family-monospace);
+ font-size: 0.75rem;
+}
+.theme-code-block [class*='codeBlockTitle'] + div { border-top: 1px solid var(--tunit-border); }
.theme-code-block button { color: var(--tunit-ink); background: var(--tunit-paper); border-color: var(--tunit-border); }
.theme-code-block .token:is(.keyword, .tag, .namespace) { color: var(--syntax-keyword) !important; }
.theme-code-block .token:is(.string, .attr-value) { color: var(--syntax-string) !important; }
.theme-code-block .token:is(.function, .method, .property) { color: var(--syntax-function) !important; }
.theme-code-block .token:is(.class-name, .type, .builtin) { color: var(--syntax-class) !important; }
.theme-code-block .token:is(.number, .boolean, .constant) { color: var(--syntax-number) !important; }
-.theme-code-block .token:is(.comment, .prolog, .doctype, .cdata) { color: var(--syntax-comment) !important; }
+.theme-code-block .token:is(.comment, .prolog, .doctype, .cdata) { color: var(--syntax-comment) !important; font-style: italic; }
.theme-code-block .token:is(.plain, .punctuation, .operator, .variable, .parameter) { color: var(--tunit-ink) !important; }
-blockquote { background: var(--tunit-code); border-left-color: var(--tunit-accent); color: var(--tunit-body); padding: 1rem; }
+blockquote { background: transparent; border-left: 3px solid var(--tunit-accent); color: var(--tunit-body); padding: 0.25rem 1rem; }
blockquote > :last-child { margin-bottom: 0; }
.tabs__item--active { color: var(--tunit-accent); border-bottom-color: var(--tunit-accent); }
.tabs__item:hover { background: var(--tunit-code); }
@@ -224,8 +290,8 @@ blockquote > :last-child { margin-bottom: 0; }
--ifm-alert-foreground-color: var(--tunit-ink);
}
.theme-admonition.alert--success {
- --ifm-alert-background-color: var(--tunit-surface);
- --ifm-alert-border-color: var(--ifm-color-primary);
+ --ifm-alert-background-color: color-mix(in srgb, var(--tunit-pass-fill) 12%, var(--tunit-paper));
+ --ifm-alert-border-color: var(--tunit-pass);
--ifm-alert-foreground-color: var(--tunit-ink);
}
.theme-admonition.alert--warning {
@@ -238,10 +304,10 @@ blockquote > :last-child { margin-bottom: 0; }
--ifm-alert-border-color: var(--tunit-danger);
--ifm-alert-foreground-color: var(--tunit-danger);
}
-.theme-admonition { color: var(--ifm-alert-foreground-color); box-shadow: none; }
+.theme-admonition { color: var(--ifm-alert-foreground-color); box-shadow: none; border-width: 0 0 0 3px; border-radius: 0 8px 8px 0; }
.theme-admonition :is(p, h5) { color: inherit; }
.theme-admonition a { color: inherit; text-decoration: underline; }
-.theme-admonition code { background: var(--tunit-paper); color: var(--tunit-ink); }
+.theme-admonition :not(pre) > code { background: var(--tunit-paper); color: var(--tunit-ink); }
.button--primary { --ifm-button-color: var(--tunit-on-primary); }
::-webkit-scrollbar { width: 10px; height: 10px; }
diff --git a/docs/src/pages/index.module.css b/docs/src/pages/index.module.css
index 62ad6f84c8c..beaf0222d8e 100644
--- a/docs/src/pages/index.module.css
+++ b/docs/src/pages/index.module.css
@@ -1,210 +1,157 @@
.page {
- --forest: var(--tunit-ink);
- --muted: var(--tunit-muted);
- --mint: var(--tunit-surface);
- --paper: var(--tunit-paper);
- --line: var(--tunit-border);
- --lilac: #e4def3;
- --plum: var(--tunit-accent);
- background: var(--paper);
- color: var(--forest);
- font-family: 'Space Grotesk', 'Segoe UI', sans-serif;
+ /* The run panel stays a terminal in both themes. */
+ --run-bg: #1a1633;
+ --run-border: #1a1633;
+ --run-text: #e6e3f7;
+ --run-muted: #a29ec4;
+ --run-rail: #2c2752;
+ --run-bar: #3d3577;
+ --run-bar-text: #e6e3f7;
+ --run-pass: #3ccf80;
+ --run-pass-text: #0d2b1b;
+ background: var(--tunit-paper);
+ color: var(--tunit-ink);
}
-.container { width: min(1280px, calc(100% - 6rem)); margin-inline: auto; }
-.page h1, .page h2, .page h3 { color: var(--forest); }
-.page p { color: var(--muted); }
-.page a { color: var(--forest); text-underline-offset: 5px; }
-.page a:hover { color: var(--plum); }
-.page :is(a, button, pre):focus-visible { outline: 3px solid var(--plum); outline-offset: 5px; }
+:global([data-theme='dark']) .page {
+ --run-bg: #100d22;
+ --run-border: #37325c;
+ --run-rail: #221d42;
+}
+
+.container { width: min(1240px, calc(100% - 4rem)); margin-inline: auto; }
+.page h1, .page h2, .page h3 { color: var(--tunit-ink); }
+.page p { color: var(--tunit-body); }
+.page a { color: var(--tunit-accent); font-weight: 600; text-underline-offset: 4px; }
+.page a:hover { color: var(--tunit-ink); }
+.page :is(a, button, pre):focus-visible { outline: 3px solid var(--tunit-accent); outline-offset: 3px; }
.page button { font: inherit; cursor: pointer; }
-.page h2 { font-size: clamp(2.4rem, 4.2vw, 3.8rem); line-height: 1.08; letter-spacing: -0.06em; font-weight: 600; }
-.page h3 { letter-spacing: -0.035em; font-weight: 600; }
-.hero { padding: 2rem 0 0; background: var(--mint); }
-.heroIntro { display: flex; justify-content: space-between; gap: 1rem; font-size: 0.85rem; }
-.heroIntro a { text-decoration: underline; }
-.heroGrid { display: grid; grid-template-columns: 0.9fr 1.1fr; gap: 4rem; align-items: center; padding: 3rem 0 3.5rem; }
-.wordmark { display: flex; align-items: center; gap: 0.18em; margin: 0 0 1.4rem -0.055em; font-size: clamp(6rem, 11.5vw, 10rem); letter-spacing: -0.085em; line-height: 1; font-weight: 700; }
-.wordmarkCheck { display: grid; place-items: center; width: 0.48em; height: 0.48em; border: 3px solid currentColor; border-radius: 0.1em; transform: rotate(-8deg); }
-.wordmarkCheck svg { width: 85%; height: 85%; }
-.page .heroTitle { color: var(--forest); font-size: clamp(2.6rem, 4vw, 3.8rem); line-height: 1.06; font-weight: 500; letter-spacing: -0.065em; margin-bottom: 1.25rem; }
-.heroLead { max-width: 24rem; font-size: 1.08rem; line-height: 1.65; }
-.actions { display: flex; flex-wrap: wrap; align-items: center; gap: 1.7rem; margin-top: 1.8rem; }
-.page .primaryButton, .page .secondaryButton { display: inline-flex; align-items: center; justify-content: center; min-height: 3.2rem; padding: 0.8rem 1.4rem; border-radius: 6px; font-weight: 600; text-decoration: none; }
-.page .primaryButton { background: var(--forest); color: var(--paper); border: 1px solid var(--forest); }
-.page .primaryButton:hover { background: #654b87; border-color: #654b87; color: #fff; }
-.page .secondaryButton { border: 1px solid var(--forest); white-space: nowrap; }
-.page .secondaryButton:hover { background: var(--forest); color: var(--paper); }
-.quietLink { font-weight: 600; text-decoration: underline; }
-.heroFootnote { font-size: 0.78rem; margin: 1.7rem 0 0; }
+.primaryButton, .quietLink { display: inline-flex; align-items: center; min-height: 3rem; }
+.page .primaryButton { padding: 0.75rem 1.4rem; border-radius: 8px; background: var(--tunit-accent); color: var(--tunit-on-primary); text-decoration: none; }
+.page .primaryButton:hover { background: var(--tunit-ink); color: var(--tunit-paper); }
+.page .quietLink { text-decoration: underline; }
-.workbench { min-width: 0; background: #fff; color: #214b3c; border: 1px solid #8eb69f; border-radius: 12px; box-shadow: 8px 8px 0 #b6dbc6; overflow: hidden; }
-.workbenchHeading { display: flex; align-items: center; justify-content: space-between; padding: 1.1rem 1.5rem; gap: 1rem; background: #edf6f0; font-weight: 600; font-size: 0.95rem; }
-.language { font-size: 0.75rem; color: #50695d; }
-.examplePicker { display: flex; padding: 0 1rem; gap: 0.5rem; border-bottom: 1px solid #dce8df; }
-.examplePicker button { position: relative; min-height: 3.1rem; padding: 0.7rem 0.5rem; border: 0; border-bottom: 3px solid transparent; background: transparent; color: #50695d; font-size: 0.76rem; }
-.examplePicker button[aria-pressed='true'] { border-bottom-color: #654b87; color: #654b87; font-weight: 700; }
-.examplePicker button:hover { background: #f4f0fa; color: #654b87; }
-.page .examplePicker button:focus-visible { outline-offset: -4px; }
-.page .workbench :is(button, pre, a):focus-visible { outline-color: #654b87; }
-.fileName { display: flex; align-items: center; gap: 0.55rem; margin: 1.2rem 1.5rem 0.6rem; font-size: 0.7rem; color: #50695d; }
-.fileName > span:first-child { color: #654b87; font-weight: 700; }
-.fileName > span:last-child { margin-left: auto; color: #50695d; font-size: 0.65rem; }
-.page .code { min-height: 16rem; margin: 0; padding: 0.5rem 1.5rem 1rem; background: #fff !important; border-radius: 0; color: #214b3c !important; font-size: 0.77rem; line-height: 1.85; tab-size: 4; overflow-x: auto; }
-.page .code code { display: block; border: 0; padding: 0; background: transparent !important; color: #214b3c !important; font-size: inherit !important; }
-.page .code span,
-:global([data-theme='dark']) .page .code span { color: var(--preview-token, #214b3c) !important; }
-.page .code .lineNumber { --preview-token: #607568; display: inline-block; width: 2.1rem; user-select: none; }
-.testResults { min-height: 9.8rem; border-top: 1px solid #dce8df; padding: 1rem 1.5rem; background: #f4f9f5; }
-.resultsHeading { display: flex; justify-content: space-between; gap: 1rem; margin-bottom: 0.65rem; font-size: 0.72rem; }
-.resultsHeading strong { color: #286044; font-weight: 600; }
-.testResults ul { padding: 0; margin: 0; list-style: none; }
-.testResults li { display: flex; align-items: center; gap: 0.55rem; padding: 0.2rem 0; font-family: var(--ifm-font-family-monospace); font-size: 0.7rem; }
-.testResults li svg { flex-shrink: 0; width: 1rem; height: 1rem; color: #286044; }
-.testResults li > span:nth-child(2) { overflow-wrap: anywhere; }
-.testResults li > span:last-child { margin-left: auto; font-family: 'Space Grotesk', sans-serif; color: #50695d; font-size: 0.65rem; }
-.exampleNote { display: flex; justify-content: space-between; align-items: center; gap: 1rem; min-height: 4rem; padding: 0.8rem 1.5rem; background: #e4def3; font-size: 0.7rem; }
-.page .exampleNote p { color: #513c6e; margin: 0; max-width: 30ch; }
-.page .exampleNote a { color: #513c6e; white-space: nowrap; text-decoration: underline; font-weight: 600; }
-.compilePath { display: flex; align-items: center; gap: 1.3rem; border-top: 1px solid var(--line); padding: 1.4rem 0; font-size: 0.82rem; }
-.pathLine { flex: 1; height: 1px; background: #8eb69f; position: relative; }
-.pathLine::after { content: ''; position: absolute; right: 0; top: -3px; width: 7px; height: 7px; border-top: 1px solid #8eb69f; border-right: 1px solid #8eb69f; transform: rotate(45deg); }
-.parallelMark { display: flex; flex-direction: column; gap: 4px; }
-.parallelMark i { width: 1.8rem; height: 3px; background: var(--forest); border-radius: 2px; }
+/* Hero */
+.hero { padding: 4.5rem 0 5rem; }
+.heroCopy { margin-bottom: 3rem; }
+.heroAside { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1.5rem 3rem; margin-top: 2rem; }
+.heroTitle { margin: 0; font-size: clamp(2.6rem, 7.2vw, 6rem); font-weight: 800; line-height: 0.98; letter-spacing: -0.055em; }
+.heroTitle span { display: block; }
+.heroLead { margin: 0; max-width: 40ch; font-size: 1.12rem; line-height: 1.6; }
+.actions { display: flex; flex-wrap: wrap; align-items: center; gap: 1.5rem; }
-.quickStart { display: grid; grid-template-columns: 0.8fr 1.2fr; gap: 4rem; padding-block: 5.5rem; }
-.page .quickStartIntro h2 { font-size: 2.25rem; }
-.quickStartIntro p { font-size: 0.9rem; }
-.quickStartIntro a { font-size: 0.85rem; text-decoration: underline; }
-.installCommands { min-width: 0; }
-.installCommands ol { list-style: none; padding: 0; margin: 0; }
-.installCommands li { display: flex; align-items: center; gap: 1.2rem; padding: 1.1rem 0; border-bottom: 1px solid var(--line); }
-.installCommands li:first-child { padding-top: 0; }
-.stepNumber { display: grid; flex-shrink: 0; place-items: center; width: 1.6rem; height: 1.6rem; border: 1px solid var(--line); border-radius: 50%; font-size: 0.75rem; }
-.installCommands li > div { min-width: 0; flex: 1; }
-.stepLabel { display: block; margin-bottom: 0.3rem; font-size: 0.72rem; color: var(--muted); }
-.page .installCommands code { border: 0; padding: 0; background: transparent !important; color: var(--forest) !important; font-size: 0.83rem; overflow-wrap: anywhere; }
-.installCommands button { border: 1px solid var(--line); background: transparent; color: var(--forest); border-radius: 4px; font-size: 0.7rem; padding: 0.5rem 0.6rem; min-height: 2.5rem; }
-.installCommands button:hover { background: var(--mint); }
-.copyStatus { margin: 0.5rem 0 0; font-size: 0.72rem; }
+/* Run panel */
+.run { margin: 0; padding: 1.5rem 1.75rem 1.25rem; background: var(--run-bg); border: 1px solid var(--run-border); border-radius: 14px; color: var(--run-text); font-family: var(--ifm-font-family-monospace); font-size: 0.8rem; font-variant-ligatures: none; }
+.runCommand { color: var(--run-text); }
+.prompt { color: var(--run-pass); margin-right: 0.4rem; }
+.page .runDiscovery { margin: 0.35rem 0 1.4rem; color: var(--run-muted); }
+.lanes { position: relative; display: grid; gap: 0.55rem; }
+.lane { display: grid; grid-template-columns: 5.5rem 1fr; align-items: center; gap: 1rem; }
+.laneName { color: var(--run-muted); font-size: 0.72rem; }
+.track { position: relative; height: 2rem; border-radius: 6px; background: var(--run-rail); }
+.bar { position: absolute; top: 0; bottom: 0; display: flex; align-items: center; gap: 0.3rem; padding: 0 0.55rem; overflow: hidden; border-radius: 6px; background: var(--run-bar); color: var(--run-bar-text); font-size: 0.7rem; white-space: nowrap; }
+.barDone { background: var(--run-pass); color: var(--run-pass-text); font-weight: 500; }
+.barLabel { overflow: hidden; text-overflow: ellipsis; }
+.bar svg { flex-shrink: 0; width: 0.9rem; height: 0.9rem; margin-left: auto; }
+.playhead { position: absolute; top: -0.4rem; bottom: -0.4rem; left: calc(6.5rem + (100% - 6.5rem) * var(--playhead)); width: 1px; background: var(--run-text); opacity: 0.5; }
+.runSummary { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem 1.5rem; margin-top: 1.4rem; padding-top: 1rem; border-top: 1px solid var(--run-rail); color: var(--run-muted); }
+.runSummary strong { color: var(--run-text); font-weight: 700; }
+.verdict { display: inline-flex; align-items: center; gap: 0.35rem; min-width: 6rem; color: var(--run-text); font-weight: 700; }
+.verdictPassed { color: var(--run-pass); }
+.verdict svg { width: 1rem; height: 1rem; }
+.page .replay { margin-left: auto; min-height: 2.25rem; padding: 0.3rem 0.8rem; border: 1px solid var(--run-bar); border-radius: 6px; background: transparent; color: var(--run-text); font-size: 0.75rem; }
+.page .replay:hover:not(:disabled) { background: var(--run-bar); }
+.page .replay:disabled { cursor: default; opacity: 0.45; }
+.page .replay:focus-visible { outline-color: var(--run-pass); }
-.architecture { background: var(--mint); padding-block: 5.5rem; }
-.architectureGrid { display: grid; grid-template-columns: 1fr 1fr; gap: 7rem; }
-.architectureIntro > p { max-width: 36ch; line-height: 1.7; margin-block: 1.5rem; }
-.architectureIntro > a { font-size: 0.85rem; }
-.discoveryDiagram { display: grid; grid-template-columns: repeat(3, 1fr); width: min(100%, 340px); margin-top: 3rem; text-align: center; }
-.discoveryDiagram > span { justify-self: center; padding: 0.4rem 0.8rem; background: var(--paper); border: 1px solid var(--line); border-radius: 4px; font-family: var(--ifm-font-family-monospace); font-size: 0.75rem; }
-.discoveryBus { grid-column: 1 / -1; height: 1.6rem; margin-inline: 16.66%; border: solid #8eb69f; border-width: 0 1px 1px; background: linear-gradient(#8eb69f, #8eb69f) center / 1px 100% no-repeat; }
-.discoveryDiagram > strong { grid-column: 1 / -1; position: relative; margin-top: 1.5rem; padding: 0.7rem; border: 1px solid var(--forest); border-radius: 5px; font-size: 0.85rem; font-weight: 500; }
-.discoveryDiagram > strong::before { content: ''; position: absolute; width: 1px; height: 1.5rem; bottom: 100%; left: 50%; background: #8eb69f; }
-.discoveryOutput { grid-column: 1 / -1; display: flex; align-items: center; justify-content: center; gap: 0.4rem; padding-top: 0.8rem; font-size: 0.7rem; }
-.discoveryOutput svg { width: 1rem; height: 1rem; }
-.featureList article { padding: 1.8rem 0; border-bottom: 1px solid var(--line); }
-.featureList article:first-child { padding-top: 0; }
-.featureList article:last-child { border: 0; padding-bottom: 0; }
-.featureList h3 { font-size: 1.45rem; }
-.featureList p { max-width: 48ch; line-height: 1.7; font-size: 0.95rem; }
-.featureList a { font-size: 0.8rem; text-decoration: underline; }
+/* Write, Build, Run */
+.stage { padding-block: 6rem; border-top: 1px solid var(--tunit-border); }
+.stageTinted { background: var(--tunit-surface); }
+.stageGrid { display: grid; grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.5fr); gap: 4rem; align-items: start; }
+.stageIntro { position: sticky; top: calc(var(--ifm-navbar-height) + 2rem); }
+.page .stageIntro h2 { margin: 0 0 1rem; font-size: clamp(3.4rem, 6vw, 5.2rem); font-weight: 800; line-height: 1; letter-spacing: -0.055em; }
+.stageIntro p { max-width: 34ch; font-size: 1.05rem; line-height: 1.65; }
-.ecosystem { padding-block: 5.5rem; }
-.sectionHeading { display: flex; justify-content: space-between; align-items: end; gap: 2rem; margin-bottom: 2.5rem; }
-.sectionHeading h2 { margin: 0; }
-.sectionHeading p { max-width: 27ch; margin: 0; line-height: 1.6; }
-.integrationList { display: grid; }
-.integration { display: grid; grid-template-columns: 1fr 1.2fr 2fr auto; gap: 2rem; align-items: center; border-top: 1px solid var(--line); padding: 1.7rem 0; text-decoration: none !important; }
-.integration:last-child { border-bottom: 1px solid var(--line); }
-.integrationKind { color: var(--muted); font-size: 0.75rem; }
-.integration h3 { font-size: 1.3rem; margin: 0; }
-.integration p { font-size: 0.83rem; line-height: 1.65; margin: 0; max-width: 44ch; }
-.integrationAction { font-size: 0.75rem; }
-.integrationAction > span { padding-left: 0.5rem; }
-.integration:hover h3, .integration:hover .integrationAction { color: var(--plum); text-decoration: underline; text-underline-offset: 4px; }
-.benchmarkNote { display: flex; align-items: center; justify-content: space-between; gap: 2rem; margin-top: 3rem; padding: 2rem; background: var(--lilac); border-radius: 8px; }
-.page .benchmarkNote h3 { color: #513c6e; font-size: 1.5rem; }
-.page .benchmarkNote p { color: #513c6e; font-size: 0.88rem; margin: 0; max-width: 60ch; }
-.page .benchmarkNote a { border-color: #654b87; color: #513c6e; font-size: 0.8rem; }
-.page .benchmarkNote a:hover { background: #654b87; color: #fff; }
+.editor { min-width: 0; overflow: hidden; background: var(--tunit-code); border: 1px solid var(--tunit-border); border-radius: 14px; }
+.editorTabs { display: flex; align-items: stretch; gap: 0.25rem; padding: 0.5rem 0.75rem 0; border-bottom: 1px solid var(--tunit-border); background: var(--tunit-surface); }
+.editorTabs button { min-height: 2.6rem; padding: 0.4rem 0.9rem; border: 1px solid transparent; border-bottom: 0; border-radius: 8px 8px 0 0; background: transparent; color: var(--tunit-muted); font-size: 0.85rem; font-weight: 500; }
+.editorTabs button:hover { color: var(--tunit-ink); }
+.editorTabs button[aria-pressed='true'] { margin-bottom: -1px; border-color: var(--tunit-border); background: var(--tunit-code); color: var(--tunit-ink); box-shadow: inset 0 2px 0 var(--tunit-accent); font-weight: 650; }
+.page .editorTabs button:focus-visible { outline-offset: -3px; }
+.editorFile { align-self: center; margin-left: auto; color: var(--tunit-muted); font-family: var(--ifm-font-family-monospace); font-size: 0.72rem; }
-.nextSteps { background: var(--mint); padding-block: 4.5rem; }
-.nextStepsGrid { display: grid; grid-template-columns: 1fr 1fr; align-items: center; gap: 7rem; }
-.nextSteps h2 { margin-bottom: 2rem; }
-.migration { padding-left: 2rem; border-left: 1px solid var(--line); }
-.migration h3 { font-size: 1.45rem; }
-.migration p { max-width: 40ch; line-height: 1.7; }
-.migration a { font-size: 0.85rem; }
+.page .code { min-height: 15rem; margin: 0; padding: 1.25rem 1.25rem 1rem 0.75rem; background: transparent; border-radius: 0; color: var(--tunit-ink); font-size: 0.82rem; line-height: 1.8; tab-size: 4; overflow-x: auto; }
+.page .code code { display: block; padding: 0; border: 0; background: transparent; color: inherit; font-size: inherit; }
+.lineNumber { display: inline-block; width: 2.25rem; padding-right: 0.9rem; color: var(--syntax-comment); text-align: right; user-select: none; }
+.code :global(.token.keyword) { color: var(--syntax-keyword); }
+.code :global(.token.string) { color: var(--syntax-string); }
+.code :global(.token:is(.function, .method)) { color: var(--syntax-function); }
+.code :global(.token:is(.class-name, .builtin)) { color: var(--syntax-class); }
+.code :global(.token:is(.number, .boolean)) { color: var(--syntax-number); }
+.code :global(.token.comment) { color: var(--syntax-comment); }
-@media (min-width: 1600px) { .heroGrid { padding-block: 4.5rem; } }
-@media (max-width: 1100px) {
- .container { width: calc(100% - 4rem); }
- .heroGrid { gap: 2rem; grid-template-columns: 0.85fr 1.15fr; }
- .wordmark { font-size: 10vw; }
- .architectureGrid, .nextStepsGrid { gap: 4rem; }
- .integration { grid-template-columns: 0.9fr 1.2fr 1.8fr; gap: 1.5rem; }
- .integrationAction { display: none; }
-}
+.results { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.75rem 2rem; padding: 1rem 1.25rem 1.1rem; border-top: 1px solid var(--tunit-border); }
+.results ul { display: grid; gap: 0.25rem; margin: 0; padding: 0; list-style: none; }
+.results li { display: flex; align-items: center; gap: 0.45rem; color: var(--tunit-ink); font-family: var(--ifm-font-family-monospace); font-size: 0.78rem; }
+.results li svg { flex-shrink: 0; width: 1.1rem; height: 1.1rem; padding: 0.1rem; border-radius: 50%; background: var(--tunit-pass-fill); color: #fff; }
+.page .results p { align-self: end; max-width: 34ch; margin: 0; font-size: 0.88rem; line-height: 1.55; }
-@media (max-width: 800px) {
- .heroGrid { grid-template-columns: 1fr; gap: 2.5rem; padding-top: 2rem; }
- .heroCopy { display: grid; grid-template-columns: 1fr 1fr; column-gap: 2rem; align-items: start; }
- .wordmark { grid-column: 1 / -1; font-size: 8rem; margin-bottom: 1.5rem; }
- .page .heroTitle { grid-row: span 2; font-size: 2.8rem; }
- .heroLead { margin-bottom: 0; font-size: 0.95rem; }
- .actions { gap: 1rem; margin-top: 1rem; }
- .heroFootnote { grid-column: 1 / -1; margin-top: 1.3rem; }
- .workbench { max-width: 620px; width: 100%; justify-self: center; }
- .quickStart { grid-template-columns: 1fr; gap: 2rem; }
- .quickStartIntro h2 br, .quickStartIntro p br { display: none; }
- .architectureGrid { grid-template-columns: 1fr 1fr; gap: 2.5rem; }
- .integration { grid-template-columns: 1fr 1.3fr; gap: 0.5rem 1.5rem; }
- .integrationKind { grid-column: 1; }
- .integration h3 { grid-column: 1; }
- .integration p { grid-column: 2; grid-row: 1 / 3; }
- .benchmarkNote { align-items: start; flex-direction: column; gap: 1.2rem; }
- .nextStepsGrid { gap: 2rem; }
-}
+.buildBody { display: grid; gap: 3rem; min-width: 0; }
+.diagnostic { margin: 0; overflow: hidden; background: var(--tunit-paper); border: 1px solid var(--tunit-border); border-radius: 14px; }
+.diagnostic .code { min-height: 0; }
+.squiggle { text-decoration: underline wavy var(--tunit-fail); text-decoration-thickness: 1.5px; text-underline-offset: 4px; text-decoration-skip-ink: none; }
+.diagnostic figcaption { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.75rem; padding: 0.9rem 1.25rem; border-top: 1px solid var(--tunit-border); background: var(--tunit-danger-surface); color: var(--tunit-danger); font-size: 0.88rem; }
+.diagnosticId { font-family: var(--ifm-font-family-monospace); font-size: 0.78rem; font-weight: 700; }
+.points { display: grid; grid-template-columns: 1fr 1fr; gap: 2.5rem; }
+.points h3 { font-size: 1.25rem; letter-spacing: -0.02em; }
+.points p { line-height: 1.65; }
-@media (max-width: 580px) {
- .container { width: calc(100% - 2.5rem); }
- .hero { padding-top: 1.2rem; }
- .heroIntro { font-size: 0.72rem; }
- .heroIntro > a { display: none; }
- .heroCopy { display: block; }
- .wordmark { font-size: clamp(6.2rem, 24vw, 8rem); }
- .page .heroTitle { font-size: 2.75rem; }
- .heroLead { max-width: 32ch; }
- .actions { margin-top: 1.5rem; gap: 1.5rem; }
- .heroFootnote { font-size: 0.7rem; }
- .heroGrid { padding-bottom: 2rem; }
- .workbenchHeading { padding: 1rem; font-size: 0.83rem; }
- .examplePicker { padding: 0 0.5rem; gap: 0; justify-content: space-between; }
- .examplePicker button { font-size: 0.7rem; padding-inline: 0.35rem; }
- .fileName { margin-inline: 1rem; font-size: 0.65rem; }
- .fileName > span:last-child { font-size: 0.6rem; }
- .page .code { padding-inline: 0.7rem; font-size: 0.72rem; min-height: 14rem; }
- .page .code .lineNumber { width: 1.6rem; }
- .testResults { padding-inline: 1rem; }
- .testResults li { font-size: 0.65rem; }
- .exampleNote { padding-inline: 1rem; font-size: 0.65rem; }
- .compilePath { gap: 0.6rem; font-size: 0.62rem; }
- .compilePath > span:not(.pathLine) { max-width: 25%; }
- .parallelMark { display: none; }
- .quickStart, .architecture, .ecosystem { padding-block: 3.5rem; }
- .installCommands li { gap: 0.65rem; }
- .page .installCommands code { font-size: 0.72rem; }
- .installCommands button { padding-inline: 0.4rem; font-size: 0.65rem; }
- .stepNumber { width: 1.4rem; height: 1.4rem; }
- .architectureGrid, .nextStepsGrid { grid-template-columns: 1fr; gap: 2.5rem; }
- .discoveryDiagram { margin-top: 2rem; }
- .sectionHeading { display: block; }
- .sectionHeading p { margin-top: 1rem; max-width: 32ch; }
- .integration { grid-template-columns: 1fr; padding-block: 1.4rem; }
- .integration p { grid-column: 1; grid-row: auto; }
- .integrationAction { display: block; margin-top: 0.4rem; }
- .benchmarkNote { padding: 1.5rem; }
- .page .benchmarkNote h3 { font-size: 1.35rem; }
- .nextSteps { padding-block: 3.5rem; }
- .migration { border-left: 0; border-top: 1px solid var(--line); padding: 2rem 0 0; }
+.integrations { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; margin: 0; padding: 0; overflow: hidden; list-style: none; background: var(--tunit-border); border: 1px solid var(--tunit-border); border-radius: 14px; }
+.integrations a { display: block; height: 100%; padding: 1.6rem 1.6rem 1.4rem; background: var(--tunit-paper); color: inherit; text-decoration: none; }
+.integrations a:hover { background: var(--tunit-surface); }
+.integrations a:hover h3 { color: var(--tunit-accent); }
+.integrations h3 { margin-bottom: 0.4rem; font-size: 1.3rem; letter-spacing: -0.025em; }
+.page .integrations p { margin: 0; font-size: 0.92rem; font-weight: 400; line-height: 1.55; }
+.page .benchmarks { margin: 1.5rem 0 0; font-size: 1rem; }
+
+/* Start */
+.start { padding-block: 6rem; background: var(--tunit-surface); border-top: 1px solid var(--tunit-border); }
+.startGrid { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); gap: 5rem; align-items: end; }
+.page .start h2 { max-width: 16ch; margin-bottom: 2rem; font-size: clamp(2.2rem, 4vw, 3.2rem); font-weight: 800; line-height: 1.05; letter-spacing: -0.045em; }
+.terminal { display: grid; gap: 0.25rem; margin: 0; padding: 1rem 1.25rem; list-style: none; background: var(--run-bg); border-radius: 12px; }
+.terminal li { display: flex; align-items: center; gap: 0.25rem; min-height: 2.75rem; }
+.page .terminal code { flex: 1; min-width: 0; padding: 0; border: 0; background: transparent; color: var(--run-text); font-size: 0.85rem; overflow-wrap: anywhere; }
+.page .terminal button { min-height: 2.25rem; padding: 0.3rem 0.75rem; border: 1px solid var(--run-bar); border-radius: 6px; background: transparent; color: var(--run-text); font-size: 0.75rem; }
+.page .terminal button:hover { background: var(--run-bar); }
+.page .terminal button:focus-visible { outline-color: var(--run-pass); }
+.page .copyStatus { min-height: 1.5rem; margin: 0.5rem 0 1rem; font-size: 0.8rem; }
+.migration { padding-left: 2rem; border-left: 3px solid var(--tunit-accent); }
+.migration h3 { font-size: 1.4rem; letter-spacing: -0.025em; }
+.migration p { max-width: 40ch; line-height: 1.65; }
+
+@media (max-width: 996px) {
+ .stageGrid, .startGrid { grid-template-columns: 1fr; gap: 2rem; }
+ .stageIntro { position: static; }
+ .stageIntro p { max-width: 52ch; }
+ .stage, .start { padding-block: 4.5rem; }
}
-@media (prefers-reduced-motion: reduce) { .page *, .page *::before, .page *::after { scroll-behavior: auto !important; transition: none !important; animation: none !important; } }
+@media (max-width: 640px) {
+ .container { width: calc(100% - 2rem); }
+ .hero { padding: 2.5rem 0 3rem; }
+ .heroCopy { margin-bottom: 2rem; }
+ .heroLead { font-size: 1rem; }
+ .run { padding: 1rem; font-size: 0.72rem; }
+ .lane { grid-template-columns: 1fr; gap: 0.25rem; }
+ .laneName { font-size: 0.65rem; }
+ .playhead { display: none; }
+ .page .replay { margin-left: 0; }
+ .points, .integrations { grid-template-columns: 1fr; }
+ .page .code { font-size: 0.74rem; padding-right: 0.75rem; }
+ .lineNumber { width: 1.75rem; padding-right: 0.6rem; }
+ .editorTabs { padding-inline: 0.4rem; }
+ .editorTabs button { padding-inline: 0.6rem; font-size: 0.8rem; }
+ .editorFile { display: none; }
+ .migration { padding-left: 1.25rem; }
+}
diff --git a/docs/src/pages/index.tsx b/docs/src/pages/index.tsx
index b7395831d3e..603be67806f 100644
--- a/docs/src/pages/index.tsx
+++ b/docs/src/pages/index.tsx
@@ -1,13 +1,143 @@
import Link from '@docusaurus/Link';
import Layout from '@theme/Layout';
import {Highlight, themes} from 'prism-react-renderer';
-import {useState, type CSSProperties, type JSX} from 'react';
+import {useCallback, useEffect, useRef, useState, type CSSProperties, type JSX} from 'react';
import styles from './index.module.css';
+/* A representative run: start and end are fractions of the run's duration. */
+const runDurationSeconds = 1.42;
+const lanes = [
+ [
+ {name: 'Add(1, 2, 3)', start: 0, end: 0.1},
+ {name: 'Add(5, 8, 13)', start: 0.11, end: 0.21},
+ {name: 'Checkout_applies_discount', start: 0.22, end: 0.55},
+ {name: 'Orders_api_returns_201', start: 0.56, end: 0.93},
+ ],
+ [
+ {name: 'Greeting_contains_name', start: 0, end: 0.18},
+ {name: 'Login_page_shows_errors', start: 0.19, end: 0.71},
+ {name: 'Token_expires', start: 0.72, end: 0.86},
+ ],
+ [
+ {name: 'Parses_iso_dates', start: 0, end: 0.15},
+ {name: 'Cart_total_includes_tax', start: 0.16, end: 0.36},
+ {name: 'Search_ranks_exact_matches', start: 0.37, end: 0.68},
+ {name: 'Invoice_pdf_renders', start: 0.69, end: 1},
+ ],
+ [
+ {name: 'Mock_repository_is_called', start: 0, end: 0.24},
+ {name: 'Queue_retries_twice', start: 0.25, end: 0.58},
+ {name: 'Settings_round_trip', start: 0.59, end: 0.79},
+ ],
+];
+const testCount = lanes.reduce((total, lane) => total + lane.length, 0);
+
+function Tick(): JSX.Element {
+ return ;
+}
+
+function RunPanel(): JSX.Element {
+ // Server render shows the finished run; the browser replays it once on load.
+ const [progress, setProgress] = useState(1);
+ const frame = useRef(0);
+
+ const play = useCallback(() => {
+ cancelAnimationFrame(frame.current);
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
+ setProgress(1);
+ return;
+ }
+ const duration = 2600;
+ const begin = performance.now();
+ const step = (now: number) => {
+ const t = Math.min((now - begin) / duration, 1);
+ setProgress(t);
+ if (t < 1) frame.current = requestAnimationFrame(step);
+ };
+ setProgress(0);
+ frame.current = requestAnimationFrame(step);
+ }, []);
+
+ useEffect(() => {
+ play();
+ return () => cancelAnimationFrame(frame.current);
+ }, [play]);
+
+ const passed = lanes.flat().filter(test => test.end <= progress).length;
+ const done = progress >= 1;
+
+ return (
+
+ TUnit is a testing framework for modern .NET. A source generator finds your tests
+ at compile time, so they start immediately and run in parallel by default.
+
+ Found while you build.
+ Run all at once.
+
+
+ {tokens.map((line, lineIndex) => {
+ const {style: _lineStyle, ...lineProps} = getLineProps({line});
+ return (
+
+ )}
+
-
- {tokens.map((line, lineIndex) => (
-
-
- {line.map((token, tokenIndex) => {
- const props = getTokenProps({token});
- return ;
- })}
-
- ))}
-
-
- )}
- Tests are plain C# methods with an attribute. Data, hooks, and assertions all use the same small set of ideas.
+ Write your first test{example.note}
- Read the guide ++
{example.note} Read the guide
+Give your tests
a head start.
Discover tests while you compile. Run them in parallel. - Get back to the code you came to write.
-The compiler already knows your code. TUnit puts that knowledge to work before a single test runs.
++
A source generator writes the code that registers each test. There is no reflection scan at startup, and test projects can publish with Native AOT.
+ Native AOT support +Analyzers flag an assertion you forgot to await, a hook with the wrong signature, or a data source that does not match its parameters.
+ Assertion diagnosticsSource generated. Async native. AOT ready.
Tests run in parallel unless you say otherwise. Add dependencies, limits, or shared resources when a suite needs more control.
+ Control parallel execution +{item.copy}
+ ++ How fast is fast? See the benchmarks and the method behind them. +
Have the .NET SDK?
You’re three commands away.
{step.command}{copyStatus ? (copyStatus.ok ? `${installSteps[copyStatus.index].label}: command copied.` : 'Couldn’t copy automatically. Select and copy the command above.') : '\u00a0'}
-The compiler already knows your code. TUnit puts that knowledge to work before your tests even start.
- Explore the design - +{step.command}
+
+ + {copyStatus ? (copyStatus.ok ? `${installSteps[copyStatus.index].label}: command copied.` : 'Couldn’t copy automatically. Select the command and copy it.') : ' '} +
+ Read the installation guideSource generators turn your tests into executable code at build time, removing the need for runtime reflection to discover them.
Native AOT supportTests run concurrently by default. Add dependencies, limits, or shared resources when your suite needs more control.
Control parallel executionRoslyn analyzers catch invalid hooks, data-source mistakes, and missing assertion awaits while you’re writing your tests.
See assertion diagnosticsMigration guides map each attribute, assertion, and lifecycle hook you use today to its TUnit equivalent.
+ Compare frameworksThe same testing framework, wherever your code takes you.
{item.copy}
View example )} -See how TUnit compares with xUnit, NUnit, and MSTest, and find a familiar place to begin.
Compare frameworks