/*
 * One stylesheet for both themes.
 *
 * The old page shipped two near-identical files and swapped the <link href> at
 * runtime, which meant a flash of unstyled content and two copies of every
 * rule to keep in sync. Colours are custom properties now: the theme follows
 * the OS by default, and the toggle sets data-theme on <html> to override it.
 */

:root {
	color-scheme: light dark;

	--bg: #ffffff;
	--fg: #1c1c1c;
	--fg-dim: #666666;
	--fg-faint: #999999;
	--rule: #cccccc;
	--rule-soft: #e6e6e6;
	--surface: #f7f7f7;
	--surface-sunk: #eeeeee;
	--accent: #1a7f8e;
	--accent-contrast: #ffffff;

	--legacy-bg: #fdf4e3;

	/* The selected cell was a saturated cyan, which shouted down everything
	   around it. Both are now tints of the same hue, a step apart. */
	--hl-self: #a8e2ee;
	--hl-self-fg: #10343d;
	--hl-op: #dcf2f8;
	--hl-op-fg: #1c4049;
	--hl-pre: rgba(219, 223, 16, 0.62);

	/* Distinguish the parts of an encoding at a glance. */
	--enc-prefix: #9a5b00;
	--enc-sub: #1a6f7d;

	/* Set by the client: the height of the toolbar when it is pinned, zero when
	   it is not. Everything else that sticks to the top offsets by it. */
	--toolbar-h: 0px;

	/* The map's squares are tiny, so its highlights are stronger than the
	   chart's rather than the same colours scaled down. */
	--map-hl-op: #1a86a0;
	--map-hl-self: #f5a623;

	/* Rings, so a highlight reads over any proposal tint. */
	--hl-ring: #1a86a0;
	--hl-ring-strong: #0d5e70;

	/* Tint strength for "colour by proposal". */
	--proposal-s: 55%;
	--proposal-l: 94%;

	/*
	 * The map's version of the same colours, stated rather than derived from the
	 * chart's. A cell is a hundred pixels of tint behind text and wants to be
	 * barely there; a map square is five pixels of nothing else and wants to be
	 * a colour. Deriving one from the other by shifting the lightness went the
	 * right way in the light theme and exactly the wrong way in the dark one,
	 * where the chart tint is already at 17% and the map took it down to 9% —
	 * squares that were, on a #1c1c1c page, black on black.
	 *
	 * Mid-lightness in both themes, because that is what reads against a white
	 * page and a near-black one alike.
	 */
	--proposal-map-s: 62%;
	--proposal-map-l: 45%;

	--radius: 6px;
	--move: 320ms;
	--ease: cubic-bezier(0.2, 0.7, 0.3, 1);
}

@media (prefers-color-scheme: dark) {
	:root:not([data-theme='light']) {
		--bg: #1c1c1c;
		--fg: #f2f2f2;
		--fg-dim: #b4b4b4;
		--fg-faint: #7d7d7d;
		--rule: #3d3d3d;
		--rule-soft: #2c2c2c;
		--surface: #262626;
		--surface-sunk: #222222;
		--accent: #4fd0e0;
		--accent-contrast: #10262a;

		--legacy-bg: #2e2820;

		/* These sit on a #1c1c1c background. The previous values were only a
		   few points lighter than it, so a lit cell read as unlit. */
		--hl-self: #35707e;
		--hl-self-fg: #eaf7fb;
		--hl-op: #27484f;
		--hl-op-fg: #dceef3;
		--hl-pre: rgba(220, 110, 16, 0.62);

		--enc-prefix: #e0a45a;
		--enc-sub: #5fc9dc;

		--proposal-s: 28%;
		--proposal-l: 17%;
		--proposal-map-s: 58%;
		--proposal-map-l: 62%;

		--map-hl-op: #38b6d4;
		--map-hl-self: #ffb52e;

		--hl-ring: #45b9d6;
		--hl-ring-strong: #8fe3f7;
	}
}

:root[data-theme='dark'] {
	--bg: #1c1c1c;
	--fg: #f2f2f2;
	--fg-dim: #b4b4b4;
	--fg-faint: #7d7d7d;
	--rule: #3d3d3d;
	--rule-soft: #2c2c2c;
	--surface: #262626;
	--surface-sunk: #222222;
	--accent: #4fd0e0;
	--accent-contrast: #10262a;

	--legacy-bg: #2e2820;

	--hl-self: #35707e;
	--hl-self-fg: #eaf7fb;
	--hl-op: #27484f;
	--hl-op-fg: #dceef3;
	--hl-pre: rgba(220, 110, 16, 0.62);

	--enc-prefix: #e0a45a;
	--enc-sub: #5fc9dc;

	--proposal-s: 28%;
	--proposal-l: 17%;
	--proposal-map-s: 58%;
	--proposal-map-l: 62%;

	--map-hl-op: #38b6d4;
	--map-hl-self: #ffb52e;

	--hl-ring: #45b9d6;
	--hl-ring-strong: #8fe3f7;
}

* { box-sizing: border-box; }

body {
	margin: 0 auto;
	padding: 1.5rem 1rem;
	max-width: 1600px;
	font-family: Roboto, system-ui, sans-serif;
	background: var(--bg);
	color: var(--fg);
	-webkit-text-size-adjust: 100%;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/*
 * The masthead is a single line: title, author, when the data was last checked,
 * and the way in to everything else. The prose that used to be here reads once
 * and then sits between the reader and the chart on every visit after that.
 */
.site-header {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: .1rem .7rem;
	margin-bottom: .8rem;
}
h1 { font-size: 1.5rem; margin: 0; }
.byline { margin: 0; color: var(--fg-dim); font-weight: 700; font-size: .9rem; }

/*
 * When the data was last checked, and the way in to everything else — on a line
 * of its own under the title rather than opposite it. `flex-basis: 100%` is what
 * breaks the line: the header is a wrapping flex row, so an item that wants the
 * whole width takes the next one.
 *
 * It reads at the size and weight the review date always had. About joined it as
 * a word in the same sentence rather than as the bordered button it was, since
 * the two are the same kind of aside about the page and one of them was shouting.
 */
.header-meta {
	display: flex;
	flex: 0 0 100%;
	align-items: baseline;
	gap: .4rem;
	margin: .15rem 0 0;
	font-size: .82rem;
}
.meta-sep { color: var(--rule); }

/* The search box takes the far end of the title line, where About was. */
.site-header .control-search { margin-left: auto; }

.lede { max-width: 68ch; color: var(--fg-dim); font-size: .95rem; }
.lede p { margin: .4rem 0; }

/* Shown only once the bootstrap script has confirmed JavaScript is running. */
.js-only { display: none; }
.js .js-only { display: flex; }

/* ------------------------------------------------------------------ toolbar */

/*
 * Not pinned by default. It is a band of controls that are set once and then
 * left alone, and holding it on screen costs a strip of every page of a chart
 * that is already several screens tall. The pin button opts in.
 */
.toolbar {
	/*
	 * Always positioned, so it reliably creates one stacking context for the
	 * "Show" dropdown to sit in. `backdrop-filter` used to create that context
	 * implicitly, at the wrong level and for no benefit: the background here is
	 * opaque, so there was nothing to blur.
	 *
	 * Below the detail panel while it is unpinned, because then the panel is a
	 * sheet laid over the page and an unpinned toolbar is part of the page:
	 * with it above, the toolbar cut the top off the sheet whenever the reader
	 * was near the top of the chart. Pinned, the toolbar goes above instead —
	 * it then has a band of its own and the panel starts below it, so nothing
	 * overlaps and the dropdown is never covered.
	 */
	position: relative;
	z-index: 20;
	flex-wrap: wrap;
	gap: .5rem 1.25rem;
	align-items: flex-start;
	margin: 1.25rem 0;
	padding: .6rem .8rem;
	background: var(--surface);
	border: 1px solid var(--rule-soft);
	border-radius: var(--radius);
	font-size: .85rem;
}

.control {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: .1rem .7rem;
	margin: 0;
	padding: 0;
	border: 0;
}

.control legend {
	padding: 0 .5rem 0 0;
	color: var(--fg-faint);
	font-size: .72rem;
	text-transform: uppercase;
	letter-spacing: .06em;
}

/* `legend` cannot be a flex item, so it is floated out of the flex flow. */
.control legend { float: left; line-height: 1.9; }

.control label {
	display: inline-flex;
	align-items: center;
	gap: .25rem;
	white-space: nowrap;
	cursor: pointer;
}

.control-actions { margin-left: auto; gap: .4rem; }

/* ----------------------------------------------------------------- search */

/*
 * Two rows: the label and the box, then the count under the box.
 *
 * A grid rather than a wrapping flex row, so the count lines up with the box
 * above it rather than with the label beside it. `.js-only` reveals its
 * elements with `display: flex` from a two-class selector, so this needs the
 * same weight to change the box at all.
 *
 * The list still hangs off this element, which is what `position: relative` is
 * for — and now it clears the count as well, since the count is inside the box
 * it measures from.
 */
.js .control-search {
	display: grid;
	grid-template-columns: auto minmax(0, auto);
	align-items: center;
	gap: 0 .4rem;
}
.control-search { position: relative; }
.control-search label {
	color: var(--fg-faint);
	font-size: .72rem;
	text-transform: uppercase;
	letter-spacing: .06em;
}
/* The box and the button that sits inside its right-hand end. */
.search-field { position: relative; display: flex; align-items: center; }

#search {
	width: 11rem;
	/* Room at the end for the clear button, which is drawn over it. */
	padding: .25rem 1.55rem .25rem .55rem;
	font: inherit;
	font-size: .85rem;
	color: var(--fg);
	background: var(--surface-sunk);
	border: 1px solid var(--rule);
	border-radius: var(--radius);
}
#search:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
#search::placeholder { color: var(--fg-faint); }
/* Chrome puts its own cross in a search input. One is enough, and the other one
   is the one that can be styled and can be relied on in every browser. */
