refactor(web): split globals.css into per-surface partials

globals.css (3,828 lines) becomes an import hub over app/styles/*.css,
cut at existing section boundaries in the original order: tailwind,
tokens-roles, base, shell, utilities, portal, home, docs, states,
docs-help, changelog, overrides. Pure move; no rule edited.

Tailwind expands each CSS file separately and appends variant utilities
(hover:, md:, ...) at the end of whichever file holds `@tailwind
utilities`, which moved them ahead of every site rule. postcss.config
now runs postcss-import (already a tailwindcss@3 dependency, hoisted in
package-lock) for ./styles/ imports so Tailwind sees one stylesheet
again. tokens.css stays a separate module as before.

The contract tests and whale-tokens.ts read the stylesheet through a new
lib/site-css.ts, which inlines the partials in cascade order.

Checks:
- Built CSS, back-to-back A/B/A builds (original / split / original):
  split output byte-identical to original (same md5 and chunk hash).
- next build: exit 0
- npm run lint: 0 errors, 2 pre-existing warnings
- npx tsc --noEmit: exit 0
- vitest (docs-theme-contract, blue-stage-contract, docs-ia,
  nav-hit-target, public-surface-contract): 5 files, 43/43 passed
- npm run check:tokens: up to date (122 tokens)

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
CodeWhale BotandClaude Opus 5.5 committed 2026-09-22 11:35:28 -07:00
1 parent 1c4edf6839
commit 28754c4c2b
21 files changed
+3870 -3833

No files matched your search

+17 -3824
View File
File diff suppressed because it is too large. Load diff
+326
View File
@@ -0,0 +1,326 @@
/* ---------- base ---------- */
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
max-width: 100%;
overflow-x: clip;
}
body {
/* The sheet: one opaque paper field. The descent into the water is drawn
by the waterline band and the ocean column, never by a page gradient. */
background: var(--paper);
color: var(--ink);
font-family: var(--font-body), "Noto Sans SC", system-ui, sans-serif;
font-feature-settings: "ss01", "cv11", "tnum";
max-width: 100%;
overflow-x: clip;
position: relative;
}
.codewhale-mark-primary { fill: var(--mark-ink); }
main, header, footer, nav { position: relative; z-index: 1; }
/* ---------- type ---------- */
/* The folio voice: Newsreader for the big headings, set at book weight with
a little negative tracking so it reads as a title page rather than a
blog. Small headings use the shared Shannon Sans label face. */
.font-display { font-family: var(--font-serif), "Newsreader", Georgia, "Times New Roman", serif; font-weight: 500; }
.font-serif { font-family: var(--font-serif), "Newsreader", Georgia, "Times New Roman", serif; }
.font-condensed { font-family: var(--font-display), ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; font-weight: 600; }
.font-cjk { font-family: var(--font-cjk), "PingFang SC", "Source Han Serif SC", serif; }
/* CJK paragraph rhythm — looser leading, wider tracking for body; tighter for headings */
.cjk-body {
line-height: 1.9;
letter-spacing: 0.02em;
word-break: break-all;
}
.cjk-heading {
letter-spacing: -0.01em;
}
.cjk-prose p {
line-height: 1.9;
letter-spacing: 0.02em;
}
/* Full-width punctuation should use CJK spacing */
.cjk-prose {
font-feature-settings: "halt", "pwid";
}
.font-mono { font-family: var(--font-mono), "IBM Plex Mono", ui-monospace, monospace; }
/* ---------- the ` · ` chain ---------- */
/* The single most recognisable thing about the TUI — its header, empty state
and footer all speak in dot chains — and the grammar the product's voice is
carried by on this site, on both sides of the waterline. The separator is
punctuation emitted by CSS, never copy: no locale translates it, and no
string is ever concatenated around one. No uppercase and no wide tracking:
the TUI header has neither, and both are what break Han. */
.dotline {
display: flex;
flex-wrap: wrap;
align-items: baseline;
font-family: var(--font-mono), "IBM Plex Mono", ui-monospace, monospace;
font-size: 0.75rem;
letter-spacing: 0.02em;
text-transform: none;
}
/* The separator hangs off the END of the preceding item, not the start of the
next one. When a long chain wraps, a line ending in `·` reads as
continuation; a line beginning with one reads as a bullet list. */
.dotline > *:not(:last-child)::after {
content: "·";
margin-inline: 0.5em;
color: var(--stage-dim);
}
html[lang="zh"] .dotline,
html[lang="ja"] .dotline,
html[lang="ko"] .dotline {
letter-spacing: 0;
}
h1, h2 {
font-family: var(--font-serif), "Newsreader", Georgia, "Times New Roman", serif;
font-weight: 500;
letter-spacing: -0.022em;
color: var(--ink);
text-wrap: balance;
}
h3, h4 {
font-family: var(--font-display), ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
font-weight: 600;
letter-spacing: -0.01em;
color: var(--ink);
}
h1 { font-size: clamp(2.5rem, 5.6vw, 5rem); line-height: 1.02; word-break: keep-all; overflow-wrap: anywhere; }
h2 { font-size: clamp(1.6rem, 2.9vw, 2.6rem); line-height: 1.1; word-break: keep-all; overflow-wrap: anywhere; }
h3, h4 { font-size: 1.12rem; line-height: 1.25; font-weight: 600; }
h1.font-display, h2.font-display { font-weight: 500; }
/* Han has no serif/sans distinction to spend; the CJK stack already leads
with a serif. */
html[lang="zh"] h1, html[lang="ja"] h1, html[lang="ko"] h1,
html[lang="zh"] h2, html[lang="ja"] h2, html[lang="ko"] h2 { letter-spacing: 0; font-weight: 600; }
/* `overflow-wrap: anywhere` keeps long English headings safe, but can strand
CJK punctuation — or a lone numeral — on a line of its own. Strict CJK
line-breaking keeps punctuation attached while still allowing breaks between
Han characters. ja and ko want the same rule: ja for the identical reason as
zh, ko because it has real word boundaries and should break on them. */
html[lang="zh"] h1,
html[lang="zh"] h2,
html[lang="zh"] h3,
html[lang="ja"] h1,
html[lang="ja"] h2,
html[lang="ja"] h3,
html[lang="ko"] h1,
html[lang="ko"] h2,
html[lang="ko"] h3 {
line-break: strict;
word-break: normal;
overflow-wrap: break-word;
}
@media (max-width: 640px) {
h1 .font-cjk {
display: inline-block;
font-size: clamp(1.6rem, 7.5vw, 2.2rem);
overflow-wrap: anywhere;
}
/* Keep headings inside the viewport without inserting awkward English hyphens. */
h1, h2 { hyphens: auto; }
html[lang="en"] h1,
html[lang="en"] h2 { hyphens: none; }
}
/* ---------- structural primitives ---------- */
/* One hairline everywhere: action blue at low alpha, visible on the deep
field without ever becoming a border wall. */
.hairline { border-color: var(--hairline); }
.hairline-t { border-top: 1px solid var(--hairline); }
.hairline-b { border-bottom: 1px solid var(--hairline); }
.hairline-l { border-left: 1px solid var(--hairline); }
.hairline-r { border-right: 1px solid var(--hairline); }
/* The single site container: width + auto margins so every page's gutters
line up with the nav. */
.site-container {
width: var(--container);
margin-inline: auto;
}
/* The single hero band shared by home, docs, and the community portals: a
quiet, flat stage surface against the deep field. */
.hero {
position: relative;
overflow: hidden;
border-bottom: 1px solid var(--hairline);
background: transparent;
color: var(--ink);
padding-block: var(--hero-pad);
}
/* The single section rhythm for per-route hero/section blocks. */
.section {
padding-block: var(--section-pad);
}
.double-rule {
background-image:
linear-gradient(var(--hairline), var(--hairline)),
linear-gradient(var(--hairline), var(--hairline));
background-size: 100% 1px, 100% 1px;
background-position: top, bottom;
background-repeat: no-repeat;
padding: 0.45rem 0;
}
.col-rule > * + * {
border-left: 1px solid var(--hairline);
}
/* Single-column phones: drop the column rules so cards stack flush. */
@media (max-width: 767px) {
.col-rule > * + * { border-left: 0; border-top: 1px solid var(--hairline); }
}
/* small-caps eyebrow — muted mono chrome for compact labels */
.eyebrow {
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.7rem;
font-weight: 500;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--ink-mute);
}
/* ---------- compact section marker ---------- */
/* The seal keeps its Han glyph but speaks as Tideline chrome now: a bordered
stage plate with muted ink, not an ink block on paper. */
.seal {
display: inline-flex;
align-items: center;
justify-content: center;
background: var(--paper-deep);
border: 1px solid var(--stage-line);
color: var(--ink-mute);
font-family: var(--font-cjk), "PingFang SC", serif;
font-weight: 700;
width: 2.6rem;
height: 2.6rem;
border-radius: 6px;
letter-spacing: -0.04em;
}
/* Action-blue variant for the few routes that need a stronger section marker. */
.seal-indigo {
color: var(--indigo);
border-color: color-mix(in srgb, var(--indigo) 55%, transparent);
}
/* Below the waterline the seal stays the same plate — the whole page is water
now, so there is nothing left to invert. */
.ocean-column .seal {
background: var(--stage-elevated);
color: var(--ink-mute);
}
/* ---------- pills / status ---------- */
.pill {
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.12rem 0.45rem;
font-family: var(--font-mono), monospace;
font-size: 0.7rem;
font-weight: 500;
letter-spacing: 0.06em;
text-transform: uppercase;
border: 1px solid var(--paper-line);
background: var(--paper-deep);
color: var(--ink);
}
.pill-hot { background: var(--indigo); color: var(--paper); border-color: var(--indigo); }
.pill-new { background: var(--paper-deep); color: var(--ink); border-color: var(--ink-mute); }
.pill-jade { background: var(--jade); color: var(--paper); border-color: var(--jade); }
.pill-ochre { background: var(--ochre); color: var(--paper); border-color: var(--ochre); }
.pill-ghost { background: transparent; color: var(--ink-mute); border-color: var(--ink-mute); }
/* ---------- numbers ---------- */
.tabular { font-variant-numeric: tabular-nums; }
.bignum {
font-family: var(--font-body), system-ui, sans-serif;
font-weight: 600;
font-size: 2.2rem;
line-height: 1;
letter-spacing: -0.04em;
font-variant-numeric: tabular-nums;
}
/* ---------- code blocks ---------- */
pre.code-block {
background: var(--code-bg);
color: var(--code-fg);
max-width: 100%;
min-width: 0;
padding: 1rem 1.1rem;
font-family: var(--font-mono), monospace;
font-size: 0.82rem;
line-height: 1.55;
border: 1px solid var(--stage-line);
border-radius: 6px;
overflow-x: auto;
position: relative;
white-space: pre;
-webkit-overflow-scrolling: touch;
}
pre.code-block::before {
content: "";
position: absolute;
top: 0; left: 0; right: 0;
height: 1px;
background: var(--hairline);
}
pre.code-block .prompt { color: var(--action-on-dark); }
pre.code-block .comment { color: var(--gpui-stage-ink-dim); }
pre.code-block .key { color: var(--signal-gold); }
@media (max-width: 640px) {
pre.code-block { font-size: 0.76rem; padding: 0.85rem 0.95rem; }
}
code.inline {
background: var(--paper-deep);
border: 1px solid var(--hairline);
padding: 0.05rem 0.32rem;
font-family: var(--font-mono), monospace;
font-size: 0.85em;
border-radius: 2px;
}
/* ---------- search ---------- */
.search-input {
font-family: var(--font-body), system-ui, sans-serif;
font-size: 1rem;
padding: 0.75rem 2.5rem 0.75rem 1rem;
background: var(--paper);
color: var(--ink);
border: 1px solid var(--hairline);
transition: border-color 180ms ease, box-shadow 180ms ease;
}
.search-input::placeholder { color: var(--ink-mute); }
.search-input:focus {
outline: none;
border-color: var(--indigo);
box-shadow: 0 0 0 3px var(--indigo-pale);
}
mark.search-highlight {
background: var(--indigo-pale);
color: var(--indigo-deep);
padding: 0.02em 0.08em;
border-radius: 2px;
}
+142
View File
@@ -0,0 +1,142 @@
/* ------------------------------------------------------------------ */
/* Changelog */
/* ------------------------------------------------------------------ */
.changelog-facts {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1rem;
margin-top: 1.75rem;
}
.changelog-facts > div {
padding: 1.1rem 1.25rem;
border: 1px solid var(--stage-line);
border-radius: 6px;
background: var(--paper-card);
}
.changelog-facts span {
display: block;
color: var(--ink-mute);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.7rem;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.changelog-facts > div { min-width: 0; }
.changelog-facts strong {
display: block;
overflow-wrap: anywhere;
margin-top: 0.45rem;
color: var(--ink);
font-size: 1.05rem;
font-weight: 600;
font-variant-numeric: tabular-nums;
}
.changelog-facts a {
display: inline-block;
margin-top: 0.55rem;
color: var(--indigo);
font-size: 0.76rem;
}
.changelog-facts a:hover { text-decoration: underline; text-underline-offset: 0.2rem; }
.changelog-facts [data-published="true"] strong { color: var(--jade); }
.changelog-facts [data-candidate="unreleased"] strong { color: var(--ochre); }
.changelog-release {
padding-block: 2.25rem;
border-top: 1px solid var(--hairline);
}
.changelog-release-head {
min-width: 0;
overflow-wrap: anywhere;
display: flex;
flex-wrap: wrap;
align-items: baseline;
justify-content: space-between;
gap: 0.75rem 1.5rem;
}
.changelog-release-head h2 {
font-size: clamp(1.4rem, 2.6vw, 1.9rem);
font-variant-numeric: tabular-nums;
}
.changelog-release-head .dotline {
color: var(--ink-mute);
}
.changelog-release-head .dotline a { color: var(--indigo); }
.changelog-release-head .dotline a:hover { text-decoration: underline; text-underline-offset: 0.2rem; }
.changelog-unreleased-note {
max-width: 44rem;
margin-top: 0.6rem;
color: var(--ink-soft);
font-size: 0.86rem;
line-height: 1.6;
}
.changelog-sections {
display: grid;
gap: 1.35rem;
margin-top: 1.4rem;
}
.changelog-sections h3 {
display: flex;
align-items: baseline;
gap: 0.6rem;
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--ink-mute);
}
.changelog-sections h3 a {
color: var(--ink-mute);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.75rem;
letter-spacing: 0.04em;
text-transform: none;
}
.changelog-sections h3 a:hover {
color: var(--indigo);
text-decoration: underline;
text-underline-offset: 0.2rem;
}
.changelog-sections ul {
margin-top: 0.55rem;
border-top: 1px solid var(--hairline);
}
.changelog-sections li {
min-width: 0;
padding: 0.6rem 0;
border-bottom: 1px solid var(--hairline);
color: var(--ink-soft);
font-size: 0.86rem;
line-height: 1.6;
/* Release notes quote commands, env names, and URLs verbatim; a long
unbroken token must wrap inside the column, never widen the page. */
overflow-wrap: anywhere;
}
.changelog-sections li::before {
content: "·";
margin-right: 0.6rem;
color: var(--stage-dim);
}
@media (max-width: 640px) {
.changelog-facts { grid-template-columns: 1fr; }
}
+149
View File
@@ -0,0 +1,149 @@
/* ------------------------------------------------------------------ */
/* Docs: release truth, task index, contextual help */
/* ------------------------------------------------------------------ */
.release-truth {
margin-top: 1.4rem;
color: var(--ink-mute);
}
.release-truth-label {
color: var(--ink-mute);
letter-spacing: 0.12em;
text-transform: uppercase;
font-size: 0.7rem;
}
.release-truth a {
color: var(--indigo);
}
.release-truth a:hover {
color: var(--indigo-deep);
text-decoration: underline;
text-underline-offset: 0.2rem;
}
.release-truth[data-release-state="published"] > span:not(.release-truth-label) {
color: var(--jade);
}
.docs-result-lead {
margin: -0.2rem 0 0.9rem;
color: var(--ink-soft);
font-size: 0.82rem;
}
.docs-result-heading h3 {
font-size: 1.05rem;
letter-spacing: -0.01em;
}
.docs-result-heading-topics {
margin-bottom: 0;
padding-bottom: 0.75rem;
}
.docs-result-topics {
display: grid;
gap: 2rem;
}
.docs-task-group .docs-topic-list {
border-top: 1px solid var(--stage-line);
}
.docs-task-row .docs-topic-source {
font-size: 0.75rem;
}
.docs-help {
display: grid;
grid-template-columns: minmax(0, 1.3fr) minmax(12rem, 0.7fr);
gap: clamp(1.5rem, 4vw, 3rem);
margin-top: 3rem;
padding: clamp(1.4rem, 3vw, 2rem);
border: 1px solid var(--stage-line);
border-radius: 6px;
background: var(--paper-card);
}
.docs-help-copy h2 {
font-size: 1.15rem;
letter-spacing: -0.015em;
}
.docs-help-copy p {
margin-top: 0.55rem;
color: var(--ink-soft);
font-size: 0.84rem;
line-height: 1.65;
}
.docs-help-source {
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.75rem !important;
color: var(--ink-mute) !important;
}
.docs-help-source a {
color: var(--indigo);
}
.docs-help-source a:hover { text-decoration: underline; text-underline-offset: 0.2rem; }
.docs-help-links {
display: grid;
gap: 0.35rem;
align-content: start;
border-top: 1px solid var(--stage-line);
padding-top: 0.6rem;
}
.docs-help-links a {
display: inline-flex;
min-height: 2.5rem;
align-items: center;
color: var(--indigo);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.75rem;
}
.docs-help-links a:hover { color: var(--indigo-deep); text-decoration: underline; text-underline-offset: 0.2rem; }
@media (max-width: 760px) {
.docs-help { grid-template-columns: 1fr; }
}
/* ---------- reference tables on docs pages ---------- */
.docs-ref-rows {
display: grid;
border-top: 1px solid var(--hairline);
}
.docs-ref-rows > div {
display: grid;
grid-template-columns: minmax(10rem, 0.42fr) minmax(0, 1fr);
gap: 1.25rem;
padding: 0.9rem 0;
border-bottom: 1px solid var(--hairline);
}
.docs-ref-rows dt {
color: var(--ink);
font-size: 0.88rem;
font-weight: 600;
}
.docs-ref-rows dd {
min-width: 0;
overflow-wrap: anywhere;
color: var(--ink-soft);
font-size: 0.86rem;
line-height: 1.6;
}
.docs-ref-rows code.inline { white-space: nowrap; }
@media (max-width: 560px) {
.docs-ref-rows > div { grid-template-columns: 1fr; gap: 0.3rem; }
}
+473
View File
@@ -0,0 +1,473 @@
/* ------------------------------------------------------------------ */
/* Documentation portal */
/* ------------------------------------------------------------------ */
.docs-portal {
background: var(--paper);
}
.community-welcome-inner {
position: relative;
}
.community-welcome-inner h1 {
max-width: 50rem;
font-size: clamp(2rem, 4.2vw, 3.6rem);
line-height: 1.03;
}
.community-welcome-inner h1 {
margin-top: 0.85rem;
}
.community-welcome-inner p {
max-width: 47rem;
margin-top: 1rem;
color: var(--ink-soft);
font-size: 1rem;
line-height: 1.7;
}
.docs-portal .hero {
padding-block: 0.75rem;
}
.docs-portal-band {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 1rem;
}
.docs-portal-band .portal-mark,
.docs-portal-band .release-truth {
margin: 0;
}
.docs-hub-lede {
margin: 0.8rem 0 1.5rem;
color: var(--ink-soft);
}
.docs-shell {
display: grid;
grid-template-columns: minmax(13rem, 0.28fr) minmax(0, 0.72fr);
grid-template-areas: "sidebar content";
gap: clamp(2rem, 5vw, 4.5rem);
align-items: start;
padding-block: clamp(2.25rem, 5vw, 4rem);
}
.docs-sidebar {
grid-area: sidebar;
border-top: 1px solid var(--hairline);
}
.docs-sidebar-heading {
padding-block: 0.9rem;
border-bottom: 1px solid var(--hairline);
color: var(--ink);
font-size: 0.82rem;
font-weight: 600;
}
.docs-sidebar-heading a {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.docs-sidebar-heading a > span:last-child {
color: var(--ink-mute);
font-size: 0.75rem;
}
.docs-sidebar-group {
padding-block: 0.9rem;
border-bottom: 1px solid var(--hairline);
}
.docs-sidebar-category {
margin-bottom: 0.35rem;
color: var(--ink-mute);
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.docs-sidebar-link {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.8rem;
padding-block: 0.32rem;
color: var(--ink-soft);
font-size: 0.8rem;
line-height: 1.4;
transition: color 150ms ease;
}
.docs-sidebar-link:hover,
.docs-sidebar-link-current {
color: var(--docs-accent);
}
.docs-sidebar-link-current {
font-weight: 600;
}
.docs-sidebar-link > span:last-child:not(:first-child) {
color: var(--ink-mute);
font-size: 0.75rem;
}
.docs-content {
grid-area: content;
width: 100%;
max-width: 52rem;
}
.docs-content p {
max-width: 70ch;
}
.docs-breadcrumb {
margin-bottom: 1.15rem;
color: var(--ink-mute);
font-family: var(--font-mono), "IBM Plex Mono", ui-monospace, monospace;
font-size: 0.75rem;
}
.docs-breadcrumb ol {
display: flex;
flex-wrap: wrap;
align-items: baseline;
margin: 0;
padding: 0;
list-style: none;
}
.docs-breadcrumb li:not(:last-child)::after {
content: "/";
margin-inline: 0.5em;
color: var(--ink-mute);
}
.docs-breadcrumb a {
color: var(--indigo);
}
.docs-breadcrumb a:hover {
text-decoration: underline;
}
.docs-breadcrumb [aria-current="page"] {
color: var(--ink);
}
.docs-search-block {
margin-bottom: 2.25rem;
}
.docs-search-label {
display: block;
margin-bottom: 0.55rem;
color: var(--ink-mute);
font-size: 0.75rem;
font-weight: 600;
}
.docs-search-input {
min-height: 3rem;
padding-right: 3rem;
border-color: var(--paper-line-soft);
border-radius: 5px;
background: color-mix(in srgb, var(--paper) 88%, transparent);
}
.docs-search-clear {
position: absolute;
right: 0.9rem;
top: 50%;
translate: 0 -50%;
color: var(--ink-mute);
font-size: 0.82rem;
}
.docs-search-clear:hover {
color: var(--ink);
}
.docs-search-count {
margin-top: 0.55rem;
color: var(--ink-mute);
font-size: 0.75rem;
}
.docs-result-groups {
display: grid;
gap: 2.5rem;
}
.docs-result-heading {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 1rem;
margin-bottom: 0.55rem;
}
.docs-result-heading h2 {
font-size: 1.28rem;
letter-spacing: -0.02em;
}
.docs-result-heading > span {
color: var(--ink-mute);
font-size: 0.75rem;
}
.docs-topic-list {
border-top: 1px solid var(--hairline);
}
.docs-topic-row {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(8rem, 0.34fr) auto;
gap: 1.25rem;
align-items: center;
padding-block: 1rem;
border-bottom: 1px solid var(--hairline);
transition: background-color 150ms ease, color 150ms ease;
}
.docs-topic-row:hover {
color: var(--docs-accent);
background: color-mix(in srgb, var(--paper-deep) 68%, transparent);
}
.docs-topic-title {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
align-items: baseline;
color: var(--ink);
font-size: 0.9rem;
font-weight: 600;
}
.docs-topic-title > span:last-child {
color: var(--ink-mute);
font-size: 0.75rem;
font-weight: 500;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.docs-topic-main p {
margin-top: 0.28rem;
color: var(--ink-soft);
font-size: 0.78rem;
line-height: 1.55;
}
.docs-topic-source {
overflow: hidden;
color: var(--ink-mute);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.75rem;
line-height: 1.5;
text-overflow: ellipsis;
}
.docs-topic-source span {
overflow-wrap: anywhere;
}
.docs-topic-arrow {
color: var(--ink-mute);
font-size: 0.78rem;
}
.docs-empty {
padding-block: 3.5rem;
border-block: 1px solid var(--hairline);
text-align: center;
}
.docs-empty > p:first-child {
color: var(--ink);
font-size: 1rem;
font-weight: 600;
}
.docs-empty > p:nth-child(2) {
margin: 0.5rem auto 1.25rem;
color: var(--ink-mute);
font-size: 0.8rem;
}
.docs-source-note {
margin-top: 2.5rem;
padding-top: 1.25rem;
border-top: 1px solid var(--hairline);
}
.docs-source-note p {
max-width: 70ch;
color: var(--ink-mute);
font-size: 0.75rem;
line-height: 1.6;
}
@media (max-width: 900px) {
.docs-shell {
grid-template-columns: 1fr;
grid-template-areas:
"content"
"sidebar";
}
/* The one docs nav stays reachable below the article: the groups reflow
into columns so 23 topics read as an index, not a scroll of links. */
.docs-sidebar nav {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
column-gap: 2rem;
}
.docs-sidebar-group:last-child { border-bottom: 0; }
}
@media (max-width: 640px) {
.docs-topic-row {
grid-template-columns: minmax(0, 1fr) auto;
gap: 0.6rem 1rem;
}
.docs-topic-source {
grid-column: 1 / -1;
grid-row: 2;
}
.docs-topic-arrow {
grid-column: 2;
grid-row: 1;
}
}
/* ---------- community release record ---------- */
.community-credit-section {
border-bottom: 0;
}
.community-record-links {
display: flex;
flex-wrap: wrap;
gap: 0.65rem 1.25rem;
margin-top: 1.25rem;
}
.community-record-links a {
color: var(--indigo-deep);
font-size: 0.75rem;
font-weight: 600;
}
.community-record-links a:hover {
text-decoration: underline;
text-underline-offset: 0.22rem;
}
.community-credit-groups {
display: grid;
gap: 2rem;
}
.community-credit-groups section {
padding-top: 1rem;
border-top: 1px solid var(--hairline);
}
.community-credit-groups h3 {
margin-bottom: 0.9rem;
color: var(--ink-mute);
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.community-credit-list {
display: flex;
flex-wrap: wrap;
gap: 0.45rem;
}
.community-credit-list a {
padding: 0.3rem 0.45rem;
border: 1px solid var(--hairline);
color: var(--ink-soft);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.75rem;
}
.community-credit-list a:hover {
border-color: var(--indigo);
color: var(--indigo-deep);
}
/* ------------------------------------------------------------------ */
/* Docs theme — paper is the site default */
/* */
/* The docs sheet reads on the same paper as the rest of the folio. */
/* The theme toggle (docs routes only) offers the whale's dark stage */
/* as an opt-in, exactly the way the TUI keeps Blue Stage beside its */
/* light preset. The dark override is set on */
/* `html[data-theme="dark"] .docs-theme` through the shared */
/* below-the-waterline rule beside :root; the attribute is applied */
/* before paint by the inline script in the locale layout, so there */
/* is no theme flash. */
/* ------------------------------------------------------------------ */
.docs-theme {
--docs-accent: var(--gpui-primary);
--docs-button-bg: var(--gpui-paper-raised);
--docs-button-border: var(--gpui-edge);
--docs-button-text: var(--gpui-ink);
background: var(--paper);
color: var(--ink);
}
.docs-theme .portal-button-secondary {
border-color: var(--docs-button-border);
background: var(--docs-button-bg);
color: var(--docs-button-text);
}
.docs-theme .portal-button-secondary:hover {
border-color: var(--docs-accent);
background: var(--docs-button-bg);
color: var(--docs-accent);
}
.docs-theme .portal-button-secondary:focus-visible,
.docs-sidebar-link:focus-visible {
outline: 2px solid var(--docs-accent);
outline-offset: 2px;
}
/* The opt-in dark sheet: the whale's stage tokens, scoped to the docs
subtree so the shared nav keeps the paper. Surface/ink tokens come from
the shared below-the-waterline rule; only the docs-specific inks are
restated here. */
html[data-theme="dark"] .docs-theme {
--docs-accent: var(--gpui-primary-dark);
--docs-button-bg: var(--gpui-stage-muted);
--docs-button-border: var(--gpui-stage-edge);
--docs-button-text: var(--gpui-stage-ink);
--code-bg: var(--gpui-stage-deep);
--code-fg: var(--gpui-stage-ink);
}
+946
View File
@@ -0,0 +1,946 @@
/* ------------------------------------------------------------------ */
/* Product home — the Tidal Folio: a sheet read under the sea. */
/* */
/* The hero is one illustrated plate: paper at the top left, the */
/* whale's water rising from the bottom right, and the real terminal */
/* capture floating at the waterline. The reading sections that */
/* follow are plain paper; the page descends for good at the */
/* waterline band, and the footer is the seabed. */
/* ------------------------------------------------------------------ */
.product-home {
background: var(--paper);
color: var(--ink);
}
.product-container {
width: var(--container);
margin-inline: auto;
}
/* ---------- the hero plate ---------- */
.folio-hero {
position: relative;
isolation: isolate;
overflow: hidden;
padding-block: clamp(3rem, 7vh, 5.5rem) 0;
/* The plate ends on the deep field so the drawing can run to the edge. */
background: var(--paper);
}
/* The strata: geometry, not a picture. Anchored to the bottom edge so the
water always fills the plate's floor whatever the copy height. */
.folio-strata {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
/* Copy on the paper at the left; the terminal and its running head on the
water at the right, so the plate reads as a page with a marginal figure
rather than a screenshot with a caption. */
.folio-hero-grid {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: minmax(0, 1.15fr) minmax(18rem, 0.85fr);
grid-template-rows: auto auto;
column-gap: clamp(2rem, 5vw, 5rem);
row-gap: clamp(1.25rem, 2.5vw, 2rem);
align-items: end;
padding-bottom: clamp(3rem, 7vw, 6rem);
}
.folio-hero-copy {
position: relative;
isolation: isolate;
grid-column: 1;
grid-row: 1 / 3;
align-self: start;
max-width: 40rem;
padding-bottom: clamp(0.5rem, 2vw, 1.5rem);
}
.folio-hero-copy::before { content: ""; position: absolute; inset: -2rem -3rem; z-index: -1; background: var(--paper); filter: blur(24px); pointer-events: none; }
.folio-hero-copy h1 {
max-width: 14ch;
font-size: clamp(2.75rem, 6.4vw, 5.75rem);
line-height: 0.98;
}
.folio-lede {
max-width: 34rem;
margin: 1.6rem 0 0;
color: var(--ink-soft);
font-size: clamp(1.02rem, 1.5vw, 1.2rem);
line-height: 1.6;
}
.folio-actions {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-top: 1.9rem;
}
/* Buttons on paper: brand navy fills the primary; the secondary is an
outlined sheet. Sentence case in the body face — this is a title page,
not a status bar. */
.folio-button {
display: inline-flex;
min-height: 3rem;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.7rem 1.35rem;
border: 1px solid var(--indigo-deep);
border-radius: 5px;
color: var(--indigo-deep);
font-family: var(--font-body), system-ui, sans-serif;
font-size: 0.95rem;
font-weight: 500;
line-height: 1;
transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
}
.folio-button:hover {
background: var(--indigo-pale);
}
.folio-button-primary {
background: var(--indigo-deep);
color: var(--paper);
}
.folio-button-primary:hover {
background: var(--indigo);
border-color: var(--indigo);
color: var(--paper);
}
/* ---------- the terminal at the waterline ---------- */
.folio-shot {
grid-column: 2;
grid-row: 1;
align-self: end;
position: relative;
width: min(100%, 40rem);
margin-top: clamp(4rem, 12vw, 11rem);
margin: 0;
border: 1px solid rgb(var(--c-stage-soft) / 0.2);
border-radius: 8px;
overflow: hidden;
background: var(--gpui-stage-deep);
/* The one shadow on the site: the plate is lifted off the water, so the
shadow is soft and offset downward the way light through water would
cast it. */
box-shadow: 0 30px 60px -24px rgb(var(--c-stage-deep) / 0.75);
}
.folio-shot img {
display: block;
width: 100%;
height: auto;
background: var(--gpui-stage-deep);
}
.folio-shot figcaption {
display: grid;
gap: 0.35rem;
padding: 0.7rem 0.95rem 0.8rem;
border-top: 1px solid rgb(var(--c-stage-soft) / 0.14);
background: var(--gpui-stage);
color: var(--stage-soft);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.75rem;
line-height: 1.5;
}
/* The status line under the capture is the TUI header, typeset as facts:
a `cw` chip and a dot chain. Spacing comes from `.dotline`; the
separators are CSS punctuation. */
.folio-shot .paper-facts {
color: var(--stage-muted);
}
.folio-shot .paper-facts .dotline-chip {
padding: 0.08rem 0.36rem;
border-radius: 3px;
background: var(--gpui-stage-muted);
color: var(--stage-text);
font-weight: 600;
}
.folio-shot .paper-facts > *:not(:last-child)::after {
color: var(--stage-hint);
}
/* The chapter marker on the water: the plate's running head. */
.folio-chapter {
grid-column: 2;
grid-row: 2;
align-self: start;
justify-self: start;
width: min(100%, 22rem);
color: var(--stage-text);
}
.folio-chapter-num {
display: block;
padding-bottom: 0.7rem;
border-bottom: 1px solid rgb(var(--c-stage-soft) / 0.3);
color: var(--action-on-dark);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.75rem;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.folio-chapter-title {
margin: 0.9rem 0 0;
font-family: var(--font-serif), "Newsreader", Georgia, serif;
font-size: clamp(1.35rem, 1.9vw, 1.7rem);
line-height: 1.25;
}
/* ---------- reading sections on paper ---------- */
.folio-section {
padding-block: clamp(3.5rem, 7vw, 6rem);
border-bottom: 1px solid var(--hairline);
}
.folio-section h2 {
max-width: 24ch;
}
.folio-section-lede {
max-width: 40rem;
margin: 1.1rem 0 0;
color: var(--ink-soft);
font-size: 1.05rem;
line-height: 1.65;
}
/* What you gain: three ruled columns, no cards, no icons. */
.folio-gain-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
margin-top: clamp(2rem, 4vw, 3rem);
border-top: 1px solid var(--hairline);
}
.folio-gain-grid > div {
padding: 1.5rem 1.75rem 1.75rem 0;
}
.folio-gain-grid > div + div {
padding-left: 1.75rem;
border-left: 1px solid var(--hairline);
}
.folio-gain-grid h3 {
font-family: var(--font-serif), "Newsreader", Georgia, serif;
font-size: 1.45rem;
font-weight: 500;
letter-spacing: -0.015em;
line-height: 1.2;
}
.folio-gain-grid p {
margin-top: 0.75rem;
color: var(--ink-soft);
font-size: 0.95rem;
line-height: 1.65;
}
/* A two-column chapter: the argument on the left, the facts on the right. */
.folio-chapter-grid {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(16rem, 0.8fr);
gap: clamp(2rem, 6vw, 6rem);
align-items: start;
}
.folio-running-head {
display: block;
margin-bottom: 1.1rem;
color: var(--indigo);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.75rem;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.folio-fact-list {
border-top: 1px solid var(--hairline);
}
.folio-fact-list > div {
display: grid;
grid-template-columns: minmax(8rem, 0.5fr) minmax(0, 1fr);
gap: 1rem;
padding: 0.95rem 0;
border-bottom: 1px solid var(--hairline);
}
.folio-fact-list dt {
color: var(--ink);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.8rem;
font-weight: 500;
}
.folio-fact-list dd {
color: var(--ink-soft);
font-size: 0.92rem;
line-height: 1.55;
}
.folio-link {
display: inline-block;
margin-top: 1.4rem;
color: var(--indigo);
font-size: 0.95rem;
font-weight: 500;
text-decoration: underline;
text-decoration-color: rgb(var(--c-indigo) / 0.35);
text-underline-offset: 0.25em;
transition: text-decoration-color 150ms ease;
}
.folio-link:hover {
text-decoration-color: currentColor;
}
/* ---------- the waterline band ---------- */
.folio-waterline {
position: relative;
height: clamp(11rem, 24vw, 20rem);
overflow: hidden;
background: var(--paper);
}
.folio-waterline > svg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
/* ---------- product: start-here band + shared getting-started steps ---------- */
.product-start {
padding-block: clamp(3.5rem, 7vw, 6rem);
border-bottom: 1px solid var(--hairline);
}
.product-start h2 {
max-width: 24ch;
}
.product-start-lede {
margin-top: 1.1rem;
max-width: 40rem;
color: var(--ink-soft);
font-size: 1.05rem;
line-height: 1.65;
}
.product-start-links {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
margin-top: 2rem;
}
.product-start-links a {
color: var(--indigo);
font-size: 0.95rem;
font-weight: 500;
}
.product-start-links a:hover {
color: var(--indigo-deep);
}
/* Shared step list rendered by <GettingStartedSteps> (homepage band and the
/docs/guide page). Light tokens; the docs dark theme re-themes it through
the same --hairline/--ink-* vars as the rest of the docs subtree. */
.gs-steps {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
margin-top: clamp(2rem, 4vw, 3rem);
padding: 0;
border-top: 1px solid var(--hairline);
list-style: none;
}
.gs-steps li {
padding: 1.35rem 1.5rem 1.5rem 0;
border-bottom: 1px solid var(--hairline);
}
.gs-steps li + li {
padding-left: 1.5rem;
border-left: 1px solid var(--hairline);
}
.docs-content .gs-steps { grid-template-columns: minmax(0, 1fr); }
.docs-content .gs-steps li + li { padding-left: 0; border-left: 0; }
.gs-step-index {
color: var(--indigo);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.75rem;
}
.gs-steps h3 {
margin-top: 1.1rem;
font-size: 1.05rem;
}
.gs-steps p {
margin-top: 0.6rem;
color: var(--ink-soft);
font-size: 0.86rem;
line-height: 1.7;
}
.gs-step-commands {
margin-top: 0.9rem;
font-size: 0.75rem;
}
pre.gs-step-commands {
white-space: pre-wrap;
overflow-wrap: anywhere;
}
.gs-step-link {
display: inline-block;
margin-top: 0.9rem;
color: var(--indigo);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.75rem;
}
.gs-step-link:hover {
color: var(--indigo-deep);
}
/* ---------- a11y: skip link ---------- */
.skip-link {
position: absolute;
left: 1rem;
top: -3.5rem;
z-index: 60;
padding: 0.55rem 0.9rem;
background: var(--ink);
color: var(--paper);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.75rem;
}
.skip-link:focus-visible {
top: 0.5rem;
outline: 2px solid var(--indigo);
outline-offset: 2px;
}
/* ---------- honest status badges ---------- */
.status-badge {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.2rem 0.55rem;
border: 1px solid var(--hairline);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.7rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--ink-soft);
}
.status-badge-dot {
width: 0.45rem;
height: 0.45rem;
border-radius: 999px;
}
.status-badge-experimental .status-badge-dot { background: var(--ochre); }
.status-badge-preview .status-badge-dot { background: var(--indigo); }
.status-badge-pending .status-badge-dot { background: var(--ochre); }
.status-badge-unavailable .status-badge-dot { background: var(--ink-mute); }
/* ---------- session media (real-session surface) ---------- */
.session-media {
border: 1px solid var(--hairline);
}
.session-media video {
display: block;
width: 100%;
height: auto;
background: var(--ocean-deep);
}
.session-media-stage {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.9rem;
padding: clamp(1.5rem, 4vw, 2.5rem);
border-bottom: 1px solid var(--hairline);
background: var(--paper-deep);
}
.session-media-pending-note {
max-width: 44rem;
margin: 0;
color: var(--ink-soft);
font-size: 0.88rem;
line-height: 1.7;
}
.session-media-caption {
display: grid;
gap: 0.5rem;
padding: 1rem 1.25rem;
color: var(--ink-soft);
font-size: 0.84rem;
line-height: 1.6;
}
.session-media-caption strong {
color: var(--ink);
font-size: 0.95rem;
}
.session-media-caption a {
color: var(--indigo);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.75rem;
}
.session-media-caption a:hover {
color: var(--indigo-deep);
}
/* The boundaries band carries no field of its own — the water is the page. */
.product-boundaries {
padding-block: clamp(4rem, 8vw, 7rem);
}
.product-boundaries-grid {
display: grid;
grid-template-columns: minmax(16rem, 0.8fr) minmax(0, 1.2fr);
gap: clamp(3rem, 8vw, 8rem);
}
.product-boundaries h2 {
font-size: clamp(2.5rem, 5vw, 4.7rem);
}
.product-boundaries h2 span {
color: var(--indigo);
}
.product-boundaries-grid > div > p {
max-width: 31rem;
margin-top: 1.25rem;
color: var(--ink-soft);
line-height: 1.75;
}
.product-boundary-list {
display: grid;
gap: 0.75rem;
}
.product-boundary-list > div {
display: grid;
grid-template-columns: minmax(12rem, 0.85fr) minmax(0, 1.15fr);
align-items: baseline;
gap: 1.5rem;
padding: 1.25rem 1.4rem;
border: 1px solid var(--stage-line);
border-radius: 6px;
background: var(--paper-card);
}
.product-boundary-list dt {
font-weight: 700;
}
.product-boundary-list dd {
color: var(--ink-soft);
font-size: 0.88rem;
}
/* ---------- below the waterline ---------- */
/* */
/* The water column. Its tokens are the dark whale palette (see the shared */
/* rule beside :root), so every component inside reads correctly without */
/* knowing it is under water. The field starts at the panel surface just */
/* under the waterline band and sinks to the deep stop the footer continues, */
/* so the seabed arrives without a seam. */
/* -------------------------------------------------------------------------- */
.ocean-column {
position: relative;
isolation: isolate;
background: linear-gradient(180deg, var(--gpui-stage) 0%, var(--gpui-stage-deep) 38rem);
color: var(--ink);
}
/* Bands inside the column carry no field of their own. */
.ocean-column > section {
position: relative;
background: transparent;
}
/* One line weight on the field. */
.ocean-column .hairline,
.ocean-column .hairline-t,
.ocean-column .hairline-b,
.ocean-column .hairline-l {
border-color: var(--stage-line);
}
.ocean-column h2 {
max-width: 24ch;
color: var(--stage-text);
}
/* Where Codewhale runs today: one honest table, no badges. */
.folio-availability {
padding-block: clamp(3.5rem, 7vw, 6rem);
border-bottom: 1px solid var(--stage-line);
}
.folio-availability-list {
margin-top: clamp(2rem, 4vw, 3rem);
border-top: 1px solid var(--stage-line);
}
.folio-availability-list > div {
display: grid;
grid-template-columns: minmax(9rem, 0.35fr) minmax(0, 1fr);
gap: 1.25rem;
padding: 1.15rem 0;
border-bottom: 1px solid var(--stage-line);
}
.folio-availability-list dt {
color: var(--stage-text);
font-family: var(--font-serif), "Newsreader", Georgia, serif;
font-size: 1.3rem;
font-weight: 500;
line-height: 1.2;
}
.folio-availability-list dd {
color: var(--stage-soft);
font-size: 0.95rem;
line-height: 1.6;
}
.folio-availability-list dd strong {
display: block;
margin-bottom: 0.2rem;
color: var(--stage-text);
font-weight: 600;
}
.folio-availability-note {
max-width: 40rem;
margin-top: 1.4rem;
color: var(--stage-muted);
font-size: 0.88rem;
line-height: 1.6;
}
.product-surfaces {
padding-block: clamp(3.5rem, 7vw, 6rem);
border-bottom: 1px solid var(--stage-line);
}
.product-surface-list {
margin-top: 2.5rem;
border-top: 1px solid var(--stage-line);
}
.product-surface-list > div {
display: grid;
grid-template-columns: minmax(13rem, 0.7fr) minmax(0, 1.3fr);
gap: 2rem;
padding: 1.2rem 0;
border-bottom: 1px solid var(--stage-line);
}
.product-surface-list strong {
color: var(--stage-text);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 1rem;
}
.product-surface-list span {
color: var(--stage-soft);
}
.product-surfaces > .product-container > a {
display: inline-block;
margin-top: 1.5rem;
color: var(--action-on-dark);
font-size: 0.95rem;
}
/* The install band is the TUI composer: a raised plate on the column,
bracketed above by Signal Gold and below by Operate violet — the
product's exact composer framing, and the one place gold is legitimate
here, because what it brackets is the point of human intent. The
descendant selector out-ranks `.ocean-column > section { transparent }`. */
.ocean-column .product-install-band {
padding-block: clamp(3rem, 6vw, 5rem);
border-top: 1px solid color-mix(in srgb, var(--signal-gold) 40%, transparent);
border-bottom: 1px solid color-mix(in srgb, var(--violet) 40%, transparent);
background: var(--stage-composer);
color: var(--stage-text);
}
.product-install-grid {
display: grid;
grid-template-columns: minmax(15rem, 0.65fr) minmax(0, 1.35fr);
gap: clamp(2rem, 7vw, 7rem);
align-items: start;
}
/* `❯` sits inside the plate, where the TUI puts it — a code-owned literal in
the same class as `Codewhale` and `npm install -g codewhale`. */
.product-composer {
position: relative;
}
.product-composer pre.code-block {
padding-left: 2.6rem;
}
.product-composer button {
color: var(--ink);
}
.product-composer-prompt {
position: absolute;
top: 1rem;
left: 1.1rem;
z-index: 1;
color: var(--cyan);
font-family: var(--font-mono), "IBM Plex Mono", ui-monospace, monospace;
font-size: 0.78rem;
line-height: 1.55;
pointer-events: none;
}
@media (max-width: 640px) {
.product-composer pre.code-block {
padding-left: 2.3rem;
}
.product-composer-prompt {
top: 0.85rem;
left: 0.95rem;
font-size: 0.76rem;
}
}
.product-install-band pre.code-block {
margin: 0;
border-color: var(--stage-line);
}
.product-install-band .dotline {
margin-top: 1rem;
color: var(--stage-hint);
}
.product-install-band a {
display: inline-block;
margin-top: 1.2rem;
padding-bottom: 0.2rem;
border-bottom: 1px solid var(--stage-line);
color: var(--action-on-dark);
font-size: 0.95rem;
transition: border-color 150ms ease;
}
.product-install-band a:hover {
border-color: var(--action-on-dark);
}
.product-community {
padding-block: clamp(3.5rem, 7vw, 6rem);
}
.product-community-grid {
display: grid;
grid-template-columns: minmax(0, 1.1fr) minmax(16rem, 0.75fr);
gap: clamp(2rem, 5vw, 5rem);
align-items: end;
}
.product-community p {
max-width: 42rem;
margin-top: 1rem;
color: var(--stage-soft);
font-size: 1rem;
line-height: 1.75;
}
.product-community nav {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1rem;
padding-top: 1.25rem;
border-top: 1px solid var(--stage-line);
}
.product-community nav a {
color: var(--ocean-current);
font-size: 0.95rem;
font-weight: 500;
}
.product-community nav a:hover {
color: var(--stage-text);
}
.product-home a:focus-visible {
outline: 3px solid var(--signal-gold);
outline-offset: 4px;
}
@media (max-width: 1050px) {
.folio-hero-grid {
grid-template-columns: 1fr;
grid-template-rows: auto auto auto;
}
.folio-hero-copy {
max-width: 44rem;
}
.folio-hero-copy {
grid-row: 1;
}
.folio-shot {
grid-column: 1;
grid-row: 2;
width: min(100%, 44rem);
margin-top: clamp(1.5rem, 6vw, 3rem);
}
.folio-chapter {
grid-column: 1;
grid-row: 3;
justify-self: start;
}
}
@media (max-width: 760px) {
.folio-gain-grid {
grid-template-columns: 1fr;
}
.folio-gain-grid > div,
.folio-gain-grid > div + div {
padding: 1.35rem 0;
border-left: 0;
border-bottom: 1px solid var(--hairline);
}
.folio-chapter-grid,
.product-install-grid,
.product-community-grid {
grid-template-columns: 1fr;
}
.gs-steps {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.gs-steps li:nth-child(3) {
padding-left: 0;
border-left: 0;
}
.folio-availability-list > div,
.product-surface-list > div {
grid-template-columns: 1fr;
gap: 0.45rem;
}
}
@media (max-width: 520px) {
.site-nav-inner {
gap: 0.5rem;
}
.site-nav-actions {
min-width: 0;
gap: 0.35rem;
}
.site-nav-actions select {
width: 6.75rem;
min-width: 0;
}
.paper-wordmark {
max-width: 9.75rem;
}
.folio-hero-copy h1 {
max-width: 100%;
font-size: clamp(2.3rem, 11vw, 3.2rem);
word-break: normal;
overflow-wrap: break-word;
}
.paper-install-cta {
display: none;
}
.folio-actions {
display: grid;
grid-template-columns: 1fr;
}
.folio-button {
width: 100%;
}
.gs-steps {
grid-template-columns: 1fr;
}
.gs-steps li,
.gs-steps li + li,
.gs-steps li:nth-child(3) {
padding: 1.25rem 0;
border-left: 0;
}
.product-community nav {
grid-template-columns: 1fr;
}
}
+207
View File
@@ -0,0 +1,207 @@
/* ---------- /product: the availability table on paper ---------- */
/* The same list the homepage sets in the water, re-inked for the sheet. */
.product-availability-paper,
.product-availability-paper > div {
border-color: var(--hairline);
}
.product-availability-paper dt,
.product-availability-paper dd strong {
color: var(--ink);
}
.product-availability-paper dd {
color: var(--ink-soft);
}
/* ---------- usage counting: the consent sheet and its footer control ---------- */
/* A quiet sheet at the foot of the viewport, on paper, with the site's own
inks. It is not a modal: the page stays usable, and the choice is one
click either way. */
.usage-counting {
display: grid;
gap: 0.75rem;
margin-top: 0.5rem;
}
.usage-counting-status {
color: var(--ink);
font-weight: 500;
}
.usage-counting-elsewhere {
color: var(--ink-mute);
font-size: 0.9rem;
}
.usage-counting-button {
display: inline-flex;
justify-self: start;
min-height: 2.75rem;
align-items: center;
padding: 0.5rem 1.1rem;
border: 1px solid var(--indigo);
border-radius: 5px;
color: var(--indigo);
font-size: 0.95rem;
font-weight: 500;
cursor: pointer;
transition: background-color 150ms ease, color 150ms ease;
}
.usage-counting-button:hover {
background: rgb(var(--c-indigo) / 0.1);
}
.usage-counting-button:focus-visible {
outline: 2px solid var(--indigo);
outline-offset: 2px;
}
/* ---------- docs: the reading sheet ---------- */
/* Read mode: comprehension first. The article gets a book measure, the serif
title voice on its headings, generous leading, and a contents rail that
stays put while the sheet scrolls. */
.docs-content {
max-width: 46rem;
}
.docs-content h1 {
font-size: clamp(2.2rem, 3.6vw, 3rem);
line-height: 1.05;
margin-bottom: 0.35rem;
}
.docs-content h2 {
font-family: var(--font-serif), "Newsreader", Georgia, serif;
font-weight: 500;
font-size: clamp(1.5rem, 2.2vw, 1.9rem);
line-height: 1.15;
letter-spacing: -0.015em;
margin-bottom: 0.25rem;
}
.docs-content h3 {
font-size: 1.05rem;
}
.docs-content p,
.docs-content li {
max-width: 66ch;
font-size: 1rem;
line-height: 1.7;
}
.docs-content > section + section {
padding-top: 0.5rem;
}
.docs-content code.inline {
background: var(--paper-card);
border-color: var(--hairline);
}
.docs-content pre.code-block {
margin-block: 1rem;
}
/* The contents rail: a running index in the margin, sticky on tall screens. */
@media (min-width: 901px) {
.docs-sidebar {
position: sticky;
top: 5.25rem;
max-height: calc(100vh - 6rem);
overflow-y: auto;
padding-right: 0.5rem;
}
}
.docs-sidebar-heading {
font-family: var(--font-serif), "Newsreader", Georgia, serif;
font-size: 1.05rem;
font-weight: 500;
}
.docs-sidebar-category {
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.68rem;
letter-spacing: 0.14em;
}
.docs-sidebar-link {
font-size: 0.86rem;
padding-block: 0.36rem;
}
.docs-sidebar-link-current {
color: var(--ink);
font-weight: 600;
}
.docs-sidebar-link-current::before {
content: "";
display: inline-block;
width: 0.4rem;
height: 1px;
margin-right: 0.45rem;
vertical-align: middle;
background: var(--indigo);
}
/* Topic rows on the hub: a title with room to breathe, the source path quiet. */
.docs-topic-title {
font-size: 0.98rem;
}
.docs-topic-main p {
font-size: 0.86rem;
line-height: 1.6;
}
.docs-result-heading h2 {
font-family: var(--font-serif), "Newsreader", Georgia, serif;
font-weight: 500;
font-size: 1.6rem;
}
/* The docs hero band: the mark and the release line as one running head. */
.docs-portal .hero {
padding-block: 1rem;
background: var(--paper-deep);
}
.docs-portal .portal-mark {
color: var(--ink);
font-family: var(--font-serif), "Newsreader", Georgia, serif;
font-size: 1.05rem;
font-weight: 500;
}
.release-truth {
margin-top: 0;
}
/* Contextual help plate at the foot of every page, on the sheet's own card. */
.docs-help {
background: var(--paper-card);
border-color: var(--hairline);
}
.docs-help-copy h2 {
font-family: var(--font-serif), "Newsreader", Georgia, serif;
font-weight: 500;
font-size: 1.4rem;
}
/* Keep the terminal centered and compact beneath the introductory copy. */
.folio-hero-grid { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto auto auto; row-gap: 2rem; }
.folio-hero-copy { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); column-gap: clamp(2rem, 5vw, 5rem); max-width: none; grid-column: 1; grid-row: 1; padding-bottom: 0; }
.folio-hero-copy h1 { grid-column: 1; grid-row: 1 / 3; max-width: none; font-size: clamp(2.75rem, 5vw, 4.5rem); line-height: 1.06; text-wrap: balance; }
.folio-hero-copy .folio-lede { grid-column: 2; grid-row: 1; margin-top: 0; align-self: end; }
.folio-hero-copy .folio-actions { grid-column: 2; grid-row: 2; align-self: start; margin-top: 1.25rem; }
.folio-shot { grid-column: 1; grid-row: 2; width: min(100%, 56rem); justify-self: center; margin-top: 0; }
.folio-chapter { grid-column: 1; grid-row: 3; justify-self: start; }
.product-install-grid > div, .product-composer, .product-composer > div { min-width: 0; }
@media (max-width: 760px) {
.folio-hero-copy { display: block; }
.folio-hero-copy .folio-lede { margin-top: 1.25rem; }
.folio-shot { width: 100%; }
}
+630
View File
@@ -0,0 +1,630 @@
/* ------------------------------------------------------------------ */
/* Documentation-led public home */
/* ------------------------------------------------------------------ */
.portal-home {
background: var(--paper);
color: var(--ink);
}
.portal-container {
width: var(--container);
margin-inline: auto;
}
.portal-current {
position: absolute;
inset: 0;
opacity: 0.22;
pointer-events: none;
background-image: repeating-radial-gradient(
ellipse 82% 44% at 24% -22%,
transparent 0,
transparent 2.7rem,
rgb(var(--c-stage-soft) / 0.18) 2.76rem,
transparent 2.83rem
);
mask-image: linear-gradient(to bottom, transparent, black 24%, black 72%, transparent);
}
.portal-hero-grid {
position: relative;
display: grid;
grid-template-columns: minmax(0, 1.12fr) minmax(20rem, 0.88fr);
gap: clamp(2.5rem, 6vw, 5rem);
align-items: center;
padding-block: clamp(3.25rem, 6vw, 4.75rem);
}
.portal-hero-copy {
max-width: 42rem;
}
.portal-mark {
display: flex;
align-items: center;
gap: 0.7rem;
margin-bottom: 1.25rem;
color: var(--ink-mute);
font-size: 0.76rem;
font-weight: 600;
}
.portal-hero h1 {
max-width: 41rem;
font-size: clamp(2.3rem, 4.8vw, 3.75rem);
line-height: 1.01;
}
.portal-lede {
max-width: 40rem;
margin-top: 1.35rem;
color: var(--ink-soft);
font-size: clamp(1rem, 1.7vw, 1.18rem);
line-height: 1.7;
}
.portal-actions {
display: flex;
flex-wrap: wrap;
gap: 0.7rem;
margin-top: 1.7rem;
}
.portal-button {
display: inline-flex;
min-height: 2.75rem;
align-items: center;
justify-content: center;
padding: 0.65rem 1rem;
border: 1px solid transparent;
border-radius: 5px;
font-size: 0.82rem;
font-weight: 600;
transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
}
.portal-button-primary {
border-color: var(--indigo);
background: var(--indigo);
color: var(--paper);
}
.portal-button-primary:hover {
background: var(--indigo-deep);
border-color: var(--indigo-deep);
}
.portal-button-secondary {
border-color: var(--paper-edge);
background: var(--paper-deep);
color: var(--ink);
}
.portal-button-secondary:hover {
border-color: var(--indigo);
color: var(--indigo);
}
.portal-meta {
max-width: 38rem;
margin-top: 0.9rem;
color: var(--ink-mute);
font-size: 0.76rem;
line-height: 1.55;
}
.portal-quickstart {
padding: clamp(1.35rem, 3vw, 2rem);
border: 1px solid rgb(var(--c-stage-soft) / 0.2);
border-radius: 8px;
background: var(--ocean-deep);
box-shadow: 0 1.25rem 3rem rgba(0, 0, 0, 0.45);
color: var(--stage-soft);
}
.portal-quickstart > span {
color: var(--stage-muted);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.7rem;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.portal-quickstart h2 {
margin-top: 0.65rem;
color: var(--stage-text);
font-size: clamp(1.2rem, 2vw, 1.55rem);
letter-spacing: -0.02em;
}
.portal-quickstart p {
margin-top: 0.7rem;
color: rgb(var(--c-stage-soft) / 0.7);
font-size: 0.84rem;
line-height: 1.65;
}
.portal-quickstart pre.code-block {
margin-top: 1.15rem;
border-color: rgb(var(--c-stage-soft) / 0.2);
background: rgb(var(--c-stage-deep) / 0.6);
}
.portal-quickstart > a {
display: inline-block;
margin-top: 1rem;
color: var(--ocean-current);
font-size: 0.76rem;
}
.portal-quickstart > a:hover {
text-decoration: underline;
text-underline-offset: 0.25rem;
}
.portal-section {
padding-block: clamp(3.25rem, 6vw, 4.75rem);
border-bottom: 1px solid var(--hairline);
}
.portal-section-muted {
background: var(--paper-deep);
}
.legal-doc {
width: var(--container);
margin-inline: auto;
padding-block: clamp(3.25rem, 6vw, 4.75rem);
max-width: 42rem;
}
.legal-doc-kicker {
margin: 0 0 0.75rem;
font-size: 0.7rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--ink-muted, color-mix(in srgb, var(--ink) 62%, var(--paper)));
}
.legal-doc h1 {
margin: 0 0 0.5rem;
font-family: var(--font-display), serif;
font-size: clamp(1.85rem, 3.6vw, 2.7rem);
line-height: 1.15;
}
.legal-doc-updated,
.legal-doc p,
.legal-doc section p {
margin: 0 0 1.15rem;
line-height: 1.6;
}
.legal-doc section h2 {
margin: 1.75rem 0 0.5rem;
font-size: 1.15rem;
}
.legal-doc-nav {
display: flex;
flex-wrap: wrap;
gap: 1rem 1.5rem;
margin-top: 2rem;
}
.legal-doc-nav a {
text-decoration: underline;
text-underline-offset: 0.2rem;
}
.public-account-entry {
max-width: 32rem;
}
.public-account-mark {
display: block;
width: 64px;
height: 64px;
margin: 0 0 1.15rem;
border-radius: 14px;
}
.portal-section-grid {
display: grid;
grid-template-columns: minmax(14rem, 0.62fr) minmax(0, 1.38fr);
gap: clamp(2.5rem, 7vw, 6.5rem);
}
.portal-section-copy > span,
.portal-docs-heading span,
.portal-community-grid > div:first-child > span {
color: var(--ink-mute);
font-size: 0.75rem;
font-weight: 600;
}
.portal-section-copy h2,
.portal-docs-heading h2,
.portal-community h2 {
margin-top: 0.65rem;
font-size: clamp(1.55rem, 3vw, 2.4rem);
}
.portal-section-copy p {
margin-top: 0.9rem;
color: var(--ink-soft);
font-size: 0.92rem;
line-height: 1.7;
}
.portal-topic-list {
border-top: 1px solid var(--hairline);
}
.portal-topic-list > a,
.portal-topic-list > div {
display: grid;
grid-template-columns: minmax(8rem, 0.42fr) minmax(0, 1fr) auto;
gap: 1.25rem;
align-items: start;
padding-block: 1.15rem;
border-bottom: 1px solid var(--hairline);
transition: background-color 150ms ease, color 150ms ease;
}
.portal-topic-list > a:hover {
color: var(--indigo);
background: color-mix(in srgb, var(--indigo) 7%, transparent);
}
.portal-topic-list strong {
font-size: 0.9rem;
font-weight: 600;
}
.portal-topic-list span {
color: var(--ink-soft);
font-size: 0.84rem;
line-height: 1.55;
}
.portal-topic-list span:last-child {
color: var(--ink-mute);
}
.portal-docs-heading {
display: flex;
align-items: end;
justify-content: space-between;
gap: 2rem;
margin-bottom: 2rem;
}
.portal-docs-heading a {
flex: none;
color: var(--indigo-deep);
font-size: 0.78rem;
}
.portal-docs-heading a:hover,
.portal-community-links a:hover {
text-decoration: underline;
text-underline-offset: 0.25rem;
}
/* Sortable model table — quiet column-header buttons; the active column
carries a direction mark instead of a color change. */
.models-sort {
display: inline-flex;
align-items: baseline;
gap: 0.35rem;
padding: 0;
border: 0;
background: none;
color: var(--ink-mute);
font: inherit;
font-weight: 500;
cursor: pointer;
white-space: nowrap;
}
.models-sort:hover {
color: var(--ink);
}
.models-sort.is-active {
color: var(--indigo-deep);
}
.models-sort-mark {
font-size: 0.72em;
color: var(--ink-mute);
}
.models-sort.is-active .models-sort-mark {
color: var(--indigo-deep);
}
.models-sort:focus-visible {
outline: 2px solid var(--indigo);
outline-offset: 3px;
border-radius: 2px;
}
.models-reasoning {
display: inline-block;
margin-left: 0.5rem;
padding: 0.05rem 0.4rem;
border: 1px solid var(--hairline);
border-radius: 999px;
color: var(--ink-mute);
font-family: var(--font-mono);
font-size: 0.62rem;
letter-spacing: 0.04em;
vertical-align: middle;
white-space: nowrap;
}
.portal-doc-groups {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: clamp(2.5rem, 6vw, 5rem);
}
.portal-doc-groups h3 {
font-size: 1.05rem;
letter-spacing: -0.01em;
}
.portal-doc-groups section > p {
min-height: 4.5rem;
margin-top: 0.55rem;
color: var(--ink-soft);
font-size: 0.84rem;
line-height: 1.6;
}
.portal-doc-groups .portal-topic-list {
margin-top: 1.25rem;
}
.portal-doc-groups .portal-topic-list > a {
grid-template-columns: minmax(7rem, 0.42fr) minmax(0, 1fr) auto;
gap: 0.9rem;
}
.portal-community {
padding-block: clamp(3.25rem, 6vw, 4.75rem);
background:
radial-gradient(circle at 84% -30%, rgb(var(--c-primary-dark) / 0.1), transparent 30rem),
var(--ocean-deep);
color: var(--stage-soft);
}
.portal-community-grid {
display: grid;
grid-template-columns: minmax(16rem, 0.85fr) minmax(0, 1.15fr);
gap: clamp(2.5rem, 7vw, 7rem);
}
.portal-community-grid > div:first-child > span {
color: var(--stage-muted);
}
.portal-community h2 {
color: var(--stage-text);
}
.portal-community p {
color: rgb(var(--c-stage-soft) / 0.76);
font-size: 0.96rem;
line-height: 1.75;
}
.portal-community-links {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.8rem 1.5rem;
margin-top: 1.5rem;
padding-top: 1.25rem;
border-top: 1px solid rgb(var(--c-stage-soft) / 0.18);
}
.portal-community-links a {
color: var(--ocean-current);
font-size: 0.78rem;
}
.contribute-path-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
border-top: 1px solid var(--hairline);
}
.contribute-path-grid article {
padding: 1.5rem 1.5rem 1.5rem 0;
border-bottom: 1px solid var(--hairline);
}
.contribute-path-grid article:nth-child(even) {
padding-inline: 1.5rem 0;
border-left: 1px solid var(--hairline);
}
.contribute-path-grid h3 {
font-size: 1rem;
letter-spacing: -0.01em;
}
.contribute-path-grid p {
margin-top: 0.55rem;
color: var(--ink-soft);
font-size: 0.84rem;
line-height: 1.65;
}
.contribute-path-grid a,
.contribute-steps a {
display: inline-block;
margin-top: 0.9rem;
color: var(--indigo-deep);
font-size: 0.75rem;
font-weight: 600;
}
.contribute-path-grid a:hover,
.contribute-steps a:hover {
text-decoration: underline;
text-underline-offset: 0.22rem;
}
.contribute-steps {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
border-top: 1px solid var(--hairline);
}
.contribute-steps li {
display: grid;
grid-template-columns: 2.25rem minmax(0, 1fr);
gap: 1rem;
padding: 1.5rem 1.5rem 1.5rem 0;
border-bottom: 1px solid var(--hairline);
}
.contribute-steps li:nth-child(even) {
padding-inline: 1.5rem 0;
border-left: 1px solid var(--hairline);
}
.contribute-steps li > span {
color: var(--indigo-deep);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.75rem;
}
.contribute-steps h3 {
font-size: 1rem;
letter-spacing: -0.01em;
}
.contribute-steps p {
margin-top: 0.5rem;
color: var(--ink-soft);
font-size: 0.82rem;
line-height: 1.65;
}
.contribute-review-list {
border-top: 1px solid var(--hairline);
}
.contribute-review-list li {
padding-block: 1rem;
border-bottom: 1px solid var(--hairline);
color: var(--ink-soft);
font-size: 0.84rem;
line-height: 1.6;
}
.contribute-dev-loop {
padding-block: clamp(3.25rem, 6vw, 4.75rem);
background: var(--ocean-deep);
}
.contribute-dev-loop .portal-section-copy > span {
color: var(--stage-muted);
}
.contribute-dev-loop .portal-section-copy h2 {
color: var(--stage-text);
}
.contribute-dev-loop .portal-section-copy p {
color: rgb(var(--c-stage-soft) / 0.76);
}
.contribute-dev-loop pre.code-block {
border-color: rgb(var(--c-stage-soft) / 0.2);
background: rgb(var(--c-stage-deep) / 0.62);
}
@media (max-width: 900px) {
.portal-hero-grid,
.portal-section-grid,
.portal-community-grid {
grid-template-columns: 1fr;
}
.portal-hero-copy {
max-width: 48rem;
}
.portal-quickstart {
max-width: 42rem;
}
}
@media (max-width: 760px) {
.portal-doc-groups {
grid-template-columns: 1fr;
}
.portal-doc-groups section > p {
min-height: 0;
}
.contribute-path-grid,
.contribute-steps {
grid-template-columns: 1fr;
}
.contribute-path-grid article,
.contribute-path-grid article:nth-child(even),
.contribute-steps li,
.contribute-steps li:nth-child(even) {
padding-inline: 0;
border-left: 0;
}
}
@media (max-width: 560px) {
.portal-hero-grid {
gap: 2.25rem;
padding-block: 2.75rem;
}
.portal-actions,
.portal-docs-heading {
align-items: stretch;
flex-direction: column;
}
.portal-button {
width: 100%;
}
.portal-topic-list > a,
.portal-topic-list > div,
.portal-doc-groups .portal-topic-list > a,
.portal-doc-groups .portal-topic-list > div {
grid-template-columns: 1fr auto;
gap: 0.45rem 1rem;
}
.portal-topic-list > a span:nth-child(2),
.portal-topic-list > div span:nth-child(2) {
grid-column: 1 / -1;
}
.portal-topic-list > a span:last-child,
.portal-topic-list > div span:last-child {
grid-column: 2;
grid-row: 1;
}
.portal-community-links {
grid-template-columns: 1fr;
}
}
+383
View File
@@ -0,0 +1,383 @@
/* ---------- nav link ---------- */
.site-nav {
position: sticky;
z-index: 30;
top: 0;
border-bottom: 1px solid var(--hairline);
background: rgb(var(--c-paper) / 0.94);
backdrop-filter: blur(10px);
}
.site-nav-inner {
display: flex;
width: var(--container);
min-height: 4.25rem;
align-items: center;
justify-content: space-between;
gap: 1.5rem;
margin-inline: auto;
}
.site-wordmark {
display: inline-flex;
flex-shrink: 0;
align-items: center;
gap: 0.7rem;
color: var(--ink);
font-family: var(--font-display), ui-sans-serif, system-ui, sans-serif;
font-size: 1.15rem;
font-weight: 600;
letter-spacing: -0.03em;
}
.paper-nav-inner {
min-height: 3.85rem;
padding-block: 0.55rem;
}
.paper-wordmark {
flex: 0 1 auto;
gap: 0.75rem;
max-width: 15rem;
min-width: 8.75rem;
overflow: hidden;
}
.paper-wordmark-text {
display: flex;
align-items: center;
gap: 8px;
line-height: 1.1;
min-width: 0;
}
.paper-wordmark-mark {
display: block;
flex: 0 0 auto;
width: 22px;
height: 22px;
}
/* The traced wordmark is ~7.1:1; when the compact nav clamps its width the
glyphs scale down inside the box instead of squashing. */
.paper-wordmark-logo {
display: block;
flex: 0 1 auto;
width: auto;
min-width: 0;
max-width: 100%;
height: 20px;
object-fit: contain;
object-position: left center;
}
.paper-star-badge {
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.75rem;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.paper-install-cta {
align-items: center;
min-height: 2.25rem;
padding: 0.35rem 0.85rem;
background: var(--indigo-deep);
color: var(--paper);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
border-radius: 5px;
transition: background-color 150ms ease;
}
.paper-install-cta:hover {
background: var(--indigo);
color: var(--paper);
}
.paper-auth {
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.paper-auth-signin,
.paper-auth-register {
align-items: center;
min-height: 2.25rem;
padding: 0.35rem 0.75rem;
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease;
}
.paper-auth-signin,
.paper-auth-register {
color: var(--ink);
}
.paper-auth-signin:hover,
.paper-auth-register:hover {
color: var(--indigo);
}
.site-nav-actions {
display: flex;
flex-shrink: 0;
align-items: center;
gap: 0.65rem;
}
.site-nav-actions > * { flex-shrink: 0; }
/* Native <select> sizes to the selected option (Safari) or the longest
option (Chrome). Unbounded, "Bahasa Indonesia (sebagian)" / "Deutsch
(teilweise)" push the compact controls past overflow-x: clip. */
.site-nav-actions select {
width: 6.75rem;
max-width: 6.75rem;
min-width: 0;
}
.site-github-link {
display: inline-flex;
min-height: 2.25rem;
align-items: center;
padding: 0.35rem 0.75rem;
border: 1px solid var(--hairline);
border-radius: 6px;
font-family: var(--font-body), system-ui, sans-serif;
font-size: 0.75rem;
font-weight: 600;
}
.site-github-link:hover { border-color: var(--ink); }
.brand-mark { width: 1rem; height: 1rem; display: block; }
.paper-star-badge .brand-mark { margin-right: 0.3rem; }
.nav-link {
font-family: var(--font-body), system-ui, sans-serif;
font-size: 0.78rem;
letter-spacing: 0;
color: var(--ink);
position: relative;
padding: 0.25rem 0;
}
.nav-link::after {
content: "";
position: absolute;
left: 0; right: 0; bottom: -2px;
height: 2px;
background: var(--indigo);
transform: scaleX(0);
transform-origin: left;
transition: transform 180ms ease;
}
.nav-link:hover::after, .nav-link[aria-current="page"]::after { transform: scaleX(1); }
/* ---------- footer ---------- */
.site-footer {
position: relative;
background: var(--ocean-deep);
color: var(--stage-soft);
}
/* The waterline before the seabed. Every page that is still on paper when
it reaches the footer descends here; the homepage is already under water
by then, so its column hides the band and the seabed simply continues. */
.site-footer-waterline {
position: relative;
height: clamp(9rem, 18vw, 15rem);
overflow: hidden;
background: var(--paper);
}
.site-footer-waterline > svg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
main:has(.ocean-column) + .site-footer .site-footer-waterline { display: none; }
.site-footer-main { border-top: 1px solid rgb(var(--c-stage-soft) / 0.13); }
main:has(.ocean-column) + .site-footer .site-footer-main { border-top-color: transparent; }
.site-footer-main,
.site-footer-meta {
width: var(--container);
margin-inline: auto;
}
.site-footer-main {
display: grid;
grid-template-columns: minmax(16rem, 1.25fr) minmax(18rem, 0.75fr);
gap: 4rem;
padding-block: 2.8rem;
}
.site-wordmark-footer { color: var(--stage-text); }
.site-wordmark-footer img { display: block; width: auto; height: 18px; }
.site-footer-brand p { max-width: 30rem; margin-top: 0.8rem; color: rgb(var(--c-stage-soft) / 0.62); font-size: 0.86rem; line-height: 1.6; }
.site-footer-links {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 2rem;
}
.site-footer-links > div { display: flex; flex-direction: column; gap: 0.55rem; }
.site-footer-links span { margin-bottom: 0.25rem; color: var(--ocean-current); font-family: var(--font-mono), "IBM Plex Mono", monospace; font-size: 0.75rem; letter-spacing: 0.1em; text-transform: uppercase; }
.site-footer-links a { color: rgb(var(--c-stage-soft) / 0.76); font-size: 0.82rem; }
.site-footer-links a:hover { color: var(--stage-text); }
.site-footer-meta {
display: grid;
grid-template-columns: 1fr auto auto;
gap: 1.5rem;
align-items: center;
padding-block: 1rem;
border-top: 1px solid rgb(var(--c-stage-soft) / 0.13);
color: rgb(var(--c-stage-soft) / 0.5);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.75rem;
}
.site-footer-meta a:hover { color: var(--ocean-current); }
.site-footer-meta > div { display: flex; gap: 1rem; }
.site-footer-meta > p { min-width: 0; max-width: 70ch; overflow-wrap: anywhere; }
/* ---------- ticker ---------- */
.ticker-viewport {
position: relative;
flex: 1 1 auto;
overflow: hidden;
}
.ticker-track {
display: inline-flex;
gap: 3rem;
white-space: nowrap;
animation: ticker 80s linear infinite;
padding-right: 3rem;
}
@keyframes ticker {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
/* One wire entry: verb, number or tag, title, by-line, age. Baseline-aligned
so the mono verb sits on the same line as the title it qualifies. */
.ticker-item {
display: inline-flex;
align-items: baseline;
gap: 0.5rem;
}
/* The verb carries the state. Merges are the headline case, so they get the
one warm-neutral ink on the sheet; releases take ochre; anything closed
recedes to mute. Everything else is the action blue. */
.ticker-verb {
color: var(--indigo);
font-size: 0.7rem;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.ticker-verb[data-event="merged"] { color: var(--jade); }
.ticker-verb[data-event="published"] { color: var(--ochre); }
.ticker-verb[data-event="closed"] { color: var(--ink-mute); }
.ticker-num,
.ticker-tag,
.ticker-by,
.ticker-age {
color: var(--ink-mute);
}
.ticker-title {
color: var(--ink);
max-width: 24rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* The contributor's handle is the point of the line, not a footnote. */
.ticker-handle {
color: var(--ink);
font-weight: 600;
}
.ticker-first {
padding: 0 0.35rem;
border: 1px solid var(--ochre);
color: var(--ochre);
font-size: 0.7rem;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.ticker-sep { color: var(--paper-line-soft); }
@media (prefers-reduced-motion: reduce) {
.ticker-track { animation: none; }
/* A frozen track would clip every entry past the fold with no way to reach
them. Stopping the motion must not also remove the content. */
.ticker-viewport { overflow-x: auto; }
}
/* ---------- ambient ---------- */
/*
* Two effects, both OPT-IN under `no-preference` rather than opted out under
* `reduce`. A `reduce` override can be defeated by specificity; a gate cannot.
* Reduced motion freezes the field — it never removes it, so the column's
* gradient and the whale below are unconditional and only the movement is not.
*
* The TUI's ambient life — fish, jellyfish, bubbles, the whale cameo — stays
* out of scope. This page quotes the product's restraint doctrine in its own
* copy. The standing loops on the whole site are four — ticker, caret, and
* these two. Entrances are transitions, and the one-shot settles live in the
* Motion section below under the same no-preference gate.
*/
/* M1 — the column breath. Opacity only, on a 90s cycle: the TUI's authored
0.018–0.055 phase bias expressed as the same fraction of a mix. Opacity is
the one property that cannot cause layout work on an element four bands
tall. No position change, no hue shift, no filter. */
@keyframes cw-breath {
0%, 100% { opacity: 0; }
50% { opacity: 0.045; }
}
/* M2 — the caustic. `ambient_life.rs`'s literal behaviour: ~1.3s crossing the
mark, parked off-canvas for the remaining ~2.7s, peak 0.33. The easing is a
sine in-out, i.e. the raised cosine it is meant to be. */
@keyframes cw-caustic {
0% { transform: translateX(0); }
32.5% { transform: translateX(1560px); }
100% { transform: translateX(1560px); }
}
@media (prefers-reduced-motion: no-preference) {
.ocean-column::after {
content: "";
position: absolute;
inset: 0;
z-index: -1;
pointer-events: none;
background: var(--stage-ambient);
opacity: 0;
animation: cw-breath 90s ease-in-out infinite;
}
.codewhale-caustic {
animation: cw-caustic 4s cubic-bezier(0.37, 0, 0.63, 1) infinite;
}
}
+194
View File
@@ -0,0 +1,194 @@
/* ------------------------------------------------------------------ */
/* Shared surface states — empty / loading / error (surface-state.tsx) */
/* ------------------------------------------------------------------ */
/*
* One plate for every data-bearing page's non-data moments. The mark on the
* left carries the state in colour AND the copy carries it in words, so no
* state is conveyed by colour alone: mute for empty, action blue for loading,
* coral for error — the status-bar grammar the product already speaks.
*/
.state-block {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: 1rem;
align-items: start;
padding: clamp(1.5rem, 4vw, 2.25rem) clamp(1.25rem, 3vw, 1.75rem);
border: 1px solid var(--stage-line);
border-radius: 6px;
background: var(--paper-card);
color: var(--ink);
}
.state-block-compact {
padding: 1rem 1.15rem;
gap: 0.8rem;
}
.state-mark {
width: 0.6rem;
height: 0.6rem;
margin-top: 0.42rem;
border-radius: 999px;
background: var(--ink-mute);
flex: none;
}
.state-block-loading .state-mark { background: var(--indigo); }
.state-block-error .state-mark { background: var(--ocean-coral); }
.state-block-error { border-color: color-mix(in srgb, var(--ocean-coral) 45%, var(--stage-line)); }
.state-copy { min-width: 0; }
.state-title {
color: var(--ink);
font-size: 0.98rem;
font-weight: 600;
letter-spacing: -0.01em;
}
.state-body {
max-width: 44rem;
margin-top: 0.4rem;
color: var(--ink-soft);
font-size: 0.86rem;
line-height: 1.65;
}
.state-actions {
display: flex;
flex-wrap: wrap;
gap: 0.6rem;
margin-top: 1rem;
}
.state-retry[aria-busy="true"] { opacity: 0.7; cursor: progress; }
/* Skeleton lines: neutral bars, never fake words. The shimmer is opt-in
under no-preference; reduced motion sees still bars. */
.state-skeleton {
display: grid;
gap: 0.55rem;
margin-top: 0.85rem;
}
.state-skeleton > span {
display: block;
height: 0.7rem;
border-radius: 3px;
background: color-mix(in srgb, var(--ink-mute) 22%, transparent);
}
@media (prefers-reduced-motion: no-preference) {
.state-skeleton > span {
background: linear-gradient(
90deg,
color-mix(in srgb, var(--ink-mute) 18%, transparent) 0%,
color-mix(in srgb, var(--ink-mute) 34%, transparent) 50%,
color-mix(in srgb, var(--ink-mute) 18%, transparent) 100%
);
background-size: 200% 100%;
animation: state-shimmer 1.6s ease-in-out infinite;
}
}
@keyframes state-shimmer {
from { background-position: 200% 0; }
to { background-position: -200% 0; }
}
/* Route-level boundaries (loading.tsx / error.tsx / not-found.tsx) sit in
the site container with the page rhythm so a boundary never looks like a
different site. */
.route-state {
width: var(--container);
margin-inline: auto;
padding-block: clamp(3rem, 6vw, 4.75rem);
max-width: 52rem;
}
/* ------------------------------------------------------------------ */
/* Connection banner — offline / reconnecting / degraded / restored */
/* ------------------------------------------------------------------ */
.connection-banner {
position: sticky;
top: 0;
z-index: 35;
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
gap: 0.9rem;
align-items: center;
width: var(--container);
margin: 0.75rem auto 0;
padding: 0.7rem 1rem;
border: 1px solid var(--stage-line);
border-radius: 6px;
background: color-mix(in srgb, var(--paper-card) 92%, transparent);
backdrop-filter: blur(8px);
color: var(--ink);
}
.connection-mark {
width: 0.6rem;
height: 0.6rem;
border-radius: 999px;
background: var(--ink-mute);
}
.connection-banner-offline { border-color: color-mix(in srgb, var(--ocean-coral) 50%, var(--stage-line)); }
.connection-banner-offline .connection-mark { background: var(--ocean-coral); }
.connection-banner-reconnecting .connection-mark { background: var(--indigo); }
.connection-banner-degraded .connection-mark { background: var(--ochre); }
.connection-banner-restored { border-color: color-mix(in srgb, var(--jade) 50%, var(--stage-line)); }
.connection-banner-restored .connection-mark { background: var(--jade); }
@media (prefers-reduced-motion: no-preference) {
.connection-banner-reconnecting .connection-mark {
animation: connection-pulse 1.2s ease-in-out infinite;
}
.connection-banner {
animation: mm-in 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}
}
@keyframes connection-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.35; }
}
.connection-copy { min-width: 0; }
.connection-title { font-size: 0.86rem; font-weight: 600; }
.connection-body { margin-top: 0.15rem; color: var(--ink-soft); font-size: 0.78rem; line-height: 1.5; }
.connection-checked { color: var(--ink-mute); font-family: var(--font-mono), "IBM Plex Mono", monospace; font-size: 0.75rem; }
.connection-actions { display: flex; gap: 0.5rem; }
.connection-button {
display: inline-flex;
min-height: 2.25rem;
align-items: center;
padding: 0.35rem 0.75rem;
border: 1px solid var(--stage-line);
border-radius: 5px;
color: var(--ink);
font-family: var(--font-mono), "IBM Plex Mono", monospace;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
transition: border-color 150ms ease, background-color 150ms ease, color 150ms ease;
}
.connection-button:hover { border-color: var(--indigo); color: var(--indigo); }
.connection-button-primary { border-color: var(--indigo); background: var(--indigo); color: var(--paper); }
.connection-button-primary:hover { background: var(--indigo-deep); border-color: var(--indigo-deep); color: var(--paper); }
.connection-button:disabled { opacity: 0.55; cursor: not-allowed; }
.connection-button[aria-busy="true"] { cursor: progress; }
@media (max-width: 560px) {
.connection-banner {
grid-template-columns: auto minmax(0, 1fr);
}
.connection-actions {
grid-column: 2;
}
}
+3
View File
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+176
View File
@@ -0,0 +1,176 @@
/* ---------- root tokens — GPUI theme ---------- */
/*
* The site is a folio read in the product client's light: warm paper at the
* surface, warm charcoal at depth. Above the waterline the field is the GPUI
* light theme's paper and the ink is its plum-charcoal foreground. Below the
* waterline (`.ocean-column`, the footer, every terminal plate) the same
* names resolve to the dark charcoal tokens, so a component written once
* reads correctly on either side.
*
* The token NAMES keep their historical shape (`--paper`, `--ink`,
* `--indigo`) so every component rule stays diffable; the VALUES resolve
* through the `--gpui-*` block below, which now references the generated
* Shoreline tokens in ./tokens.css (crates/palette/src/tokens.rs is the
* single source — the same constants the GPUI client and the TUI open on).
* Only values with no Shoreline slot stay literal: the hover tones, the
* accent washes, and the AA-adapted state hues. `--whale-*`/`--light-*`
* remain available to anything that genuinely wants the terminal palette.
*/
:root {
/* --- The GPUI source palette (generated Shoreline slots) --- */
--gpui-paper: var(--shoreline-light-surface); /* light background — warm paper */
--gpui-paper-raised: var(--shoreline-light-elevated); /* light card/popover */
--gpui-paper-muted: var(--shoreline-light-panel); /* light muted surface */
--gpui-ink: var(--shoreline-light-text-body); /* light foreground */
--gpui-ink-soft: var(--shoreline-light-text-soft);
--gpui-ink-mute: var(--shoreline-light-text-muted); /* light muted_foreground */
--gpui-edge: var(--shoreline-light-border); /* light border */
--gpui-primary: var(--shoreline-light-action); /* light primary */
--gpui-primary-hover: #00536d; /* light button_primary_hover — no Shoreline slot */
--gpui-accent: #dcebee; /* light accent — no Shoreline slot */
--gpui-selection: var(--shoreline-light-selection); /* light selection */
--gpui-stage: var(--shoreline-surface); /* dark background — warm charcoal */
--gpui-stage-deep: var(--shoreline-chrome); /* deepest dark */
--gpui-stage-muted: var(--shoreline-panel); /* dark muted surface */
--gpui-stage-raised: var(--shoreline-elevated); /* raised dark plate */
--gpui-stage-ink: var(--shoreline-text-body); /* dark foreground */
--gpui-stage-ink-soft: var(--shoreline-text-muted); /* dark muted_foreground */
--gpui-stage-ink-dim: var(--shoreline-text-dim);
--gpui-stage-edge: var(--shoreline-border); /* dark border */
--gpui-primary-dark: var(--shoreline-action); /* dark primary */
--gpui-primary-hover-dark: #93cde1; /* dark button_primary_hover — no Shoreline slot */
--gpui-on-primary: #102d38; /* dark primary_foreground — no Shoreline slot */
--gpui-accent-dark: #303f48; /* dark accent — no Shoreline slot */
--gpui-selection-dark: var(--shoreline-selection); /* dark selection */
/* Warm-muted state hues from the GPUI mockups, darkened or lifted per
side of the waterline so text uses of them still clear AA. No Shoreline
slots: these are site-specific adaptations. */
--gpui-tan: #b1a17a; /* human mark */
--gpui-moss: #739686; /* outcome */
--gpui-rust: #aa6f6c; /* attention */
/* RGB channel triples backing the Tailwind surface/ink tokens (see
tailwind.config.ts). `.ocean-column` and the opt-in docs dark sheet
override them for their dark subtree. */
--c-paper: var(--shoreline-light-surface-rgb);
--c-paper-deep: var(--shoreline-light-panel-rgb);
--c-paper-edge: var(--shoreline-light-border-rgb);
--c-ink: var(--shoreline-light-text-body-rgb);
--c-ink-soft: var(--shoreline-light-text-soft-rgb);
--c-ink-mute: var(--shoreline-light-text-muted-rgb);
--c-indigo: var(--shoreline-light-action-rgb);
--c-indigo-deep: 0 83 109; /* light primary hover — no Shoreline slot */
--c-stage-soft: var(--shoreline-text-muted-rgb);
--c-stage-deep: var(--shoreline-chrome-rgb);
--c-primary-dark: var(--shoreline-action-rgb);
/* The sheet. */
--paper: var(--gpui-paper);
--paper-deep: var(--gpui-paper-muted);
--paper-edge: var(--gpui-edge);
--paper-card: var(--gpui-paper-raised);
--paper-line: var(--gpui-edge);
--paper-line-soft: var(--gpui-edge);
--ink: var(--gpui-ink);
--ink-soft: var(--gpui-ink-soft);
--ink-mute: var(--gpui-ink-mute);
/* Action on paper is the GPUI light primary; hover sinks to its hover. */
--indigo: var(--gpui-primary);
--indigo-deep: var(--gpui-primary-hover);
--indigo-pale: rgb(var(--c-indigo) / 0.1);
--action-on-dark: var(--gpui-primary-dark);
/* State inks darkened from the mockup hues so they clear AA on paper:
moss is outcome, rust is attention, tan is the human mark. */
--ochre: #7d6a3f;
--jade: #4e6f61;
--seafoam: var(--gpui-moss);
--cobalt: var(--gpui-primary);
--cyan: var(--gpui-primary); /* reserved for the composer prompt glyph and the release line */
--ocean-deep: var(--gpui-stage-deep);
--ocean-mid: var(--gpui-stage-muted);
--ocean-current: var(--gpui-primary-dark);
--ocean-mist: var(--gpui-stage-ink-soft);
--ocean-coral: #8f5a56;
--signal-gold: #7d6a3f;
/* The whale mark is ink on paper; below the waterline it is the light
stage foreground. The warm tan stays reserved for human moments. */
--mark-ink: var(--gpui-ink);
--stage-text: var(--gpui-stage-ink);
--stage-soft: var(--gpui-stage-ink-soft);
--stage-muted: var(--gpui-stage-ink-dim);
/* The water column, re-inked in the GPUI dark ramp. */
--stage-field-top: var(--gpui-stage-muted); /* surface */
--stage-field-mid: var(--gpui-stage); /* the authored 42% break */
--stage-field-deep: var(--gpui-stage-deep); /* deep field, and the footer seabed */
--stage-ambient: var(--gpui-accent-dark);
--stage-composer: var(--gpui-stage-muted); /* the raised input plate */
--stage-elevated: var(--gpui-stage-raised);
--stage-line: var(--gpui-stage-edge); /* the charcoal border on stage */
--stage-hint: var(--gpui-stage-ink-dim);
--stage-dim: var(--gpui-stage-ink-soft);
--violet: var(--gpui-selection-dark); /* 1px rules only, never text */
/* Newsreader carries h1/h2; Shannon Sans supplies body and small headings.
The historic condensed role keeps its scale and weight, not a second face.
IBM Plex Mono and the Unicode fallback stacks retain their own roles. */
--font-body: var(--font-shannon-sans);
--font-display: var(--font-shannon-sans);
--font-cjk: "PingFang SC", "Hiragino Sans GB", "Source Han Serif SC",
"Noto Serif CJK SC", serif;
/* Hairline + code surfaces routed through vars so the dark subtrees can
re-ink them without touching every rule. Code plates are always the
stage's own deepest field, on either side of the waterline. */
--hairline: rgb(var(--c-ink) / 0.16);
--code-bg: var(--gpui-stage-deep);
--code-fg: var(--gpui-stage-ink);
/* One site container and one vertical rhythm. Every page gutter aligns
with the nav; every hero and section shares a single padding token. */
--container: min(100% - 2rem, 76rem);
--hero-pad: clamp(3rem, 6vw, 4.75rem);
--section-pad: clamp(2.75rem, 5vw, 4rem);
color-scheme: light;
}
/*
* Below the waterline. One rule, applied to every dark subtree — the water
* column on the homepage, the footer seabed, the opt-in docs dark sheet
* (`.docs-portal` is the docs layout root, which also carries `.docs-theme`)
* — so a component never needs to know which side of the surface it is on.
*/
.ocean-column,
.site-footer,
html[data-theme="dark"] .docs-portal {
--c-paper: var(--shoreline-surface-rgb);
--c-paper-deep: var(--shoreline-panel-rgb);
--c-paper-edge: var(--shoreline-border-rgb);
--c-ink: var(--shoreline-text-body-rgb);
--c-ink-soft: var(--shoreline-text-muted-rgb);
--c-ink-mute: var(--shoreline-text-dim-rgb);
--c-indigo: var(--shoreline-action-rgb);
--c-indigo-deep: 147 205 225; /* lifted primary — hover on dark, no Shoreline slot */
--paper: var(--gpui-stage);
--paper-deep: var(--gpui-stage-muted);
--paper-edge: var(--gpui-stage-edge);
--paper-card: var(--gpui-stage-muted);
--paper-line: var(--gpui-stage-edge);
--paper-line-soft: var(--gpui-stage-edge);
--ink: var(--gpui-stage-ink);
--ink-soft: var(--gpui-stage-ink-soft);
--ink-mute: var(--gpui-stage-ink-dim);
--indigo: var(--gpui-primary-dark);
--indigo-deep: var(--gpui-primary-hover-dark);
--indigo-pale: rgb(var(--c-primary-dark) / 0.14);
--ochre: #d6c78f;
--jade: #9ec7b2;
--cyan: var(--gpui-primary-dark);
--ocean-coral: #d08a80;
--signal-gold: #d6c78f;
--mark-ink: var(--gpui-stage-ink);
--hairline: rgb(var(--c-stage-soft) / 0.2);
color-scheme: dark;
}
+185
View File
@@ -0,0 +1,185 @@
/* ------------------------------------------------------------------ */
/* Motion — entrances, considered hovers, and one-shot settles */
/* ------------------------------------------------------------------ */
/*
* Grammar: the page is complete and static; motion answers a person's action
* (a hover draws a rule, a press stamps a copy). The one authored ambient
* moment is the column breath above. Everything here animates transform
* and/or opacity only and is gated on `no-preference`, so reduced motion is
* the *absence* of motion rather than an override fighting it.
*/
/* The nav-link underline draw, extended to the other mono-voice links. Hover
draws the rule; the transition is the whole effect, so reduced motion keeps
the existing colour change and simply skips the draw. */
.product-start-links a,
.gs-step-link,
.product-surfaces > .product-container > a,
.product-community nav a {
position: relative;
}
.product-start-links a::after,
.gs-step-link::after,
.product-surfaces > .product-container > a::after,
.product-community nav a::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: -2px;
height: 1px;
background: currentColor;
transform: scaleX(0);
transform-origin: left;
transition: transform 180ms ease;
}
@media (prefers-reduced-motion: no-preference) {
.product-start-links a:hover::after,
.gs-step-link:hover::after,
.product-surfaces > .product-container > a:hover::after,
.product-community nav a:hover::after {
transform: scaleX(1);
}
}
/* The primary-button arrow leans into the direction it points. */
.product-button > span[aria-hidden="true"] {
display: inline-block;
transition: transform 150ms ease;
}
@media (prefers-reduced-motion: no-preference) {
.product-button:hover > span[aria-hidden="true"],
.product-button:focus-visible > span[aria-hidden="true"] {
transform: translateX(2px);
}
}
/* Copy-to-clipboard: the press is a stamp, the confirmation a jade seal —
the wire's merge verb colour, borrowed for one beat. The colour change is
unconditional (it is the confirmation); only the motion is gated. */
.copy-btn[data-copied="true"] {
background: var(--jade);
color: var(--paper);
}
@media (prefers-reduced-motion: no-preference) {
.copy-btn {
transition: background-color 150ms ease, color 150ms ease, transform 120ms ease;
}
.copy-btn:active {
transform: scale(0.95);
}
.copy-btn[data-copied="true"] {
animation: copy-settle 300ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
}
@keyframes copy-settle {
0% { transform: scale(1); }
35% { transform: scale(0.93); }
70% { transform: scale(1.04); }
100% { transform: scale(1); }
}
/* Mobile menu: the sheet drops a few points while its items settle in
sequence; exit is a short fade. The exit is driven by a delayed unmount in
components/mobile-menu.tsx — with reduced motion the menu mounts and
unmounts instantly, exactly as before. */
@media (prefers-reduced-motion: no-preference) {
.mm-panel {
animation: mm-in 240ms cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}
.mm-panel.mm-closing {
animation: mm-out 170ms cubic-bezier(0.4, 0, 0.8, 0.4) both;
pointer-events: none;
}
.mm-panel li,
.mm-panel nav > a {
animation: mm-rise 280ms cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}
.mm-panel li:nth-child(1) { animation-delay: 34ms; }
.mm-panel li:nth-child(2) { animation-delay: 58ms; }
.mm-panel li:nth-child(3) { animation-delay: 82ms; }
.mm-panel li:nth-child(4) { animation-delay: 106ms; }
.mm-panel li:nth-child(5) { animation-delay: 130ms; }
.mm-panel li:nth-child(6) { animation-delay: 154ms; }
.mm-panel li:nth-child(7) { animation-delay: 178ms; }
.mm-panel li:nth-child(8) { animation-delay: 202ms; }
.mm-panel nav > a { animation-delay: 226ms; }
}
@keyframes mm-in {
from { opacity: 0; transform: translateY(-8px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes mm-out {
from { opacity: 1; transform: translateY(0); }
to { opacity: 0; transform: translateY(-6px); }
}
@keyframes mm-rise {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
/* ---------- decorative big CJK in margin ---------- */
.margin-glyph {
font-family: var(--font-cjk), "PingFang SC", "Source Han Serif SC", serif;
font-weight: 700;
color: var(--ink);
opacity: 0.04;
font-size: 18rem;
line-height: 0.9;
pointer-events: none;
user-select: none;
position: absolute;
}
/* ---------- focus + selection ---------- */
::selection { background: var(--indigo); color: var(--paper); }
:focus-visible { outline: 2px solid var(--indigo); outline-offset: 2px; }
/* ---------- link reset ---------- */
a { color: inherit; text-decoration: none; }
a.body-link {
color: var(--ink);
background-image: linear-gradient(var(--indigo), var(--indigo));
background-repeat: no-repeat;
background-position: 0 100%;
background-size: 100% 1px;
transition: background-size 180ms ease;
}
a.body-link:hover { background-size: 100% 6px; color: var(--ink); }
/* ---------- mobile-only adjustments ---------- */
@media (max-width: 640px) {
/* Big CJK margin glyph already hidden via tailwind's `hidden lg:block`,
but re-assert that nothing hits the viewport edge. */
.margin-glyph { display: none; }
/* Ticker text gets cramped on phones — shrink + tighten gaps */
.ticker-track { gap: 1.5rem; padding-right: 1.5rem; }
}
@media (max-width: 900px) {
.site-nav-inner { gap: 1rem; }
.site-github-link { display: none; }
.site-footer-main { grid-template-columns: 1fr; gap: 2.5rem; }
.site-footer-meta { grid-template-columns: minmax(0, 1fr); gap: 0.7rem; }
.site-footer-meta > div { min-width: 0; flex-wrap: wrap; }
}
@media (max-width: 700px) {
.site-wordmark { font-size: 1.05rem; }
}
/* Anchor fallback; page-level scroll-mt utilities should be able to override it. */
:where([id]) { scroll-margin-top: 5rem; }
+2 -1
View File
@@ -1,8 +1,9 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
import { resolveWhale } from "./whale-tokens";
import { siteCss } from "./site-css";
const CSS = readFileSync(new URL("../app/globals.css", import.meta.url), "utf8");
const CSS = siteCss();
function selectorBlock(selector: string): string {
const match = CSS.match(
+2 -1
View File
@@ -24,6 +24,7 @@ import {
secondaryNavLinks as buildSecondaryNavLinks,
} from "./i18n/links";
import { SITE_URL } from "./page-meta";
import { siteCss } from "./site-css";
const webRoot = new URL("../", import.meta.url);
const repoRoot = new URL("../../", import.meta.url);
@@ -38,7 +39,7 @@ const navLinks = webText("components/nav-links.tsx");
const mobileMenu = webText("components/mobile-menu.tsx");
const footer = webText("components/footer.tsx");
const localeLayout = webText("app/[locale]/layout.tsx");
const css = webText("app/globals.css");
const css = siteCss();
describe("docs-map registration", () => {
it("registers the guide and vocabulary topics as first-party pages", () => {
+2 -2
View File
@@ -1,8 +1,8 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
import { resolveWhale } from "./whale-tokens";
import { siteCss } from "./site-css";
const CSS = readFileSync(new URL("../app/globals.css", import.meta.url), "utf8");
const CSS = siteCss();
function selectorBlock(selector: string): string {
const match = CSS.match(new RegExp(`(?:^|\n)${selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\{([^}]*)\\}`, "s"));
+2 -1
View File
@@ -16,6 +16,7 @@ import { locales } from "./config";
import { getChrome } from "./dictionaries";
import { navLinks } from "./links";
import { isDocsPath, replacePathLocale } from "./path";
import { siteCss } from "../site-css";
const webRoot = new URL("../../", import.meta.url);
@@ -35,7 +36,7 @@ function advance(text: string, fontRem: number): number {
}
describe("localized chrome keeps a clickable home control", () => {
const css = webText("app/globals.css");
const css = siteCss();
const theme = webText("components/theme-toggle.tsx");
it("keeps the wordmark and locale switcher from shrinking to zero", () => {
+2 -1
View File
@@ -16,6 +16,7 @@ import { SNIPPETS, VERIFY } from "./install-binary-snippets";
import { getChrome, getHome } from "./i18n/dictionaries";
import { footerProjectLinks } from "./i18n/links";
import { TERMINAL_SCREENSHOT } from "./media-manifest";
import { siteCss } from "./site-css";
const root = new URL("../../", import.meta.url);
@@ -643,7 +644,7 @@ done
});
it("keeps reduced motion static without hiding the reasoning trace", () => {
const css = text("web/app/globals.css");
const css = siteCss();
expect(css).toMatch(
/@media \(prefers-reduced-motion: reduce\)\s*\{[\s\S]*?\.ticker-track\s*\{\s*animation:\s*none;\s*\}[\s\S]*?\}/,
+19
View File
@@ -0,0 +1,19 @@
import { readFileSync } from "node:fs";
/**
* The hand-written site stylesheet as one string, in cascade order.
*
* `app/globals.css` is an import hub; its rules live in `app/styles/*.css`.
* This inlines each `./styles/` import in place so the contract tests can read
* the stylesheet the way the browser resolves it. The generated `tokens.css`
* is left out, as it was before the split.
*
* Node-only (`node:fs`): imported by the contract tests, never by a component.
*/
export function siteCss(): string {
const appDir = new URL("../app/", import.meta.url);
const hub = readFileSync(new URL("globals.css", appDir), "utf8");
return hub.replace(/^@import\s+"\.\/(styles\/[\w-]+\.css)";$/gm, (_, path: string) =>
readFileSync(new URL(path, appDir), "utf8"),
);
}
+4 -3
View File
@@ -1,10 +1,11 @@
import { readFileSync } from "node:fs";
import { siteCss } from "./site-css";
/**
* The design palette as the site actually resolves it.
*
* `app/tokens.css` is generated from `crates/palette/src/tokens.rs` by
* `scripts/export-design-tokens.py`, and `globals.css` carries the hand-kept
* `scripts/export-design-tokens.py`, and `app/styles/tokens-roles.css` carries the hand-kept
* `--gpui-*` block that mirrors the GPUI client's theme. Site variables state
* which token each uses (`--paper: var(--gpui-paper)`) instead of repeating
* the hex. The contract tests still need the literal color to check parity
@@ -19,7 +20,7 @@ import { readFileSync } from "node:fs";
*/
const RAW: Record<string, string> = (() => {
const generated = readFileSync(new URL("../app/tokens.css", import.meta.url), "utf8");
const globals = readFileSync(new URL("../app/globals.css", import.meta.url), "utf8");
const globals = siteCss();
const raw: Record<string, string> = {};
for (const match of generated.matchAll(/--((?:whale|light|shoreline-light|shoreline)-[\w-]+):\s*([^;]+);/g)) {
raw[match[1]] = match[2].trim();
@@ -28,7 +29,7 @@ const RAW: Record<string, string> = (() => {
raw[match[1]] = match[2].trim();
}
if (Object.keys(raw).length === 0) {
throw new Error("no palette properties found in tokens.css/globals.css");
throw new Error("no palette properties found in tokens.css/styles");
}
return raw;
})();
+6
View File
@@ -1,5 +1,11 @@
const config = {
plugins: {
// Inline the app/styles/* partials into globals.css before Tailwind runs.
// Tailwind expands each file on its own and appends variant utilities
// (hover:, md:, ...) at the end of the file that holds `@tailwind
// utilities`; inlining keeps them after every partial, as they were when
// globals.css was one file. tokens.css stays a separate module.
"postcss-import": { filter: (path) => path.startsWith("./styles/") },
tailwindcss: {},
autoprefixer: {},
},