Files
observer/web/src/index.css
rob thijssen b006f54cd5
All checks were successful
deploy / build (push) Successful in 7m12s
deploy / deploy-web (push) Successful in 5s
deploy / deploy-api (push) Successful in 14s
feat: a runtime's own page
`/quantus/runtime/152` — every pallet, call, event, error, storage entry and
constant the runtime declares, with the constants' values decoded against their
own declared types. Nothing transcribed from a source tree: a source tree says
what the chain should be running, metadata says what it ran.

The set of runtimes is found rather than waited for. Decoding caches one the
moment it needs it, so left alone the cache is "whatever the backfill has walked
past" — days on a chain a million blocks deep. `spec_version` only increases, so
the boundaries are a sorted sequence and bisection finds each in log₂(height)
probes. On Heisenberg that turned up six runtimes and the block each took over
at: v126 from 1, v128 from 132, v131 from 342,813, v136 from 669,129, v144 from
812,055, v148 from 977,079 — two more than the four upgrade boundaries this
approach was originally verified against.

Discovery is its own task, not a poll step. `state_getRuntimeVersion` at an old
block makes the node instantiate the runtime WASM from that block's state:
~4 s against Heisenberg's endpoint versus ~0.25 s at the tip, and a hundred of
those inside a four-second loop stalls difficulty and the summary broadcast for
minutes on every start.

Reading Heisenberg's two ends side by side is the case for the page. Between
v126 and v148 the chain dropped Referenda, ConvictionVoting, Recovery, Assets
and AssetsHolder, added Vesting and Origins, gained `WeightReclaim` and moved
`ChargeTransactionPayment` after the two Quantus-specific extensions. Every one
breaks a decoder written against the other version and none is visible from a
block.

`U256`/`U512` now render as one decimal rather than four or eight little-endian
limbs, identified by registry path exactly as `AccountId32` already was.
Difficulty as `[1189189, 0, 0, 0, 0, 0, 0, 0]` describes the bytes correctly and
tells a reader nothing. `ChainSummary` gained `spec_version`/`spec_name` so the
footer can say what the site is decoding against, and link to it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jp6a8EDar9ueEhAxzep4V5
2026-09-09 17:45:28 +03:00