#search::-webkit-search-cancel-button { display: none; }

/*
 * Clearing the box, from inside it.
 *
 * There is a "Clear search" button in the nothing-matched message already, but
 * that only exists when nothing matched — which is the one case where the
 * reader can see that the query is the problem. The usual case is a query that
 * found something and has been read, and there was nothing to press.
 */
.search-x {
	position: absolute;
	right: .3rem;
	display: grid;
	place-items: center;
	width: 1.1rem;
	height: 1.1rem;
	padding: 0;
	font: inherit;
	font-size: 1rem;
	line-height: 1;
	color: var(--fg-faint);
	background: none;
	border: 0;
	border-radius: 50%;
	cursor: pointer;
}
.search-x:hover { color: var(--fg); background: var(--rule-soft); }
/* `display: grid` above would otherwise beat the attribute. */
.search-x[hidden] { display: none; }

/*
 * The count goes under the box, in the second column so that it lines up with
 * it, on a line that is there whether or not there is anything to say. Beside
 * the box it reserved five and a half rems that collapsed when empty, so the
 * box slid sideways the moment anyone typed — and in the masthead, where the
 * control is pushed to the right, it slid the moment the number changed width.
 */
.search-count {
	grid-column: 2;
	min-height: 1.1em;
	margin-top: .1rem;
	font-size: .72rem;
	line-height: 1.1;
	color: var(--fg-faint);
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}

/*
 * `js-only` reveals its elements with `display: flex`, which beats the `hidden`
 * attribute and, here, would also swallow the spaces between the words by
 * making each text node a flex item. This one is a sentence, so it is a block,
 * and it stays hidden when it says it is.
 */
.js .search-empty { display: block; }
.js .search-empty[hidden] { display: none; }

.search-empty {
	margin: .6rem 0 0;
	padding: .5rem .7rem;
	font-size: .9rem;
	color: var(--fg-dim);
	background: var(--surface-sunk);
	border-left: 3px solid var(--rule);
	border-radius: 0 var(--radius) var(--radius) 0;
}
.search-empty strong { color: var(--fg); }
.search-empty .ghost { margin-left: .4rem; }

/*
 * The ranked answer.
 *
 * Wider than the box it hangs from, because a result is three columns — the
 * opcode, the name, and why it matched — and the box only has to be wide enough
 * to type into. Above everything: it is a menu, and a menu that anything can
 * paint over is worse than no menu.
 */
.search-results {
	position: absolute;
	top: 100%;
	left: 0;
	right: auto;
	z-index: 60;
	/*
	 * Wider than the box it hangs from, but never wider than the page. On a
	 * phone the box is a third of the width and the list is twenty-two rems, so
	 * without the cap it ran off the side and gave the whole document a
	 * horizontal scrollbar.
	 */
	width: min(max(22rem, 100%), calc(100vw - 1rem));
	max-height: 22rem;
	margin: .3rem 0 0;
	padding: .2rem;
	overflow-y: auto;
	list-style: none;
	background: var(--bg);
	border: 1px solid var(--rule);
	border-radius: var(--radius);
	box-shadow: 0 10px 30px rgb(0 0 0 / .22);
}
.search-results[hidden] { display: none; }

/*
 * Hung from whichever edge of the box has the room.
 *
 * The box has two homes and they sit at opposite ends: pushed to the right of
 * the masthead, first control on the left of the toolbar. A list wider than the
 * box has to open away from the near edge in each case, or it leaves the page.
 */
.site-header .search-results { left: auto; right: 0; }

.result {
	display: grid;
	grid-template-columns: 7.5rem minmax(0, 1fr) auto;
	gap: .5rem;
	align-items: baseline;
	padding: .25rem .4rem;
	font-size: .85rem;
	border-radius: calc(var(--radius) - 2px);
	cursor: pointer;
}
.result-active { background: var(--hl-op); color: var(--hl-op-fg); }

.result-op { white-space: nowrap; font-size: .78rem; }
.result-name {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	font-weight: 700;
}
/* Why a line the reader did not type is in the list. */
.result-note {
	white-space: nowrap;
	font-family: Georgia, serif;
	font-style: italic;
	font-size: .78rem;
	color: var(--fg-faint);
}

.result mark {
	padding: 0 .05em;
	color: inherit;
	background: var(--hl-pre);
	border-radius: 2px;
}

.result-more {
	padding: .35rem .4rem;
	font-size: .75rem;
	color: var(--fg-faint);
}

.toolbar.toolbar-pinned {
	position: sticky;
	top: 0;
	z-index: 45;
}

/*
 * While the "Show" menu is open, and only then.
 *
 * The menu is inside the toolbar, so it can never rise above anything the
 * toolbar sits below — and unpinned, the toolbar sits below the detail panel on
 * purpose, since the panel is then a sheet laid over the page and was having
 * its top cut off. Both are right, and they only disagree for as long as the
 * menu is actually open, so that is exactly how long the toolbar comes forward
 * for. Last of the three, so it wins whether or not the toolbar is pinned.
 */
.toolbar:has(.control-filter[open]) { z-index: 50; }
#pin-toolbar[aria-pressed='true'] { color: var(--accent); border-color: var(--accent); }
#pin-toolbar[aria-pressed='true'] svg { fill: currentColor; }

/* --------------------------------------------------------------- filtering */

/* Section and status filters are set once and then left alone, so they fold
   away into a dropdown instead of spanning the toolbar. */
.control-filter { position: relative; }

.control-filter > summary {
	display: inline-flex;
	align-items: center;
	gap: .3rem;
	padding: .25rem .6rem;
	color: var(--fg-dim);
	border: 1px solid var(--rule);
	border-radius: var(--radius);
	cursor: pointer;
	list-style: none;
	user-select: none;
}
.control-filter > summary::-webkit-details-marker { display: none; }
.control-filter > summary::after {
	content: '';
	width: .4rem;
	height: .4rem;
	margin-left: .1rem;
	border-right: 1.5px solid currentColor;
	border-bottom: 1.5px solid currentColor;
	transform: translateY(-2px) rotate(45deg);
}
.control-filter[open] > summary::after { transform: translateY(1px) rotate(225deg); }
.control-filter > summary:hover,
.control-filter[open] > summary { color: var(--fg); border-color: var(--accent); }

/* A dot on the summary when the view is filtered, so a collapsed panel can
   still say that something is being hidden. */
.filter-badge:not([hidden]) {
	width: .45rem;
	height: .45rem;
	border-radius: 50%;
	background: var(--accent);
}

.filter-panel {
	position: absolute;
	top: calc(100% + .3rem);
	left: 0;
	z-index: 40;
	display: grid;
	gap: .5rem;
	min-width: 15rem;
	padding: .7rem .8rem;
	background: var(--bg);
	border: 1px solid var(--rule);
	border-radius: var(--radius);
	box-shadow: 0 8px 24px rgba(0, 0, 0, .16);
}

.filter-group { display: grid; gap: .15rem; }
.filter-group + .filter-group { padding-top: .5rem; border-top: 1px solid var(--rule-soft); }
.filter-panel .toggle { display: flex; align-items: center; gap: .35rem; cursor: pointer; }
.filter-presets { grid-auto-flow: column; gap: .4rem; justify-content: start; }

/*
 * Grouping and sorting have no meaning in the byte grid, where a cell's
 * position is its opcode and nothing else can be done with it. They were dimmed
 * rather than hidden, so that the toolbar would not reflow as you used it —
 * which meant the arrangement everyone arrives in, and most people stay in,
 * spent half its toolbar on two greyed-out sets of controls that do nothing.
 * Emptiness costs less than that.
 */
.toolbar[data-layout='matrix'] [data-needs-grouping] { display: none; }

button.ghost {
	padding: .25rem .6rem;
	font: inherit;
	color: var(--fg-dim);
	background: transparent;
	border: 1px solid var(--rule);
	border-radius: var(--radius);
	cursor: pointer;
}
button.ghost:hover { color: var(--fg); border-color: var(--accent); }

.icon-button {
	display: inline-grid;
	place-items: center;
	width: 2rem;
	height: 2rem;
	padding: 0;
	color: var(--fg-dim);
	background: transparent;
	border: 1px solid var(--rule);
	border-radius: var(--radius);
	cursor: pointer;
}
.icon-button:hover { color: var(--accent); border-color: var(--accent); }
.icon-button svg { fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; }
.icon-moon { fill: currentColor; stroke: none; }

/* Show whichever icon represents the theme you would switch *to*. */
.icon-moon { display: none; }
:root[data-theme='dark'] .icon-moon { display: block; }
:root[data-theme='dark'] .icon-sun { display: none; }
@media (prefers-color-scheme: dark) {
	:root:not([data-theme='light']) .icon-moon { display: block; }
	:root:not([data-theme='light']) .icon-sun { display: none; }
}

