eleven / messenger Public

web: unify status pages under one shared stylesheet + consistent header/nav (#796)

The six public status pages (fleet, income & expenditure, security, carbon,
contributors, prompt ledger) were built by different sessions: nav markup and
CSS diverged three ways and two pages had no top nav at all. Consolidate them
into ONE design system.

- Add web/status/status.css: the canonical design tokens (the dataviz-validated
  role set already in use — kept the existing names, since all six pages already
  shared them; this file is just their single home), the reset, the brand
  masthead, the six-item stepper nav, and every shared component (tiles, the
  single-series bar list, notes, the feed-error alert, footer).
- Every page now <link>s status.css and keeps only a small inline <style> for
  its own unique bits (fleet's capacity/hosts/services, books' expense ledger,
  security's fact lists, carbon's units toggle + chart, contributors' identity
  block, the prompt viewer). Same-origin in the status bucket — no third-party
  request, so the privacy property holds.
- Identical brand header + six-item nav on all six (carbon and contributors
  gained one; prompts gained the brand masthead). Current page is a non-link
  <span aria-current="page"> emphasized by weight, not color alone; the nav
  wraps on narrow widths.
- Drop the now-redundant page-link row from each footer, keeping the per-page
  explanatory prose (security's mailto:team@ survives in the kept paragraph).
- status-publish.sh uploads status.css first (text/css, max-age=300) so a page
  is never served ahead of its stylesheet; docs/status.md + the prereq list note
  the shared stylesheet.

Contract test (test/status-contract.test.mjs) untouched and green — it reads the
FIELDS manifests + render JS, which this change doesn't touch.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 2748a4c6096811707db2b1f003412705923d5e80 parent e1380c9
9 files changed, +226 −375
  • deploy/status-publish.sh +10 −3
  • docs/status.md +10 −3
  • web/status/books.html +17 −70
  • web/status/carbon.html +22 −64
  • web/status/contributors.html +13 −55
  • web/status/index.html +17 −81
  • web/status/prompts.html +18 −34
  • web/status/security.html +16 −65
  • web/status/status.css +103 −0
diff --git a/deploy/status-publish.sh b/deploy/status-publish.sh
index 0709b6a..b07f272 100755
--- a/deploy/status-publish.sh
+++ b/deploy/status-publish.sh
@@ -101,9 +101,11 @@
# (an account API token — read-only use here, never
# echoed — plus the numeric account id).
# /opt/lchat/status-pages/ — the viewer pages, copied from web/status/
-# (index.html, books.html, carbon.html, carbon.json,
-# contributors.html, contributors.json, security.html,
-# security.json, icon-192.png).
+# (status.css, index.html, books.html, carbon.html,
+# carbon.json, contributors.html, contributors.json,
+# security.html, security.json, prompts.html,
+# icon-192.png). status.css is the shared stylesheet every
+# page links; it uploads before the pages (see publish()).
# aws CLI + sqlite3 (apt install awscli sqlite3).
set -euo pipefail
@@ -887,6 +889,11 @@ publish() {
# The viewer pages + brand mark (from web/status/, staged on the box).
# Re-uploading tiny idempotent files every run self-heals a missed copy.
if [ -d "$PAGES_DIR" ]; then
+ # The shared stylesheet goes up FIRST, before any page that <link>s it, so a
+ # freshly-uploaded page is never served ahead of its styles (a page fetched
+ # in the gap would render unstyled until it revalidates). Same-origin in the
+ # status bucket — no third-party request, so the privacy line holds.
+ [ -f "$PAGES_DIR/status.css" ] && upload "$PAGES_DIR/status.css" status.css "text/css; charset=utf-8" "max-age=300"
[ -f "$PAGES_DIR/index.html" ] && upload "$PAGES_DIR/index.html" index.html "text/html; charset=utf-8" "max-age=300"
[ -f "$PAGES_DIR/books.html" ] && upload "$PAGES_DIR/books.html" books.html "text/html; charset=utf-8" "max-age=300"
[ -f "$PAGES_DIR/carbon.html" ] && upload "$PAGES_DIR/carbon.html" carbon.html "text/html; charset=utf-8" "max-age=300"
diff --git a/docs/status.md b/docs/status.md
index 5257bcc..1940e08 100644
--- a/docs/status.md
+++ b/docs/status.md
@@ -54,7 +54,13 @@ public *ledger* repo is still roadmap — see `docs/planet-scale.md`).
Pieces: generator `deploy/status-publish.sh` (box timer, mirrors
`deploy/fleet-backup.sh`), viewer pages `web/status/`, bucket + IAM
-`infra/prod/status.tf`, contract test `test/status-contract.test.mjs`.
+`infra/prod/status.tf`, contract test `test/status-contract.test.mjs`. All six
+pages share one stylesheet, `web/status/status.css` — the design tokens, the
+brand masthead, the six-item nav that steps through every dashboard, and the
+common components (tiles, bar lists, notes, footer). Each page keeps only a
+small inline `<style>` for its own bits. It's same-origin in the status bucket
+(no third-party request — the privacy line holds), and the publish script
+uploads it *before* the pages so a page is never served ahead of its styles.
## The privacy rules (non-negotiable)
@@ -397,8 +403,9 @@ Accruing a machine's sessions:
completion key), and `STATUS_DNSIMPLE_TOKEN=…` + `STATUS_DNSIMPLE_ACCOUNT=…`
(a DNSimple account API token — read-only use here, never echoed — plus
the numeric account id). `chown lchat:lchat`, `chmod 0600`.
-3. **Pages on the box**: `scp web/status/{index.html,books.html,carbon.html,carbon.json,contributors.html,contributors.json,security.html,security.json,icon-192.png}
- root@<box>:/opt/lchat/status-pages/`.
+3. **Pages on the box**: `scp web/status/{status.css,index.html,books.html,carbon.html,carbon.json,contributors.html,contributors.json,security.html,security.json,prompts.html,icon-192.png}
+ root@<box>:/opt/lchat/status-pages/`. `status.css` is the shared stylesheet
+ every page links (uploaded before the pages by the publish script).
4. **Install the timer**: `scp deploy/status-publish.sh root@<box>:/opt/lchat/`
then `ssh root@<box> /opt/lchat/status-publish.sh install`. The unit runs
as the `lchat` user (least privilege: registry + cloud env are lchat-owned;
diff --git a/web/status/books.html b/web/status/books.html
index 40f5574..b1c808b 100644
--- a/web/status/books.html
+++ b/web/status/books.html
@@ -12,83 +12,25 @@
<meta name="color-scheme" content="light dark" />
<title>Eleven — the books</title>
<link rel="icon" type="image/png" href="./icon-192.png" />
+<link rel="stylesheet" href="./status.css" />
<style>
- /* Same validated palette roles as index.html (dataviz six-checks, light +
- dark); violet series hue, secondary ink for all small text (>=4.5:1). */
- :root {
- --plane: #f9f9f7; --surface: #fcfcfb;
- --ink: #0b0b0b; --ink-2: #52514e;
- --grid: #e1e0d9; --border: rgba(11,11,11,0.10);
- --series: #4a3aa7;
- --bad: #b23030; --bad-mark: #d03b3b;
- --link: #4a3aa7;
- }
- @media (prefers-color-scheme: dark) {
- :root {
- --plane: #0d0d0d; --surface: #1a1a19;
- --ink: #ffffff; --ink-2: #c3c2b7;
- --grid: #2c2c2a; --border: rgba(255,255,255,0.10);
- --series: #9085e9;
- --bad: #e66767; --bad-mark: #e66767;
- --link: #9085e9;
- }
- }
- * { box-sizing: border-box; margin: 0; }
- body {
- background: var(--plane); color: var(--ink);
- font: 16px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
- padding: 24px 16px 48px;
- }
- .wrap { max-width: 760px; margin: 0 auto; }
- a { color: var(--link); }
- a:focus-visible { outline: 2px solid var(--series); outline-offset: 2px; border-radius: 4px; }
-
- header { display: flex; align-items: center; gap: 14px; margin-bottom: 6px; }
- header img { width: 44px; height: 44px; border-radius: 10px; }
- header h1 { font-size: 22px; font-weight: 650; }
- header .brand { color: var(--ink-2); font-size: 14px; }
- nav.pages { font-size: 14px; color: var(--ink-2); margin: 2px 0 10px; }
- nav.pages [aria-current] { color: var(--ink); font-weight: 650; }
- .lede { color: var(--ink-2); margin: 6px 0 2px; }
- .asof { font-weight: 600; margin: 10px 0 24px; }
- .asof .stale { color: var(--bad); font-weight: 600; }
-
- .tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 10px; margin-bottom: 28px; }
- .tile { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px; }
- .tile .label { font-size: 13px; color: var(--ink-2); }
- .tile .value { font-size: 30px; font-weight: 600; margin-top: 2px; }
-
- section h2 { font-size: 15px; font-weight: 650; margin: 0 0 10px; }
- section { margin-bottom: 28px; }
+ /* Books-only: the per-tier bars label, the itemized expense ledger, and the
+ income-unavailable notice (shared #error covers the feed-load error).
+ Everything shared lives in status.css. */
section h3 { font-size: 14px; font-weight: 650; margin: 18px 0 10px; }
- .note { color: var(--ink-2); font-size: 14px; }
- /* One-line design explainer directly under each section heading. */
- .explain { margin: -4px 0 12px; }
- /* Per-tier subscriptions: single-series horizontal bar list; rows are real
- text (their own table view), bars aria-hidden. Thin mark, 4px rounded
- data-end, square baseline. */
- .bars { list-style: none; padding: 0; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; }
- .bars li { display: grid; grid-template-columns: minmax(90px, 160px) 1fr 3.5em; align-items: center; gap: 12px; padding: 10px 16px; }
- .bars li + li { border-top: 1px solid var(--grid); }
+ .bars .tier { font-size: 14px; font-weight: 600; text-transform: capitalize; overflow-wrap: anywhere; }
+
+ /* Expenditure ledger: one line per provider, its own billing currency. */
.ledger { list-style: none; padding: 0; display: grid; gap: 0; }
.ledger li { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 9px 2px; border-bottom: 1px solid var(--border); font-size: 15px; }
.ledger .amt { font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap; }
.ledger-total { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 11px 2px 0; font-weight: 700; }
.ledger-total .amt { font-variant-numeric: tabular-nums; white-space: nowrap; }
.ledger-total .caveat { font-weight: 400; font-size: 13px; color: var(--ink-2); }
- .bars .tier { font-size: 14px; font-weight: 600; text-transform: capitalize; overflow-wrap: anywhere; }
- .bars .track { height: 12px; }
- .bars .bar { height: 12px; min-width: 2px; background: var(--series); border-radius: 0 4px 4px 0; }
- .bars .count { font-size: 14px; color: var(--ink-2); text-align: right; font-variant-numeric: tabular-nums; }
- .bars .empty { display: block; color: var(--ink-2); }
-
- #unavailable, #error { display: none; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px; margin-bottom: 24px; }
- #error { border-color: var(--bad-mark); }
- #unavailable.show, #error.show { display: block; }
- footer { margin-top: 36px; color: var(--ink-2); font-size: 14px; }
- footer p { margin-top: 6px; }
+ #unavailable { display: none; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px; margin-bottom: 24px; }
+ #unavailable.show { display: block; }
</style>
</head>
<body>
@@ -100,8 +42,14 @@
<h1>The books</h1>
</div>
</header>
- <!-- Cross-links between the three status pages; the current one is inert. -->
- <nav class="pages" aria-label="Status pages"><a href="./index.html">Fleet</a> · <span aria-current="page">Books</span> · <a href="./security.html">Security</a></nav>
+ <nav class="status-nav" aria-label="Status">
+ <a href="./index.html">Fleet</a>
+ <span aria-current="page">Income &amp; Expenditure</span>
+ <a href="./security.html">Security</a>
+ <a href="./carbon.html">Carbon use</a>
+ <a href="./contributors.html">Contributors</a>
+ <a href="./prompts.html">Prompt Ledger</a>
+ </nav>
<p class="lede">Eleven’s income and spending, published as they stand — part
of running the service in the open.</p>
<p class="asof">As of <time id="asof" datetime="">—</time><span id="staleness"></span></p>
@@ -140,7 +88,6 @@
</div>
<footer>
- <p><a href="./index.html">Fleet status</a> · <a href="./carbon.html">The carbon tally</a> · <a href="./contributors.html">Contributors</a></p>
<p>Published every 5 minutes from Stripe aggregates and provider billing.
Tier names are the public price book; income amounts are EUR, expenses in
each provider’s own billing currency. Disbursements join when the public
diff --git a/web/status/carbon.html b/web/status/carbon.html
index 3627720..ef89060 100644
--- a/web/status/carbon.html
+++ b/web/status/carbon.html
@@ -13,43 +13,15 @@
<meta name="color-scheme" content="light dark" />
<title>Eleven — the carbon tally</title>
<link rel="icon" type="image/png" href="./icon-192.png" />
+<link rel="stylesheet" href="./status.css" />
<style>
- /* Same validated palette roles as index.html (dataviz six-checks, light +
- dark); violet series hue, secondary ink for all small text (>=4.5:1). */
- :root {
- --plane: #f9f9f7; --surface: #fcfcfb;
- --ink: #0b0b0b; --ink-2: #52514e;
- --grid: #e1e0d9; --border: rgba(11,11,11,0.10);
- --series: #4a3aa7;
- --bad: #b23030; --bad-mark: #d03b3b;
- --link: #4a3aa7;
- }
- @media (prefers-color-scheme: dark) {
- :root {
- --plane: #0d0d0d; --surface: #1a1a19;
- --ink: #ffffff; --ink-2: #c3c2b7;
- --grid: #2c2c2a; --border: rgba(255,255,255,0.10);
- --series: #9085e9;
- --bad: #e66767; --bad-mark: #e66767;
- --link: #9085e9;
- }
- }
- * { box-sizing: border-box; margin: 0; }
- body {
- background: var(--plane); color: var(--ink);
- font: 16px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
- padding: 24px 16px 48px;
- }
- .wrap { max-width: 760px; margin: 0 auto; }
- a { color: var(--link); }
- a:focus-visible { outline: 2px solid var(--series); outline-offset: 2px; border-radius: 4px; }
-
- header { display: flex; align-items: center; gap: 14px; margin-bottom: 6px; }
- header img { width: 44px; height: 44px; border-radius: 10px; }
- header h1 { font-size: 22px; font-weight: 650; }
- header .brand { color: var(--ink-2); font-size: 14px; }
- .lede { color: var(--ink-2); margin: 6px 0 2px; }
- .asof { font-weight: 600; margin: 10px 0 24px; }
+ /* Carbon-only: the units toggle, the equivalents lines, the accrual chart,
+ and the ledger bars' wider columns + period/basis/kg cells. Everything
+ shared lives in status.css. */
+ .tiles { margin-bottom: 12px; }
+ .equiv { color: var(--ink-2); margin: 0 0 28px; }
+ /* The undercounts note follows the ledger list flush — give it air. */
+ #notcounted { margin-top: 18px; }
.row { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin: 0 0 10px; }
.row h2 { margin: 0; }
@@ -67,20 +39,9 @@
}
.units button:focus-visible { outline: 2px solid var(--series); outline-offset: -2px; }
- .tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 10px; margin-bottom: 12px; }
- .tile { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px; }
- .tile .label { font-size: 13px; color: var(--ink-2); }
- .tile .value { font-size: 30px; font-weight: 600; margin-top: 2px; }
- .equiv { color: var(--ink-2); margin: 0 0 28px; }
- /* The undercounts note follows the ledger list flush — give it air. */
- #notcounted { margin-top: 18px; }
-
- section h2 { font-size: 15px; font-weight: 650; margin: 0 0 10px; }
- section { margin-bottom: 28px; }
-
- /* Cumulative chart: single series (no legend box — the title names it),
- 2px line, 10%-wash uncertainty band, hairline gridlines, crosshair +
- one tooltip. The ledger list below is the chart's table view. */
+ /* Cumulative chart: single series (the title names it), 2px line, 10%-wash
+ uncertainty band, hairline gridlines, crosshair + one tooltip. The ledger
+ list below is the chart's table view. */
figure { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px 8px 6px; position: relative; }
figure svg { display: block; width: 100%; height: auto; }
figcaption { color: var(--ink-2); font-size: 13px; padding: 4px 8px 8px; }
@@ -93,22 +54,12 @@
#tip .sub { color: var(--ink-2); }
.pt:focus-visible { outline: 2px solid var(--series); outline-offset: 2px; border-radius: 50%; }
- /* Measurement ledger: single-series horizontal bars (central estimate);
- rows are real text, bars aria-hidden — same mark spec as books.html. */
- .bars { list-style: none; padding: 0; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; }
- .bars li { display: grid; grid-template-columns: minmax(120px, 200px) 1fr 5.5em; align-items: center; gap: 12px; padding: 10px 16px; }
- .bars li + li { border-top: 1px solid var(--grid); }
+ /* Measurement ledger: wider first column + a kg cell; shared .bars supplies
+ the container, rows, and bar mark. */
+ .bars li { grid-template-columns: minmax(120px, 200px) 1fr 5.5em; }
.bars .period { font-size: 14px; font-weight: 600; }
.bars .basis { display: block; font-size: 12px; font-weight: 400; color: var(--ink-2); }
- .bars .track { height: 12px; }
- .bars .bar { height: 12px; min-width: 2px; background: var(--series); border-radius: 0 4px 4px 0; }
.bars .kg { font-size: 14px; color: var(--ink-2); text-align: right; font-variant-numeric: tabular-nums; }
-
- #error { display: none; background: var(--surface); border: 1px solid var(--bad-mark); border-radius: 12px; padding: 14px 16px; margin-bottom: 24px; }
- #error.show { display: block; }
-
- footer { margin-top: 36px; color: var(--ink-2); font-size: 14px; }
- footer p { margin-top: 6px; }
</style>
</head>
<body>
@@ -120,6 +71,14 @@
<h1>The carbon tally</h1>
</div>
</header>
+ <nav class="status-nav" aria-label="Status">
+ <a href="./index.html">Fleet</a>
+ <a href="./books.html">Income &amp; Expenditure</a>
+ <a href="./security.html">Security</a>
+ <span aria-current="page">Carbon use</span>
+ <a href="./contributors.html">Contributors</a>
+ <a href="./prompts.html">Prompt Ledger</a>
+ </nav>
<p class="lede">Eleven is AI-written, and the honest carbon ledger includes
the AI: this is a running estimate of the CO₂e cost of the LLM tokens spent
<em>building</em> the app. Token counts are exact; the carbon conversion is
@@ -168,7 +127,6 @@
</div>
<footer>
- <p><a href="./index.html">Fleet status</a> · <a href="./books.html">The books</a> · <a href="./contributors.html">Contributors</a></p>
<p>Accrues when we re-measure (at least weekly — the tally is committed to
the repo and republished with these dashboards). The running fleet’s own
footprint is tiny by comparison: see the carbon comparison linked above.</p>
diff --git a/web/status/contributors.html b/web/status/contributors.html
index 2afe0d2..065e859 100644
--- a/web/status/contributors.html
+++ b/web/status/contributors.html
@@ -11,52 +11,12 @@
<meta name="color-scheme" content="light dark" />
<title>Eleven — contributors</title>
<link rel="icon" type="image/png" href="./icon-192.png" />
+<link rel="stylesheet" href="./status.css" />
<style>
- /* Same validated palette roles as index.html (dataviz six-checks, light +
- dark); violet series hue, secondary ink for all small text (>=4.5:1). */
- :root {
- --plane: #f9f9f7; --surface: #fcfcfb;
- --ink: #0b0b0b; --ink-2: #52514e;
- --grid: #e1e0d9; --border: rgba(11,11,11,0.10);
- --series: #4a3aa7;
- --bad: #b23030; --bad-mark: #d03b3b;
- --link: #4a3aa7;
- }
- @media (prefers-color-scheme: dark) {
- :root {
- --plane: #0d0d0d; --surface: #1a1a19;
- --ink: #ffffff; --ink-2: #c3c2b7;
- --grid: #2c2c2a; --border: rgba(255,255,255,0.10);
- --series: #9085e9;
- --bad: #e66767; --bad-mark: #e66767;
- --link: #9085e9;
- }
- }
- * { box-sizing: border-box; margin: 0; }
- body {
- background: var(--plane); color: var(--ink);
- font: 16px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
- padding: 24px 16px 48px;
- }
- .wrap { max-width: 760px; margin: 0 auto; }
- a { color: var(--link); }
- a:focus-visible { outline: 2px solid var(--series); outline-offset: 2px; border-radius: 4px; }
-
- header { display: flex; align-items: center; gap: 14px; margin-bottom: 6px; }
- header img { width: 44px; height: 44px; border-radius: 10px; }
- header h1 { font-size: 22px; font-weight: 650; }
- header .brand { color: var(--ink-2); font-size: 14px; }
- .lede { color: var(--ink-2); margin: 6px 0 2px; }
- .asof { font-weight: 600; margin: 10px 0 24px; }
-
- section h2 { font-size: 15px; font-weight: 650; margin: 0 0 10px; }
- section { margin-bottom: 28px; }
-
- /* Contributor rows: identity block | commit bar | count — the .bars spec
- from books.html grown an avatar and a bio line. Rows are real text. */
- .bars { list-style: none; padding: 0; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; }
- .bars li { display: grid; grid-template-columns: minmax(180px, 260px) 1fr 5em; align-items: center; gap: 12px; padding: 12px 16px; }
- .bars li + li { border-top: 1px solid var(--grid); }
+ /* Contributors-only: the identity block (avatar / AI mark, name, kind, bio)
+ and the wider first column. Shared .bars supplies the container, rows, bar
+ mark, and count; everything else shared lives in status.css. */
+ .bars li { grid-template-columns: minmax(180px, 260px) 1fr 5em; padding: 12px 16px; }
.who { display: flex; gap: 10px; align-items: flex-start; min-width: 0; }
.who img, .who .ai-mark {
width: 36px; height: 36px; border-radius: 50%; flex: none;
@@ -72,15 +32,6 @@
.who .name a:hover { text-decoration: underline; }
.who .kind { font-size: 12px; color: var(--ink-2); font-weight: 400; margin-left: 6px; }
.who .bio { display: block; font-size: 12px; font-weight: 400; color: var(--ink-2); margin-top: 1px; }
- .track { height: 12px; }
- .bar { height: 12px; min-width: 2px; background: var(--series); border-radius: 0 4px 4px 0; display: block; }
- .count { font-size: 14px; color: var(--ink-2); text-align: right; font-variant-numeric: tabular-nums; }
-
- #error { display: none; background: var(--surface); border: 1px solid var(--bad-mark); border-radius: 12px; padding: 14px 16px; margin-bottom: 24px; }
- #error.show { display: block; }
-
- footer { margin-top: 36px; color: var(--ink-2); font-size: 14px; }
- footer p { margin-top: 6px; }
</style>
</head>
<body>
@@ -92,6 +43,14 @@
<h1>Contributors</h1>
</div>
</header>
+ <nav class="status-nav" aria-label="Status">
+ <a href="./index.html">Fleet</a>
+ <a href="./books.html">Income &amp; Expenditure</a>
+ <a href="./security.html">Security</a>
+ <a href="./carbon.html">Carbon use</a>
+ <span aria-current="page">Contributors</span>
+ <a href="./prompts.html">Prompt Ledger</a>
+ </nav>
<p class="lede">Who builds Eleven — humans and AI models on the same metric.
This app is AI-written, so the models appear in the index exactly as the
humans do: <span id="metric">—</span>.</p>
@@ -105,7 +64,6 @@
</section>
<footer>
- <p><a href="./index.html">Fleet status</a> · <a href="./books.html">The books</a> · <a href="./carbon.html">The carbon tally</a></p>
<p>Refreshed from GitHub by tools/contributors.mjs and committed to the
repo. Contributors own their row: edit your bio (or hide yourself) in
web/status/contributors.json by PR.</p>
diff --git a/web/status/index.html b/web/status/index.html
index d7484f5..66cbd55 100644
--- a/web/status/index.html
+++ b/web/status/index.html
@@ -10,65 +10,17 @@
<meta name="color-scheme" content="light dark" />
<title>Eleven — fleet status</title>
<link rel="icon" type="image/png" href="./icon-192.png" />
+<link rel="stylesheet" href="./status.css" />
<style>
- /* Palette roles (validated: dataviz six-checks, light + dark). Series hue is
- the violet slot — the Eleven brand family. Secondary ink is used for ALL
- small text (>=4.5:1 both modes); the muted axis tone is deliberately
- absent — it fails AA at body sizes. */
- :root {
- --plane: #f9f9f7; --surface: #fcfcfb;
- --ink: #0b0b0b; --ink-2: #52514e;
- --grid: #e1e0d9; --border: rgba(11,11,11,0.10);
- --series: #4a3aa7;
- --good: #006300; --good-mark: #0ca30c;
- --warn: #7a4d00;
- --bad: #b23030; --bad-mark: #d03b3b;
- --link: #4a3aa7;
- }
- @media (prefers-color-scheme: dark) {
- :root {
- --plane: #0d0d0d; --surface: #1a1a19;
- --ink: #ffffff; --ink-2: #c3c2b7;
- --grid: #2c2c2a; --border: rgba(255,255,255,0.10);
- --series: #9085e9;
- --good: #0ca30c; --good-mark: #0ca30c;
- --warn: #e0a33c;
- --bad: #e66767; --bad-mark: #e66767;
- --link: #9085e9;
- }
- }
- * { box-sizing: border-box; margin: 0; }
- body {
- background: var(--plane); color: var(--ink);
- font: 16px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
- padding: 24px 16px 48px;
- }
- .wrap { max-width: 760px; margin: 0 auto; }
- a { color: var(--link); }
- a:focus-visible { outline: 2px solid var(--series); outline-offset: 2px; border-radius: 4px; }
-
- header { display: flex; align-items: center; gap: 14px; margin-bottom: 6px; }
- header img { width: 44px; height: 44px; border-radius: 10px; }
- header h1 { font-size: 22px; font-weight: 650; }
- header .brand { color: var(--ink-2); font-size: 14px; }
- nav.pages { font-size: 14px; color: var(--ink-2); margin: 2px 0 10px; }
- nav.pages [aria-current] { color: var(--ink); font-weight: 650; }
- .lede { color: var(--ink-2); margin: 6px 0 2px; }
- .asof { font-weight: 600; margin: 10px 0 24px; }
- .asof .stale { color: var(--bad); font-weight: 600; }
-
- .tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; margin-bottom: 28px; }
- .tile { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px; }
- .tile .label { font-size: 13px; color: var(--ink-2); }
- .tile .value { font-size: 30px; font-weight: 600; margin-top: 2px; }
- .tile .sub { color: var(--ink-2); font-size: 14px; margin-top: 6px; }
+ /* Fleet-only: the capacity band, the serving-hosts + services lists, and the
+ monospace version label. Everything shared lives in status.css. */
+ .tiles { grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); }
- section h2 { font-size: 15px; font-weight: 650; margin: 0 0 10px; }
- section { margin-bottom: 28px; }
+ .bars .ver { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 14px; overflow-wrap: anywhere; }
- /* Capacity: the abstract room-left band — deliberately a word + glyph, no
- numbers (the JSON carries none). Tone classes color it, but the word and
- glyph carry the state on their own (never color alone). */
+ /* Capacity: the abstract room-left band — a word + glyph, no numbers (the
+ JSON carries none). Tone classes only color it; the word and glyph carry
+ the state on their own (never color alone). */
.capacity { margin-bottom: 10px; }
.capacity .value.ok { color: var(--good); }
.capacity .value.warn { color: var(--warn); }
@@ -89,35 +41,14 @@
.hosts .state.bad { color: var(--bad); }
/* Services: word + glyph carry the state; color is supplementary (never
- color alone). */
+ color alone). Replication is full-width for its lag detail. */
.services { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; list-style: none; padding: 0; }
.services li { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 12px 16px; display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.services .name { font-weight: 600; }
.services .state { font-size: 14px; font-weight: 600; white-space: nowrap; }
- /* Replication is the odd one out (it carries a lag detail) — a full-width
- row gives the detail room instead of wrapping inside a narrow tile. */
.services li:last-child { grid-column: 1 / -1; }
.services .state.up { color: var(--good); }
.services .state.down { color: var(--bad); }
-
- /* Version distribution: a single-series horizontal bar list. Every row is
- real text (version + count) so the list is its own table view; the bar is
- decoration for the eye and hidden from AT. Thin mark, 4px rounded
- data-end, square at the baseline. */
- .bars { list-style: none; padding: 0; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; }
- .bars li { display: grid; grid-template-columns: minmax(90px, 160px) 1fr 3.5em; align-items: center; gap: 12px; padding: 10px 16px; }
- .bars li + li { border-top: 1px solid var(--grid); }
- .bars .ver { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 14px; overflow-wrap: anywhere; }
- .bars .track { height: 12px; }
- .bars .bar { height: 12px; min-width: 2px; background: var(--series); border-radius: 0 4px 4px 0; }
- .bars .count { font-size: 14px; color: var(--ink-2); text-align: right; font-variant-numeric: tabular-nums; }
- .bars .empty { display: block; color: var(--ink-2); }
-
- #error { display: none; background: var(--surface); border: 1px solid var(--bad-mark); border-radius: 12px; padding: 14px 16px; margin-bottom: 24px; }
- #error.show { display: block; }
-
- footer { margin-top: 36px; color: var(--ink-2); font-size: 14px; }
- footer p { margin-top: 6px; }
</style>
</head>
<body>
@@ -129,8 +60,14 @@
<h1>Fleet status</h1>
</div>
</header>
- <!-- Cross-links between the three status pages; the current one is inert. -->
- <nav class="pages" aria-label="Status pages"><span aria-current="page">Fleet</span> · <a href="./books.html">Books</a> · <a href="./security.html">Security</a></nav>
+ <nav class="status-nav" aria-label="Status">
+ <span aria-current="page">Fleet</span>
+ <a href="./books.html">Income &amp; Expenditure</a>
+ <a href="./security.html">Security</a>
+ <a href="./carbon.html">Carbon use</a>
+ <a href="./contributors.html">Contributors</a>
+ <a href="./prompts.html">Prompt Ledger</a>
+ </nav>
<p class="lede">Live aggregate health of the Eleven fleet — how many spaces are running
(always-on or on-demand, and of the on-demand ones how many are awake), what
they run, and whether the shared services are up.
@@ -181,7 +118,6 @@
</section>
<footer>
- <p><a href="./books.html">The books — income, in aggregate</a> · <a href="./carbon.html">The carbon tally — what the AI build cost</a> · <a href="./contributors.html">Contributors</a></p>
<p>Published every 5 minutes from aggregate counters on the fleet.
By design it contains no hostnames and nothing about any individual space.</p>
</footer>
diff --git a/web/status/prompts.html b/web/status/prompts.html
index 97997a8..843ded2 100644
--- a/web/status/prompts.html
+++ b/web/status/prompts.html
@@ -15,36 +15,12 @@
<meta name="color-scheme" content="light dark" />
<title>Eleven — the prompt registry</title>
<link rel="icon" type="image/png" href="./icon-192.png" />
+<link rel="stylesheet" href="./status.css" />
<style>
- /* Same validated palette roles as index.html / carbon.html. */
- :root {
- --plane: #f9f9f7; --surface: #fcfcfb;
- --ink: #0b0b0b; --ink-2: #52514e;
- --grid: #e1e0d9; --border: rgba(11,11,11,0.10);
- --series: #4a3aa7;
- --bad: #b23030;
- --link: #4a3aa7;
- --redact-bg: #f3e9d8; --redact-ink: #7a5b16;
- }
- @media (prefers-color-scheme: dark) {
- :root {
- --plane: #0d0d0d; --surface: #1a1a19;
- --ink: #ffffff; --ink-2: #c3c2b7;
- --grid: #2c2c2a; --border: rgba(255,255,255,0.10);
- --series: #9085e9;
- --bad: #e66767;
- --link: #9085e9;
- --redact-bg: #3a3220; --redact-ink: #e0c274;
- }
- }
- * { box-sizing: border-box; margin: 0; }
- body {
- background: var(--plane); color: var(--ink);
- font: 16px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
- padding: 24px 16px 48px;
- }
+ /* Prompt-ledger-only styles; the tokens, reset, masthead, and the six-item
+ nav live in status.css. This page is wider than the others and adds the
+ search controls, the sessions table, and the transcript viewer. */
.wrap { max-width: 860px; margin: 0 auto; }
- a { color: var(--link); }
a:focus-visible, summary:focus-visible, button:focus-visible, input:focus-visible {
outline: 2px solid var(--series); outline-offset: 2px; border-radius: 4px;
}
@@ -119,21 +95,29 @@
}
.legend dt { font-weight: 600; margin-top: 8px; }
.legend dd { color: var(--ink-2); margin-left: 0; }
- nav.status-nav { margin-bottom: 4px; font-size: 0.9rem; }
.backlink { display: inline-block; margin: 8px 0; }
#viewer-status[hidden], #viewer[hidden], #home[hidden] { display: none; }
</style>
</head>
<body>
<div class="wrap">
- <nav class="status-nav" aria-label="Status pages">
- <a href="./index.html">fleet</a> · <a href="./books.html">the books</a> ·
- <a href="./security.html">security</a> · <a href="./carbon.html">carbon</a> ·
- <a href="./contributors.html">contributors</a> · <strong>prompts</strong>
+ <header>
+ <img src="./icon-192.png" alt="" />
+ <div>
+ <p class="brand">Eleven Messenger</p>
+ <h1>The prompt registry</h1>
+ </div>
+ </header>
+ <nav class="status-nav" aria-label="Status">
+ <a href="./index.html">Fleet</a>
+ <a href="./books.html">Income &amp; Expenditure</a>
+ <a href="./security.html">Security</a>
+ <a href="./carbon.html">Carbon use</a>
+ <a href="./contributors.html">Contributors</a>
+ <span aria-current="page">Prompt Ledger</span>
</nav>
<div id="home">
- <h1>The prompt registry</h1>
<!-- 👨 Paul's wording — do not rewrite -->
<p class="sub">Every AI session transcript behind Eleven, published whole, secrets
redacted. If an AI vendor can see it, you should be able to see it too.</p>
diff --git a/web/status/security.html b/web/status/security.html
index 0281b21..28611e8 100644
--- a/web/status/security.html
+++ b/web/status/security.html
@@ -28,71 +28,19 @@
<meta name="color-scheme" content="light dark" />
<title>Eleven — security</title>
<link rel="icon" type="image/png" href="./icon-192.png" />
+<link rel="stylesheet" href="./status.css" />
<style>
- /* Same validated palette roles as index.html (dataviz six-checks, light +
- dark); violet series hue, secondary ink for all small text (>=4.5:1).
- Status colors are reserved for state and always ride with a glyph + word —
- never color alone. */
- :root {
- --plane: #f9f9f7; --surface: #fcfcfb;
- --ink: #0b0b0b; --ink-2: #52514e;
- --grid: #e1e0d9; --border: rgba(11,11,11,0.10);
- --series: #4a3aa7;
- --good: #006300;
- --warn: #7a4d00;
- --bad: #b23030; --bad-mark: #d03b3b;
- --link: #4a3aa7;
- }
- @media (prefers-color-scheme: dark) {
- :root {
- --plane: #0d0d0d; --surface: #1a1a19;
- --ink: #ffffff; --ink-2: #c3c2b7;
- --grid: #2c2c2a; --border: rgba(255,255,255,0.10);
- --series: #9085e9;
- --good: #0ca30c;
- --warn: #e0a33c;
- --bad: #e66767; --bad-mark: #e66767;
- --link: #9085e9;
- }
- }
- * { box-sizing: border-box; margin: 0; }
- body {
- background: var(--plane); color: var(--ink);
- font: 16px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
- padding: 24px 16px 48px;
- }
- .wrap { max-width: 760px; margin: 0 auto; }
- a { color: var(--link); }
- a:focus-visible { outline: 2px solid var(--series); outline-offset: 2px; border-radius: 4px; }
-
- header { display: flex; align-items: center; gap: 14px; margin-bottom: 6px; }
- header img { width: 44px; height: 44px; border-radius: 10px; }
- header h1 { font-size: 22px; font-weight: 650; }
- header .brand { color: var(--ink-2); font-size: 14px; }
- nav.pages { font-size: 14px; color: var(--ink-2); margin: 2px 0 10px; }
- nav.pages [aria-current] { color: var(--ink); font-weight: 650; }
- .lede { color: var(--ink-2); margin: 6px 0 2px; }
- .asof { font-weight: 600; margin: 10px 0 24px; }
- .asof .stale { color: var(--bad); font-weight: 600; }
-
- .tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 10px; margin-bottom: 12px; }
- .tile { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px; }
- .tile .label { font-size: 13px; color: var(--ink-2); }
- .tile .value { font-size: 26px; font-weight: 600; margin-top: 2px; }
+ /* Security-only: tighter tiles + status tones on the values, the fact
+ checklists, and the scan-pending notice. Status colors always ride with a
+ glyph + word — never color alone. Everything shared lives in status.css. */
+ .tiles { margin-bottom: 12px; }
+ .tile .value { font-size: 26px; }
.tile .value.ok { color: var(--good); }
.tile .value.warn { color: var(--warn); }
- .tile .sub { color: var(--ink-2); font-size: 14px; margin-top: 6px; }
.tile .value.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 22px; }
- section h2 { font-size: 15px; font-weight: 650; margin: 0 0 10px; }
- section { margin-bottom: 28px; }
- .note { color: var(--ink-2); font-size: 14px; }
- /* One-line design explainer directly under each section heading. */
- .explain { margin: -4px 0 12px; }
-
- /* Fact checklists: one card row per attested fact. The leading glyph is
- decorative reinforcement; the words carry the meaning (never color, or
- glyph, alone). Edges use the brand violet marker — they are disclosed
+ /* Fact checklists: the glyph reinforces, the words carry the meaning (never
+ color, or glyph, alone). Edges use the brand violet marker — disclosed
tradeoffs, not statuses. */
.facts { list-style: none; padding: 0; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; }
.facts li { display: grid; grid-template-columns: 1.4em 1fr; gap: 8px; padding: 11px 16px; }
@@ -103,9 +51,6 @@
#pending { display: none; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px; margin-bottom: 12px; }
#pending.show { display: block; }
-
- footer { margin-top: 36px; color: var(--ink-2); font-size: 14px; }
- footer p { margin-top: 6px; }
</style>
</head>
<body>
@@ -117,7 +62,14 @@
<h1>Security</h1>
</div>
</header>
- <nav class="pages" aria-label="Status pages"><a href="./index.html">Fleet</a> · <a href="./books.html">Books</a> · <span aria-current="page">Security</span></nav>
+ <nav class="status-nav" aria-label="Status">
+ <a href="./index.html">Fleet</a>
+ <a href="./books.html">Income &amp; Expenditure</a>
+ <span aria-current="page">Security</span>
+ <a href="./carbon.html">Carbon use</a>
+ <a href="./contributors.html">Contributors</a>
+ <a href="./prompts.html">Prompt Ledger</a>
+ </nav>
<p class="lede">Eleven’s security posture, published as it stands. This is
what is true by design and how the hygiene looks today — never a map of open
problems: if a scan finds something, this page shows a <em>count</em> until
@@ -250,7 +202,6 @@
</section>
<footer>
- <p><a href="./index.html">Fleet status</a> · <a href="./books.html">The books</a> · <a href="./carbon.html">The carbon tally</a> · <a href="./contributors.html">Contributors</a></p>
<p>The commitment, hardening, and known-edges sections are attested
content: they live in the product’s repository and change only by reviewed
commit. The scan block is generated at build time against the exact binary
diff --git a/web/status/status.css b/web/status/status.css
new file mode 100644
index 0000000..81b48fb
--- /dev/null
+++ b/web/status/status.css
@@ -0,0 +1,103 @@
+/* 🤖 Shared stylesheet for the public status pages (web/status/*.html): fleet,
+ income & expenditure, security, carbon, contributors, and the prompt ledger.
+ ONE design system — the design tokens, the reset, and every component the
+ pages share (the brand masthead, the six-item nav, tiles, the single-series
+ bar list, notes, the "couldn't load" alert, footer). Each page <link>s this
+ and keeps only a SMALL inline <style> for its own unique bits.
+
+ Same-origin: uploaded from the same status bucket as the pages, so the
+ privacy property still holds — a page pulls its styles from its own origin,
+ never a third-party host. Published by deploy/status-publish.sh, which uploads
+ this file BEFORE the html so a page is never served ahead of its stylesheet.
+
+ Palette: the dataviz-validated role set (six-checks, light + dark) already in
+ use across all six pages — the violet slot (--series / --link) is the Eleven
+ brand family; --ink-2 carries ALL small text (>=4.5:1 in both modes). The
+ names are the ones every page already used; this file is simply their single
+ home (the union of every role any page referenced). */
+:root {
+ --plane: #f9f9f7; --surface: #fcfcfb;
+ --ink: #0b0b0b; --ink-2: #52514e;
+ --grid: #e1e0d9; --border: rgba(11,11,11,0.10);
+ --series: #4a3aa7;
+ --good: #006300; --good-mark: #0ca30c;
+ --warn: #7a4d00;
+ --bad: #b23030; --bad-mark: #d03b3b;
+ --link: #4a3aa7;
+ --redact-bg: #f3e9d8; --redact-ink: #7a5b16;
+}
+@media (prefers-color-scheme: dark) {
+ :root {
+ --plane: #0d0d0d; --surface: #1a1a19;
+ --ink: #ffffff; --ink-2: #c3c2b7;
+ --grid: #2c2c2a; --border: rgba(255,255,255,0.10);
+ --series: #9085e9;
+ --good: #0ca30c; --good-mark: #0ca30c;
+ --warn: #e0a33c;
+ --bad: #e66767; --bad-mark: #e66767;
+ --link: #9085e9;
+ --redact-bg: #3a3220; --redact-ink: #e0c274;
+ }
+}
+
+* { box-sizing: border-box; margin: 0; }
+body {
+ background: var(--plane); color: var(--ink);
+ font: 16px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
+ padding: 24px 16px 48px;
+}
+.wrap { max-width: 760px; margin: 0 auto; }
+a { color: var(--link); }
+a:focus-visible { outline: 2px solid var(--series); outline-offset: 2px; border-radius: 4px; }
+
+/* Masthead: the brand line + page title, identical on every page. */
+header { display: flex; align-items: center; gap: 14px; margin-bottom: 6px; }
+header img { width: 44px; height: 44px; border-radius: 10px; }
+header h1 { font-size: 22px; font-weight: 650; }
+header .brand { color: var(--ink-2); font-size: 14px; }
+
+/* The status nav — a horizontal stepper through the six public dashboards,
+ wrapping onto more rows on narrow widths. The current page is a non-link,
+ marked aria-current and bolded, so weight (not color alone) carries it. */
+nav.status-nav { display: flex; flex-wrap: wrap; gap: 6px 16px; font-size: 14px; margin: 8px 0 14px; }
+nav.status-nav a { color: var(--link); text-decoration: none; }
+nav.status-nav a:hover { text-decoration: underline; }
+nav.status-nav [aria-current="page"] { color: var(--ink); font-weight: 650; }
+
+.lede { color: var(--ink-2); margin: 6px 0 2px; }
+.asof { font-weight: 600; margin: 10px 0 24px; }
+.asof .stale { color: var(--bad); font-weight: 600; }
+
+/* Section rhythm + heading (pages built from <section> blocks). */
+section { margin-bottom: 28px; }
+section h2 { font-size: 15px; font-weight: 650; margin: 0 0 10px; }
+
+/* Muted body notes and the one-line design explainer under a section heading. */
+.note { color: var(--ink-2); font-size: 14px; }
+.explain { margin: -4px 0 12px; }
+
+/* Stat tiles. */
+.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 10px; margin-bottom: 28px; }
+.tile { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px; }
+.tile .label { font-size: 13px; color: var(--ink-2); }
+.tile .value { font-size: 30px; font-weight: 600; margin-top: 2px; }
+.tile .sub { color: var(--ink-2); font-size: 14px; margin-top: 6px; }
+
+/* Single-series horizontal bar list (fleet versions, book tiers, the carbon
+ ledger, contributors): every row is real text — its own table view — and the
+ bar is aria-hidden decoration. Thin 12px mark, 4px rounded data-end, square at
+ the baseline. Per-page first/last column widths override .bars li inline. */
+.bars { list-style: none; padding: 0; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; }
+.bars li { display: grid; grid-template-columns: minmax(90px, 160px) 1fr 3.5em; align-items: center; gap: 12px; padding: 10px 16px; }
+.bars li + li { border-top: 1px solid var(--grid); }
+.bars .track { height: 12px; }
+.bars .bar { height: 12px; min-width: 2px; background: var(--series); border-radius: 0 4px 4px 0; display: block; }
+.bars .count { font-size: 14px; color: var(--ink-2); text-align: right; font-variant-numeric: tabular-nums; }
+.bars .empty { display: block; color: var(--ink-2); }
+
+/* The inline "couldn't load the feed" alert (retries every minute). */
+#error { display: none; background: var(--surface); border: 1px solid var(--bad-mark); border-radius: 12px; padding: 14px 16px; margin-bottom: 24px; }
+#error.show { display: block; }
+
+footer { margin-top: 36px; color: var(--ink-2); font-size: 14px; }
+footer p { margin-top: 6px; }