diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-08-03 21:33:33 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-08-03 21:39:25 +0300 |
| commit | 22bb476d646ccadec71f584b90c2a135fec92929 (patch) | |
| tree | c6d4b6dffe0b8891c52e7e0cc10c8e6051734c30 /website/css | |
| parent | eacea2daf0b72973173b728936b27478374f2cd2 (diff) | |
feat(website): rebuild marketing pages on new brand chassis
Diffstat (limited to 'website/css')
| -rw-r--r-- | website/css/og-card.css | 102 | ||||
| -rw-r--r-- | website/css/site.css | 992 |
2 files changed, 1094 insertions, 0 deletions
diff --git a/website/css/og-card.css b/website/css/og-card.css new file mode 100644 index 00000000..8bb852d7 --- /dev/null +++ b/website/css/og-card.css @@ -0,0 +1,102 @@ +/* ────────────────────────────────────────────────────────────── + Shared chassis for the OG cards. + + Cards are authored as real 1200x630 HTML so they use the same fonts + and palette as the site and can be regenerated with a screenshot + instead of a design session. Tokens mirror site.css's ink ground — + duplicated deliberately, because a card is a frozen artifact and + must not change shape when the site's CSS does. + ────────────────────────────────────────────────────────────── */ + +@font-face { + font-family: "JetBrains Mono"; + font-style: normal; + font-weight: 400 700; + font-display: block; + src: url("/assets/jbm.woff2?v=2") format("woff2"); +} + +:root { + --bg: #0c0c12; + --panel: #111119; + --mass: #1a1a24; + --ink: #e9e6f2; + --dim: #a29db8; + --faint: #736e8a; + --line: #1a1a24; + --line2: #2a2839; + --spot: #cba6f7; + --run: #5fae74; + --wait: #d3a027; + --idle: #5a615c; + --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace; + --disp: "Archivo", system-ui, -apple-system, "Segoe UI", sans-serif; +} + +* { box-sizing: border-box; } +/* On a card page the document IS the card, so a full-page capture is the + finished asset at exactly 1200x630 whatever the window size. Scoped to + .og so the comparison viewer, which loads this same file for the tokens, + isn't clamped to card dimensions. */ +html.og, body.og { + margin: 0; padding: 0; background: var(--bg); + width: 1200px; height: 630px; overflow: hidden; +} + +.card { + position: relative; + width: 1200px; + height: 630px; + overflow: hidden; + background: var(--bg); + color: var(--ink); + font-family: var(--mono); + -webkit-font-smoothing: antialiased; + background-image: + linear-gradient(var(--line) 1px, transparent 1px), + linear-gradient(90deg, var(--line) 1px, transparent 1px); + background-size: 60px 60px; +} + +/* the ram as mass, bleeding off the right edge */ +.card .ram { + background: currentColor; + -webkit-mask: url("/assets/ram.svg") center/contain no-repeat; + mask: url("/assets/ram.svg") center/contain no-repeat; + display: block; +} +.card .mass { + position: absolute; + right: -110px; + top: 50%; + transform: translateY(-50%); + width: 620px; + height: 620px; + color: var(--mass); + z-index: 0; +} +.card .in { position: relative; z-index: 1; height: 100%; } + +/* lockup */ +.lockup { display: flex; align-items: center; gap: 14px; } +.lockup .ram { width: 38px; height: 38px; color: var(--ink); } +.lockup b { + font-family: var(--disp); + font-weight: 900; + font-size: 34px; + letter-spacing: -0.045em; +} + +.eyebrow { + display: flex; + align-items: center; + gap: 18px; + color: var(--spot); + font-size: 19px; + letter-spacing: 0.3em; + text-transform: uppercase; +} +.eyebrow s { width: 56px; height: 2px; background: var(--spot); text-decoration: none; } + +/* keep the safe area generous: some surfaces crop toward square */ +.pad { padding: 54px 62px; } diff --git a/website/css/site.css b/website/css/site.css new file mode 100644 index 00000000..19506d43 --- /dev/null +++ b/website/css/site.css @@ -0,0 +1,992 @@ +/* ────────────────────────────────────────────────────────────── + site.css — herdr.dev chassis. Loaded after style.css, which is + kept only for the session mock (.herdr-ui-mock) and its tokens. + + Ink is the default ground and the presentation layer; paper is an opt-in + preference shared with the docs. html[data-palette] themes the mock only + and follows the ground, so page and plate always share one mauve. + ────────────────────────────────────────────────────────────── */ + +/* Ink is the presentation layer: terminals are dark and the audience has + internalised that, so it is the default and never follows the system. + Paper is a preference the reader opts into, shared with the docs through + Starlight's own `starlight-theme` key so one choice covers both surfaces. + The mock follows the ground, and the mauve shifts with it — one mauve in + view at any time, never two. */ +html, +html[data-mode="ink"] { + /* The ground is near-neutral by test, not by taste. A blue-violet dark at + 20% saturation measurably matched the old site's hue, but read as harder + — saturated blue at low luminance softens the edges of light text, and + the eye has least acuity there. Draining it to 6% won a side-by-side. + Colour now lives in the mauve accent, which pops harder against neutral. + Contrast: ink 14.7, dim 9.3, faint 5.3 — all above AA. */ + --bg: #17171a; --panel: #1e1e22; --mass: #26262b; + --ink: #eae8ee; --dim: #bdb9c9; --faint: #8e8a98; + --line: #26262b; --line2: #35353d; + --spot: #cba6f7; --spot-ink: #17171a; + --grid: #202024; +} +html[data-mode="paper"] { + --st-done: #2aa198; + --bg: #efece5; --panel: #e7e3da; --mass: #ddd8cc; + --ink: #15140f; --dim: #55534a; --faint: #86826f; + --line: #e2ded4; --line2: #cbc5b6; + --spot: #8839ef; --spot-ink: #fff; + --grid: #e4e0d6; +} +:root { + --run: #5fae74; --wait: #d3a027; --idle: #5a615c; --done: #6f6a86; + /* The TUI's own mapping, not a palette invented for the website: + src/ui/status.rs state_dot() paints blocked with p.red, working with + p.yellow and idle with p.green once seen, and the default palette in + src/app/state.rs is Catppuccin Mocha — the same #f38ba8 / #f9e2af / + #a6e3a1 that style.css already defines for the plate. Aliasing rather + than copying keeps the legend, the plate and the app in step, and lets + all three follow the ground when it changes. */ + --st-blocked: var(--red); + --st-working: var(--yellow); + --st-idle: var(--green); + /* done is p.teal, and style.css has no teal token to alias, so it is set + per ground: Catppuccin Mocha teal on ink, Solarized cyan on paper */ + --st-done: #94e2d5; + --disp: "Archivo", system-ui, -apple-system, "Segoe UI", sans-serif; + /* Sentences are not code. Mono removes the word-shape cues you read by, so + it costs speed in prose and gets worse below ~14px. Inter is already what + the docs declare, so using it here makes the two surfaces agree. */ + --body: "Inter", system-ui, -apple-system, "Segoe UI", Helvetica, sans-serif; + --gut: 34px; +} + +/* The plate's chrome takes the page's spot, so there is exactly one mauve + in view. Agent identity colours inside the panes (claude coral, opencode + purple, codex) stay native. */ +/* The plate's agent states follow the TUI, not the page accent. `done` reads + var(--accent) in style.css, and aliasing accent to the page mauve painted a + done agent purple — a colour Herdr's sidebar never shows. src/ui/status.rs + state_dot() is the authority: blocked red, working filled yellow, done + filled teal, idle hollow green, unknown grey. */ +body.site .mock-agent.done > span, +body.site .mock-agent.done small { color: var(--st-done); } + +html[data-palette] { + --accent: var(--spot); + --term-tab-active-bg: var(--spot); + --term-tab-active-text: var(--spot-ink); + --term-prompt: var(--spot); + --term-blue: var(--spot); +} + +/* The ported plugins page still reads style.css tokens directly, and style.css + only knows the 20 mock palettes — so catppuccin's violet #181825 was reaching + the publish card, which is why that block stayed tinted after the ground went + neutral. Bridge the legacy names onto the neutral scale at the page wrapper. + Scoped to this page, not to body.site, because the mock reads --radius-* for + its tabs and panes and must keep its own corners. Terminal tokens are left + alone on purpose: the plate stays catppuccin. */ +body.site .plugins-page { + --bg-elevated: var(--panel); + --ink-soft: var(--dim); + --muted: var(--faint); + --muted-2: var(--faint); + --line-strong: var(--line2); + --accent-soft: color-mix(in srgb, var(--spot) 8%, transparent); + /* the language is hairline cells in a sheet; nothing here is a rounded box */ + --radius-sm: 0; --radius-md: 0; --radius-lg: 0; +} + +body.site { + margin: 0; background: var(--bg); color: var(--ink); + font-family: var(--body); font-size: 14.5px; line-height: 1.65; + -webkit-font-smoothing: antialiased; + background-image: + linear-gradient(var(--grid) 1px, transparent 1px), + linear-gradient(90deg, var(--grid) 1px, transparent 1px); + background-size: 72px 72px; + overflow-x: clip; + /* exactly one grid module of ground below the sheet */ + padding-bottom: 72px; +} +body.site ::selection { background: var(--spot); color: var(--spot-ink); } +body.site a { color: inherit; } +body.site .ram { + background: currentColor; display: block; + -webkit-mask: url("/assets/ram.svg") center/contain no-repeat; + mask: url("/assets/ram.svg") center/contain no-repeat; +} +body.site .hd-frame { + max-width: 1440px; margin: 0 auto; background: var(--bg); position: relative; + border-left: 1px solid var(--line2); border-right: 1px solid var(--line2); + /* close the sheet: without this the two vertical rules stopped in mid-air on + the last pixel of the document, which read as a crop rather than an end */ + border-bottom: 1px solid var(--line2); +} + +/* ── nav ── */ +body.site .hd-nav { + display: flex; align-items: center; gap: 24px; + height: 60px; padding: 0 var(--gut); border-bottom: 1px solid var(--line2); +} +body.site .lock { display: flex; align-items: center; gap: 10px; text-decoration: none; } +body.site .lock .ram { width: 25px; height: 25px; color: var(--ink); } +body.site .lock b { font-family: var(--disp); font-weight: 900; font-size: 20px; letter-spacing: -.045em; color: var(--ink); } +body.site .hd-nav .sp { flex: 1; } +body.site .hd-nav a.lnk { color: var(--dim); text-decoration: none; font-size: 11.5px; letter-spacing: .07em; text-transform: uppercase; } +body.site .hd-nav a.lnk:hover { color: var(--ink); } +body.site .hd-nav .btn { border: 1px solid var(--line2); padding: 7px 15px; color: var(--ink); text-decoration: none; font-size: 11.5px; letter-spacing: .07em; text-transform: uppercase; } +body.site .hd-nav .btn:hover { background: var(--spot); color: var(--spot-ink); border-color: var(--spot); } +/* Square hairline cells, the same box as the mode toggle and the Install + button — so the nav reads as one row of cells rather than text plus chrome. + The GitHub one widens to carry its count; the count is real, baked at build + and refreshed live, so it is proof rather than decoration. */ +body.site .hd-nav .ico { + display: inline-flex; align-items: center; justify-content: center; + width: 30px; height: 30px; flex: 0 0 auto; + border: 1px solid var(--line2); color: var(--dim); text-decoration: none; +} +body.site .hd-nav .ico.wide { width: auto; gap: 8px; padding: 0 10px; } +body.site .hd-nav .ico svg { width: 14px; height: 14px; fill: currentColor; } +body.site .hd-nav .ico b { + font-family: var(--mono); font-weight: 400; font-size: 11.5px; + letter-spacing: .04em; font-variant-numeric: tabular-nums; +} +/* an empty count must not leave a lopsided cell */ +body.site .hd-nav .ico.wide:has(b:empty) { width: 30px; gap: 0; padding: 0; } +body.site .hd-nav .ico:hover { color: var(--spot); border-color: var(--spot); } + +/* the menu cell is the same box as the icons and Install, so the nav stays one + row of cells; it only exists below the nav's own breakpoint */ +body.site .hd-nav .menu { + display: none; align-items: center; justify-content: center; gap: 4px; + flex-direction: column; width: 30px; height: 30px; flex: 0 0 auto; padding: 0; + border: 1px solid var(--line2); background: none; color: var(--dim); cursor: pointer; +} +body.site .hd-nav .menu span { display: block; width: 13px; height: 1px; background: currentColor; } +body.site .hd-nav .menu[aria-expanded="true"] { color: var(--spot); border-color: var(--spot); } + +/* The sheet is a continuation of the nav rather than a modal: same hairline + rows, same cell language, dropped under the bar it belongs to. */ +body.site .sheet { display: none; border-bottom: 1px solid var(--line2); } +body.site .sheet a { + display: block; padding: 15px var(--gut); text-decoration: none; color: var(--ink); + font-size: 12px; letter-spacing: .07em; text-transform: uppercase; + border-bottom: 1px solid var(--line); +} +body.site .sheet a:last-of-type { color: var(--dim); } +body.site .sheet a:active { background: color-mix(in srgb, var(--spot) 8%, transparent); } +body.site .sheet-ground { + display: flex; align-items: center; justify-content: space-between; gap: 16px; + padding: 13px var(--gut); +} +body.site .sheet-ground > span { + color: var(--faint); font-size: 10.5px; letter-spacing: .2em; text-transform: uppercase; +} + +body.site .mode { display: flex; border: 1px solid var(--line2); } +body.site .mode button { + background: none; border: 0; color: var(--faint); cursor: pointer; + font-family: var(--mono); font-size: 10px; letter-spacing: .16em; + text-transform: uppercase; padding: 7px 10px; +} +body.site .mode button:hover { color: var(--ink); } +body.site .mode button[aria-pressed="true"] { background: var(--ink); color: var(--bg); } + +/* ── hero ── + Single column. The ram is mass bleeding off the right edge, not an + object sitting in a second column with nothing to do. */ +body.site .hd-hero { position: relative; overflow: hidden; padding: 68px var(--gut) 56px; } +body.site .ghost { + position: absolute; right: -14%; top: 50%; transform: translateY(-50%); + width: min(880px, 62vw); height: min(880px, 62vw); + color: var(--mass); z-index: 0; pointer-events: none; +} +body.site .hd-hero .in { position: relative; z-index: 1; } +body.site .hd-eyebrow { display: flex; align-items: center; gap: 14px; color: var(--spot); font-size: 11px; letter-spacing: .3em; text-transform: uppercase; } +body.site .hd-eyebrow s { width: 42px; height: 1px; background: var(--spot); text-decoration: none; } +body.site h1 { + font-family: var(--disp); font-weight: 900; letter-spacing: -.055em; line-height: .88; + /* wide enough that each sentence takes one line and the type reaches + across the frame — a narrow measure is what left the right side empty */ + font-size: clamp(38px, 6.6vw, 132px); margin: 24px 0 0; max-width: 20ch; color: var(--ink); +} +body.site h1 em { font-style: normal; color: var(--spot); } +body.site .lede { color: var(--dim); font-size: 15px; line-height: 1.75; max-width: 50ch; margin: 32px 0 0; } +body.site .lede b { color: var(--ink); font-weight: 400; } + +/* one command, one link — every other install path lives on /install/ */ +body.site .go { display: flex; align-items: center; gap: 22px; margin-top: 38px; flex-wrap: wrap; } +body.site .hd-cmd { + display: inline-flex; align-items: center; border: 1px solid var(--line2); + background: color-mix(in srgb, var(--bg) 80%, transparent); backdrop-filter: blur(6px); +} +body.site .hd-cmd code { padding: 14px 19px; font-size: 13.5px; font-family: var(--mono); } +body.site .hd-cmd code b { color: var(--spot); font-weight: 400; margin-right: 11px; } +body.site .hd-cmd button { + padding: 14px 16px; border: 0; border-left: 1px solid var(--line2); background: none; cursor: pointer; + color: var(--faint); font-family: var(--mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; +} +body.site .hd-cmd button:hover { color: var(--ink); } +body.site .meta { margin-top: 20px; color: var(--faint); font-size: 11.5px; letter-spacing: .04em; } +body.site .meta a { color: var(--spot); text-decoration: none; } +body.site .meta a:hover { text-decoration: underline; } + +/* ── numbers ── + Every value is real: baked at build from the R2 snapshots, refreshed live + and counted up if it moved since the last deploy. */ +body.site .strip { display: grid; grid-template-columns: repeat(4, 1fr); border-top: 1px solid var(--line2); border-bottom: 1px solid var(--line2); } +body.site .stat { + position: relative; display: block; overflow: hidden; text-decoration: none; + padding: 24px var(--gut) 26px; border-right: 1px solid var(--line); +} +body.site .stat:last-child { border-right: 0; } +body.site .stat:hover { background: color-mix(in srgb, var(--spot) 5%, transparent); } +body.site .stat b { + position: relative; z-index: 1; display: block; + font-family: var(--disp); font-weight: 900; font-size: clamp(26px, 3vw, 40px); + letter-spacing: -.045em; line-height: 1; color: var(--ink); + font-variant-numeric: tabular-nums; +} +body.site .stat span { + position: relative; z-index: 1; + display: flex; align-items: center; gap: 9px; margin-top: 11px; + color: var(--faint); font-size: 10px; letter-spacing: .2em; text-transform: uppercase; +} +body.site .stat:hover span { color: var(--dim); } +body.site .stat .ic { + width: 14px; height: 14px; flex: 0 0 14px; + fill: currentColor; stroke: currentColor; color: var(--spot); opacity: .85; +} +body.site .stat .ic [fill="none"] { fill: none; } +body.site .stat i.live { + width: 6px; height: 6px; flex: 0 0 6px; background: var(--run); + animation: site-pulse 2.2s ease-in-out infinite; +} +@keyframes site-pulse { 0%,100% { opacity: .25 } 50% { opacity: 1 } } + + +@media (prefers-reduced-motion: reduce) { + body.site .stat i.live { animation: none; } +} + +/* ── FIG 1 · the plate ── + Inset with paper around it and a hairline border, so it reads as a plate + resting on the sheet rather than a hole punched through it. */ +body.site .fig { padding: 40px var(--gut) 46px; border-bottom: 1px solid var(--line2); } +body.site .fighd { + display: flex; align-items: baseline; gap: 16px; padding: 0 0 11px; + color: var(--faint); font-size: 10.5px; letter-spacing: .2em; text-transform: uppercase; +} +body.site .fighd i { font-style: normal; color: var(--spot); letter-spacing: .1em; } +body.site .fighd .r { margin-left: auto; text-align: right; } +body.site .figcap { + margin: 13px 0 0; color: var(--faint); font-size: 11.5px; line-height: 1.7; max-width: 74ch; +} +body.site .figcap b { color: var(--dim); font-weight: 400; } +body.site .plate { + border: 1px solid var(--line2); overflow: hidden; + aspect-ratio: 1438 / 620; background: var(--term-bg); +} + +body.site .plate .hero-product-shot { height: 100%; margin: 0; padding: 0; } +body.site .plate .hero-product-shot figcaption { display: none; } +body.site .plate .herdr-ui-mock { + max-width: none; width: 100%; height: 100%; aspect-ratio: auto; + border: 0; border-radius: 0; box-shadow: none; + font-size: clamp(.6rem, .92cqw, .95rem); +} +/* .mock-main only sets min-width: 0, so as a grid item it grows to its + content height and the plate never clamps the pane */ +body.site .plate .mock-main { min-height: 0; } +body.site .plate .mock-panes { grid-template-rows: minmax(0, 1fr); } + +/* ── router rows: signposts, not pillars ── + Stacked so each gets room and the destination is the heaviest thing in + the row — these are for going somewhere, not for persuading. */ +body.site .caps { border-bottom: 1px solid var(--line2); } +/* The whole row is the link, so the destination button is gone and the marks + move to the right — which is also where the frame had a half-empty column + doing nothing. The arrow is the affordance and it is always visible, since a + row that only reveals it on hover gives a reader nothing to go on. */ +/* Three columns: index, text at its measure, evidence. The row used to be text + plus a lot of nothing, and making the whole row a hover target drew a box + around the nothing. Every row now ends in something real — the session the + plate above shows, the agent states the copy names, the commands agents run, + the agents themselves — so the width is used rather than merely spanned. */ +body.site .caps > .cap { + display: grid; grid-template-columns: 130px minmax(0, 58ch) minmax(0, 1fr); + gap: 0 40px; align-items: start; + padding: 26px var(--gut); border-bottom: 1px solid var(--line); + text-decoration: none; cursor: pointer; +} +body.site .caps > .cap:last-child { border-bottom: 0; } +body.site .caps > .cap:hover { background: color-mix(in srgb, var(--spot) 4%, transparent); } +body.site .caps > .cap:hover h3 { color: var(--spot); } +/* .arw, not .go — .go is already the hero's button group and carries a + 38px top margin that pushed the arrow to the bottom of the column. + It rides the headline rather than sitting in the mark column: with the + marks moved left, an arrow down there would point away from the text and + away from the destination. On the headline it sits where the eye lands. */ +/* Inline, not inline-block: an atomic inline carries a wrap opportunity before + it whatever the whitespace does, so at narrow widths the arrow dropped to a + line of its own under the heading. As plain inline text the no-break space in + the markup binds it to the last word. The hover nudge moves the margin for + the same reason — transforms don't apply to a non-replaced inline. */ +body.site .caps .arw { + margin-left: 4px; vertical-align: 2px; + font-family: var(--body); font-weight: 400; font-size: 15px; line-height: 1; + color: var(--faint); transition: color .12s ease, margin-left .12s ease; +} +body.site .caps > .cap:hover .arw { color: var(--spot); margin-left: 8px; } +/* The marks lead the row. On the right they sat past the end of a 62ch + measure and read as debris pinned to the frame edge; on the left they are a + margin the text hangs off, and the row's ragged right edge is just how + columns end. The index is set large for mass but kept low in contrast — it + is texture and position, not a second headline competing with the h3. */ +body.site .caps .n { + display: flex; flex-direction: column; gap: 13px; align-items: flex-start; + align-self: start; line-height: 1; +} +body.site .caps .n > span:first-child { + font-family: var(--disp); font-weight: 900; font-size: 44px; + letter-spacing: -.05em; color: var(--mass); transition: color .12s ease; +} +body.site .caps > .cap:hover .n > span:first-child { color: var(--line2); } + +/* One frame for all four, at one width, flush to the right gutter — so the + evidence reads as the same kind of object in every row and the column has a + straight edge instead of a ragged one. The frame is the terminal's, moved up + from .tape so the typing line no longer owns a border the others lack. */ +body.site .caps .ev { + align-self: center; justify-self: end; + width: 100%; max-width: 520px; + border: 1px solid var(--line2); background: var(--panel); + padding: 13px 15px; + font-family: var(--mono); font-size: 11.5px; color: var(--faint); +} +body.site .caps .ev-row { display: flex; align-items: center; gap: 14px; padding: 6px 0; } +body.site .caps .ev-row > span { color: var(--dim); } +body.site .caps .ev-row b { + margin-left: auto; font-weight: 400; + display: inline-flex; align-items: center; gap: 9px; +} +body.site .caps .ev-row i { display: block; width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; } +body.site .caps .ev-note { margin-top: 9px; line-height: 1.7; } + +/* ── 05, the tease ── + Inert by design: no link, no pointer, no hover reveal, no arrow. It reads + the same to everyone on every device, which is the only version of this + that a phone gets too. The blocks are set in mono because JetBrains Mono + actually carries U+2580–259F; in the display face they would fall back to + whatever the system happens to have. */ +body.site .caps .cap-soon { cursor: default; } +body.site .caps .cap-soon .mask { font-family: var(--mono); color: var(--line2); letter-spacing: -.02em; } +body.site .caps .cap-soon .tape { color: var(--faint); } +/* The line is fully redacted, so there is nothing being typed. A caret would + imply a live prompt and put the one moving thing on the page next to the row + that is deliberately saying nothing. */ +body.site .caps .cap-soon .tape > span { border-right: 0; animation: none; } +/* available to a screen reader, never on screen */ +body.site .sr { + position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; + overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; +} + +/* green live dot: "this is up right now" */ +body.site .caps .beat { + display: block; width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; + background: var(--run); animation: site-pulse 2.2s ease-in-out infinite; +} +/* blocked, working, idle — traffic-light order, and all three sit still: they + are a legend, and a legend that moves reads as an alarm. Only the heartbeat + in the row above animates. */ +body.site .caps .ev-row .b { background: var(--st-blocked); } +body.site .caps .ev-row .w { background: var(--st-working); } +/* hollow, because state_dot() draws idle as ○ and every other state as ● — + the fill is part of the signal, not decoration */ +body.site .caps .ev-row .i { background: none; box-shadow: inset 0 0 0 2px var(--st-idle); } + +/* the typing line. The frame lives on .ev now, so this is just the line — + still nowrap and clipped, so a long command can never reflow the box. */ +body.site .caps .tape { + display: flex; align-items: baseline; gap: 9px; margin: 0; + color: var(--dim); min-height: 19px; overflow: hidden; white-space: nowrap; +} +body.site .caps .tape b { color: var(--spot); font-weight: 400; } +/* The caret is the span's own right border, not a sibling box. As a sibling it + sat at the shrunk span's edge while the text overflowed past it, which drew + the caret on top of the words — and a block sibling can't sit on the text + baseline. A border rides the line box, so it lands after the last character + at the right height by construction. */ +/* direct child only: row 05's line wraps a nested redaction block, and a + descendant selector gave that block a caret of its own mid-line */ +body.site .caps .tape > span { + flex: 0 1 auto; min-width: 0; + border-right: 7px solid var(--dim); + animation: site-caret 1.05s steps(1) infinite; +} +@keyframes site-caret { 0%, 49% { border-color: var(--dim) } 50%, 100% { border-color: transparent } } + +/* Masked, not inlined: the source files carry their own brand fills, and one + of them is white — which would vanish on paper. Silhouettes in one colour + also stop the row turning into a logo salad. */ +/* two columns so eight entries stay a compact block rather than a tall list + that would tower over the other three evidence boxes */ +body.site .caps .herd { + display: grid; grid-template-columns: 1fr 1fr; gap: 11px 24px; margin: 0; +} +body.site .caps .herd span { display: flex; align-items: center; gap: 11px; color: var(--dim); } +body.site .caps .herd i { + width: 15px; height: 15px; flex: 0 0 auto; background: var(--faint); + -webkit-mask: var(--m) center/contain no-repeat; + mask: var(--m) center/contain no-repeat; +} +body.site .caps > .cap:hover .herd i { background: var(--dim); } + +@media (prefers-reduced-motion: reduce) { + body.site .caps .beat, + body.site .caps .tape > span { animation: none; } +} +body.site .caps h3 { margin: 0; font-family: var(--disp); font-weight: 800; letter-spacing: -.03em; font-size: 21px; color: var(--ink); } +body.site .caps p { margin: 9px 0 0; color: var(--dim); font-size: 13px; line-height: 1.7; max-width: 62ch; } +body.site .caps h3 { transition: color .12s ease; } + +/* ── page head: shared by blog and any other framed page ── */ +body.site .ph { padding: 68px var(--gut) 44px; border-bottom: 1px solid var(--line2); } +/* a wide frame left the right half of the head empty — give it a slot with + something real in it rather than widening the measure past readability */ +body.site .ph-split { + display: grid; grid-template-columns: minmax(0, 1fr) auto; + gap: 60px; align-items: end; +} +body.site .ph-side { text-align: right; } +body.site .ph-side b { + display: block; font-family: var(--disp); font-weight: 900; + font-size: clamp(40px, 4.6vw, 68px); letter-spacing: -.05em; line-height: 1; color: var(--ink); +} +body.site .ph-side span { + display: block; margin-top: 10px; + color: var(--faint); font-size: 10px; letter-spacing: .2em; text-transform: uppercase; +} +body.site .ph-side a { display: inline-block; margin-top: 18px; color: var(--spot); text-decoration: none; font-size: 12px; } +body.site .ph-side a:hover { text-decoration: underline; } +body.site .ph h1 { + font-family: var(--disp); font-weight: 900; letter-spacing: -.05em; line-height: .92; + font-size: clamp(34px, 5vw, 72px); margin: 22px 0 0; max-width: 18ch; color: var(--ink); +} +body.site .ph p { margin: 22px 0 0; color: var(--dim); font-size: 14.5px; line-height: 1.75; max-width: 56ch; } + +body.site .ph code { + color: var(--ink); background: var(--panel); + border: 1px solid var(--line2); padding: 1px 6px; font-size: 12px; + /* a chip split across two lines reads as a rendering fault, not a wrap */ + white-space: nowrap; +} +body.site .ph-actions { display: flex; gap: 12px; margin-top: 30px; flex-wrap: wrap; } +body.site .btn-solid, body.site .btn-ghost { + text-decoration: none; padding: 12px 18px; font-size: 11.5px; + letter-spacing: .07em; text-transform: uppercase; border: 1px solid var(--line2); +} +body.site .btn-solid { background: var(--spot); border-color: var(--spot); color: var(--spot-ink); } +body.site .btn-ghost { color: var(--ink); } +body.site .btn-ghost:hover { background: var(--spot); border-color: var(--spot); color: var(--spot-ink); } + +/* The ported pages still carry style.css's .button classes on the publish + links and the load-more control. Given the site language here rather than + swapped in the markup, so all three stay in step. The solid one rendered as + an empty light slab because `body.site a { color: inherit }` outranks + .button-primary's own `color`, so the label took the card's near-white. */ +body.site .button { + min-height: 0; padding: 12px 18px; border-radius: 0; + border: 1px solid var(--line2); font-family: var(--mono); font-weight: 400; + font-size: 11.5px; letter-spacing: .07em; text-transform: uppercase; +} +body.site .button-primary { background: var(--spot); border-color: var(--spot); color: var(--spot-ink); } +body.site .button-primary:hover { background: var(--ink); border-color: var(--ink); color: var(--bg); } +body.site .button-secondary { background: none; color: var(--ink); } +body.site .button-secondary:hover { background: var(--spot); border-color: var(--spot); color: var(--spot-ink); } + +/* ── plugins ── + The markup is the original page's; only the language is restyled, so the + live fetch, search and sort behaviour is untouched. ── */ +/* not flex: a wrapping flex row put the status dot on a line of its own */ +body.site .plugins-trust { + display: block; margin: 20px 0 0; + color: var(--faint); font-size: 12px; line-height: 1.7; max-width: 78ch; +} +body.site .plugins-trust .status-dot { + display: inline-block; width: 7px; height: 7px; border-radius: 50%; + margin-right: 9px; vertical-align: 1px; background: var(--idle); +} +body.site .plugins-trust a { color: var(--spot); text-decoration: none; } +body.site .plugins-controls { + display: flex; gap: 16px; align-items: center; flex-wrap: wrap; + padding: 20px var(--gut); border-bottom: 1px solid var(--line2); +} +body.site .plugins-search-wrap { position: relative; flex: 1; min-width: 240px; } +body.site .plugins-search-icon { + position: absolute; left: 14px; top: 50%; transform: translateY(-50%); + color: var(--spot); font-size: 13px; +} +body.site .plugins-search { + width: 100%; background: var(--panel); border: 1px solid var(--line2); border-radius: 0; + color: var(--ink); font-family: var(--mono); font-size: 13px; padding: 12px 14px 12px 34px; +} +body.site .plugins-search::placeholder { color: var(--faint); } +body.site .plugins-search:focus { outline: 0; border-color: var(--spot); } +body.site .plugins-sort-wrap { display: flex; align-items: center; gap: 10px; } +body.site .plugins-sort-label { color: var(--faint); font-size: 10px; letter-spacing: .2em; text-transform: uppercase; } +body.site .plugins-sort { + background: var(--panel); border: 1px solid var(--line2); border-radius: 0; + color: var(--ink); font-family: var(--mono); font-size: 12.5px; + padding: 11px 34px 11px 13px; cursor: pointer; + appearance: none; -webkit-appearance: none; + background-image: linear-gradient(45deg, transparent 50%, var(--faint) 50%), + linear-gradient(135deg, var(--faint) 50%, transparent 50%); + background-position: calc(100% - 18px) 50%, calc(100% - 13px) 50%; + background-size: 5px 5px, 5px 5px; + background-repeat: no-repeat; +} +body.site .plugins-sort:focus { outline: 0; border-color: var(--spot); } +/* the count sits flush against the frame otherwise */ +body.site .plugins-status { margin-left: auto; padding: 0; white-space: nowrap; } +body.site .plugins-metabar { border-bottom: 1px solid var(--line); } +body.site .plugins-meta, +body.site .plugins-status { + margin: 0; padding: 13px var(--gut); color: var(--faint); font-size: 10.5px; + letter-spacing: .18em; text-transform: uppercase; +} +body.site .plugins-status { border-top: 1px solid var(--line); } +/* the old page wraps every block in .section, which brings its own vertical + padding, a top border and a horizontal inset — so blocks drifted further + right the deeper they were nested. Neutralise it and let each block own + its gutter, aligned with the page head. */ +body.site .plugins-page .section { + padding: 0; border-top: 0; max-width: none; margin: 0; +} + +body.site .plugins-grid { + display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + gap: 14px; padding: 26px var(--gut) 34px; +} +/* cards are hairline cells in a sheet, not filled boxes floating on it */ +body.site .plugin-card { + display: flex; flex-direction: column; min-width: 0; + padding: 20px 22px 22px; text-decoration: none; + background: none; border: 1px solid var(--line2); border-radius: 0; + text-transform: none; letter-spacing: normal; +} +body.site .plugin-card:hover { + background: color-mix(in srgb, var(--spot) 5%, transparent); border-color: var(--spot); +} +body.site .plugin-card-head { display: flex; align-items: baseline; gap: 12px; } +body.site .plugin-title { min-width: 0; font-size: 15px; letter-spacing: -.01em; } +body.site .plugin-owner { color: var(--faint); } +body.site .plugin-name { color: var(--ink); font-weight: 500; } +body.site .plugin-card:hover .plugin-name { color: var(--spot); } +body.site .plugin-go { margin-left: auto; color: var(--faint); font-size: 14px; } +body.site .plugin-card:hover .plugin-go { color: var(--spot); } +/* clamp to two lines so every cell has the same shape — the feed's own + ellipsis truncated at a character count, which made the rows ragged */ +body.site .plugin-desc { + margin: 11px 0 0; color: var(--dim); font-size: 12.5px; line-height: 1.65; + text-transform: none; letter-spacing: normal; + display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; + overflow: hidden; min-height: calc(2 * 1.65em); +} +body.site .plugin-meta-row { + display: flex; align-items: center; gap: 16px; flex-wrap: wrap; + margin-top: auto; padding-top: 18px; + color: var(--faint); font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase; +} +body.site .plugin-stat { display: flex; align-items: center; gap: 7px; } +body.site .plugin-star { color: var(--spot); } +body.site .plugin-lang-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; } +body.site .plugin-updated { margin-left: auto; } +/* .plugins-more is the button, not a wrapper — given section padding it + inflated into a floating slab. It's the foot of a long list, so make it the + full-width row a list foot should be rather than a box parked in the middle. */ +body.site .plugins-more { + display: block; width: 100%; margin: 0; padding: 22px var(--gut); + border: 0; border-top: 1px solid var(--line2); background: none; + color: var(--dim); text-align: center; cursor: pointer; +} +body.site .plugins-more:hover { + background: color-mix(in srgb, var(--spot) 5%, transparent); color: var(--spot); +} +/* the rule above outranks the page's own .plugins-more[hidden] guard, so the + exhausted button stayed on screen; restate the guard at this weight */ +body.site .plugins-more[hidden] { display: none; } +body.site .plugins-publish { padding: 40px var(--gut) 48px; border-bottom: 1px solid var(--line2); } +body.site .plugins-publish-card { border: 1px solid var(--line2); padding: 26px 28px; } +/* the card's heading is an h3, so the h2 rule missed it and the page's own + mono declaration stood — the one section heading not in Archivo */ +body.site .plugins-publish h2, +body.site .plugins-publish h3 { + margin: 0; font-family: var(--disp); font-weight: 900; letter-spacing: -.035em; + font-size: 26px; color: var(--ink); +} +body.site .plugins-publish p { margin: 12px 0 0; color: var(--dim); font-size: 13px; line-height: 1.75; max-width: 70ch; } +body.site .plugins-publish-actions { display: flex; gap: 12px; margin-top: 22px; flex-wrap: wrap; } + +/* ── blog index ── */ +body.site .posts { border-bottom: 1px solid var(--line2); } +body.site .posts a { + display: grid; grid-template-columns: 132px minmax(0, 1fr) auto; + gap: 0 30px; align-items: baseline; text-decoration: none; + padding: 24px var(--gut); border-bottom: 1px solid var(--line); +} +body.site .posts a:last-child { border-bottom: 0; } +body.site .posts a:hover { background: color-mix(in srgb, var(--spot) 5%, transparent); } +body.site .posts time { color: var(--faint); font-size: 11px; letter-spacing: .12em; text-transform: uppercase; } +body.site .posts h2 { + margin: 0; font-family: var(--disp); font-weight: 800; letter-spacing: -.03em; + font-size: 21px; color: var(--ink); +} +body.site .posts p { margin: 8px 0 0; color: var(--dim); font-size: 12.5px; line-height: 1.7; max-width: 74ch; } +body.site .posts .go-arrow { color: var(--faint); font-size: 15px; } +body.site .posts a:hover .go-arrow { color: var(--spot); } + +/* ── article ── */ +/* The measure column is capped at the body's own 76ch. Left as 1fr it ate every + spare pixel and parked the sections list against the far edge, with the gap + between them growing without limit on a wide display. */ +body.site .article { + display: grid; grid-template-columns: minmax(0, 76ch) 224px; gap: 60px; + justify-content: center; + padding: 54px var(--gut) 76px; border-bottom: 1px solid var(--line2); align-items: start; +} +/* No sidebar means no second column, and the measure centres rather than + hugging the left gutter with the whole frame empty beside it. Elsewhere on + the site a short measure sits left because something else occupies the + right; here nothing does. */ +body.site .article-solo { grid-template-columns: minmax(0, 1fr); } +body.site .article-solo .article-body { margin-inline: auto; } +body.site .article-body { min-width: 0; max-width: 76ch; } +body.site .article-meta { + display: flex; gap: 16px; align-items: baseline; + color: var(--faint); font-size: 11px; letter-spacing: .14em; text-transform: uppercase; +} +body.site .article-meta a { color: var(--spot); text-decoration: none; } +body.site .article h1 { + font-family: var(--disp); font-weight: 900; letter-spacing: -.045em; line-height: .95; + font-size: clamp(30px, 3.6vw, 52px); margin: 18px 0 0; color: var(--ink); +} +body.site .article-lede { margin: 20px 0 0; color: var(--dim); font-size: 15px; line-height: 1.75; } +body.site .article-rule { margin: 34px 0 0; border: 0; border-top: 1px solid var(--line2); } + +/* prose: mono body, display headings, hairlines instead of boxes */ +body.site .prose { margin-top: 34px; color: var(--dim); font-size: 13.5px; line-height: 1.85; } +body.site .prose > * + * { margin-top: 20px; } +body.site .prose h2 { + font-family: var(--disp); font-weight: 800; letter-spacing: -.03em; font-size: 25px; + color: var(--ink); margin-top: 46px; padding-top: 18px; border-top: 1px solid var(--line); +} +body.site .prose h3 { font-family: var(--disp); font-weight: 700; font-size: 18px; color: var(--ink); margin-top: 32px; } +body.site .prose a { color: var(--spot); text-underline-offset: 3px; } +body.site .prose strong { color: var(--ink); font-weight: 500; } +body.site .prose code { + color: var(--ink); background: var(--panel); + border: 1px solid var(--line2); padding: 1px 6px; font-size: 12px; +} +body.site .prose pre { + background: var(--panel); border: 1px solid var(--line2); + padding: 16px 18px; overflow-x: auto; font-size: 12.5px; line-height: 1.7; +} +body.site .prose pre code { background: none; border: 0; padding: 0; font-size: inherit; } +body.site .prose ul, body.site .prose ol { padding-left: 22px; } +body.site .prose li + li { margin-top: 8px; } +body.site .prose blockquote { + border-left: 2px solid var(--spot); padding-left: 18px; margin-left: 0; color: var(--faint); +} +body.site .prose img { max-width: 100%; border: 1px solid var(--line2); } +body.site .prose table { width: 100%; border-collapse: collapse; font-size: 12.5px; } +body.site .prose th, body.site .prose td { text-align: left; padding: 9px 12px; border: 1px solid var(--line); } +body.site .prose th { color: var(--ink); font-weight: 500; } + +/* ── post figures ── + Diagrams are built from the site's own tokens rather than exported as PNGs. + They switch with ink and paper, stay sharp at any density, and a diagram of + the sidebar stops being a lie the next time the sidebar changes. */ +body.site .pf { margin: 34px 0 0; } +body.site .pf-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; } +body.site .pf-panel { border: 1px solid var(--line2); background: var(--panel); min-width: 0; } +body.site .pf-label { + display: flex; justify-content: space-between; gap: 10px; + padding: 8px 12px; border-bottom: 1px solid var(--line2); + color: var(--faint); font-family: var(--mono); font-size: 10px; + letter-spacing: .16em; text-transform: uppercase; +} +body.site .pf-label b { color: var(--dim); font-weight: 400; } +body.site .pf-cap { margin: 12px 0 0; color: var(--faint); font-size: 11.5px; line-height: 1.6; } + +/* agent rows */ +body.site .pf-row { + display: grid; grid-template-columns: 14px minmax(0, 1fr); gap: 9px; align-items: baseline; + padding: 7px 12px; font-family: var(--mono); font-size: 12px; +} +body.site .pf-row + .pf-row { border-top: 1px solid var(--line); } +body.site .pf-row strong { color: var(--ink); font-weight: 400; } +body.site .pf-row small { display: block; color: var(--faint); font-size: 10.5px; } +body.site .pf-mark { line-height: 1; } +body.site .pf-row.working .pf-mark, body.site .pf-row.working small { color: var(--st-working); } +body.site .pf-row.blocked .pf-mark, body.site .pf-row.blocked small { color: var(--st-blocked); } +body.site .pf-row.done .pf-mark, body.site .pf-row.done small { color: var(--st-done); } +body.site .pf-row.idle .pf-mark, body.site .pf-row.idle small { color: var(--st-idle); } + +/* The left panel animates because the argument is the motion. Four braille + frames at 8fps is what 0.7.5 actually did, per working pane, forever. */ +body.site .pf-spin { position: relative; display: inline-block; width: 1ch; } +body.site .pf-spin i { position: absolute; left: 0; top: 0; font-style: normal; opacity: 0; animation: pf-frame .5s steps(1, end) infinite; } +body.site .pf-spin i:nth-child(2) { animation-delay: .125s; } +body.site .pf-spin i:nth-child(3) { animation-delay: .25s; } +body.site .pf-spin i:nth-child(4) { animation-delay: .375s; } +@keyframes pf-frame { 0%, 24.9% { opacity: 1; } 25%, 100% { opacity: 0; } } +@media (prefers-reduced-motion: reduce) { + body.site .pf-spin i { animation: none; } + body.site .pf-spin i:first-child { opacity: 1; } +} + +/* flow steps */ +body.site .pf-flow { padding: 14px 12px; font-family: var(--mono); font-size: 11.5px; } +body.site .pf-step { color: var(--dim); padding: 5px 0; } +body.site .pf-step b { color: var(--ink); font-weight: 400; } +body.site .pf-step s { display: block; color: var(--faint); text-decoration: none; font-size: 10.5px; padding-top: 2px; } +body.site .pf-arrow { color: var(--line2); padding: 1px 0; } +/* the two outcomes are alternatives, not the next two steps, so they get a + fork rather than another length of the same connector */ +body.site .pf-fork em { color: var(--faint); font-style: normal; margin-right: 8px; } +body.site .pf-fork s { padding-left: 34px; } +body.site .pf-stop { color: var(--st-done); } +body.site .pf-cost { color: var(--st-blocked); } + +/* bars */ +body.site .pf-bars { padding: 16px 14px; font-family: var(--mono); font-size: 11px; } +body.site .pf-bar-row + .pf-bar-row { margin-top: 16px; } +body.site .pf-bar-name { color: var(--dim); } +body.site .pf-bar { display: grid; grid-template-columns: 46px minmax(0, 1fr) 58px; gap: 8px; align-items: center; margin-top: 5px; } +body.site .pf-bar span:first-child { color: var(--faint); font-size: 10px; } +body.site .pf-bar i { display: block; height: 9px; min-width: 1px; } +body.site .pf-bar em { color: var(--faint); font-style: normal; text-align: right; } +body.site .pf-bar.old i { background: var(--line2); } +body.site .pf-bar.new i { background: var(--spot); } +body.site .pf-bar.new em { color: var(--dim); } + +@media (max-width: 560px) { + body.site .pf-2 { grid-template-columns: minmax(0, 1fr); } + body.site .pf-bar { grid-template-columns: 42px minmax(0, 1fr) 52px; } +} + +/* sticky section index */ +body.site .toc { position: sticky; top: 30px; } +body.site .toc p { margin: 0 0 12px; color: var(--faint); font-size: 10px; letter-spacing: .2em; text-transform: uppercase; } +body.site .toc nav { display: flex; flex-direction: column; } +body.site .toc a { + color: var(--dim); text-decoration: none; font-size: 12px; line-height: 1.5; + padding: 7px 0 7px 13px; border-left: 1px solid var(--line2); +} +body.site .toc a:hover { color: var(--ink); } +body.site .toc a[aria-current] { color: var(--spot); border-left-color: var(--spot); } + +/* ── close ── */ +body.site .end { position: relative; overflow: hidden; padding: 96px var(--gut) 104px; border-bottom: 1px solid var(--line2); } +/* The bleed is a fraction of the mark, not of the section. As a percentage it + resolved against .end's height, so the taller the copy wrapped the further + the ram sank — on a phone only its crown cleared the divider. */ +body.site .end .ram { + --ram-size: min(560px, 54vw); + position: absolute; right: -6%; bottom: calc(var(--ram-size) * -0.22); + width: var(--ram-size); height: var(--ram-size); color: var(--mass); z-index: 0; +} +body.site .end .in { position: relative; z-index: 1; } +body.site .end h2 { font-family: var(--disp); font-weight: 900; letter-spacing: -.05em; font-size: clamp(36px, 5.4vw, 70px); line-height: .93; margin: 0; max-width: 13ch; color: var(--ink); } +body.site .end p { margin: 20px 0 0; color: var(--dim); font-size: 13.5px; max-width: 52ch; line-height: 1.8; } + +/* ── footer ── + The drafting title block that used to sit here is gone. Three of its five + cells were marketing in a spec costume, it was identical on every page so it + said nothing after the first view, and its version was hardcoded in two + files. The two facts worth keeping now sit in the footer line, with the + version read from latest.json at build. */ +body.site .foot { padding: 22px var(--gut); border-top: 1px solid var(--line2); display: flex; gap: 22px; align-items: center; flex-wrap: wrap; color: var(--faint); font-size: 11.5px; } +body.site .foot a { color: var(--dim); text-decoration: none; } +body.site .foot a:hover { color: var(--ink); } +body.site .foot .ram { width: 15px; height: 15px; color: var(--faint); } +body.site .foot .fact { color: var(--faint); font-variant-numeric: tabular-nums; } +/* the outbound cluster sits right: site links and provenance on one side, ways + to reach the project on the other. Its own gap is tighter than the row's, so + two 14px marks read as a pair, not as two more items in the list. */ +body.site .foot .out { margin-left: auto; display: flex; align-items: center; gap: 16px; } +body.site .foot .ico { display: inline-flex; color: var(--faint); } +body.site .foot .ico svg { width: 14px; height: 14px; fill: currentColor; } +body.site .foot .ico:hover { color: var(--ink); } + +@media (max-width: 900px) { + :root { --gut: 20px; } + body.site { background-size: 40px 40px; padding-bottom: 40px; } + /* One row can't hold the lockup, the count, Install and three links at + 390px — those are 450px of cells in 350px. The links move into the sheet, + which also carries what phone width otherwise drops silently. */ + body.site .hd-nav a.lnk { display: none; } + /* the toggle is 103px of the bar and the sheet now carries it. Left in, it + overflowed the nav at 430–560px, which widened the document past the + viewport and left a dead strip down the right of every section. */ + body.site .hd-nav .mode { display: none; } + body.site .hd-nav .menu { display: inline-flex; } + body.site .sheet.open { display: block; } + /* two up: the column divider alone left the rows floating together, so the + four cells read as one block rather than a grid. The bottom row keeps its + edge from the strip's own border. */ + body.site .strip { grid-template-columns: 1fr 1fr; } + body.site .strip .stat { border-bottom: 1px solid var(--line); } + body.site .strip .stat:nth-last-child(-n + 2) { border-bottom: 0; } + body.site .strip .stat:nth-child(2n) { border-right: 0; } + /* the evidence drops under the text rather than competing for width */ + body.site .caps > .cap { grid-template-columns: 54px minmax(0, 1fr); gap: 0 18px; } + body.site .caps .n > span:first-child { font-size: 30px; } + /* stacked, the card butted straight into the last line of the paragraph — + the 20px was inside its border, so it padded the card rather than + separating it. Margin instead, and the interior goes back to even. */ + body.site .caps .ev { grid-column: 2; margin-top: 20px; } + body.site .fig { padding: 26px var(--gut) 30px; } + body.site .plate { aspect-ratio: auto; } + body.site .hd-hero { padding: 60px var(--gut) 52px; } + /* 224px of sections beside a phone measure left the title breaking every two + words. A sticky jump list is worth least on the screen it costs most, so + the post falls back to the same single column a heading-less post uses. */ + body.site .article { grid-template-columns: minmax(0, 1fr); } + body.site .article .toc { display: none; } +} + +/* Below this the nav's cells stop fitting, so they come off in order of how + little they're worth on a phone. Measured at the 24px gap: lock 88 + github + 88 + x 30 + toggle 103 + install 86 is 515px of cells, and a 480px viewport + leaves 440px. X goes first — it's the one thing the footer also carries. */ +@media (max-width: 560px) { + body.site .hd-nav { gap: 14px; } + body.site .hd-nav .ico[href*="x.com"] { display: none; } + /* the hint stops competing for the same line as the title and becomes the + line under it — right-aligned, it read as a second ragged column */ + body.site .fighd { flex-wrap: wrap; gap: 6px 16px; } + body.site .fighd .r { margin-left: 0; text-align: left; } + /* the close is mostly the watermark's room at this width */ + body.site .end { padding: 56px var(--gut) 64px; } + /* nothing to push against on one column: the cluster hung alone on its own + line while the rest of the footer sat left */ + body.site .foot .out { margin-left: 0; } + /* the mark carries no wordmark of its own, so once the row wrapped it read as + a stray glyph leading a list rather than as a lockup. The identity is at the + top of the page; here it was only costing the row its first cell. */ + body.site .foot .ram { display: none; } + /* the 132px date column left the title about 120px to live in, so every + headline broke every two words. The date goes above the entry instead. */ + body.site .posts a { grid-template-columns: minmax(0, 1fr) auto; gap: 8px 16px; } + body.site .posts time { grid-column: 1 / -1; } + /* the count column took half the head and squeezed the copy into a + three-words-per-line ribbon; stacked, it becomes the head's footer */ + body.site .ph-split { grid-template-columns: minmax(0, 1fr); gap: 30px; } + body.site .ph-side { text-align: left; } + body.site .ph-side b { font-size: 44px; } +} + +@media (max-width: 430px) { + /* 44 characters at 13.5px ran to a third line with "sh" alone on it. At 12px + the only break opportunity — the space after -fsSL — lands mid-line and the + URL stays whole. */ + body.site .hd-cmd code { padding: 12px 14px; font-size: 12px; } + body.site .hd-cmd button { padding: 12px 13px; } +} + +/* The narrow end of the range — SE-class phones and folded covers, 320px up. + Four cells and three gaps came to 334px against 324px of bar at 364, so the + nav overflowed again and the document went wider than the screen. The count + is the widest thing that can go and still leave a working link: the mark + keeps the destination, and the same number is one screen down in the strip. */ +@media (max-width: 380px) { + :root { --gut: 16px; } + body.site .hd-nav { gap: 10px; } + body.site .hd-nav .ico.wide { width: 30px; gap: 0; padding: 0; } + body.site .hd-nav .ico.wide b { display: none; } + /* the button's 60px was the difference between two lines and a third with + "sh" on it, so it becomes the row under the command instead of the cell + beside it — the same shape the plugins list uses for its foot */ + body.site .hd-cmd { flex-wrap: wrap; } + body.site .hd-cmd code { flex: 1 1 100%; } + body.site .hd-cmd button { + flex: 1 1 100%; text-align: left; padding: 11px 14px; + border-left: 0; border-top: 1px solid var(--line2); + } +} + +/* The mock scales on its own width — .hero-product-shot is the container. + Sidebar left, tabs above the pane: the same layout as the wide plate, just + smaller. It used to reflow into stacked strips, which is a shape herdr + never renders and which overflowed sideways anyway. Type is the thing that + shrinks, so the terminal stays crisp rather than being transform-scaled. */ +@container (max-width: 900px) { + body.site .herdr-ui-mock { + aspect-ratio: auto; height: 74vh; min-height: 460px; + /* the sidebar keeps enough width for a branch name under the workspace */ + grid-template-columns: minmax(84px, 27%) minmax(0, 1fr); + } + body.site .mock-space, body.site .mock-agent { padding: 6px 8px; } + /* "grouped" is the sort mode; at this width it collides with the heading */ + body.site .mock-agent-title span:last-child { display: none; } + body.site .mock-main { min-height: 0; min-width: 0; } + body.site .mock-view, body.site .mock-pane { min-width: 0; } + /* splits stack out of view rather than halving an already narrow pane */ + body.site .mock-panes { grid-template-columns: minmax(0, 1fr) !important; min-width: 0; } + body.site .mock-panes .mock-pane:not(.focused) { display: none; } +} + +@media (prefers-reduced-motion: reduce) { + body.site .strip i.live { animation: none; } +} + +/* ────────────────────────────────────────────────────────────── + Typography split — readability first. + + Prose inherits --body from body.site. Everything below is put back + into mono because mono is doing a real job there: identifiers and + repo paths, commands, uppercase labels, and figures with units. + Display headings stay Archivo and are set where they're declared. + ────────────────────────────────────────────────────────────── */ +body.site .hd-nav a.lnk, +body.site .hd-nav .btn, +body.site .hd-eyebrow, +body.site .meta, +body.site .stat span, +body.site .fighd, +body.site .caps .n, +body.site .foot, +body.site .ph-side span, +body.site .posts time, +body.site .article-meta, +body.site .toc p, +body.site .plugins-search, +body.site .plugins-sort, +body.site .plugins-sort-label, +body.site .plugins-meta, +body.site .plugins-status, +body.site .plugin-title, +body.site .plugin-meta-row, +body.site .btn-solid, +body.site .btn-ghost, +body.site code, +body.site pre { + font-family: var(--mono); +} + +/* prose sizes: comfortable rather than compact */ +body.site .lede { font-size: 16px; line-height: 1.7; max-width: 56ch; } +body.site .ph p { font-size: 15.5px; line-height: 1.7; max-width: 62ch; } +body.site .caps p { font-size: 14.5px; line-height: 1.7; color: var(--dim); } +body.site .end p { font-size: 15px; line-height: 1.75; } +body.site .figcap { font-size: 13.5px; line-height: 1.7; } +body.site .posts p { font-size: 14px; line-height: 1.65; } +body.site .article-lede { font-size: 16.5px; line-height: 1.7; } +body.site .prose { font-size: 16px; line-height: 1.75; } +body.site .prose code, body.site .prose pre { font-size: 13.5px; } +body.site .toc a { font-family: var(--body); font-size: 13px; line-height: 1.5; } +body.site .plugins-trust { font-size: 13.5px; line-height: 1.7; } +body.site .plugin-desc { font-size: 14px; line-height: 1.6; min-height: calc(2 * 1.6em); } +body.site .plugin-title { font-size: 14.5px; } |