/* ---------------------------------------------------------------------- map */

/*
 * The map sits before the chart in the document, so with no layout applied it
 * lands above it — which is where it belongs on a narrow window. Given a wide
 * enough window it moves into the margin instead and stays there while you
 * scroll. Nothing scripted decides this; the container query below does.
 */
/* Overrides the flex that `.js-only` applies to reveal it. */
.js .map-dock { display: block; }
.map-dock { margin: 0 0 1rem; }

.map-fold > summary {
	display: inline-flex;
	align-items: center;
	gap: .3rem;
	padding: .15rem .5rem;
	font-size: .72rem;
	text-transform: uppercase;
	letter-spacing: .06em;
	color: var(--fg-faint);
	border: 1px solid var(--rule-soft);
	border-radius: var(--radius);
	cursor: pointer;
	list-style: none;
}
.map-fold > summary::-webkit-details-marker { display: none; }
.map-fold > summary::after {
	content: '';
	width: .35rem;
	height: .35rem;
	border-right: 1.5px solid currentColor;
	border-bottom: 1.5px solid currentColor;
	transform: translateY(-2px) rotate(45deg);
}
.map-fold[open] > summary::after { transform: translateY(1px) rotate(225deg); }
.map-fold > summary:hover { color: var(--fg); }


/*
 * Above the chart: the sections run across, to spend width rather than height.
 *
 * Each hugs its own contents rather than being stretched to the height of the
 * tallest in its row. Stretched, a section is a grid of two rows in a box
 * taller than both, so the spare height goes *between* the label and the
 * squares: on a phone, where three or four sections share a row with Table 0,
 * the short ones had their squares floating half a centimetre below their own
 * labels and level with nothing. Now every row of the map starts at the top.
 */
.map {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	gap: .6rem 1rem;
	padding: .6rem 0;
}

.map-section { display: grid; gap: .2rem; }
.map-section[data-empty] { display: none; }

.map-label {
	font-size: .62rem;
	color: var(--fg-faint);
	white-space: nowrap;
}

.map-grid {
	display: grid;
	grid-template-columns: repeat(16, 1fr);
	gap: 1px;
	width: max-content;
}

/* Small enough above the chart that all seven sections fit on one line, so the
   map costs one band of height rather than two. The rail has width to spare and
   sizes them up again. */
.map-cell {
	width: 5px;
	height: 5px;
	background: var(--fg-faint);
	border-radius: 1px;
	cursor: pointer;
	transition: background-color 120ms linear;
}
.map-cell[data-status='reserved'] { background: var(--rule-soft); cursor: default; }
.map-cell[data-status='proposal'] { background: var(--rule); }
.map-cell[data-status='legacy'],
.map-cell[data-status='dormant'],
.map-cell[data-status='withdrawn'] { background: var(--rule-soft); opacity: .7; }

/* A row the byte grid is not drawing at all. Whole rows only, so the columns
   stay in line — see `NavMap.trim`. */
.map-cell[data-offgrid] { display: none; }

/* The chart's 128-opcode break, at map scale: a full-width sliver, so the same
   landmark is in the same place in both. */
.map-band {
	grid-column: 1 / -1;
	height: 3px;
}
.map-band[hidden] { display: none; }

/*
 * Which instructions are on screen right now — the "you are here".
 *
 * Said by holding those squares at full strength and setting the rest back,
 * rather than by marking each one. An outline per square drew a box round every
 * cell of the run instead of round the run, which at five pixels and a one-pixel
 * gap was a shimmering grid rather than a region; it was clipped on the squares
 * at either edge of the map; and it is the same property the pinned square
 * uses, so the two fought over it.
 *
 * Anything the reader has lit or pinned keeps its strength wherever it is. A
 * highlight exists to be seen when it is off screen — that is what the map is
 * for — so it is the one thing "off screen" must not dim.
 */
.map-cell:not(.in-view, .hl-op, .hl-self, .hl-tag, .map-part-lit, .pinned) {
	opacity: .45;
}

/*
 * Except while the map is answering a question about a set of instructions,
 * when the whole of it steps back and only the answer is at full strength.
 * Two highlights in the same language — the matches, and the squares that
 * happen to be on screen — is one too many; see `describeHighlight`.
 */
.map[data-lit] .map-cell:not(.hl-op, .hl-self, .hl-tag, .map-part-lit, .pinned) {
	opacity: .45;
}

/*
 * Hidden by the chart's filters or a search: the square keeps its place, since
 * the map is a picture of the byte space and closing the gap would move every
 * square after it, but it shows nothing. A dimmed square still reads as an
 * instruction that is there; an empty one reads as what the reader asked for —
 * these bytes are not in play.
 *
 * Listed against the proposal colouring too, which is on by default and whose
 * selector is otherwise more specific: without that, hiding the legacy
 * encodings left them sitting in the map in full colour.
 */
.map-cell[data-filtered],
.map[data-colour='proposal'] .map-cell[data-filtered] {
	background: transparent;
	cursor: default;
	box-shadow: none;
}

/*
 * A map square is five pixels across, so the gentle tints the chart uses
 * disappear at that size. These are the loud versions of the same three
 * states — the map's whole job is to be readable at a glance from the corner
 * of your eye.
 */
.map-cell.hl-op,
.map-cell.hl-tag {
	background: var(--map-hl-op);
	box-shadow: 0 0 0 1px var(--map-hl-op);
}
.map-cell.hl-self {
	background: var(--map-hl-self);
	box-shadow: 0 0 0 2px var(--map-hl-self);
	position: relative;
	z-index: 1;
}
/*
 * The selected instruction. Two rings rather than one: the page colour first,
 * so the mark is separated from whatever it sits among, then the text colour
 * outside it.
 *
 * A single light ring was legible only against the squares the map had set
 * back, which is to say only while the selection was scrolled off screen —
 * exactly when it matters least. Against its own neighbours at full strength it
 * disappeared. It was drawn with `outline` and an offset, too, so it reached
 * into the gap around the square and the squares that come after it in the
 * document painted over its right and bottom edges. Lifting it out of the flow
 * fixes that; a ring drawn by `box-shadow` cannot be sliced by a neighbour.
 */
.map-cell.pinned {
	position: relative;
	z-index: 3;
	outline: none;
	box-shadow: 0 0 0 1px var(--bg), 0 0 0 2.5px var(--fg);
}

/*
 * Wide enough for the rail plus a chart still worth looking at. A container
 * query would read more naturally here, but the element that has to change is
 * the container itself, which `@container` cannot style — and putting the
 * container further up would apply `contain: layout`, which makes that element
 * a containing block for the fixed-position detail panel.
 */
@media (min-width: 1220px) {
	.layout {
		display: grid;
		grid-template-columns: max-content minmax(0, 1fr);
		gap: 1.5rem;
		align-items: start;
	}

	.map-dock {
		position: sticky;
		top: calc(var(--toolbar-h) + 1rem);
		margin: 0;
		max-height: calc(100dvh - var(--toolbar-h) - 2rem);
		overflow-y: auto;
		/*
		 * Setting either axis to something other than `visible` makes the other
		 * one scroll too, so this box clips horizontally as well — and a
		 * highlighted square at the left or right edge of the map draws its ring
		 * outside its own five pixels. Enough room for the widest of them.
		 */
		padding: 2px 3px;
		margin-inline: -3px;
	}

	/* In the margin there is height to spend and no width, so the sections
	   stack instead of running across. */
	.map { flex-direction: column; flex-wrap: nowrap; gap: .5rem; padding: 0; }
	.map-cell { width: 7px; height: 7px; }
}

/* -------------------------------------------------------------------- chart */

.chart {
	display: grid;
	gap: 0;
	align-items: stretch;
}

/* A column for the row labels, then one per byte. The count is on the element
   rather than in a media query because the item list has to agree with it —
   see `ViewOptions.columns`. */
.chart[data-layout='matrix'] {
	grid-template-columns: 2.6rem repeat(16, minmax(0, 1fr));
}
.chart[data-layout='matrix'][data-cols='8'] {
	grid-template-columns: 2.4rem repeat(8, minmax(0, 1fr));
}

.chart[data-layout='cards'] {
	grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
	gap: .3rem;
}

/*
 * A break across the grid every 128 sub-opcodes.
 *
 * Space rather than a rule. A line would be a sixth kind of border in a chart
 * whose cells already say four things with theirs, and the gap is doing the
 * work a rule would: it stops the eye, and it says the row under it starts a
 * round number. Enough to be seen at a glance down the page and not enough to
 * read as the end of the table.
 */
.band {
	grid-column: 1 / -1;
	height: 6px;
}
.chart:not([data-layout='matrix']) .band { display: none; }

.group {
	grid-column: 1 / -1;
	margin: 2rem 0 .5rem;
	font-size: 1.25rem;
	font-weight: 700;
	scroll-margin-top: 5rem;
}
.chart > .group:first-child { margin-top: .5rem; }

/*
 * A table's mark: its letter reversed out of a filled square.
 *
 * The tables carried an emoji each — ⭕, ⭐, 🌀, 🧵 — back when they were named
 * after proposals and there was nothing in a name to abbreviate to. Lettered,
 * the letter is the abbreviation, and it names the table instead of merely
 * standing for one. Inverted so that it still reads as a symbol beside running
 * text rather than as a stray capital in the middle of a sentence.
 *
 * Everything is in `em`, so one rule serves a 1.25rem heading, a .62rem map
 * label and the line inside a byte-grid cell.
 */
