aboutsummaryrefslogtreecommitdiff
path: root/website/css
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-08-10 23:32:04 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-08-10 23:34:01 +0300
commit42789c8e1d86a3d5ea529b1b047172bfd6979a21 (patch)
treeed953dadc20545c7ec82b85bd1a77d6f7f592380 /website/css
parent4a8a8f9087cc2576ad643858d7483de46268f449 (diff)
refactor(website): clean up homepage
Diffstat (limited to 'website/css')
-rw-r--r--website/css/site.css294
1 files changed, 37 insertions, 257 deletions
diff --git a/website/css/site.css b/website/css/site.css
index 8049408d..5654fc48 100644
--- a/website/css/site.css
+++ b/website/css/site.css
@@ -1,30 +1,12 @@
/* ──────────────────────────────────────────────────────────────
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 default ground; paper is shared with the docs via
+ Starlight's `starlight-theme` key.
────────────────────────────────────────────────────────────── */
-/* 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.
- The greys are drained for the same reason the ground was: violet-tinted
- text on a violet-tinted ground loses perceived contrast to shared hue,
- and it crowds the mauve. faint2 is the recessed tier — the old page had
- two muted steps and collapsing them flattened the card hierarchy.
- Contrast: ink 14.7, dim 11.2, faint 8.3, faint2 5.6 — all above AA. */
--bg: #17171a; --panel: #1e1e22; --mass: #26262b;
--ink: #eae8ee; --dim: #cdccd2; --faint: #b0afb6; --faint2: #908f96;
--line: #26262b; --line2: #35353d;
@@ -41,35 +23,18 @@ html[data-mode="paper"] {
}
: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. */
+ /* agent state colours alias the style.css tokens so the legend, the
+ plate and the app stay in step */
--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 */
+ /* style.css has no teal token to alias, so done is set per ground */
--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); }
@@ -81,13 +46,8 @@ html[data-palette] {
--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. */
+/* bridge style.css's legacy token names onto the neutral scale; scoped to
+ this page so the mock keeps its own --radius-* */
body.site .plugins-page {
--bg-elevated: var(--panel);
--ink-soft: var(--dim);
@@ -95,21 +55,18 @@ body.site .plugins-page {
--muted-2: var(--faint2);
--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;
- /* no `antialiased`: grayscale smoothing thins light-on-dark glyphs on
- macOS, and every text tone here is already near its contrast floor */
+ /* no `antialiased`: grayscale smoothing thins light-on-dark glyphs on macOS */
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); }
@@ -122,8 +79,6 @@ body.site .ram {
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);
}
@@ -140,10 +95,6 @@ body.site .hd-nav a.lnk { color: var(--dim); text-decoration: none; font-size: 1
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;
@@ -155,12 +106,9 @@ 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;
@@ -169,8 +117,6 @@ body.site .hd-nav .menu {
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);
@@ -196,9 +142,7 @@ body.site .mode button {
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. */
+/* ── hero ── */
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%);
@@ -210,15 +154,12 @@ body.site .hd-eyebrow { display: flex; align-items: center; gap: 14px; color: va
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);
@@ -235,9 +176,7 @@ body.site .meta { margin-top: 20px; color: var(--faint); font-size: 11.5px; lett
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. */
+/* ── numbers ── */
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;
@@ -273,9 +212,7 @@ body.site .stat i.live {
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. */
+/* ── FIG 1 · the plate ── */
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;
@@ -304,19 +241,8 @@ body.site .plate .herdr-ui-mock {
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. */
+/* ── router rows ── */
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;
@@ -326,27 +252,14 @@ body.site .caps > .cap {
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. */
+/* plain inline: an atomic inline adds a wrap opportunity before it, and
+ transforms don't apply to a non-replaced inline — so the nudge is margin */
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;
@@ -357,10 +270,6 @@ body.site .caps .n > span:first-child {
}
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;
@@ -377,18 +286,15 @@ body.site .caps .ev-row b {
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; }
+/* ── 05 ──
+ masks are mono because JetBrains Mono carries U+2580–259F; the display
+ face would fall back to whatever the system has */
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 .soon-tag {
+ font-family: var(--mono); font-size: 10px; letter-spacing: .16em;
+ text-transform: uppercase; color: var(--faint);
+ border: 1px solid var(--line2); padding: 4px 8px;
+}
body.site .caps .cap-soon .tape > span { border-right: 0; animation: none; }
/* available to a screen reader, never on screen */
body.site .sr {
@@ -396,34 +302,22 @@ body.site .sr {
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 */
+/* hollow: state_dot() draws idle as ○ */
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 */
+/* the caret is the span's own right border so it rides the line box; direct
+ child only — row 05 nests a mask span */
body.site .caps .tape > span {
flex: 0 1 auto; min-width: 0;
border-right: 7px solid var(--dim);
@@ -431,11 +325,6 @@ body.site .caps .tape > span {
}
@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;
}
@@ -457,8 +346,6 @@ 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;
@@ -483,7 +370,6 @@ body.site .ph p { margin: 22px 0 0; color: var(--dim); font-size: 14.5px; line-h
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; }
@@ -495,11 +381,8 @@ body.site .btn-solid { background: var(--spot); border-color: var(--spot); color
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 a { color: inherit }` outranks .button-primary's own color, so
+ the site language is restated here for style.css's .button classes */
body.site .button {
min-height: 0; padding: 12px 18px; border-radius: 0;
border: 1px solid var(--line2); font-family: var(--mono); font-weight: 400;
@@ -510,10 +393,7 @@ body.site .button-primary:hover { background: var(--ink); border-color: var(--in
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 */
+/* ── plugins ── */
body.site .plugins-trust {
display: block; margin: 20px 0 0;
color: var(--faint); font-size: 12px; line-height: 1.7; max-width: 78ch;
@@ -552,23 +432,15 @@ body.site .plugins-sort {
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); }
-/* Counts and timestamps are data, not labels — the uppercase + wide-tracked
- micro-type treatment that works for one-word chrome labels destroys word
- shapes when it carries whole sentences of metadata, which is most of what
- this page shows. Data rows stay mono but keep their natural case. */
body.site .plugins-meta,
body.site .plugins-status {
margin: 0; padding: 13px var(--gut); color: var(--faint); font-size: 11.5px;
letter-spacing: .04em;
}
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. */
+/* neutralise style.css's .section padding/borders on the ported page */
body.site .plugins-page .section {
padding: 0; border-top: 0; max-width: none; margin: 0;
}
@@ -577,9 +449,6 @@ 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, but a fully transparent cell puts its
- text on the same ground as everything around it — a whisper of panel fill
- gives each cell a surface to read against without rounding it into a box */
body.site .plugin-card {
display: flex; flex-direction: column; min-width: 0;
padding: 20px 22px 22px; text-decoration: none;
@@ -596,8 +465,6 @@ 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(--faint2); 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;
@@ -613,9 +480,6 @@ 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; color: var(--faint2); }
-/* .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;
@@ -624,13 +488,10 @@ body.site .plugins-more {
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 */
+/* the rule above outranks the page's own [hidden] guard, so restate it */
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;
@@ -658,18 +519,11 @@ 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; }
@@ -685,7 +539,6 @@ body.site .article h1 {
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 {
@@ -714,10 +567,7 @@ body.site .prose table { width: 100%; border-collapse: collapse; font-size: 12.5
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. */
+/* ── post figures ── */
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; }
@@ -744,8 +594,6 @@ body.site .pf-row.blocked .pf-mark, body.site .pf-row.blocked small { color: var
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; }
@@ -763,8 +611,6 @@ 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); }
@@ -841,9 +687,8 @@ body.site .vs-list p { margin: 0; color: var(--dim); font-size: 14px; line-heigh
/* ── 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. */
+/* the bleed is a fraction of the mark, not the section — a % would resolve
+ against .end's height */
body.site .end .ram {
--ram-size: min(560px, 54vw);
position: absolute; right: -6%; bottom: calc(var(--ram-size) * -0.22);
@@ -853,12 +698,7 @@ 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. */
+/* ── footer ── */
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); }
@@ -868,9 +708,6 @@ body.site .meta .yc { color: var(--dim); display: inline-flex; align-items: cent
body.site .meta .yc img { display: block; }
body.site .meta .yc:hover { color: var(--ink); text-decoration: none; }
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; }
@@ -879,92 +716,49 @@ 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 {
@@ -973,23 +767,16 @@ body.site .foot .ico:hover { color: var(--ink); }
}
}
-/* 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. */
+/* the mock scales on its own width — .hero-product-shot is the container */
@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; }
}
@@ -998,14 +785,8 @@ body.site .foot .ico:hover { color: var(--ink); }
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.
- ────────────────────────────────────────────────────────────── */
+/* ── typography: mono where it carries identifiers, commands, labels,
+ figures; prose stays --body ── */
body.site .hd-nav a.lnk,
body.site .hd-nav .btn,
body.site .hd-eyebrow,
@@ -1032,7 +813,6 @@ 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); }