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>
9 files changed,
+226
−375
deploy/status-publish.sh+10 −3docs/status.md+10 −3web/status/books.html+17 −70web/status/carbon.html+22 −64web/status/contributors.html+13 −55web/status/index.html+17 −81web/status/prompts.html+18 −34web/status/security.html+16 −65web/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 & 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 & 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 & 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 & 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 & 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 & 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; } |