.table-mark {
	display: inline-block;
	min-width: 1.35em;
	padding: 0 .18em;
	font-family: 'Source Code Pro', monospace;
	/* Set below the text it sits in, because a boxed letter carries its own
	   weight of ink and matching the size would out-shout the words. Not as far
	   below as it was: at .82 the letter was the smallest thing on a heading it
	   is supposed to be the mark of. */
	font-size: .92em;
	font-weight: 700;
	line-height: 1.3;
	text-align: center;
	color: var(--bg);
	background: var(--fg-dim);
	border-radius: .18em;
	/* It sits beside text of another size in several places; centring it on the
	   x-height rather than the baseline keeps it level with all of them. */
	vertical-align: .04em;
}

/* Two marks in a row, on a doorway byte that opened more than one table, would
   otherwise run together into one block of ink. */
.table-mark + .table-mark { margin-left: .2em; }

/* Set in the dimmer grey rather than the faintest one. It is a small number in
   a thin outline, often over a tinted cell, and in the dark theme the faint
   grey is only a step and a half off the background it was being read against.
   Quieter than the heading it follows, which is all it has to be. */
.group-count {
	margin-left: .4rem;
	padding: .05rem .4rem;
	font-size: .7rem;
	font-weight: 400;
	color: var(--fg-dim);
	border: 1px solid var(--rule);
	border-radius: 999px;
	vertical-align: middle;
	white-space: nowrap;
}
/* A bare number beside a heading is a riddle in every layout — 205 of what? —
   so the word stays. The one place it does not fit is inside a doorway cell,
   which is sixty pixels wide and has a range and a table mark to fit as well;
   there the number is one of three figures in a block that is plainly about
   the table behind the byte. */
.prefix-cell .count-unit { display: none; }

.group-intro {
	display: block;
	margin-top: .25rem;
	font-size: .85rem;
	font-weight: 400;
	color: var(--fg-dim);
}

.corner, .colhead, .rowhead {
	display: grid;
	place-items: center;
	padding: .25rem;
	font-size: .75rem;
	font-weight: 700;
	color: var(--fg-faint);
}

/*
 * The axes are half of an opcode, so they are lettered as one.
 *
 * A hex axis is the other nibble of the byte in the cell, so it takes the
 * colour and the face the hex half of an identifier has everywhere else; a
 * decimal axis is part of a sub-opcode and takes that half's. Neutral grey
 * said only "this is a heading", which the position had already said, and left
 * the reader to work out that `_E` and `0xBE` were the same kind of thing.
 */
.colhead[data-notation='hex'],
.rowhead[data-notation='hex'] {
	font-family: 'Source Code Pro', monospace;
	color: var(--enc-prefix);
}
.colhead[data-notation='dec'],
.rowhead[data-notation='dec'] {
	font-family: Georgia, serif;
	font-style: italic;
	font-size: .85rem;
	color: var(--enc-sub);
}

/* The corner states the section's prefix, so the axes are readable as an
   equation rather than as bare nibbles: 0xFD + E_ + _8. */
.corner {
	font-family: 'Source Code Pro', monospace;
	font-size: .68rem;
	color: var(--enc-prefix);
	cursor: help;
}
.chart[data-layout='cards'] .corner,
.chart[data-layout='cards'] .colhead,
.chart[data-layout='cards'] .rowhead { display: none; }

/* --------------------------------------------------------------------- cell */

.cell {
	display: flex;
	flex-direction: column;
	justify-content: flex-start;
	min-height: 3.4rem;
	padding: .3rem .2rem;
	font-size: .8rem;
	line-height: 1.15;
	text-align: center;
	color: var(--fg-dim);
	/* The single source of truth for a cell's background. Text sitting over the
	   crosshatch reuses it to blank out the hatching behind itself, so it stays
	   correct when a highlight changes the colour. */
	--cell-bg: var(--bg);
	background-color: var(--cell-bg);
	border: 1px solid var(--rule);
	background-clip: padding-box;
	/* `anywhere` let the browser split words even when a break opportunity was
	   available further back. With separators marked, breaking mid-word is a
	   last resort again. */
	overflow-wrap: break-word;
	text-decoration: none;
	transition: background-color 120ms linear, color 120ms linear;
}
.cell:hover { text-decoration: none; }

/*
 * Status is shown by tint and border rather than by hatching. The old
 * crosshatch was one pattern for every non-standard state, it moired at some
 * zoom levels — thin repeating diagonals always will — and it ran underneath
 * the lettering. Four states, four treatments, no pattern.
 */
.cell[data-status='reserved'] {
	--cell-bg: var(--surface-sunk);
}

/* Not yet standardised: dashed edge, the usual "provisional" signal. */
.cell[data-status='proposal'] {
	--cell-bg: var(--surface-sunk);
	border-style: dashed;
	color: var(--fg-dim);
}

/*
 * Superseded but still in use: readable, tinted to set it apart, and set in the
 * same faint italic as a withdrawn encoding.
 *
 * `try` at 0x06 and `ref.test` at 0xFB 64 are the same kind of answer — this
 * byte used to mean that — and they were drawn as two different kinds. Sitting
 * among the current instructions in the same face and the same strength, `try`
 * read as one of them.
 */
.cell[data-status='legacy'] {
	--cell-bg: var(--legacy-bg);
	border-style: dashed;
	color: var(--fg-faint);
	font-style: italic;
}

/* Abandoned: the slot is unassigned today, so it is set back furthest. */
.cell[data-status='withdrawn'] {
	--cell-bg: var(--surface-sunk);
	border-style: dotted;
	color: var(--fg-faint);
	font-style: italic;
}

/* A stalled proposal: present, but nothing is built on it. Italic like the
   other two states that are not what a byte means today. */
.cell[data-status='dormant'] {
	--cell-bg: var(--surface-sunk);
	border-style: dashed;
	color: var(--fg-faint);
	font-style: italic;
}

/*
 * A cell hidden by a filter keeps its slot but loses its content, and every
 * blank slot is the same blank.
 *
 * It used to keep the treatment of whatever it was hiding — the legacy tint,
 * the dotted border of a withdrawn encoding — which turned the filters into a
 * second, quieter chart: hide the legacy encodings and you could still read
 * where they were, in cream, through the gap. That is not what hiding means,
 * and it is not a distinction worth a colour, since the cursor already says
 * there is something under an empty square and the panel says what.
 *
 * Written after the status rules and repeated against the proposal tint, both
 * of which are otherwise at least as specific.
 */
.cell[data-filtered],
.chart[data-colour='proposal'] .cell[data-proposal][data-filtered] {
	--cell-bg: var(--surface-sunk);
	border-style: solid;
	border-color: var(--rule);
}

/*
 * With "Unassigned slots" turned off, the unassigned bytes are left as gaps
 * rather than drawn as empty boxes — which is the difference the control was
 * offering all along and never showed, since a hidden unassigned slot looked
 * exactly like a shown one. Now the assigned bytes stand as islands and the
 * shape of what is actually used reads off the page.
 */
.cell[data-status='reserved'][data-filtered] {
	--cell-bg: transparent;
	border-color: transparent;
	cursor: default;
}

.cell[data-filtered] .cell-name,
.cell[data-filtered] .cell-hex,
.cell[data-filtered] .cell-summary { visibility: hidden; }
.cell[data-filtered] { cursor: help; }

.cell-hex {
	display: none;
	align-self: center;
	font-family: 'Source Code Pro', monospace;
	font-size: .74rem;
	letter-spacing: .02em;
	color: var(--fg-dim);
}
/* In the byte grid a cell's position already states its opcode; in the card
   and table layouts it has to be spelled out. It still labels the card rather
   than titling it, so it sits smaller and quieter than the name. */
.chart[data-layout='cards'] .cell-hex {
	display: block;
	margin-bottom: .2rem;
	font-size: .68rem;
}
.chart[data-layout='cards'] .cell { min-height: 4rem; padding: .45rem .35rem; }
.chart[data-layout='cards'] .cell-name { font-size: .95rem; line-height: 1.25; }
.chart[data-layout='cards'] .op,
.chart[data-layout='cards'] .op-x { font-size: 1em; }
.chart[data-layout='cards'] .cell-summary {
	display: block;
	margin-top: .2rem;
	font-size: .68rem;
	line-height: 1.2;
	color: var(--fg-faint);
}

/* The identifier is the spec form here too, but at cell scale the type tag
   should not compete with the value it qualifies. */
/* In a cell the opcode labels the instruction; it should not outshout it. */
.cell-hex .op-utype { display: none; }

/* Hugs the text rather than filling the cell, so the flat background over the
   crosshatch is only as wide as the lettering needs. */
.cell-name { display: inline-block; align-self: center; max-width: 100%; }

/* Extra columns, shown only by the card and table layouts. */
.cell-summary, .cell-status, .cell-cat { display: none; }

/* --------------------------------------------------------------- subgroups */

/*
 * A divider inside a category. "Numeric" alone runs to 140 instructions, which
 * is a wall; broken into comparison, arithmetic, bitwise and conversion it is
 * four shelves you can scan.
 */
