craigcloud design
One visual language across every surface Craig owns, so that opening the status
page, the console, and an upload site feels like opening three parts of one
product rather than three projects.
The stylesheet is the source of truth, not this document.
craigcloud.css sits beside this file. Import it or inline it — never
re-derive the palette by eye, and never introduce a second one in a project.
<link rel="stylesheet" href="craigcloud.css">
<!-- or, for a self-contained page, inline the file's contents in a <style> -->
Generators inline it. ai-lawnmower/loop/status-page.py is the reference
implementation: it reads this file when present and falls back to an embedded
copy, so the page still renders if the skill is not installed.
What it looks like
Tech-noir, near-black, monospace, cyan. The aesthetic lives in the frame —
corner brackets, a masked grid, notched panels, bracketed section rules — and
never in the data layer. The standard way this look fails is neon text
nobody can read at 6am on a phone, so:
- Neon is for accents and STATE only. Body copy is high-contrast neutral.
- Magenta is chrome. At 5.31:1 it can never carry a data column.
- Cyan is the one structural accent. One accent per screen; anything else
coloured that is not a status is a bug.
- Committed dark. There is no light mode; paint the background explicitly
rather than inheriting it.
The rules that are not negotiable
- Hue is never the only signal. Every status carries a glyph and the
word:
● ok, ▲ warn, ✕ bad, ◆ running, ○ pending. The naive neon
triad collapses to ΔE 9.1 under protanopia — ok and warn become the same
colour. The palette's triad holds ΔE 23.9 worst case.
- Every section explains itself — behind an (i), not above the data. Each
heading carries a small circled
i next to its title, and the .cc-explain
line sits inside the heading, revealed on hover or on focus (which is what a
click, and a tap on a phone, land as). Plain English, saying what it is and
why it matters. Someone who was not in the design conversation must still be
able to read the page — and someone who reads it daily must not pay a
paragraph for it every time. Craig, 9/15/26: "No need for it to take up
space always. I never read it."
Reference implementation: h2() in ai-lawnmower/loop/status-page.py, with
the disclosure proven in a real browser by loop/status_explain_check.mjs.
craigcloud.css deliberately does not ship the hide/reveal rules: other
surfaces carry .cc-explain as a plain sibling paragraph with no toggle, and
hiding it there would take their copy away with no way to get it back. Each
surface opts in locally until Craig says otherwise.
- Body text ≥ 4.5:1 and ≥ 12px.
--cc-dim (4.7:1) is the floor and is for
ornaments only.
- Touch targets ≥ 44px with real spacing. A bevelled corner must not eat
a tap zone.
- One animation, and only on genuinely live things. Behind
prefers-reduced-motion: no-preference, with a blanket kill switch for
reduce. A glitching number is indistinguishable from a broken data feed.
- Visible focus.
:focus-visible outline in cyan. Never outline: none.
- Hierarchy by size and weight first, colour second. If it only works in
colour it fails in greyscale.
- Wide content scrolls inside its own container. The body never scrolls
horizontally.
Type
| role |
stack |
notes |
| data, numbers, everything scanned |
--cc-mono (JetBrains Mono) |
raised x-height at fixed advance; survives 12px on a phone. Tabular by construction, so columns align. |
| headings, labels, tags |
--cc-disp (Chakra Petch) |
the only techno face here readable near body sizes |
Uppercase is fine for labels of three words or fewer and always needs
letter-spacing: .08–.11em. Never uppercase a sentence, a commit message, a
task title, or a monospace number.
Components
.cc-card / .cc-card.flag · .cc-row (.alert, .live) · .cc-big +
.cc-lbl · .cc-tag (.on) · .cc-wrap + table (tr.head, td.n) ·
.cc-h1 / .cc-h2 + .cc-explain · .cc-gl glyphs · .cc-pulse.
Brackets (.flag) go only on the card asking for attention. On every card
they carry no information.
Sparklines
Inline SVG, no library, no request. A single number is a point with no trend,
and a point cannot say whether 90% is healthy or falling. See sparkline() in
ai-lawnmower/loop/status-page.py.
Where it is used
ai-lawnmower status page and console · upload-factory ·
mariners-lineup (old-iPad browser baseline — verify there before shipping a
new CSS feature) · any craigcloud.io surface.
1---2name: craigcloud-design3description: The craigcloud visual language for Craig's own tools and sites -- measured dark palette, JetBrains Mono + Chakra Petch, self-explaining sections, and the accessibility rules that keep it usable. Read this BEFORE choosing any color, font, spacing or layout for anything on craigcloud.io or in his personal tooling -- status pages, dashboards, consoles, upload sites, internal web UIs -- and whenever Craig says: make this look good, make it match, use my theme, craigcloud style, make it look like a product, this page is ugly. Use spectrocloud-brand instead for anything customer-facing or Spectro-branded; this is for HIS surfaces, not the company's.4---56# craigcloud design78One visual language across every surface Craig owns, so that opening the status9page, the console, and an upload site feels like opening three parts of one10product rather than three projects.1112**The stylesheet is the source of truth, not this document.**13`craigcloud.css` sits beside this file. Import it or inline it — never14re-derive the palette by eye, and never introduce a second one in a project.1516```html17<link rel="stylesheet" href="craigcloud.css">18<!-- or, for a self-contained page, inline the file's contents in a <style> -->19```2021Generators inline it. `ai-lawnmower/loop/status-page.py` is the reference22implementation: it reads this file when present and falls back to an embedded23copy, so the page still renders if the skill is not installed.2425## What it looks like2627Tech-noir, near-black, monospace, cyan. The aesthetic lives in the **frame** —28corner brackets, a masked grid, notched panels, bracketed section rules — and29**never in the data layer**. The standard way this look fails is neon text30nobody can read at 6am on a phone, so:3132- **Neon is for accents and STATE only.** Body copy is high-contrast neutral.33- **Magenta is chrome.** At 5.31:1 it can never carry a data column.34- **Cyan is the one structural accent.** One accent per screen; anything else35 coloured that is not a status is a bug.36- **Committed dark.** There is no light mode; paint the background explicitly37 rather than inheriting it.3839## The rules that are not negotiable40411. **Hue is never the only signal.** Every status carries a glyph *and* the42 word: `●` ok, `▲` warn, `✕` bad, `◆` running, `○` pending. The naive neon43 triad collapses to ΔE 9.1 under protanopia — ok and warn become the same44 colour. The palette's triad holds ΔE 23.9 worst case.452. **Every section explains itself — behind an (i), not above the data.** Each46 heading carries a small circled `i` next to its title, and the `.cc-explain`47 line sits inside the heading, revealed on hover or on focus (which is what a48 click, and a tap on a phone, land as). Plain English, saying what it is and49 why it matters. Someone who was not in the design conversation must still be50 able to read the page — and someone who reads it daily must not pay a51 paragraph for it every time. Craig, 9/15/26: *"No need for it to take up52 space always. I never read it."*53 Reference implementation: `h2()` in `ai-lawnmower/loop/status-page.py`, with54 the disclosure proven in a real browser by `loop/status_explain_check.mjs`.55 `craigcloud.css` deliberately does **not** ship the hide/reveal rules: other56 surfaces carry `.cc-explain` as a plain sibling paragraph with no toggle, and57 hiding it there would take their copy away with no way to get it back. Each58 surface opts in locally until Craig says otherwise.593. **Body text ≥ 4.5:1 and ≥ 12px.** `--cc-dim` (4.7:1) is the floor and is for60 ornaments only.614. **Touch targets ≥ 44px** with real spacing. A bevelled corner must not eat62 a tap zone.635. **One animation, and only on genuinely live things.** Behind64 `prefers-reduced-motion: no-preference`, with a blanket kill switch for65 `reduce`. A glitching number is indistinguishable from a broken data feed.666. **Visible focus.** `:focus-visible` outline in cyan. Never `outline: none`.677. **Hierarchy by size and weight first, colour second.** If it only works in68 colour it fails in greyscale.698. **Wide content scrolls inside its own container.** The body never scrolls70 horizontally.7172## Type7374| role | stack | notes |75|---|---|---|76| data, numbers, everything scanned | `--cc-mono` (JetBrains Mono) | raised x-height at fixed advance; survives 12px on a phone. Tabular by construction, so columns align. |77| headings, labels, tags | `--cc-disp` (Chakra Petch) | the only techno face here readable near body sizes |7879Uppercase is fine for labels of three words or fewer and **always** needs80`letter-spacing: .08–.11em`. Never uppercase a sentence, a commit message, a81task title, or a monospace number.8283## Components8485`.cc-card` / `.cc-card.flag` · `.cc-row` (`.alert`, `.live`) · `.cc-big` +86`.cc-lbl` · `.cc-tag` (`.on`) · `.cc-wrap` + `table` (`tr.head`, `td.n`) ·87`.cc-h1` / `.cc-h2` + `.cc-explain` · `.cc-gl` glyphs · `.cc-pulse`.8889Brackets (`.flag`) go **only** on the card asking for attention. On every card90they carry no information.9192## Sparklines9394Inline SVG, no library, no request. A single number is a point with no trend,95and a point cannot say whether 90% is healthy or falling. See `sparkline()` in96`ai-lawnmower/loop/status-page.py`.9798## Where it is used99100`ai-lawnmower` status page and console · `upload-factory` ·101`mariners-lineup` (old-iPad browser baseline — verify there before shipping a102new CSS feature) · any craigcloud.io surface.