1194 lines
26 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* blackbeard.observer — the design system.
*
* Dark, and only dark. This is a deliberate commitment rather than an omission:
* the site is a scoreboard for a proof-of-work chain, read in the same rooms as
* the miners it reports on, and a light variant would be a second palette to
* validate for the sake of a context this content does not have. `color-scheme`
* is stated so the browser paints form controls and scrollbars to match instead
* of flashing white.
*
* The two data colours were validated with the dataviz palette checker against
* the chart surface (#14110d): bronze #bd8829 passes the lightness band, chroma
* floor and 3:1 contrast. Bronze and crimson are adjacent hues and do NOT pass
* as a categorical pair — which is why no chart here uses them as two series.
* Every chart is single-series (magnitude, one hue); identity is carried by
* labels and row treatment, never by a second colour a reader has to tell apart.
*/
:root {
color-scheme: dark;
/* Surfaces, warm-black — a cold grey reads as a developer tool, not a arena. */
--surface-0: #0d0b09;
--surface-1: #14110d;
--surface-2: #1c1813;
--surface-3: #262019;
--border: #2f2719;
--border-strong: #453923;
/* Ink. Contrast against --surface-1: 16.0, 6.3, 3.7. */
--text-primary: #f2ece0;
--text-secondary: #a2937c;
--text-muted: #7a6c59;
/* The single data hue: magnitude, share bars, the history area. */
--data: #bd8829;
--data-bright: #e0a63a;
--data-wash: rgba(189, 136, 41, 0.1);
/* Identity and alarm. Never a second series alongside --data. */
--accent: #d8453a;
--accent-bright: #f05a4c;
--accent-wash: rgba(216, 69, 58, 0.1);
--good: #3f9d76;
--warn: #d9a441;
--font-display: 'Iowan Old Style', 'Palatino Linotype', Palatino, 'Book Antiqua', Georgia, serif;
--font-body: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
--font-mono: ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, monospace;
--rule: 1px solid var(--border);
--shadow: 0 1px 0 rgba(255, 255, 255, 0.03) inset;
}
* {
box-sizing: border-box;
}
html,
body,
#root {
height: 100%;
}
body {
margin: 0;
background:
/* A faint warm glow behind the masthead, so the page has a top rather than
being a flat field. Fixed so it does not travel with the scroll. */
radial-gradient(120% 60% at 50% -10%, rgba(189, 136, 41, 0.08), transparent 60%),
var(--surface-0);
background-attachment: fixed;
color: var(--text-primary);
font-family: var(--font-body);
font-size: 15px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
a {
color: inherit;
text-decoration: none;
}
/* Visible focus everywhere. The site is a table of links; keyboard users need
to see where they are, and the default ring is invisible on this ground. */
:focus-visible {
outline: 2px solid var(--data-bright);
outline-offset: 2px;
border-radius: 2px;
}
/* ---- type ---------------------------------------------------------------- */
.display {
font-family: var(--font-display);
font-weight: 700;
letter-spacing: 0.02em;
}
.eyebrow {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--text-muted);
}
.numeral {
font-family: var(--font-mono);
font-variant-numeric: tabular-nums;
/* Tabular figures are the whole point: a column of hashrates that shifts
horizontally on every update is unreadable on a page that updates every
few seconds. */
}
/* ---- layout -------------------------------------------------------------- */
.shell {
max-width: 1180px;
margin: 0 auto;
padding: 0 20px 72px;
}
.panel {
background: var(--surface-1);
border: var(--rule);
box-shadow: var(--shadow);
}
.panel-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
padding: 14px 18px;
border-bottom: var(--rule);
flex-wrap: wrap;
}
.panel-title {
font-family: var(--font-display);
font-size: 17px;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
margin: 0;
}
/* ---- masthead ------------------------------------------------------------ */
.masthead {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
padding: 22px 0 18px;
flex-wrap: wrap;
}
.mark {
display: flex;
align-items: center;
gap: 12px;
}
.mark-name {
font-family: var(--font-display);
font-size: 25px;
font-weight: 700;
letter-spacing: 0.04em;
line-height: 1;
}
.mark-name span {
color: var(--data);
}
.mark-tagline {
font-size: 11px;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--text-muted);
margin-top: 5px;
}
.masthead-right {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
}
/* ---- connection light ---------------------------------------------------- */
.status {
display: inline-flex;
align-items: center;
gap: 7px;
font-size: 11px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--text-secondary);
}
.status-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--text-muted);
flex: none;
}
.status-live .status-dot {
background: var(--good);
box-shadow: 0 0 0 3px rgba(63, 157, 118, 0.18);
}
.status-reconnecting .status-dot,
.status-connecting .status-dot {
background: var(--warn);
animation: pulse 1.1s ease-in-out infinite;
}
.status-offline .status-dot {
background: var(--accent);
}
@keyframes pulse {
50% {
opacity: 0.25;
}
}
/* ---- chain switcher & window selector ------------------------------------ */
.segmented {
display: inline-flex;
border: var(--rule);
background: var(--surface-1);
}
.segmented a {
appearance: none;
text-decoration: none;
display: inline-block;
border: 0;
background: transparent;
color: var(--text-secondary);
font: inherit;
font-size: 12px;
letter-spacing: 0.1em;
text-transform: uppercase;
padding: 7px 13px;
cursor: pointer;
white-space: nowrap;
}
.segmented a + a {
border-left: var(--rule);
}
.segmented a:hover {
color: var(--text-primary);
background: var(--surface-2);
}
.segmented a[aria-current='page'] {
background: var(--data-wash);
color: var(--data-bright);
}
/* ---- chain nav ----------------------------------------------------------- */
.chains {
display: flex;
align-items: center;
gap: 14px;
flex-wrap: wrap;
padding: 0 0 18px;
}
.chains-label {
flex: none;
}
.chains-list {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.chain-chip {
display: inline-flex;
/* The tracked chips are links now, so they arrive underlined. */
text-decoration: none;
align-items: center;
gap: 8px;
appearance: none;
background: var(--surface-1);
border: var(--rule);
color: var(--text-secondary);
font: inherit;
font-size: 13px;
padding: 6px 11px;
cursor: pointer;
white-space: nowrap;
}
.chain-chip:hover:not(:disabled) {
color: var(--text-primary);
border-color: var(--border-strong);
background: var(--surface-2);
}
.chain-chip.is-active {
color: var(--data-bright);
border-color: var(--data);
background: var(--data-wash);
}
.chain-name {
font-weight: 600;
}
.chain-nodes {
font-family: var(--font-mono);
font-variant-numeric: tabular-nums;
font-size: 11px;
color: var(--text-muted);
background: var(--surface-3);
padding: 1px 6px;
}
.chain-chip.is-active .chain-nodes {
color: var(--data-bright);
background: rgba(189, 136, 41, 0.18);
}
/* ---- stat tiles ---------------------------------------------------------- */
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(178px, 1fr));
gap: 1px;
background: var(--border);
border: var(--rule);
margin-bottom: 26px;
}
.stat {
background: var(--surface-1);
padding: 15px 18px 16px;
min-width: 0;
}
/* Two columns and five tiles leaves a blank cell. Letting the hero span the row
makes six, which fills the grid — and gives the headline number the width it
deserves on the screen where it is most cramped. */
@media (max-width: 620px) {
.stat-hero {
grid-column: 1 / -1;
}
}
.stat-label {
display: flex;
align-items: center;
gap: 6px;
font-size: 10px;
font-weight: 600;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--text-muted);
}
.stat-value {
font-family: var(--font-mono);
font-variant-numeric: tabular-nums;
font-size: 25px;
font-weight: 600;
line-height: 1.15;
margin-top: 7px;
overflow-wrap: anywhere;
}
.stat-hero .stat-value {
font-size: 33px;
color: var(--data-bright);
}
.stat-note {
font-size: 11px;
color: var(--text-muted);
margin-top: 5px;
min-height: 1.2em;
}
.stat-note.nominal {
color: var(--warn);
}
/* The note line doubles as the sparkline's readout, so a hover changes its
text. Brightening it says the line is being read rather than captioned —
without moving anything, which at this size would be a flicker. */
.stat-note.reading {
color: var(--text-secondary);
font-variant-numeric: tabular-nums;
}
/* ---- sparklines ---------------------------------------------------------- */
/* Reserved on every tile, drawn on four: the fifth (height) has nothing worth
plotting, and an absent slot there would leave one tile's baselines out of
step with the rest of the row. */
.spark {
height: 30px;
margin-top: 9px;
}
.spark-svg {
display: block;
touch-action: none;
}
.spark-svg:focus-visible {
outline: 2px solid var(--data-bright);
outline-offset: 2px;
}
/* Single series, single hue — the same bronze every other mark on the site
uses. The palette's second colour is reserved for identity and alarm, and as
an adjacent hue it would not survive CVD as a second series here. */
.spark-line {
fill: none;
stroke: var(--data);
stroke-width: 2;
stroke-linejoin: round;
stroke-linecap: round;
}
/* Emphasis on the newest point, as a lighter step rather than a second hue.
`--data-bright` sits above the validator's categorical lightness band (L
0.762 against a 0.480.67 ceiling) — that check governs slots a reader has to
tell apart from each other, which a single-hue emphasis step is not. It is
the same token the hero figure already wears, and it clears contrast against
the tile surface. Do not "fix" it by giving the marker the accent colour. */
.spark-marker {
fill: var(--data-bright);
}
.spark-crosshair {
stroke: var(--border-strong);
stroke-width: 1;
}
/* The miner's identifiers, stacked label-over-value. A definition list because
that is what it is: terms and their values, which also gives a screen reader
the pairing without any ARIA. */
.identifiers {
display: grid;
grid-template-columns: auto 1fr;
gap: 2px 12px;
margin: 8px 0 0;
font-size: 11px;
}
.identifiers dt {
color: var(--text-muted);
letter-spacing: 0.1em;
text-transform: uppercase;
font-size: 9px;
align-self: center;
}
.identifiers dd {
margin: 0;
color: var(--text-secondary);
overflow-wrap: anywhere;
}
/* ---- block facts --------------------------------------------------------- */
/* The same grid as the stat tiles: this is the same kind of content — a handful
of single values with a caveat each — and giving it a second look would say
the two are different sorts of fact when they are not. */
.facts {
display: grid;
/* Three, not auto-fit. There are exactly six facts, and auto-fit resolves to
five at this width — leaving one on a row of its own beside four empty
cells that read as a grid that failed to load. Six divides evenly by three
and by two, so every breakpoint below is a full rectangle. A seventh fact
would need this revisited, which is the right amount of friction. */
grid-template-columns: repeat(3, 1fr);
gap: 1px;
background: var(--border);
border-bottom: var(--rule);
}
@media (max-width: 760px) {
.facts {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 460px) {
.facts {
grid-template-columns: 1fr;
}
}
.fact {
background: var(--surface-1);
padding: 14px 18px 15px;
min-width: 0;
}
.fact-label {
font-size: 10px;
font-weight: 600;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--text-muted);
}
.fact-value {
font-family: var(--font-mono);
font-size: 17px;
margin-top: 7px;
overflow-wrap: anywhere;
}
.fact-note {
font-size: 11px;
color: var(--text-muted);
margin-top: 5px;
}
/* A value nobody has. Muted and in the body face so it does not read as data —
it is a sentence explaining an absence, and formatting it like a figure would
invite it to be compared with one. */
.fact-unknown {
font-family: var(--font-body);
font-size: 12px;
color: var(--text-muted);
}
.block-links {
padding: 15px 18px 18px;
display: grid;
gap: 12px;
font-size: 12px;
}
.block-links a {
color: var(--text-secondary);
overflow-wrap: anywhere;
}
.block-links a:hover {
color: var(--data-bright);
}
/* The one place the accent hue belongs: not a series, a state. A block that
lost its height is the single most important thing its page can say. */
.badge {
margin-left: 10px;
padding: 2px 7px;
font-family: var(--font-body);
font-size: 10px;
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
vertical-align: middle;
}
.badge-warn {
background: var(--accent-wash);
color: var(--accent-bright);
border: 1px solid var(--accent);
}
a.ticker-height:hover {
color: var(--data-bright);
}
/* ---- leaderboard --------------------------------------------------------- */
.board {
width: 100%;
border-collapse: collapse;
}
.board th {
font-size: 10px;
font-weight: 600;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--text-muted);
text-align: right;
padding: 10px 14px;
border-bottom: var(--rule);
white-space: nowrap;
}
.board th:first-child,
.board th.left {
text-align: left;
}
.board td {
padding: 9px 14px;
border-bottom: 1px solid rgba(47, 39, 25, 0.55);
text-align: right;
white-space: nowrap;
}
.board td:first-child,
.board td.left {
text-align: left;
}
.board tbody tr:hover {
background: var(--surface-2);
}
/* The pinned row: a left rule and a wash, plus an explicit "YOU" chip. Three
redundant cues, none of them colour alone. */
.board tbody tr.mine {
background: var(--accent-wash);
box-shadow: inset 3px 0 0 var(--accent);
}
.board tbody tr.mine:hover {
background: rgba(216, 69, 58, 0.16);
}
.rank {
font-family: var(--font-mono);
font-variant-numeric: tabular-nums;
font-size: 14px;
color: var(--text-muted);
width: 1%;
}
.rank-1,
.rank-2,
.rank-3 {
color: var(--data-bright);
font-weight: 700;
}
.miner-cell {
display: flex;
align-items: center;
gap: 9px;
min-width: 0;
}
.miner-name {
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
}
.miner-name.anonymous {
font-family: var(--font-mono);
font-weight: 400;
font-size: 13px;
color: var(--text-secondary);
}
.chip {
font-size: 9px;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
padding: 2px 6px;
border: 1px solid currentColor;
flex: none;
line-height: 1.4;
}
.chip-you {
color: var(--accent-bright);
}
.chip-named {
color: var(--text-muted);
}
/* A mapping resting on one block, or on a narrow lead. Dashed rather than a
second colour: the palette's accent is reserved for identity and alarm, and
this is neither — it is the same claim held more loosely, which a change in
border weight says without introducing a hue a reader has to decode. */
.chip-guess {
border-style: dashed;
opacity: 0.85;
}
/* Share: the percentage as the direct label, the bar beneath it as magnitude.
One hue, 4px rounded data-end at the far side, square against the baseline it
grows from. */
.share-cell {
/* Wide enough that the bar has usable resolution between a 20% leader and
a 0.6% tail; narrower and every small miner's bar is the same sliver. */
min-width: 92px;
}
.share-bar {
position: relative;
height: 5px;
margin-top: 4px;
background: var(--surface-3);
}
.share-bar > i {
display: block;
height: 100%;
background: var(--data);
border-radius: 0 3px 3px 0;
}
tr.mine .share-bar > i {
background: var(--accent);
}
/* Below this width the table would need a horizontal scrollbar, and a column
behind a scrollbar is a column nobody reads. "Last block" is the one a reader
can most easily do without — the live ticker beside the table already says
who mined most recently. */
@media (max-width: 1320px) {
.board .last-seen {
display: none;
}
}
/* On a phone the table still scrolls inside its own box — which is correct for
a data table — but dropping the least load-bearing column and tightening the
cells means far less of it is behind that scroll. Blocks, share and estimated
hashrate, the three a miner actually came for, stay visible without scrolling. */
@media (max-width: 720px) {
.board .streak {
display: none;
}
.board th,
.board td {
padding: 9px 9px;
}
.miner-name {
max-width: 150px;
}
}
.pin-button {
appearance: none;
background: transparent;
border: 0;
color: var(--text-muted);
cursor: pointer;
font: inherit;
font-size: 15px;
line-height: 1;
padding: 3px 5px;
}
.pin-button:hover {
color: var(--data-bright);
}
.pin-button[aria-pressed='true'] {
color: var(--accent-bright);
}
/* ---- block ticker -------------------------------------------------------- */
.ticker-row {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 12px;
align-items: baseline;
padding: 8px 18px;
border-bottom: 1px solid rgba(47, 39, 25, 0.55);
font-size: 13px;
}
.ticker-row.mine {
background: var(--accent-wash);
box-shadow: inset 3px 0 0 var(--accent);
}
/* Newly arrived blocks flash once. Suppressed for reduced-motion, below. */
.ticker-row.fresh {
animation: arrive 1.4s ease-out;
}
@keyframes arrive {
from {
background: var(--data-wash);
}
}
.ticker-height {
font-family: var(--font-mono);
font-variant-numeric: tabular-nums;
color: var(--text-muted);
font-size: 12px;
}
.ticker-gap {
font-family: var(--font-mono);
font-variant-numeric: tabular-nums;
color: var(--text-muted);
font-size: 12px;
}
/* ---- chart --------------------------------------------------------------- */
.chart {
width: 100%;
display: block;
touch-action: none;
}
.chart .grid {
stroke: var(--border);
stroke-width: 1;
}
.chart .area {
fill: var(--data-wash);
}
.chart .line {
fill: none;
stroke: var(--data);
stroke-width: 2;
stroke-linejoin: round;
stroke-linecap: round;
}
.chart .crosshair {
stroke: var(--border-strong);
stroke-width: 1;
}
.chart .marker {
fill: var(--data);
stroke: var(--surface-1);
stroke-width: 2;
}
.chart-axis {
font-family: var(--font-mono);
font-size: 10px;
fill: var(--text-muted);
}
.tooltip {
position: absolute;
pointer-events: none;
background: var(--surface-3);
border: 1px solid var(--border-strong);
padding: 7px 10px;
font-size: 12px;
line-height: 1.45;
white-space: nowrap;
z-index: 5;
}
.tooltip-label {
color: var(--text-muted);
font-size: 10px;
letter-spacing: 0.1em;
text-transform: uppercase;
}
/* ---- misc ---------------------------------------------------------------- */
.empty {
padding: 40px 18px;
text-align: center;
color: var(--text-muted);
font-size: 13px;
}
.banner {
display: flex;
align-items: center;
gap: 10px;
padding: 11px 16px;
border: 1px solid var(--border-strong);
background: var(--surface-2);
font-size: 13px;
margin-bottom: 20px;
}
.banner-warn {
border-color: rgba(217, 164, 65, 0.4);
color: var(--warn);
}
.footer {
margin-top: 44px;
padding-top: 20px;
border-top: var(--rule);
font-size: 12px;
color: var(--text-muted);
display: flex;
gap: 18px;
flex-wrap: wrap;
justify-content: space-between;
}
.footer a {
color: var(--text-secondary);
text-decoration: underline;
text-underline-offset: 3px;
}
.footer a:hover {
color: var(--data-bright);
}
/* The source link, on its own line under the caveats. `flex-basis: 100%` rather
than a fourth flex item: `space-between` would otherwise strand a short line
against a long one, and this is a different kind of statement from the
three above it — where to check the numbers, not a caveat about them. */
.colophon {
flex-basis: 100%;
padding-top: 14px;
border-top: var(--rule);
}
/* Wide content scrolls inside its own box; the page never scrolls sideways. */
.scroll-x {
overflow-x: auto;
}
.two-col {
display: grid;
grid-template-columns: minmax(0, 1.75fr) minmax(0, 1fr);
gap: 22px;
align-items: start;
}
@media (max-width: 880px) {
.two-col {
grid-template-columns: minmax(0, 1fr);
}
}
/* Motion is decoration here — the numbers carry the information, so it can all
go without losing anything. */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
}
}
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}
/* ---- account activity ---------------------------------------------------- */
/* The buttons in a panel head. Written out once here rather than inline on each
one: they were four copies of the same eight declarations, and a fifth was
about to be a fifth copy. */
.panel-button {
padding: 7px 13px;
font: inherit;
font-size: 12px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--text-secondary);
cursor: pointer;
}
/* One event's fields on one line. They are read left to right as a phrase —
"0.31 QUAN", "to qzp2AxZw…rs8L" — so they flow rather than tabulate: the
fields differ per event type and a column per possible field would be mostly
empty. */
.event-fields {
display: inline-flex;
flex-wrap: wrap;
align-items: baseline;
gap: 4px 10px;
}
.event-field {
display: inline-flex;
align-items: baseline;
gap: 5px;
}
.event-key {
color: var(--text-muted);
font-size: 10px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
/* Deliberately not `.last-seen`, which the leaderboard drops below 1320px. On
a payment history the date is the second thing anyone reads after the amount;
it only goes on a phone, where the table scrolls anyway. */
.event-when {
white-space: nowrap;
}
@media (max-width: 640px) {
.board .event-when {
display: none;
}
}
/* The caveat under a panel, in the same register as the page footer: what the
numbers above are, and what they are not. */
.panel-note {
margin: 0;
padding: 12px 16px;
border-top: var(--rule);
color: var(--text-muted);
font-size: 11px;
line-height: 1.6;
}
/* ---- runtime ------------------------------------------------------------- */
/* The version strip. Reuses the chain chip: it is the same gesture — a row of
short labels where one is current — and a second visual language for it
would say the two are different sorts of choice. */
.runtime-strip {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 14px 16px;
border-bottom: var(--rule);
}
.runtime-pallet {
border-bottom: var(--rule);
}
.runtime-pallets > .runtime-pallet:last-child {
border-bottom: 0;
}
/* A whole-row target. A pallet's name is not the only part of the row worth
clicking — the counts beside it are what tells you whether opening it is
worth doing. */
.runtime-pallet-head {
display: grid;
grid-template-columns: 34px 1fr auto 20px;
align-items: center;
gap: 12px;
width: 100%;
appearance: none;
border: 0;
background: transparent;
color: var(--text-primary);
font: inherit;
text-align: left;
padding: 11px 16px;
cursor: pointer;
}
.runtime-pallet-head:hover {
background: var(--surface-2);
}
.runtime-pallet-name {
font-weight: 600;
}
/* The pallet index, and the variant indexes inside. Monospace and tabular
because they are the bytes on the wire, not decoration: reading down the
column is how you see that a pallet was inserted rather than appended. */
.runtime-index {
font-family: var(--font-mono);
font-variant-numeric: tabular-nums;
font-size: 11px;
color: var(--text-muted);
background: var(--surface-3);
padding: 1px 6px;
text-align: right;
min-width: 26px;
}
.runtime-counts {
font-size: 11px;
color: var(--text-muted);
}
.runtime-chevron {
font-family: var(--font-mono);
color: var(--text-muted);
text-align: center;
}
.runtime-pallet-body {
padding: 4px 16px 18px;
background: var(--surface-1);
}
.runtime-group {
padding: 12px 0 4px;
}
.runtime-envelope {
padding: 14px 16px;
border-bottom: var(--rule);
}
.runtime-list {
list-style: none;
margin: 8px 0 0;
padding: 0;
display: grid;
gap: 5px;
}
/* Flow rather than a grid: the parts of a line are a name, sometimes a value,
sometimes a type and sometimes a sentence, and a column per possible part
would be four columns mostly empty. */
.runtime-list li {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 4px 10px;
font-size: 12px;
line-height: 1.5;
}
.runtime-list code {
font-family: var(--font-mono);
font-size: 12px;
color: var(--text-primary);
overflow-wrap: anywhere;
}
/* The compiled-in value, in the data hue: it is the one thing on the line that
is a fact about this runtime rather than about its shape. */
.runtime-value {
font-family: var(--font-mono);
font-variant-numeric: tabular-nums;
color: var(--data-bright);
overflow-wrap: anywhere;
}
.runtime-type {
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-muted);
overflow-wrap: anywhere;
}
/* `optional` or `default`, which is a property of the entry and belongs on its
line rather than under it. */
.runtime-modifier {
font-size: 10px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted);
}
/* Documentation the runtime carries. Given the whole remaining width so it
wraps as prose, because that is what it is. */
.runtime-doc {
flex-basis: 100%;
color: var(--text-secondary);
font-size: 11px;
padding-left: 36px;
}
.runtime-extensions {
counter-reset: none;
}