.subgroup {
	grid-column: 1 / -1;
	margin: 1rem 0 .1rem;
	font-size: .72rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: .07em;
	color: var(--fg-faint);
}
.chart:not([data-layout='cards']) .subgroup { display: none; }

/* ----------------------------------------------------- colour by proposal */

/*
 * Optional, and off by default: it paints the whole chart, which is exactly
 * what makes it useful for seeing where a proposal's instructions landed, and
 * exactly what makes it too much to have on all the time. One hue per
 * proposal, emitted into the page from the registry.
 */
/*
 * The fill carries the hue; the border does not. Tinting the border too meant
 * every cell was ruled in a slightly different colour, so the grid had no lines
 * of its own — the edge between two cells changed character depending on which
 * two proposals met there. One rule colour throughout, and the tints sit inside
 * it.
 */
.chart[data-colour='proposal'] .cell[data-proposal] {
	--cell-bg: hsl(var(--proposal-hue) var(--proposal-s) var(--proposal-l));
}
.map[data-colour='proposal'] .map-cell[data-proposal] {
	background: hsl(var(--proposal-hue) var(--proposal-map-s) var(--proposal-map-l));
}
.map[data-colour='proposal'] .map-cell.hl-op,
.map[data-colour='proposal'] .map-cell.hl-tag { background: var(--map-hl-op); }
.map[data-colour='proposal'] .map-cell.hl-self { background: var(--map-hl-self); }

/* A chip carrying its proposal's colour, so the legend and the chart agree. */
.group-proposals {
	display: flex;
	flex-wrap: wrap;
	gap: .25rem;
	margin-top: .4rem;
	font-weight: 400;
}
.group-proposals .tag[data-proposal] {
	background: hsl(var(--proposal-hue) var(--proposal-s) var(--proposal-l));
	color: hsl(var(--proposal-hue) 40% 25%);
}
:root[data-theme='dark'] .group-proposals .tag[data-proposal],
.group-proposals .tag[data-proposal] { }
@media (prefers-color-scheme: dark) {
	:root:not([data-theme='light']) .group-proposals .tag[data-proposal] {
		color: hsl(var(--proposal-hue) 45% 82%);
	}
}
:root[data-theme='dark'] .group-proposals .tag[data-proposal] {
	color: hsl(var(--proposal-hue) 45% 82%);
}

/* --------------------------------------------------------------- history */

.detail-history { display: grid; gap: .15rem; margin: .3rem 0; }
.history-name { font-weight: 700; }
.history-standing { font-size: .82rem; color: var(--fg-dim); }
.history-note { font-size: .85rem; color: var(--fg-faint); }
/* Set apart from the note above it: that says where the proposal stands in
   the standards process, this says who has actually shipped it. */
.history-support {
	justify-self: start;
	margin-top: .25rem;
	font-size: .82rem;
}
.history-support::after { content: ' ↗'; }

/* Where a proposal reaches into more than one table. Set as a list rather than
   a sentence: it is two or three facts of the same shape, and a sentence
   joining them with "and" reads as though the split were incidental. */
.history-spread-lede { margin: .6rem 0 .2rem; font-size: .85rem; color: var(--fg-dim); }
.history-spread {
	margin: .2rem 0;
	padding-left: 1.1rem;
	font-size: .85rem;
	color: var(--fg-dim);
}
.history-spread li { margin: .15rem 0; }
/* As on a doorway byte: the whereabouts are a note under the table's name, not
   a second list. */
.history-spread ul { margin: 0; padding-left: 0; list-style: none; color: var(--fg-faint); }

/* -------------------------------------------------------------- properties */

.detail-tags { display: flex; flex-wrap: wrap; gap: .3rem; margin: .3rem 0; }

.tag {
	padding: .12rem .5rem;
	font: inherit;
	font-size: .78rem;
	color: var(--fg-dim);
	background: var(--surface-sunk);
	border: 1px solid transparent;
	border-radius: 999px;
	cursor: pointer;
}
.tag:hover { color: var(--fg); border-color: var(--rule); }
.tag[aria-pressed='true'] {
	color: var(--hl-self-fg);
	background: var(--hl-self);
	border-color: var(--accent);
}
.tag[data-kind='status'] { background: var(--legacy-bg); }

/*
 * A heading that doubles as a chip. It has to keep looking like the heading it
 * is — the chip treatment on an <h2> would read as a control rather than a
 * title — so it only takes on the chip's colour once it is pinned.
 */
.heading-tag {
	padding: 0;
	font: inherit;
	color: inherit;
	background: none;
	border-radius: .3rem;
}
.heading-tag:hover { color: var(--accent); border-color: transparent; }
.heading-tag[aria-pressed='true'] {
	padding: 0 .3rem;
	margin: 0 -.3rem;
	color: var(--hl-self-fg);
	background: var(--hl-self);
	border-color: transparent;
}

/* Cells matching a pinned property. */
.cell.hl-tag { --cell-bg: var(--hl-op); color: var(--hl-op-fg); background-image: none; }
.map-cell.hl-tag { background: var(--hl-op); }

/*
 * Highlighting has to survive the proposal colouring, in two senses.
 *
 * It has to win the cascade: the tint is applied by a more specific selector,
 * so without these the lit cells simply kept their proposal colour and nothing
 * appeared to happen at all.
 *
 * And it has to be legible over any hue. A tint alone cannot be — whatever
 * colour it is, some proposal is already near it — so highlights also draw a
 * ring, which reads against anything and makes a scattered set of matches
 * countable at a glance.
 */
.cell.hl-op, .cell.hl-tag {
	box-shadow: inset 0 0 0 1.5px var(--hl-ring);
}
.cell.hl-self {
	box-shadow: inset 0 0 0 2.5px var(--hl-ring-strong);
}

.chart[data-colour='proposal'] .cell[data-proposal].hl-op,
.chart[data-colour='proposal'] .cell[data-proposal].hl-tag {
	--cell-bg: var(--hl-op);
	color: var(--hl-op-fg);
}
.chart[data-colour='proposal'] .cell[data-proposal].hl-self {
	--cell-bg: var(--hl-self);
	color: var(--hl-self-fg);
}
/* The pinned cell needed its border recoloured only because the proposal tint
   had taken it; now that the border is the same everywhere, the outline that
   marks a selection is the whole mark. */

/* ------------------------------------------------------------ table layout */

/*
 * One instruction per row, its details in aligned columns. Each cell carries
 * the same column template rather than the container doing it, so a cell stays
 * a single box — which is what the animation measures and moves. `display:
 * contents` would align just as well and have no box at all.
 */
.chart[data-layout='table'] { grid-template-columns: minmax(0, 1fr); gap: 0; }

.chart[data-layout='table'] .cell,
.tablehead {
	display: grid;
	grid-template-columns:
		minmax(7rem, 8rem) minmax(9rem, 1.6fr) minmax(6rem, .6fr) minmax(8rem, .7fr);
	gap: .25rem .9rem;
	align-items: baseline;
	text-align: left;
	padding: .35rem .5rem;
}

.chart[data-layout='table'] .cell {
	min-height: 0;
	border-width: 0 0 1px 0;
	border-style: solid;
	border-color: var(--rule-soft);
	font-size: .84rem;
}
.chart[data-layout='table'] .cell[data-status='proposal'],
.chart[data-layout='table'] .cell[data-status='legacy'],
.chart[data-layout='table'] .cell[data-status='withdrawn'] { border-bottom-style: dashed; }

.tablehead {
	grid-column: 1 / -1;
	font-size: .68rem;
	text-transform: uppercase;
	letter-spacing: .06em;
	color: var(--fg-faint);
	background: var(--bg);
	border-bottom: 1px solid var(--rule);
}
.chart:not([data-layout='table']) .tablehead { display: none; }

/*
 * Ungrouped there is one heading row and it sticks, which is the whole point of
 * a table that runs for six hundred rows.
 *
 * Grouped there is one per group, and they cannot: every cell of the chart is a
 * child of the same grid, so a sticky row has no group to be bounded by and
 * pins to the top of the window for the rest of the page. All eleven ended up
 * stacked in the same place, and the group headings — the thing the reader just
 * asked for — slid underneath them and were gone.
 */
.chart[data-layout='table'][data-group='none'] .tablehead {
	position: sticky;
	top: var(--toolbar-h);
	z-index: 5;
}

/* Sits with the group it belongs to rather than floating above the first row. */
.chart[data-layout='table']:not([data-group='none']) .tablehead {
	margin-top: .1rem;
}

.chart[data-layout='table'] .cell-hex { display: block; margin: 0; }
.chart[data-layout='table'] .cell-name {
	display: block;
	align-self: baseline;
	font-size: .95rem;
}
/* The generated line breaks belong to the byte grid, where cells are narrow. */
.chart[data-layout='table'] .op::before,
.chart[data-layout='table'] .post::before { content: none; }
.chart[data-layout='table'] .op,
.chart[data-layout='table'] .op-x { font-size: 1em; }

/* Set in the text face rather than the code face: it is a fact about the
   instruction, not part of it. Last of the four columns, after the category:
   the two are both notes about the row rather than the row itself, and of the
   two the category is the one a reader scans down. */
.chart[data-layout='table'] .cell-status {
	display: block;
	color: var(--fg-dim);
	font-size: .78rem;
}
.chart[data-layout='table'] .cell-cat { display: block; color: var(--fg-faint); font-size: .78rem; }
.chart[data-layout='table'] .cell-summary {
	display: block;
	grid-column: 2;
	margin-top: .05rem;
	font-size: .76rem;
	color: var(--fg-faint);
}

