carlos / carloku Public

🤖 The docs skin, in Carloku's world

Reuses site.css's tokens whole — no redefinitions, so both colour
schemes come along for free through the one mechanism this site has
(prefers-color-scheme; there is no [data-theme] switch to match).

Sidebar at --wrap, two columns above 60rem and stacked below it in
document order. Body copy at 34rem, computed from Geist's real prose
advance (0.467em) at site.css's 1rem body size — about 73 characters.
The column is 42rem so the corpus's longest command (66 chars, 34.9rem
in Geist Mono at 0.88rem) sits in its fence without scrolling; anything
longer scrolls inside the fence, never the page, which is what the
min-width: 0 on the shell's grid children and the minmax(0, 1fr) track
are there to guarantee.

Carloku's own vocabulary rather than the sibling's: fences wear the
terminal's clothes and stay dark in both schemes, the you-are-here mark
is the accent over the accent-soft wash, the index is two ruled columns
rather than boxed cards, and no border is heavier than a hairline.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 4673d8c1e5f0eaedb50e02f0f3294bada3938cad parent e6e3420
1 file changed, +450 −0
  • src/docs.css +450 −0
diff --git a/src/docs.css b/src/docs.css
index e69de29..2e5feed 100644
--- a/src/docs.css
+++ b/src/docs.css
@@ -0,0 +1,450 @@
+/* 🤖 carloku.com/docs — the documentation skin.
+
+ Loaded after site.css, never instead of it. Every colour, measure,
+ type stack, focus ring, scrollbar and selection colour here comes
+ from that file's tokens, so both schemes come along for free: the
+ whole palette swaps under `prefers-color-scheme: dark`, which is the
+ one theme mechanism this site has. There is no [data-theme] switch to
+ match, and inventing a second, half-supported one for these pages
+ only would put the docs out of step with / and /get-started.
+
+ What this file adds is the shape site.css has no page for — a
+ sidebar, a reference column, and fenced code. It stays inside the
+ house rules in DESIGN.md while it does it:
+
+ - hairlines divide, boxes do not enclose (no bordered cards, and
+ no border heavier than 1px anywhere below);
+ - one purple carries links, focus and the you-are-here mark, over
+ an accent-soft wash — the same wash the code chips and the quiet
+ button's hover already use;
+ - a fence wears the terminal's clothes and stays dark in both
+ schemes, because anything terminal-shaped does (--term-*);
+ - mono carries commands and addresses, never prose;
+ - weights come off the variable axis at the tuned steps
+ (480/560/620/640/650/680), and tracking tightens as size grows.
+
+ Same build rules as the rest of the site: nothing fetched, no
+ @import, no JavaScript. Motion is colour-only and short, so the
+ global prefers-reduced-motion kill switch in site.css covers it. */
+
+/* ---------------------------------------------------------------- tokens -- */
+
+:root {
+ /* The two measures site.css lacks, both computed from the real Geist
+ metrics rather than borrowed.
+
+ Body copy sets at 1rem (site.css `body`). Geist's prose advance —
+ its letters weighted by English frequency, plus a space every 5.7
+ characters — is 0.467em, so a 65-75 character line wants 30.4rem
+ to 35.0rem. --docs-measure sits near the top of that band at
+ roughly 73 characters, and is also site.css's existing lede width,
+ so the docs read at the same measure the rest of the site does.
+
+ The column is wider than the measure because a reference page also
+ carries command lines. The longest in the corpus is 66 characters;
+ in Geist Mono (a true 0.6em advance) at the 0.88rem fences use,
+ that is 34.9rem, and 42rem of column leaves it 4.6rem of room
+ inside the fence's padding. So the ordinary command sits in its box
+ and only a genuinely long one has to scroll. 42rem is also what
+ site.css already caps /get-started's prose at. */
+ --docs-measure: 34rem;
+ --docs-column: 42rem;
+}
+
+/* ------------------------------------------------------------ page frame -- */
+
+/* .docs-body and .language-sh carry nothing of their own, deliberately.
+ site.css's `body` rule already sets the ground, the type and the
+ voice; and the site ships no highlighter, so a fence's language class
+ has nothing to say. Inventing a declaration so the hooks appear in
+ this file would be worse than leaving them alone. */
+
+/* Where you are, in words, above a hairline. The drawn wordmark and the
+ sticky translucent header belong to the pages that are a destination;
+ a reference page gets a line of text and gets out of the way. */
+.brandline {
+ display: flex;
+ align-items: baseline;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ max-width: var(--wrap);
+ margin-inline: auto;
+ padding: 1.3rem 1.5rem 0.85rem;
+ border-bottom: 1px solid var(--line);
+ font-size: 0.9rem;
+ color: var(--muted);
+}
+.brandline a { color: var(--muted); text-decoration: none; transition: color 120ms ease-out; }
+.brandline a:hover { color: var(--accent); }
+.brandline b { font-weight: 640; letter-spacing: -0.01em; color: var(--ink); }
+.brandline a:hover b { color: inherit; }
+.brandline .here { color: var(--ink); font-weight: 560; }
+
+/* The folio is an address, so it sets as data — mono, the way every
+ other identifier on this site does — and it is the first thing a
+ narrow viewport gives up. */
+.folio {
+ margin-left: auto;
+ font-family: var(--mono);
+ font-size: 0.78rem;
+ letter-spacing: 0.01em;
+ color: var(--faint);
+}
+@media (max-width: 40rem) { .folio { display: none; } }
+
+/* The skip link is site.css's .skip, whole: parked at top: -3rem and
+ slid into view on :focus-visible. It is the site's own idiom and it
+ already does what these pages need — with 40+ sidebar links ahead of
+ the article and no search to route around them, a keyboard user needs
+ the way past. Restating it here would only let the two drift. */
+
+/* ----------------------------------------------------------------- shell -- */
+
+.docs-shell {
+ max-width: var(--wrap);
+ margin-inline: auto;
+ padding: clamp(1.75rem, 4vw, 2.75rem) 1.5rem;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: 2.25rem;
+ align-items: start;
+}
+
+/* Both children are grid items, so both get min-width: 0. Without it a
+ grid child's min-width: auto refuses to shrink below its widest
+ unbreakable content, and a long command line would scroll the page
+ sideways instead of scrolling inside its own fence. */
+.docs-shell > * { min-width: 0; }
+
+/* Two columns once there is room for both. Below this the nav keeps its
+ document order — it is written before the article — so it simply
+ stacks above the content, with no toggle and no script. */
+@media (min-width: 60rem) {
+ .docs-shell {
+ grid-template-columns: 15rem minmax(0, 1fr);
+ gap: clamp(2.5rem, 5vw, 4rem);
+ }
+ .docs-nav {
+ position: sticky;
+ top: 1.75rem;
+ max-height: calc(100vh - 3.5rem);
+ overflow-y: auto;
+ overscroll-behavior: contain;
+ }
+}
+
+/* ------------------------------------------------------------------- nav -- */
+
+/* Every link is padded rather than pulled out with a negative margin:
+ the sidebar becomes a scroll container at the two-column breakpoint,
+ and an overhang inside `overflow-y: auto` would give the nav its own
+ horizontal scrollbar. The labels above are padded to match, so the
+ column still aligns with itself. */
+.docs-nav { font-size: 0.94rem; }
+.docs-nav ul { list-style: none; padding: 0; }
+.docs-nav li { margin-top: 0.1rem; }
+
+.docs-nav a {
+ display: block;
+ padding: 0.26rem 0.55rem;
+ border-radius: 0.4rem;
+ color: var(--muted);
+ font-weight: 480;
+ text-decoration: none;
+ transition: color 120ms ease-out, background 120ms ease-out;
+}
+.docs-nav a:hover { color: var(--ink); }
+
+/* The page you are on, marked with the accent over the accent-soft wash
+ — the same pair the code chips, the step numerals and the quiet
+ button's hover already use, so the mark is house vocabulary rather
+ than a new device. Keyed to both the class the template stamps and
+ the aria-current it stamps beside it, so the mark cannot outlive
+ either one. */
+.docs-nav a.active,
+.docs-nav a[aria-current="page"] {
+ background: var(--accent-soft);
+ color: var(--accent);
+ font-weight: 560;
+}
+
+.docs-nav-title {
+ margin-bottom: 1.15rem;
+ padding-inline: 0.55rem;
+ font-size: 1.05rem;
+ font-weight: 640;
+ letter-spacing: -0.01em;
+}
+.docs-nav-title a { color: var(--ink); font-weight: inherit; }
+.docs-nav-title a:hover { color: var(--accent); }
+
+/* A section label, not a heading and not data: it stays in the voice
+ face at a quiet weight above a hairline. This site never sets tracked
+ small caps, and importing them here to say "documentation" would be
+ borrowing a convention rather than using ours. */
+.docs-nav-section {
+ margin-top: 1.6rem;
+ padding-top: 0.9rem;
+ margin-bottom: 0.5rem;
+ padding-inline: 0.55rem;
+ border-top: 1px solid var(--line);
+ font-size: 0.82rem;
+ font-weight: 560;
+ color: var(--faint);
+}
+.docs-nav-title + .docs-nav-section { margin-top: 0; padding-top: 0; border-top: 0; }
+
+/* The open page's own sections, indented under it against a hairline. */
+.docs-subnav {
+ margin: 0.35rem 0 0.7rem 0.85rem;
+ padding-left: 0.5rem;
+ border-left: 1px solid var(--line);
+ font-size: 0.875rem;
+}
+.docs-subnav li { margin-top: 0; }
+.docs-subnav a {
+ padding: 0.16rem 0.45rem;
+ font-weight: 400;
+ color: var(--faint);
+}
+.docs-subnav a:hover { color: var(--accent); }
+
+/* --------------------------------------------------------------- article -- */
+
+.docs-article { max-width: var(--docs-column); min-width: 0; }
+
+.docs-article h1 {
+ font-size: clamp(2rem, 4.4vw, 2.7rem);
+ line-height: 1.06;
+ letter-spacing: -0.032em;
+ font-weight: 680;
+ text-wrap: balance;
+ /* site.css's element-level h1/h2 rules carry a margin-bottom for the
+ landing page's rhythm. Here the following element owns the gap, so
+ the heading gives it up rather than adding to it. */
+ margin-bottom: 0;
+}
+.docs-article h1 + p {
+ margin-top: 1.15rem;
+ font-size: 1.12rem;
+ line-height: 1.65;
+ max-width: var(--docs-measure);
+}
+
+/* A group opens on a full hairline — the same divider /get-started uses
+ between its sections. The reference reads as a run of divided
+ entries, never as a stack of boxes. */
+.docs-article h2 {
+ margin-top: clamp(2.75rem, 5.5vw, 4rem);
+ padding-top: clamp(1.6rem, 3.2vw, 2.25rem);
+ border-top: 1px solid var(--line);
+ font-size: clamp(1.35rem, 2.4vw, 1.7rem);
+ line-height: 1.2;
+ letter-spacing: -0.025em;
+ font-weight: 650;
+ text-wrap: balance;
+ margin-bottom: 0;
+ /* No measure cap: the hairline is the divider, so it runs the width of
+ the column rather than stopping short of the fences beneath it. The
+ longest heading in the corpus is 37 characters — 29rem at this size
+ — so nothing wraps for want of the cap. */
+}
+
+/* h3/h4 and the block elements further down are the floor for markdown
+ this corpus does not use yet — it is authored in this repo and grows
+ by hand. Kept deliberately short: enough that a new page cannot land
+ unstyled, not so much that it becomes a theme nobody asked for. */
+.docs-article h3 {
+ margin-top: 2.2rem;
+ font-size: 1.08rem;
+ font-weight: 620;
+ letter-spacing: -0.01em;
+ color: var(--ink);
+}
+.docs-article h4 {
+ margin-top: 1.8rem;
+ font-size: 0.98rem;
+ font-weight: 620;
+ letter-spacing: -0.01em;
+ color: var(--ink);
+}
+
+.docs-article :is(h1, h2, h3, h4) { scroll-margin-top: 1.5rem; }
+
+.docs-article p {
+ margin-top: 1.05rem;
+ max-width: var(--docs-measure);
+ color: var(--muted);
+ text-wrap: pretty;
+}
+.docs-article :is(h2, h3, h4) + p { margin-top: 0.85rem; }
+.docs-article strong { color: var(--ink); font-weight: 620; }
+.docs-article em { color: var(--ink); }
+
+.docs-article ul:not(.docs-cards),
+.docs-article ol {
+ margin-top: 1rem;
+ padding-left: 1.2rem;
+ max-width: var(--docs-measure);
+ color: var(--muted);
+}
+/* Scoped with a child combinator rather than `.docs-article li + li`,
+ which would outrank `.docs-cards li` below and break the index
+ grid's row alignment. */
+.docs-article ul:not(.docs-cards) > li + li,
+.docs-article ol > li + li { margin-top: 0.45rem; }
+.docs-article blockquote {
+ margin-top: 1.15rem;
+ padding-left: 1.1rem;
+ border-left: 1px solid var(--line-strong);
+ max-width: var(--docs-measure);
+ color: var(--faint);
+ font-size: 0.95rem;
+}
+.docs-article hr {
+ margin-block: 2.25rem;
+ border: 0;
+ border-top: 1px solid var(--line);
+}
+.docs-article table {
+ margin-top: 1.25rem;
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 0.95rem;
+}
+.docs-article :is(th, td) {
+ text-align: left;
+ padding: 0.6rem 0.9rem 0.6rem 0;
+ border-top: 1px solid var(--line);
+ color: var(--muted);
+ vertical-align: baseline;
+}
+.docs-article th { color: var(--ink); font-weight: 620; }
+
+.docs-article a {
+ color: var(--accent);
+ text-decoration: none;
+}
+.docs-article a:hover { text-decoration: underline; text-underline-offset: 0.2em; }
+
+/* ------------------------------------------------------------------ code -- */
+
+/* A fence is a box that scrolls itself: the page must never scroll
+ sideways because a command was long. It wears the terminal's clothes
+ and keeps them in both schemes, under the rule that anything
+ terminal-shaped sits on Terminal Black — the same clothes
+ /get-started's blocks already wear, and the reason a Carloku page
+ reads as a Carloku page. */
+.docs-article pre {
+ margin: 1.15rem 0 0;
+ max-width: 100%;
+ min-width: 0;
+ overflow-x: auto;
+ overscroll-behavior-x: contain;
+ -webkit-overflow-scrolling: touch;
+ padding: 1rem 1.2rem;
+ border: 1px solid var(--term-line);
+ border-radius: 0.7rem;
+ background: var(--term-bg);
+ color: var(--term-ink);
+ font-family: var(--mono);
+ font-size: 0.88rem;
+ line-height: 1.7;
+}
+.docs-article p + pre { margin-top: 0.9rem; }
+
+/* Inline code is a chip: mono on an accent-soft wash, the way the
+ commands appear in prose everywhere else on this site. Inside a fence
+ that chip would tile the whole block, so it is undone there. */
+.docs-article code {
+ font-family: var(--mono);
+ font-size: 0.88em;
+ background: var(--accent-soft);
+ color: var(--accent-ink);
+ padding: 0.12em 0.4em;
+ border-radius: 0.3em;
+}
+.docs-article pre code {
+ display: block;
+ padding: 0;
+ border-radius: 0;
+ background: none;
+ color: inherit;
+ font-size: inherit;
+ white-space: pre;
+}
+.docs-article a code { color: inherit; }
+
+/* ------------------------------------------------------------- the index -- */
+
+/* The index's entries: a label and a line saying what is inside. They
+ are not cards and they are not boxed — this site separates siblings
+ with a single hairline and whitespace, the way the feature list and
+ the guide's sections do. Two ruled columns of contents, nothing to
+ lift off the page. */
+.docs-cards {
+ margin-top: 1.25rem;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ column-gap: 2.5rem;
+ list-style: none;
+ padding: 0;
+}
+@media (min-width: 46rem) {
+ .docs-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+}
+.docs-cards li {
+ min-width: 0;
+ margin-top: 0;
+ padding: 1.05rem 0 1.15rem;
+ border-top: 1px solid var(--line);
+}
+.docs-cards a { display: inline-block; text-decoration: none; }
+.docs-cards a strong {
+ font-size: 1.05rem;
+ font-weight: 620;
+ letter-spacing: -0.01em;
+ color: var(--ink);
+ transition: color 120ms ease-out;
+}
+.docs-cards a:hover strong { color: var(--accent); }
+.docs-cards a:hover { text-decoration: none; }
+.docs-cards span {
+ display: block;
+ margin-top: 0.25rem;
+ font-size: 0.95rem;
+ color: var(--muted);
+ text-wrap: pretty;
+}
+
+/* The index's h2s head a grid rather than prose, so they need less air
+ under them and no measure cap. */
+.docs-article h2 + .docs-cards { margin-top: 1.4rem; }
+
+/* ------------------------------------------------------------------ feet -- */
+
+/* Qualified so it outranks `.docs-article p` — this is a <p> inside the
+ article, and the unqualified class would lose the colour and the top
+ margin to it. */
+.docs-article .docs-foot {
+ margin-top: clamp(2.75rem, 5.5vw, 4rem);
+ padding-top: 1.15rem;
+ border-top: 1px solid var(--line);
+ max-width: var(--docs-measure);
+ font-size: 0.9rem;
+ color: var(--faint);
+}
+.docs-article .docs-foot a { color: var(--faint); }
+.docs-article .docs-foot a:hover { color: var(--accent); }
+
+/* site.css's <footer> already gives the rule, the muted voice and the
+ block padding; these pages have no .wrap element inside it, so the
+ measure and the gutters are what is left to supply. */
+.docs-sitefoot {
+ max-width: var(--wrap);
+ margin-inline: auto;
+ padding-inline: 1.5rem;
+}
+.docs-provenance { font-size: 0.85rem; color: var(--faint); }
+.docs-provenance a { color: var(--faint); }
+.docs-provenance a:hover { color: var(--accent); }