/*
 * Narrow windows cannot carry four columns, so the row folds to two: the byte
 * on the left, everything about it in a block on the right.
 *
 * It used to be written as a single column, which is not what it did. The
 * summary is placed in column 2 by hand, and asking for column 2 of a
 * one-column grid makes a second one — so the rows ended up with a wide empty
 * first column and a narrow strip on the right holding the name, the summary
 * and the type, each hard against the right-hand edge. The stack was there; it
 * was just built out of a column that was never declared and so was as wide as
 * the leftovers.
 */
@media (max-width: 800px) {
	.chart[data-layout='table'] .cell {
		grid-template-columns: 5.5rem minmax(0, 1fr);
		gap: .05rem .7rem;
		padding: .4rem .5rem;
	}
	.tablehead { display: none; }
	.chart[data-layout='table'] .cell-hex { grid-column: 1; grid-row: 1; }
	/* A doorway byte has no hex column of its own — its block opens with the
	   byte, larger — so it takes the whole row rather than leaving the first
	   column empty beside it. */
	.chart[data-layout='table'] .cell:has(.prefix-cell) {
		grid-template-columns: minmax(0, 1fr);
	}
	/* Including the empty spans it carries for columns it has nothing to put in:
	   the summary asks for column 2 by hand, and asking for a second column of a
	   one-column grid makes one — which is what pushed the block into a strip on
	   the right. */
	.chart[data-layout='table'] .cell:has(.prefix-cell) > * { grid-column: 1; }
	.chart[data-layout='table'] .cell-name,
	.chart[data-layout='table'] .cell-status { grid-column: 2; }
	/*
	 * Only where it is a caution. Standardised is what a byte is unless told
	 * otherwise, and on a phone the column is a third line under every row —
	 * five hundred of them reading "Wasm 2.0", which is the answer to a question
	 * a reader of a list is not asking, in the place where the exceptions should
	 * have been standing out.
	 *
	 * The ones that remain are set like the summary above them rather than
	 * louder than it: italic, faint, an aside about the row. On a narrow screen
	 * a row is a small stack of lines and whichever is darkest is read first,
	 * which should be the instruction's name.
	 */
	.chart[data-layout='table'] .cell[data-status='standard'] .cell-status { display: none; }
	.chart[data-layout='table'] .cell-status {
		color: var(--fg-faint);
		font-style: italic;
		font-size: .76rem;
	}
	/* The category is the one column worth dropping: on a phone it is a word
	   per row repeating the group heading a few rows above it. */
	.chart[data-layout='table'] .cell-cat { display: none; }
}

.pre, .pre-x { color: inherit; }
.op, .op-x { font-size: 1.05em; font-weight: 700; }
.post, .post-x { color: inherit; }

/* A line break that is not picked up by the clipboard, so copying a cell still
   yields `i64.load16_u` on a single line. */
.op::before, .post::before { content: '\A'; white-space: pre; }
.chart[data-layout='cards'] .op::before,
.chart[data-layout='cards'] .post::before { content: none; }

/* ------------------------------------------------------- the doorway bytes */

/*
 * 0xFB, 0xFC, 0xFD and 0xFE are not instructions, and their cells stopped
 * pretending to be. They used to hold links to their sub-tables, which was two
 * click targets inside a cell that was already a click target, and a name —
 * "🌀 SIMD opcodes" — that said less than the byte itself.
 *
 * What a doorway byte has to say is where it leads and how far: the prefix in
 * hex, then a line per table giving the span of sub-opcodes behind it, how many
 * are assigned, and the table's emoji to recognise it by. Written in the same
 * notation as the axes of the table it points at, so the range in the cell and
 * the row labels in the grid below are visibly the same numbers.
 */
.prefix-cell { display: block; line-height: 1.35; }

/* The card and table layouts label every cell with its opcode; this one has
   already said it, larger, as the first thing in the cell. */
.cell:has(.prefix-cell) .cell-hex { display: none; }

.prefix-byte {
	display: block;
	font-family: 'Source Code Pro', monospace;
	font-weight: 700;
	font-size: 1.05em;
}

/* Wrapping rather than nowrap, because the same line has to sit in a cell of
   the byte grid — sixty-odd pixels — and in a table row with a column to
   itself. Given room it is one line; given a grid cell it folds. */
.prefix-line {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: center;
	gap: 0 .2rem;
	font-weight: 400;
}
/* Hidden by the client when the reader has turned that table off. */
.prefix-line[hidden] { display: none; }

.prefix-range {
	white-space: nowrap;
	font-family: Georgia, serif;
	font-style: italic;
	color: var(--enc-sub);
}
/* A line of its own: the mark is how a table is recognised at a glance, and
   trailing a range and a badge it reads as punctuation on the end of a number
   rather than as the table it stands for. */
.prefix-marks { flex: 0 0 100%; line-height: 1.6; }

/* Given a column of its own the lines run left to right like the rest of a
   table row. */
.chart[data-layout='table'] .prefix-line { justify-content: flex-start; }

/*
 * The same badge as a group heading's, sized for a cell — but read against a
 * proposal tint rather than the page, and at .6rem it was the smallest and
 * faintest thing in a cell whose whole content is four numbers. Full text
 * colour, over the cell's own background so the tint does not sit behind it,
 * and a little larger.
 */
.prefix-cell .group-count {
	margin-left: .25rem;
	padding: 0 .3rem;
	font-size: .68rem;
	color: var(--fg);
	background: var(--cell-bg);
	vertical-align: baseline;
}

.prefix-tables { margin: .3rem 0; }

/* Where a table or a proposal is, under what it is. The inner list is set
   quieter and unbulleted: it is a note on the line above rather than a list of
   its own, and two levels of bullet would say the second level was as important
   as the first. */
.prefix-tables ul, .prefix-proposals ul {
	margin: .1rem 0 .35rem;
	padding-left: 0;
	list-style: none;
	font-size: .88em;
	color: var(--fg-dim);
}
.prefix-proposals > li { margin-top: .25rem; }
.prefix-past { margin: .5rem 0; }
.prefix-past > summary { cursor: pointer; color: var(--fg-dim); }

.cell .jump { display: block; font-weight: 700; }

/* ---------------------------------------------------------------- highlights */

/*
 * Hovering tints the matching *words*, not the cells. Every part of a name is
 * its own span, so hovering the `i64` of `i64.trunc_sat_f64_s` lights every
 * `i64` and hovering `trunc` lights every `trunc`. Understated on purpose: it
 * answers a question you asked by moving the mouse, and a wash of colour across
 * the chart is too loud a reply.
 */
/* On the word, not on the whole part: the dot or underscore that joins it to
   its neighbour is punctuation shared between two words, and lighting it made
   the mark look as though it had overrun the thing it was answering about. */
.part-lit .part-word {
	background-color: var(--hl-pre);
	border-radius: 2px;
	box-shadow: 0 0 0 1px var(--hl-pre);
}

/* The map has no words, only squares, so it answers at cell level — and has to
   be bold about it at five pixels across. */
.map-cell.map-part-lit {
	background: var(--map-hl-op);
	box-shadow: 0 0 0 1px var(--map-hl-op);
}

/* A pinned property is a deliberate question and gets a deliberate answer. */
.cell.hl-tag {
	--cell-bg: var(--hl-op);
	color: var(--hl-op-fg);
	background-image: none;
	box-shadow: inset 0 0 0 1.5px var(--hl-ring);
}
.map-cell.hl-tag {
	background: var(--map-hl-op);
	box-shadow: 0 0 0 1px var(--map-hl-op);
}
.chart[data-colour='proposal'] .cell[data-proposal].hl-tag {
	--cell-bg: var(--hl-op);
	color: var(--hl-op-fg);
}

/* Selection is an outline: it marks one cell rather than relating many. */
.cell.pinned {
	outline: 2px solid var(--accent);
	outline-offset: -2px;
}
/* The map's own is up with the rest of its rules — it is a pair of rings there,
   not an outline, for the reasons written beside it. */

/* ------------------------------------------------------------------ movement */

/*
 * Cells are moved by the FLIP technique: after a re-layout each cell is
 * transformed back to where it used to be, then released. The transition is
 * deliberately short — long enough to follow a cell with your eye, not long
 * enough to wait for.
 */
/*
 * No `will-change` here. It was promoting every moving element to a compositor
 * layer of its own, and a rearrangement moves hundreds at once — building that
 * many layers and then tearing them all down again cost more than the hint ever
 * bought, and it was paid as a stall right after the animation finished. A
 * plain transform transition composites perfectly well without it.
 */
.cell.moving, .rowhead.moving, .colhead.moving, .corner.moving, .group.moving,
.band.moving {
	transition: transform var(--move) var(--ease);
}
.chart.settling .cell { pointer-events: none; }

@media (prefers-reduced-motion: reduce) {
	:root { --move: 1ms; }
}

/* -------------------------------------------------------------------- panel */

/*
 * Below the docking width the panel is a sheet over the page: there is no room
 * to put it beside anything.
 */
.panel {
	position: fixed;
	/* Starts below a pinned toolbar and is shortened to match. The toolbar
	   paints above the panel, so without this its top rows sit behind it. */
	top: var(--toolbar-h);
	right: 0;
	z-index: 30;
	flex-direction: column;
	width: min(28rem, 92vw);
	height: calc(100dvh - var(--toolbar-h));
	padding: 0 1.25rem 1.5rem;
	background: var(--bg);
	border-left: 1px solid var(--rule);
	box-shadow: -12px 0 32px rgba(0, 0, 0, .12);
	overflow-y: auto;
	overscroll-behavior: contain;
}
.js .panel[data-open='true'] { display: flex; }
.js .panel[data-open='false'] { display: none; }

.panel-head {
	position: sticky;
	top: 0;
	display: flex;
	justify-content: flex-end;
	padding: .6rem 0;
	background: var(--bg);
}

.panel-empty { display: none; }

.panel-body { font-size: .92rem; }
.panel-body h2 { margin: .2rem 0 .6rem; font-size: 1.3rem; word-break: break-word; }

/*
 * The opcode above the name, small and quiet. It is the other half of the
 * instruction's identity, and the heading gave only one half — the bytes were
 * in the picture below, which is the right way to explain an encoding and the
 * wrong way to label a section.
 */
.detail-op {
	display: block;
	margin-bottom: .1rem;
	font-size: .8rem;
	font-weight: 400;
}

/* What a cell is, where it is not an instruction and so has no name to be
   titled by. The byte is the title; this is the subtitle under it. */
.detail-role {
	display: block;
	margin-top: .1rem;
	font-size: .85rem;
	font-weight: 400;
	color: var(--fg-dim);
}

/*
 * Present in the document, absent from the page. Two readers want the same
 * content in different forms: the property chips are buttons, which anything
 * extracting the page as prose discards, leaving a heading with nothing under
 * it. This is that list as a sentence — off screen for sighted readers, and
 * hidden from screen readers, which read the chips themselves.
 */
.reading-text {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}
/*
 * The panel is six or seven short sections in a narrow column, and small faint
 * uppercase was not enough to tell one from the next — the encoding, the
 * description and the stack signature ran together as one column of text. A
 * hairline above each heading draws the break; the lettering itself is only a
 * little stronger, since it is a label on a section and not a title over it.
 *
 * The static detail blocks are the same content in a wider column, and are what
 * the page falls back to without JavaScript, so they are ruled the same way.
 */
.panel-body h4,
.detail h4 {
	margin: 1.4rem 0 .35rem;
	padding-top: .55rem;
	border-top: 1px solid var(--rule-soft);
	font-size: .72rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: .08em;
	color: var(--fg-dim);
}
.panel-body p { margin: .5rem 0; }
.panel-body ol, .panel-body ul { margin: .4rem 0; padding-left: 1.4rem; }
.panel-body li { margin: .2rem 0; }

/*
 * Wide enough to give the panel a column instead of laying it over the chart.
 * It stays in place when nothing is selected, so choosing an instruction does
 * not shove the whole chart sideways.
 */
@media (min-width: 1500px) {
	.layout { grid-template-columns: max-content minmax(0, 1fr) 24rem; }

	.js .panel,
	.js .panel[data-open='false'] {
		display: flex;
		position: sticky;
		/* Below a pinned toolbar, not behind it — and shortened to match, so
		   the bottom of the panel stays on screen. */
		top: calc(var(--toolbar-h) + 1rem);
		width: auto;
		height: auto;
		max-height: calc(100dvh - var(--toolbar-h) - 2rem);
		padding: 0 0 1rem 1.25rem;
		border-left: 1px solid var(--rule-soft);
		box-shadow: none;
	}

	.panel[data-open='false'] .panel-head { display: none; }
	.panel[data-open='false'] .panel-empty {
		display: block;
		margin: 0;
		padding-top: .5rem;
		font-size: .85rem;
		color: var(--fg-faint);
	}
}

/*
 * The cards keep the column at any width above the bottom-sheet one.
 *
 * A byte grid is a shape, and taking 24rem off it at 1200px would fold sixteen
 * columns into eight and redraw every row — so the grid earns its rail only
 * when there is room to spare, and until then the panel lies over the top. A
 * field of cards has no shape to lose: it reflows to whatever width it is
 * given, and the panel opening over it hides the cards next to the one just
 * clicked, which are exactly the cards being compared with it.
 *
 * The declarations repeat the block above because there is no way to say "this
 * selector, or that width" in one rule.
 */
@media (min-width: 901px) {
	.js .layout:has(.chart[data-layout='cards']) {
		display: grid;
		grid-template-columns: minmax(0, 1fr) 24rem;
		gap: 1.5rem;
		align-items: start;
	}
	/* Above the pair, until the window is wide enough for the rail to sit
	   beside them; below that width the map is a fold-out across the top. */
	.js .layout:has(.chart[data-layout='cards']) > .map-dock { grid-column: 1 / -1; }

	.js .layout:has(.chart[data-layout='cards']) .panel,
	.js .layout:has(.chart[data-layout='cards']) .panel[data-open='false'] {
		display: flex;
		position: sticky;
		/* Below a pinned toolbar, not behind it — and shortened to match, so
		   the bottom of the panel stays on screen. */
		top: calc(var(--toolbar-h) + 1rem);
		width: auto;
		height: auto;
		max-height: calc(100dvh - var(--toolbar-h) - 2rem);
		padding: 0 0 1rem 1.25rem;
		border-left: 1px solid var(--rule-soft);
		box-shadow: none;
	}

	.layout:has(.chart[data-layout='cards']) .panel[data-open='false'] .panel-head {
		display: none;
	}
	.layout:has(.chart[data-layout='cards']) .panel[data-open='false'] .panel-empty {
		display: block;
		margin: 0;
		padding-top: .5rem;
		font-size: .85rem;
		color: var(--fg-faint);
	}
}

/* Where the map has a rail of its own, the cards' three columns are the map,
   the cards and the panel. */
@media (min-width: 1220px) {
	.js .layout:has(.chart[data-layout='cards']) {
		grid-template-columns: max-content minmax(0, 1fr) 24rem;
	}
	.js .layout:has(.chart[data-layout='cards']) > .map-dock { grid-column: auto; }
}

/* ------------------------------------------------------------------ details */

.details { margin-top: 3rem; }
/* With JavaScript the detail panel replaces this block; without it, it is the
   destination of every cell link and has to stay reachable. */
.js .details { display: none; }

.detail { padding: 1rem 0; border-top: 1px solid var(--rule-soft); scroll-margin-top: 5rem; }
.detail-name { margin: .2rem 0 .15rem; }
.detail-summary { margin: 0 0 .6rem; color: var(--fg-dim); font-size: .92rem; }

.detail-status {
	display: flex;
	gap: .45rem;
	margin: .4rem 0;
	padding: .45rem .6rem;
	font-size: .82rem;
	color: var(--fg-dim);
	background: var(--surface-sunk);
	border-left: 3px solid var(--rule);
	border-radius: 0 var(--radius) var(--radius) 0;
}
.detail-status[data-status='legacy'] { background: var(--legacy-bg); border-left-color: var(--enc-prefix); }
.detail-status[data-status='proposal'] { border-left-style: dashed; }
.detail-status[data-status='dormant'] { border-left-style: dashed; }
.detail-status[data-status='withdrawn'] { border-left-style: dotted; }
/* Not a caution at all — a note that this one is done bar the paperwork. */
.detail-status[data-tone='settled'] { border-left-style: solid; border-left-color: var(--accent); }
.status-mark { flex: none; line-height: 1.4; }
.status-text strong { color: var(--fg); }
.detail-proposal, .detail-superseded {
	display: block;
	margin-top: .25rem;
	color: var(--fg-faint);
}

/*
 * The bytes, shown once and split into their parts. The captions sit under the
 * bytes they describe and each group is tinted, so which byte is which reads
 * off the layout instead of having to be spelled out in prose.
 */
.enc-row {
	display: flex;
	flex-wrap: wrap;
	gap: .3rem .9rem;
	align-items: flex-start;
	margin: .2rem 0 .1rem;
}

.enc-group { display: block; }

.enc-bytes {
	display: block;
	padding: .1rem .35rem;
	font-family: 'Source Code Pro', monospace;
	font-size: 1.5rem;
	letter-spacing: .04em;
	line-height: 1.25;
	border-bottom: 2px solid currentColor;
}
/* A byte is a byte. A single-byte instruction's opcode was drawn in the text
   colour, which made it the one byte on the page not in the byte colour — and
   the same value one row down in the chart, or as `0x28` in the heading above
   it, was orange. Two colours for one number. */
.enc-group[data-part='prefix'] .enc-bytes,
.enc-group[data-part='opcode'] .enc-bytes { color: var(--enc-prefix); }
.enc-group[data-part='sub'] .enc-bytes { color: var(--enc-sub); }

/* Notation, not value. Set back so the bytes are what you read. */
.enc-0x { color: var(--fg-faint); }

/* Immediate operands are not a fixed run of bytes, so they are drawn as an
   open-ended slot rather than a solid one. */
.enc-group[data-part='imm'] .enc-bytes {
	color: var(--fg-dim);
	font-family: Georgia, serif;
	font-style: italic;
	border-bottom-style: dashed;
}

/* The empty slot a sub-opcode goes in, on the four bytes whose entire meaning
   is that one follows. Same open-ended treatment as the immediates, in the
   sub-opcode's own colour and face, so the picture reads as "this byte, then a
   value of that kind". */
.enc-group[data-part='subslot'] .enc-bytes {
	color: var(--enc-sub);
	font-family: Georgia, serif;
	font-style: italic;
	border-bottom-style: dashed;
}

.enc-caption {
	display: block;
	padding: .2rem .35rem 0;
	font-size: .75rem;
	color: var(--fg-dim);
}
.enc-caption .enc-sub { display: block; color: var(--fg-faint); }

/*
 * Hex and decimal are told apart by colour, matching the tints the encoding
 * breakdown gives the prefix byte and the sub-opcode bytes. The `:u32` tag
 * rides along quietly but is never dropped: it is what says the decimal is a
 * value and not a byte.
 */
.op-hex, .op-utype, .prose-hex { font-family: 'Source Code Pro', monospace; }
.op-hex { color: var(--enc-prefix); }
.op-utype { color: var(--fg-faint); font-size: .85em; }

/*
 * A hex literal in a sentence is a byte, so it takes the same face as one in a
 * cell — but it is almost never this instruction's own byte, so it does not take
 * the opcode colour. The face carries the family resemblance; the body colour
 * keeps the two apart.
 */
.prose-hex { color: inherit; }

/*
 * A sub-opcode is not a byte, and it is worth being able to see that without
 * reading the `:u32` beside it. Monospace said the opposite: set in the same
 * face as the prefix, `0xFD 263` looked like a number written half in one base
 * and half in another, and the two halves ran together.
 *
 * Georgia's italic is the face this page already uses for the things that are
 * values rather than bytes — immediate operands, stack types — so a sub-opcode
 * joins them. A size up as well, because Georgia's figures are small on the
 * body and the sub-opcode is the part of the identifier that varies.
 */
.op-dec {
	font-family: Georgia, serif;
	font-style: italic;
	font-size: 1.15em;
	font-weight: 700;
	color: var(--enc-sub);
}

.immediate-args { color: var(--fg-faint); font-family: Georgia, serif; font-style: italic; }
/*
 * A stack signature. Metavariables are italic and concrete types upright, so
 * `[t t i32] → [t]` says which of its three operands is a variable without
 * anything having to explain it.
 *
 * The `.supsub` rules that used to sit here stacked a superscript over a
 * subscript in an inline-block at half size, which was small enough that the
 * five signatures using it carried a hand-set `large` flag to compensate. The
 * subscript and the star sit side by side now, at the size of everything
 * around them, and neither the flag nor the rules are needed.
 */
.op-type { font-family: Georgia, serif; }
.op-type sub, .op-type sup { font-size: .7em; }

/*
 * The operand list. The face used to be set by a `<span class="op-imm-args">`
 * written into the data 285 times — and left out of block, loop and if, so one
 * list rendered in two typefaces. It belongs to the section, not to each line.
 *
 * `em` rather than `rem`, so an operand takes the scale of whatever it is set
 * beside: the panel body is .92rem and the static article is 1rem, and an
 * absolute size was larger than its surroundings in one and smaller in the
 * other.
 *
 * A single operand drops the numbering. Every list here is an `<ol>` so that
 * the renderer emits one shape rather than choosing between two, and "1." on
 * its own numbers nothing.
 */
/*
 * Direct children only: the operand list is the section's own first child, and
 * a kind's explanation below it may bullet its rules too. Those are prose and
 * belong in the body face — unscoped, they came out in the operand serif, which
 * made a paragraph about alignment look like more operands.
 */
.detail-followed > ol { margin: .4rem 0; padding-left: 1.4rem; }
.detail-followed > ol > li { margin: .2rem 0; font-family: Georgia, serif; font-size: .95em; }
.detail-followed > ol:has(> li:only-child) { padding-left: 0; list-style: none; }

/*
 * Scoped to `.detail`, which both the static article and the panel carry —
 * the panel clones the article, class attribute and all. These were scoped to
 * `.panel-body`, so the articles at the foot of the page, which is where every
 * cell link lands without JavaScript, fell back to the user agent's spacing.
 */
.detail p { margin: .5rem 0; }
.detail ol, .detail ul { margin: .4rem 0; padding-left: 1.4rem; }
.detail li { margin: .2rem 0; }
.detail-stack-note { margin-top: .6rem; font-size: .92em; color: var(--fg-dim); }

/*
 * A comparison opens with its operator as a symbol. The 2022 page marked these
 * up for it and never wrote the rule, so `==` and `≥` were set as body text and
 * read as a stray first line. There are 86 of them, and the symbol is the
 * quickest way to tell lt from ge without reading a sentence.
 *
 * Georgia, because a mathematical operator is a value rather than a byte, which
 * is the same reason the sub-opcode and the stack types are set in it.
 */
.detail p.bigsign {
	margin: 0 0 .1rem;
	font-family: Georgia, serif;
	font-size: 1.9rem;
	line-height: 1.1;
	color: var(--fg-dim);
}

code, .code {
	font-family: 'Source Code Pro', monospace;
	padding: .1em .35em;
	font-size: 85%;
	background: var(--surface-sunk);
	border-radius: var(--radius);
}

/* ------------------------------------------------------------------- about */

/* A column, so the body is the only thing that scrolls — sizing the two
   independently gave the dialog a scrollbar of its own beside the body's. */
.about {
	flex-direction: column;
	overflow: hidden;
	width: min(64rem, calc(100vw - 2rem));
	max-height: min(80vh, 52rem);
	padding: 0;
	color: var(--fg);
	background: var(--surface);
	border: 1px solid var(--rule);
	border-radius: var(--radius);
	box-shadow: 0 12px 40px rgb(0 0 0 / .35);
}
/*
 * Only when it is open. The browser hides a closed `<dialog>` from its own
 * stylesheet, and any author `display` at all beats that however specific it
 * is — so declaring the column layout unconditionally left the whole reference
 * section sitting at the foot of the page, open or not.
 */
.about[open] { display: flex; }
.about::backdrop { background: rgb(0 0 0 / .55); }

/* The heading stays put while the reference material scrolls under it, so the
   way out is always in the same place. */
.about-head {
	display: flex;
	flex: none;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: .8rem 1.2rem;
	background: var(--surface);
	border-bottom: 1px solid var(--rule-soft);
}
.about-head h2 { margin: 0; font-size: 1.1rem; }
.about-body {
	flex: 1;
	min-height: 0;
	overflow-y: auto;
	padding: 1rem 1.2rem 1.5rem;
	font-size: .9rem;
}
.about-body ul { margin: .3rem 0; }

/*
 * Without JavaScript nothing can open it, so it is not a dialog at all: it
 * unfolds at the foot of the page as ordinary text. The content is in the
 * document either way — only the way it is reached changes.
 */
html:not(.js) #about {
	display: block;
	position: static;
	width: auto;
	max-height: none;
	margin: 2rem 0 0;
	border-color: var(--rule-soft);
	box-shadow: none;
}
html:not(.js) .about-body { overflow: visible; }

/* Four topics side by side where there is room. */
.footer-columns {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
	gap: .5rem 2rem;
	margin-top: .5rem;
}
/* Sits above the columns, so it keeps the lede's measure rather than a
   column's. */
.footer-lede { max-width: 68ch; margin: 1rem 0 0; }

.footer-columns h3 { margin: .6rem 0 .2rem; font-size: .95rem; }
.footer-columns section { break-inside: avoid; }

.reviewed { color: var(--fg-faint); font-size: .82rem; }
.about-body .reviewed { display: block; margin: .6rem 0 1rem; max-width: 68ch; }

/* ------------------------------------------------------------------- mobile */

@media (max-width: 900px) {
	body { padding: 1rem .5rem 3rem; }

	/* Half the columns and twice the rows, so a cell is wide enough to hold a
	   name at a size worth reading rather than being squeezed to fit. */
	.chart[data-layout='matrix'][data-cols='8'] {
		grid-template-columns: 2.1rem repeat(8, minmax(0, 1fr));
	}
	.cell { font-size: .68rem; min-height: 2.8rem; padding: .2rem .1rem; }
	.toolbar { font-size: .78rem; }

	/* On a narrow screen a side panel would leave nothing beside it, so the
	   details come up as a bottom sheet instead. */
	.panel {
		top: auto;
		bottom: 0;
		left: 0;
		width: 100%;
		height: min(70dvh, 34rem);
		border-left: 0;
		border-top: 1px solid var(--rule);
		border-radius: var(--radius) var(--radius) 0 0;
		box-shadow: 0 -12px 32px rgba(0, 0, 0, .18);
		/* Vertical panning is the sheet's own gesture to interpret: it either
		   scrolls the description or pushes the sheet away, and the browser
		   cannot know which until the sheet says where it is scrolled to. */
		touch-action: pan-y;
	}

	/* Springs back when a drag is let go without going far enough. During the
	   drag there is no transition, so the sheet stays under the finger. */
	.panel { transition: transform 180ms var(--ease); }
	.panel.panel-dragging { transition: none; }

	/* A handle, because a sheet you can push down should look like one. */
	.panel-head::before {
		content: '';
		position: absolute;
		top: .35rem;
		left: 50%;
		width: 2.2rem;
		height: 4px;
		margin-left: -1.1rem;
		background: var(--rule);
		border-radius: 999px;
	}
	.panel-head { position: sticky; padding-top: 1rem; }
}
