/* Roswaal editor styling. Light and dark are the same layout with swapped
   tokens; only the palette lives in the media query. */

:root {
	color-scheme: light dark;

	--bg-app: #eceef2;
	--bg-panel: #f6f7f9;
	--bg-canvas: #d9dce3;
	--bg-input: #ffffff;
	--bg-hover: rgba(0, 0, 0, 0.06);
	--bg-active: rgba(0, 0, 0, 0.12);

	--fg: #1c1f24;
	--fg-muted: #5c636e;
	--fg-faint: #8b93a0;

	--border: #c6cad2;
	--border-strong: #a8aeb9;

	/* The grid behind graphs, at each strength the Grid contrast setting
	   offers. Which one draws is chosen in "The grid", below. */
	--grid-dot-light: rgba(0, 0, 0, 0.13);
	--grid-dot-standard: rgba(0, 0, 0, 0.2);
	--grid-dot-strong: rgba(0, 0, 0, 0.34);
	--grid-fine-light: rgba(0, 0, 0, 0.05);
	--grid-fine-standard: rgba(0, 0, 0, 0.07);
	--grid-fine-strong: rgba(0, 0, 0, 0.12);
	--grid-coarse-light: rgba(0, 0, 0, 0.1);
	--grid-coarse-standard: rgba(0, 0, 0, 0.14);
	--grid-coarse-strong: rgba(0, 0, 0, 0.24);
	--watermark: rgba(0, 0, 0, 0.26);

	--node-body: #fbfbfd;
	--node-border: #b3b9c4;
	--node-shadow: 0 2px 6px rgba(0, 0, 0, 0.14);

	/* The scale everything outside the canvas is drawn to. Three corners --
	   a chip, a control, a surface -- three heights of shadow, and six sizes
	   of text with one for display. Nodes, pins and the docs' reading text
	   keep their own numbers: node layout measures text at its sizes, and
	   reading text is set for reading. */
	--radius-xs: 3px;
	--radius-sm: 6px;
	--radius-md: 8px;
	--shadow-raised: 0 4px 14px rgb(0 0 0 / 28%);
	--shadow-popover: 0 8px 28px rgb(0 0 0 / 36%);
	--shadow-modal: 0 20px 60px rgb(0 0 0 / 45%);
	--text-xs: 10px;
	--text-sm: 11px;
	--text-md: 12px;
	--text-lg: 13px;
	--text-xl: 15px;
	--text-2xl: 20px;
	--text-display: 30px;
	/* Floating chrome: the glass it is made of, the size of a control in it,
	   the gap it keeps from the window's edge, and how far down the cards and
	   notices start so they clear the top clusters. A finger gets more. */
	--glass: color-mix(in srgb, var(--bg-panel) 94%, transparent);
	--hit: 30px;
	--edge: 10px;
	--chrome-top: calc(var(--hit) + 8px + var(--edge) * 2);

	/* Pin geometry. One slot size for every pin, because the slot is what lines
	   the labels up down the side of a node; the shape inside it varies by kind.
	   The slot is also what the row *reserves*: a pin is drawn on the node's
	   edge rather than inside it, and the space it used to occupy stays, so the
	   labels and value fields keep their columns. `--pin-pad` is the row's own
	   side padding, which is how far the slot starts in from the edge.

	   `NODE.pinSlot`, `NODE.rowPadding` and `NODE.execAspect` in layers.ts are
	   the same three numbers; the documentation's node previews are drawn from
	   those. Change one and change the other. */
	--pin-slot: 16px;
	--pin-pad: 6px;
	/* The node's own border, named because a pin has to be placed *through* it.
	   A wire ends on the outside of the border; a row is laid out inside it, so
	   every offset below carries this back or the pin lands a pixel off the
	   thing it is wired to. One pixel, and visible: the border shows as a sliver
	   down one side of the dot. */
	--node-stroke: 1px;
	/* How much of the row a pin still takes, now that most of it is outside the
	   node. The dot reaches 6px in from the border; this is that plus air, and
	   the label starts where the pin visibly stops rather than where the old
	   16px slot used to end. It is the *layout*, not the pin: the pin is still
	   drawn `--pin-slot` across. */
	--pin-lane: 10px;
	/* An execution pin's triangle: equilateral, pointing right, as tall as the
	   slot -- so √3/2 as wide. This plus the gap below is how far it hangs past
	   the node. */
	--exec-width: calc(var(--pin-slot) * 0.866);
	/* Daylight between the triangle and the node it belongs to. Touching, the
	   two read as one shape with a bite out of it; apart, the pin reads as
	   something hung on the node, and the wire is visible arriving at it. */
	--exec-gap: 5px;
	/* How much smaller the hollow inside an unwired execution pin is than the
	   arrow around it. An equilateral triangle shrinks towards its centre, which
	   sits a third of the way in from the base, so the four insets differ while
	   the wall they leave does not; 0.433 is a 2px wall at a 16px slot. */
	--exec-shrink: 0.433;
	/* A node's corner. A preference rather than a theme token — `theme.ts` sets
	   it to 0 when square corners are chosen — which is why it is a variable at
	   all rather than the literal it used to be. Capsules and reroute knots
	   deliberately do not read it: their shapes are what say "value" and "just a
	   bend", and squaring those would delete the signal rather than restyle it. */
	--node-radius: 8px;
	/* How much of its category's colour a node header takes, and how much of
	   it a pure node's name is drawn in (the rest is the text colour, which is
	   what keeps a dark category readable on a dark body). Both depend on
	   whether the scheme is dark, so both are among `derivedTokens`. */
	--head-tint: 0.14;
	--cat-ink: 85%;
	/* The corner tab's width: `NODE.tab`. */
	--tab-width: 30px;
	/* What shows through an unwired execution triangle: the surface it is drawn
	   against, so it reads as empty rather than as a lighter shade of itself.
	   A knot and a capsule override it, because they are not a node body. */
	--pin-hollow: var(--node-body);
	/* The well a data pin sits in, and the thin ring of it drawn just outside
	   the pin's coloured edge.
	 *
	   Darker than the canvas in both themes, and deliberately not either surface
	   the pin touches: the pin straddles the node's border, so a fill taken from
	   the body or the canvas would change shade across the middle of the dot.
	   The ring does the same job on the outside — the pin keeps its colour
	   against a pale node body and a dark canvas alike — and is thinner than the
	   coloured edge, so it reads as a socket the pin is set into rather than as
	   a second outline competing with the first. */
	--pin-well: #2a2f37;

	--comment-fill: 0.1;
	--comment-header-alpha: 0.75;

	--wire-exec: #5a616d;

	--accent: #3b6ea5;
	--danger: #c0392b;
	--warning: #b8860b;
	--ok: #2e7d47;

	/* The project tree's script and folder colours; see derivedTokens. */
	--tree-script-server: #4b5563;
	--tree-script-local: #2e7d47;
	--tree-script-module: #1f64c4;
	--tree-folder-plain: var(--fg-muted);
	--tree-folder-special: #1f64c4;
	--tree-folder-packages: #7048c0;

	--select: #ff9c2e;
	/* A pinned node's ring and pin: not the selection's orange, not a warning's amber. */
	--pin: #2fbfa8;
}

@media (prefers-color-scheme: dark) {
	:root {
		color-scheme: dark;

		--bg-app: #16181d;
		--bg-panel: #1b1e24;
		--bg-canvas: #101216;
		--bg-input: #23272f;
		--bg-hover: rgba(255, 255, 255, 0.07);
		--bg-active: rgba(255, 255, 255, 0.13);

		--tree-script-server: #ffffff;
		--tree-script-local: #5cb87a;
		--tree-script-module: #5ea2ee;
		--tree-folder-plain: var(--fg-muted);
		--tree-folder-special: #5ea2ee;
		--tree-folder-packages: #b48cf0;

		--fg: #e4e7ec;
		--fg-muted: #9aa2af;
		--fg-faint: #6b7280;

		--border: #2d323b;
		--border-strong: #414855;

		--grid-dot-light: rgba(255, 255, 255, 0.1);
		--grid-dot-standard: rgba(255, 255, 255, 0.15);
		--grid-dot-strong: rgba(255, 255, 255, 0.28);
		--grid-fine-light: rgba(255, 255, 255, 0.04);
		--grid-fine-standard: rgba(255, 255, 255, 0.05);
		--grid-fine-strong: rgba(255, 255, 255, 0.09);
		--grid-coarse-light: rgba(255, 255, 255, 0.08);
		--grid-coarse-standard: rgba(255, 255, 255, 0.1);
		--grid-coarse-strong: rgba(255, 255, 255, 0.18);
		--watermark: rgba(255, 255, 255, 0.22);

		--node-body: #262a32;
		--node-border: #3a404b;
		--node-shadow: 0 3px 10px rgba(0, 0, 0, 0.45);
		--head-tint: 0.24;
		--cat-ink: 45%;
		/* Still darker than the canvas, or the well stops reading as one. */
		--pin-well: #06080b;

		--comment-fill: 0.14;

		--wire-exec: #b9c0cc;

		--accent: #5b9bd5;
		--danger: #e06c5a;
		--warning: #d8a83a;
		--ok: #5cb87a;
	}
}

* {
	box-sizing: border-box;
}

html,
body,
#root {
	height: 100%;
	margin: 0;
}

body {
	background: var(--bg-app);
	color: var(--fg);
	font: 13px/1.45 "Segoe UI", system-ui, -apple-system, sans-serif;
	overflow: hidden;
	-webkit-font-smoothing: antialiased;
}

button,
input,
select,
textarea {
	font: inherit;
	color: inherit;
}

/* The list a <select> opens is a browser widget, not part of this page, and it
 * was rendering light under a dark app — unhovered entries close to invisible,
 * because only the hovered one got a system highlight to rescue it.
 *
 * **The colours here are literals on purpose.** Chromium paints that popup
 * outside the document, and custom properties do not resolve there: with
 * `var(--bg-input)` the popup stays light, and with the same colour written out
 * it does not. The computed style on the option is correct either way, so this
 * is invisible to devtools and only shows up by opening the thing and looking.
 *
 * Which means these four values are a copy of four tokens and will not follow
 * them. They are the input surface in each theme; change one, change the other.
 */
select option,
select optgroup {
	background-color: #ffffff;
	color: #1c1f24;
}

@media (prefers-color-scheme: dark) {
	select option,
	select optgroup {
		background-color: #23272f;
		color: #e4e7ec;
	}
}

/* ------------------------------------------------------------------ shell */

/* A column rather than a grid with named rows, because one of the rows comes
   and goes: the document bar is only there when a document is open, and fixed
   `grid-template-rows` would hand the workspace the wrong row when it is not. */
.app {
	display: flex;
	flex-direction: column;
	height: 100%;
}

/* ------------------------------------------------------------- workspace */

/* The canvas fills the window and everything else floats over it. The docks
   are columns of cards over the centre, placed by Workspace from the layout's
   sizes; the top clusters and the side strip float over the centre itself.
   See docs/PANELS.md for the layout model, which this only draws. */
.workspace {
	flex: 1;
	position: relative;
	min-height: 0;
	min-width: 0;
	overflow: hidden;
	--strip-w: 0px;
}

/* Room for the side strip, when the centre shows a canvas that has one. */
.workspace.has-strip {
	--strip-w: calc(var(--hit) + 8px + var(--edge));
}

/* Its own stacking context, so the canvas's layers -- nodes at 50, its menus at
   70 -- stay inside it and the cards float over all of them. */
.centre {
	position: absolute;
	inset: 0;
	z-index: 0;
	isolation: isolate;
	display: flex;
	flex-direction: column;
	min-width: 0;
	min-height: 0;
	background: var(--bg-canvas);
}

/* The graph, node map, source view or placeholder. The floating tools are
   positioned against it, over whatever it shows. */
.centre-body {
	flex: 1;
	display: grid;
	grid-template-rows: minmax(0, 1fr);
	min-width: 0;
	min-height: 0;
	position: relative;
}

/* A dock is a column of cards, and takes no clicks itself: between and below
   its cards is still the graph. */
.dock {
	position: absolute;
	z-index: 3;
	display: flex;
	flex-direction: column;
	gap: 8px;
	min-width: 0;
	min-height: 0;
	box-sizing: border-box;
	pointer-events: none;
}

.dock.left {
	left: calc(var(--edge) + var(--strip-w));
	top: var(--chrome-top);
	/* Clear of the status pill below it, however tall that is. */
	max-height: calc(100% - var(--chrome-top) - var(--pill-h, var(--hit)) - var(--edge) * 3);
}

.dock.right {
	right: var(--edge);
	top: var(--chrome-top);
	max-height: calc(100% - var(--chrome-top) - var(--edge));
}

/* The status pill, and what it opens into. Sized by what it says. */
.dock.bottom {
	left: var(--edge);
	bottom: var(--edge);
	max-width: min(640px, calc(100% - var(--edge) * 2));
	max-height: 45%;
}

/* The strip along the foot: the bottom dock's cards other than the pill -- the
   Code panel. Between the side columns and above the pill's row, so neither
   moves; as tall as the dock's size, set inline. The pill is where it was, and
   when it opens to list problems it lies over the strip's corner rather than
   pushing it about while code is being typed. In full view the strip grows
   to the top bar. See `FootDock`. */
.dock.foot {
	flex-direction: row;
	left: calc(var(--edge) * 2 + var(--strip-w) + var(--left-w, 0px));
	right: calc(var(--edge) * 2 + var(--right-w, 0px));
	bottom: calc(var(--edge) * 2 + var(--hit) + 6px);
	max-height: calc(100% - var(--chrome-top) - var(--edge) * 3 - var(--hit) - 6px);
}

.dock.foot.full {
	top: var(--chrome-top);
	max-height: none;
	z-index: 4;
}

/* The pill over the strip's corner, open or shut. */
.dock.bottom {
	z-index: 5;
}

.dock.foot > .card {
	flex: 1 1 0;
	min-width: 0;
	display: flex;
	flex-direction: column;
}

.dock.foot > .card.folded {
	align-self: flex-end;
}

/* The top edge drags the strip's height. */
.dock.foot > .splitter {
	left: 0;
	right: 0;
	top: -9px;
	bottom: auto;
	width: auto;
	height: 12px;
	cursor: row-resize;
}

.dock.foot > .splitter::after {
	top: 5px;
	bottom: auto;
	left: 12px;
	right: 12px;
	width: auto;
	height: 2px;
}

.panel {
	display: flex;
	flex-direction: column;
	min-width: 0;
	min-height: 0;
}

/* Each card is glass over the graph: as tall as what it shows and no taller
   when it is alone in its dock, its share of the column when it is not. See
   `Cards.tsx`. */
.dock > .card {
	pointer-events: auto;
	flex: 0 1 auto;
	overflow: hidden;
	/* Nearly solid: a card holds dense text, and the graph showing through
	   it reads as noise. The small clusters keep the lighter glass. */
	background: color-mix(in srgb, var(--bg-panel) 98%, transparent);
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-raised);
	backdrop-filter: blur(14px) saturate(1.2);
	-webkit-backdrop-filter: blur(14px) saturate(1.2);
}

/* The tree scrolls inside its own card, and Variables keeps the height of
   what it lists up to a share of the column. */
.dock .panel-tree .tree {
	min-height: 0;
	overflow: auto;
}

.dock .panel-variables,
.dock .panel-inspector,
.dock .panel-properties {
	overflow: auto;
}

.dock.bottom > .card {
	overflow: auto;
}

/* The grab handle on a card's inner edge. Invisible until the pointer finds
   it, and wider to hit than to see. */
.dock > .splitter {
	position: absolute;
	top: 0;
	bottom: 0;
	width: 12px;
	z-index: 1;
	cursor: col-resize;
	touch-action: none;
	pointer-events: auto;
}

.dock.left > .splitter {
	right: -9px;
}

.dock.right > .splitter {
	left: -9px;
}

.dock > .splitter::after {
	content: "";
	position: absolute;
	top: 12px;
	bottom: 12px;
	left: 5px;
	width: 2px;
	border-radius: 1px;
	background: transparent;
	transition: background 120ms ease;
}

.dock > .splitter:hover::after,
.dock > .splitter:active::after {
	background: var(--accent);
}

/* ---------------------------------------------------------- floating tools */

/* Along the top edge of the view. The container takes no pointer events, so
   the gaps between groups are still the canvas. See FloatingTools.tsx. */
.floating-tools {
	position: absolute;
	top: var(--edge);
	left: var(--edge);
	right: var(--edge);
	z-index: 5;
	display: flex;
	align-items: flex-start;
	flex-wrap: wrap;
	gap: 8px;
	pointer-events: none;
}

.floating-tools .spacer {
	flex: 1;
}

/* A cluster: glass over the canvas, so the graph still reads through, and the
   buttons in it bare until the pointer is on one. Procreate's bar is the model:
   a few small groups at the window's edge, nothing in a full-width strip. */
.tool-group {
	pointer-events: auto;
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 2px;
	padding: 3px;
	background: var(--glass);
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-raised);
	backdrop-filter: blur(14px) saturate(1.2);
	-webkit-backdrop-filter: blur(14px) saturate(1.2);
}

.tool-group button.tb,
.tool-group select.tb {
	min-height: var(--hit);
	background: transparent;
	border-color: transparent;
}

.tool-group button.tb.icon-only {
	width: var(--hit);
	height: var(--hit);
}

.tool-group button.tb:hover:not(:disabled),
.tool-group select.tb:hover {
	background: var(--bg-hover);
	border-color: transparent;
}

.tool-group button.tb.on,
.tool-group button.tb.icon-only.on,
.tool-group button.tb.on:hover:not(:disabled) {
	background: color-mix(in srgb, var(--accent) 20%, transparent);
	border-color: transparent;
	color: var(--accent);
}

.tool-group button.tb.primary,
.tool-group button.tb.primary:hover:not(:disabled) {
	background: var(--accent);
	color: #fff;
}

/* In a cluster, a switch is two of the cluster's own buttons: the same height,
   corner and padding as the button beside it, lit with the tint. */
.tool-group .segmented {
	padding: 0;
	gap: 2px;
	background: none;
	border: 0;
}

.tool-group .segmented button {
	min-height: var(--hit);
	padding: 0 12px;
	border-radius: var(--radius-sm);
	font-size: var(--text-md);
}

.tool-group .segmented button.on {
	font-weight: 600;
}

.tool-group .divider {
	width: 1px;
	align-self: stretch;
	margin: 5px 3px;
	background: var(--border-strong);
}

.tool-group .doc-name {
	font-weight: 600;
	margin: 0 2px 0 4px;
}

/* Not a graph tab: a tab carries its own dot, `.tab-dirty`, and a second one
   here landed after its close button. */
.tool-group .dirty:not(.graph-tab)::after {
	content: "•";
	color: var(--warning);
	margin-left: 5px;
}

.tool-group .doc-of {
	font-weight: 400;
	color: var(--fg-muted);
}

/* Unsaved edits, when the name that would carry the dot is hidden. */
.tool-group .doc-dirty {
	width: 6px;
	height: 6px;
	margin: 0 2px 0 4px;
	border-radius: 50%;
	background: var(--warning);
}

.tool-group select.doc-target.lune {
	color: var(--warning);
	border-color: color-mix(in srgb, var(--warning) 60%, transparent);
}

/* While a panel is in flight, nothing in the workspace is selectable. The
   selection is also cleared once when the drag begins; this stops a new one
   starting under the pointer as it travels. */
.workspace.dragging {
	user-select: none;
	cursor: grabbing;
}

/* Where a dragged panel would land. Over everything, hit by nothing — an
   overlay that accepted pointer events would be the answer `elementFromPoint`
   gave for every position under it, which is every position. */
.drop-preview {
	position: absolute;
	z-index: 30;
	pointer-events: none;
	background: color-mix(in srgb, var(--accent) 22%, transparent);
	border: 2px solid var(--accent);
}

/* A panel heading is a handle, so it says so on the way past. The panels'
   headings keep every job they already had — the Add button still adds, the
   diagnostics bar still collapses — because nothing is a drag until the
   pointer has actually moved. */
.dock h2,
.dock .bar,
.float-panel h2,
.float-panel .bar {
	cursor: grab;
}

.dock h2:active,
.dock .bar:active,
.float-panel h2:active,
.float-panel .bar:active {
	cursor: grabbing;
}

/* ------------------------------------------------------------- graph tabs */

/* The open graphs, as a group among the floating tools: the active one's name
   in full, the others beside it while there is room, and the list at the end.
   The group scrolls sideways rather than growing past its share of the row. */
.graph-tabs {
	flex: 0 1 auto;
	flex-wrap: nowrap;
	min-width: 0;
	max-width: min(560px, 46vw);
	overflow-x: auto;
	overflow-y: hidden;
	scrollbar-width: none;
}

.graph-tabs::-webkit-scrollbar {
	display: none;
}

.graph-tab {
	display: flex;
	align-items: center;
	gap: 6px;
	flex: none;
	height: var(--hit);
	padding: 0 6px 0 10px;
	border-radius: var(--radius-sm);
	color: var(--fg-muted);
	cursor: pointer;
	white-space: nowrap;
	max-width: 220px;
}

.graph-tab:hover {
	background: var(--bg-hover);
}

/* The open graph: its name, plainly, as the document's title. */
.graph-tab.on {
	color: var(--fg);
	font-weight: 600;
	background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.graph-tab .name {
	overflow: hidden;
	text-overflow: ellipsis;
}

/* Unsaved edits: an element of its own beside the name, so the name's
   ellipsis can never cut it off. */
/* A picker field with nothing chosen yet. */
.literal.picker .preview-unset {
	color: var(--fg-faint);
	font-style: italic;
}

.graph-tab .tab-dirty {
	flex: none;
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: var(--warning);
}

/* A tab's kind, coloured as the project tree colours the same file: graphs
   and functions in the accent, maps green, Luau by the script it becomes. */
.tab-kind {
	flex: none;
	color: var(--fg-muted);
}

.tab-kind.tab-kind-nodescript,
.tab-kind.tab-kind-function {
	color: var(--accent);
}

.tab-kind.tab-kind-nodemap {
	color: var(--ok);
}

.tab-kind.tree-script-server {
	color: var(--tree-script-server);
}

.tab-kind.tree-script-local {
	color: var(--tree-script-local);
}

.tab-kind.tree-script-module {
	color: var(--tree-script-module);
}

.graph-tab .close {
	background: none;
	border: 0;
	color: var(--fg-faint);
	cursor: pointer;
	font-size: var(--text-lg);
	line-height: 1;
	padding: 0 3px;
	border-radius: var(--radius-xs);
	visibility: hidden;
}

/* Shown on the tab you are pointing at, and always on the active one. A row of
   close buttons is a row of things to hit by accident. */
.graph-tab:hover .close,
.graph-tab.on .close {
	visibility: visible;
}

.graph-tab .close:hover {
	background: var(--bg-active);
	color: var(--danger);
}

/* A tab being dragged fades rather than moving: the tabs are narrow, so
   animating positions would be motion nobody can follow. The line marking
   where it lands is what says what will happen. */
.graph-tabs.reordering .graph-tab {
	cursor: grabbing;
}

.graph-tab.dragging {
	opacity: 0.45;
}

.graph-tab.drop-before,
.tab-rest.drop-before {
	box-shadow: inset 2px 0 0 var(--accent);
}

/* The end of the group: a drop target, and the home of the list button. */
.tab-rest {
	display: flex;
	align-items: center;
	flex: none;
	min-width: 8px;
}

.tab-list {
	position: relative;
	display: flex;
	align-items: center;
}

.tab-list > button.tb {
	width: var(--hit);
	height: var(--hit);
	padding: 0;
	display: grid;
	place-items: center;
}

/* On a phone the group is the open graph's name and the list; the others are
   in the list. */
@media (max-width: 699px) {
	.graph-tab:not(.on) {
		display: none;
	}

	.graph-tabs {
		max-width: 44vw;
	}

	.graph-tab.on .close {
		display: none;
	}
}

/* ----------------------------------------------------------- value picker */

/* Search at the top, everything below it in columns. Sized to the window
   rather than to its contents: the list is six hundred long and the useful
   thing is how much of it you can see at once. */
.value-picker {
	/* The shared backdrop is a grid that centres on one axis only, because the
	   docs overlay fills the other. This one is a fixed width and has to place
	   itself across. */
	justify-self: center;
	display: flex;
	flex-direction: column;
	width: min(1040px, 92vw);
	height: min(660px, 84vh);
	background: var(--bg-panel);
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-modal);
	overflow: hidden;
}

.value-search {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 8px 10px;
	border-bottom: 1px solid var(--border);
	flex: none;
}

/* The search field, with its glyph inside it. The field keeps the glyph's room
   whatever padding the field style gives it. */
.value-field {
	position: relative;
	flex: 1;
	min-width: 0;
	display: flex;
}

.value-field > .icon {
	position: absolute;
	left: 10px;
	top: 50%;
	transform: translateY(-50%);
	color: var(--fg-faint);
	pointer-events: none;
}

.value-field > input.tb {
	flex: 1;
	min-width: 0;
	padding-left: 32px;
	min-height: 30px;
}

.value-search > .tb.icon-only {
	flex: none;
	width: 30px;
	height: 30px;
	padding: 0;
	display: inline-grid;
	place-items: center;
}

/* The groups down the side, and the list beside them. */
.value-main {
	flex: 1;
	min-height: 0;
	display: grid;
	grid-template-columns: minmax(0, 1fr);
}

.value-main.with-rail {
	grid-template-columns: 170px minmax(0, 1fr);
}

.value-rail {
	overflow-y: auto;
	padding: 6px;
	border-right: 1px solid var(--border);
	background: color-mix(in srgb, var(--accent) 5%, var(--bg-panel));
	display: flex;
	flex-direction: column;
	gap: 1px;
}

.value-rail button {
	display: flex;
	align-items: center;
	gap: 6px;
	width: 100%;
	padding: 4px 8px;
	border: 0;
	border-radius: var(--radius-xs);
	background: transparent;
	color: var(--fg-muted);
	font-size: var(--text-sm);
	text-align: left;
	cursor: pointer;
}

.value-rail button:hover {
	background: var(--bg-hover);
	color: var(--fg);
}

.value-rail button.on {
	background: color-mix(in srgb, var(--accent) 16%, transparent);
	color: var(--fg);
	font-weight: 600;
}

.value-rail-name {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.value-rail-count {
	margin-left: auto;
	font-weight: 400;
	color: var(--fg-faint);
	font-variant-numeric: tabular-nums;
}

/* No padding above the groups: a heading held at the top sits flush with the
   list's edge, so nothing scrolls past above it. */
.value-list {
	min-height: 0;
	overflow-y: auto;
	padding: 0 12px 10px;
}

.value-group + .value-group {
	margin-top: 4px;
}

.value-group > .pane-head {
	position: sticky;
	top: 0;
	z-index: 1;
	margin-top: 0;
	background: var(--bg-panel);
}

/* Columns rather than one tall list, which is what makes six hundred names
   scannable — and what Studio's Insert Object does for the same reason. They
   are sized rather than counted, so a narrow window gets fewer of them. */
.value-options {
	column-width: 190px;
	column-gap: 10px;
	padding: 4px 0 6px;
}

.value-option {
	display: flex;
	align-items: center;
	gap: 7px;
	width: 100%;
	text-align: left;
	padding: 3px 8px;
	border: 0;
	border-radius: var(--radius-xs);
	background: none;
	color: var(--fg);
	font: var(--text-sm) "Cascadia Mono", Consolas, monospace;
	cursor: pointer;
	white-space: nowrap;
	break-inside: avoid;
}

.value-name {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
}

.value-dot {
	flex: none;
	width: 8px;
	height: 8px;
	border-radius: 50%;
}

.value-option.on {
	background: var(--bg-hover);
}

/* The value the pin already holds, ringed even when the highlight is elsewhere
   -- otherwise a picker opened to browse loses track of where it started. */
.value-option.current {
	color: var(--accent);
	font-weight: 600;
	background: color-mix(in srgb, var(--accent) 16%, transparent);
	box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent);
}

.value-empty {
	margin: 12px 4px;
	color: var(--fg-muted);
}

/* The inheritance chain as chips, or the count, and the keys. A fixed row so
   the list above it does not jump every time the highlight moves. */
.value-detail {
	flex: none;
	display: flex;
	align-items: center;
	gap: 6px;
	min-height: 22px;
	padding: 7px 12px;
	border-top: 1px solid var(--border);
	color: var(--fg-faint);
	font-size: var(--text-sm);
	white-space: nowrap;
	overflow: hidden;
}

.value-chain {
	display: inline-flex;
	align-items: center;
	gap: 6px;
}

.value-detail-text {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
}

.value-keys {
	margin-left: auto;
	padding-left: 12px;
	font-size: var(--text-xs);
}

@media (max-width: 699px) {
	.value-main.with-rail {
		grid-template-columns: minmax(0, 1fr);
	}

	.value-rail,
	.value-keys {
		display: none;
	}
}

/* The pin's own control: what it holds, and a chevron saying there is more. */
.literal.picker {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 4px;
	cursor: pointer;
	text-align: left;
}

.literal.picker .preview {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* --------------------------------------------------------- floating panels */

/* The windows' own layer, the centre's size: over the graph and under the
   docks, and over the docks while one of its windows is the card in use --
   level with the pill, and under the top clusters, which come after it. */
.float-layer {
	position: absolute;
	inset: 0;
	z-index: 2;
	pointer-events: none;
}

.float-layer.front {
	z-index: 5;
}

.float-layer > * {
	pointer-events: auto;
}

/* A panel out of its dock, over the graph. Positioned inside `.centre`, so its
   coordinates are the graph's own and opening a dock does not drag it sideways. */
.float-panel {
	position: absolute;
	display: flex;
	flex-direction: column;
	min-width: 200px;
	min-height: 140px;
	background: var(--bg-panel);
	border: 1px solid var(--border-strong);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-popover);
	overflow: hidden;
	/* Over the canvas and its wires, under the menus and dialogs. */
	z-index: 60;
}

/* The panel's own heading is the window's title bar, so it says what a title
   bar says and carries what the panel already put there. The room for the
   button over its right end is in `.dock h2`, which both shapes share. */
.float-panel .float-body h2 {
	user-select: none;
}

/* The two buttons that move a panel between a dock and a window: ⇥ out of the
   dock, ⇤ back into it.
   
   Sized to the Add button beside them rather than to the toolbar's icon
   buttons, which are 28px and tower over a heading. They sit on the heading's
   own row, so they have to be that row's height. */
button.tb.icon-only.float-dock,
button.tb.icon-only.panel-float {
	width: 20px;
	height: 20px;
	min-width: 0;
	padding: 0;
	font-size: var(--text-md);
	line-height: 1;
	z-index: 2;
}

/* How far down the button sits, so it is centred on the row it shares.

   One value per shape of header, because there are two. Every panel heads
   itself with an `h2`, 32px tall; the diagnostics panel heads itself with the
   errors-and-warnings bar, which is 25px with a rule above it. This was a flat
   10px, measured off the `h2` and applied to both -- so on the bar the button
   sat 6px low and hung four pixels out of the bottom of the row it belongs to.

   Written as the offset rather than derived from a row height, so each number
   says what it is for instead of being the output of arithmetic that would
   also need explaining. */
.panel,
.float-panel {
	--dock-button-top: 6px;
}
.panel-analysis,
.float-analysis {
	--dock-button-top: 3px;
}

/* Over the heading, level with the button already in it. */
.float-panel .float-dock {
	position: absolute;
	top: var(--dock-button-top);
	right: 8px;
}

/* The same button in a dock, pointing the other way.

   Sticky rather than absolute, and above the heading in the flow with a
   negative margin pulling the heading back up under it: the dock scrolls, the
   heading is sticky, and a button pinned to the panel would scroll away from
   the heading it belongs to. The bottom margin is the button's whole block
   height -- its own 20px and the offset above it -- so the heading lands where
   it would have without it, whatever that offset is. */
.panel .panel-float {
	position: sticky;
	top: var(--dock-button-top);
	align-self: flex-end;
	margin: var(--dock-button-top) 8px calc(-20px - var(--dock-button-top)) 0;
}

/* The window's scrolling half, which is the job `.dock.left` does for a dock:
   one scroll container round the panel rather than one per panel. */
.float-body {
	flex: 1;
	min-height: 0;
	display: flex;
	flex-direction: column;
	overflow: auto;
}

/* The panel fills the window, the way it fills a dock — and *only* the panel.
   This was `> *`, which reached every element a panel renders: the project
   tree draws its outline and its tree as two siblings, and both grew to half
   the window, leaving a screen of nothing above the tree. */
.float-body > .panel {
	flex: 1;
	min-height: 0;
}

.float-size {
	position: absolute;
	width: 16px;
	height: 16px;
	cursor: nwse-resize;
	/* Over the panel's own content, or a list that reaches the corner takes the
	   drag and the window cannot be resized from that end. */
	z-index: 3;
}

.float-size::after {
	content: "";
	position: absolute;
	width: 8px;
	height: 8px;
}

.float-size.se { right: 0; bottom: 0; }

.float-size.se::after {
	right: 3px;
	bottom: 3px;
	border-right: 2px solid var(--border-strong);
	border-bottom: 2px solid var(--border-strong);
}

.float-size.nw { left: 0; top: 0; }

.float-size.nw::after {
	left: 3px;
	top: 3px;
	border-left: 2px solid var(--border-strong);
	border-top: 2px solid var(--border-strong);
}

/* The type control: what it holds, and a chevron saying the list is there. The
   same shape as a pin's class picker, because it opens the same window. */
.tb.type-picker {
	display: flex;
	align-items: center;
	gap: 6px;
	width: 100%;
	min-width: 0;
	text-align: left;
	font: var(--text-sm) "Cascadia Mono", Consolas, monospace;
	color: var(--fg);
}

/* The type's wire colour, so the field reads as a value rather than a menu. */
.tb.type-picker .type-dot {
	flex: none;
	width: 8px;
	height: 8px;
	border-radius: 50%;
}

.tb.type-picker .preview {
	flex: 1;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.tb.type-picker > .icon {
	flex: none;
	color: var(--fg-faint);
}

/* A constant, marked in the Variables list. Named `const` on the badge because
   that is the word it writes into the file — a mark that reads as the keyword
   is one fewer thing to learn. */
.variable-head .badge.const {
	font-size: var(--text-xs);
	font-family: var(--mono, ui-monospace, monospace);
	color: var(--warning);
	border: 1px solid color-mix(in srgb, var(--warning) 55%, transparent);
	background: color-mix(in srgb, var(--warning) 12%, transparent);
	border-radius: var(--radius-xs);
	padding: 0 4px;
	flex: none;
}

/* ------------------------------------------------------------- node picker */

/* Two columns: the list you walk and the node you are walking towards. The
   picture is the whole reason this exists beside the palette. */
.node-picker {
	width: min(880px, 100%);
	max-height: 72vh;
	display: flex;
	flex-direction: column;
	background: var(--bg-panel);
	border: 1px solid var(--border-strong);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-modal);
	overflow: hidden;
}

.node-picker-field {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 10px 12px;
	border-bottom: 1px solid var(--border);
}

.node-picker-field input {
	flex: 1;
	min-width: 0;
	background: none;
	border: 0;
	color: var(--fg);
	font-size: var(--text-xl);
	outline: none;
}

.node-picker-field .count {
	font-size: var(--text-sm);
	color: var(--fg-faint);
}

.node-picker-body {
	flex: 1;
	min-height: 0;
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
}

.node-picker-list {
	overflow: auto;
	padding: 6px;
	border-right: 1px solid var(--border);
	display: flex;
	flex-direction: column;
	gap: 1px;
}

.node-picker-group .head {
	font-size: var(--text-xs);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: var(--fg-faint);
	padding: 8px 8px 4px;
}

.node-picker-list .empty {
	padding: 12px 8px;
	color: var(--fg-faint);
	font-size: var(--text-md);
}

.node-picker-hit {
	display: flex;
	align-items: baseline;
	gap: 8px;
	width: 100%;
	background: none;
	border: 0;
	border-radius: var(--radius-sm);
	padding: 5px 8px;
	text-align: left;
	cursor: pointer;
	color: var(--fg-muted);
	font-size: var(--text-md);
}

.node-picker-hit.on {
	background: color-mix(in srgb, var(--accent) 20%, transparent);
	color: var(--fg);
}

/* As the menu's: the title gives way, and the tags stay on one line. */
.node-picker-hit .title {
	flex: 0 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* A member a script can read and not set. Not dimmed: reading it is fine. */
.menu .item .read-only-mark,
.node-picker-hit .read-only-mark {
	flex: none;
	align-self: center;
	color: var(--fg-muted);
}

.node-picker-hit .hint {
	margin-left: auto;
	font-size: var(--text-xs);
	color: var(--fg-faint);
	flex: none;
	white-space: nowrap;
	/* Same as the menu's: this row is baseline-aligned too. */
	align-self: center;
}

/* The canvas's own background under the picture, so a node is read against
   what it will sit on rather than against a panel. */
.node-picker-preview {
	display: flex;
	flex-direction: column;
	gap: 12px;
	padding: 16px;
	overflow: auto;
	background: var(--bg-canvas, var(--bg-input));
}

.node-picker-preview .shot {
	display: flex;
	justify-content: center;
	align-items: center;
	min-height: 120px;
}

.node-picker-preview .name { font-size: var(--text-lg); color: var(--fg); }
.node-picker-preview .where {
	font-size: var(--text-xs);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: var(--fg-faint);
}

.node-picker-preview .summary {
	margin: 6px 0 0;
	font-size: var(--text-md);
	color: var(--fg-muted);
	line-height: 1.5;
}

.node-picker-foot {
	display: flex;
	gap: 14px;
	padding: 8px 12px;
	border-top: 1px solid var(--border);
	font-size: var(--text-xs);
	color: var(--fg-faint);
}

.node-picker-foot kbd {
	font-family: inherit;
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	padding: 0 4px;
	margin-right: 3px;
}

.node-picker-foot {
	flex-wrap: wrap;
}

.node-picker-foot-touch {
	display: none;
}

@media (hover: none) and (pointer: coarse) {
	.node-picker-foot-keys {
		display: none;
	}

	.node-picker-foot-touch {
		display: flex;
	}
}

.node-picker-spawn {
	align-self: flex-start;
}

/* A row being dragged out: the picker steps aside so the graph can be seen
   and dropped on, and lets the pointer through to it. */
.picker-backdrop.picker-backdrop-dragging {
	pointer-events: none;
	opacity: 0.15;
	background: transparent;
}

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

.toolbar {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 3px 8px;
	background: var(--bg-panel);
	border-bottom: 1px solid var(--border);
	flex-wrap: wrap;
}

/* The mark, and whatever sits with it. Centred rather than baseline-aligned:
   an SVG has no baseline, so `baseline` drops it to the bottom of the line box
   and it rides low against the text next to it. */
.toolbar .logo {
	font-weight: 600;
	letter-spacing: 0.04em;
	margin-right: 4px;
	display: inline-flex;
	align-items: center;
	gap: 6px;
	/* A <button> since 0.18.2, so the browser's own button paint has to be
	   taken off before it looks like the mark it replaced. */
	background: none;
	border: 1px solid transparent;
	border-radius: var(--radius-sm);
	padding: 3px 6px;
	color: inherit;
	font-family: inherit;
	cursor: pointer;
}

/* It does something now, so it has to look like it can. Left invisible until
   the pointer is on it: a bordered button sitting where the mark used to be
   reads as the toolbar having grown a control, rather than as the mark having
   gained a menu. */
.toolbar .logo:hover,
.toolbar .logo:focus-visible {
	background: var(--bg-hover);
	border-color: var(--border);
}

/* The version, deliberately quiet. It only matters when something is wrong —
   and then it is the first thing anyone asks for. */
.toolbar .logo .version {
	font-weight: 400;
	font-size: var(--text-xs);
	letter-spacing: 0;
	color: var(--fg-faint);
	font-variant-numeric: tabular-nums;
}

.toolbar .spacer {
	flex: 1;
}

/* Names what a segmented control sets, where the buttons alone cannot. Quiet,
   because it is a caption rather than a control: it should be readable when
   you are looking for it and invisible when you are not. */
.toolbar .group-label {
	font-size: var(--text-sm);
	color: var(--fg-muted);
	margin-right: -3px;
	white-space: nowrap;
}

.toolbar .doc-name {
	font-weight: 600;
	margin-right: 2px;
}

.toolbar .dirty::after {
	content: "•";
	color: var(--warning);
	margin-left: 5px;
}

button.tb,
select.tb,
input.tb {
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	padding: 4px 9px;
	cursor: pointer;
}

button.tb:hover:not(:disabled),
select.tb:hover {
	background: var(--bg-hover);
	border-color: var(--border-strong);
}

button.tb:disabled {
	opacity: 0.45;
	cursor: default;
}

/* A toolbar button that is a mode rather than an action. Held-down rather than
   filled, because it sits beside real buttons and should not read as the one
   thing on the bar you are meant to press. */
button.tb.on {
	background: color-mix(in srgb, var(--accent) 18%, var(--bg-input));
	border-color: var(--accent);
	color: var(--accent);
}

button.tb.on:hover:not(:disabled) {
	background: color-mix(in srgb, var(--accent) 26%, var(--bg-input));
	border-color: var(--accent);
}

button.tb.primary {
	background: var(--accent);
	border-color: transparent;
	color: #fff;
}

button.tb.primary:hover:not(:disabled) {
	filter: brightness(1.1);
	background: var(--accent);
}

/* A track with the chosen option lit inside it. Lit as a held mode is --
   `button.tb.on`, Straighten -- with a tint rather than a fill, so a setting
   does not shout louder than the action beside it. */
.segmented {
	display: inline-flex;
	gap: 2px;
	padding: 2px;
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
}

.segmented button {
	background: transparent;
	border: 0;
	border-radius: var(--radius-xs);
	padding: 3px 10px;
	color: var(--fg-muted);
	cursor: pointer;
}

.segmented button:hover {
	background: var(--bg-hover);
	color: var(--fg);
}

.segmented button.on {
	background: color-mix(in srgb, var(--accent) 20%, transparent);
	color: var(--accent);
}

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

/* Scoped to any dock rather than to one of them, because a panel keeps its
   look wherever it is docked -- which is the whole point of being able to move
   it. This was `.sidebar h2` until the sidebar became `.dock.left`, and the
   rename orphaned it: the project name and the Variables heading both fell
   back to the browser's default 2em bold, because `.variables h2` only sets
   its own flex properties and inherited everything else from here.
   
   And a second time in 0.46.0, when a panel could be in a window instead of a
   dock: the same heading, the same 2em bold. "Wherever it is docked" now has to
   include "and where it is not", so the window is named here too rather than
   restating the rule beside itself. */
.dock h2,
.float-panel h2 {
	font-size: var(--text-sm);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: var(--fg-faint);
	margin: 0;
	/* Wide on the right: both shapes hang a button over that end — the window's
	   dock button, the dock's undock one — and a heading with an Add button in
	   it has no room to spare otherwise. */
	padding: 10px 34px 6px 12px;
	position: sticky;
	top: 0;
	background: var(--bg-panel);
	z-index: 1;
}

.tree {
	padding: 0 6px 12px;
	user-select: none;
	flex: 1;
}

.tree-row {
	position: relative;
	display: flex;
	align-items: center;
	gap: 6px;
	padding: 3px 6px;
	border-radius: var(--radius-xs);
	cursor: default;
	white-space: nowrap;
}

/* Indent guides: a hairline under each open folder, one per level of depth,
   through the middle of that level's chevron. A graph with functions puts its
   own chevron in its parent's column, so its row stops a line short of it. */
.tree-row:has(> .function-twist)::before {
	width: calc((var(--depth, 0) - 1) * 13px);
}

.tree-row::before {
	content: "";
	position: absolute;
	top: 0;
	bottom: 0;
	left: calc(1px + var(--guide-from, 0) * 13px);
	width: calc(var(--depth, 0) * 13px);
	background: repeating-linear-gradient(
		to right,
		transparent 0 12px,
		color-mix(in srgb, var(--fg) 14%, transparent) 12px 13px
	);
	pointer-events: none;
}

.tree-row:hover {
	background: var(--bg-hover);
}

.tree-row.selected {
	background: color-mix(in srgb, var(--accent) 16%, transparent);
}

/* The document in front: its name in bold, rather than a rule down the side. */
.tree-row.open-doc .label {
	font-weight: 650;
	color: var(--fg);
}

.tree-row.drop-target {
	outline: 1px dashed var(--accent);
	outline-offset: -1px;
}

.tree-row .glyph {
	width: 14px;
	text-align: center;
	color: var(--fg-faint);
	flex: none;
}

.tree-row .label {
	overflow: hidden;
	text-overflow: ellipsis;
}

.tree-row.readonly .label {
	color: var(--fg-muted);
}

.tree-row .badge {
	margin-left: auto;
	font-size: var(--text-xs);
	color: var(--fg-faint);
	border: 1px solid var(--border);
	border-radius: 999px;
	padding: 0 6px;
}

/* ----------------------------------------------------------------- canvas */

.canvas {
	position: relative;
	overflow: hidden;
	background: var(--bg-canvas);
	touch-action: none;
	min-height: 0;
	contain: strict;
}

.canvas:focus {
	outline: none;
}

.canvas .grid {
	position: absolute;
	inset: 0;
	/* A canvas keeps its own 300 by 150 unless told to fill. */
	display: block;
	width: 100%;
	height: 100%;
	pointer-events: none;
}

/* ------------------------------------------------------------- the grid */

/* The grid behind graphs, and the one place it is drawn: the canvas, Node
   Design, the start page, the node packs page, the sheets a map or a source
   opens in, and the docs' drawings of all of them. The front page and its
   tour use it too.

   Dots by default, one at every point Shift-drag snaps to, so the grid marks
   where a node can land without ruling the page into cells. Lines, the grid
   before 0.150.0, for anyone who would rather have them: `data-grid` on the
   root, from the Grid pattern setting.

   How strong it draws is `data-grid-contrast`, from Grid contrast: light by
   default, and strong when the system asks for more contrast and the setting
   has been left to follow it. The colours for each strength are the theme's,
   from `derivedTokens`.

   A surface sets `--grid-step` to change its spacing. The editor's canvas,
   which zooms, draws its grid itself from these same properties rather than
   painting this background: a repeating background is rounded to whole
   device pixels a tile at a time, and zooming made the dots jitter. See
   `GridLayer.tsx`. */
:root {
	--grid-step: 24px;
	--grid-x: 0px;
	--grid-y: 0px;
	--grid-dot: var(--grid-dot-light);
	--grid-fine: var(--grid-fine-light);
	--grid-coarse: var(--grid-coarse-light);
	--grid-dot-size: 1.1px;
}

:root[data-grid-contrast="standard"] {
	--grid-dot: var(--grid-dot-standard);
	--grid-fine: var(--grid-fine-standard);
	--grid-coarse: var(--grid-coarse-standard);
}

:root[data-grid-contrast="strong"] {
	--grid-dot: var(--grid-dot-strong);
	--grid-fine: var(--grid-fine-strong);
	--grid-coarse: var(--grid-coarse-strong);
	--grid-dot-size: 1.4px;
}

@media (prefers-contrast: more) {
	:root:not([data-grid-contrast]),
	:root[data-grid-contrast="auto"] {
		--grid-dot: var(--grid-dot-strong);
		--grid-fine: var(--grid-fine-strong);
		--grid-coarse: var(--grid-coarse-strong);
		--grid-dot-size: 1.4px;
	}
}

/* Every surface that draws the grid. `:where` so it adds no weight: each
   surface's own rule keeps its colour, padding and the rest, and sets
   `background-color` rather than the `background` shorthand, which would
   take the grid away again. */
:where(
	.grid-surface,
	.start-page,
	.pack-browser,
	.node-editor-stage,
	.node-editor:not(.split) > .node-logic,
	.centre-body:has(> .map-editor, > .source, > .alias-doc),
	.docs-preview,
	.docs-bar-frame.float,
	.docs-layout-region.kind-canvas
) {
	background-image: radial-gradient(
		circle,
		var(--grid-dot) var(--grid-dot-size),
		transparent calc(var(--grid-dot-size) + 0.6px)
	);
	background-size: var(--grid-step) var(--grid-step);
	background-position: calc(var(--grid-x) - var(--grid-step) / 2)
		calc(var(--grid-y) - var(--grid-step) / 2);
}

/* Lines: the fine grid, and every fifth line heavier. */
:root[data-grid="lines"]
	:where(
		.grid-surface,
		.start-page,
		.pack-browser,
		.node-editor-stage,
		.node-editor:not(.split) > .node-logic,
		.centre-body:has(> .map-editor, > .source, > .alias-doc),
		.docs-preview,
		.docs-bar-frame.float,
		.docs-layout-region.kind-canvas
	) {
	background-image:
		linear-gradient(to right, var(--grid-coarse) 1px, transparent 1px),
		linear-gradient(to bottom, var(--grid-coarse) 1px, transparent 1px),
		linear-gradient(to right, var(--grid-fine) 1px, transparent 1px),
		linear-gradient(to bottom, var(--grid-fine) 1px, transparent 1px);
	background-size:
		calc(var(--grid-step) * 5) calc(var(--grid-step) * 5),
		calc(var(--grid-step) * 5) calc(var(--grid-step) * 5),
		var(--grid-step) var(--grid-step),
		var(--grid-step) var(--grid-step);
	background-position: var(--grid-x) var(--grid-y);
}

.canvas .world {
	position: absolute;
	top: 0;
	left: 0;
	transform-origin: 0 0;
	width: 0;
	height: 0;
}

/* What the canvas is showing, in its corner: the script's class and name, or
   the script and the function. One quiet line rather than a title across the
   work -- it is there to be found, not read -- with ink a little stronger
   than a bigger word would need, so the smaller text still reads. */
.canvas .watermark {
	position: absolute;
	right: 18px;
	bottom: 18px;
	display: flex;
	align-items: baseline;
	gap: 8px;
	font-size: var(--text-xl);
	font-weight: 700;
	color: var(--watermark);
	pointer-events: none;
	user-select: none;
	white-space: nowrap;
	line-height: 1.2;
}

/* On a graph with nothing in it: how to make the first node, at the top
   centre under the top row of clusters, as a solid card over the grid. */
.canvas .canvas-start-hint {
	position: absolute;
	top: calc(var(--chrome-top, 58px) + 14px);
	left: 50%;
	transform: translateX(-50%);
	padding: 7px 16px;
	border: 1px solid var(--border);
	border-radius: 999px;
	background: var(--bg-panel);
	box-shadow: var(--shadow-raised);
	color: var(--fg-muted);
	font-size: var(--text-md);
	font-weight: 600;
	white-space: nowrap;
	pointer-events: none;
	user-select: none;
}

.canvas .canvas-start-hint .when-touch {
	display: none;
}

@media (hover: none) and (pointer: coarse) {
	.canvas .canvas-start-hint .when-pointer {
		display: none;
	}

	.canvas .canvas-start-hint .when-touch {
		display: inline;
	}
}

.canvas .watermark small {
	font-size: var(--text-sm);
	font-weight: 600;
	letter-spacing: 0.12em;
	text-transform: uppercase;
}

.canvas .wires {
	position: absolute;
	left: 0;
	top: 0;
	width: 1px;
	height: 1px;
	overflow: visible;
	pointer-events: none;
}

.canvas .wires path {
	fill: none;
	stroke-linecap: round;
}

.canvas .wires path.hit {
	stroke: transparent;
	stroke-width: 12;
	pointer-events: stroke;
	cursor: pointer;
}

.canvas .marquee {
	position: absolute;
	border: 1px solid var(--select);
	background: color-mix(in srgb, var(--select) 14%, transparent);
	pointer-events: none;
}

/* ------------------------------------------------------------------- node */

.node {
	position: absolute;
	border-radius: var(--node-radius);
	background: var(--node-body);
	border: var(--node-stroke) solid var(--node-border);
	box-shadow: var(--node-shadow);
	user-select: none;
}

.node.node--selected {
	border-color: var(--select);
	box-shadow: 0 0 0 2px color-mix(in srgb, var(--select) 55%, transparent), var(--node-shadow);
}

/* The node a selection is anchored on -- the one Align lines the rest up
   with. A heavier ring rather than a second colour: it is a stronger case of
   being selected, not a different state, and a new hue on the canvas is one
   more thing to learn the meaning of. Restated for the capsule and the knot
   further down, where their own rules would otherwise win. */
/* A pinned node: Realign leaves it where it is. A dashed ring of its own
   colour, standing off the node so a selection's outline still shows inside
   it, and a pin in the corner the problem badge does not use. */
.node.node--pinned {
	outline: 2px dashed var(--pin);
	outline-offset: 5px;
}

.node .node-pin {
	position: absolute;
	top: -10px;
	left: -10px;
	width: 20px;
	height: 20px;
	display: grid;
	place-items: center;
	border-radius: 50%;
	background: color-mix(in srgb, var(--pin) 80%, transparent);
	color: rgb(255 255 255 / 0.85);
	pointer-events: none;
	z-index: 1;
}

.node .node-pin svg {
	transform: rotate(-30deg);
}

.node.node--selected.node--anchor {
	box-shadow: 0 0 0 4px var(--select), var(--node-shadow);
}

.node.node--has-error {
	border-color: var(--danger);
}

/* A tint of the category's colour rather than a block of it, with the title
   in the ordinary text colour.

   The solid headers were the loudest thing on the canvas, and every one of
   them was loud: a graph of twenty nodes read as twenty coloured bars before
   it read as anything else, and white text on the lighter categories was the
   hardest text in the editor to read. The colour is still there to find a
   category by -- in the corner tab, at full strength, where it is small enough
   not to shout. `--head-tint` is how much of it the header takes; dark schemes
   take more, because a 14% tint of a dark colour over a dark body vanishes. */
.node .head {
	height: 30px;
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 0 9px 0 0;
	/* One pixel inside the body's own corner, so the header's arc sits within
	   the border rather than crossing it. `max()` keeps it at zero when corners
	   are square, instead of going negative and being ignored. */
	border-radius: max(0px, calc(var(--node-radius) - 1px)) max(0px, calc(var(--node-radius) - 1px)) 0 0;
	background: color-mix(in srgb, var(--cat) calc(var(--head-tint) * 100%), var(--node-body));
	/* The header's lower edge, as a shadow so it takes no height: the rows'
	   positions are computed from the header's, and a border would move them. */
	box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--cat) calc(var(--head-tint) * 200%), var(--node-body));
	color: var(--fg);
	font-weight: 600;
	font-size: var(--text-md);
	cursor: grab;
	overflow: hidden;
}

/* The category, as a tab in the header's corner: the full height of the
   header, flush to the node's edge, with an 18px glyph. Big enough to read with
   a whole graph in view, which a 12px icon in a chip was not. */
.node .head .tab {
	align-self: stretch;
	flex: none;
	width: var(--tab-width);
	display: flex;
	align-items: center;
	justify-content: center;
	background: var(--cat);
	color: #fff;
}

.node .head .tab svg {
	display: block;
}

/* A node that holds code takes the Code panel's mark in its tab, in the code
   face and colours, on the code editor's own surface: `{ }`, `ƒx`, `<T>`. The
   colours are the `.code-mark-*` ones the panel's tabs use. */
.node .head .tab.code-tab {
	background: var(--bg-input);
	box-shadow: inset -1px 0 0 color-mix(in srgb, var(--cat) 35%, var(--node-border));
	font-family: "Cascadia Mono", Consolas, monospace;
	font-size: var(--text-md);
	font-weight: 700;
	white-space: nowrap;
}

.node .head .tab.code-tab.code-mark-block {
	color: var(--code-keyword);
}

.node .head .tab.code-tab.code-mark-expression {
	color: var(--code-function);
}

.node .head .tab.code-tab.code-mark-type {
	color: var(--code-type);
}

.node .head .tab.code-tab .code-mark-angle {
	color: var(--fg-faint);
	font-weight: 400;
}

/* A value has no tab, so a value that holds code -- Luau Expression -- takes
   its mark as other values take their glyph: inline before its name, with no
   surface or edge of its own. */
.node.node--pure .head .tab.code-tab {
	background: none;
	box-shadow: none;
	min-width: 15px;
}

.node .head .title {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.node .head .marker {
	font-size: var(--text-xs);
	opacity: 0.85;
}

.node .rows {
	position: relative;
	/* One output column for every row, as wide as the widest output: a pin
	   with its type chip is wider than the lane a row reserves, and the values
	   of that row then sat left of the ones below it. */
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	align-content: start;
}

.node .rows > .row {
	grid-column: 1 / -1;
	display: grid;
	grid-template-columns: subgrid;
}

.node .row {
	height: 24px;
	display: flex;
	align-items: center;
	position: relative;
}

.node .row .side {
	display: flex;
	align-items: center;
	gap: 5px;
	min-width: 0;
}

.node .row .side.left {
	flex: 1;
	padding-left: var(--pin-pad);
}

.node .row .side.right {
	margin-left: auto;
	padding-right: var(--pin-pad);
	justify-content: flex-end;
	/* One pin's worth, reserved whether or not this row has an output.
	 *
	 * Without it every row is measured independently, so the one row that
	 * happens to carry the node's output gets a narrower left side and its value
	 * sits further left than the rows below it. Reserving the column makes the
	 * input values line up down the node instead of stepping around the output
	 * pin. The lane is the whole footprint, padding included. */
	min-width: var(--pin-lane);
}

.node .pin {
	width: var(--pin-slot);
	height: var(--pin-slot);
	flex: none;
	position: relative;
	cursor: crosshair;
}

/* A pin sits *on* the node's edge rather than inside it.
 *
 * `left` on a relatively positioned element moves what is drawn and leaves the
 * layout alone, which is the whole trick: the row still reserves the slot, so
 * the labels and the value column do not move, and only the pin travels.
 *
 * A data pin is balanced halfway off the edge — its centre is exactly where a
 * wire attaches, which it never was before: the wire used to stop at the border
 * with the dot 14px inside it. An execution pin clears the edge altogether and
 * hangs beside the node, point first. */
.node .row .side .pin {
	/* Up by the border, for the same reason the sideways offsets carry it: a
	   row sits inside the border and a wire attaches outside it. */
	top: calc(-1 * var(--node-stroke));
	/* And the row gives back the space the pin no longer occupies, so the label
	   beside it starts at `--pin-lane` rather than a slot and a half in. */
	margin: 0 calc(var(--pin-lane) - var(--pin-pad) - var(--pin-slot));
}

.node .row .side.left .pin {
	left: calc(-1 * (var(--pin-pad) + var(--pin-slot) / 2 + var(--node-stroke)));
	margin-left: 0;
}

.node .row .side.right .pin {
	left: calc(var(--pin-pad) + var(--pin-slot) / 2 + var(--node-stroke));
	margin-right: 0;
}

.node .row .side.left .pin.exec {
	left: calc(-1 * (var(--pin-pad) + var(--exec-width) + var(--exec-gap) + var(--node-stroke)));
}

.node .row .side.right .pin.exec {
	left: calc(var(--pin-pad) + var(--pin-slot) + var(--exec-gap) + var(--node-stroke));
}

/* The flow in and the flow on, level with the middle of the header rather than
   on a row of their own, so a run of steps is joined header to header and
   reads straight across. `top` is the header's middle, set inline from the
   header's height; the box is a pin slot, centred on it. Absolutely placed in
   the node's padding box, which starts inside the border, so both offsets
   carry the border back -- the same arithmetic as a row's flow pins. */
.node .head-pin {
	position: absolute;
	display: flex;
	height: var(--pin-slot);
	margin-top: calc(-1 * (var(--pin-slot) / 2 + var(--node-stroke)));
}

.node .head-pin.in {
	left: calc(-1 * (var(--exec-width) + var(--exec-gap) + var(--node-stroke)));
}

.node .head-pin.out {
	right: calc(-1 * (var(--pin-slot) + var(--exec-gap) + var(--node-stroke)));
}

/* Asymmetric hit areas, on purpose.
 *
 * Starting a wire asks for aim: the grab area is the dot and a hair around it,
 * so the rest of the row belongs to the node and can be dragged. Landing a wire
 * asks for none: while one is in flight every pin widens, because by then you
 * know which pin you want and fighting for the last three pixels is nobody's
 * idea of a good time.
 *
 * The resting inset stops one pixel short of the next row: a 16px slot plus 3px
 * either side is 22px inside a 24px row, so no click is ever answered by two
 * pins at once. Widen the slot and this number has to come down with it. */
.node .pin::after {
	content: "";
	position: absolute;
	inset: -3px;
}

.canvas.canvas--wiring .node .pin::after {
	inset: -9px;
}

/* A data pin is a 10px circle centred in the slot — the size it has always
   been. The slot grew around it, not it.
 *
 * Filled with the surface behind it rather than left transparent, because the
 * circle now straddles the node's border: half of it is over the body and half
 * over the canvas, and a see-through middle would show the seam running through
 * the pin. One fill makes it read as a ring sitting on the edge. */
.node .pin .dot {
	position: absolute;
	inset: 3px;
	border-radius: 50%;
	border: 2px solid currentColor;
	background: var(--pin-well);
	box-shadow: 0 0 0 1px var(--pin-well);
}

.node .pin.connected .dot {
	background: currentColor;
}

/* An execution pin is an equilateral triangle pointing right, as tall as the
   slot, hung outside the node's border.
 *
 * It used to be a house-shaped arrow drawn inside the node, the shape Blueprints
 * uses. The triangle is the shape Bolt and Blender give a flow socket, and
 * putting it outside the border is what makes a run of steps read as a chain:
 * the pin belongs to the wire rather than to the box, and the eye follows it out
 * of one node and into the next instead of stopping at two walls.
 *
 * Empty versus wired is drawn the way node editors usually draw it — outline,
 * then solid.
 * That used to be a half-opacity fill, which reads as "disabled" rather than
 * "nothing here yet"; a hollow arrow says the same thing at full colour, so an
 * unwired exec pin stays as easy to find as a wired one.
 *
 * The hollow is a second triangle in the surface colour rather than a border,
 * because clip-path cuts straight through a border and leaves the point blunt
 * on the diagonal. */
.node .pin.exec .dot {
	position: absolute;
	inset: 0 auto 0 0;
	width: var(--exec-width);
	border: 0;
	border-radius: 0;
	background: currentColor;
	clip-path: polygon(0 0, 100% 50%, 0 100%);
}

.node .pin.exec .dot::after {
	content: "";
	position: absolute;
	/* The same triangle shrunk towards its centre — see `--exec-shrink`. The
	   right inset is twice the left because the centre is a third of the way in
	   from the base, which is what keeps the wall even all round. */
	inset:
		calc(var(--pin-slot) / 2 * var(--exec-shrink))
		calc(var(--exec-width) * 2 / 3 * var(--exec-shrink))
		calc(var(--pin-slot) / 2 * var(--exec-shrink))
		calc(var(--exec-width) / 3 * var(--exec-shrink));
	background: var(--pin-hollow);
	clip-path: polygon(0 0, 100% 50%, 0 100%);
}

.node .pin.exec.connected .dot::after {
	display: none;
}

/* A data pin's shape is its type's family -- see `typeFamily.ts`. A value
   keeps the circle above; the other four are drawn here, each in the same
   10px footprint, so a row's pins line up whatever they carry.

   An object -- an Instance, any class -- is a rounded square. */
.node .pin.fam-object .dot {
	border-radius: 3px;
}

/* A table is a diamond: a square turned, a little smaller so its points stay
   inside the slot. The ring of well turns with it. */
.node .pin.fam-table .dot {
	inset: 3.5px;
	border-radius: 2px;
	transform: rotate(45deg);
}

/* A function is a dot in a ring: something you call. Wired, the dot grows to
   nearly fill the ring, keeping a hair of well so it still reads as two parts. */
.node .pin.fam-function .dot {
	background: radial-gradient(closest-side, currentColor 0 1.5px, var(--pin-well) 2px);
}

.node .pin.fam-function.connected .dot {
	background: radial-gradient(closest-side, currentColor 0 1.8px, var(--pin-well) 2.3px 2.6px, currentColor 3px);
}

/* A signal is a hexagon: something to listen to. Cut out by clip-path, which
   cuts away a border and a box-shadow too, so the outline is a second, smaller
   hexagon of well inside it, and the hover and drag halos move to the pin. */
.node .pin.fam-signal .dot {
	inset: 2.5px 2px;
	border: 0;
	border-radius: 0;
	background: currentColor;
	box-shadow: none;
	clip-path: polygon(25% 0, 75% 0, 100% 50%, 75% 100%, 25% 100%, 0 50%);
}

.node .pin.fam-signal .dot::after {
	content: "";
	position: absolute;
	inset: 2px 2.3px;
	background: var(--pin-well);
	clip-path: polygon(25% 0, 75% 0, 100% 50%, 75% 100%, 25% 100%, 0 50%);
}

.node .pin.fam-signal.connected .dot::after {
	display: none;
}

.node .pin.fam-signal.compatible::before,
.node .pin.fam-signal.highlighted::before {
	content: "";
	position: absolute;
	inset: 2px;
	border-radius: 50%;
	box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 45%, transparent);
}

.node .pin.fam-signal.highlighted::before {
	box-shadow: 0 0 0 3px var(--accent);
}

/* The well's ring stays under it, or a legal target loses the outline every
   other pin has while a wire is in flight. */
.node .pin.compatible .dot {
	box-shadow: 0 0 0 1px var(--pin-well), 0 0 0 4px color-mix(in srgb, var(--ok) 45%, transparent);
}

.node .pin.incompatible {
	opacity: 0.3;
}

/* A pin's name is most of what a node says, so it is drawn nearly in the text
   colour rather than the muted grey of a secondary label. */
.node .pin-label {
	font-size: var(--text-sm);
	color: color-mix(in srgb, var(--fg) 88%, var(--node-body));
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* An output's type in words, where its name does not already say it: a short
   name in a chip of the type's colour, so it reads as a label for the pin
   rather than more of the pin's name. Pointing at it opens the type's card
   (`typeCard.ts`). It gives way before the name does. */
.node .type-chip {
	flex: 0 1 auto;
	min-width: 0;
	max-width: 96px;
	height: 16px;
	padding: 0 5px;
	border-radius: 4px;
	background: color-mix(in srgb, var(--pin) 16%, var(--node-body));
	color: color-mix(in srgb, var(--pin) 62%, var(--fg));
	font-size: var(--text-xs);
	font-weight: 600;
	line-height: 16px;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	cursor: help;
}

/* A knot's, a getter's or a pill's type, under it and centred on it: each is
   too small to hold a chip beside its one output. Outside the node's box, so
   it takes no part in the node's size, its hit area or its wires' geometry. */
.node .hanging-type {
	position: absolute;
	top: calc(100% + 4px);
	left: 50%;
	transform: translateX(-50%);
	max-width: 120px;
}

/* A member a script can read and not set: a lock before its type, as the menus have it. */
.node .type-chip.type-chip--read-only {
	display: inline-flex;
	align-items: center;
	gap: 3px;
}

.node .type-chip:hover {
	box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--pin) 70%, transparent);
}

/* The card a chip opens: the code editor's hover card, outside the editor, so
   it carries the frame CodeMirror would otherwise give it. Above every panel,
   as a menu is. */
.type-card {
	position: fixed;
	z-index: 1000;
	background: var(--bg-panel);
	border: 1px solid var(--border-strong);
	border-radius: var(--radius-sm);
	box-shadow: var(--shadow-popover);
	color: var(--fg);
}

/* A call's result cast where it is made: the Cast pill's `::` and the type, in
   the type's colour, beside the pin it changes. */
.node .cast-mark {
	flex: 0 1 auto;
	min-width: 0;
	max-width: 110px;
	margin: 0 2px 0 4px;
	padding: 0 5px;
	border: 1px solid color-mix(in srgb, var(--pin) 55%, transparent);
	border-radius: var(--radius-xs);
	background: color-mix(in srgb, var(--pin) 12%, transparent);
	color: var(--fg);
	font-family: var(--mono, ui-monospace, monospace);
	font-size: var(--text-xs);
	line-height: 16px;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* A component of a split struct pin. The rule is the tell: without something
   joining them, X, Y and Z read as three unrelated inputs rather than one
   Vector taken apart. */
.node .pin-label.part {
	position: relative;
	padding-left: 9px;
	color: var(--fg-faint);
}

.node .pin-label.part::before {
	content: "";
	position: absolute;
	left: 2px;
	top: 50%;
	width: 4px;
	height: 1px;
	background: var(--border-strong);
}

.node .side.right .pin-label.part {
	padding-left: 0;
	padding-right: 9px;
}

.node .side.right .pin-label.part::before {
	left: auto;
	right: 2px;
}

/* A constant the node declared, or one Roswaal folded back when a split pin was
   recombined. Shown flat and unboxed, because it is not a field: nothing here
   takes typing, and a box that cannot be typed in is a lie. */
.node .literal.constant {
	background: none;
	border: 1px solid transparent;
	color: var(--fg-faint);
	font-family: var(--mono, ui-monospace, monospace);
	font-size: var(--text-xs);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	/* Pushed to the right edge and given its natural width. The label beside it
	   is short and known ("Up"); the value is the part worth reading, and
	   "Vector3.z…" does not distinguish `zero` from anything else. No padding,
	   because this is text rather than a field. */
	flex: none;
	margin-left: auto;
	padding: 0;
	max-width: 140px;
	text-align: right;
	cursor: default;
}

/* Every inline value sits in one column at the right of the input side, whether
   it is a field, a dropdown, a code button or a constant. Mixing left-aligned
   fields with right-aligned constants read as two unrelated things on the same
   node; one column reads as a value column. */
.node .row .side.left > .literal,
.node .row .side.left > input[type="checkbox"] {
	margin-left: auto;
}

/* Not on a pill, where the row is the whole node and the right of it is the
   symbol and the result pin. Pushed there, a field sat on top of both — and the
   pill's width already reserves a column for it beside the pins, which is where
   `operatorLayout` has always put it. */
.node.node--operator .row .side.left > .literal,
.node.node--operator .row .side.left > input[type="checkbox"] {
	margin-left: 0;
}

/* An optional pin nobody has set. Reads as the word rather than as a value,
   because the argument is genuinely not passed and showing a number here would
   claim otherwise. Dashed, so it looks like a placeholder rather than a field
   somebody already filled in. */
.node .literal.unset {
	background: none;
	border: 1px dashed var(--border-strong);
	color: var(--fg-faint);
	font-size: var(--text-xs);
	font-style: italic;
	padding: 0 5px;
	cursor: pointer;
}

.node .literal.unset:hover {
	border-color: var(--accent);
	color: var(--fg-muted);
}

/* A set optional pin: its editor, and the way back to unset. */
.node .optional-value {
	display: flex;
	align-items: center;
	gap: 2px;
	min-width: 0;
}

.node .optional-value .clear {
	background: none;
	border: 0;
	color: var(--fg-faint);
	cursor: pointer;
	font-size: var(--text-md);
	line-height: 1;
	padding: 0 1px;
}

.node .optional-value .clear:hover {
	color: var(--danger);
}

.node .literal {
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	padding: 1px 4px;
	font-size: var(--text-sm);
	width: 70px;
	min-width: 0;
}

.node .literal.wide {
	width: 92px;
}

/* Set Property's Property, once the wired value has a class: the field, and
   the lock it shows when the name in it is read-only. */
.node .property-field {
	display: flex;
	align-items: center;
	gap: 3px;
}

.node .property-field:focus-within {
	outline: 1px solid var(--accent);
}

.node .property-field input {
	flex: 1 1 auto;
	min-width: 0;
	border: 0;
	padding: 0;
	background: none;
	color: inherit;
	font: inherit;
	outline: none;
}

.node .property-field.property-field--read-only {
	border-color: var(--warning);
}

.node .property-field .property-field-lock {
	color: var(--warning);
}

/* What the field offers, hung under it outside the canvas. */
.property-list {
	position: fixed;
	box-sizing: border-box;
	min-width: 176px;
	max-width: 260px;
	max-height: 260px;
	overflow-y: auto;
	padding: 3px;
	background: var(--bg-panel);
	border: 1px solid var(--border-strong);
	border-radius: var(--radius-sm);
	box-shadow: var(--shadow-popover);
	font-size: var(--text-sm);
	color: var(--fg);
}

.property-list .property-list-class {
	padding: 3px 6px 2px;
	font-size: var(--text-xs);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--fg-faint);
}

.property-list .property-list-item {
	display: flex;
	align-items: center;
	gap: 6px;
	padding: 4px 6px;
	border-radius: 4px;
	cursor: pointer;
}

.property-list .property-list-item.on {
	background: var(--bg-active);
}

.property-list .property-list-item .swatch {
	flex: none;
	width: 7px;
	height: 7px;
	border-radius: 2px;
}

.property-list .property-list-item .name {
	flex: 1 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.property-list .property-list-item .type {
	flex: none;
	font-size: var(--text-xs);
	color: var(--fg-faint);
}

.property-list .property-list-note {
	display: flex;
	align-items: flex-start;
	gap: 5px;
	margin: 3px 2px 0;
	padding: 5px 4px 3px;
	border-top: 1px solid var(--border);
	font-size: var(--text-xs);
	line-height: 1.4;
	color: var(--fg-muted);
}

.property-list .property-list-note .icon {
	margin-top: 2px;
}

.node .literal:focus {
	outline: 1px solid var(--accent);
}

/* A pill's corner is rounded away from where a square node's corner is, so a
   badge hung at -7px sits off the shape entirely rather than on its edge. */
.node.node--operator .badge-count,
.node.node--capsule .badge-count {
	top: -6px;
	right: -1px;
}

.node .badge-count {
	position: absolute;
	top: -7px;
	right: -7px;
	background: var(--danger);
	color: #fff;
	border-radius: var(--radius-md);
	font-size: var(--text-xs);
	padding: 0 5px;
	line-height: 15px;
}

/* Something to look at rather than something that is wrong: a Lune call whose
   module nothing requires will not compile, but the node itself is fine and the
   fix is one button away in the Inspector. Yellow and a single mark, in the
   corner the error count uses -- one place to glance at, and never both at
   once, because an error is the more urgent of the two and says how many. */
.node .badge-count.warn {
	background: var(--warning);
	color: #1c1f24;
	font-weight: 700;
	/* A round pip rather than the error's oval: one character wants a circle,
	   and the two should not be mistaken for each other at a glance. */
	min-width: 15px;
	padding: 0;
	text-align: center;
}

/* A problem written out under its node -- see ProblemNotes.tsx. In the world,
   so it pans and zooms with the node, at `LAYER.problem`, above every node. */
.problem-note {
	position: absolute;
	width: max-content;
	padding: 5px 9px;
	border-radius: var(--radius-md);
	border: 1px solid color-mix(in srgb, var(--danger) 40%, var(--bg-panel));
	background: color-mix(in srgb, var(--danger) 10%, var(--bg-panel));
	color: color-mix(in srgb, var(--danger) 55%, var(--fg));
	box-shadow: var(--shadow-raised);
	font-size: var(--text-md);
	line-height: 1.4;
	overflow-wrap: anywhere;
	pointer-events: none;
}

.problem-note.warn {
	border-color: color-mix(in srgb, var(--warning) 50%, var(--bg-panel));
	background: color-mix(in srgb, var(--warning) 12%, var(--bg-panel));
	color: color-mix(in srgb, var(--warning) 40%, var(--fg));
}

.problem-note .more {
	opacity: 0.75;
}

/* The pin a problem names, so the note and the node point at the same row. */
.node .pin-label.fault {
	color: var(--danger);
	font-weight: 600;
}

/* ---------------------------------------------------------------- comment */

.comment {
	position: absolute;
	border-radius: var(--radius-sm);
	border: 1px solid color-mix(in srgb, var(--comment-color) 65%, transparent);
	background: color-mix(in srgb, var(--comment-color) calc(var(--comment-fill) * 100%), transparent);
	user-select: none;
}

.comment.comment--selected {
	border-color: var(--select);
	box-shadow: 0 0 0 2px color-mix(in srgb, var(--select) 45%, transparent);
}

/* The header holds as much as it is given: a long line wraps and several lines
   each take one, so a comment can say what it is for rather than name it. */
.comment .bar {
	min-height: 26px;
	display: flex;
	align-items: flex-start;
	padding: 5px 8px;
	font-weight: 600;
	font-size: var(--text-md);
	line-height: 16px;
	color: #fff;
	background: color-mix(in srgb, var(--comment-color) 82%, transparent);
	border-radius: var(--radius-sm) var(--radius-sm) 0 0;
	cursor: grab;
	text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4);
	white-space: pre-wrap;
	overflow-wrap: anywhere;
}

.comment .bar textarea {
	background: rgba(0, 0, 0, 0.28);
	border: 0;
	border-radius: var(--radius-xs);
	color: #fff;
	font: inherit;
	line-height: inherit;
	width: 100%;
	padding: 1px 4px;
	/* Sized by its content from the keystroke, so the header grows as you type
	   rather than scrolling inside a box the size of one line. */
	resize: none;
	overflow: hidden;
}

.comment .bar textarea:focus {
	outline: 1px solid #fff;
}

.comment .resize {
	position: absolute;
	width: 16px;
	height: 16px;
	cursor: nwse-resize;
	/* Above the header bar, which the top-left corner sits on top of and which
	   would otherwise take the drag as a move. */
	z-index: 36;
}

.comment .resize::after {
	content: "";
	position: absolute;
	width: 8px;
	height: 8px;
}

.comment .resize.se {
	right: 0;
	bottom: 0;
}

.comment .resize.se::after {
	right: 3px;
	bottom: 3px;
	border-right: 2px solid color-mix(in srgb, var(--comment-color) 90%, transparent);
	border-bottom: 2px solid color-mix(in srgb, var(--comment-color) 90%, transparent);
}

.comment .resize.nw {
	left: 0;
	top: 0;
}

.comment .resize.nw::after {
	left: 3px;
	top: 3px;
	border-left: 2px solid color-mix(in srgb, var(--comment-color) 90%, transparent);
	border-top: 2px solid color-mix(in srgb, var(--comment-color) 90%, transparent);
}

/* ------------------------------------------------------------------- menu */

.menu {
	position: fixed;
	background: var(--bg-panel);
	border: 1px solid var(--border-strong);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-popover);
	overflow: hidden;
	display: flex;
	flex-direction: column;
	max-height: 60vh;
	width: 280px;
}

/* The palette is wider than a menu of commands: its entries are names, and a
   member or a service method is a long one, ‘Workspace:GetPartBoundsInRadius’,
   whose last word is the one that tells it from the next. One width while
   you type, so the list does not jump as it narrows. */
.menu.menu--palette {
	width: 360px;
	max-width: calc(100vw - 16px);
}

/* Shown only when the palette was opened by dragging a wire. Above the search
   box, because it is the reason the list is short. */
.menu-from {
	display: flex;
	align-items: center;
	gap: 6px;
	padding: 7px 10px;
	border-bottom: 1px solid var(--border);
	background: var(--bg-hover);
	font-size: var(--text-sm);
	color: var(--fg-muted);
}

.menu-from .dot {
	width: 8px;
	height: 8px;
	border-radius: 50%;
	flex: none;
}

.menu input.search {
	border: 0;
	border-bottom: 1px solid var(--border);
	background: var(--bg-input);
	padding: 8px 10px;
	outline: none;
}

.menu .items {
	overflow: auto;
	padding: 4px;
}

.menu .group {
	font-size: var(--text-xs);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: var(--fg-faint);
	padding: 6px 8px 3px;
}

/* A datatype inside Engine Types. Indented and not shouted, so the eye reads
   the category first and the type second — the other way round would make nine
   datatype headings compete with every category heading in the list. */
.menu .subgroup {
	font-size: var(--text-sm);
	font-weight: 600;
	color: var(--fg-muted);
	padding: 5px 8px 2px 16px;
}

.menu .item {
	display: flex;
	align-items: baseline;
	gap: 7px;
	padding: 4px 8px;
	border-radius: var(--radius-sm);
	cursor: pointer;
}

.menu .item:hover,
.menu .item.active {
	background: var(--bg-active);
}

/* An item describing something this copy of Roswaal cannot do -- showing a file
   in a file manager, from a browser tab. Shown rather than removed, because it
   names a real thing the tool does and somebody trying the hosted editor is
   exactly who should learn that; its title says where it does work. Compound
   with `.item` so the rule cannot reach anything else calling itself disabled. */
.menu .item.item-unavailable {
	cursor: default;
	opacity: 0.45;
}

.menu .item.item-unavailable:hover {
	background: none;
}

/* The name gives way, on one line, to what trails it: a member of a long
   name, ‘mantlet.AssemblyAngularVelocity’, wrapped its badge onto two lines
   and pushed the row past the menu's edge. */
.menu .item .name {
	flex: 0 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.menu .item .swatch {
	width: 8px;
	height: 8px;
	border-radius: var(--radius-xs);
	flex: none;
}

.menu .item .hint {
	margin-left: auto;
	font-size: var(--text-xs);
	color: var(--fg-faint);
	flex: none;
	white-space: nowrap;
	/* Centred, like everything else that trails a row. The row aligns on the
	   baseline, which is right for the swatch and the title and wrong for a
	   trailing chip -- `pure` and a bordered badge beside it sat two pixels
	   apart because one was on the baseline and the other was not. */
	align-self: center;
}

/* Only the first hint pushes. `margin-left: auto` on every one of them splits
   the free space between them, so on a row carrying both `pure` and a runtime
   badge the `pure` floated to a different x on every row -- 843 here, 827 the
   next row down -- and the column stopped being a column. The rest follow at
   the row's own gap, so the group right-aligns as one thing. */
.menu .item .hint ~ .hint {
	margin-left: 0;
}

.node-picker-hit .hint ~ .hint {
	margin-left: 0;
}

.menu .empty {
	padding: 12px;
	color: var(--fg-faint);
	text-align: center;
}

/* ------------------------------------------------------------- base menu */

/* Every context menu and dropdown drawn by `Menu.tsx`: one look, one divider.
   As wide as its longest entry, and never wider than the window. The node
   palette keeps the fixed width above, for a list that changes as you type. */
.menu.menu--list {
	width: max-content;
	min-width: 180px;
	max-width: min(340px, calc(100vw - 16px));
	max-height: calc(100vh - 16px);
}

/* Focus sits on the menu itself until an entry takes it; the highlight is
   the entry's, as in a native menu, so the box needs no ring of its own. */
.menu:focus {
	outline: none;
}

.menu-body {
	overflow: auto;
	padding: 4px;
	display: flex;
	flex-direction: column;
}

.menu-section {
	display: flex;
	flex-direction: column;
}

.menu-sep {
	flex: none;
	height: 1px;
	margin: 4px 6px;
	background: var(--border);
}

.menu-label {
	padding: 6px 8px 3px;
	font-size: var(--text-xs);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: var(--fg-faint);
}

.menu-item {
	display: flex;
	align-items: center;
	gap: 8px;
	width: 100%;
	padding: 5px 8px;
	border: 0;
	border-radius: var(--radius-sm);
	background: none;
	color: inherit;
	font: inherit;
	text-align: left;
	text-decoration: none;
	white-space: nowrap;
	cursor: pointer;
}

/* The pointer moves focus as it moves (`Menu.tsx`), so hover and focus are
   one highlight, and the keyboard's is the same as the mouse's. */
.menu-item:hover,
.menu-item:focus {
	background: var(--bg-active);
	outline: none;
}

/* Shown rather than removed: it names something Roswaal does, somewhere
   else. Its tooltip says where. */
.menu-item[aria-disabled="true"] {
	opacity: 0.45;
	cursor: default;
}

.menu-item[aria-disabled="true"]:hover,
.menu-item[aria-disabled="true"]:focus {
	background: none;
}

.menu-icon {
	flex: none;
	color: var(--fg-muted);
}

.menu-icon-space {
	flex: none;
	width: 15px;
}

/* Centred in the icon's 15px, so a swatch's words line up with an icon's. */
.menu-swatch {
	flex: none;
	width: 8px;
	height: 8px;
	margin-inline: 3.5px;
	border-radius: var(--radius-xs);
}

.menu-text {
	flex: 1 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
}

.menu-hint {
	margin-left: auto;
	padding-left: 16px;
	font-size: var(--text-xs);
	color: var(--fg-faint);
}

.menu-item--danger,
.menu-item--danger .menu-icon {
	color: var(--danger);
}

.menu-item--checked,
.menu-item--checked .menu-icon {
	color: var(--accent);
}

.menu-item--current .menu-text {
	font-weight: 600;
}

/* A dot at the end of the row: a document with changes not yet saved. */
.menu-dot {
	display: block;
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: var(--fg-muted);
}

.menu-empty {
	max-width: 260px;
	padding: 10px 12px;
	font-size: var(--text-sm);
	line-height: 1.5;
	color: var(--fg-faint);
	white-space: normal;
}

/* A field whose control has something beside it -- the variable picker and its
   "New..." button. The select takes the room and the button takes what it
   needs, so a long variable name does not push the button off the panel. */
.inspector .field-row {
	display: flex;
	align-items: center;
	gap: 6px;
	min-width: 0;
}

/* A picker beside a text field, as Key Value Pair's Value is: one height, the
   picker as wide as what it says and the field taking the rest to type in.
   Centred, the picker sat six pixels shorter than the field beside it. */
.inspector .field-row:has(> .tb.type-picker) {
	align-items: stretch;
}

.inspector .field-row > .tb.type-picker {
	flex: 0 0 auto;
	width: auto;
	min-width: 104px;
}

.inspector .field-row > input.tb {
	flex: 1 1 auto;
	min-width: 0;
}

/* An optional setting not yet set, as an optional pin's "default" is drawn:
   dashed, quiet, and one click from being set. */
.inspector .tb.unset-choice {
	width: 100%;
	background: none;
	border: 1px dashed var(--border-strong);
	color: var(--fg-faint);
	font-style: italic;
	text-align: left;
	cursor: pointer;
}

.inspector .tb.unset-choice:hover {
	border-color: var(--accent);
	color: var(--fg-muted);
}

/* A set optional setting, and its way back to unset. */
.inspector .cast-row {
	display: flex;
	align-items: center;
	gap: 4px;
	min-width: 0;
}

.inspector .cast-row > .tb.type-picker {
	flex: 1 1 auto;
	min-width: 0;
}

.inspector .cast-row > .list-remove {
	flex: 0 0 auto;
	width: 24px;
	height: 24px;
	padding: 0;
	display: inline-grid;
	place-items: center;
	background: transparent;
	border-color: transparent;
	color: var(--fg-faint);
}

.inspector .cast-row > .list-remove:hover {
	background: var(--bg-hover);
	color: var(--danger);
}

/* The way out of a shortened description. Underlined rather than only coloured,
   because "See docs page" sitting at the end of a sentence has to read as a
   link on the first glance and not on the second. */
.inspector .docs-link {
	color: var(--accent);
	text-decoration: underline;
	white-space: nowrap;
	cursor: pointer;
}

/* A checkbox and the words that explain it, on one line. They were two: the
   control sat under its own heading and its text under that again, because a
   <label> nested inside the field's own <label> is not a row. */
.inspector .check-row {
	display: flex;
	align-items: center;
	gap: 6px;
	cursor: pointer;
	/* Both needed. Without the first, a long explanation refuses to wrap and
	   widens the whole panel until it scrolls sideways; without the second, the
	   text has nowhere to go when the dock is dragged narrow. */
	min-width: 0;
	flex-wrap: wrap;
}

/* `.inspector .field input` sets width:100% for the text fields, and a checkbox
   inherited it: 265px wide, which renders the tick centred in a box the width of
   the panel and pushes its own label onto the next line. That was the whole of
   the misalignment -- the row was a row all along. */
.inspector .check-row input[type="checkbox"] {
	margin: 0;
	flex: none;
	width: auto;
	cursor: pointer;
}

.inspector .check-row span {
	min-width: 0;
	overflow-wrap: anywhere;
}

/* The Refactor panel's changes: a row each, its icon, its name over what it
   does, and a shortcut where it has one. */
.refactor .refactor-act {
	display: flex;
	align-items: center;
	gap: 10px;
	width: 100%;
	min-width: 0;
	padding: 8px 10px;
	text-align: left;
	color: var(--fg);
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	font: inherit;
	cursor: pointer;
}

.refactor .refactor-act:hover:not(:disabled) {
	border-color: var(--accent);
}

.refactor .refactor-act:disabled {
	cursor: default;
	opacity: 0.55;
}

.refactor .refactor-text {
	display: flex;
	flex-direction: column;
	gap: 2px;
	flex: 1;
	min-width: 0;
}

.refactor .refactor-label {
	font-weight: 600;
}

.refactor .refactor-hint {
	font-size: var(--text-xs);
	color: var(--fg-faint);
	overflow-wrap: anywhere;
}

.refactor .refactor-act kbd {
	flex: none;
	font-family: inherit;
	font-size: var(--text-xs);
	color: var(--fg-faint);
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	padding: 0 4px;
}

/* The list rows are three controls wide and the dock can be dragged to 140px.
   Left alone they push the panel wider than it is and it scrolls sideways. */
.inspector .list-row {
	min-width: 0;
}

.inspector .list-row input {
	min-width: 0;
}

/* A Luau type written out, shown as the code it is and opened in the code
   editor on a click. A button that looks like a field: it is where the text
   is, and pressing it is how you change it. */
.inspector button.type-definition {
	display: flex;
	flex-direction: column;
	width: 100%;
	min-height: 3.2em;
	max-height: 14em;
	overflow: auto;
	padding: 5px 8px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--bg-input);
	color: var(--fg);
	font-family: var(--mono);
	font-size: var(--text-sm);
	line-height: 1.5;
	text-align: left;
	white-space: pre;
	cursor: text;
}

.inspector button.type-definition:hover { border-color: var(--border-strong); }
.inspector button.type-definition.bad { border-color: var(--danger); }
.inspector button.type-definition .placeholder { color: var(--fg-faint); }

.inspector .type-definition-problem {
	margin: 4px 0 0;
	font-size: var(--text-sm);
	color: var(--danger);
}

/* The two halves of a project: what you author, and what Roswaal writes. A
   section heading rather than a folder, because it is not one -- there is no
   such directory, and making it look like a row would invite you to
   right-click it and wonder why nothing is offered. The two sit 6px from the
   top, 6px apart and 6px above the end when both are folded. */
.tree-section {
	padding: 0 2px;
}

.tree-section > .pane-head {
	margin-top: 0;
}

.tree-section:first-child {
	padding-top: 6px;
}

.tree-section + .tree-row,
.tree-row + .tree-section,
.tree-section + .tree-section {
	margin-top: 6px;
}

.tree-section .empty {
	font-size: var(--text-xs);
	color: var(--fg-faint);
}

/* The folder a new graph would go into. Quieter than a selection -- it is
   context, not a choice you made, and it has to sit under the selected and
   open-document styles without fighting them. */
.tree-row.target-dir {
	background: color-mix(in srgb, var(--accent) 7%, transparent);
}

/* What is left when a render throws. Deliberately plain: it has to work when
   whatever it is reporting has already gone wrong, so it uses nothing but the
   colour tokens. */
.crash {
	max-width: 780px;
	margin: 0 auto;
	padding: 48px 24px;
	display: flex;
	flex-direction: column;
	gap: 14px;
}

.crash h1 {
	font-size: var(--text-2xl);
	margin: 0;
}

.crash p {
	margin: 0;
	color: var(--fg-muted);
}

.crash-detail {
	background: var(--bg-panel);
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	padding: 12px;
	max-height: 40vh;
	overflow: auto;
	font-size: var(--text-sm);
	line-height: 1.5;
	white-space: pre-wrap;
	word-break: break-word;
	margin: 0;
}

.crash-actions {
	display: flex;
	gap: 8px;
}

/* The pin menu is the node palette's small sibling: no search, a handful of
   entries, and a header naming the pin so there is never a doubt about which
   one was hit -- pin hit areas overlap their labels deliberately. */
.menu.pin-menu {
	width: 220px;
	max-height: none;
}

.menu.pin-menu .pin-head {
	display: flex;
	align-items: center;
	gap: 7px;
	padding: 7px 10px;
	border-bottom: 1px solid var(--border);
	background: var(--bg-input);
}

.menu.pin-menu .pin-head .swatch {
	width: 9px;
	height: 9px;
	border-radius: 50%;
	flex: none;
}

/* An execution pin is drawn as its triangle here too, so the header matches the
   thing that was clicked rather than describing it. */
.menu.pin-menu .pin-head .swatch.exec {
	border-radius: 0;
	width: 8px;
	clip-path: polygon(0 0, 100% 50%, 0 100%);
}

.menu.pin-menu .pin-head .name {
	font-weight: 600;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.menu.pin-menu .pin-head .type {
	margin-left: auto;
	font-size: var(--text-xs);
	color: var(--fg-faint);
	white-space: nowrap;
}

/* -------------------------------------------------------------- diagnostics */

.status {
	border-top: 1px solid var(--border);
	background: var(--bg-panel);
	max-height: 260px;
	display: flex;
	flex-direction: column;
}

/* The bar: the two scopes, each with its health, then one status, Check and
   the fold. Folding is the button's, not the bar's: a tap on a scope used to
   fold the card on its way through. */
.status .bar {
	display: flex;
	align-items: center;
	gap: 8px;
	/* Room on the right for the undock button, as the headings have. */
	padding: 4px 34px 4px 6px;
	min-height: 32px;
	font-size: var(--text-md);
}

/* One line however narrow the card: the scopes keep their words, and the
   status gives way first. */
.status .bar > * {
	flex: none;
	white-space: nowrap;
}

.status .bar > .spacer {
	flex: 1 1 0;
	min-width: 4px;
}

.status .scopes {
	display: inline-flex;
	gap: 2px;
	padding: 2px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--bg-input);
}

.status .scope-tab {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	min-height: 22px;
	padding: 1px 5px 1px 9px;
	border: 0;
	border-radius: var(--radius-xs);
	background: none;
	color: var(--fg-muted);
	font: inherit;
	cursor: pointer;
}

.status .scope-tab[aria-selected="true"] {
	background: color-mix(in srgb, var(--accent) 20%, transparent);
	color: var(--fg);
	font-weight: 600;
}

.status .scope-tab .short {
	display: none;
}

/* A scope's count in the colour of its worst: red, amber, or a tick. */
.status .badge {
	display: inline-grid;
	place-items: center;
	min-width: 18px;
	height: 16px;
	padding: 0 5px;
	border-radius: 999px;
	box-sizing: border-box;
	font: 700 10px/1 system-ui, sans-serif;
	font-variant-numeric: tabular-nums;
	color: #fff;
}

.status .badge.error {
	background: var(--danger);
}

.status .badge.warning {
	background: var(--warning);
}

.status .badge.clean {
	background: var(--ok);
}

.status .badge.unknown {
	background: var(--bg-active);
	color: var(--fg-muted);
}

.status .bar .status-text {
	flex: 0 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	color: var(--fg-muted);
	font-size: var(--text-sm);
}

.status .status-check {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	min-height: 22px;
	padding: 0 8px;
	font-size: var(--text-sm);
}

.status .status-check.spin svg {
	animation: status-spin 0.8s linear infinite;
}

@keyframes status-spin {
	to {
		transform: rotate(360deg);
	}
}

.status .status-fold-button {
	color: var(--fg-faint);
}

.status .status-fold {
	transform: rotate(-90deg);
	transition: transform 120ms;
}

.status .status-fold-button[aria-expanded="true"] .status-fold {
	transform: none;
}

/* The split, under the bar: what the badges counted, in words. */
.status .summary {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0 4px;
	padding: 4px 10px;
	border-top: 1px solid var(--border);
	color: var(--fg-muted);
	font-size: var(--text-sm);
}

.status .summary b {
	color: var(--fg);
	font-weight: 600;
}

.status .summary .err {
	color: var(--danger);
	font-weight: 600;
}

.status .summary .warn {
	color: var(--warning);
	font-weight: 600;
}

/* Said in the bar, except on a phone. */
.status .summary-status {
	display: none;
}

/* In a side dock the card is as tall as its list (see \`DockCards\`), and a
   problem there takes two or three lines: room for about five before the
   list scrolls. */
.dock.left .status,
.dock.right .status {
	max-height: 340px;
}

/* A severity mark: a tick, or an exclamation in a red or amber disc. */
.sev-mark,
.status .entry::before {
	flex: none;
	width: 16px;
	height: 16px;
	border-radius: 50%;
	display: inline-grid;
	place-items: center;
	font: 700 10px/1 system-ui, sans-serif;
	color: #fff;
	background: var(--ok);
}

.status .entry::before {
	content: "\2713";
	margin-top: 1px;
}

.status .entry.error::before {
	content: "!";
	background: var(--danger);
}

.status .entry.warning::before {
	content: "!";
	background: var(--warning);
}

.status .list {
	overflow: auto;
	border-top: 1px solid var(--border);
	padding: 4px;
}

.status .entry {
	display: flex;
	align-items: flex-start;
	gap: 8px;
	padding: 4px 6px;
	border-radius: var(--radius-xs);
	font-size: var(--text-sm);
	line-height: 1.45;
	cursor: pointer;
}

.status .entry:hover {
	background: var(--bg-hover);
}

/* What happened, as the sentence's first word rather than a coloured column. */
.status .entry .sev {
	flex: none;
	font-weight: 600;
	color: var(--fg);
}

.status .entry .sev::first-letter {
	text-transform: uppercase;
}

.status .entry .where {
	margin-left: auto;
	padding: 0 7px;
	border: 1px solid var(--border);
	border-radius: 999px;
	background: var(--bg-input);
	color: var(--fg-muted);
	font: var(--text-xs) / 1.5 "Cascadia Mono", Consolas, monospace;
	white-space: nowrap;
}

.status .entry .msg {
	min-width: 0;
}

.status .entry .where.link {
	cursor: pointer;
	text-decoration: underline;
}

/* The Project scope: a heading per graph, its problems under it. */
.status .group + .group {
	margin-top: 2px;
}

.status .group-head {
	display: flex;
	align-items: center;
	gap: 6px;
	padding: 4px 6px;
	border-radius: var(--radius-xs);
	font-size: var(--text-sm);
	cursor: pointer;
}

.status .group-head:hover {
	background: var(--bg-hover);
}

.status .group-chev {
	flex: none;
	color: var(--fg-faint);
	transition: transform 120ms;
}

.status .group[data-open="false"] .group-chev {
	transform: rotate(-90deg);
}

.status .group[data-open="false"] .group-body {
	display: none;
}

.status .group-glyph {
	flex: none;
	color: var(--accent);
}

.status .group-head .name {
	font-weight: 650;
}

.status .group-head .path {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	color: var(--fg-faint);
	font: var(--text-xs) / 1.5 "Cascadia Mono", Consolas, monospace;
}

.status .group-head .open-here {
	flex: none;
	color: var(--accent);
	font-size: var(--text-xs);
}

.status .group-head .chips {
	margin-left: auto;
	flex: none;
}

.status .group-body {
	padding-left: 16px;
}

.status .stale-note {
	margin: 0 6px 2px 22px;
	color: var(--warning);
	font-size: var(--text-xs);
}

.status .clean-line {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 4px 6px;
	color: var(--fg-muted);
	font-size: var(--text-sm);
}

/* On a touch screen every target a finger can land on: the bar 44 high, its
   controls 32. The pictures of a tablet and a phone in the docs are drawn at
   the same sizes. */
.workspace.compact .status .bar,
.docs-layout-screen:is(.dev-tablet, .dev-phone) .status .bar {
	min-height: 44px;
	gap: 6px;
	font-size: var(--text-lg);
}

.workspace.compact .status .scope-tab,
.docs-layout-screen:is(.dev-tablet, .dev-phone) .status .scope-tab {
	min-height: 32px;
	padding: 4px 8px 4px 12px;
}

.workspace.compact .status .badge,
.docs-layout-screen:is(.dev-tablet, .dev-phone) .status .badge {
	min-width: 20px;
	height: 18px;
	font-size: 11px;
}

.workspace.compact .status :is(.status-check, .status-fold-button),
.docs-layout-screen:is(.dev-tablet, .dev-phone) .status :is(.status-check, .status-fold-button) {
	min-width: var(--hit);
	min-height: 32px;
	justify-content: center;
}

.workspace.compact .status :is(.entry, .group-head),
.docs-layout-screen:is(.dev-tablet, .dev-phone) .status :is(.entry, .group-head) {
	padding: 7px 8px;
	font-size: var(--text-md);
}

/* A phone: the scopes fill the bar under their short names, Check is its
   glyph, and the status moves under the bar. */
@media (max-width: 699px) {
	.workspace.compact .status .scopes {
		flex: 1 1 auto;
	}

	.workspace.compact .status .scope-tab {
		flex: 1 1 0;
		justify-content: center;
	}

	.workspace.compact .status .scope-tab .long,
	.workspace.compact .status .bar :is(.status-text, .spacer) {
		display: none;
	}

	.workspace.compact .status .scope-tab .short,
	.workspace.compact .status .summary-status {
		display: inline;
	}

	.workspace.compact .status .status-check .tb-label {
		position: absolute;
		width: 1px;
		height: 1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}
}

.docs-layout-screen.dev-phone .status .scopes {
	flex: 1 1 auto;
}

.docs-layout-screen.dev-phone .status .scope-tab {
	flex: 1 1 0;
	justify-content: center;
}

.docs-layout-screen.dev-phone .status .scope-tab .long,
.docs-layout-screen.dev-phone .status .bar :is(.status-text, .spacer) {
	display: none;
}

.docs-layout-screen.dev-phone .status .scope-tab .short,
.docs-layout-screen.dev-phone .status .summary-status {
	display: inline;
}

@media (prefers-reduced-motion: reduce) {
	.status :is(.status-fold, .group-chev) {
		transition: none;
	}

	.status .status-check.spin svg {
		animation: none;
	}
}

/* ------------------------------------------------------------------ misc */

.placeholder {
	display: grid;
	place-content: center;
	gap: 10px;
	text-align: center;
	color: var(--fg-muted);
	padding: 40px;
}

.placeholder h1 {
	font-size: var(--text-2xl);
	margin: 0;
	color: var(--fg);
}

.placeholder code {
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	padding: 1px 5px;
}

/* ------------------------------------------------------------------ start */

/* The start page: a window on the canvas's grid, its chrome floating along the
   top as every other window's does, and a column of cards in the middle. The
   cards are solid, tinted towards the accent: the grid behind text made it
   harder to read. */
.start-page {
	--start-card: color-mix(in srgb, var(--accent) 5%, var(--bg-panel));
	--start-card-hover: color-mix(in srgb, var(--accent) 10%, var(--bg-panel));
	position: relative;
	height: 100%;
	overflow: hidden;
	background-color: var(--bg-canvas);
}

.start-page .window-mark {
	cursor: default;
}

.start-scroll {
	position: absolute;
	inset: 0;
	overflow: auto;
}

.start-column {
	width: min(820px, calc(100% - 32px));
	margin: 0 auto;
	padding: calc(var(--chrome-top) + 24px) 0 60px;
}

.start-hero {
	display: flex;
	align-items: center;
	gap: 16px;
	margin-bottom: 18px;
}

.start-hero .logo-mark {
	flex: none;
}

.start-hero h1 {
	margin: 0;
	font-size: 26px;
	line-height: 1.1;
}

.start-hero p {
	margin: 4px 0 0;
	color: var(--fg-muted);
	font-size: var(--text-md);
	text-wrap: balance;
}

.start-card {
	padding: 14px;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	background: var(--start-card);
	box-shadow: var(--shadow-raised);
}

.start-label {
	display: block;
	margin: 0 0 8px;
	font-size: var(--text-sm);
	color: var(--fg-muted);
}

.start-row {
	display: flex;
	gap: 8px;
	align-items: stretch;
}

.start-row > .tb {
	height: 36px;
	padding: 0 14px;
}

.start-field {
	flex: 1;
	min-width: 0;
	display: flex;
	align-items: center;
	gap: 8px;
	height: 36px;
	padding: 0 10px;
	box-sizing: border-box;
	border: 1px solid var(--border-strong);
	border-radius: var(--radius-sm);
	background: var(--bg-input);
	color: var(--fg-faint);
}

.start-field:focus-within {
	border-color: var(--accent);
	box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}

.start-field input {
	flex: 1;
	min-width: 0;
	height: 100%;
	border: 0;
	outline: 0;
	padding: 0;
	background: none;
	color: var(--fg);
	font-family: var(--mono, ui-monospace, monospace);
	font-size: var(--text-md);
}

.start-verdict {
	display: flex;
	align-items: center;
	gap: 8px;
	margin: 10px 2px 0;
	min-height: 20px;
	font-size: var(--text-md);
	color: var(--fg-muted);
}

.start-dot {
	flex: none;
	width: 8px;
	height: 8px;
	border-radius: 50%;
	background: var(--fg-faint);
}

.start-verdict-ok .start-dot {
	background: var(--ok);
}

.start-verdict-new .start-dot {
	background: var(--accent);
}

.start-verdict-bad {
	color: var(--danger);
}

.start-verdict-bad .start-dot {
	background: var(--danger);
}

/* What Create makes, under the verdict while Create is offered: the runtime,
   and whether the project starts with a place. */
.start-new {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 8px 16px;
	margin: 10px 2px 0;
	font-size: var(--text-md);
	color: var(--fg-muted);
}

.start-new-place {
	display: inline-flex;
	align-items: center;
	gap: 7px;
	cursor: pointer;
}

.start-new-place.off {
	opacity: 0.5;
	cursor: default;
}

.start-new-place code {
	font-size: var(--text-sm);
}

/* The host never answered. This is the screen instead of the folder picker,
   because a picker is an answer to a question nobody asked when the thing that
   would open the folder is the thing that is down. */
.start-down {
	display: grid;
	gap: 10px;
}

.start-down h2 {
	margin: 0;
	font-size: var(--text-lg);
	color: var(--danger);
}

.start-down p {
	margin: 0;
	color: var(--fg-muted);
}

.start-down .start-note {
	font-size: var(--text-sm);
	color: var(--fg-faint);
}

.start-command {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 6px 6px 6px 10px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--bg-input);
}

.start-command code {
	flex: 1;
	font-family: var(--mono, ui-monospace, monospace);
}

.start-section {
	display: flex;
	align-items: center;
	gap: 10px;
	margin: 26px 0 10px;
	font-size: var(--text-xs);
	font-weight: 700;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--fg-faint);
}

.start-section .spacer,
.start-foot .spacer {
	flex: 1;
}

.start-count {
	font-weight: 400;
	letter-spacing: 0;
}

.start-filter {
	width: 200px;
	height: 26px;
	padding: 0 8px;
	box-sizing: border-box;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--bg-input);
	color: var(--fg);
	font: inherit;
	font-size: var(--text-sm);
	font-weight: 400;
	letter-spacing: 0;
	text-transform: none;
}

.start-cards {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 10px;
}

.start-empty {
	margin: 0;
	padding: 18px;
	text-align: center;
	color: var(--fg-faint);
	border: 1px dashed var(--border);
	border-radius: var(--radius-md);
	background: color-mix(in srgb, var(--accent) 3%, var(--bg-canvas));
}

/* A recent project or a demo. The whole card opens it; Forget sits over its
   corner, shown on hover and always on touch. */
.start-tile {
	position: relative;
	display: grid;
	min-width: 0;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	background: var(--start-card);
	box-shadow: var(--shadow-raised);
	color: var(--fg);
	font: inherit;
	text-align: left;
}

.start-tile:hover,
.start-tile:focus-within {
	border-color: color-mix(in srgb, var(--accent) 50%, var(--border));
	background: var(--start-card-hover);
}

.start-tile-open,
button.start-tile {
	display: grid;
	gap: 6px;
	min-width: 0;
	padding: 11px 12px;
	background: none;
	color: inherit;
	font: inherit;
	text-align: left;
	cursor: pointer;
}

.start-tile-open {
	border: 0;
}

button.start-tile {
	background: var(--start-card);
}

.start-tile-open:focus-visible,
button.start-tile:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: -2px;
	border-radius: var(--radius-md);
}

.start-tile-name {
	display: flex;
	align-items: center;
	gap: 8px;
	min-width: 0;
	padding-right: 30px;
	font-weight: 600;
	font-size: var(--text-lg);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.start-tile-path {
	font-family: var(--mono, ui-monospace, monospace);
	font-size: var(--text-sm);
	color: var(--fg-faint);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.start-tile-what {
	font-size: var(--text-sm);
	color: var(--fg-muted);
}

.start-tile-meta {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 6px;
	min-height: 20px;
	font-size: var(--text-sm);
	color: var(--fg-muted);
}

.start-faint {
	color: var(--fg-faint);
}

.start-missing {
	color: var(--warning);
}

.start-tile-gone .start-tile-name {
	color: var(--fg-muted);
}

.start-tile-acts {
	position: absolute;
	top: 7px;
	right: 7px;
	display: flex;
	gap: 2px;
	opacity: 0;
	transition: opacity 120ms;
}

.start-tile:hover .start-tile-acts,
.start-tile:focus-within .start-tile-acts {
	opacity: 1;
}

@media (hover: none) {
	.start-tile-acts {
		opacity: 1;
	}
}

.start-tile-acts .tb.icon-only {
	width: 24px;
	height: 24px;
	min-height: 0;
	padding: 0;
	display: inline-grid;
	place-items: center;
	border-color: transparent;
	background: none;
}

.start-tile-acts .tb.icon-only:hover {
	background: var(--bg-hover);
}

button.start-tile:disabled {
	cursor: progress;
	opacity: 0.7;
}

.start-foot {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 8px 16px;
	margin-top: 30px;
	font-size: var(--text-sm);
	color: var(--fg-faint);
}

.start-foot a {
	color: var(--fg-muted);
}

.start-foot code {
	padding: 1px 6px;
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	background: var(--bg-input);
	color: var(--fg-muted);
}

/* What a project or pack compiles for, as a chip. */
.runtime-chip {
	display: inline-flex;
	align-items: center;
	padding: 1px 8px;
	border: 1px solid var(--border);
	border-radius: 999px;
	font-size: var(--text-xs);
	font-weight: 400;
	color: var(--fg-muted);
	white-space: nowrap;
}

.runtime-chip-roblox {
	color: var(--accent);
	border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
}

.runtime-chip-lune {
	color: var(--warning);
	border-color: color-mix(in srgb, var(--warning) 45%, var(--border));
}

@media (max-width: 699px) {
	.start-cards {
		grid-template-columns: minmax(0, 1fr);
	}

	.start-row {
		flex-wrap: wrap;
	}

	.start-field {
		flex-basis: 100%;
	}

	.start-filter {
		width: 150px;
	}
}

@media (prefers-reduced-motion: reduce) {
	.start-tile-acts {
		transition: none;
	}
}

/* ------------------------------------------------------- .luau source view */

.source {
	display: grid;
	grid-template-rows: auto auto 1fr;
	min-height: 0;
	height: 100%;
	background: var(--bg-canvas);
}

.source-head {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 6px 10px;
	background: var(--bg-panel);
	border-bottom: 1px solid var(--border);
}

.source-head .name {
	font-size: var(--text-md);
	font-weight: 600;
	color: var(--fg);
}

.source-head .badge {
	font-size: var(--text-xs);
	text-transform: uppercase;
	letter-spacing: 0.06em;
	border: 1px solid var(--border);
	border-radius: 999px;
	padding: 1px 7px;
	color: var(--fg-muted);
}

/* Generated files are Roswaal's, and saying so in the accent colour it uses
   for its own output everywhere else saves reading the word. */
.source-head .badge.generated {
	border-color: color-mix(in srgb, var(--accent) 55%, transparent);
	color: var(--accent);
}

.source-head .meta {
	font-size: var(--text-sm);
	color: var(--fg-faint);
}

.source-note {
	margin: 0;
	padding: 6px 12px;
	font-size: var(--text-sm);
	color: var(--fg-faint);
	background: var(--bg-panel);
	border-bottom: 1px solid var(--border);
}

.source-body {
	min-height: 0;
	overflow: hidden;
}

/* -------------------------------------------------------------- inspector */

.inspector {
	background: var(--bg-panel);
	border-left: 1px solid var(--border);
	overflow: auto;
	min-height: 0;
}

.inspector h2 {
	font-size: var(--text-sm);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: var(--fg-faint);
	margin: 0;
	padding: 10px 12px 6px;
	position: sticky;
	top: 0;
	background: var(--bg-panel);
}

.inspector-body {
	padding: 10px 10px 18px;
	display: flex;
	flex-direction: column;
	gap: 10px;
}

.inspector .summary {
	margin: 0;
	font-size: var(--text-sm);
	line-height: 1.5;
	color: var(--fg-muted);
}

.inspector .field {
	display: flex;
	flex-direction: column;
	gap: 4px;
}

.inspector .field > .field-label {
	font-size: var(--text-sm);
	font-weight: 600;
	color: var(--fg);
}

/* A label beside a one-line field, in bold as a switch's is. Anything taller
   -- a code box, swatches, a segmented switch -- keeps its label above it. */
.inspector .field:has(> input.tb, > select.tb, > .tb.type-picker, > .target-pick, > .docs-bar-field:not(.docs-bar-code), > .docs-bar-select) {
	display: grid;
	grid-template-columns: minmax(70px, 36%) minmax(0, 1fr);
	align-items: center;
	gap: 10px;
}

.inspector .field input {
	cursor: text;
	width: 100%;
}

/* A row of colour choices, which wraps rather than shrinking: a swatch smaller
   than this stops being a colour you can tell from its neighbour. */
.inspector .swatches {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
}

.inspector .swatch {
	width: 22px;
	height: 22px;
	padding: 0;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	cursor: pointer;
}

.inspector .swatch.on {
	/* Two rings rather than a border, so the mark reads on a swatch of any
	   colour -- including one close to the panel's own background. */
	outline: 2px solid var(--fg);
	outline-offset: 1px;
}

.inspector .swatch:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 1px;
}

.inspector .list-editor {
	display: flex;
	flex-direction: column;
	gap: 4px;
}

.inspector .list-editor > .summary {
	padding-left: 6px;
}

/* A parameter, field or return: its wire colour, its name, its type, and a
   quiet remove. Name and type the same height, on the same line. */
.inspector .list-row {
	display: grid;
	grid-template-columns: 10px minmax(0, 1fr) minmax(0, 46%) 24px;
	gap: 6px;
	align-items: center;
	padding-left: 6px;
}

.inspector .list-row input.tb,
.inspector .list-row .tb.type-picker {
	min-width: 0;
	height: 28px;
	box-sizing: border-box;
	padding-block: 0;
}

.inspector .list-row .list-remove {
	width: 24px;
	height: 24px;
	padding: 0;
	display: inline-grid;
	place-items: center;
	background: transparent;
	border-color: transparent;
	color: var(--fg-faint);
}

.inspector .list-row .list-remove:hover:not(:disabled) {
	background: var(--bg-hover);
	border-color: transparent;
	color: var(--danger);
}

/* A data pin: its wire colour, filled going in and hollow coming out. */
.inspector .pin-summary {
	display: grid;
	grid-template-columns: 10px minmax(0, 1fr) auto;
	gap: 8px;
	align-items: center;
	padding: 3px 6px;
	border-radius: var(--radius-xs);
	font-size: var(--text-sm);
}

.inspector .pin-summary:hover {
	background: var(--bg-hover);
}

.pin-dot {
	width: 9px;
	height: 9px;
	border-radius: 50%;
	background: var(--pin);
}

.pin-dot.out {
	background: transparent;
	box-shadow: inset 0 0 0 2px var(--pin);
}

.inspector .pin-name {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.inspector .pin-dir {
	margin-left: 5px;
	font-size: var(--text-xs);
	color: var(--fg-faint);
}

/* Two-line node headers: a name on top, its signature or target underneath. */

.node .head.two-line {
	align-items: center;
}

.node .head .lines {
	display: flex;
	flex-direction: column;
	justify-content: center;
	min-width: 0;
	line-height: 1.25;
}

.node .head .subtitle {
	font-size: var(--text-xs);
	font-weight: 500;
	opacity: 0.78;
	font-family: "Cascadia Mono", Consolas, monospace;
	letter-spacing: -0.01em;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* -------------------------------------------------------------- variables */

.variables {
	border-top: 1px solid var(--border);
	flex: none;
}

.variables h2 {
	display: flex;
	align-items: center;
	justify-content: space-between;
	position: static;
}

.variables h2 button {
	padding: 1px 8px;
	font-size: var(--text-sm);
	text-transform: none;
	letter-spacing: 0;
}

.variable-list {
	padding: 0 6px 12px;
}

.variables .hint {
	margin: 0;
	padding: 2px 8px 6px;
	font-size: var(--text-sm);
	line-height: 1.5;
	color: var(--fg-faint);
}

/* A second list under the variables: the graph's locals, and its types. */
/* A section of the Variables panel, headed as every panel's sections are:
   the name in the accent colour with a line running on (see .pane-head). */
.variables-sub {
	display: flex;
	align-items: center;
	gap: 6px;
	min-height: 24px;
	margin: 8px 0 2px;
	padding: 0 4px;
	font-size: calc(12em / 13);
	font-weight: 650;
	letter-spacing: 0;
	text-transform: none;
	color: var(--accent);
}

/* The line running on after the name, before any button at the end. */
.variables-sub::after {
	content: "";
	order: 1;
	flex: 1;
	min-width: 12px;
	height: 1px;
	background: color-mix(in srgb, var(--accent) 35%, var(--border));
}

.variables-sub > button.tb {
	order: 2;
	min-height: 0;
	height: 22px;
	margin-left: 0;
	padding: 0 7px;
	font-size: var(--text-sm);
	font-weight: 400;
}

.variable-head {
	display: flex;
	align-items: center;
	gap: 7px;
	padding: 3px 6px;
	border-radius: var(--radius-sm);
	cursor: grab;
	user-select: none;
	white-space: nowrap;
}

.variable-head:hover {
	background: var(--bg-hover);
}

.variable.expanded .variable-head {
	background: var(--bg-active);
}

.variable-head .swatch {
	width: 9px;
	height: 9px;
	border-radius: 50%;
	flex: none;
}

/* The name is what a row is found by, so it keeps its width, up to most of
   the row; a long trailing path or type is what gives way. */
.variable-head .name {
	flex: 0 0 auto;
	max-width: 65%;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* A type has no pin colour, so it takes the colour types have in code. Square,
   because it is not a value. */
.variable-head .type-swatch {
	border-radius: var(--radius-xs);
	background: var(--code-type);
}

.variable-head .type {
	flex: 0 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	margin-left: auto;
	font-size: var(--text-xs);
	color: var(--fg-faint);
	font-family: "Cascadia Mono", Consolas, monospace;
}

/* What a named-result row is, in the type's quiet colour so the name reads first. */
.variable-head .badge-result {
	font-size: var(--text-xs);
	color: var(--fg-faint);
	font-family: "Cascadia Mono", Consolas, monospace;
}

/* Faint until pointed at, so a list of locals does not read as a list of
   deletes; still there for a finger, which cannot hover. */
.variable-head .variable-delete {
	flex: none;
	padding: 0 5px;
	line-height: 16px;
	background: transparent;
	border-color: transparent;
	color: var(--fg-faint);
	cursor: pointer;
}

.variable-head .variable-delete:hover,
.variable-head .variable-delete:focus-visible {
	color: var(--danger, #e06c6c);
	border-color: var(--border);
}

.variable-body {
	display: flex;
	flex-direction: column;
	gap: 7px;
	padding: 8px 8px 12px 22px;
}

.variable-body .field {
	display: grid;
	grid-template-columns: 84px 1fr;
	align-items: center;
	gap: 6px;
}

.variable-body .field > span {
	font-size: var(--text-sm);
	color: var(--fg-faint);
}

.variable-body .field input[type="text"],
.variable-body .field input:not([type]),
.variable-body .field input[type="number"],
.variable-body .field select {
	cursor: text;
	min-width: 0;
	width: 100%;
}

.variable-footer {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 8px;
}

.variable-footer .hint {
	padding: 0;
}

.variable-footer button {
	padding: 1px 8px;
	font-size: var(--text-sm);
	color: var(--danger);
}

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

.map-editor {
	display: grid;
	grid-template-columns: 1fr 380px;
	min-height: 0;
	overflow: hidden;
}

/* The editor's own panel has a handle between the tree and the Inspector, in
   a track of its own with no width. The docs draw the panel without one. Each
   side keeps at least 180px however the window shrinks. */
.map-editor.map-editor-resizable {
	grid-template-columns: minmax(180px, 1fr) 0 min(var(--map-side-w, 380px), calc(100% - 180px));
}

.map-splitter {
	position: relative;
	z-index: 2;
	cursor: col-resize;
	touch-action: none;
}

/* Wider to hit than to see, and drawn only when found. */
.map-splitter::before {
	content: "";
	position: absolute;
	inset: 0 -6px;
}

.map-splitter::after {
	content: "";
	position: absolute;
	top: 0;
	bottom: 0;
	left: -1px;
	width: 2px;
	background: transparent;
	transition: background 120ms ease;
}

.map-splitter:hover::after,
.map-splitter:active::after {
	background: var(--accent);
}

.map-tree {
	overflow: auto;
	background: var(--bg-canvas);
	padding-bottom: 16px;
}

.map-bar {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 6px 10px;
	border-bottom: 1px solid var(--border);
	background: var(--bg-panel);
	position: sticky;
	top: 0;
	z-index: 1;
	font-weight: 600;
}

.map-bar .dirty::after {
	content: "•";
	color: var(--warning);
	margin-left: 5px;
}

.map-row {
	display: flex;
	align-items: center;
	gap: 7px;
	padding: 4px 10px;
	cursor: default;
	user-select: none;
	white-space: nowrap;
}

.map-row:hover {
	background: var(--bg-hover);
}

.map-row.selected {
	background: var(--bg-active);
	box-shadow: inset 2px 0 0 var(--accent);
}

.map-row .glyph {
	width: 12px;
	color: var(--fg-faint);
	cursor: pointer;
	flex: none;
}

.map-row .name {
	font-weight: 500;
}

.map-row .class,
.map-row .path {
	font-size: var(--text-sm);
	font-family: "Cascadia Mono", Consolas, monospace;
	color: var(--fg-faint);
}

.map-row .path {
	margin-left: auto;
	color: var(--accent);
}

.map-side {
	border-left: 1px solid var(--border);
	background: var(--bg-panel);
	overflow: auto;
	display: flex;
	flex-direction: column;
	min-height: 0;
}

/* The column scrolls; nothing in it gives way. A flex item shrinks when the
   column is shorter than its contents, and the project file -- a scroller of
   its own, so free to shrink to nothing -- took all of it: on an iPad it was
   one line high under a column that scrolled past it. */
.map-side > * {
	flex-shrink: 0;
}

.map-side h2 {
	font-size: var(--text-sm);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: var(--fg-faint);
	margin: 0;
	padding: 10px 12px 6px;
}

.map-side .summary {
	margin: 0;
	padding: 0 12px 6px;
	font-size: var(--text-sm);
	color: var(--fg-muted);
}

.map-fields,
.map-side > .field {
	display: flex;
	flex-direction: column;
	gap: 7px;
	padding: 0 12px 8px;
}

.map-fields .field,
.map-side > .field {
	display: grid;
	grid-template-columns: 100px 1fr;
	align-items: center;
	gap: 6px;
}

.map-fields .field > span,
.map-side > .field > span {
	font-size: var(--text-sm);
	color: var(--fg-faint);
}

.map-fields input,
.map-fields select,
.map-side > .field input {
	cursor: text;
	min-width: 0;
	width: 100%;
}

.map-actions {
	display: flex;
	gap: 6px;
	padding: 0 12px 10px;
	flex-wrap: wrap;
}

.map-preview {
	margin: 0 12px 12px;
	padding: 10px 12px;
	background: var(--bg-canvas);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	font: 11px/1.55 "Cascadia Mono", Consolas, monospace;
	overflow: auto;
	max-height: 340px;
	white-space: pre;
}

.map-diagnostics {
	padding: 0 12px 16px;
}

.map-diagnostics .entry {
	display: flex;
	gap: 8px;
	font-size: var(--text-sm);
	padding: 2px 0;
}

.map-diagnostics .entry .sev {
	width: 56px;
	flex: none;
	font-weight: 600;
}

.map-diagnostics .entry.error .sev {
	color: var(--danger);
}

.map-diagnostics .entry.warning .sev {
	color: var(--warning);
}

/* ------------------------------------------------------------ code editor */

:root {
	--code-keyword: #9a4f9a;
	--code-string: #2f7f4f;
	--code-number: #8a5a2f;
	--code-comment: #7a8290;
	--code-operator: #4a6fa5;
	--code-property: #2c6f8f;
}

@media (prefers-color-scheme: dark) {
	:root {
		--code-keyword: #c98fd0;
		--code-string: #8fce9b;
		--code-number: #d9a86c;
		--code-comment: #6b7280;
		--code-operator: #86b3e0;
		--code-property: #7fc4d6;
	}
}

/* The raw-literal button on a node: a peek at the code, not the code. */
/* A node with nothing below its header is only its header (0.170.0): Script
   Start, a Return with no values. It had an empty strip under it, and a node
   with no pins at all a whole empty row. */
.node.node--head-only .head {
	border-radius: max(0px, calc(var(--node-radius) - 1px));
	box-shadow: none;
}

/* The code on a code node's face (0.170.0): Code Block, Luau Expression and
   the Declare Types show their Luau rather than one clipped line behind a
   label. The numbers are `CODE_FACE` in `src/core/codeFace.ts`, which the
   wires and the documentation's drawings are measured from. Change both. */
.node .node-face-body {
	position: relative;
	box-sizing: border-box;
	padding: 8px 10px 10px;
}

.node .node-face {
	position: relative;
	display: block;
	box-sizing: border-box;
	padding: 5px 8px;
	overflow: hidden;
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	font: 11px/16px "Cascadia Mono", Consolas, monospace;
	white-space: pre;
	color: var(--fg);
	cursor: pointer;
}

.node .node-face-line {
	display: block;
	overflow: hidden;
	text-overflow: ellipsis;
}

.node .node-face:hover,
.node .node-face:focus-visible {
	border-color: var(--accent);
	outline: none;
}

.node .node-face:not([role="button"]) {
	cursor: inherit;
}

.node .node-face.clipped {
	padding-bottom: 19px;
}

.node .node-face.clipped::after {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 30px;
	background: linear-gradient(to bottom, transparent, var(--bg-input) 70%);
	pointer-events: none;
}

.node .node-face .more {
	position: absolute;
	right: 6px;
	bottom: 3px;
	z-index: 1;
	font: 600 10px/12px system-ui, sans-serif;
	color: var(--fg-muted);
	background: var(--bg-panel);
	border: 1px solid var(--border);
	border-radius: 7px;
	padding: 0 6px;
}

.node .node-face.empty {
	border-style: dashed;
	color: var(--fg-faint);
	font: 12px/18px system-ui, sans-serif;
	text-align: center;
	white-space: normal;
}

.node.node--has-error .node-face.empty {
	border-color: color-mix(in srgb, var(--danger) 60%, var(--border));
	color: color-mix(in srgb, var(--danger) 70%, var(--fg));
}

/* A face's pins sit level with its first line, centred on the node's edge as
   a row's data pins are. `top` is set inline from `codeFacePinY`. */
.node .node-face-pin {
	position: absolute;
	display: flex;
	height: var(--pin-slot);
	margin-top: calc(-1 * (var(--pin-slot) / 2 + var(--node-stroke)));
}

.node .node-face-pin.in {
	left: calc(-1 * (var(--pin-slot) / 2 + var(--node-stroke)));
}

.node .node-face-pin.out {
	right: calc(-1 * (var(--pin-slot) / 2 + var(--node-stroke)));
}

.node .literal.code {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	max-width: 104px;
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	padding: 1px 5px;
	font: 10px/1.5 "Cascadia Mono", Consolas, monospace;
	cursor: pointer;
	overflow: hidden;
}

.node .literal.code:hover {
	border-color: var(--accent);
}

.node .literal.code .preview {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	color: var(--fg-muted);
}

.node .literal.code .more {
	flex: none;
	color: var(--fg-faint);
	border-left: 1px solid var(--border);
	padding-left: 4px;
}

.code-backdrop {
	position: fixed;
	inset: 0;
	background: rgba(0, 0, 0, 0.45);
	display: grid;
	place-items: center;
	padding: 40px;
}

.code-modal {
	background: var(--bg-panel);
	border: 1px solid var(--border-strong);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-modal);
	width: min(880px, 100%);
	height: min(600px, 100%);
	display: grid;
	grid-template-rows: auto 1fr auto;
	overflow: hidden;
}

.code-head {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 8px 12px;
	border-bottom: 1px solid var(--border);
}

.code-head .title {
	font-weight: 600;
}

.code-head .hint {
	font-size: var(--text-sm);
	color: var(--fg-faint);
}

.code-body {
	overflow: hidden;
	min-height: 0;
}

.code-body .cm-editor {
	height: 100%;
}

/* A Code Block node's Luau, opened from a graph in the docs: the code
   editor's frame and look, read-only, and as tall as the code up to the
   editor's own height. The gutter and face are `luauTheme.ts`'s. */
.docs-code-backdrop {
	z-index: 1000;
}

.code-modal.read-only {
	height: auto;
	max-height: min(600px, 100%);
	grid-template-rows: auto minmax(0, 1fr);
}

.code-modal.read-only .code-head .hint {
	flex: 1;
}

.code-modal.read-only .code-body {
	overflow: auto;
	background: var(--bg-canvas);
}

.code-view {
	display: flex;
	min-height: 100%;
	font-family: "Cascadia Mono", Consolas, monospace;
	font-size: var(--text-md);
	line-height: 1.5;
}

.code-view pre {
	margin: 0;
	padding: 10px 0;
	font: inherit;
	line-height: inherit;
	white-space: pre;
	background: none;
	border: 0;
	border-radius: 0;
}

.code-view-gutter {
	flex: none;
	padding-left: 14px !important;
	padding-right: 10px !important;
	text-align: right;
	color: var(--fg-faint);
	background: var(--bg-panel) !important;
	border-right: 1px solid var(--border) !important;
	user-select: none;
}

.code-view-code {
	flex: 1;
	padding-left: 8px !important;
	padding-right: 14px !important;
}

.code-view-code code {
	font: inherit;
	padding: 0;
	background: none;
	color: var(--fg);
}

/* A node in a docs graph that opens its code: the pointer says so. */
.graph-viewport .docs-code-node {
	cursor: pointer;
}

.graph-viewport .docs-code-node:hover {
	filter: brightness(1.15);
}

.code-body .cm-scroller {
	overflow: auto;
}

/* ------------------------------------------------------------- the Code panel */

/* A tab per open field, the editor, and its status line, filling the card.
   See `CodePanel.tsx`. */
.card-body.panel-code {
	display: flex;
	flex-direction: column;
	overflow: hidden;
}

.code-panel {
	flex: 1 1 auto;
	min-height: 0;
	display: flex;
	flex-direction: column;
}

/* The tabs, in the card's header after its title: one per open field, the one
   in front underlined along the header's foot. The strip scrolls sideways when
   there are more than fit. */
.card-lead {
	flex: 1 1 auto;
	min-width: 0;
	align-self: stretch;
	display: flex;
}

.code-tabs {
	display: flex;
	align-items: stretch;
	gap: 2px;
	min-width: 0;
	overflow-x: auto;
	scrollbar-width: none;
}

.code-tabs::-webkit-scrollbar {
	display: none;
}

/* Folded, the header is the title, the tabs, the list and the card's own
   buttons: Go to node and Full view are for the code, which is put away. */
.card.folded > .card-head .code-head-action {
	display: none;
}

/* On a touch screen the two are their glyphs, so the tab keeps its name: on
   a phone the words left a few letters of it. The words stay as the buttons'
   names, and each is a finger wide. */
.workspace.compact .code-head-action .tb-icon-when-narrow {
	display: inline-block;
}

.workspace.compact .code-head-action {
	min-width: var(--hit);
	justify-content: center;
	padding-inline: 0;
}

.workspace.compact .code-head-action .tb-label {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

.code-tab-list {
	flex: none;
	align-self: center;
}

/* A tab is the header's full height, and the one in front is filled with the
   accent tint the menus use for the row under the pointer, × and all. The
   title and the × share a size and a line height and are each centred in the
   full height, so they sit on one baseline; the graph's name beside the title
   shares the title's baseline inside it. */
.code-tab {
	display: flex;
	align-items: stretch;
	flex: none;
	max-width: 260px;
	color: var(--fg-muted);
}

.code-tab:hover {
	background: var(--bg-hover);
	color: var(--fg);
}

.code-tab.on {
	color: var(--fg);
	background: color-mix(in srgb, var(--accent) 28%, var(--card-head));
}

.code-tab-pick,
.code-tab-close {
	display: flex;
	align-items: center;
	border: 0;
	background: none;
	color: inherit;
	font: inherit;
	font-size: var(--text-md);
	line-height: 16px;
	cursor: pointer;
}

.code-tab-pick {
	min-width: 0;
	padding: 0 4px 0 10px;
}

.code-tab-label {
	display: flex;
	align-items: baseline;
	gap: 6px;
	min-width: 0;
}

.code-tab.on .code-tab-title {
	font-weight: 600;
}

/* What the tab edits, before its name, in the colour code gives the same
   idea: braces for a Code Block, ƒx for a Luau Expression, <T> for a type --
   its brackets grey, its T a light blue taken from the theme's accent. */
.code-mark {
	flex: none;
	min-width: 18px;
	font-family: "Cascadia Mono", Consolas, monospace;
	font-size: var(--text-sm);
	font-weight: 700;
	text-align: center;
	white-space: nowrap;
}

.code-mark-block {
	color: var(--code-keyword);
}

.code-mark-expression {
	color: var(--code-function);
}

.code-mark-type {
	color: color-mix(in srgb, var(--accent) 72%, #ffffff);
}

.code-mark-angle {
	color: var(--fg-faint);
	font-weight: 400;
}

/* The node's name keeps its width; what tells it apart gives way. */
.code-tab-title {
	flex: none;
	white-space: nowrap;
}

.code-tab-where {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	color: var(--fg-faint);
	font-size: var(--text-sm);
}

.code-tab-close {
	flex: none;
	justify-content: center;
	width: 22px;
	padding: 0 4px 0 0;
	color: var(--fg-faint);
}

.code-tab-close:hover,
.code-tab-close:focus-visible {
	color: var(--fg);
}

.code-tab-pick:focus-visible,
.code-tab-close:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: -2px;
}

/* One editor per tab, each kept while it is open; only the one in front shows. */
.code-field {
	flex: 1 1 auto;
	min-height: 0;
	display: flex;
	flex-direction: column;
}

.code-field[hidden] {
	display: none;
}

/* Tall enough to write in wherever the card is: a lone card in a side column
   is as tall as what it holds. Along the foot the strip's height decides, and
   the status line stays in view however short it is dragged. */
.code-field .code-body {
	flex: 1 1 auto;
	min-height: 160px;
}

.dock.foot .code-field .code-body {
	min-height: 0;
}

.card-tools .code-full {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	white-space: nowrap;
}

.code-field .code-status {
	padding: 4px 12px;
	flex: none;
}

.code-status .code-ok {
	display: inline-block;
	width: 7px;
	height: 7px;
	margin-right: 4px;
	border-radius: 50%;
	background: var(--ok, #2e7d47);
	vertical-align: 1px;
}

.code-status .code-note {
	color: var(--fg-faint);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	min-width: 0;
}

.code-status .code-verdict {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.code-status .keys {
	flex: none;
}

.code-status {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 6px 12px;
	border-top: 1px solid var(--border);
	font-size: var(--text-sm);
	color: var(--fg-muted);
}

.code-status.bad {
	color: var(--danger);
}

.code-status.warn {
	color: var(--warning);
}

.code-status .where {
	color: var(--fg-faint);
}

.code-status .keys {
	color: var(--fg-faint);
	font-size: var(--text-xs);
}

/* ---------------------------------------------------------- operator pills */

/* A comparison is `a == b`, and a full node says that in three rows under a
   header repeating what the symbol already says. The pill is the same node
   drawn as the expression, which is how a graph of conditions reads best. */
/* The corner comes from `NODE.operatorRadius`, set inline: the wire router and
   the documentation previews measure the same constant, and a second opinion
   here would put the shape and the pins a few pixels apart. */
.node.node--operator {
	cursor: grab;
}

.node.node--operator .operator-rows {
	position: absolute;
	left: 0;
	right: 0;
}

/* A and B say nothing the symbol between them has not. */
.node.node--operator .pin-label {
	display: none;
}

.node.node--operator .operator-symbol {
	position: absolute;
	top: 0;
	bottom: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	font-family: "Cascadia Mono", Consolas, monospace;
	font-size: var(--text-lg);
	font-weight: 700;
	color: var(--fg);
	/* The pill is dragged by its middle like any other node. */
	pointer-events: none;
}

.node.node--operator .operator-out {
	position: absolute;
	right: var(--pin-pad);
	display: flex;
}

/* A pill's result is not on a row, so it misses the rule that puts a data pin on
   the node's edge. Same offset, stated once more for the one pin outside a row.
   The pill's inputs are in rows and are already covered. */
.node.node--operator .operator-out .pin {
	left: calc(var(--pin-pad) + var(--pin-slot) / 2 + var(--node-stroke));
}

.node.node--operator .operator-grow {
	position: absolute;
	top: 0;
	bottom: 0;
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: 2px;
}

.node.node--operator .operator-grow button {
	width: 14px;
	height: 14px;
	padding: 0;
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	background: var(--bg-input);
	color: var(--fg-muted);
	font-size: var(--text-xs);
	line-height: 1;
	cursor: pointer;
}

.node.node--operator .operator-grow button:disabled {
	opacity: 0.3;
	cursor: default;
}

/* A node at rest shows only what it says: the Add row and the × that change
   how many entries a node has, and an operator's − and +, appear when they
   are reached for -- the pointer over the node, the node selected, or a key
   moving focus onto them -- and not otherwise. The operator's are faded rather
   than removed, so the pill does not reflow; the ×, which sits beside a label
   with nothing after it that could move, appears outright. A touch screen has
   no hover; there, selecting the node with a tap shows them. */
.node .add-row,
.node.node--operator .operator-grow {
	opacity: 0;
	pointer-events: none;
	transition: opacity 0.12s ease;
}

.node:hover .add-row,
.node.node--selected .add-row,
.node .add-row:focus-visible,
.node.node--operator:hover .operator-grow,
.node.node--operator.node--selected .operator-grow,
.node.node--operator .operator-grow:focus-within {
	opacity: 1;
	pointer-events: auto;
}

.node:hover .pin-remove,
.node.node--selected .pin-remove,
.node:focus-within .pin-remove {
	display: inline-flex;
	align-items: center;
	justify-content: center;
}

@media (prefers-reduced-motion: reduce) {
	.node .add-row,
	.node.node--operator .operator-grow {
		transition: none;
	}
}

/* --------------------------------------------------------- capsule getters */

/* The usual node-editor shape for a variable getter: a dark pill, the name, one
   output. The form itself says "this is a value", so it needs no header and no
   title row. */
.node.node--capsule {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 0 10px 0 12px;
	/* A capsule's pin sits on the capsule, not on a node body, so its hollow has
	   to be the capsule's colour -- the same reason a knot sets this. */
	--pin-hollow: var(--capsule-bg);
	border-radius: 999px;
	background: var(--capsule-bg);
	border: 1px solid var(--capsule-border);
	box-shadow: var(--node-shadow);
	cursor: grab;
}

.node.node--capsule .capsule-label {
	flex: 1;
	min-width: 0;
	font-size: var(--text-md);
	font-weight: 500;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.node.node--capsule .pin-label {
	display: none;
}

/* Balanced on the capsule's right edge, where the wire attaches, the same way
   a data pin sits on a node's. The 10px is the capsule's own right padding. */
.node.node--capsule .pin {
	flex: none;
	left: calc(10px + var(--pin-slot) / 2 + var(--node-stroke));
}

:root {
	--capsule-bg: #e9ebf0;
	--capsule-border: #b3b9c4;
}

@media (prefers-color-scheme: dark) {
	:root {
		--capsule-bg: #2b3038;
		--capsule-border: #3d434e;
	}
}

/* ----------------------------------------------------------------- dialog */

.dialog-backdrop {
	position: fixed;
	inset: 0;
	background: rgba(0, 0, 0, 0.4);
	display: grid;
	place-items: center;
	padding: 40px;
}

.dialog {
	background: var(--bg-panel);
	border: 1px solid var(--border-strong);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-modal);
	width: min(420px, 100%);
	padding: 16px 18px 14px;
	display: flex;
	flex-direction: column;
	gap: 12px;
}

.dialog h3 {
	margin: 0;
	font-size: var(--text-lg);
}

.dialog p {
	margin: 0;
	color: var(--fg-muted);
	line-height: 1.5;
}

.dialog .field {
	display: flex;
	flex-direction: column;
	gap: 5px;
}

.dialog .field > span {
	font-size: var(--text-sm);
	color: var(--fg-faint);
}

.dialog .field input {
	cursor: text;
	width: 100%;
}

.dialog .field select {
	width: 100%;
}

.dialog-actions {
	display: flex;
	justify-content: flex-end;
	gap: 8px;
}

/* A form dialog's choices: a group of radios, and checks beneath. */
.dialog-choices {
	margin: 0;
	padding: 0;
	border: 0;
	display: flex;
	flex-direction: column;
	gap: 6px;
}

.dialog-choices legend {
	padding: 0 0 5px;
	font-size: var(--text-sm);
	color: var(--fg-faint);
}

.dialog-option {
	display: flex;
	align-items: center;
	gap: 8px;
	color: var(--fg);
	line-height: 1.4;
	cursor: pointer;
}

.dialog-option input {
	margin: 0;
	accent-color: var(--accent);
}

/* Export Project: a panel of labelled rows rather than a question -- a label
   column on the left, the control and a line on what it does on the right,
   grouped under small headings, and what goes out summed up in the foot. */
.dialog.export-menu {
	width: min(520px, 100%);
	gap: 14px;
}

.export-menu-form {
	display: grid;
	gap: 10px;
}

.export-menu-row {
	display: grid;
	grid-template-columns: 110px minmax(0, 1fr);
	gap: 12px;
	align-items: start;
}

.export-menu-label {
	font-size: var(--text-md);
	color: var(--fg-muted);
	text-align: right;
	padding-top: 5px;
	overflow-wrap: anywhere;
}

.export-menu-control {
	display: grid;
	gap: 5px;
	min-width: 0;
	justify-items: start;
}

.export-menu-control select {
	min-width: 200px;
}

.export-menu-section {
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.07em;
	text-transform: uppercase;
	color: var(--fg-faint);
	border-bottom: 1px solid var(--border);
	padding: 6px 0 4px;
	margin-top: 2px;
}

/* The name, and straight after it what it will be saved as. */
.export-menu-name {
	display: flex;
	align-items: center;
	gap: 4px;
	width: 100%;
}

.export-menu-name input {
	flex: 1;
	min-width: 0;
	cursor: text;
}

.export-menu-suffix {
	font-family: var(--font-mono, ui-monospace, monospace);
	font-size: var(--text-md);
	color: var(--fg-muted);
}

.export-menu-note {
	font-size: var(--text-sm);
	color: var(--fg-faint);
	line-height: 1.45;
}

.export-menu-problem {
	font-size: var(--text-md);
	color: var(--warning);
	line-height: 1.45;
	margin: 0;
}

.dialog-actions.export-menu-foot {
	align-items: center;
	border-top: 1px solid var(--border);
	padding-top: 12px;
}

.export-menu-contents {
	margin-right: auto;
}

/* Dropped from the Export button rather than covering the window: no dimming
   behind it, and it sits under the button, its right edge on the button's. */
.dialog-backdrop.export-menu-backdrop {
	background: transparent;
	backdrop-filter: none;
}

.dialog.export-menu.export-menu-anchored {
	position: fixed;
	width: min(480px, calc(100vw - 16px));
	max-height: calc(100dvh - 80px);
	overflow-y: auto;
}

.export-menu .segmented {
	flex-wrap: wrap;
}

.export-menu-legacy {
	font-size: var(--text-xs);
	color: var(--fg-faint);
	margin-left: 2px;
}

.export-menu-note-flush {
	padding-top: 5px;
}

.export-menu-remember {
	display: flex;
	align-items: center;
	gap: 8px;
	font-size: var(--text-md);
	color: var(--fg-muted);
	cursor: pointer;
}

.export-menu-remember input {
	margin: 0;
	accent-color: var(--accent);
}

/* The same menu on a touch screen, every row of it: finger-sized, and the name
   in 16px so Safari does not zoom onto it. */
@media (pointer: coarse) {
	.export-menu .segmented button {
		min-height: 32px;
		padding: 0 12px;
	}

	.export-menu-name input {
		height: 36px;
		font-size: 16px;
	}

	.export-menu-remember input {
		width: 20px;
		height: 20px;
	}

	.export-menu-foot .tb {
		height: 36px;
		padding: 0 16px;
	}
}

@media (max-width: 480px) {
	.export-menu-row {
		grid-template-columns: minmax(0, 1fr);
		gap: 4px;
	}

	.export-menu-label {
		text-align: left;
		padding-top: 0;
	}
}

.dialog-option.dialog-option-off {
	color: var(--fg-faint);
	cursor: default;
}

/* The filled destructive button: the confirm on "Delete this?".

   It states all three of background, colour and border rather than only the
   one that differs from `.primary`. `.danger` on its own is the *outline*
   destructive button -- danger text, danger border, no fill -- and it sits
   later in this file than `.primary`, so at equal specificity its `color`
   won. A filled button that took its fill from here and its text from there
   rendered `--danger` on `--danger`: the word "Delete" was still in the
   button, in exactly the colour of the button.

   The hover needs saying for the same kind of reason. `button.tb.primary:hover`
   carries a pseudo-class and so outranks a plain `.primary.danger`, which
   turned the delete button accent-blue the moment the pointer reached it. */
button.tb.primary.danger {
	background: var(--danger);
	border-color: transparent;
	color: #fff;
}

button.tb.primary.danger:hover:not(:disabled) {
	background: var(--danger);
}

/* A pure node: a value, worked out where it is used, with no place in the
   order of things. It used to say so with a green stripe down its left edge,
   which meant nothing until someone explained it. Now it says so with its
   shape: rounder, with no header bar, and its name and glyph in the category's
   colour on a faint wash of it -- a different kind of thing from the steps,
   whose headers the flow runs along.

   The corner follows the square-corners preference by doubling it, so a
   reader who asked for square nodes gets square values too. The outline takes
   a little of `--pure-edge`, which is what that theme role is for now. */
.node.node--pure {
	border-radius: calc(var(--node-radius) * 2);
	border-color: color-mix(in srgb, var(--pure-edge) 40%, var(--node-border));
	background: color-mix(in srgb, var(--cat) 5%, var(--node-body));
}

.node.node--pure .head {
	border-radius: max(0px, calc(var(--node-radius) * 2 - 1px)) max(0px, calc(var(--node-radius) * 2 - 1px)) 0 0;
	background: none;
	box-shadow: none;
	color: color-mix(in srgb, var(--cat) var(--cat-ink), var(--fg));
	padding-left: 6px;
	gap: 5px;
}

.node.node--pure .head .tab {
	align-self: center;
	width: auto;
	background: none;
	color: inherit;
}

:root {
	--pure-edge: #6aa84f;
}

@media (prefers-color-scheme: dark) {
	:root {
		--pure-edge: #7fbf5f;
	}
}

/* A pin with a known value set gets a dropdown rather than a text field. */
.node select.literal {
	cursor: pointer;
	appearance: none;
	padding-right: 14px;
	background-image: linear-gradient(45deg, transparent 50%, var(--fg-faint) 50%),
		linear-gradient(135deg, var(--fg-faint) 50%, transparent 50%);
	background-position: calc(100% - 8px) 6px, calc(100% - 4px) 6px;
	background-size: 4px 4px, 4px 4px;
	background-repeat: no-repeat;
	text-overflow: ellipsis;
}

/* Adding to a node that grows: a row hung under it, as wide as the node, that
   always adds at the end -- "+ Add parameter", "+ Add output". Outside the
   node's box, so it moves no pin and takes no part in the node's size. The
   bridge above it keeps the pointer "on the node" while it crosses the gap. */
.node .add-row {
	position: absolute;
	left: 10px;
	right: 10px;
	top: calc(100% + 3px);
	height: 22px;
	padding: 0;
	border: 1px dashed color-mix(in srgb, var(--cat, var(--fg-faint)) 45%, var(--node-border));
	border-radius: var(--radius-sm);
	background: var(--node-body);
	color: var(--fg-muted);
	font-size: var(--text-sm);
	cursor: pointer;
}

.node .add-row::before {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	top: -5px;
	height: 5px;
}

.node .add-row:hover {
	border-style: solid;
	color: var(--fg);
	background: color-mix(in srgb, var(--cat, var(--fg-faint)) 12%, var(--node-body));
}

/* Taking one out: a grey × beside each entry that can go, at any position. */
.node .pin-remove {
	display: none;
	flex: none;
	width: 14px;
	height: 14px;
	padding: 0;
	border: 0;
	border-radius: var(--radius-xs);
	background: none;
	color: var(--fg-faint);
	font-size: 13px;
	line-height: 13px;
	cursor: pointer;
}

.node .pin-remove:hover {
	color: var(--danger);
	background: color-mix(in srgb, var(--danger) 12%, transparent);
}

/* A function's signature under its name: types only, each in its pin's colour. */
.node .head .subtitle .sig-type {
	color: color-mix(in srgb, var(--pin) 75%, var(--fg));
	font-weight: 600;
}

/* Opens a Declare Function's graph, at the right of its header. */
.node .head .open-graph {
	display: flex;
	align-items: center;
	justify-content: center;
	flex: none;
	width: 18px;
	height: 18px;
	margin-left: auto;
	padding: 0;
	border: 1px solid color-mix(in srgb, var(--cat) 45%, var(--node-border));
	border-radius: var(--radius-xs);
	background: var(--node-body);
	color: var(--fg);
	cursor: pointer;
}

.node .head .open-graph:hover {
	background: color-mix(in srgb, var(--cat) 22%, var(--node-body));
}


/* A node that a dragged wire can grow to accept. */
.node.grow-target {
	box-shadow: 0 0 0 2px var(--ok), var(--node-shadow);
}

/* ------------------------------------------------------------------ icons */

.icon {
	flex: none;
	display: block;
	transition: transform 0.12s ease;
}

/* The mark. It takes the colour of whatever it sits in — see logo.tsx — so it
   needs no colour of its own here, only the same layout the icons get. */
.logo-mark {
	flex: none;
	display: block;
}

.tree-row .twist {
	color: var(--fg-faint);
	cursor: pointer;
}

.tree-row .kind {
	color: var(--fg-muted);
}

/* Graphs are the editable thing here, so they carry the accent; generated
   Luau sits back with the rest of the repository. */
.tree-row .kind.nodescript {
	color: var(--accent);
}

.tree-row .kind.function {
	color: var(--accent);
}

.tree-row .function-twist {
	display: inline-flex;
	margin-left: -14px;
}

.tree-row .kind.nodemap {
	color: var(--ok);
}

.tree-row .kind.luau {
	color: var(--fg-faint);
}

/* A Luau file by the script it becomes: a Server Script white on dark and
   slate on light, a LocalScript green, a ModuleScript blue. */
.tree-row .kind.luau.tree-script-server {
	color: var(--tree-script-server);
}

.tree-row .kind.luau.tree-script-local {
	color: var(--tree-script-local);
}

.tree-row .kind.luau.tree-script-module {
	color: var(--tree-script-module);
}

/* Folders say what they are: grey for a plain folder, blue
   for a service, container or script in Studio, red for place/, which only
   the place holds. A shade of each for light schemes and dark; see derivedTokens. */
.tree-row .kind.tree-folder-plain {
	color: var(--tree-folder-plain);
}

.tree-row .kind.tree-folder-special {
	color: var(--tree-folder-special);
}

/* Wally's: its folders in Compile content, and wally.toml's packages in Graph
   content, one colour so the two read as the same thing. */
.tree-row .kind.tree-folder-packages,
.tree-row .kind.package {
	color: var(--tree-folder-packages);
}

.tree-row .kind.tree-folder-place {
	color: var(--danger);
}

/* ------------------------------------------------------- DataModel browser */

/* The Project panel's Files / DataModel switch, at the end of its card's
   header. */
.project-views {
	flex: none;
	text-transform: none;
	letter-spacing: 0;
}

.project-views button {
	font-size: var(--text-md);
	padding: 2px 10px;
	color: var(--fg);
}

/* Either view fills the panel under the switch and scrolls inside itself. */
.project-view {
	display: flex;
	flex-direction: column;
	flex: 1;
	min-height: 0;
}

.project-view[hidden] {
	display: none;
}

.place-browser {
	display: flex;
	flex-direction: column;
	flex: 1;
	min-height: 0;
}

/* The filter, as wide as the tree's rows under it, and clear of the card's
   heading above. */
.place-browser-head {
	display: flex;
	gap: 6px;
	padding: 6px 7px;
	flex: none;
}

.place-filter {
	flex: 1;
	min-width: 0;
	cursor: text;
}

.place-tree {
	min-height: 96px;
	overflow: auto;
}

.place-row .place-twist {
	display: inline-flex;
	width: 14px;
	flex: none;
}

.place-row .label {
	flex: 0 1 auto;
	min-width: 0;
}

.place-class {
	margin-left: auto;
	padding-left: 8px;
	font-size: var(--text-sm);
	color: var(--fg-faint);
	flex: none;
}

/* A filter's match says what it is in, since it is listed out of the tree. */
.place-where {
	font-size: var(--text-sm);
	color: var(--fg-faint);
	overflow: hidden;
	text-overflow: ellipsis;
	min-width: 0;
}

.place-more .label {
	font-size: var(--text-md);
	color: var(--accent);
	cursor: pointer;
}

/* The Properties panel: its heading, with Close where Variables has Add,
   and the instance under it, scrolling on its own. */
.place-properties {
	display: flex;
	flex-direction: column;
	flex: 1;
	min-height: 0;
}

.place-properties h2 {
	display: flex;
	align-items: center;
	justify-content: space-between;
	position: static;
	flex: none;
}

.place-properties h2 button {
	padding: 1px 8px;
	font-size: var(--text-sm);
	text-transform: none;
	letter-spacing: 0;
}

.place-props {
	flex: 1;
	min-height: 0;
	overflow: auto;
	padding: 2px 12px 12px;
}

/* The instance's name block, which is also a handle to drag it by. */
.place-props-head {
	margin-top: 8px;
	cursor: grab;
}

.place-path {
	margin-top: 3px;
	font-size: var(--text-sm);
	color: var(--fg-faint);
	overflow-wrap: anywhere;
}

.place-summary {
	margin: 6px 0 0;
	font-size: var(--text-md);
	line-height: 1.45;
	color: var(--fg-muted);
}

.place-summary code {
	padding: 0 4px;
	border-radius: var(--radius-xs);
	background: var(--bg-input);
	color: var(--fg);
	font-size: var(--text-sm);
}

.place-owner {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
	margin-top: 8px;
}

.place-note {
	margin: 4px 0;
	padding: 0 12px;
	font-size: var(--text-md);
	color: var(--fg-faint);
}

.place-props .place-note {
	padding: 0;
}

.place-group {
	margin-top: 6px;
}

/* A name and its value. The value a weight above its name, so the two columns
   do not read as one grey run; the row is a handle for dragging it out. */
.place-prop {
	display: grid;
	grid-template-columns: minmax(0, 46%) minmax(0, 1fr);
	gap: 10px;
	align-items: center;
	min-height: 24px;
	padding: 1px 6px;
	border-radius: var(--radius-xs);
	font-size: var(--text-md);
	cursor: grab;
}

/* An attribute with its ×, in a column of its own beside the value. */
.place-prop.place-prop--removable {
	grid-template-columns: minmax(0, 46%) minmax(0, 1fr) auto;
}

.place-prop:hover {
	background: var(--bg-hover);
}

.place-prop-name {
	color: var(--fg-muted);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.place-prop-value {
	color: var(--fg);
	font-weight: 500;
	font-variant-numeric: tabular-nums;
	overflow-wrap: anywhere;
}

/* An enum's number, set back behind its name. */
.place-enum-value {
	color: var(--fg-faint);
	font-weight: 400;
}

/* True and false as a box. */
.place-bool {
	display: inline-flex;
	align-items: center;
	gap: 6px;
}

.place-bool > i {
	width: 12px;
	height: 12px;
	display: inline-grid;
	place-items: center;
	border: 1px solid var(--border-strong);
	border-radius: var(--radius-xs);
	font-style: normal;
	font-size: 9px;
	line-height: 1;
}

.place-bool.on > i {
	background: var(--accent);
	border-color: var(--accent);
	color: #fff;
}

.place-swatch {
	display: inline-block;
	width: 10px;
	height: 10px;
	margin-right: 5px;
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	vertical-align: -1px;
}

/* A reference goes to what it names: a chip to follow. */
.place-ref {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	max-width: 100%;
	padding: 0 7px;
	border: 0;
	border-radius: 999px;
	background: color-mix(in srgb, var(--accent) 16%, transparent);
	color: var(--accent);
	font: inherit;
	text-align: left;
	cursor: pointer;
}

.place-ref:hover {
	background: color-mix(in srgb, var(--accent) 26%, transparent);
}

/* A reference that can be changed: the value to click, and the way to what it points at. */
.place-ref-edit {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	max-width: 100%;
	min-width: 0;
}

.place-ref.place-ref-go {
	flex: none;
	padding: 2px 4px;
}

/* With Modify DataModel Instances on: a value that can be changed reads as
   the value still, with a box round it on hover, and turns into its field. */
.place-edit {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	max-width: 100%;
	min-height: 20px;
	margin: 0 -5px;
	padding: 0 5px;
	border: 1px solid transparent;
	border-radius: var(--radius-xs);
	background: none;
	color: inherit;
	font: inherit;
	text-align: left;
	overflow-wrap: anywhere;
	cursor: text;
}

.place-edit:hover:not(:disabled) {
	border-color: var(--border-strong);
	background: var(--bg);
}

.place-bool.place-edit {
	cursor: pointer;
}

.place-edit:disabled {
	opacity: 0.6;
}

.place-edit-field {
	width: 100%;
	min-width: 0;
	height: 22px;
	padding: 0 5px;
	font-size: var(--text-md);
}

select.place-edit-field {
	padding: 0 2px;
}

.place-prop-editable {
	cursor: default;
}

.place-prop-editable .place-prop-value {
	display: flex;
	align-items: center;
	gap: 5px;
	min-width: 0;
}

/* The swatch is the picker. */
.place-colour {
	flex: none;
	width: 16px;
	height: 16px;
	padding: 0;
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	background: none;
	cursor: pointer;
}

.place-colour::-webkit-color-swatch-wrapper {
	padding: 0;
}

/* Apart: a browser drops a whole rule over a pseudo-element it does not know. */
.place-colour::-webkit-color-swatch {
	border: 0;
	border-radius: 1px;
}

.place-colour::-moz-color-swatch {
	border: 0;
	border-radius: 1px;
}

.place-empty {
	color: var(--fg-faint);
	font-style: italic;
	font-weight: 400;
}

/* A vector's components, or a CFrame's position and orientation: a small
   field each, named above it, and a row a part of the CFrame. */
.place-fields {
	display: grid;
	gap: 6px;
	flex: 1 1 auto;
	min-width: 0;
}

.place-fields-row {
	display: grid;
	gap: 2px;
}

.place-fields-label {
	font-size: var(--text-sm);
	color: var(--fg-muted);
	font-weight: 400;
}

.place-fields-grid {
	display: grid;
	grid-auto-flow: column;
	grid-auto-columns: minmax(0, 1fr);
	gap: 4px;
}

.place-fields-grid.place-fields-four {
	grid-auto-flow: row;
	grid-template-columns: repeat(2, minmax(0, 1fr));
}

.place-field {
	display: grid;
	gap: 1px;
	min-width: 0;
}

.place-field > span {
	font-size: var(--text-xs);
	color: var(--fg-faint);
	font-weight: 400;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* Why a change was not made, under the row it was made in. */
.place-prop-refused {
	grid-column: 1 / -1;
	color: var(--danger);
	font-size: var(--text-sm);
	font-weight: 400;
}

.place-studio {
	color: var(--warning);
}

/* A finger needs room: the field and the box are touch targets. */
@media (pointer: coarse) {
	.place-edit,
	.place-edit-field {
		min-height: 32px;
	}

	.place-colour {
		width: 28px;
		height: 28px;
	}
}

.place-tags {
	display: flex;
	flex-wrap: wrap;
	gap: 4px;
	padding-top: 5px;
}

.place-tag {
	display: inline-flex;
	align-items: center;
	gap: 3px;
	padding: 1px 6px;
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	font-size: var(--text-sm);
	color: var(--fg-muted);
}

/* The × that takes a tag or an attribute away, with Modify DataModel Instances on. */
.place-tag-remove,
.place-prop-remove {
	flex: none;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 2px;
	border: 0;
	border-radius: var(--radius-xs);
	background: none;
	color: var(--fg-faint);
	cursor: pointer;
}

.place-tag-remove:hover,
.place-prop-remove:hover {
	background: var(--bg-hover);
	color: var(--danger);
}

/* iOS zooms the page onto a field under 16px when it takes focus. */
@media (pointer: coarse) {
	.place-filter,
	.place-edit-field {
		font-size: 16px;
	}
}

.menu .item .icon {
	color: var(--fg-muted);
}

.menu .item.danger .icon {
	color: var(--danger);
}

button.tb .icon,
.toolbar .icon {
	color: currentColor;
}

/* A toolbar button that is an icon plus a label. */
button.tb.with-icon {
	display: inline-flex;
	align-items: center;
	gap: 6px;
}

/* An icon on its own, which is most of the toolbar: the label is the tooltip,
   and the row keeps its width for the two buttons that say what they do. A
   square rather than a pill, so a row of them reads as a set of tools. */
button.tb.icon-only {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 5px;
	width: 28px;
	height: 28px;
	color: var(--fg-muted);
}

button.tb.icon-only:hover:not(:disabled) {
	color: var(--fg);
}

button.tb.icon-only.on {
	background: var(--bg-active);
	color: var(--fg);
	border-color: var(--border-strong);
}

/* Path field with a resolution hint: Rojo builds an empty instance for a path
   that is not there, so an unresolved one has to be loud here. */
.map-fields .path-field {
	display: flex;
	align-items: center;
	gap: 6px;
	min-width: 0;
}

.map-fields .path-field input {
	flex: 1;
	min-width: 0;
}

.path-pending {
	display: inline-flex;
	align-items: center;
	gap: 3px;
	flex: none;
	font-size: var(--text-xs);
	color: var(--fg-muted);
	white-space: nowrap;
}

.path-missing {
	display: inline-flex;
	align-items: center;
	gap: 3px;
	flex: none;
	font-size: var(--text-xs);
	color: var(--danger);
	white-space: nowrap;
}

.map-list {
	border-top: 1px solid var(--border);
	margin: 0 12px;
	padding: 8px 0;
	display: flex;
	flex-direction: column;
	gap: 5px;
}

.map-list-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	font-size: var(--text-sm);
	text-transform: uppercase;
	letter-spacing: 0.06em;
	color: var(--fg-faint);
}

.map-list-head button {
	padding: 1px 8px;
	font-size: var(--text-sm);
	text-transform: none;
	letter-spacing: 0;
}

.map-list .summary {
	padding: 0;
}

.map-list-row {
	display: grid;
	grid-template-columns: 1fr 24px;
	gap: 4px;
}

.map-list-row input {
	cursor: text;
	min-width: 0;
	font-family: "Cascadia Mono", Consolas, monospace;
	font-size: var(--text-sm);
}

.map-list-row button {
	padding: 0;
	text-align: center;
}

/* The menu shown when a file is dragged from the tree onto the canvas. */
.drop-menu {
	width: 260px;
	max-height: none;
}

.drop-head {
	padding: 8px 10px;
	border-bottom: 1px solid var(--border);
	display: flex;
	flex-direction: column;
	gap: 3px;
}

.drop-head code {
	font: 10px/1.4 "Cascadia Mono", Consolas, monospace;
	color: var(--accent);
	word-break: break-all;
}

.drop-note {
	margin: 4px 8px 6px;
	font-size: var(--text-sm);
	line-height: 1.5;
	color: var(--fg-faint);
}

/* Extra token colours the Luau mode distinguishes, and the cream wash over
   the word completion is replacing (`.cm-completing`, in luauTheme.ts). */
:root {
	--code-global: #2c6f8f;
	--code-function: #7a5a2f;
	--code-type: #2f7a6a;
	--code-completing: rgb(226 196 130 / 0.38);
}

@media (prefers-color-scheme: dark) {
	:root {
		--code-global: #7fc4d6;
		--code-function: #d9b06c;
		--code-type: #6fc2a8;
		--code-completing: rgb(245 228 188 / 0.16);
	}
}

/* Lint squiggles, themed with the rest of the app. */
.code-body .cm-lintRange-error {
	background: none;
	border-bottom: 2px wavy var(--danger);
}

.code-body .cm-lint-marker-error {
	content: none;
}

/* A wash across any line carrying a problem, so the bad line is findable
   without reading the gutter. Kept faint: it marks the line, it does not
   compete with the squiggle for attention. */
.code-body .cm-errorLine {
	background: color-mix(in srgb, var(--danger) 11%, transparent);
	box-shadow: inset 2px 0 0 color-mix(in srgb, var(--danger) 65%, transparent);
}

/* The active line still reads as active when it is also the broken one. */
.code-body .cm-errorLine.cm-activeLine {
	background: color-mix(in srgb, var(--danger) 17%, transparent);
}

/* A reroute knot: a dot the wire passes through, with both pins stacked at its
   centre so the wire enters and leaves at the same point. */
.node.node--reroute {
	border-radius: 50%;
	background: var(--capsule-bg);
	/* An exec knot draws a hollow arrow, and the hollow has to be the colour it
	   is actually sitting on — the capsule, not a node body. */
	--pin-hollow: var(--capsule-bg);
	border: 1px solid var(--capsule-border);
	box-shadow: var(--node-shadow);
	display: grid;
	place-items: center;
	cursor: grab;
}

/* A knot is 22px and cannot afford the slot a node's pin row uses.
 *
 * At 16px plus a 3px hit margin the pin covered the knot edge to edge, and the
 * knot stopped being draggable — there was no ring left to grab, only pin. The
 * slot shrinks back here so the two gestures stay separable: the middle starts
 * a wire, the rim moves the knot. */
.node.node--reroute .pin {
	--pin-slot: 12px;
	position: absolute;
	left: 50%;
	top: 50%;
	transform: translate(-50%, -50%);
}

.node.node--reroute .pin::after {
	inset: -1px;
}

/* A knot's pins stay at its centre -- it is a bend in a wire, not a node with
   sides to hang anything off. An execution triangle is narrower than its slot,
   so centre it in the slot rather than leaving it against the left edge, where
   the rows want it. */
.node.node--reroute .pin.exec .dot {
	left: calc((var(--pin-slot) - var(--exec-width)) / 2);
}

/* A knot is 22px across and its stacked pins take 14 of them, which left a
   4px ring of node to grab -- less than that zoomed out. Selecting or moving
   a knot was a precision task, and both are as common as dragging a new wire
   off one. The ring grows outwards rather than taking from the pin, so
   starting a wire is exactly as easy as it was. Behind everything, so it
   catches only what would have hit the canvas. */
.node.node--reroute::before {
	content: "";
	position: absolute;
	inset: -6px;
	border-radius: 50%;
	z-index: -1;
}

.canvas.canvas--wiring .node.node--reroute .pin::after {
	inset: -5px;
}

/* Both of a knot's pins sit at its centre, stacked. Drawing both means an
   unwired output's hollow ring shows the wired input's fill through the middle,
   which reads as a pin that will not let go of its colour. Only the first is
   drawn; the second keeps its hit area, so a wire can still be dragged out. */
.node.node--reroute .pin + .pin .dot {
	display: none;
}

.node.node--reroute .pin-label {
	display: none;
}

.node.node--reroute.node--selected {
	border-color: var(--select);
}

/* ------------------------------------------------------------------- docs
 *
 * The Docs panel. Wider and taller than the old Help dialog because it now
 * holds the whole reference — a nav of fourteen sections and a page per node —
 * rather than five prose panes.
 */

.docs-backdrop {
	position: fixed;
	inset: 0;
	background: rgba(0, 0, 0, 0.5);
	display: grid;
	place-items: center;
	padding: 32px;
}

.docs {
	background: var(--bg-panel);
	border: 1px solid var(--border-strong);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-modal);
	width: min(1080px, 100%);
	height: min(820px, 100%);
	display: grid;
	grid-template-rows: auto 1fr;
	overflow: hidden;
}

.docs-head {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 10px 14px;
	border-bottom: 1px solid var(--border);
}

.docs-head .sub {
	color: var(--fg-faint);
	font-size: var(--text-md);
}

.docs-body {
	display: grid;
	grid-template-columns: 232px 1fr;
	min-height: 0;
}

/* ----------------------------------------------------------------- docs nav */

.docs-nav {
	border-right: 1px solid var(--border);
	padding: 10px 8px;
	overflow: auto;
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.docs-nav .search {
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	padding: 5px 8px;
	margin-bottom: 8px;
	outline: none;
	font: inherit;
	color: inherit;
}

.docs-nav .search:focus {
	border-color: var(--accent);
}

.docs-section-head {
	display: flex;
	align-items: center;
	gap: 6px;
	width: 100%;
	background: none;
	border: 0;
	text-align: left;
	padding: 5px 6px;
	border-radius: var(--radius-sm);
	cursor: pointer;
	color: var(--fg);
	font-weight: 600;
	font-size: var(--text-md);
}

.docs-section-head:hover { background: var(--bg-hover); }

/* Rotated rather than swapped for an icon, so the two states are obviously the
   same control in two positions. */
.docs-section-head .icon {
	transition: transform 0.12s ease;
	transform: rotate(-90deg);
	flex: none;
}

.docs-section-head[aria-expanded="true"] .icon { transform: none; }

@media (prefers-reduced-motion: reduce) {
	.docs-section-head .icon { transition: none; }
}

.docs-section-head .count {
	margin-left: auto;
	font-weight: 400;
	font-size: var(--text-xs);
	color: var(--fg-faint);
	font-variant-numeric: tabular-nums;
}

.docs-pages {
	display: flex;
	flex-direction: column;
	padding: 1px 0 4px 14px;
}

.docs-link,
.docs-hit {
	display: flex;
	align-items: baseline;
	gap: 6px;
	width: 100%;
	background: none;
	border: 0;
	text-align: left;
	padding: 4px 8px;
	border-radius: var(--radius-sm);
	cursor: pointer;
	color: var(--fg-muted);
	font-size: var(--text-md);
}

.docs-link:hover,
.docs-hit:hover { background: var(--bg-hover); color: var(--fg); }

.docs-link.on,
.docs-hit.on {
	background: color-mix(in srgb, var(--accent) 20%, transparent);
	color: var(--fg);
}

.docs-hit { flex-direction: column; gap: 0; align-items: stretch; }
.docs-hit .title { color: inherit; }
.docs-hit .where { font-size: var(--text-xs); color: var(--fg-faint); }

.docs-results { display: flex; flex-direction: column; gap: 1px; }
.docs-results .empty { padding: 12px 8px; color: var(--fg-faint); font-size: var(--text-md); }

/* ------------------------------------------------------- docs search palette */

/* Over the page rather than beside it: the palette is what you reach for when
   you do not know which page you want, and the answer is worth the width. */
/* Shared by the two pickers that cover the page: the documentation's and the
   node one. The docs palette named it first; the class is the shape. */
.picker-backdrop,
.docs-palette-backdrop {
	position: fixed;
	inset: 0;
	background: rgba(0, 0, 0, 0.45);
	display: flex;
	justify-content: center;
	/* High rather than centred: the list grows downwards, so a centred panel
	   would walk up the screen as you type. */
	align-items: flex-start;
	padding: 10vh 16px 16px;
}

.docs-palette {
	width: min(720px, 100%);
	max-height: 70vh;
	display: flex;
	flex-direction: column;
	background: var(--bg-panel);
	border: 1px solid var(--border-strong);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-modal);
	overflow: hidden;
}

.docs-palette-field {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 10px 12px;
	border-bottom: 1px solid var(--border);
}

.docs-palette-field input {
	flex: 1;
	min-width: 0;
	background: none;
	border: 0;
	color: var(--fg);
	font-size: var(--text-xl);
	outline: none;
}

.docs-palette-list {
	overflow: auto;
	padding: 6px;
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.docs-palette-head {
	font-size: var(--text-xs);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: var(--fg-faint);
	padding: 6px 8px 4px;
}

.docs-palette-list .empty {
	padding: 12px 8px;
	color: var(--fg-faint);
	font-size: var(--text-md);
}

.docs-palette-hit {
	display: flex;
	align-items: flex-start;
	gap: 10px;
	width: 100%;
	background: none;
	border: 0;
	border-radius: var(--radius-sm);
	padding: 8px;
	text-align: left;
	cursor: pointer;
	color: var(--fg-muted);
}

.docs-palette-hit.on {
	background: color-mix(in srgb, var(--accent) 20%, transparent);
	color: var(--fg);
}

/* Article or Node, in a fixed column so the titles line up down the list, and
   coloured so the two kinds separate before you have read either. Yellow is a
   node, blue is an article -- tinted rather than filled, because the row's own
   highlight is the accent and two solid blocks would compete for it. */
.docs-palette-hit .kind {
	flex: none;
	width: 52px;
	font-size: var(--text-xs);
	text-transform: uppercase;
	letter-spacing: 0.06em;
	color: var(--fg-faint);
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	padding: 1px 0;
	text-align: center;
	margin-top: 2px;
}

.docs-palette-hit .kind-node {
	color: var(--warning);
	border-color: color-mix(in srgb, var(--warning) 55%, transparent);
	background: color-mix(in srgb, var(--warning) 12%, transparent);
}

.docs-palette-hit .kind-article {
	color: var(--accent);
	border-color: color-mix(in srgb, var(--accent) 55%, transparent);
	background: color-mix(in srgb, var(--accent) 12%, transparent);
}

/* A heading each, so "Best match" and "Related" read as two lists rather than
   as one list with a line in it. */
.docs-palette-section {
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.docs-palette-section + .docs-palette-section {
	margin-top: 6px;
	border-top: 1px solid var(--border);
	padding-top: 4px;
}

.docs-palette-hit .body {
	display: flex;
	flex-direction: column;
	gap: 1px;
	min-width: 0;
}

.docs-palette-hit .title { font-size: var(--text-lg); color: var(--fg); }
.docs-palette-hit .where { font-size: var(--text-xs); color: var(--fg-faint); }

/* One line of it. The summary is there to tell two similar titles apart, and a
   paragraph per row would make the list unscannable. */
.docs-palette-hit .summary {
	font-size: var(--text-sm);
	color: var(--fg-muted);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.docs-palette-foot {
	display: flex;
	gap: 14px;
	padding: 8px 12px;
	border-top: 1px solid var(--border);
	font-size: var(--text-xs);
	color: var(--fg-faint);
}

.docs-palette-foot kbd {
	font-family: inherit;
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	padding: 0 4px;
	margin-right: 3px;
}

.docs .badge {
	font-size: var(--text-xs);
	text-transform: uppercase;
	letter-spacing: 0.06em;
	color: var(--fg-faint);
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	padding: 0 4px;
	white-space: nowrap;
}

/* ------------------------------------------------------------- docs content */

/* The last line of a page has to be able to reach the middle of the screen.
   With a bottom padding the height of the text, the end of a page sits at the
   very bottom of the window when you have scrolled as far as you can -- which
   is where a taskbar is, and is the worst place to read the paragraph you
   scrolled down for. A viewport-relative pad gives the same room on a laptop
   and a large display, and is capped so it does not become a page of nothing. */
.docs-content {
	overflow: auto;
	padding: 22px 30px 60px;
	/* Long-form reading, so the measure is capped rather than filling the pane. */
	max-width: 74ch;
}

.docs-content h1:where(:not(.docs-layout-screen *)) {
	font-size: 22px;
	margin: 0 0 4px;
	display: flex;
	align-items: center;
	gap: 8px;
}

.docs-content .summary:where(:not(.docs-layout-screen *)) {
	margin: 0 0 22px;
	color: var(--fg-faint);
}

.docs-content h2:where(:not(.docs-layout-screen *)) {
	font-size: 15px;
	margin: 28px 0 8px;
	padding-top: 14px;
	border-top: 1px solid var(--border);
}

.docs-content h3:where(:not(.docs-layout-screen *)) { font-size: 13px; margin: 20px 0 6px; }
.docs-content p:where(:not(.docs-layout-screen *)) { margin: 0 0 12px; line-height: 1.6; }
.docs-content ul:where(:not(.docs-layout-screen *)),
.docs-content ol:where(:not(.docs-layout-screen *)) { margin: 0 0 12px; padding-left: 20px; line-height: 1.6; }
.docs-content li:where(:not(.docs-layout-screen *)) { margin-bottom: 4px; }
.docs-content li:where(:not(.docs-layout-screen *))::marker { color: var(--fg-faint); }

.docs-content code:where(:not(.docs-layout-screen *)) {
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	padding: 0 4px;
	font-size: 11px;
}

.docs-content pre:where(:not(.docs-layout-screen *)) {
	background: var(--bg-canvas);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	padding: 10px 12px;
	overflow-x: auto;
	font-size: 11.5px;
	line-height: 1.55;
	margin: 0 0 14px;
}

.docs-table { overflow-x: auto; margin: 0 0 14px; }

.docs-content table:where(:not(.docs-layout-screen *)) {
	border-collapse: collapse;
	width: 100%;
	font-size: 12px;
}

.docs-content th:where(:not(.docs-layout-screen *)) {
	text-align: left;
	font-size: 10px;
	text-transform: uppercase;
	letter-spacing: 0.06em;
	color: var(--fg-faint);
	padding: 0 10px 5px 0;
	border-bottom: 1px solid var(--border);
	white-space: nowrap;
}

.docs-content td:where(:not(.docs-layout-screen *)) {
	padding: 7px 10px 7px 0;
	border-bottom: 1px solid var(--border);
	vertical-align: top;
	line-height: 1.5;
}

.docs-content td.mono:where(:not(.docs-layout-screen *)) { font-size: 11px; color: var(--fg-muted); white-space: nowrap; }
.docs-content td em:where(:not(.docs-layout-screen *)) { color: var(--fg-faint); font-style: normal; }

.docs-content table.pins td.swatch-cell:where(:not(.docs-layout-screen *)) { width: 14px; padding-right: 6px; }

.docs-swatch {
	display: inline-block;
	width: 9px;
	height: 9px;
	border-radius: 50%;
}

/* Execution pins keep their triangle here too, so the table matches the canvas. */
.docs-swatch.exec {
	border-radius: 0;
	width: 8px;
	clip-path: polygon(0 0, 100% 50%, 0 100%);
}

/* After Docusaurus's admonitions: the kind is a colour, a word and an icon,
   so the reader knows why the box is there before reading it. The colour is
   the theme's own, so a note takes whichever theme the reader has chosen. */
.docs-note {
	--note: var(--border-strong);
	border-left: 4px solid var(--note);
	background: color-mix(in srgb, var(--note) 9%, var(--bg-input));
	border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
	padding: 9px 12px;
	margin: 0 0 14px;
	line-height: 1.55;
}

.docs-note.note-info { --note: var(--accent); }
.docs-note.note-good { --note: var(--ok); }
.docs-note.note-warn { --note: var(--warning); }
.docs-note.note-danger { --note: var(--danger); }

.docs-note-head {
	display: flex;
	align-items: center;
	gap: 6px;
	margin: 0 0 4px;
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--note);
}

.docs-note-body > :last-child {
	margin-bottom: 0;
}

/* A list inside a note: several separate claims rather than one thought.
   Tightened against the paragraph above it, so the sentence introducing them
   and the points themselves read as one block rather than two. */
.docs-note ul {
	margin: 6px 0 0;
	padding-left: 18px;
}

.docs-note li {
	margin: 3px 0;
}

/* The docs as their own window, at /docs. Fills the viewport rather than
   floating over a canvas, because there is no canvas behind it. */
.docs-page {
	display: grid;
	grid-template-rows: auto 1fr;
	height: 100%;
	background: var(--bg-app);
}

/* Centred rather than baseline-aligned. This bar holds a mark, a version, a
 * warning chip and a link, and only two of those are text — an SVG's baseline
 * is its bottom edge, so `baseline` dropped the mark to the floor of the line
 * box and hung everything else off that. */
/* A floating bar, as the editor's clusters are: glass, inset from the
   window's edges, its buttons bare until the pointer is on one. In the page's
   flow rather than over it, so the published site and the docs window lay
   their pages out the same way under it. */
.docs-page-head {
	display: flex;
	align-items: center;
	gap: 4px;
	margin: var(--edge) var(--edge) 0;
	padding: 3px 6px 3px 3px;
	background: var(--glass);
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-raised);
	backdrop-filter: blur(14px) saturate(1.2);
	-webkit-backdrop-filter: blur(14px) saturate(1.2);
	position: relative;
	z-index: 5;
}

.docs-page-head button.tb,
.docs-page-head a.tb {
	min-height: var(--hit);
	background: transparent;
	border-color: transparent;
}

.docs-page-head button.tb:hover:not(:disabled),
.docs-page-head a.tb:hover {
	background: var(--bg-hover);
	border-color: transparent;
}

.docs-page-head button.tb.on {
	background: color-mix(in srgb, var(--accent) 20%, transparent);
	color: var(--accent);
}

.docs-page-head .logo {
	font-weight: 600;
	letter-spacing: 0.04em;
	display: inline-flex;
	align-items: center;
	gap: 6px;
}

/* Says this is the browser build. Compound with `.version` so it inherits the
   size and numerals rather than restating them, and reads as a chip beside the
   number rather than as a second number.

   Every surface of that build carries it and every link into it is labelled
   with it -- see `src/app/previewBuild.tsx`. So the selector is every header
   that has a mark, plus the documentation site's header link, rather than the
   editor's toolbar alone: scoped to `.toolbar`, Node Design grew the component
   and rendered it invisible. */
.toolbar .logo .version.preview-chip,
.docs-page-head .logo .version.preview-chip,
.docs-page-head a.tb .version.preview-chip {
	/* The colour is the variant's, not this rule's. These three selectors are
	   scoped to a context and so outspecify any plain `.preview-chip.canary`,
	   which is how the canary chip first shipped painted accent blue while
	   reading CANARY. Reading a custom property moves the decision to the
	   element, where both variants set it at their own specificity and the more
	   specific one wins -- rather than to whichever selector happens to have
	   collected more classes. */
	color: var(--chip-ink);
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	padding: 0 4px;
	margin-left: 2px;
	text-transform: uppercase;
	font-size: var(--text-xs);
	letter-spacing: 0.04em;
}

/* In the header's link it sits inside a button-shaped anchor, which sets its
   own colour and its own gap. The chip keeps the accent and takes the gap from
   the flex row rather than a margin of its own. */
.docs-page-head a.tb .version.preview-chip {
	margin-left: 0;
	font-weight: 400;
	line-height: 1.6;
}

/* Which ink a chip is drawn in. Set on the element, so the variant decides and
   no amount of context around it can overrule the claim it is making.

   The shape is the shape and only the colour is the claim: the canary wears the
   same chip in the warning colour -- the yellow the Lune `experimental` flag
   already uses, so the stylesheet means one thing by it. */
.version.preview-chip {
	--chip-ink: var(--accent);
}

/* The same claim as a colour, on the logo every window wears: see
   `MarkedLogo`. Plain for an installed build, blue for the web app, yellow for
   the canary. The class is doubled so no context rule can outrank it -- the
   projects panel had one painting its mark the accent, which turned the
   canary's mark blue there and nowhere else. */
.logo-mark.logo-mark.mark-preview {
	color: var(--accent);
}

.logo-mark.logo-mark.mark-canary {
	color: var(--warning);
}

.version.preview-chip.canary {
	--chip-ink: var(--warning);
	border-color: color-mix(in srgb, var(--warning) 55%, transparent);
}

/* ------------------------------------------------------- the canary banner */

/* Across the top of every canary window, above the toolbar.
 *
 * The chip says which build you are on; this says what that means for you. On a
 * site anyone can reach, the reader who most needs telling is the one who did
 * not choose to be there -- which is also why it leads somewhere. A warning
 * with no way out is a warning that gets dismissed rather than acted on. */
.canary-banner {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 6px 14px;
	font-size: var(--text-md);
	line-height: 1.5;
	color: var(--fg);
	background: color-mix(in srgb, var(--warning) 14%, var(--bg-panel));
	border-bottom: 1px solid color-mix(in srgb, var(--warning) 45%, transparent);
	/* It is chrome, not content: a long sentence must not push the way out off
	   the end of a narrow window. */
	flex-wrap: wrap;
}

.canary-banner-mark {
	text-transform: uppercase;
	letter-spacing: 0.06em;
	font-size: var(--text-xs);
	font-weight: 600;
	color: var(--warning);
	border: 1px solid color-mix(in srgb, var(--warning) 55%, transparent);
	border-radius: var(--radius-xs);
	padding: 1px 6px;
	white-space: nowrap;
}

.canary-banner-text {
	flex: 1;
	min-width: 12rem;
}

.canary-banner-out {
	color: var(--accent);
	white-space: nowrap;
}

/* The copy at the site's old address: news rather than a warning, so the
   accent rather than the warning colour. */
.canary-banner.canary-banner-backup {
	background: color-mix(in srgb, var(--accent) 12%, var(--bg-panel));
	border-bottom-color: color-mix(in srgb, var(--accent) 45%, transparent);
}

.canary-banner-backup .canary-banner-mark {
	color: var(--accent);
	border-color: color-mix(in srgb, var(--accent) 55%, transparent);
}

.docs-page-head .logo .version {
	font-weight: 400;
	font-size: var(--text-xs);
	letter-spacing: 0;
	color: var(--fg-faint);
	font-variant-numeric: tabular-nums;
}

.docs-page-head .sub { color: var(--fg-faint); font-size: var(--text-md); }

/* Pushes the controls to the right edge. The editor's own docs window does the
   same thing with an inline style on a spacer span; the published site has no
   React to write one, so it gets a class. */
.docs-page-head .grow { flex: 1; }

.docs-page-head .warn {
	font-size: var(--text-sm);
	color: var(--warning);
	border: 1px solid var(--warning);
	border-radius: var(--radius-xs);
	padding: 0 6px;
}

/* A link in the header is drawn as the button it acts as — Docs, Open Editor —
   so a row of them reads as one set of controls rather than buttons beside
   underlined text. The introduction panel's footer is the same row of controls
   in a different frame, so it is named here rather than given a second set of
   rules that would have to be kept level with these. */
.docs-page-head a.tb,
.intro-foot a.tb {
	text-decoration: none;
	color: inherit;
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	padding: 4px 9px;
}

/* One height for every control in the header, links and buttons alike. A link
   given its own font size came out two pixels shorter than Settings beside it,
   and the row stopped lining up. */
.docs-page-head a.tb,
.docs-page-head button.tb,
.intro-foot a.tb,
.intro-foot button.tb {
	height: 29px;
	box-sizing: border-box;
}

.docs-page-head a.tb:hover,
.intro-foot a.tb:hover {
	background: var(--bg-hover);
}

/* An icon is a block, so an icon and a word in one button stack by default. */
.docs-page-head button.tb,
.docs-page-head a.tb,
.intro-foot button.tb,
.intro-foot a.tb {
	display: inline-flex;
	align-items: center;
	gap: 5px;
}

.docs-page-head a.tb.icon-only {
	justify-content: center;
	padding: 4px;
	width: 29px;
	color: var(--fg-muted);
}

.docs-page .docs-body {
	min-height: 0;
	grid-template-columns: 260px 1fr;
}

/* Full window, so the reading column can be centred rather than left-hugging. */
.docs-page .docs-content {
	max-width: none;
	padding: 26px 40px 80px;
}

.docs-page .docs-content > *:where(:not(.docs-layout-screen *)) {
	max-width: 78ch;
}

/* A capsule getter redeclares border and box-shadow further down this file
   than .node.node--selected, so it would win and the selection ring would
   vanish on every Get Variable and Get Function node. Restated at the right
   specificity. */
.node.node--capsule.node--selected {
	border-color: var(--select);
	box-shadow: 0 0 0 2px color-mix(in srgb, var(--select) 55%, transparent), var(--node-shadow);
}

.node.node--capsule.node--selected.node--anchor,
.node.node--reroute.node--selected.node--anchor {
	box-shadow: 0 0 0 4px var(--select), var(--node-shadow);
}

/* ----------------------------------------------------- docs, second pass
 *
 * Closer to Roblox's Creator Hub and other engine documentation: three columns —
 * nav, a capped reading measure, and an outline of the page — with code blocks
 * that carry their language and a copy button rather than being bare <pre>.
 */

.docs-page .docs-body {
	grid-template-columns: 268px minmax(0, 1fr) 210px;
}

/* Below that width the outline is the first thing to go: it is a convenience,
   and the reading column is not. */
@media (max-width: 1100px) {
	.docs-page .docs-body { grid-template-columns: 268px minmax(0, 1fr); }
	/* Scoped, so it outranks `.docs-toc` further down. Unscoped, the later
	   rule won: the outline stayed, wrapped into a second grid row under the
	   nav, and halved the height of everything on a tablet or a phone. */
	.docs-page .docs-toc { display: none; }
}

.docs-group + .docs-group { margin-top: 14px; }

.docs-group-head {
	font-size: var(--text-xs);
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.09em;
	color: var(--fg-faint);
	padding: 6px 6px 4px;
	border-bottom: 1px solid var(--border);
	margin-bottom: 4px;
}

/* --------------------------------------------------------------- outline */

.docs-toc {
	border-left: 1px solid var(--border);
	padding: 26px 14px 40px;
	overflow: auto;
	display: flex;
	flex-direction: column;
	gap: 2px;
	align-content: start;
}

.docs-toc-head {
	font-size: var(--text-xs);
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.09em;
	color: var(--fg-faint);
	margin-bottom: 6px;
}

.docs-toc-link {
	color: var(--fg-muted);
	text-decoration: none;
	font-size: var(--text-sm);
	line-height: 1.4;
	padding: 3px 6px;
	border-left: 2px solid transparent;
	border-radius: 0 var(--radius-xs) var(--radius-xs) 0;
}

.docs-toc-link:hover {
	color: var(--fg);
	border-left-color: var(--accent);
	background: var(--bg-hover);
}

/* ------------------------------------------------------------ code blocks */

/* ------------------------------------------------- node previews in docs */

/* A node picture sits on the canvas colour rather than the page colour, so it
   reads as "this is what you will see over there" instead of as an illustration
   drawn for the page. The grid is the canvas's own fine grid at rest, which is
   what puts the node in a place rather than on a background. */
.docs-preview {
	margin: 0 0 18px;
	padding: 18px 16px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background-color: var(--bg-canvas);
}

/* Wraps, so two nodes sit side by side on a wide page and stack on a narrow
   one. Aligned to the top: comparing two nodes means comparing their headers,
   and centring a tall one against a short one puts those on different lines. */
.docs-preview .row {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	gap: 20px;
}

.docs-preview .node-preview-frame {
	filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.18));
}

.docs-preview svg.node-preview {
	display: block;
	/* The canvas font, so a title in a preview is set the way the title on the
	   node is. `font-family: inherit` inside the SVG picks this up. */
	font-family: inherit;
}

.docs-preview figcaption {
	margin-top: 14px;
	font-size: var(--text-md);
	line-height: 1.5;
	color: var(--fg-muted);
}

.docs-code {
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	overflow: hidden;
	margin: 0 0 16px;
	background: var(--bg-canvas);
}

.docs-code-head {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 4px 8px 4px 12px;
	background: var(--bg-panel);
	border-bottom: 1px solid var(--border);
}

.docs-code-head .lang {
	font-size: var(--text-xs);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: var(--fg-faint);
}

.docs-code-head .copy {
	margin-left: auto;
	background: none;
	border: 1px solid transparent;
	border-radius: var(--radius-sm);
	padding: 2px 8px;
	font-size: var(--text-sm);
	color: var(--fg-muted);
	cursor: pointer;
}

.docs-code-head .copy:hover {
	background: var(--bg-hover);
	border-color: var(--border);
	color: var(--fg);
}

.docs-code pre {
	margin: 0;
	border: 0;
	border-radius: 0;
	background: none;
	padding: 12px 14px;
	/* Selectable and copyable, never editable: nothing here is wired to
	   anything, so an edit would look like it did something and would not. */
	user-select: text;
}

/* --------------------------------------------------- content, restyled */

.docs-page .docs-content {
	padding: 30px 44px 90px;
}

.docs-page .docs-content h1:where(:not(.docs-layout-screen *)) {
	font-size: 30px;
	letter-spacing: -0.015em;
	margin-bottom: 6px;
}

/* The rule belongs to the header block, which spans the article; the summary
   inside it keeps a prose measure. Putting the rule on the summary made it stop
   wherever the text did, which looked like a broken header rather than a
   deliberate one. */
.docs-page .docs-title {
	border-bottom: 1px solid var(--border);
	padding-bottom: 18px;
	margin-bottom: 30px;
}

.docs-page .docs-content .summary:where(:not(.docs-layout-screen *)) {
	font-size: 15px;
	line-height: 1.55;
	max-width: none;
	margin: 0;
	padding: 0;
	border: 0;
	color: var(--fg-muted);
}

.docs-page .docs-content h2:where(:not(.docs-layout-screen *)) {
	font-size: 19px;
	letter-spacing: -0.01em;
	margin: 36px 0 10px;
	padding-top: 0;
	border-top: 0;
	scroll-margin-top: 20px;
}

.docs-page .docs-content h3:where(:not(.docs-layout-screen *)) {
	font-size: 14px;
	margin: 24px 0 8px;
	color: var(--fg);
}

.docs-page .docs-content p:where(:not(.docs-layout-screen *)),
.docs-page .docs-content li:where(:not(.docs-layout-screen *)) { font-size: 13.5px; line-height: 1.68; }

/* Creator Hub rules its tables top and bottom and leaves the inner lines
   light, which is what keeps a long mapping table readable. */
.docs-page .docs-content table:where(:not(.docs-layout-screen *)) {
	font-size: 13px;
	border-top: 1px solid var(--border-strong);
	border-bottom: 1px solid var(--border-strong);
}

.docs-page .docs-content th:where(:not(.docs-layout-screen *)) {
	padding: 9px 14px 9px 0;
	border-bottom: 1px solid var(--border-strong);
}

.docs-page .docs-content td:where(:not(.docs-layout-screen *)) { padding: 10px 14px 10px 0; }
.docs-page .docs-content tbody tr:last-child td:where(:not(.docs-layout-screen *)) { border-bottom: 0; }

.docs-page .docs-note {
	border-radius: var(--radius-sm);
	padding: 12px 16px;
	font-size: 13.5px;
	margin-bottom: 16px;
}

/* Two, matching the generated files themselves — the default eight turns a
   nested loop body into a horizontal scroll. */
.docs-code pre {
	tab-size: 2;
	-moz-tab-size: 2;
}

/* When a page has no outline the column still holds its place, so the reading
   measure does not jump between pages. It just has nothing in it. */
.docs-toc:empty { border-left-color: transparent; }

/* ------------------------------------------------- docs: code and pin lists */

/* Luau, wherever it is shown as text.
 *
 * `highlightLuau` emits these classes from anywhere; what makes a block
 * coloured is being inside one of the containers listed here. That caught the
 * selection preview out on the day it was written -- the highlighter ran, the
 * spans carried the right classes, and every one of them inherited body text,
 * because the palette was scoped to the docs and nothing said so.
 *
 * So the container list is the thing to extend when a fourth place shows Luau,
 * and it is `:is()` rather than eight comma-separated pairs so that adding one
 * is a single edit rather than eight.
 *
 * The variables are the ones the Code Block editor's HighlightStyle uses, so a
 * snippet in the docs, the same code in the editor and the same code in the
 * preview are one opinion about Luau rather than four. */
:is(.docs-code, .preview-code, .landing-code, .luau-hover code, .luau-signature code, .code-view, .node-face) .tok-keyword { color: var(--code-keyword); }
:is(.docs-code, .preview-code, .landing-code, .luau-hover code, .luau-signature code, .code-view, .node-face) .tok-string { color: var(--code-string); }
:is(.docs-code, .preview-code, .landing-code, .luau-hover code, .luau-signature code, .code-view, .node-face) .tok-number { color: var(--code-number); }
:is(.docs-code, .preview-code, .landing-code, .luau-hover code, .luau-signature code, .code-view, .node-face) .tok-comment { color: var(--code-comment); font-style: italic; }
:is(.docs-code, .preview-code, .landing-code, .luau-hover code, .luau-signature code, .code-view, .node-face) .tok-meta { color: var(--code-type); font-style: italic; }
:is(.docs-code, .preview-code, .landing-code, .luau-hover code, .luau-signature code, .code-view, .node-face) .tok-operator { color: var(--code-operator); }
:is(.docs-code, .preview-code, .landing-code, .luau-hover code, .luau-signature code, .code-view, .node-face) .tok-global { color: var(--code-global); }
:is(.docs-code, .preview-code, .landing-code, .luau-hover code, .luau-signature code, .code-view, .node-face) .tok-function { color: var(--code-function); }
:is(.docs-code, .preview-code, .landing-code, .luau-hover code, .luau-signature code, .code-view, .node-face) .tok-type { color: var(--code-type); }
:is(.docs-code, .preview-code, .landing-code, .luau-hover code, .luau-signature code, .code-view, .node-face) .tok-property { color: var(--code-property); }
:is(.docs-code, .preview-code, .landing-code, .luau-hover code, .luau-signature code, .code-view, .node-face) .tok-bracket,
:is(.docs-code, .preview-code, .landing-code, .luau-hover code, .luau-signature code, .code-view, .node-face) .tok-punctuation { color: var(--fg-muted); }

/* Pin lists, in the shape Roblox's own reference uses for properties: one
   bordered row per entry, the signature scannable down the left, detail
   underneath rather than in a column that is empty on most rows. */
.docs-pins {
	list-style: none;
	margin: 0 0 18px;
	padding: 0;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	overflow: hidden;
}

.docs-pins li {
	padding: 10px 14px;
	margin: 0;
	border-bottom: 1px solid var(--border);
}

.docs-pins li:last-child { border-bottom: 0; }
.docs-pins li:nth-child(odd) { background: color-mix(in srgb, var(--fg) 2.5%, transparent); }

.docs-pins .sig {
	display: flex;
	align-items: baseline;
	gap: 7px;
	flex-wrap: wrap;
	font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
	font-size: var(--text-md);
}

.docs-pins .sig .docs-swatch { align-self: center; flex: none; }
.docs-pins .name { color: var(--fg); font-weight: 600; }
.docs-pins .sep { color: var(--fg-faint); }

/* Underlined the way Creator Hub underlines a linked type. Nothing to link to
   yet, so it is not an anchor — the underline is the affordance the reader
   already reads as "this is the type". */
.docs-pins .type {
	color: var(--code-global);
	text-decoration: underline;
	text-decoration-color: var(--border-strong);
	text-underline-offset: 3px;
}

.docs-pins .def { color: var(--fg-faint); }
.docs-pins .def code { border: 0; background: none; padding: 0; color: var(--fg-muted); }

.docs-pins .badge {
	font-family: "Segoe UI", system-ui, sans-serif;
	font-size: var(--text-xs);
	text-transform: uppercase;
	letter-spacing: 0.06em;
	color: var(--fg-faint);
	background: var(--bg-hover);
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	padding: 1px 5px;
}

.docs-pins .badge.warn { color: var(--warning); border-color: var(--warning); }

.docs-pins .detail {
	margin-top: 5px;
	font-size: var(--text-md);
	line-height: 1.55;
	color: var(--fg-muted);
}

/* ------------------------------------------------ docs: page measure
 *
 * Engine documentation, Roblox's included, centres the article in whatever
 * room the window gives it rather than hugging the nav, and lets it grow well
 * past a prose measure — those pages are mostly tables and signatures, and so are
 * ours. The nav and the outline stay pinned to the edges; only the middle
 * column centres.
 */
.docs-page .docs-content {
	display: flex;
	justify-content: center;
	/* So a figure can measure the room the page has, not only its column. */
	container-type: inline-size;
}

/* A picture at more than 100% grows past the article column, centred on it, up
   to the width of the pane it scrolls in. `--preview-scale` is set on the
   figure by the Docs window; the static site never sets it. */
.docs-preview.breakout {
	--frame: min(calc(100% * var(--preview-scale)), calc(100cqw - 48px));
	box-sizing: border-box;
	width: var(--frame);
	margin-left: calc((100% - var(--frame)) / 2);
}

/* A node picture never outgrows its frame: at a size the frame cannot hold, it
   shrinks to fit rather than spilling past the border. A graph's own SVG is
   left alone — the pan-and-zoom viewer positions that one. */
.docs-preview .node-preview-frame {
	min-width: 0;
	max-width: 100%;
}

.docs-preview svg.node-preview:not(.graph-preview) {
	max-width: 100%;
	height: auto;
}

.docs-page .docs-content > *:where(:not(.docs-layout-screen *)) {
	max-width: none;
}

/* One wrapper holds the measure, so every block inside shares an edge and the
   pin lists and tables line up with the prose above them. */
/* The last line of a page has to be able to reach the middle of the screen.
   Without this, scrolling as far as it goes leaves the end of a page at the very
   bottom of the window -- which is where a taskbar is, and the worst place to
   read the paragraph you scrolled down for.

   An empty box rather than bottom padding, on either the article or the scroll
   container. Both were tried and measured: 510px of padding between them bought
   a single pixel of anything to scroll past. A box with a height cannot be
   argued with. */
.docs-tail {
	height: min(45vh, 420px);
	flex: none;
}

/* What a confirmation is about, listed under its message. Capped, so a graph
   with forty of something scrolls rather than pushing the buttons off screen. */
/* What an overwrite changes: the file's lines going, the graph's arriving,
   and the unchanged runs between them folded to a count. */
.dialog-diff {
	margin: 0 0 14px;
	max-height: 320px;
	overflow: auto;
	padding: 6px 0;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--bg-input);
	font-family: var(--mono, ui-monospace, monospace);
	font-size: var(--text-xs);
	line-height: 1.5;
	white-space: pre;
}

.dialog-diff > span {
	display: block;
	padding: 0 10px;
}

.dialog-diff .diff-add {
	background: color-mix(in srgb, var(--ok) 14%, transparent);
	color: var(--fg);
}

.dialog-diff .diff-del {
	background: color-mix(in srgb, var(--danger) 14%, transparent);
	color: var(--fg-muted);
}

.dialog-diff .diff-same {
	color: var(--fg-faint);
}

.dialog-diff .diff-gap {
	color: var(--fg-faint);
	font-style: italic;
}

/* Sentences under a confirmation's message, where items would be names. */
.dialog-points {
	margin: 0 0 14px;
	padding-left: 18px;
	display: grid;
	gap: 6px;
	font-size: var(--text-md);
	line-height: 1.45;
}

.dialog-list {
	margin: 0 0 14px;
	padding: 6px 10px 6px 26px;
	max-height: 180px;
	overflow: auto;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--bg-input);
	font-size: var(--text-md);
	line-height: 1.6;
}

/* What the open graph compiles for, beside the button that compiles it.
   Lune takes the warning colour, because it is experimental and it is where
   Roblox nodes fail. */
select.doc-target.lune {
	color: var(--warning);
	border-color: color-mix(in srgb, var(--warning) 60%, transparent);
}

/* --------------------------------------------------------- editing a page */

/* The pencil sits with the title, where a page's own controls are looked for. */
.docs-title h1 .docs-edit {
	margin-left: 10px;
	vertical-align: middle;
}

.page-editor {
	display: flex;
	flex-direction: column;
	gap: 6px;
}

/* Stays in reach while you work down a long page. */
.page-editor-bar {
	position: sticky;
	top: 0;
	z-index: 2;
	display: flex;
	align-items: center;
	gap: 8px;
	flex-wrap: wrap;
	padding: 8px 10px;
	margin-bottom: 8px;
	background: var(--bg-panel);
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
}

.page-editor .hint,
.page-editor-said {
	font-size: var(--text-sm);
	line-height: 1.6;
	color: var(--fg-faint);
	margin: 0;
}

.page-editor-said {
	color: var(--ok);
}

/* Every block keeps its place; only the one being changed becomes a field. */
.page-block {
	position: relative;
	border: 1px dashed transparent;
	border-radius: var(--radius-sm);
	padding: 2px 8px;
}

.page-block:hover {
	border-color: var(--border);
}

.page-block-tools {
	position: absolute;
	top: 2px;
	right: 4px;
	display: none;
	gap: 4px;
	z-index: 1;
}

.page-block:hover .page-block-tools {
	display: flex;
}

.page-block-tools button.tb {
	padding: 2px 6px;
	line-height: 1.2;
}

.page-block-add {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
	padding: 6px 0;
}

.page-block-add.end {
	border-top: 1px solid var(--border);
	margin-top: 10px;
}

.page-block-shown {
	cursor: text;
}

.block-stack {
	display: flex;
	flex-direction: column;
	gap: 6px;
	padding: 6px 0;
}

.block-row {
	display: flex;
	gap: 6px;
	padding: 6px 0;
}

.block-row > input,
.block-row > select {
	flex: 1;
	min-width: 0;
}

.block-field {
	width: 100%;
	padding: 8px 10px;
	background: var(--bg-input);
	color: var(--fg);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	font: inherit;
	line-height: 1.6;
	resize: vertical;
}

.block-field.mono {
	font-family: "Cascadia Mono", Consolas, monospace;
	font-size: var(--text-md);
}

.block-matches,
.block-chips,
.block-preview-row {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
	align-items: center;
}

.page-editor-source {
	max-height: 320px;
	overflow: auto;
	padding: 10px 12px;
	background: var(--bg-canvas);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	font-family: "Cascadia Mono", Consolas, monospace;
	font-size: var(--text-sm);
	line-height: 1.5;
	white-space: pre;
}

/* ------------------------------------------------------------ node designer */

/* A form over a node definition, with the node drawn beside it. Its own page,
   like the docs: it is a thing you sit down to do rather than a panel you keep
   open while wiring. */
.designer {
	display: flex;
	flex-direction: column;
	height: 100vh;
	background: var(--bg-app);
	color: var(--fg);
}

.designer-body {
	display: grid;
	grid-template-columns: minmax(0, 1fr) 360px;
	gap: 18px;
	padding: 18px;
	overflow: auto;
	align-items: start;
}

.designer-form,
.designer-side {
	display: flex;
	flex-direction: column;
	gap: 10px;
	min-width: 0;
}

.designer-side {
	position: sticky;
	top: 0;
}

.designer h2 {
	font-size: var(--text-sm);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: var(--fg-faint);
	margin: 8px 0 0;
}

.designer .field {
	display: grid;
	grid-template-columns: 96px 1fr;
	align-items: center;
	gap: 8px;
}

.designer .field > span {
	font-size: var(--text-sm);
	color: var(--fg-faint);
}

.designer .field textarea {
	font-family: "Cascadia Mono", Consolas, monospace;
	resize: vertical;
}

.designer-pins {
	display: flex;
	flex-direction: column;
	gap: 8px;
	border-top: 1px solid var(--border);
	padding-top: 10px;
}

.designer-pin {
	display: flex;
	flex-direction: column;
	gap: 5px;
	border-left: 2px solid var(--border);
	padding-left: 8px;
}

.designer-row {
	display: flex;
	gap: 6px;
}

.designer-row > * {
	min-width: 0;
	flex: 1;
}

.designer-row > button.tb {
	flex: none;
}

/* The node as the canvas will draw it, from the documentation's own generator. */
.designer-preview {
	display: flex;
	justify-content: center;
	padding: 14px;
	background: var(--bg-canvas);
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
}

.designer-problems {
	margin: 0;
	padding-left: 18px;
	color: var(--danger);
	font-size: var(--text-md);
	line-height: 1.6;
}

.designer-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
}

.designer-said {
	margin: 0;
	color: var(--ok);
	font-size: var(--text-md);
}

.designer-failed {
	margin: 0;
	color: var(--danger);
	font-size: var(--text-md);
}

.designer .hint {
	margin: 0;
	font-size: var(--text-sm);
	line-height: 1.6;
	color: var(--fg-faint);
}

/* ------------------------------------------------------------ pack browser */

/* A toast in the corner, not a bar under the header: a bar took a row from the
   pack view and the node editor every time something was saved. */
.designer-notice {
	position: fixed;
	right: 18px;
	bottom: 18px;
	z-index: 80;
	display: flex;
	align-items: center;
	gap: 10px;
	max-width: min(440px, calc(100vw - 36px));
	padding: 8px 8px 8px 14px;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	font-size: var(--text-md);
	background: var(--bg-panel);
	box-shadow: var(--shadow-popover);
	animation: designer-toast-in 140ms ease-out;
}

@keyframes designer-toast-in {
	from {
		opacity: 0;
		transform: translateY(8px);
	}
}

.designer-notice.ok {
	border-color: color-mix(in srgb, var(--ok) 50%, transparent);
}

.designer-notice.failed {
	border-color: color-mix(in srgb, var(--danger) 60%, transparent);
	color: var(--danger);
}

.designer-notice span {
	flex: 1;
}

/* The node packs: the filters card at the left, the packs in the middle, and
   the picked pack's contents at the right. Three columns of one grid, so each
   scrolls on its own and the filters never scroll away. The cards are solid,
   tinted towards the accent, as the start page's are. */
.pack-browser {
	--packs-card: color-mix(in srgb, var(--accent) 5%, var(--bg-panel));
	--packs-card-hover: color-mix(in srgb, var(--accent) 10%, var(--bg-panel));
	display: grid;
	grid-template-columns: 210px minmax(0, 1fr);
	grid-template-rows: minmax(0, 1fr);
	gap: var(--edge);
	height: 100%;
	box-sizing: border-box;
	padding: var(--chrome-top) var(--edge) 0;
	overflow: hidden;
	background-color: var(--bg-canvas);
}

.pack-browser-peeking {
	grid-template-columns: 210px minmax(0, 1fr) 320px;
}

/* The search sits beside the mark, ahead of the spacer that pushes the rest
   of the row right. */
.window-chrome .mark-group {
	order: -3;
}

/* The mode strip stays with the mark, as it does in every window. */
.window-chrome .mode-strip {
	order: -2;
}

.window-chrome .packs-search-group {
	order: -1;
}

.packs-search {
	display: flex;
	align-items: center;
	gap: 6px;
	width: 280px;
	height: var(--hit);
	padding: 0 8px 0 10px;
	box-sizing: border-box;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--bg-input);
	color: var(--fg-faint);
	cursor: text;
}

.packs-search:focus-within {
	border-color: var(--accent);
}

.packs-search input {
	flex: 1;
	min-width: 0;
	border: 0;
	outline: 0;
	padding: 0;
	background: none;
	color: var(--fg);
	font: inherit;
}

.packs-search kbd {
	padding: 0 6px;
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	font: inherit;
	font-size: var(--text-xs);
	color: var(--fg-faint);
}

.packs-clear {
	display: inline-grid;
	place-items: center;
	width: 20px;
	height: 20px;
	padding: 0;
	border: 0;
	border-radius: var(--radius-xs);
	background: none;
	color: var(--fg-faint);
	cursor: pointer;
}

.packs-clear:hover {
	background: var(--bg-hover);
	color: var(--fg);
}

.packs-naming {
	display: flex;
	gap: 6px;
}

.packs-filters,
.packs-peek {
	display: flex;
	flex-direction: column;
	min-height: 0;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	background: var(--packs-card);
	box-shadow: var(--shadow-raised);
	overflow: hidden;
}

.packs-filters {
	align-self: start;
}

.packs-peek {
	margin-bottom: var(--edge);
}

.packs-filters .card-head,
.packs-peek .card-head {
	cursor: default;
}

.packs-filter-groups {
	display: flex;
	flex-direction: column;
	padding: 0 6px 8px;
}

.packs-filter-head {
	margin: 10px 6px 4px;
	font-size: var(--text-xs);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: var(--fg-faint);
}

.packs-filter-rule {
	display: none;
}

.packs-filter {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 5px 8px;
	border: 0;
	border-radius: var(--radius-sm);
	background: none;
	color: var(--fg-muted);
	font: inherit;
	text-align: left;
	cursor: pointer;
}

.packs-filter:hover {
	background: var(--bg-hover);
	color: var(--fg);
}

.packs-filter-on {
	background: color-mix(in srgb, var(--accent) 14%, transparent);
	color: var(--accent);
}

.packs-filter .packs-count {
	margin-left: auto;
}

.packs-count {
	font-size: var(--text-sm);
	font-weight: 400;
	letter-spacing: 0;
	color: var(--fg-faint);
}

.packs-main {
	min-width: 0;
	overflow: auto;
	padding: 0 2px 40px;
}

.packs-section {
	display: flex;
	align-items: center;
	gap: 10px;
	margin: 4px 0 10px;
	font-size: var(--text-xs);
	font-weight: 700;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--fg-faint);
}

.packs-main > .packs-section ~ .packs-section {
	margin-top: 26px;
}

/* Beside the count, where the eye already is. */
.tb.packs-fold {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	height: 22px;
	min-height: 0;
	padding: 0 8px 0 4px;
	font-size: var(--text-sm);
	font-weight: 400;
	letter-spacing: 0;
	text-transform: none;
}

.tb.packs-fold svg {
	transition: transform 120ms;
}

.tb.packs-fold[aria-expanded="false"] svg {
	transform: rotate(-90deg);
}

.packs-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
	gap: 10px;
}

.packs-grid-builtin {
	grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
}

.packs-card {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: 8px;
	min-width: 0;
	padding: 11px 12px;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	background: var(--packs-card);
	box-shadow: var(--shadow-raised);
	color: var(--fg);
	cursor: pointer;
}

.packs-card:hover {
	border-color: var(--border-strong);
	background: var(--packs-card-hover);
}

.packs-card:focus-visible,
.packs-row:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: -1px;
}

.packs-card.packs-selected {
	border-color: color-mix(in srgb, var(--accent) 60%, var(--border));
	box-shadow:
		0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent),
		var(--shadow-raised);
}

.packs-head {
	display: flex;
	align-items: center;
	gap: 8px;
	min-width: 0;
}

.packs-name {
	min-width: 0;
	font-weight: 600;
	font-size: var(--text-lg);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.tb.icon-only.packs-more {
	width: 24px;
	height: 24px;
	min-height: 0;
	padding: 0;
	display: inline-grid;
	place-items: center;
	border-color: transparent;
	background: none;
}

.packs-head .packs-more {
	margin-left: auto;
}

.tb.icon-only.packs-more:hover {
	background: var(--bg-hover);
}

.packs-path {
	font-family: var(--mono, ui-monospace, monospace);
	font-size: var(--text-sm);
	color: var(--fg-faint);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.packs-pills {
	display: flex;
	flex-wrap: wrap;
	gap: 5px;
}

.packs-pill {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	max-width: 100%;
	padding: 2px 8px 2px 6px;
	box-sizing: border-box;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--bg-input);
	font-size: var(--text-sm);
	color: var(--fg-muted);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.packs-pill i,
.packs-peek-node i {
	flex: none;
	width: 8px;
	height: 8px;
	border-radius: 2px;
}

.packs-pill-more {
	color: var(--fg-faint);
}

.packs-pill.packs-hit,
.packs-peek-node.packs-hit {
	border-color: color-mix(in srgb, var(--accent) 70%, var(--border));
	background: color-mix(in srgb, var(--accent) 14%, var(--bg-input));
	color: var(--fg);
}

.packs-requires {
	margin: 0;
	font-size: var(--text-sm);
	color: var(--fg-muted);
}

.packs-foot,
.packs-meta {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 6px 8px;
	font-size: var(--text-sm);
	color: var(--fg-muted);
}

.packs-foot {
	margin-top: auto;
}

.packs-foot .spacer {
	flex: 1;
}

.packs-foot .tb {
	height: 24px;
	min-height: 0;
	padding: 0 8px;
	font-size: var(--text-sm);
}

.runtime-chip-warn {
	color: var(--warning);
	border-color: color-mix(in srgb, var(--warning) 60%, var(--border));
}

/* The built-in library: smaller tiles, each with its category's colour down
   its left edge. */
.packs-card-builtin {
	gap: 4px;
	padding-left: 16px;
}

.packs-card-builtin::before {
	content: "";
	position: absolute;
	left: 0;
	top: 10px;
	bottom: 10px;
	width: 4px;
	border-radius: 0 3px 3px 0;
	background: var(--packs-stripe, var(--border-strong));
}

.packs-line {
	font-size: var(--text-sm);
	color: var(--fg-faint);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.packs-line-hit {
	color: var(--fg);
	font-weight: 600;
}

.packs-ghosts {
	display: grid;
	grid-template-rows: 1fr 1fr;
	gap: 8px;
}

.packs-ghost {
	display: grid;
	place-content: center;
	justify-items: center;
	gap: 2px;
	padding: 12px;
	border: 1px dashed var(--border-strong);
	border-radius: var(--radius-md);
	background: color-mix(in srgb, var(--accent) 3%, var(--bg-canvas));
	color: var(--fg-muted);
	font: inherit;
	text-align: center;
	cursor: pointer;
}

.packs-ghost:hover {
	border-color: var(--accent);
	color: var(--accent);
	background: color-mix(in srgb, var(--accent) 8%, var(--bg-canvas));
}

.packs-ghost strong {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	font-size: var(--text-lg);
}

.packs-ghost span {
	font-size: var(--text-sm);
}

.packs-none {
	margin: 0;
	padding: 20px;
	text-align: center;
	color: var(--fg-faint);
	border: 1px dashed var(--border);
	border-radius: var(--radius-md);
	background: color-mix(in srgb, var(--accent) 3%, var(--bg-canvas));
}

.packs-grid + .packs-none {
	margin-top: 10px;
}

/* The list: one row a pack, in one card. */
.packs-list {
	grid-template-columns: minmax(0, 1fr);
	gap: 0;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	overflow: hidden;
	background: var(--packs-card);
	box-shadow: var(--shadow-raised);
}

.packs-row {
	display: grid;
	grid-template-columns: 12px minmax(120px, 1.2fr) 90px 80px minmax(0, 1.4fr) minmax(0, 2fr) 28px;
	align-items: center;
	gap: 10px;
	padding: 7px 10px;
	border-top: 1px solid var(--border);
	cursor: pointer;
}

.packs-row:first-child {
	border-top: 0;
}

.packs-row:hover {
	background: var(--packs-card-hover);
}

.packs-row.packs-selected {
	background: color-mix(in srgb, var(--accent) 14%, var(--bg-panel));
}

.packs-row .packs-name {
	font-size: var(--text-md);
}

.packs-swatch {
	width: 10px;
	height: 10px;
	border-radius: 3px;
	background: var(--border-strong);
}

.packs-row-count {
	font-size: var(--text-sm);
	color: var(--fg-muted);
}

.packs-row-runs {
	display: flex;
	gap: 4px;
	min-width: 0;
	overflow: hidden;
}

/* The picked pack: everything in it, beside the list. */
.packs-peek-body {
	flex: 1;
	min-height: 0;
	overflow: auto;
	display: grid;
	align-content: start;
	gap: 8px;
	padding: 10px 12px;
}

.packs-peek-about {
	margin: 0;
	font-size: var(--text-md);
	color: var(--fg-muted);
	overflow-wrap: anywhere;
}

.packs-peek-node {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 6px 8px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--bg-input);
	color: var(--fg);
	font: inherit;
	text-align: left;
	cursor: pointer;
}

.packs-peek-node:hover {
	border-color: var(--accent);
}

.packs-peek-open {
	margin-left: auto;
	font-size: var(--text-sm);
	color: var(--fg-faint);
}

.packs-peek-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 6px;
	padding: 10px 12px;
	border-top: 1px solid var(--border);
	background: var(--card-head);
}

.pack-dialog {
	display: grid;
	gap: 10px;
	width: min(420px, 92vw);
	box-sizing: border-box;
	padding: 14px 16px;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	background: var(--bg-panel);
	box-shadow: var(--shadow-modal);
}

.pack-dialog p {
	margin: 0;
	color: var(--fg-muted);
}

.pack-dialog-row {
	display: flex;
	justify-content: flex-end;
	gap: 8px;
}

/* Narrow: the browser's own row in the page, the filters a row of chips, and
   the picked pack a sheet from the bottom. */
.pack-browser-narrow,
.pack-browser-narrow.pack-browser-peeking {
	display: flex;
	flex-direction: column;
	gap: 8px;
	padding: var(--chrome-top) 10px 0;
}

.packs-bar {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	flex: none;
}

.packs-bar .tool-group {
	flex-wrap: nowrap;
}

.packs-bar .packs-search-group {
	flex: 1 1 220px;
	min-width: 0;
}

.packs-bar .packs-search {
	width: 100%;
}

.pack-browser-narrow .packs-filters {
	flex: none;
	align-self: stretch;
}

.pack-browser-narrow .packs-filters .card-head,
.pack-browser-narrow .packs-filter-head {
	display: none;
}

.pack-browser-narrow .packs-filter-groups {
	flex-direction: row;
	gap: 2px;
	padding: 4px;
	overflow-x: auto;
	scrollbar-width: none;
}

.pack-browser-narrow .packs-filter {
	flex: none;
}

.pack-browser-narrow .packs-filter-rule {
	display: block;
	flex: none;
	width: 1px;
	margin: 4px 2px;
	background: var(--border);
}

.pack-browser-narrow .packs-main {
	flex: 1;
}

.pack-browser-narrow .packs-peek {
	position: absolute;
	left: 6px;
	right: 6px;
	bottom: 6px;
	z-index: 20;
	max-height: 62%;
	margin: 0;
	box-shadow: var(--shadow-popover);
}

.pack-browser-narrow .packs-row {
	grid-template-columns: 12px minmax(0, 1fr) 70px 28px;
}

.pack-browser-narrow .packs-row-kind,
.pack-browser-narrow .packs-row-runs,
.pack-browser-narrow .packs-row .packs-path,
.pack-browser-narrow .packs-row .packs-line {
	display: none;
}

@media (max-width: 699px) {
	.packs-search kbd {
		display: none;
	}
}

@media (prefers-reduced-motion: reduce) {
	.tb.packs-fold svg {
		transition: none;
	}
}

.pack-nodes h1 {
	font-size: var(--text-2xl);
	margin: 0;
}

.pack-import .spacer {
	flex: 1;
}

.designer .badge {
	padding: 1px 7px;
	font-size: var(--text-sm);
	border: 1px solid var(--border);
	border-radius: 999px;
	color: var(--fg-muted);
	white-space: nowrap;
}

.designer .badge.warn {
	color: var(--warning);
	border-color: color-mix(in srgb, var(--warning) 60%, transparent);
}

.designer .count {
	color: var(--fg-faint);
}

/* The button that destroys something, when it is not already in a menu. */
button.tb.danger {
	color: var(--danger);
	border-color: color-mix(in srgb, var(--danger) 55%, transparent);
}

.pack-import {
	width: min(720px, 92vw);
	max-height: 80vh;
	overflow: auto;
	background: var(--bg-panel);
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	padding: 12px 14px;
	box-shadow: var(--shadow-modal);
}

.pack-import-head {
	display: flex;
	align-items: center;
	gap: 8px;
}

.pack-import ul {
	list-style: none;
	margin: 10px 0 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: 6px;
}

.pack-import li {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 6px 8px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	font-size: var(--text-md);
}

.pack-import .name {
	font-weight: 600;
}

/* ---------------------------------------------------------------- pack view */

.pack-view {
	flex: 1;
	display: grid;
	grid-template-columns: 280px minmax(0, 1fr);
	/* One row, held to the window, so the editor's logic panel stays on screen. */
	grid-template-rows: minmax(0, 1fr);
	min-height: 0;
}

.pack-nodes {
	display: flex;
	flex-direction: column;
	gap: 8px;
	padding: 14px;
	border-right: 1px solid var(--border);
	background: var(--bg-panel);
	overflow: auto;
}

.pack-back {
	align-self: flex-start;
	display: inline-flex;
	align-items: center;
	gap: 4px;
}

.pack-nodes ul {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.pack-node {
	display: grid;
	grid-template-columns: 8px 1fr;
	grid-template-rows: auto auto;
	column-gap: 8px;
	width: 100%;
	text-align: left;
	padding: 5px 8px;
	background: none;
	border: 1px solid transparent;
	border-radius: var(--radius-sm);
	color: var(--fg);
	cursor: pointer;
}

.pack-node:hover {
	background: var(--bg-hover);
}

.pack-node.on {
	background: var(--bg-active);
	border-color: var(--border);
}

.pack-node .swatch {
	grid-row: 1 / 3;
	width: 8px;
	border-radius: var(--radius-xs);
}

.pack-node .id {
	font-size: var(--text-sm);
	color: var(--fg-faint);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.designer-stage {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 12px;
	padding: 24px;
	background: var(--bg-canvas);
	overflow: auto;
	position: relative;
}

/* ------------------------------------------------------------- node editor */

.node-editor {
	display: flex;
	flex-direction: column;
	min-width: 0;
	min-height: 0;
}

/* The canvas the node sits on, with the graph's grid so it reads as one. */
.node-editor-stage {
	position: relative;
	flex: 1;
	min-height: 0;
	overflow: hidden;
	background-color: var(--bg-canvas);
	/* The node is shown larger here than on a canvas, and so is its grid. */
	--grid-step: 38px;
}

.node-editor-world {
	position: absolute;
	top: 0;
	left: 0;
	transform-origin: 0 0;
}

.node-plate {
	position: absolute;
	box-sizing: border-box;
	background: color-mix(in srgb, var(--bg-panel) 70%, transparent);
	border: 1px dashed var(--border-strong);
	border-radius: 14px;
	pointer-events: none;
}

/* The header's own title is hidden, and this input is typed into in its place. */
.node-editor-world .node .head .title {
	visibility: hidden;
}

.node-title-edit {
	position: absolute;
	top: 0;
	left: 0;
	/* Above the node, which sits at LAYER.node (50), or the header covers it. */
	z-index: 60;
	box-sizing: border-box;
	padding: 0 10px;
	background: transparent;
	border: 0;
	color: #fff;
	font: inherit;
	font-weight: 600;
	font-size: var(--text-md);
	outline: none;
}

.node-title-edit::placeholder {
	color: rgb(255 255 255 / 55%);
}

/* In the designer a pin is something you edit, so what is drawn for it — dot,
   label, value — is a target: a tint on hover, and a stronger one with an
   outline while it is the pin the popover is about. Sized to that content with
   a little padding, which the negative margin gives back, so nothing moves.
   Only the designer draws `pin-target`. */
.node .pin-target {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	/* No max-width. An output's side is one pin wide (22px), and capping the
	   target to it with 12px of padding left the pin 4px, so it overflowed the
	   node's edge. `min-width: 0` is enough for a long label to shrink. */
	min-width: 0;
	padding: 2px 6px;
	margin: -2px -6px;
	border-radius: var(--radius-sm);
	cursor: pointer;
	transition: background-color 80ms ease;
}

.node .pin-target:hover {
	background: color-mix(in srgb, var(--accent) 16%, transparent);
}

.node .pin-target.pin-selected,
.node .pin-target.pin-selected:hover {
	background: color-mix(in srgb, var(--accent) 30%, transparent);
	box-shadow: inset 0 0 0 1px var(--accent);
}

.node .pin-target.pin-selected .pin-label {
	color: var(--fg);
}

/* A checkbox value sits beside its label, as it does in a row, rather than
   pushed to the edge of a side the target no longer fills. */
.node .pin-target > input[type="checkbox"] {
	margin-left: 4px;
}

.node .pin.highlighted .dot {
	box-shadow: 0 0 0 1px var(--pin-well), 0 0 0 3px var(--accent);
}

.pin-chip {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	padding: 3px 8px;
	font-size: var(--text-md);
	border: 1px solid var(--border);
	border-radius: 999px;
	background: var(--bg-input);
	cursor: grab;
	user-select: none;
}

.chip-dot {
	width: 9px;
	height: 9px;
	border: 2px solid currentColor;
	border-radius: 50%;
	box-sizing: border-box;
}

.chip-dot.exec {
	border-radius: 1px;
	transform: rotate(45deg) scale(0.85);
}

.tool-group .tool-label {
	font-size: var(--text-sm);
	color: var(--fg-faint);
}

.tool-group .badge {
	padding: 1px 7px;
	font-size: var(--text-sm);
	border: 1px solid var(--border);
	border-radius: 999px;
	color: var(--fg-muted);
}

.tool-group .badge.warn {
	color: var(--warning);
	border-color: color-mix(in srgb, var(--warning) 60%, transparent);
}

.node-details,
.node-problems {
	position: absolute;
	bottom: 10px;
	z-index: 5;
	padding: 8px 10px;
	background: color-mix(in srgb, var(--bg-panel) 94%, transparent);
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-raised);
	font-size: var(--text-md);
}

/* Expands out under the floating tools, from their Details button. */
.node-details {
	left: 10px;
	top: 56px;
	bottom: auto;
	display: grid;
	gap: 6px;
	width: min(380px, 45%);
}

.node-details-head {
	display: flex;
	align-items: center;
	gap: 6px;
	margin-bottom: 2px;
}

.node-details textarea.tb {
	resize: vertical;
	min-height: 54px;
	font: inherit;
	line-height: 1.45;
}

/* A choice fills its row the way the fields above and below it do, its options
   sharing the width, rather than stopping at the length of its labels. */
/* A segmented control in a stacked field fills the field, the way the select
   and the text input beside it do. Left to `inline-flex` it sits in the corner
   of a full-width box with the rest of the row empty, which reads as a control
   that failed to load rather than as a choice of two. */
.node-details .segmented,
.inspector .field .segmented,
.variable-body .field .segmented {
	display: flex;
	width: 100%;
}

/* Each option starts from its own label and they share what is left, so the
   row is filled without cutting a long label into two lines, which equal thirds
   did to "Roblox and Lune". */
.node-details .segmented button,
.inspector .field .segmented button,
.variable-body .field .segmented button {
	flex: 1 1 auto;
	text-align: center;
	white-space: nowrap;
}

.node-details label.summary {
	align-items: start;
}

.node-details label.summary > span {
	padding-top: 5px;
}

/* Labels as the Inspector's: in bold, beside their fields. */
.node-details label,
.pin-popover label {
	display: grid;
	grid-template-columns: min(max(70px, 30%), 110px) minmax(0, 1fr);
	align-items: center;
	gap: 10px;
}

.node-details label > span,
.pin-popover label > span {
	font-size: var(--text-sm);
	font-weight: 600;
	color: var(--fg);
}

.pin-popover label.check > span {
	font-weight: 400;
	color: var(--fg-muted);
}

.node-problems {
	right: 10px;
	max-width: min(420px, 45%);
	color: var(--ok);
}

.node-problems.bad {
	color: var(--danger);
}

.node-problems ul {
	margin: 0;
	padding-left: 16px;
	line-height: 1.6;
}

/* The selected pin's settings, drawn as the Inspector draws a panel: a head
   band with the pin's name and side, labels in bold beside their fields, and
   the pin's moves in a foot. Only the body scrolls, so a short column keeps
   the head and the foot in view. */
.pin-popover {
	position: absolute;
	z-index: 6;
	width: 270px;
	display: flex;
	flex-direction: column;
	overflow: hidden;
	background: var(--bg-panel);
	border: 1px solid var(--border-strong);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-popover);
	font-size: var(--text-md);
}

.pin-popover-head,
.pin-popover-actions {
	display: flex;
	align-items: center;
	gap: 6px;
	flex: none;
}

.pin-popover-head {
	min-height: calc(var(--hit) + 8px);
	padding: 4px 4px 4px 10px;
	border-bottom: 1px solid var(--border);
	background: var(--card-head);
}

.pin-popover.folded .pin-popover-head {
	border-bottom: 0;
}

.pin-popover-head strong {
	font-size: 1em;
}

.pin-popover-kind {
	font-size: var(--text-sm);
	color: var(--fg-muted);
}

.pin-popover-body {
	display: flex;
	flex-direction: column;
	gap: 8px;
	min-height: 0;
	padding: 10px;
	overflow-y: auto;
}

.pin-popover-body > p.hint {
	margin: 0;
}

.pin-popover-actions {
	padding: 4px 6px;
	border-top: 1px solid var(--border);
}

.pin-popover label.check {
	grid-template-columns: auto 1fr;
}

/* A line about the field above it, under that field rather than its label. */
.pin-popover-body > .pin-popover-note.hint {
	margin: -4px 0 0 calc(min(max(70px, 30%), 110px) + 10px);
	font-size: var(--text-sm);
}

.pin-popover .hint code {
	font-size: var(--text-sm);
}

.logic-splitter {
	height: 5px;
	flex: none;
	cursor: row-resize;
	background: var(--border);
}

.logic-splitter:hover {
	background: var(--accent);
}

.node-logic {
	flex: none;
	display: flex;
	flex-direction: column;
	min-height: 0;
	background-color: var(--bg-panel);
}

.logic-head {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 10px;
	padding: 6px 12px;
	border-bottom: 1px solid var(--border);
}

.logic-tab {
	padding: 2px 10px;
	font-size: var(--text-md);
	border-radius: 999px;
	border: 1px solid var(--border);
}

.logic-tab.on {
	border-color: var(--accent);
	color: var(--fg);
}

.luau-field {
	flex: 1;
	min-height: 0;
	overflow: hidden;
}

/* Logic built from nodes: the graph, and beside it the Luau it compiles to. */
.logic-split {
	flex: 1;
	min-height: 0;
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(220px, 30%);
}

.logic-canvas {
	position: relative;
	min-width: 0;
	min-height: 0;
}

.logic-canvas > .canvas {
	position: absolute;
	inset: 0;
}

/* A logic graph is not a script, so the script's name and class say nothing. */
.logic-canvas .watermark {
	display: none;
}

.logic-compiled {
	margin: 0;
	padding: 8px 12px;
	overflow: auto;
	border-left: 1px solid var(--border);
	background: var(--bg-canvas);
	font-family: "Cascadia Mono", Consolas, monospace;
	font-size: var(--text-md);
	line-height: 1.5;
	white-space: pre;
	color: var(--fg-muted);
}

.node-problems ul.warnings {
	color: var(--warning);
	margin-top: 4px;
}

.pack-requires {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 5px;
}

.pack-requires .tool-label {
	font-size: var(--text-sm);
	color: var(--fg-faint);
	margin-right: 2px;
}

.pack-requires select.tb {
	padding: 1px 6px;
	font-size: var(--text-sm);
}

.badge .badge-remove {
	margin-left: 4px;
	padding: 0;
	background: none;
	border: 0;
	color: inherit;
	cursor: pointer;
	font-size: var(--text-sm);
}

.logic-empty {
	padding: 12px;
}

/* ---------------------------------------------------------------- docs tabs */

/* One switch over several routes through the same subject — three ways to
   define a custom node. The static site switches with radios and no script;
   the panel renders the chosen one. Both use these rules. */
.docs-tabs {
	margin: 16px 0;
	/* A containing block for the radios below, and it is load-bearing.
	 *
	 * Without it their nearest positioned ancestor is the *body*, so three
	 * absolutely-positioned inputs sitting at their static position deep inside
	 * a separately-scrolling column were contributing to the body's scrollable
	 * overflow -- on the Toolbars page they landed at y=2845 and y=3441 and
	 * stretched the document to 3454px when the content box is one viewport
	 * tall and scrolls internally.
	 *
	 * Which was survivable until somebody clicked a tab. A `<label for>` focuses
	 * its input, the browser scrolls a focused element into view, and the window
	 * scrolled to a radio two thousand pixels below anything that renders:
	 * **a blank page, with nothing in the console**. Reported twice and
	 * invisible to a scripted click, because `.click()` does not focus. */
	position: relative;
}

/* Hidden, and pinned to the top of their own tabs block so that focusing one
   scrolls to the switch the reader just used rather than into empty space.
   1px rather than `auto`: an input has an intrinsic width, and at its natural
   size it can still widen a narrow page. */
.docs-tabs > input {
	position: absolute;
	top: 0;
	left: 0;
	width: 1px;
	height: 1px;
	opacity: 0;
	pointer-events: none;
}

.docs-tabs-label {
	font-size: var(--text-sm);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: var(--fg-faint);
	margin-bottom: 6px;
}

.docs-tab-bar {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
	border-bottom: 1px solid var(--border);
	padding-bottom: 8px;
}

.docs-tab-bar label,
.docs-tab-bar button {
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: 999px;
	padding: 4px 12px;
	font: inherit;
	font-size: var(--text-md);
	color: var(--fg);
	cursor: pointer;
}

.docs-tab-bar label:hover,
.docs-tab-bar button:hover {
	background: var(--bg-hover);
}

.docs-tab-bar button.on {
	background: var(--accent);
	border-color: var(--accent);
	color: #fff;
}

/* The reader's own screen's tab, marked so the page opening on a tab that is
   not the first one explains itself. */
.docs-tab-here {
	margin-left: 6px;
	font-size: var(--text-xs);
	opacity: 0.75;
}

.docs-tab-here::before {
	content: "· ";
}

/* Hidden by default so the static site can show one; the panel renders only
   the chosen panel and marks it, so it is never hidden by these rules. */
.docs-tab-panel {
	display: none;
	padding-top: 14px;
}

.docs-tab-panel.on {
	display: block;
}

.docs-tabs > input:nth-of-type(1):checked ~ .docs-tab-panels > .docs-tab-panel:nth-child(1),
.docs-tabs > input:nth-of-type(2):checked ~ .docs-tab-panels > .docs-tab-panel:nth-child(2),
.docs-tabs > input:nth-of-type(3):checked ~ .docs-tab-panels > .docs-tab-panel:nth-child(3),
.docs-tabs > input:nth-of-type(4):checked ~ .docs-tab-panels > .docs-tab-panel:nth-child(4) {
	display: block;
}

.docs-tabs > input:nth-of-type(1):checked ~ .docs-tab-bar > label:nth-child(1),
.docs-tabs > input:nth-of-type(2):checked ~ .docs-tab-bar > label:nth-child(2),
.docs-tabs > input:nth-of-type(3):checked ~ .docs-tab-bar > label:nth-child(3),
.docs-tabs > input:nth-of-type(4):checked ~ .docs-tab-bar > label:nth-child(4) {
	background: var(--accent);
	border-color: var(--accent);
	color: #fff;
}

/* A picture is not prose, and a wide screen has room the measure does not use.
   A graph drawn at 78 characters wide is a graph nobody can read the pins of,
   so on a screen with the space these step outside the column -- centred, and
   never wider than the article's own maximum. */
@media (min-width: 1180px) {
	.docs-page .docs-article.narrow .docs-preview.graph,
	.docs-page .docs-article.narrow .docs-graph-tabs,
	.docs-page .docs-article.narrow .docs-tab-panel .docs-preview.graph {
		width: min(940px, calc(100vw - 420px));
		margin-left: calc((100% - min(940px, calc(100vw - 420px))) / 2);
	}
}

/* Taller too, once it is wider: the frame is a window onto the graph, and a
   940px window 260px tall crops what the width just made visible. */
@media (min-width: 1180px) {
	.docs-page .docs-article.narrow .docs-preview.graph .graph-viewport {
		height: 360px;
	}
}

/* Several graphs in one frame, a tab each — the shape the editor uses for two
   open documents, so an example that is two files reads as two files.
   Radios on the static site, state in the app; both hide the radios.

   Pinned inside their own block, as `.docs-tabs` pins its radios: left at their
   static position they reached the body's overflow, and clicking a graph's tab
   scrolled the window to a radio below anything drawn -- a blank page. */
.docs-graph-tabs {
	position: relative;
}

.docs-graph-tabs > input {
	position: absolute;
	top: 0;
	left: 0;
	width: 1px;
	height: 1px;
	opacity: 0;
	pointer-events: none;
}

.docs-graph-tabs .docs-tab-bar {
	border-bottom: 0;
	padding-bottom: 0;
	gap: 4px;
}

/* Square-topped and joined to the frame below, as a document tab is: a pill
   floating over a picture would read as a filter on it rather than as which
   one of them you are looking at. */
.docs-graph-tabs .docs-tab-bar label,
.docs-graph-tabs .docs-tab-bar button {
	border-radius: var(--radius-sm) var(--radius-sm) 0 0;
	border-bottom-color: transparent;
}

/* Tabs with what the file declares beside them: the panel in its own column,
   level with the graph, and the tab bar over the graph it switches. The
   same columns as a single graph with a panel (`.with-panel` below). */
.docs-graph-tabs.with-panel {
	display: grid;
	grid-template-columns: minmax(0, 240px) minmax(0, 1fr);
	grid-template-areas:
		"label label"
		".     bar"
		"panel graph";
	column-gap: 14px;
	align-items: start;
}

.docs-graph-tabs.with-panel > .docs-tabs-label { grid-area: label; }
.docs-graph-tabs.with-panel > .docs-tab-bar { grid-area: bar; }
.docs-graph-tabs.with-panel > .graph-declares { grid-area: panel; }
.docs-graph-tabs.with-panel > .docs-tab-panels,
.docs-graph-tabs.with-panel > .docs-preview { grid-area: graph; min-width: 0; }

@media (max-width: 860px) {
	.docs-graph-tabs.with-panel {
		grid-template-columns: minmax(0, 1fr);
		grid-template-areas: "label" "panel" "bar" "graph";
	}
	.docs-graph-tabs.with-panel > .graph-declares { margin-bottom: 14px; }
}

.docs-graph-tabs .docs-graph-panel {
	display: none;
	margin-top: 0;
	border-top-left-radius: 0;
}

.docs-graph-tabs.live .docs-graph-panel {
	display: block;
}

.docs-graph-tabs > input:nth-of-type(1):checked ~ .docs-tab-panels > .docs-graph-panel:nth-child(1),
.docs-graph-tabs > input:nth-of-type(2):checked ~ .docs-tab-panels > .docs-graph-panel:nth-child(2),
.docs-graph-tabs > input:nth-of-type(3):checked ~ .docs-tab-panels > .docs-graph-panel:nth-child(3),
.docs-graph-tabs > input:nth-of-type(4):checked ~ .docs-tab-panels > .docs-graph-panel:nth-child(4) {
	display: block;
}

.docs-graph-tabs > input:nth-of-type(1):checked ~ .docs-tab-bar > label:nth-child(1),
.docs-graph-tabs > input:nth-of-type(2):checked ~ .docs-tab-bar > label:nth-child(2),
.docs-graph-tabs > input:nth-of-type(3):checked ~ .docs-tab-bar > label:nth-child(3),
.docs-graph-tabs > input:nth-of-type(4):checked ~ .docs-tab-bar > label:nth-child(4) {
	/* The docs window's selected tab (`button.on`), so the site reads the same. */
	background: var(--accent);
	border-color: var(--accent);
	color: #fff;
}

/* Release history, folded by minor version: one summary line per fold, its
   count and date at the right where a heading keeps its aside. */
.docs-content details.docs-details:where(:not(.docs-layout-screen *)) {
	border-top: 1px solid var(--border);
	padding: 10px 0;
}

.docs-content details.docs-details:last-child:where(:not(.docs-layout-screen *)) {
	border-bottom: 1px solid var(--border);
}

.docs-content details.docs-details > summary:where(:not(.docs-layout-screen *)) {
	display: flex;
	align-items: baseline;
	gap: 10px;
	cursor: pointer;
	list-style: none;
	font-size: 16px;
	font-weight: 600;
}

.docs-content details.docs-details > summary:where(:not(.docs-layout-screen *))::-webkit-details-marker {
	display: none;
}

.docs-content details.docs-details > summary:where(:not(.docs-layout-screen *))::before {
	content: "▸";
	font-size: 11px;
	color: var(--fg-faint);
}

.docs-content details.docs-details[open] > summary:where(:not(.docs-layout-screen *))::before {
	content: "▾";
}

.docs-content details.docs-details > summary .aside:where(:not(.docs-layout-screen *)),
.docs-content h3 .aside:where(:not(.docs-layout-screen *)) {
	margin-left: auto;
	font-size: 12px;
	font-weight: 400;
	color: var(--fg-faint);
}

/* A fold's title and, under it, what that minor version was: shown closed. */
.docs-content details.docs-details > summary .docs-details-title:where(:not(.docs-layout-screen *)) {
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-width: 0;
}

.docs-content details.docs-details > summary .docs-details-sub:where(:not(.docs-layout-screen *)) {
	font-size: var(--text-md);
	font-weight: 400;
	color: var(--fg-muted);
	text-wrap: pretty;
}

.docs-content details.docs-details.with-sub > summary:where(:not(.docs-layout-screen *))::before {
	align-self: flex-start;
	margin-top: 5px;
}

.docs-content details.docs-details[hidden] {
	display: none;
}

/* The count and date stay one line; the title and headline take what is left. */
.docs-content details.docs-details > summary .aside:where(:not(.docs-layout-screen *)) {
	flex: none;
	white-space: nowrap;
}

/* One release: a solid card, tinted towards the accent, its version the
   anchor a link to it lands on. The latest sits at the top of the page. */
.docs-release {
	--release-card: color-mix(in srgb, var(--accent) 5%, var(--bg-panel));
	margin: 14px 0;
	padding: 14px 16px 6px;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	background: var(--release-card);
	scroll-margin-top: calc(var(--chrome-top, 60px) + 12px);
}

.docs-release[hidden],
.docs-release-section[hidden],
.docs-release-entries > li[hidden] {
	display: none;
}

.docs-release.is-latest {
	border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
	box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 25%, transparent);
}

.docs-release-head {
	display: flex;
	align-items: baseline;
	gap: 10px;
	margin-bottom: 8px;
}

.docs-release-version {
	font-size: 17px;
	font-weight: 700;
	font-variant-numeric: tabular-nums;
	color: var(--fg);
	text-decoration: none;
}

a.docs-release-version:hover {
	color: var(--accent);
}

.docs-release-head .aside {
	margin-left: auto;
	font-size: 12px;
	color: var(--fg-faint);
	font-variant-numeric: tabular-nums;
}

/* New since this browser last read the notes. */
.docs-release-new {
	display: none;
	align-items: center;
	gap: 5px;
	font-size: 10px;
	font-weight: 700;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ok);
}

.docs-release-new::before {
	content: "";
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: var(--ok);
}

.is-new > .docs-release-new {
	display: inline-flex;
}

/* A page the contents do not list, shown while it is the one being read: an
   older version reached from the dropdown. */
.docs-link.docs-link--unlisted {
	font-style: italic;
}

/* A release notes page in the contents with something this browser has not
   seen. */
.docs-link.is-new::after {
	content: "";
	width: 7px;
	height: 7px;
	margin-left: auto;
	border-radius: 50%;
	background: var(--ok);
	flex: none;
	align-self: center;
}

.docs-release .docs-tags {
	margin: 0 0 8px;
}

.docs-page .docs-content .docs-release-headline {
	margin: 0 0 8px;
	font-weight: 600;
	color: var(--fg);
}

.docs-page .docs-content .docs-release h4 {
	margin: 12px 0 4px;
}

/* One entry a row, with a rule between: a list of separate claims, without
   the box a table drew around every one. */
.docs-page .docs-content ul.docs-release-entries {
	margin: 0 0 8px;
	padding: 0;
	list-style: none;
}

.docs-page .docs-content ul.docs-release-entries > li {
	margin: 0;
	padding: 8px 0;
	border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
	font-size: var(--text-md);
	line-height: 1.55;
}

.docs-page .docs-content ul.docs-release-entries > li:first-child {
	border-top: 0;
}

/* A minor version's notes. Each line leads with the release it shipped in --
   a small fixed-width badge, so the words line up down the list -- and its
   releases' own summaries are a list of their own under the headline. */
.docs-ver {
	display: inline-block;
	min-width: 4.6em;
	margin-right: 8px;
	padding: 1px 6px;
	border: 1px solid color-mix(in srgb, var(--fg-faint) 40%, transparent);
	border-radius: var(--radius-xs);
	font-size: 11px;
	font-weight: 600;
	font-variant-numeric: tabular-nums;
	text-align: center;
	color: var(--fg-muted);
	vertical-align: 1px;
}

.docs-page .docs-content ul.docs-release-entries > li,
.docs-page .docs-content ol.docs-minor-releases > li {
	scroll-margin-top: calc(var(--chrome-top, 60px) + 40px);
}

.docs-page .docs-content ol.docs-minor-releases {
	margin: 0 0 8px;
	padding: 0;
	list-style: none;
}

.docs-page .docs-content ol.docs-minor-releases > li {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 4px 8px;
	margin: 0;
	padding: 7px 0;
	border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
	font-size: var(--text-md);
}

.docs-page .docs-content ol.docs-minor-releases > li:first-child {
	border-top: 0;
}

.docs-minor-releases .docs-ver-text {
	flex: 1 1 18em;
	min-width: 0;
}

.docs-minor-releases .aside {
	margin-left: auto;
	font-size: 12px;
	color: var(--fg-faint);
	font-variant-numeric: tabular-nums;
}

/* One release, asked for by its address: its lines lit, the rest left as
   they are. */
.docs-release li.is-target {
	background: color-mix(in srgb, var(--accent) 9%, transparent);
	box-shadow: inset 3px 0 0 var(--accent);
	padding-left: 8px;
}

/* The front page's recent minor versions, a line each. */
.docs-minor-rows {
	display: grid;
	margin: 6px 0 14px;
}

a.docs-minor-row {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 4px 8px;
	padding: 9px 6px;
	border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
	color: var(--fg);
	text-decoration: none;
}

a.docs-minor-row:hover {
	background: var(--bg-hover);
}

a.docs-minor-row .docs-ver-text {
	flex: 1 1 18em;
	min-width: 0;
	color: var(--fg-muted);
}

a.docs-minor-row .aside {
	margin-left: auto;
	font-size: 12px;
	color: var(--fg-faint);
	font-variant-numeric: tabular-nums;
}

/* Every version, by tens, from any release notes page. */
.docs-versions {
	display: flex;
	justify-content: flex-end;
	margin: 0 0 6px;
}

.docs-versions label {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	font-size: var(--text-sm);
	color: var(--fg-muted);
}

.docs-versions select {
	max-width: 100%;
	padding: 5px 8px;
	border: 1px solid var(--border-strong);
	border-radius: var(--radius-sm);
	background: var(--bg-input);
	color: var(--fg);
	font: inherit;
	font-variant-numeric: tabular-nums;
}

.docs-release-pager {
	display: grid;
	grid-template-columns: 1fr auto 1fr;
	gap: 10px;
	align-items: center;
	margin: 10px 0 4px;
	font-size: var(--text-md);
}

.docs-release-pager a {
	color: var(--accent);
	text-decoration: none;
}

.docs-release-pager a:hover {
	text-decoration: underline;
}

.docs-release-pager .newer {
	justify-self: end;
}

/* Security fixes, said plainly: a version that closed a way in carries the
   badge, and the release that did carries the tag. Its own colour, mixed from
   the theme's, so every scheme draws it and it reads as neither a feature nor
   a fault. */
.docs-content .badge.security {
	padding: 1px 7px;
	border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--danger));
	border-radius: 999px;
	background: color-mix(in srgb, color-mix(in srgb, var(--accent) 45%, var(--danger)) 12%, transparent);
	color: color-mix(in srgb, var(--accent) 45%, var(--danger));
	font-size: 10px;
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	white-space: nowrap;
}

@media (max-width: 699px) {
	.docs-release {
		padding: 12px 12px 4px;
	}
}

/* The current release, right beside its version number. */
.docs-content .badge.latest:where(:not(.docs-layout-screen *)) {
	padding: 1px 7px;
	border: 1px solid color-mix(in srgb, var(--accent) 60%, transparent);
	border-radius: 999px;
	color: var(--accent);
	font-size: 10px;
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	white-space: nowrap;
}

.docs-content details.docs-details h3:where(:not(.docs-layout-screen *)) {
	display: flex;
	align-items: baseline;
	gap: 10px;
	margin-top: 22px;
}

.docs-page .docs-content h4:where(:not(.docs-layout-screen *)) {
	margin: 14px 0 6px;
	font-size: 11.5px;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--fg-muted);
}

/* The reader's chosen face for the docs, from Settings → Docs → Font. Headings and
   prose follow it; code keeps its own monospace, which it sets for itself.
   Stacks of fonts an operating system already has — the docs run offline. */
:root[data-docs-font="serif"] .docs-article {
	font-family: Georgia, "Iowan Old Style", "Palatino Linotype", "Times New Roman", serif;
}

:root[data-docs-font="wide"] .docs-article {
	font-family: Verdana, Tahoma, "DejaVu Sans", sans-serif;
}

:root[data-docs-font="mono"] .docs-article {
	font-family: "Cascadia Mono", Consolas, ui-monospace, monospace;
}

/* A slider and the value it is at, for Settings → Docs → Preview size. */
.settings-range {
	display: flex;
	align-items: center;
	gap: 10px;
}

.settings-range input[type="range"] {
	width: 180px;
	accent-color: var(--accent);
}

.settings-range .value {
	min-width: 4ch;
	color: var(--fg-muted);
	font-variant-numeric: tabular-nums;
}

/* A badge beside a page's title: a pack's page, or how far a page has been
   reviewed. Pending takes the warning colour because it is the one asking the
   reader to take care; Verified takes the colour the notes use for a promise
   being kept. */
.docs-title .badge {
	margin-left: 10px;
	vertical-align: middle;
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--fg-muted);
	border: 1px solid var(--border);
	border-radius: 999px;
	padding: 1px 8px;
	white-space: nowrap;
}

/* The review badge has a line of its own, under the summary. */
.docs-title p.docs-status {
	margin: 10px 0 0;
	line-height: 1;
}

.docs-title .docs-status .badge {
	margin-left: 0;
}

.docs-title .badge.review.pending {
	color: var(--warning);
	border-color: color-mix(in srgb, var(--warning) 60%, transparent);
}

/* A page of the technical specification: whether it binds an implementation,
   and which draft it is. Normative in the danger colour, because it is the
   one that holds a reader to something; the draft in the warning colour, as
   a page still pending review is. */
.docs-title .badge.spec.normative {
	color: var(--danger);
	border-color: color-mix(in srgb, var(--danger) 55%, transparent);
}

.docs-title .badge.spec.informative {
	color: var(--fg-muted);
	border-color: var(--border);
}

.docs-title .badge.spec.draft {
	color: var(--warning);
	border-color: color-mix(in srgb, var(--warning) 60%, transparent);
}

.docs-title .docs-status .badge + .badge {
	margin-left: 6px;
}

/* Which runtime this node needs, beside its name.
 *
 * The same words and the same colours the node menu puts on a row, so a badge
 * there and a badge here are recognisably one thing rather than two vocabularies
 * for one fact. Luau is stated rather than left off, which is the difference
 * from the menu: a list is scanned and an absence reads, but a reference page is
 * arrived at one at a time and a page that says nothing has not answered. */
.docs-title .badge.runtime.luau {
	color: var(--fg-muted);
	border-color: var(--border);
}

.docs-title .badge.runtime.roblox {
	color: var(--accent);
	border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}

.docs-title .badge.runtime.lune {
	color: var(--warning);
	border-color: color-mix(in srgb, var(--warning) 50%, transparent);
}

.docs-title .badge.review.reviewed {
	color: var(--accent);
	border-color: color-mix(in srgb, var(--accent) 60%, transparent);
}

.docs-title .badge.review.verified {
	color: var(--ok);
	border-color: color-mix(in srgb, var(--ok) 60%, transparent);
}

/* Where to go next, at the foot of the page.
   Back on the left and on on the right, the way a book runs. Each says where
   it goes as well as which way: a reader deciding whether to carry on is
   deciding about the page, not about the direction. */
.docs-neighbours {
	display: flex;
	justify-content: space-between;
	gap: 12px;
	margin: 40px 0 0;
}

.docs-neighbour {
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-width: 0;
	max-width: 48%;
	padding: 8px 12px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	text-decoration: none;
	color: var(--fg);
}

.docs-neighbour:hover {
	border-color: var(--border-strong);
	background: var(--bg-hover);
}

.docs-neighbour.next {
	margin-left: auto;
	text-align: right;
}

/* An end of the site has nothing on that side, and the other one stays put. */
.docs-neighbour.none {
	border: 0;
	padding: 0;
	pointer-events: none;
}

.docs-neighbour .way {
	font-size: var(--text-sm);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: var(--fg-faint);
}

.docs-neighbour .title {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* "Last reviewed", at the foot of the page where GOV.UK puts it. */
.docs-content p.docs-reviewed:where(:not(.docs-layout-screen *)) {
	margin: 44px 0 0;
	padding-top: 12px;
	border-top: 1px solid var(--border);
	font-size: 12.5px;
	color: var(--fg-faint);
}

/* What a verified pass still needs, under the date. */
.docs-content p.docs-verify:where(:not(.docs-layout-screen *)) {
	margin: 4px 0 0;
	font-size: 12.5px;
	line-height: 1.55;
	color: var(--fg-faint);
}

.docs-page .docs-article {
	width: 100%;
	max-width: 940px;
}

/* Prose stays narrower than the tables it sits between: 100 characters of body
   text is tiring, 100 characters of table is fine. */
/* Structural lists — the pin reference — are not prose and want the full
   measure, the same as a table does. */
.docs-page .docs-article > p,
.docs-page .docs-article > ul:not(.docs-pins),
.docs-page .docs-article > ol {
	/* The article's own width is the measure. Capping prose inside it left
	   sentences wrapping at half the column beside tables that ran the full
	   width, which read as unfinished rather than as a reading measure. */
	max-width: none;
}

@media (min-width: 1500px) {
	.docs-page .docs-body { grid-template-columns: 288px minmax(0, 1fr) 240px; }
	.docs-page .docs-article { max-width: 1040px; }
}

/* ------------------------------------------------- docs: the static build
 *
 * The same markup as the in-app panel, with two differences the browser
 * supplies rather than React: the nav sections are <details>, and the links are
 * anchors rather than buttons.
 */
.docs-static .docs-link,
.docs-static .docs-hit,
.docs-static .docs-palette-hit { text-decoration: none; color: inherit; }

.docs-static .docs-section-head {
	list-style: none;
	display: flex;
	align-items: center;
	gap: 6px;
}

.docs-static .docs-section-head::-webkit-details-marker { display: none; }

/* The chevron is an icon component in the app and cannot be here, so the
   disclosure triangle is drawn in CSS and rotated the same way. */
.docs-static .docs-section-head::before {
	content: "";
	width: 0;
	height: 0;
	border-left: 4px solid currentColor;
	border-top: 3.5px solid transparent;
	border-bottom: 3.5px solid transparent;
	opacity: 0.55;
	transition: transform 0.12s ease;
	flex: none;
}

.docs-static details[open] > .docs-section-head::before { transform: rotate(90deg); }

@media (prefers-reduced-motion: reduce) {
	.docs-static .docs-section-head::before { transition: none; }
}

.docs-static .docs-page-head .logo { text-decoration: none; color: inherit; }

/* A nav section holding one page is rendered as that page's link. No chevron,
   because there is nothing to unfold, and it takes the selected state a page
   link would. */
.docs-section-head.solo {
	padding-left: 6px;
	text-decoration: none;
}

.docs-static .docs-section-head.solo::before { content: none; }

.docs-section-head.solo.on {
	background: color-mix(in srgb, var(--accent) 20%, transparent);
	color: var(--fg);
}

/* ------------------------------------------------ docs: headings and measure */

/* A heading can carry a right-aligned aside — a version's date, a status. The
   heading stays the thing you read; the aside is the thing you check. */
.docs-page .docs-content h2:where(:not(.docs-layout-screen *)) {
	display: flex;
	align-items: baseline;
	gap: 14px;
}

.docs-page .docs-content h2 .aside:where(:not(.docs-layout-screen *)) {
	margin-left: auto;
	font-size: 12px;
	font-weight: 400;
	color: var(--fg-faint);
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}

/* A page that is mostly prose sets its measure once, on the article, so the
   rules, notes and paragraphs all share one right edge. A wide page caps its
   paragraphs individually instead, because its tables want the full width. */
.docs-page .docs-article.narrow { max-width: 78ch; }

.docs-page .docs-article.narrow > p,
.docs-page .docs-article.narrow > ul,
.docs-page .docs-article.narrow > ol { max-width: none; }

/* Release entries are ruled apart, the way Roblox rules its own. The version is
   the heading; the date is the aside. */
.docs-page .docs-content h2:where(:not(.docs-layout-screen *)) {
	border-top: 1px solid var(--border);
	padding-top: 20px;
	margin-top: 34px;
}

.docs-page .docs-content h2:first-of-type:where(:not(.docs-layout-screen *)) { border-top: 0; padding-top: 0; }

/* ---------------------------------------------------------- compile toast */

/* A project compile, in the bottom-right corner of the graph.
 *
 * Separate from the status panel on purpose: that panel is script analysis and
 * answers "what is wrong with this graph". A compile's progress is neither
 * about the open graph nor about anything being wrong, and a thousand rows of
 * good news in there buries the one diagnostic the panel exists for.
 *
 * It floats rather than taking a row of its own, because it is temporary — the
 * graph underneath is what you were looking at and will be again in a moment. */
/* A notice: what happened and where, dropping from the top centre of the
   window and gone again. See Toast.tsx. The layer takes no clicks, so the
   canvas under it stays live. */
.toast-layer {
	position: fixed;
	left: 0;
	right: 0;
	top: calc(var(--chrome-top) + env(safe-area-inset-top, 0px));
	z-index: 69;
	display: flex;
	justify-content: center;
	padding: 0 16px;
	pointer-events: none;
}

.toast {
	display: inline-flex;
	align-items: center;
	gap: 10px;
	max-width: min(520px, 100%);
	min-height: 40px;
	box-sizing: border-box;
	padding: 6px 18px 6px 7px;
	border-radius: 999px;
	background: var(--glass);
	border: 1px solid var(--border);
	box-shadow: var(--shadow-popover);
	backdrop-filter: blur(14px) saturate(1.2);
	-webkit-backdrop-filter: blur(14px) saturate(1.2);
	opacity: 0;
	transform: translateY(-10px) scale(0.96);
	transition:
		opacity 160ms ease,
		transform 260ms cubic-bezier(0.2, 0.9, 0.3, 1.25);
}

.toast.shown {
	opacity: 1;
	transform: none;
}

.toast-icon {
	flex: none;
	display: grid;
	place-items: center;
	width: 28px;
	height: 28px;
	border-radius: 50%;
	background: color-mix(in srgb, var(--accent) 18%, transparent);
	color: var(--accent);
}

.toast.ok .toast-icon {
	background: color-mix(in srgb, var(--ok) 18%, transparent);
	color: var(--ok);
}

.toast.warn .toast-icon {
	background: color-mix(in srgb, var(--warning) 20%, transparent);
	color: var(--warning);
}

.toast-text {
	display: grid;
	min-width: 0;
	line-height: 1.3;
}

.toast-text b {
	font-weight: 600;
	font-size: var(--text-md);
}

.toast-text b strong {
	color: var(--accent);
}

.toast-text span {
	font-size: var(--text-sm);
	color: var(--fg-muted);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
	.toast {
		transition: opacity 120ms ease;
		transform: none;
	}
}

/* A notice that opens its details. The layer takes no clicks; this one does. */
.toast.toast-clickable {
	pointer-events: auto;
	cursor: pointer;
}

.toast.toast-clickable:hover {
	border-color: var(--border-strong);
}

.compile-toast {
	position: absolute;
	right: 16px;
	bottom: 16px;
	width: 260px;
	padding: 8px 10px 9px;
	background: var(--bg-panel);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	box-shadow: var(--shadow-raised);
	font-size: var(--text-sm);
	cursor: pointer;
	z-index: 5;
}

/* On a touch screen the bottom is the bar, Script analysis and the Code panel,
   and the card lay over them; under the top row the graph is all there is. */
.workspace.compact .compile-toast {
	top: var(--chrome-top);
	bottom: auto;
	right: var(--edge);
	max-width: calc(100% - var(--edge) * 2);
}

.compile-toast .head {
	display: flex;
	align-items: baseline;
	gap: 8px;
}

.compile-toast .what {
	font-weight: 600;
	color: var(--fg);
}

.compile-toast .count {
	margin-left: auto;
	color: var(--fg-faint);
	font-variant-numeric: tabular-nums;
}

/* The file being written, or what happened once none is. One line, and clipped
   rather than wrapped: a toast that changes height as it counts is a toast
   that draws the eye every time a file finishes. */
.compile-toast .detail {
	margin-top: 2px;
	color: var(--fg-muted);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.compile-toast.has-failures .detail {
	color: var(--danger);
}

/* Determinate, because the number of files is known before the walk starts.
   An indeterminate spinner would throw away the one thing that tells slow
   apart from stuck, which is the whole reason this exists. */
.compile-toast .track {
	margin-top: 7px;
	height: 3px;
	border-radius: var(--radius-xs);
	background: var(--bg-input);
	overflow: hidden;
}

.compile-toast .track .fill {
	display: block;
	height: 100%;
	background: var(--accent);
	transition: width 0.12s linear;
}

.compile-toast.has-failures .track .fill {
	background: var(--danger);
}

/* ------------------------------------------------------- the compile lock */

/* The graph while it is being compiled, outside hot reload.
 *
 * The border is on the canvas rather than on the overlay because `inset` on an
 * absolutely positioned child sits *inside* the scroll box and would slide
 * under the status panel; drawn here it frames the graph exactly. */
.canvas.canvas--locked {
	box-shadow: inset 0 0 0 2px var(--accent);
}

/* Swallows the pointer for the whole canvas, so no handler underneath has to
   check whether it is allowed to run. A second or two, and then it is gone. */
.canvas-lock {
	position: absolute;
	inset: 0;
	display: flex;
	align-items: flex-start;
	justify-content: center;
	cursor: progress;
	/* No dimming. The graph is what you are waiting to get back, and greying it
	   out to say "wait" hides the thing the waiting is about. */
	background: color-mix(in srgb, var(--accent) 5%, transparent);
}

.canvas-lock span {
	/* Below the floating tools along the canvas's top edge, not under them. */
	margin-top: 52px;
	padding: 4px 12px;
	font-size: var(--text-sm);
	font-weight: 600;
	color: var(--bg-panel);
	background: var(--accent);
	border-radius: 0 0 var(--radius-sm) var(--radius-sm);
	box-shadow: var(--shadow-raised);
}

/* A panel that edits the graph, while the graph is refusing edits.
 *
 * The store is what actually refuses them; this is so the panel does not sit
 * there looking like it took one. Inert rather than disabled control by
 * control: there are eighteen of them across the two panels, and the one
 * somebody forgets to add `disabled` to next year is the bug. */
.editing-locked {
	pointer-events: none;
	opacity: 0.5;
}

/* ------------------------------------------- release notes: tags and rows */

/* What kind of release this was, under its version number.
 *
 * Derived from what the release contains — see `releaseTags` — so a tag cannot
 * claim something the entries below it do not show. Breaking is the exception
 * and the only one a release states for itself, so it is the only one that
 * borrows the warning colour. */
.docs-tags {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
	margin: -4px 0 12px;
}

.docs-tag {
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.04em;
	padding: 2px 8px;
	border-radius: var(--radius-xs);
	border: 1px solid color-mix(in srgb, var(--fg-faint) 40%, transparent);
	color: var(--fg-muted);
	white-space: nowrap;
}

.docs-tag.tag-feature {
	color: var(--ok);
	border-color: color-mix(in srgb, var(--ok) 45%, transparent);
	background: color-mix(in srgb, var(--ok) 10%, transparent);
}

.docs-tag.tag-fix {
	color: var(--accent);
	border-color: color-mix(in srgb, var(--accent) 45%, transparent);
	background: color-mix(in srgb, var(--accent) 10%, transparent);
}

.docs-tag.tag-security {
	color: color-mix(in srgb, var(--accent) 45%, var(--danger));
	border-color: color-mix(in srgb, color-mix(in srgb, var(--accent) 45%, var(--danger)) 50%, transparent);
	background: color-mix(in srgb, color-mix(in srgb, var(--accent) 45%, var(--danger)) 10%, transparent);
}

.docs-tag.tag-breaking {
	color: var(--danger);
	border-color: color-mix(in srgb, var(--danger) 50%, transparent);
	background: color-mix(in srgb, var(--danger) 10%, transparent);
}

/* The surface tags read as a second row of information on one line, so they are
   deliberately quieter than the four that say what kind of change it is: an
   outline and no fill. Which part of the tool changed is context for the
   entries, not a claim about them. */
.docs-tag.tag-editor,
.docs-tag.tag-designer,
.docs-tag.tag-docs {
	color: var(--fg-faint);
	border-style: dashed;
}

/* A table with no header row: one release entry per row.
 *
 * A dot in front of four lines of prose stops separating them — the eye loses
 * which sentence started where. A ruled row is the same information with the
 * boundary drawn, and the box around it says these belong together without
 * needing a heading over a single column to say "Note". */
.docs-table.bare {
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	overflow: hidden;
	margin-bottom: 16px;
}

.docs-table.bare td {
	padding: 10px 12px;
	font-size: var(--text-md);
	line-height: 1.55;
}

/* The last row's rule would sit on top of the container's own border. */
.docs-table.bare tr:last-child td {
	border-bottom: none;
}

/* A rule between releases.
 *
 * Scoped by `:has(+ .docs-tags)` — a heading immediately followed by tags is a
 * release heading, and nothing else on the site is. That avoids inventing a
 * page-level class to carry one border, and a browser without `:has()` loses
 * the rule and nothing else.
 *
 * Not on the first: the article's own title rule is directly above it, and two
 * lines a paragraph apart read as a mistake. */
.docs-content h2:has(+ .docs-tags):not(:first-of-type):where(:not(.docs-layout-screen *)) {
	border-top: 1px solid var(--border);
	padding-top: 26px;
	margin-top: 34px;
}

/* A whole graph, rather than a row of separate nodes.
 *
 * It gets to run the full width and scroll, because a graph's shape is the
 * thing being shown and squeezing it to a reading measure would fold the wires
 * back on themselves. The frame is the same one a single node preview uses, so
 * the two read as the same kind of illustration. */
/* A graph you can move around in, rather than one squashed to fit.
 *
 * Fit-to-width was the first answer and the wrong one: a graph of any size
 * becomes unreadable, and the scrollbar it replaced was chrome on a page that
 * has none. This is a window onto the graph instead — `graphView.ts` gives it
 * the canvas's own wheel-zoom and drag-pan, and double-click refits.
 *
 * Everything here works without that script. The viewport clips, the graph
 * renders at its natural size, and the page is still readable — which is what
 * the static site promises. `interactive` is added by the script, so the
 * cursor only claims to be draggable once something is listening. */
/* The graph figure carries no frame of its own: the viewport below has one,
   and nesting them put a grid and a second border behind the caption, which
   read as part of the canvas when it is a sentence about it. */
.docs-preview.graph {
	padding: 0;
	border: none;
	border-radius: 0;
	background: none;
}

.docs-preview.graph figcaption {
	padding: 8px 2px 0;
}

.docs-preview.graph .graph-viewport {
	position: relative;
	overflow: hidden;
	height: 260px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--bg-canvas);
	/* The canvas's grid, at rest, so the frame reads as a piece of the editor. */
	background-image:
		linear-gradient(to right, color-mix(in srgb, var(--border) 55%, transparent) 1px, transparent 1px),
		linear-gradient(to bottom, color-mix(in srgb, var(--border) 55%, transparent) 1px, transparent 1px);
	background-size: 24px 24px;
}

.docs-preview.graph .graph-viewport.interactive {
	cursor: grab;
	touch-action: none;
	/* The graph is full of `<text>`, so a drag across it would otherwise start a
	   text selection and the reader would highlight node titles instead of
	   panning. Nothing in a diagram is worth selecting — the Luau underneath is
	   the copyable part, and it still is. */
	user-select: none;
	-webkit-user-select: none;
}

.docs-preview.graph .graph-viewport.panning {
	cursor: grabbing;
}

.docs-preview.graph .graph-viewport svg {
	display: block;
	/* Positioned by `transform`; the script owns both. */
	position: absolute;
	top: 0;
	left: 0;
}

/* Says what the frame does, without a row of buttons.
 *
 * Only once the script has claimed the frame, because otherwise it would
 * promise something static markup cannot do — and only on hover, because a
 * permanent label sits over the bottom-right of every graph whether or not
 * anybody is looking for it. The `grab` cursor is the always-on affordance. */
.docs-preview.graph .graph-viewport.interactive::after {
	opacity: 0;
	transition: opacity 0.12s ease;
	content: "scroll to zoom · drag to pan · double-click to fit";
	position: absolute;
	right: 8px;
	bottom: 6px;
	font-size: var(--text-xs);
	color: var(--fg-faint);
	background: color-mix(in srgb, var(--bg-panel) 80%, transparent);
	border-radius: var(--radius-xs);
	padding: 1px 6px;
	pointer-events: none;
}

.docs-preview.graph .graph-viewport.interactive:hover::after {
	opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
	.docs-preview.graph .graph-viewport.interactive::after { transition: none; }
}

/* ------------------------------------------------------------- settings */

/* Settings: a sheet of its own over the window, the same in the editor, Node
   Design, the docs window and the docs site. Down the left, the pages under
   where they are kept -- this project's roswaal.json, or this browser -- with a
   search over all of them; on the right, a page's groups of rows, each row a
   label and its explanation with the control at a fixed column, so a column
   of controls lines up and can be scanned. See `SettingsPanel.tsx`. */
.settings-sheet {
	display: grid;
	grid-template-columns: 236px minmax(0, 1fr);
	width: min(1040px, 100%);
	height: min(760px, 100%);
	background: var(--bg-panel);
	border: 1px solid var(--border-strong);
	border-radius: 14px;
	box-shadow: var(--shadow-modal);
	overflow: hidden;
	outline: none;
}

/* The docs site's: two pages, so a smaller sheet. */
.settings-compact {
	grid-template-columns: 210px minmax(0, 1fr);
	width: min(820px, 100%);
	height: min(560px, 100%);
}

.settings-nav {
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-height: 0;
	padding: 16px 12px;
	overflow: auto;
	background: color-mix(in srgb, var(--bg-canvas) 30%, var(--bg-panel));
	border-right: 1px solid var(--border);
}

.settings-brand {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 0 6px 12px;
	font-size: var(--text-lg);
}

.settings-search {
	display: flex;
	align-items: center;
	gap: 8px;
	margin-bottom: 6px;
	padding: 6px 10px;
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: 8px;
	color: var(--fg-faint);
}

.settings-search:focus-within {
	border-color: var(--accent);
}

.settings-search input {
	flex: 1;
	min-width: 0;
	border: 0;
	outline: none;
	background: transparent;
	color: var(--fg);
	font: inherit;
}

.settings-keeper {
	display: flex;
	flex-direction: column;
	gap: 2px;
	margin-top: 8px;
}

/* Licences, at the foot of the list. */
.settings-keeper.keeper-about {
	margin-top: auto;
	padding-top: 12px;
}

.settings-keeper-head {
	display: flex;
	align-items: baseline;
	gap: 6px;
	padding: 6px 10px 4px;
	font-size: var(--text-sm);
	color: var(--fg-muted);
}

.settings-keeper-head code {
	font-size: var(--text-xs);
	color: var(--fg-faint);
}

.settings-link {
	padding: 7px 10px;
	border: 0;
	border-radius: 7px;
	background: transparent;
	color: var(--fg);
	font: inherit;
	text-align: left;
	cursor: pointer;
}

.settings-link:hover {
	background: var(--bg-hover);
}

.settings-link.on {
	background: color-mix(in srgb, var(--accent) 16%, transparent);
	font-weight: 600;
}

/* The docs' note on where the rest is. */
.settings-elsewhere {
	margin: auto 4px 0;
	padding: 10px;
	border: 1px dashed var(--border);
	border-radius: 8px;
	font-size: var(--text-sm);
	line-height: 1.45;
	color: var(--fg-muted);
}

.settings-elsewhere a {
	color: var(--accent);
}

.settings-keeper.keeper-about + .settings-elsewhere {
	margin-top: 10px;
}

.settings-main {
	position: relative;
	display: flex;
	flex-direction: column;
	min-width: 0;
	min-height: 0;
	background: color-mix(in srgb, var(--bg-canvas) 14%, var(--bg-panel));
}

.settings-close {
	position: absolute;
	top: 14px;
	right: 14px;
	z-index: 1;
}

.settings-current,
.settings-results {
	flex: 1;
	min-height: 0;
	overflow: auto;
	padding: 20px 26px 32px;
}

.settings-page-head {
	padding-right: 44px;
}

.settings-page-head h2 {
	margin: 0 0 4px;
	font-size: var(--text-xl);
}

.settings-page-head .settings-note {
	margin: 0;
}

.settings-group h3 {
	margin: 22px 4px 8px;
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--fg-muted);
}

.settings-box {
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: 10px;
}

.settings-box > .setting {
	padding: 14px 16px;
}

.settings-box > .setting:first-child {
	border-top: 0;
}

/* Searching: every page's matches in turn, and a page or a group with none
   left out. A page found by its name shows whole. */
.settings-results .settings-section + .settings-section {
	margin-top: 30px;
}

.settings-results .settings-group:not(:has(.setting)) {
	display: none;
}

.settings-results .settings-section:not(.matched):not(:has(.setting)) {
	display: none;
}

.settings-empty {
	display: none;
	color: var(--fg-muted);
}

.settings-results:not(:has(.setting, .settings-section.matched)) .settings-empty {
	display: block;
}

/* Segmented controls as pills on a track, the one in force raised. */
.settings-sheet .segmented {
	gap: 2px;
	padding: 2px;
	border: 0;
	border-radius: 8px;
	background: color-mix(in srgb, var(--bg-canvas) 70%, var(--bg-panel));
}

.settings-sheet .segmented button {
	border-radius: 6px;
	padding: 5px 10px;
}

.settings-sheet .segmented button.on {
	background: var(--bg-input);
	color: var(--fg);
	font-weight: 600;
	box-shadow: 0 1px 2px rgb(0 0 0 / 14%);
}

/* An on-off setting as a switch, its words beside it. The knob is part of
   the background: Safari draws nothing in a checkbox's ::before. */
.settings-sheet .settings-toggle {
	justify-content: flex-end;
	gap: 10px;
	padding: 2px 0;
	border: 0;
	background: none;
	color: var(--fg-muted);
}

.settings-sheet .settings-toggle input {
	appearance: none;
	-webkit-appearance: none;
	flex: none;
	order: 1;
	width: 34px;
	height: 20px;
	margin: 0;
	border-radius: 999px;
	background:
		radial-gradient(7.5px 7.5px at 10px 50%, #ffffff 92%, transparent 100%),
		var(--border-strong);
	cursor: pointer;
	transition: background 150ms ease;
}

.settings-sheet .settings-toggle input:checked {
	background:
		radial-gradient(7.5px 7.5px at calc(100% - 10px) 50%, #ffffff 92%, transparent 100%),
		var(--accent);
}

.settings-sheet .settings-toggle input:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
}

/* Something a node needs that the script has not got, said in the Inspector
   where the node is selected -- and with the button that fixes it. Said and
   offered, never done: declaring a module because somebody picked a call would
   be the editor expanding what the project depends on without being asked. */
.inspector-warn {
	margin: 8px 0 4px;
	padding: 9px 10px;
	border-radius: var(--radius-sm);
	border-left: 3px solid var(--warning);
	background: color-mix(in srgb, var(--warning) 10%, transparent);
	line-height: 1.5;
}

.inspector-warn p {
	margin: 0 0 8px;
}

/* -------------------------------------------------------- .luaurc document */

/* A `.luaurc`, opened from the tree like any other file. It borrows the
   settings rows because it is the same kind of thing to look at -- a name, what
   it does, and the control that changes it -- and the page around them is this
   file's own. */
.alias-doc {
	overflow: auto;
	padding: 20px 24px 40px;
	height: 100%;
}

/* Everything here shares one right edge, and it is the wide one.
   `.settings-warn` and the notes cap themselves at a reading measure, which is
   right inside a settings panel a third of the screen across and wrong here:
   this is a document, the rows already use the full width, and a paragraph that
   stops two-thirds short reads as a column that failed to load rather than as a
   line kept short on purpose. */
.alias-doc .settings-warn,
.alias-doc .settings-note,
.alias-doc .settings-problem,
.alias-doc .alias-scope {
	max-width: none;
}

/* A `.setting` is a wide label and a narrow control, which is right for a
   preference: most of the row is the sentence explaining it. An alias is the
   other shape -- a short name and a path -- so the columns are the other way
   round. Borrowing the settings row wholesale put the name in a column wide
   enough for a paragraph and the path in a box at the far end of it. */
.alias-doc .alias-row {
	grid-template-columns: minmax(140px, 220px) 1fr;
	gap: 12px;
	align-items: center;
}

/* The resolved path under the name, which is the thing being checked. */
.alias-doc .alias-row .setting-label {
	gap: 1px;
	min-width: 0;
}

.alias-doc .alias-row .setting-label span {
	overflow-wrap: anywhere;
}

.alias-doc-head h1 {
	margin: 0 0 4px;
	font-size: var(--text-xl);
}

.alias-doc h2 {
	margin: 28px 0 4px;
	font-size: var(--text-lg);
}

.alias-scope {
	margin: 0 0 18px;
	color: var(--fg-muted);
	line-height: 1.55;
	max-width: 64ch;
}

/* The value and the buttons that act on it, on one line: the path is the thing
   being edited and the rest is furniture beside it. */
.alias-control {
	display: flex;
	gap: 6px;
	align-items: center;
}

.alias-control input {
	flex: 1;
	min-width: 0;
}

.alias-row .setting-label input {
	margin-bottom: 3px;
}

/* Where an inherited alias was defined. Not a control: it belongs to another
   file, and the way to change it is to open that one. */
.alias-from {
	justify-content: flex-end;
	color: var(--fg-faint);
}

.alias-inherited .setting-control {
	opacity: 0.8;
}

/* Said where it happened rather than in a toast: an edit that was refused is
   about the file the reader is looking at, and a message that floats away takes
   the reason with it. */
.settings-warn {
	margin: 0 0 14px;
	padding: 10px 12px;
	border-radius: var(--radius-sm);
	border-left: 3px solid var(--warning);
	background: color-mix(in srgb, var(--warning) 10%, transparent);
	line-height: 1.55;
	max-width: 64ch;
}

.settings-problem {
	margin: 4px 0;
	line-height: 1.5;
	max-width: 64ch;
}

.settings-problem.error { color: var(--danger); }
.settings-problem.warning { color: var(--warning); }

/* The published site's settings popover: the editor's panel, sized for the two
   preferences a reader of a page can change. Everything inside it is the app's
   own -- only the frame is smaller, because a full-height dialog for two rows
   is a dialog that looks like it failed to load the rest. */
.docs.docs-compact {
	width: min(560px, 100%);
	height: auto;
	max-height: min(520px, 100%);
}

.settings-note {
	margin: 0 0 14px;
	color: var(--fg-muted);
	line-height: 1.55;
	max-width: 64ch;
}

.settings-note code {
	background: var(--capsule-bg);
	border: 1px solid var(--capsule-border);
	border-radius: var(--radius-xs);
	padding: 0 4px;
}

.setting {
	display: grid;
	grid-template-columns: 1fr 260px;
	gap: 16px;
	align-items: start;
	padding: 11px 0;
	border-top: 1px solid var(--border);
}

.setting-label {
	display: flex;
	flex-direction: column;
	gap: 3px;
}

.setting-label span {
	color: var(--fg-faint);
	line-height: 1.5;
}

.setting-control {
	display: flex;
	justify-content: flex-end;
}

.setting-control > * {
	width: 100%;
}

/* A segmented control is `inline-flex` everywhere else, because on the toolbar
   it should be exactly as wide as its labels. Here it sits in a fixed column
   beside a select and a checkbox, so it takes the column — and its buttons have
   to take the control, or the border stretches to the full width while the two
   halves huddle at the left end of it and the empty remainder reads as a third,
   broken option.

   `flex: 1` rather than natural widths, so the two halves are equal whatever
   they are labelled: "Manually" and "On every change" are very different
   lengths, and a divider that lands in a different place on every row is a
   worse answer than one that is always in the middle. */
.setting-control .segmented {
	display: flex;
}

.setting-control .segmented button {
	flex: 1;
	text-align: center;
}

.settings-toggle {
	display: flex;
	align-items: center;
	gap: 7px;
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	padding: 4px 9px;
	cursor: pointer;
}

.settings-toggle:hover {
	border-color: var(--border-strong);
}

.path-list {
	display: flex;
	flex-direction: column;
	gap: 6px;
	max-width: 520px;
}

.path-row {
	display: flex;
	gap: 6px;
}

.path-row input {
	flex: 1;
}

/* ----------------------------------------------------------- theme cards */

.theme-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
	gap: 10px;
	margin-bottom: 16px;
}

.theme-card {
	display: flex;
	flex-direction: column;
	gap: 4px;
	padding: 6px;
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	cursor: pointer;
	text-align: left;
	color: inherit;
	font: inherit;
}

.theme-card:hover {
	border-color: var(--border-strong);
}

/* The chosen scheme is outlined rather than filled: the card's whole job is to
   show you a palette, and painting our accent across it would be showing you
   ours instead. */
.theme-card.on {
	border-color: var(--accent);
	box-shadow: 0 0 0 1px var(--accent);
}

.theme-swatch,
.theme-swatch-pair {
	display: block;
	width: 100%;
	aspect-ratio: 160 / 78;
	border-radius: var(--radius-sm);
	overflow: hidden;
	border: 1px solid var(--border);
}

/* Follow-the-system has two palettes and no single answer, so it shows both,
   split down the middle the way the setting actually behaves. */
.theme-swatch-pair {
	position: relative;
}

.theme-swatch-pair .half {
	position: absolute;
	inset: 0;
	width: 50%;
	overflow: hidden;
}

.theme-swatch-pair .half svg {
	width: 200%;
	height: 100%;
}

.theme-swatch-pair .half.right {
	left: 50%;
}

.theme-swatch-pair .half.right svg {
	margin-left: -100%;
}

.theme-name {
	font-weight: 600;
	padding: 0 2px;
}

.theme-credit {
	font-size: var(--text-sm);
	color: var(--fg-faint);
	padding: 0 2px 2px;
	min-height: 14px;
}

.role-table {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
	gap: 2px 16px;
}

.role {
	display: grid;
	grid-template-columns: 14px 118px 1fr;
	gap: 8px;
	align-items: center;
	padding: 3px 0;
}

.role .chip {
	width: 14px;
	height: 14px;
	border-radius: var(--radius-xs);
	border: 1px solid var(--border-strong);
}

.role span:last-child {
	color: var(--fg-faint);
}

/* -------------------------------------------------------------- licences */

/* Settings › Licences: one row per licence, opening to its text. See
   `LicenceView.tsx`, which the Attributions page in the docs shares. */
.settings-box > .licence-row {
	display: block;
	padding: 0;
}

.licence-head {
	display: flex;
	align-items: center;
	gap: 8px;
	width: 100%;
	min-height: 44px;
	padding: 0 14px;
	border: 0;
	background: none;
	color: var(--fg);
	font: inherit;
	text-align: left;
	cursor: pointer;
}

.licence-head:hover {
	background: var(--bg-hover);
}

.licence-name {
	font-weight: 600;
}

.licence-holder {
	margin-left: auto;
	color: var(--fg-faint);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.spdx {
	font-size: var(--text-sm);
	color: var(--fg-muted);
	border: 1px solid var(--capsule-border);
	background: var(--capsule-bg);
	border-radius: var(--radius-xs);
	padding: 1px 6px;
	white-space: nowrap;
}

.licence-detail {
	display: flex;
	flex-direction: column;
	gap: 10px;
	padding: 4px 16px 16px;
}

.licence-meta {
	display: flex;
	flex-direction: column;
	gap: 3px;
	font-size: var(--text-sm);
	line-height: 1.5;
	color: var(--fg-muted);
}

.licence-hash code {
	overflow-wrap: anywhere;
}

/* The text in a read-only editor view: tall enough to read a licence through,
   and scrolling past that rather than pushing the page away. */
.licence-view {
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	overflow: hidden;
}

.licence-view .cm-editor {
	max-height: 360px;
	font-size: 12px;
}

.licence-view .cm-scroller {
	overflow: auto;
}

.licence-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
}

.licence-actions a.tb {
	display: inline-flex;
	align-items: center;
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	padding: 4px 9px;
	color: var(--fg);
	text-decoration: none;
}

.licence-actions a.tb:hover {
	background: var(--bg-hover);
	border-color: var(--border-strong);
}

/* The docs' licences: a fold per licence. On the static site the text is a
   numbered list, the numbers drawn by the list so copying takes only the
   licence; in the editor's docs it is the same editor view as Settings. */
.docs-licences {
	display: flex;
	flex-direction: column;
	gap: 8px;
	margin: 12px 0;
}

/* The name and its licence on one line, as in Settings. */
.docs-content details.docs-licence > summary .docs-details-title {
	flex-direction: row;
	align-items: center;
	gap: 8px;
}

.docs-licence .docs-details-title .spdx {
	flex: none;
	font-size: 12px;
	font-weight: 400;
}

.licence-meta a {
	color: var(--accent);
}

.docs-content .licence-meta code {
	padding: 0;
	border: 0;
	background: none;
	font-size: 12px;
	overflow-wrap: anywhere;
}

p.licence-meta,
.docs-licence .licence-meta,
.docs-licences > .licence-meta {
	display: block;
	margin: 6px 0 10px;
}

.docs-licence .licence-detail {
	padding: 0;
}

.licence-lines,
.docs-content ol.licence-lines {
	margin: 0;
	padding: 10px 14px 10px 0;
	list-style: none;
	counter-reset: licence-line;
	max-height: 360px;
	overflow: auto;
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	font: 12px/1.6 "Cascadia Mono", ui-monospace, Consolas, monospace;
	color: var(--fg);
}

/* The number in a column of its own, drawn rather than written, so a wrapped
   line hangs under its text and a copy takes the licence alone. */
.licence-lines li,
.docs-content .licence-lines li {
	display: grid;
	grid-template-columns: 40px minmax(0, 1fr);
	column-gap: 12px;
	margin: 0;
	padding: 0;
	min-height: 1.6em;
	line-height: 1.6;
	font-size: 12px;
	white-space: pre-wrap;
	overflow-wrap: anywhere;
	counter-increment: licence-line;
}

.licence-lines li::before {
	content: counter(licence-line);
	text-align: right;
	color: var(--fg-faint);
	user-select: none;
	-webkit-user-select: none;
}

/* ------------------------------------------------- attributions browser */

/* Every entry, findable by whoever holds it: a grid of holders against the
   seven ways Roswaal uses their work (Simple), and the list by holder with
   search and filters (Advanced). See `attributionsHtml.ts` and
   `attributionsBrowser.ts`. Each usage type has a hue, mixed into the theme so
   it reads in every scheme, and always its name beside it. */
.attr {
	--use-included: var(--accent);
	--use-quoted: #2f8a76;
	--use-target: #6a5acd;
	--use-works: var(--fg-muted);
	--use-example: var(--fg-muted);
	--use-inspired: #b8860b;
	--use-homage: #b0448a;
	container-type: inline-size;
	display: flex;
	flex-direction: column;
	gap: 14px;
	margin: 18px 0 30px;
}

.attr-use,
.attr-pick {
	--use: var(--use-works);
	background: color-mix(in srgb, var(--use) 16%, var(--bg-input));
	color: color-mix(in srgb, var(--use) 72%, var(--fg));
}

.use-included { --use: var(--use-included); }
.use-quoted { --use: var(--use-quoted); }
.use-target { --use: var(--use-target); }
.use-works { --use: var(--use-works); }
.use-inspired { --use: var(--use-inspired); }
.use-homage { --use: var(--use-homage); }

.attr-use.use-example,
.attr-pick.use-example {
	background: var(--bg-input);
	box-shadow: inset 0 0 0 1px var(--border-strong);
	color: var(--fg-muted);
}

.attr-use {
	display: inline-flex;
	align-items: center;
	height: 22px;
	padding: 0 9px;
	border-radius: 999px;
	font-size: 12px;
	font-weight: 600;
	white-space: nowrap;
}

/* The view switch: two radios and their labels, so it works with no script. */
.attr-radio {
	position: absolute;
	opacity: 0;
	pointer-events: none;
}

.attr-switch {
	display: inline-flex;
	align-self: flex-start;
	gap: 2px;
	padding: 3px;
	border-radius: 999px;
	background: var(--bg-active);
}

.attr-switch label {
	display: inline-flex;
	align-items: center;
	min-height: 34px;
	padding: 0 18px;
	border-radius: 999px;
	font-weight: 600;
	color: var(--fg-muted);
	cursor: pointer;
	user-select: none;
}

#attr-view-simple:checked ~ .attr-switch label[for="attr-view-simple"],
#attr-view-advanced:checked ~ .attr-switch label[for="attr-view-advanced"] {
	background: var(--bg-input);
	color: var(--fg);
	box-shadow: 0 1px 3px rgba(0, 0, 0, 0.16);
}

#attr-view-simple:focus-visible ~ .attr-switch label[for="attr-view-simple"],
#attr-view-advanced:focus-visible ~ .attr-switch label[for="attr-view-advanced"] {
	outline: 2px solid var(--accent);
	outline-offset: 1px;
}

#attr-view-simple:checked ~ .attr-advanced,
#attr-view-advanced:checked ~ .attr-simple {
	display: none;
}

.attr-hint {
	margin: 0 0 10px;
	color: var(--fg-muted);
}

/* Simple: the grid. */
.attr-grid-box {
	overflow-x: auto;
	border: 1px solid var(--border);
	border-radius: 12px;
	background: var(--bg-input);
}

.attr-grid {
	width: 100%;
	border-collapse: collapse;
	font-size: 12.5px;
	line-height: 1.3;
}

/* Everything centred in its cell, but the holders, which read as a list
   down the left and so start at the same edge, clear of the border. Prefixed
   with `.attr` so it outranks the docs' own table cells, which have no
   padding on the left and 14px on the right: enough, on a name that wraps
   and so fills its cell, to push the whole box off centre. */
.attr .attr-grid th,
.attr .attr-grid td {
	padding: 8px;
	border-top: 1px solid var(--border);
	text-align: center;
	vertical-align: middle;
}

.attr .attr-grid th:first-child {
	padding-left: 14px;
	text-align: left;
}

.attr-grid thead th {
	border-top: 0;
	background: var(--bg-panel);
	font-size: 11px;
	font-weight: 650;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--fg-muted);
}

.attr-grid thead .attr-use {
	letter-spacing: 0;
	text-transform: none;
}

.attr .attr-grid tbody th {
	min-width: 120px;
	max-width: 190px;
	font-size: 13px;
	font-weight: 600;
	letter-spacing: 0;
	text-transform: none;
	color: var(--fg);
	white-space: normal;
}

.attr-grid td + td,
.attr-grid th + td {
	border-left: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
}

.attr-pick {
	display: inline-block;
	margin: 2px;
	padding: 3px 7px;
	border: 0;
	border-radius: 6px;
	font-family: inherit;
	font-size: 12px;
	font-weight: 600;
	line-height: 1.3;
	text-align: left;
	cursor: pointer;
}

/* In the grid a name that wraps stays centred in its cell, as one line does. */
.attr-grid .attr-pick {
	text-align: center;
}

.attr-pick:hover {
	filter: brightness(0.95);
}

.attr-dot {
	color: var(--fg-faint);
}

.attr-grid-list,
.docs-content .attr ul.attr-grid-list {
	display: none;
	margin: 0;
	padding: 0;
	list-style: none;
	flex-direction: column;
	gap: 8px;
}

.attr-grid-list li {
	display: flex;
	flex-direction: column;
	gap: 6px;
	padding: 10px 12px;
	border: 1px solid var(--border);
	border-radius: 12px;
	background: var(--bg-input);
}

/* Advanced: search, filters, and the entries by holder. */
.attr-tools {
	display: flex;
	flex-direction: column;
	gap: 10px;
}

.attr-tools[hidden] {
	display: none;
}

.attr-search {
	display: flex;
	flex-direction: column;
	gap: 5px;
	font-size: 11px;
	font-weight: 650;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--fg-muted);
}

.attr-search input {
	height: 42px;
	padding: 0 14px;
	border: 1px solid var(--border-strong);
	border-radius: 10px;
	background: var(--bg-input);
	color: var(--fg);
	font: inherit;
	font-size: 15px;
	letter-spacing: 0;
	text-transform: none;
}

.attr-chips {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
}

.attr-chip {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	min-height: 34px;
	padding: 0 13px;
	border: 1px solid var(--border);
	border-radius: 999px;
	background: var(--bg-input);
	color: var(--fg);
	font: inherit;
	font-size: 13px;
	cursor: pointer;
}

.attr-chip span {
	font-weight: 600;
	opacity: 0.6;
}

.attr-chip[aria-pressed="true"] {
	background: var(--fg);
	border-color: var(--fg);
	color: var(--bg-input);
}

.attr-summary {
	margin: 0;
	font-size: 13px;
	font-weight: 600;
}

.attr-jumps {
	display: flex;
	flex-wrap: wrap;
	gap: 2px 12px;
	font-size: 13px;
}

.attr-jumps a[hidden] {
	display: none;
}

.attr-empty {
	padding: 28px;
	border: 1px solid var(--border);
	border-radius: 12px;
	text-align: center;
	color: var(--fg-muted);
}

.attr-groups {
	display: flex;
	flex-direction: column;
	gap: 22px;
	margin-top: 6px;
}

.attr-holder[hidden],
.attr-row[hidden] {
	display: none;
}

.attr-holder {
	scroll-margin-top: 80px;
}

.attr-holder h3 {
	display: flex;
	align-items: baseline;
	gap: 10px;
	margin: 0 4px 6px;
	font-size: 12px;
	font-weight: 650;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--fg-muted);
}

.attr-count {
	font-weight: 400;
	letter-spacing: 0;
	text-transform: none;
	color: var(--fg-faint);
}

.attr-statement {
	margin: 0 4px 8px;
	font-size: 13px;
	line-height: 1.5;
	color: var(--fg-muted);
}

.attr-rows {
	border: 1px solid var(--border);
	border-radius: 12px;
	background: var(--bg-input);
	overflow: hidden;
}

.attr-row {
	display: grid;
	grid-template-columns: minmax(130px, 190px) 112px 140px minmax(0, 1fr);
	gap: 14px;
	align-items: start;
	padding: 12px 16px;
	border-top: 1px solid var(--border);
	scroll-margin-top: 120px;
	transition: background-color 0.4s;
}

.attr-row:first-child {
	border-top: 0;
}

.attr-row.attr-found {
	background: color-mix(in srgb, var(--accent) 12%, var(--bg-input));
}

.attr-name {
	font-weight: 600;
	min-width: 0;
}

.attr-name a {
	color: var(--fg);
	text-decoration: none;
}

.attr-name a:hover {
	text-decoration: underline;
}

.attr-licence {
	display: inline-flex;
	align-items: center;
	min-height: 28px;
	padding: 0 10px;
	border: 1px solid var(--border-strong);
	border-radius: 8px;
	background: var(--bg-input);
	color: var(--fg);
	font: inherit;
	font-size: 12.5px;
	font-weight: 600;
	text-align: left;
	cursor: pointer;
}

.attr-licence:hover {
	border-color: var(--accent);
	color: var(--accent);
}

.attr-none {
	font-size: 13px;
	line-height: 28px;
	color: var(--fg-muted);
}

.attr-what {
	display: flex;
	flex-direction: column;
	gap: 4px;
	min-width: 0;
	line-height: 1.45;
}

.attr-where {
	font-size: 12.5px;
	color: var(--fg-muted);
	overflow-wrap: anywhere;
}

.attr-legend ul,
.docs-content .attr-legend ul {
	margin: 10px 0 0;
	padding: 0;
	list-style: none;
	display: flex;
	flex-direction: column;
	gap: 8px;
}

.attr-legend li {
	display: grid;
	grid-template-columns: 120px minmax(0, 1fr);
	gap: 12px;
	align-items: baseline;
	font-size: 13.5px;
	line-height: 1.5;
}

@container (max-width: 760px) {
	.attr-grid-box {
		display: none;
	}

	.attr-grid-list,
	.docs-content .attr ul.attr-grid-list {
		display: flex;
	}

	.attr-row {
		grid-template-columns: minmax(0, 1fr) auto;
		gap: 6px 12px;
	}

	.attr-row .attr-lic {
		grid-column: 2;
		grid-row: 1;
	}

	.attr-row .attr-how,
	.attr-row .attr-what {
		grid-column: 1 / -1;
	}

	.attr-legend li {
		grid-template-columns: minmax(0, 1fr);
		gap: 4px;
	}
}

/* A licence in a modal: the browser's own on the static site, the editor's
   read-only view in the app. */
.attr-dialog {
	width: min(780px, calc(100vw - 32px));
	max-height: calc(100vh - 48px);
	padding: 0;
	border: 1px solid var(--border);
	border-radius: 14px;
	background: var(--bg-panel);
	color: var(--fg);
	box-shadow: 0 24px 60px rgba(0, 0, 0, 0.3);
}

.attr-dialog[open] {
	display: flex;
	flex-direction: column;
	gap: 10px;
	padding: 16px 20px 18px;
}

.attr-dialog::backdrop {
	background: rgba(20, 22, 26, 0.45);
}

.attr-dialog-head {
	display: flex;
	align-items: center;
	gap: 12px;
}

.attr-dialog-head h2,
.docs-content .attr-dialog-head h2 {
	flex: 1;
	margin: 0;
	padding: 0;
	border: 0;
	font-size: 18px;
}

.attr-dialog .licence-lines {
	max-height: min(60vh, 520px);
}

.attr-dialog .licence-detail {
	padding: 0;
}

.attr-dialog .licence-meta {
	margin: 0;
}

.made-note {
	display: block;
	padding: 14px 16px;
	line-height: 1.55;
}

.made-note p {
	margin: 0;
}

.made-note p + p {
	margin-top: 8px;
	color: var(--fg-muted);
}

/* ------------------------------------------------- selection preview */

/* Deliberately narrower than the docs window: this is one column of code, and
   a wide box would set the eye scanning for a second column that is not there. */
.preview-luau {
	width: min(760px, 100%);
	height: min(620px, 100%);
	grid-template-rows: auto 1fr;
}

.preview-luau .docs-head .spacer {
	flex: 1;
}

.preview-toggle {
	display: flex;
	align-items: center;
	gap: 6px;
	font-size: var(--text-sm);
	color: var(--fg-muted);
	cursor: pointer;
}

.preview-body {
	overflow: auto;
	padding: 12px 14px 20px;
	min-height: 0;
}

.preview-note {
	margin: 0 0 12px;
	padding: 9px 11px;
	background: var(--capsule-bg);
	border: 1px solid var(--capsule-border);
	border-radius: var(--radius-sm);
	color: var(--fg-muted);
	line-height: 1.55;
}

.preview-code {
	margin: 0;
	font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
	font-size: var(--text-md);
	line-height: 1.6;
	white-space: pre;
	overflow-x: auto;
}

.preview-code .ln {
	display: block;
}

.preview-code .num {
	display: inline-block;
	width: 3.5ch;
	margin-right: 12px;
	text-align: right;
	color: var(--fg-faint);
	user-select: none;
}

/* The lines these nodes actually produced. A left rule rather than a fill,
   because the code is already carrying colour and a highlight behind it would
   fight the syntax rather than frame it. */
.preview-code .ln.mine {
	background: color-mix(in srgb, var(--accent) 14%, transparent);
	box-shadow: inset 2px 0 0 var(--accent);
}

.preview-code .ln.mine .num {
	color: var(--accent);
}

/* Where an inlined pure node's value ended up. Marked differently from a line
   the node produced, because it is a different claim. */
.preview-code .ln.downstream {
	background: color-mix(in srgb, var(--ok) 12%, transparent);
	box-shadow: inset 2px 0 0 var(--ok);
}

.preview-code .ln.downstream .num {
	color: var(--ok);
}

.preview-code .fold {
	display: block;
	color: var(--fg-faint);
	padding-left: 4.5ch;
	user-select: none;
}

/* ------------------------------------------- toolbars drawn in the docs */

/* A bar of the tool, drawn on the page it is documented on. See
   `src/core/docs/toolbars.ts` for why it is a replica rather than a
   screenshot: it takes the reader's theme, and it goes stale visibly.

   Everything inside the picture is the real chrome's own class names, so
   these rules only have to do two things — put the bar in a frame, and make
   sure nothing in it can be pressed. */
.docs-bar {
	margin: 0 0 22px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	overflow: hidden;
	background: var(--bg-panel);
}

/* A bar is as wide as it is. On a narrow page it scrolls sideways rather than
   wrapping into a shape the reader will never see on their own screen —
   wrapping was the first version, and it put Settings on a second row, which
   is the one thing the page must not say. */
.docs-bar-picture {
	overflow-x: auto;
}

/* The bar itself. Nothing in it does anything: no handlers, no tab stops, and
   `aria-hidden` on the frame, because the legend underneath is what carries the
   content. It does take the pointer, though -- hovering a button lights its
   explanation, which is the whole point of the page.

   The bar sits *inside* the frame rather than welded to its edges. Flush
   against the legend it read as the top row of a table -- part of the list
   rather than the thing the list is about -- and a control nobody reads as a
   control is not one they think to hover. A little of the app's own background
   around it, and the chrome becomes an object on a surface.

   Padding on the frame rather than on the scroller around it: the frame is the
   scrolled content, so its padding is part of the scrollable width and the
   trailing edge survives being scrolled to. On a padded scroller it would
   not. */
.docs-bar-frame {
	user-select: none;
	min-width: min-content;
	padding: 12px;
	background-color: var(--bg-app);
}

/* The drawn chrome, as a thing with edges. In the tool these bars run the full
   width of a window and have no outer border to draw; here they are a picture
   of one, so they get the corners that say where they stop. */
.docs-bar-frame.bar > .toolbar,
.docs-bar-frame.head > .docs-page-head {
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
}

/* A docked panel, drawn on a page.
 *
 * In the editor a panel fills its dock and scrolls; here it is a picture of
 * one, so it takes the width it needs and stops. The contents carry the
 * editor's own class names -- `.variable`, `.swatch`, `.variables-sub` -- which
 * is where the likeness comes from, so this rule only has to undo the dock. */
/* Centred in its frame: at its own width against the left edge, a wide page
   left it stranded beside an empty stage. A column, said here rather than
   inherited, because `.panel` and `.inspector` are the editor's own classes and
   bring their own flex direction with them -- centring on the wrong axis is
   what the first version of this did. `safe`, so a picture wider than the
   frame still scrolls from its left edge rather than losing it. */
.docs-bar-frame.panel,
.docs-bar-frame.inspector {
	display: flex;
	flex-direction: column;
	align-items: safe center;
}

.docs-bar-frame.panel > .variables {
	width: min(340px, 100%);
	padding: 4px 0 8px;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	background: var(--bg-panel);
}

/* The Inspector, drawn on a page.
 *
 * As the docked panel above: the contents carry the editor's own class names --
 * `.field`, `.list-row`, `.check-row` -- so the likeness is the editor's own
 * stylesheet rather than a second description of it. This rule only has to give
 * the picture its edges and stop it filling the page. */
.docs-bar-frame.inspector > .inspector {
	width: min(360px, 100%);
	padding: 12px;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	background: var(--bg-panel);
}

/* The rows the Inspector draws in a list editor, as pictures: no input to
   focus, so the boxes are spans and want the height a field has. */
/* The boxes. A drawn field is a span rather than an input -- nothing here is
   focusable -- so it needs the border and the fill an input paints itself. */
.docs-bar-frame.inspector .docs-bar-field,
.docs-bar-frame.inspector .docs-bar-select {
	display: block;
	box-sizing: border-box;
	width: 100%;
	min-width: 0;
	padding: 5px 8px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--bg-input);
	color: var(--fg);
	text-align: left;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* The select keeps its chevron, and the row it sits in does not grow for it. */
.docs-bar-frame.inspector .docs-bar-select {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 8px;
}

/* A picture of a panel does not scroll: a definition too long for the box is
   cut with an ellipsis, as the editor's own field cuts it. */
.docs-bar-frame.inspector > .inspector {
	overflow: hidden;
}

/* The project tree, the DataModel browser, the Properties panel and a dialog,
 * drawn on a page.
 *
 * As the panels above: the rows carry the editor's own classes -- `.tree-row`,
 * `.place-prop`, `.dialog` -- so the likeness is the editor's own stylesheet.
 * These rules give each picture its edges, set the editor's type size in an
 * article that sets its own, and restate the headings an article outspecifies. */
.docs-bar-frame.filetree,
.docs-bar-frame.place,
.docs-bar-frame.properties,
.docs-bar-frame.modal {
	display: flex;
	flex-direction: column;
	align-items: safe center;
}

.docs-bar-frame > .docs-tree-shot,
.docs-bar-frame > .docs-dialog-shot {
	font: 13px/1.45 "Segoe UI", system-ui, -apple-system, sans-serif;
	text-align: left;
}

.docs-bar-frame > .docs-tree-shot {
	flex: none;
	width: min(320px, 100%);
	padding: 8px 0 4px;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	background: var(--bg-panel);
}

.docs-bar-frame.properties > .docs-tree-shot {
	width: min(340px, 100%);
}

.docs-tree-shot .tree,
.docs-tree-shot .place-props {
	flex: none;
	overflow: visible;
}

.docs-bar-frame.properties .place-properties h2 {
	display: flex;
	align-items: center;
	justify-content: space-between;
	margin: 2px 0 6px;
	padding: 0 12px;
	border: 0;
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--fg-faint);
}

.docs-bar-frame.properties .place-properties h2 button.tb {
	font-size: var(--text-sm);
	padding: 1px 8px;
	text-transform: none;
	letter-spacing: 0;
}

/* A dialog where it sits rather than over a backdrop. */
.docs-bar-frame > .docs-dialog-shot {
	box-shadow: none;
}

.docs-bar-frame.modal h3 {
	margin: 0;
	font-size: var(--text-lg);
	font-weight: 600;
	letter-spacing: 0;
	text-transform: none;
	color: var(--fg);
}

.docs-bar-frame.modal p {
	margin: 0;
	font-size: var(--text-lg);
	line-height: 1.5;
	color: var(--fg-muted);
}

.docs-bar-frame.modal ul.dialog-list {
	margin: 0;
}

.docs-bar-frame.modal ul.dialog-list li {
	margin: 0;
}

.docs-bar-frame.modal .docs-bar-field,
.docs-bar-frame.modal .docs-bar-select {
	display: flex;
	align-items: center;
	box-sizing: border-box;
	width: 100%;
	min-width: 0;
	min-height: 28px;
	padding: 4px 8px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--bg-input);
	color: var(--fg);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.docs-bar-frame.modal .export-menu-name .docs-bar-field {
	width: auto;
	flex: 1;
}

/* A drawn list row takes the editor's grid; its boxes are spans, so they
   give up the drawn field's minimum width to fit it. */
.docs-bar-frame.inspector .list-row {
	margin-top: 2px;
}

.docs-bar-frame.inspector .list-row .docs-bar-field {
	min-width: 0;
	height: 28px;
	box-sizing: border-box;
}

/* The type field, drawn as a span: the button's box, which button.tb gives
   the real one. */
.docs-bar-frame.inspector span.tb.type-picker {
	height: 28px;
	box-sizing: border-box;
	padding: 0 9px;
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
}

/* The panel's headings, restated in full.
 *
 * They carry the editor's own `.variables-sub`, which is how every other part
 * of this picture gets its likeness -- but an article styles its headings, and
 * `.docs-page .docs-content h3` outspecifies a lone class. So the drawn panel
 * came out with the article's h3: twice the size, in the article's colour, and
 * not remotely what the editor shows.
 *
 * Qualified by the element to match that specificity, and the type is restated
 * rather than inherited, because inheriting is exactly what did not happen. */
.docs-bar-frame.panel h2 {
	display: flex;
	align-items: center;
	gap: 8px;
	margin: 10px 0 4px;
	padding: 0 10px;
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--fg-faint);
}

.docs-bar-frame.panel h2 button.tb {
	margin-left: auto;
	font-size: var(--text-sm);
	padding: 1px 8px;
}

.docs-bar-frame.panel h3.variables-sub {
	display: flex;
	align-items: center;
	gap: 6px;
	min-height: 24px;
	margin: 8px 0 2px;
	padding: 0 8px;
	font-size: calc(12em / 13);
	font-weight: 650;
	letter-spacing: 0;
	text-transform: none;
	color: var(--accent);
}

/* The line running on after the name, before any button at the end. */
.docs-bar-frame.panel h3.variables-sub::after {
	content: "";
	order: 1;
	flex: 1;
	min-width: 12px;
	height: 1px;
	background: color-mix(in srgb, var(--accent) 35%, var(--border));
}

.docs-bar-frame.panel h3.variables-sub > button.tb {
	order: 2;
	min-height: 0;
	height: 22px;
	margin-left: 0;
	padding: 0 7px;
	font-size: var(--text-sm);
	font-weight: 400;
}

.docs-bar-frame.panel .variable-head {
	padding: 3px 10px;
}

/* A section of a drawn panel: its heading and the rows under it.

   The picture has to be the whole panel -- a cropped one is a drawing of
   something nobody has -- but a page is about one section of it, and a reader
   given four equal sections has to find that out from the prose. So the page's
   own section keeps the light and the rest fall back: a scrim with a cutout,
   which is the same thing the words are already saying.

   Only the frame has the padding, so a section can carry a background edge to
   edge without a gap down either side. */
.docs-bar-frame.panel .panel-section {
	padding: 1px 0 3px;
	border-radius: var(--radius-sm);
	transition: opacity 120ms ease;
}

.docs-bar-frame.panel .panel-section.dim {
	opacity: 0.4;
}

.docs-bar-frame.panel .panel-section.focus {
	background: color-mix(in srgb, var(--accent) 9%, transparent);
	box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent);
}

/* The card's header, when its section is drawn: a band across the top, as
   the card's own is, held 1px inside the section so a lit section's edge
   runs unbroken round it rather than under the header's background. */
.docs-bar-frame.panel .panel-section > .card-head {
	margin: 0 1px;
	border-radius: calc(var(--radius-sm) - 1px) calc(var(--radius-sm) - 1px) 0 0;
}

/* Pointing wins over the scrim. Hovering a dimmed section's explanation lights
   it in the picture, and a lit section the reader cannot read is a highlight
   that hides the thing it is pointing at. */
.docs-bar-frame.panel .panel-section.dim:has(.lit) {
	opacity: 1;
}

.docs-bar-frame.panel .hint {
	padding: 2px 10px 4px;
	margin: 0;
}

/* The floating tools already float: they sit over a piece of canvas, and that
   canvas is the inset. Padding here would inset the inset.

   The grid is on the frame rather than on the strip inside it, so the whole
   containing element reads as canvas. On the strip alone it stopped where the
   tools stopped, which drew a second edge just inside the frame's own and made
   the picture look like two boxes -- and the point of this one is that the
   groups float over a view that carries on past them. */
.docs-bar-frame.float {
	/* Padding, not a margin on the strip inside. With `padding: 0` here the
	   strip's vertical margins collapsed straight out through this element:
	   the frame ended up exactly as tall as the tools, the space above and
	   below them belonged to the figure instead, and the grid stopped at the
	   tools with a bare band either side of it. The gap has to be *inside* the
	   thing that paints the canvas. */
	padding: 20px 14px;
	background-color: var(--bg-canvas);
}

/* Which is also why the real hover paint is off in here. A drawn button lights
   the same way whether the pointer is on it or on its row in the list, and two
   different highlights for one relationship reads as two relationships. */
.docs-bar-frame [data-control],
.docs-bar-frame [data-control] button {
	cursor: help;
}

.docs-bar-frame button.tb:hover:not(:disabled) {
	background: var(--bg-input);
	border-color: var(--border);
}

.docs-bar-frame .tool-group button.tb:hover:not(:disabled),
.docs-bar-frame .segmented button:hover {
	background: transparent;
	border-color: transparent;
	color: inherit;
}

.docs-bar-frame .segmented button.on:hover {
	background: color-mix(in srgb, var(--accent) 20%, transparent);
	color: var(--accent);
}

.docs-bar-frame button.tb.primary:hover:not(:disabled) {
	background: var(--accent);
	filter: none;
}

.docs-bar-frame button.tb.on:hover:not(:disabled) {
	background: color-mix(in srgb, var(--accent) 18%, var(--bg-input));
	border-color: var(--accent);
}

.docs-bar-frame button.tb.icon-only:hover:not(:disabled) {
	color: var(--fg-muted);
}

/* The editor's bar is the full width of a window, so it keeps its own rule.
   Nothing to override: `.toolbar` is already a plain flex row. */
.docs-bar-frame.bar > .toolbar,
.docs-bar-frame.head > .docs-page-head {
	/* The real ones are separated from what is below them by this rule. A
	   drawn one has its own border instead, so the rule would be a second
	   edge in the same place. */
	border-bottom: 0;
	flex-wrap: nowrap;
}

/* The graph's tools really do float, absolutely positioned over the canvas.
   Here there is no canvas to float over, so the strip is laid out in flow —
   and given some of the canvas above and below it, because the gaps between
   the groups are the thing being explained. */
/* Laid out in flow rather than absolutely, since there is no canvas here for
   them to be positioned over -- the frame behind them is the canvas now, and
   its padding is the room they float in. */
.docs-bar-frame.float > .floating-tools {
	position: static;
	flex-wrap: nowrap;
	margin: 0;
}

/* A real <select> is a tab stop even with nothing to focus, and opens on a
   click the frame is trying to swallow. This is the box without either:
   `select.tb`'s paint, and the chevron drawn on. */
.docs-bar-frame .tb.docs-bar-select {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	padding: 4px 9px;
	white-space: nowrap;
}

.docs-bar-frame .tb.docs-bar-select::after {
	content: "";
	width: 0;
	height: 0;
	border: 4px solid transparent;
	border-top-color: var(--fg-muted);
	margin-top: 2px;
}

/* ---- the lit pair
 *
 * One control and its explanation, lit together. A ring rather than a fill: the
 * drawn control already uses fill to mean "this setting is on", and a second
 * meaning for the same paint would have Straighten look pressed because the
 * pointer was over its row in the list.
 *
 * `.linked` is set by the script, so the cursor and the ring only appear where
 * the pairing actually works. */
.docs-bar [data-control] {
	border-radius: var(--radius-sm);
}

.docs-bar.linked [data-control] {
	transition: box-shadow 90ms ease, background-color 90ms ease;
}

.docs-bar-frame [data-control].lit {
	box-shadow: 0 0 0 2px var(--accent);
}

.docs-bar-legend li.lit {
	background: color-mix(in srgb, var(--accent) 12%, transparent);
	box-shadow: inset 2px 0 0 var(--accent);
}

.docs-bar.linked .docs-bar-legend li {
	cursor: help;
}

/* A link in an explanation is a link first -- the script lets it navigate, so
   it has to keep looking like one. */
.docs-bar.linked .docs-bar-legend li a {
	cursor: pointer;
}

/* Said once, under the first bar on the page, by the renderers. Quiet: it is an
   instruction for a thing the reader will find by doing it anyway. */
.docs-bar-hint {
	padding: 8px 14px;
	font-size: var(--text-sm);
	color: var(--fg-faint);
	border-top: 1px solid var(--border);
}

/* Nothing to hint at without a pointer, and nothing to hint at before the
   script has claimed the figure. */
.docs-bar:not(.linked) .docs-bar-hint {
	display: none;
}

@media (hover: none) {
	.docs-bar-hint::after {
		content: " Tap one to keep it lit.";
	}
}

/* What the bar is and where it sits, between the picture and the list. */
.docs-bar-summary {
	margin: 0;
	padding: 10px 14px;
	font-size: var(--text-md);
	color: var(--fg-muted);
	border-top: 1px solid var(--border);
	background: var(--bg-panel);
}

/* Every control, in the order it is drawn. A list rather than a table: the
   name is the thing being scanned and the explanation is detail under it,
   which is a column that would be empty on no rows and wide on all of them. */
/* Scoped under `.docs-bar` so it outranks `.docs-content ul` and `li`, whose
   indent and row gap otherwise inset the legend from its frame. */
.docs-bar .docs-bar-legend {
	margin: 0;
	padding: 0;
	list-style: none;
	border-top: 1px solid var(--border);
}

.docs-bar .docs-bar-legend li {
	margin: 0;
	display: grid;
	grid-template-columns: minmax(9rem, 14rem) 1fr;
	gap: 4px 18px;
	padding: 9px 14px;
	border-bottom: 1px solid var(--border);
	font-size: var(--text-lg);
	line-height: 1.55;
}

.docs-bar-legend li:last-child {
	border-bottom: 0;
}

.docs-bar-name {
	font-weight: 600;
	display: flex;
	flex-direction: column;
	gap: 2px;
}

/* Where to look for it. Under the name rather than in the sentence, because a
   reader who cannot find a button is scanning for this and nothing else. */
.docs-bar-where {
	font-weight: 400;
	font-size: var(--text-sm);
	color: var(--fg-faint);
}

.docs-bar-what {
	color: var(--fg-muted);
}

.docs-bar figcaption {
	padding: 10px 14px;
	border-top: 1px solid var(--border);
	font-size: var(--text-md);
	line-height: 1.5;
	color: var(--fg-muted);
}

/* One column when the reading column is narrow -- a *container* query, not a
   media query. The window can be 780px wide with the article at 440 once the
   nav and the outline have taken their share, and a viewport query does not
   see that: the name column held its 9rem and left the explanation wrapping
   at four words. `.docs-page .docs-content` is the container. */
@container (max-width: 620px) {
	.docs-bar .docs-bar-legend li {
		grid-template-columns: 1fr;
		gap: 2px;
	}
}

/* A module's swatch. A variable's is its type's colour; a module has no type,
   so it gets the outline that says "a thing, not a value". */
.variables .swatch.module {
	background: none;
	border: 1px solid var(--fg-faint);
}

/* Services and Modules take a row dragged out of the DataModel browser, and
   say so while one is held over them. */
.variables-drop {
	border-radius: var(--radius-sm);
}

.variables-drop.over {
	outline: 1px dashed var(--accent);
	outline-offset: -1px;
	background: color-mix(in srgb, var(--accent) 8%, transparent);
}

/* Where a service is typed when Add is pressed: in line with the rows. */
.variables .variable-add {
	padding: 3px 6px 3px 22px;
}

.variables .variable-add input {
	width: 100%;
}

/* The Modules heading carries an Add button, which the other subheadings do
   not -- they list what the graph already declares, and this one declares. */
.variables h3.variables-sub {
	display: flex;
	align-items: center;
	gap: 8px;
}

.variables h3.variables-sub button.tb {
	margin-left: auto;
	font-size: var(--text-sm);
	padding: 1px 8px;
}

/* ------------------------------------------ the runtime filter and badge */

/* Which runtime, on top of the filter the graph's own target already applies.
   A row of chips above the search box in both the node menu and the node
   picker -- the same control in both, because it is the same question and the
   answer is one shared preference. */
.menu-runtimes {
	display: flex;
	gap: 4px;
	/* Room underneath as well as above. With `0` at the bottom the chips sat
	   directly on the search field -- which has its own background and a rule
	   under it, so the two read as one control stuck to another rather than as
	   a row above a box. */
	padding: 8px 8px;
	flex-wrap: wrap;
	align-items: center;
}

.menu-runtimes button {
	font: inherit;
	font-size: var(--text-sm);
	line-height: 1.6;
	padding: 1px 8px;
	border: 1px solid var(--border);
	border-radius: 999px;
	background: var(--bg-input);
	color: var(--fg-muted);
	cursor: pointer;
}

.menu-runtimes button:hover {
	border-color: var(--border-strong);
	color: var(--fg);
}

/* What the row says when there is only one runtime to say it about -- which is
   every Lune graph until the Lune library lands. Hiding the row entirely was
   the first answer and it read as the feature being missing rather than as
   there being nothing to choose. */
.menu-runtimes .only {
	font-size: var(--text-sm);
	line-height: 1.6;
	padding: 1px 2px;
	color: var(--fg-faint);
}

.menu-runtimes .only strong {
	font-weight: 600;
	color: var(--fg-muted);
}

.menu-runtimes button.on {
	background: color-mix(in srgb, var(--accent) 18%, var(--bg-input));
	border-color: var(--accent);
	color: var(--accent);
}

/* What a node needs, on its row. Base Luau is the unmarked case: badging four
   rows in five would be noise rather than information, and the absence is the
   claim -- this one runs anywhere.

   Compounded on `.hint`, which already sits at the right of a row in both
   lists and is where `pure` goes, so a row never grows a second alignment. */
.hint.runtime {
	border: 1px solid transparent;
	border-radius: var(--radius-xs);
	padding: 0 4px;
	text-transform: uppercase;
	letter-spacing: 0.04em;
	font-size: var(--text-xs);
	/* Centred wherever it lands, including the picker, whose rows do not share
	   the menu's rule. A bordered box on a baseline hangs below it. */
	align-self: center;
}

.hint.runtime.roblox {
	color: var(--accent);
	border-color: color-mix(in srgb, var(--accent) 40%, transparent);
}

.hint.runtime.lune {
	color: var(--warning);
	border-color: color-mix(in srgb, var(--warning) 45%, transparent);
}

/* A node from a node pack: somebody's Luau, which goes into the game as they
   wrote it. Said as plainly as where a node runs, and as quietly: a pack is
   not a warning, it is whose code this is. */
.hint.runtime.pack {
	color: var(--fg-muted);
	border-color: color-mix(in srgb, var(--fg-muted) 45%, transparent);
	border-style: dashed;
}

.node-picker-preview .from-pack {
	font-size: var(--text-sm);
	color: var(--fg-muted);
	line-height: 1.5;
}

.node-picker-preview .from-pack code,
.pack-source .pack-file code {
	font-family: var(--mono, ui-monospace, monospace);
	font-size: var(--text-xs);
	overflow-wrap: anywhere;
}

/* The Inspector's half: which file, and the Luau, folded until asked for. */
.pack-source .pack-file {
	display: flex;
	align-items: center;
	gap: 6px;
	margin: 4px 0;
	color: var(--fg-muted);
}

.pack-source .pack-note {
	margin: 6px 0;
	font-size: var(--text-sm);
	color: var(--fg-muted);
	line-height: 1.5;
}

.pack-source .pack-luau {
	margin: 0 0 6px;
	padding: 8px 10px;
	max-height: 240px;
	overflow: auto;
	border-radius: var(--radius-sm);
	background: var(--bg-input);
	border: 1px solid var(--border);
	font-family: var(--mono, ui-monospace, monospace);
	font-size: var(--text-xs);
	line-height: 1.5;
	white-space: pre-wrap;
	overflow-wrap: anywhere;
}

/* Something this graph declares rather than a node from the library -- a
   variable you named, a local, a function, one of its parameters. Quieter than
   the runtimes: it is a fact about where the entry came from, not a warning
   about where it will run. */
.hint.runtime.graph {
	color: var(--fg-muted);
	border-color: var(--border);
}

/* --------------------------------------------------------------------------
   The map editor, drawn in the documentation.

   Almost nothing here is about how the panel looks: it is drawn with the
   editor's own class names, so `.map-editor` and everything under it is
   already styled by the rules above and cannot be restyled out of step with
   the real thing. What is left is the frame it sits in, the height it takes
   when it is not filling a window, and the highlight that pairs a row with
   the lines it writes.
   ----------------------------------------------------------------------- */

/* The figure is wider than the measure.

   A page of prose caps itself at 78ch, which is right for sentences and far
   too narrow for a tree, an Inspector and a project file side by side. So the
   figure is sized against the content column rather than against the article
   -- `.docs-content` is already a container for exactly this, and this is the
   same breakout `.docs-preview.breakout` above makes.

   The negative left margin is the part that is easy to leave out. The article
   is centred in the column, so a child merely made wider grows to the right
   only and runs off the end of the page; re-centring it on the column is what
   makes it a breakout rather than an overflow. */
.docs-page .docs-article > .docs-map {
	--frame: min(1280px, calc(100cqw - 32px));
	box-sizing: border-box;
	width: var(--frame);
	margin: 0 0 18px calc((100% - var(--frame)) / 2);
	padding: 0;
	max-width: none;
}

/* The article's typography does not reach inside the panel.

   `.docs-page .docs-content h2` is two classes and an element; `.map-side h2`
   is one and an element, so the article won and the Inspector's section
   headings came out at body size in the article's colour -- and the hints
   under them came out as paragraphs, which between them ate most of the
   height the figure had to show a project file in.

   The same fix, and the same reason, as `.docs-bar-frame.panel h2` above:
   qualified enough to match, and restated rather than inherited, because
   inheriting is exactly what did not happen. */
.docs-page .docs-content .docs-map-frame h2:where(:not(.docs-layout-screen *)) {
	margin: 0;
	padding: 10px 12px 6px;
	border: 0;
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--fg-faint);
}

.docs-page .docs-content .docs-map-frame p.summary:where(:not(.docs-layout-screen *)) {
	margin: 0;
	padding: 0 12px 6px;
	max-width: none;
	font-size: 11px;
	line-height: 1.45;
	color: var(--fg-muted);
}

.docs-page .docs-content .docs-map-frame p.summary code:where(:not(.docs-layout-screen *)) {
	font-size: 10.5px;
	padding: 0 3px;
}

/* Panel and legend side by side. The panel is tall, and a column of eleven
   names underneath a tall picture is a column nobody reads beside the thing it
   names. */
.docs-map-body {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 290px);
	gap: 18px;
	/* Stretched, not started. The legend is eleven entries and usually the
	   taller of the two, and a panel that stopped at its own content height
	   left a column of empty page beside the second half of the list. The
	   panel takes the row's height instead, so the two columns end together. */
	align-items: stretch;
}

@media (max-width: 900px) {
	.docs-map-body {
		grid-template-columns: minmax(0, 1fr);
	}
}

.docs-map-frame {
	display: flex;
	/* The ceiling lives here rather than on the editor inside it. Stretched to
	   the row, the frame takes the legend's height and the editor fills it, so
	   the two columns end together instead of leaving a column of empty page
	   beside the last few entries. */
	max-height: min(860px, 88vh);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	overflow: hidden;
	background: var(--bg-panel);
}

/* The editor fills a window; here it is given a height to fill instead. Tall
   enough for a small tree and the whole of its project file, which is the
   figure's job -- a reader scrolling the file to find the lit lines is being
   asked to do the thing the highlight was for. */
/* Tall enough to be worked in rather than looked at -- the figure is the
   centrepiece of the page, and a panel with a project file the reader has to
   scroll a two-line window through is a picture of a panel again.

   A ceiling rather than a height, because a small map should not be followed
   by four hundred pixels of empty tree. The children carry their own
   scrollers, so the grid sizes to whatever they need up to this. */
.docs-map-frame .map-editor {
	flex: 1;
	min-width: 0;
	min-height: 380px;
}

/* The project file keeps a scroller of its own rather than growing until the
   whole Inspector scrolls. Selecting a row then moves the file to the lines
   that row writes and leaves Name, Class and Path where the reader is looking. */
.docs-map-frame .map-preview {
	max-height: 320px;
}

/* Lines of the project file are spans so each can carry the node that wrote
   it. `<pre>` already keeps their whitespace; this only keeps them on their
   own rows. */
.map-preview-line {
	display: block;
	border-radius: var(--radius-xs);
	transition: background-color 90ms ease, box-shadow 90ms ease;
}

/* On a filesystem map, what reaches the file, pushed to the right of it. */
.map-reach {
	float: right;
	padding-left: 24px;
	color: var(--fg-faint);
}

/* Both sides take the same highlight, because the point being made is that
   they are the same thing seen twice. `lit` is the row under the pointer;
   `selected` is the row the Inspector is showing, and is the editor's own
   class doing its own job. */
/* A walkthrough's map sits in the page's narrow column, so its Inspector
   gives the tree half the room rather than taking 380px of it. */
.docs-walk-map .map-editor {
	grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

.docs-map .map-preview-line.lit,
.docs-map .map-row.lit,
.docs-walk-map .map-preview-line.lit {
	background: color-mix(in srgb, var(--accent) 14%, transparent);
	box-shadow: inset 2px 0 0 var(--accent);
}

.docs-map .map-row[data-control] {
	cursor: pointer;
}

/* A demonstrator, not a sandbox.

   The buttons are the parts that genuinely do nothing -- there is no project
   to add a folder to -- so they are dimmed to say so before anybody presses
   one. The fields are not: they are filled from whichever row is selected and
   change as you go, so dimming them would be calling live output dead. */
.docs-map-frame .map-editor button[disabled],
.docs-map-frame .map-editor .map-actions select[disabled] {
	opacity: 0.38;
	cursor: default;
}

.docs-map-frame .map-editor input[readonly],
.docs-map-frame .map-editor select[disabled] {
	cursor: default;
}

/* --- the legend, and pointing between it and the panel ------------------- */

/* The entries are separated by space rather than by rules.

   Each one is a name over a sentence, so the gap between two entries has to
   beat the gap between a name and its own sentence or the list reads as one
   run of alternating weights. Hence the tight `margin` inside an entry and the
   much larger `gap` between them. */
.docs-map-legend {
	margin: 0;
	padding: 0;
	list-style: none;
	display: flex;
	flex-direction: column;
	gap: 10px;
	align-content: start;
}

.docs-map-legend li {
	padding: 4px 10px 5px;
	border-radius: var(--radius-sm);
	border-left: 2px solid transparent;
	font-size: var(--text-md);
	line-height: 1.5;
	transition: background-color 90ms ease, opacity 90ms ease;
}

.docs-map-legend .docs-map-part {
	display: block;
	margin-bottom: 1px;
	font-weight: 600;
	color: var(--fg);
}

.docs-map-legend .docs-map-what {
	display: block;
	color: var(--fg-muted);
	text-wrap: pretty;
}

.docs-map.linked .docs-map-legend li {
	cursor: help;
}

/* Pointing at one part dims everything that is not it, on both sides at once.

   The scrim is on the figure rather than on each part, so there is one rule
   deciding what "not the thing you are pointing at" looks like -- the same
   arrangement the Variables panel preview makes, where a section that is not
   the subject of the paragraph steps back rather than disappearing. */
.docs-map.pointing .docs-map-legend li,
.docs-map.pointing .docs-map-frame [data-part] {
	opacity: 0.32;
}

.docs-map.pointing .docs-map-legend li.shown,
.docs-map.pointing .docs-map-frame [data-part].shown {
	opacity: 1;
}

.docs-map-legend li.shown {
	background: color-mix(in srgb, var(--accent) 12%, transparent);
	border-left-color: var(--accent);
}

/* The ring is an overlay, not a shadow or an outline.

   Both of those are painted with the element's own box decoration, which put
   them underneath two different things. The tree fills its half of the frame
   and touches three of its edges, so an outer shadow was cut off by the
   frame's `overflow: hidden`; and the tree's bar is `position: sticky` with a
   z-index, so an inset one was painted over along the top. The Inspector's
   fields had the milder version of the same thing, the next field's background
   covering the millimetre of ring between them.

   A pseudo-element sits above all of it, inside the box so nothing can clip
   it, and takes no pointer events so it cannot change what is being hovered. */
.docs-map-frame [data-part] {
	position: relative;
}

.docs-map-frame [data-part].shown::after {
	content: "";
	position: absolute;
	inset: 0;
	border: 2px solid var(--accent);
	border-radius: var(--radius-sm);
	pointer-events: none;
	z-index: 4;
}

.docs-map > figcaption {
	margin: 8px 0 0;
}

/* One under the other once there is no room for two: an Inspector squeezed
   beside a tree on a phone is two unreadable columns instead of one. */
@media (max-width: 860px) {
	.docs-map-frame .map-editor {
		grid-template-columns: minmax(0, 1fr);
		height: auto;
	}

	.docs-map-frame .map-side {
		border-left: none;
		border-top: 1px solid var(--border);
	}
}

/* --------------------------------------------------------------------------
   The introduction panel, on the Roswaal mark in every window.

   Sized to its content rather than to the window: it is a way in, not a
   workspace, and a dialog that filled a 4K screen would put its two buttons
   half a metre apart.
   ----------------------------------------------------------------------- */

.intro-backdrop {
	position: fixed;
	inset: 0;
	/* Under the dialogs, not over them.

	   `Dialog` sits at `LAYER.menu + 1`, and this was at 90 — so a notice
	   raised while the panel was open rendered behind it: invisible, modal,
	   and holding the focus. Opening a recent project that had been deleted
	   did exactly that, which is how it was found. Above everything on the
	   canvas, below anything that speaks. */
	z-index: 68;
	display: grid;
	place-items: center;
	padding: 24px;
	background: rgba(0, 0, 0, 0.45);
	backdrop-filter: blur(2px);
}

.intro-panel {
	display: flex;
	flex-direction: column;
	width: min(760px, 100%);
	max-height: min(640px, 100%);
	border: 1px solid var(--border-strong);
	border-radius: var(--radius-md);
	background: var(--bg-panel);
	box-shadow: var(--shadow-modal);
	overflow: hidden;
}

.intro-head {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 12px 12px 12px 16px;
	border-bottom: 1px solid var(--border);
	background: var(--bg-input);
}

/* The mark and the wordmark, which is the one place in the app they sit
   together at this size -- everywhere else the mark is chrome. */
.intro-head .logo {
	display: flex;
	align-items: center;
	gap: 9px;
	font-size: var(--text-xl);
	font-weight: 600;
	letter-spacing: 0.01em;
	color: var(--fg);
}

/* Which window you are in, said quietly. The panel is the same in all three,
   so this is the only thing on it that answers "where am I". */
.intro-where {
	display: flex;
	align-items: center;
	gap: 6px;
	padding-left: 10px;
	border-left: 1px solid var(--border);
	font-size: var(--text-md);
	color: var(--fg-dim);
}

.intro-body {
	flex: 1;
	min-height: 0;
	overflow-y: auto;
	padding: 4px 0 8px;
}

.intro-shelf {
	padding: 10px 0 4px;
}

.intro-shelf-head {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 0 16px 8px;
}

.intro-shelf-head h2 {
	margin: 0;
	font-size: var(--text-sm);
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--fg-faint);
}

.intro-arrows {
	display: flex;
	gap: 4px;
	margin-left: auto;
}

/* One chevron glyph, turned. It points down as drawn. */
.turn-left,
.turn-right {
	display: inline-flex;
}

.turn-left svg {
	transform: rotate(90deg);
}

.turn-right svg {
	transform: rotate(-90deg);
}

/* The rail scrolls sideways and snaps, so a nudge never leaves half a card
   showing. Padding rather than margin on the ends, so the first card starts
   level with the heading above it and the last one can still reach the edge. */
.intro-rail {
	display: flex;
	gap: 10px;
	padding: 2px 16px 6px;
	overflow-x: auto;
	scroll-snap-type: x proximity;
	/* The gutter, said twice.

	   `padding` puts it there; `scroll-padding` keeps it there. A snap aligns
	   a card to the *scrollport* edge, which ignores padding -- so the moment
	   this rail was scrolled at all, the first card slid flush against the
	   panel's edge while the shelf below it, still at zero, kept its 16px. Two
	   rows of the same cards, indented differently, because one of them had
	   been touched. */
	scroll-padding-inline: 16px;
	/* Always thin, never toggled. Showing it on hover changed the scrollport's
	   height, so every card in the shelf shifted up a pixel or two as the
	   pointer arrived and back again as it left. A quiet scrollbar that is
	   always there costs less than a tidy one that moves the furniture. */
	scrollbar-width: thin;
	scrollbar-color: transparent transparent;
	transition: scrollbar-color 120ms ease;
}

.intro-shelf:hover .intro-rail {
	scrollbar-color: var(--border-strong) transparent;
}

.intro-card {
	flex: 0 0 auto;
	scroll-snap-align: start;
	display: flex;
	flex-direction: column;
	gap: 3px;
	width: 220px;
	padding: 11px 12px;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	background: var(--bg-input);
	text-align: left;
	cursor: pointer;
	transition: border-color 90ms ease, background-color 90ms ease;
}

.intro-card:hover {
	border-color: var(--accent);
	background: var(--bg-hover);
}

/* The project this window already has open. Marked rather than hidden: it is
   still the one you are most likely to mean. */
.intro-card.on {
	border-color: color-mix(in srgb, var(--accent) 60%, transparent);
}

.intro-card-name {
	display: flex;
	align-items: center;
	gap: 6px;
	font-size: var(--text-lg);
	font-weight: 600;
	color: var(--fg);
}

/* The runtime chip on a demo card, drawn as the documentation draws it on a
   page title -- same shape, same two colours, because it is answering the same
   question in both places. Restated rather than shared: `.docs .badge` is
   scoped to the documentation, and widening that selector to reach a panel in
   the editor would make every badge in the app depend on a docs rule. */
.intro-card .badge {
	font-size: var(--text-xs);
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.06em;
	color: var(--fg-faint);
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	padding: 1px 4px;
	white-space: nowrap;
}

.intro-card .badge.runtime.roblox {
	color: var(--accent);
	border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}

.intro-card .badge.runtime.lune {
	color: var(--warning);
	border-color: color-mix(in srgb, var(--warning) 50%, transparent);
}

.intro-card-what {
	font-size: var(--text-sm);
	line-height: 1.4;
	color: var(--fg-muted);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.intro-card-open {
	margin-top: 2px;
	font-size: var(--text-xs);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--fg-faint);
}

.intro-empty {
	margin: 0;
	padding: 28px 16px;
	text-align: center;
	font-size: var(--text-md);
	color: var(--fg-muted);
}

.intro-foot {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 10px 12px;
	border-top: 1px solid var(--border);
	background: var(--bg-input);
}

/* Wraps rather than scrolls: the project actions are host-dependent, so how
   many are in this row is not something the layout can be written around. */
@media (max-width: 620px) {
	.intro-foot {
		flex-wrap: wrap;
	}

	.intro-foot > span[style] {
		display: none;
	}
}

/* The mark in a window's header, once it became a button.

   Styled back to what it was: it is the same chip it has always been and the
   only change is that pressing it now does something. A button's own border,
   background and padding would have made the header look rearranged. */
.docs-page-head .logo.as-chip {
	border: 1px solid transparent;
	border-radius: var(--radius-sm);
	margin-left: -6px;
	padding: 2px 6px;
	background: transparent;
	/* Family and size, not the `font` shorthand. `font: inherit` also resets
	   weight and letter-spacing, which `.docs-page-head .logo` above is the
	   one supplying -- so the wordmark came out at the header's normal weight
	   the moment it became a button. */
	font-family: inherit;
	font-size: inherit;
	color: inherit;
	cursor: pointer;
	transition: background-color 90ms ease, border-color 90ms ease;
}

.docs-page-head .logo.as-chip:hover {
	border-color: var(--border);
	background: var(--bg-hover);
}

.docs-page-head .logo.as-chip:active {
	background: color-mix(in srgb, var(--accent) 14%, var(--bg-hover));
}

.docs-page-head .logo.as-chip:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 1px;
}

/* A copy that did not happen, said where it was asked for rather than in a
   dialog over the top of the panel. */
.intro-trouble {
	margin: 0;
	padding: 4px 16px 10px;
	font-size: var(--text-md);
	color: var(--danger);
}

/* A card mid-copy. Dimmed rather than removed, so the row does not reflow
   under the pointer that just pressed it. */
.intro-card:disabled {
	opacity: 0.55;
	cursor: default;
}

/* --------------------------------------------------------------------------
   A drawn graph with what it declares beside it.

   The panel goes on the left because it is read first: `fs` is declared and
   then used, and a reader following `fs.readFile` back to its module should
   travel the way the eye already goes.
   ----------------------------------------------------------------------- */
.docs-preview.graph.with-panel {
	display: grid;
	grid-template-columns: minmax(0, 240px) minmax(0, 1fr);
	gap: 14px;
	align-items: start;
}

.docs-preview.graph.with-panel > figcaption {
	grid-column: 1 / -1;
}

/* The panel draws itself; this only stops it stretching to the graph's height,
   which would leave a tall empty box under three rows of modules. */
.graph-declares .docs-bar-frame {
	margin: 0;
}

/* One under the other once there is no room for two: a 240px panel beside a
   graph on a phone is two things too narrow to read instead of one. */
@media (max-width: 860px) {
	.docs-preview.graph.with-panel {
		grid-template-columns: minmax(0, 1fr);
	}
}

/* A recent card is two controls: the project, and taking it off the list.

   The cross is the affordance the project menu had and the first version of
   this panel dropped — which left a root that had been deleted, or thrown away
   with the browser volume, sitting there failing every time it was pressed. */
.intro-card {
	position: relative;
}

.intro-card-open-it {
	display: flex;
	flex-direction: column;
	gap: 3px;
	width: 100%;
	padding: 0;
	border: 0;
	background: none;
	text-align: left;
	font: inherit;
	color: inherit;
	cursor: pointer;
}

.intro-forget {
	position: absolute;
	top: 6px;
	right: 6px;
	width: 20px;
	height: 20px;
	min-width: 0;
	padding: 0;
	opacity: 0;
	transition: opacity 90ms ease;
}

/* Shown on the card it belongs to, and always once the keyboard is on it —
   a control that only exists under a pointer is one a keyboard cannot reach. */
.intro-card:hover .intro-forget,
.intro-forget:focus-visible {
	opacity: 1;
}

/* A project that would not open. Marked rather than removed: taking it off the
   list is the reader's decision, and one they may not want if the drive it is
   on is simply not plugged in today. */
.intro-card.gone {
	border-color: color-mix(in srgb, var(--danger) 55%, transparent);
}

.intro-card-open.gone {
	color: var(--danger);
}

/* --------------------------------------------------------------------------
   A checkbox the page puts beside the thing it changes.

   The settings popover is for what a reader sets once and forgets; this is for
   a choice that only makes sense while looking at the page it belongs to.
   ----------------------------------------------------------------------- */
.docs-toggle {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	align-items: start;
	column-gap: 9px;
	row-gap: 2px;
	margin: 0 0 18px;
	padding: 10px 12px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--bg-input);
	cursor: pointer;
}

.docs-toggle input {
	margin: 2px 0 0;
	cursor: pointer;
}

.docs-toggle-label {
	font-weight: 600;
	color: var(--fg);
}

.docs-toggle-hint {
	grid-column: 2;
	font-size: var(--text-md);
	line-height: 1.5;
	color: var(--fg-muted);
}

/* --------------------------------------------------------------------------
   Phones, tablets and fingers.

   Last in the file on purpose: every rule here narrows one written further
   up, and a narrowing that loses on order does nothing. That is how the
   docs' outline stayed on screen below 1100px.
   ----------------------------------------------------------------------- */

/* A finger has no hover and no double-click, and iPadOS reads a quick second
   tap as "zoom the page". `manipulation` keeps pans and pinches and drops that
   one, so a double tap reaches `touch.ts` as the double-click it stands for.
   The callout is the iOS menu a long press raises over text, which would sit
   on top of the one Roswaal opens for the same press. */
.app,
.designer,
.docs-page {
	touch-action: manipulation;
	-webkit-touch-callout: none;
}

@media (pointer: coarse) {
	/* A long press is a right-click here, so a press on a tree row or a node
	   title must not start selecting its text first. Fields are left alone. */
	.app,
	.designer {
		-webkit-user-select: none;
		user-select: none;
	}

	.app :is(input, textarea, [contenteditable="true"], .cm-content),
	.designer :is(input, textarea, [contenteditable="true"], .cm-content) {
		-webkit-user-select: text;
		user-select: text;
	}

	/* Five pixels is a line for a mouse and a miss for a finger. The strip
	   drawn stays the same; only what catches the press grows. */
	.splitter.col {
		border-left-width: 9px;
		border-right-width: 9px;
		margin: 0 -9px;
	}
}

/* A finger held on empty canvas: drag for a marquee, lift for the menu. The
   ring says the hold has registered, since nothing else on screen changes. */
.canvas-hold {
	position: absolute;
	width: 44px;
	height: 44px;
	margin: -22px 0 0 -22px;
	border: 2px solid var(--select);
	border-radius: 50%;
	background: color-mix(in srgb, var(--select) 14%, transparent);
	pointer-events: none;
	animation: canvas-hold-in 140ms ease-out;
}

@keyframes canvas-hold-in {
	from { transform: scale(0.4); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
	.canvas-hold { animation: none; }
}

/* Dragging by touch: see `touch.ts`. Held long enough, the item lifts to say
   a drag is armed; a label follows the finger once it moves. */
.touch-lifted {
	outline: 2px solid var(--select);
	outline-offset: -2px;
	border-radius: var(--radius-sm);
	background: color-mix(in srgb, var(--select) 14%, transparent);
}

.touch-drag-ghost {
	position: fixed;
	z-index: 1000;
	pointer-events: none;
	transform: translate(-50%, calc(-100% - 18px));
	max-width: 240px;
	padding: 6px 10px;
	border: 1px solid var(--select);
	border-radius: var(--radius-sm);
	background: var(--bg-panel);
	color: var(--fg);
	box-shadow: var(--shadow-popover);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* A drag image the source drew, such as a node from the node picker. Placed by
   the offset the source gave rather than centred over the finger. */
.touch-drag-image {
	position: fixed;
	z-index: 1000;
	pointer-events: none;
	opacity: 0.9;
	filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.35));
}

.touch-drag-image svg {
	display: block;
}

@media (hover: none) and (pointer: coarse) {
	/* iOS's own drag would lift the same item at the same moment and take the
	   finger away from the one above. */
	[draggable="true"] {
		-webkit-user-drag: none;
	}

	/* A tapped button keeps `:hover` on iOS until something else is tapped,
	   so the last one pressed stayed painted as if a pointer were over it --
	   the Variables button, after its drawer was closed by tapping the graph.
	   A finger does not hover; the rest state is the only one. */
	button.tb:hover:not(:disabled):not(.on):not(.primary):not(.danger),
	select.tb:hover {
		background: var(--bg-input);
		border-color: var(--border);
	}

	button.tb.on:hover:not(:disabled) {
		background: color-mix(in srgb, var(--accent) 18%, var(--bg-input));
	}
}

/* ---------------------------------------- the editor, on a tablet or a phone */

/* See `useCompact` in Workspace.tsx: the side docks become drawers, one panel
   at a time. On a tablet a drawer is a card hanging under the top clusters, put
   out by the buttons there; on a phone it is a sheet from the bottom, put out by
   the bar along the bottom. A tap on the graph puts either away. */
.dock.drawer {
	position: absolute;
	top: var(--chrome-top);
	bottom: var(--edge);
	z-index: 67;
	width: min(340px, 86vw);
	box-sizing: border-box;
	padding: 0;
	pointer-events: auto;
	background: var(--bg-panel);
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-popover);
	overflow: auto;
	transition:
		transform 200ms cubic-bezier(0.2, 0.9, 0.3, 1),
		opacity 160ms ease,
		visibility 200ms;
}

.dock.drawer.left {
	left: var(--edge);
}

.dock.drawer.right {
	right: var(--edge);
}

.dock.drawer:not(.drawer-open) {
	visibility: hidden;
	opacity: 0;
	box-shadow: none;
}

.dock.drawer.left:not(.drawer-open) {
	transform: translateX(-16px);
}

.dock.drawer.right:not(.drawer-open) {
	transform: translateX(16px);
}

/* The drawer is the card, so the panel in it is not another one. */
.dock.drawer > .panel {
	flex: 1;
	min-height: 0;
	background: none;
	border: 0;
	border-radius: 0;
	box-shadow: none;
	backdrop-filter: none;
	-webkit-backdrop-filter: none;
}

.dock.drawer .panel.panel-away {
	display: none;
}

.dock.drawer .panel-variables {
	flex: 1;
	max-height: none;
}

/* Which of the side's panels is out, when it has more than one: tabs in the
   header band, as a card's are on a computer. */
.dock.drawer .card-tabs {
	flex: none;
}

.dock.drawer .card-tab {
	cursor: pointer;
	touch-action: manipulation;
}

/* Over the graph, under the drawer: a tap here puts the drawer away. */
.drawer-backdrop {
	position: absolute;
	inset: 0;
	z-index: 66;
	background: rgba(0, 0, 0, 0.25);
}

/* The bar along the bottom: the two sides' cards at its ends, and what can be
   done to the selection between them. One glass group, as the top clusters are. */
.drawer-toggles {
	position: absolute;
	left: 50%;
	bottom: calc(var(--edge) + env(safe-area-inset-bottom, 0px));
	transform: translateX(-50%);
	z-index: 68;
	display: flex;
	align-items: center;
	gap: 2px;
	max-width: calc(100% - var(--edge) * 2);
	padding: 3px;
	box-sizing: border-box;
	background: var(--glass);
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-raised);
	backdrop-filter: blur(14px) saturate(1.2);
	-webkit-backdrop-filter: blur(14px) saturate(1.2);
	overflow-x: auto;
	scrollbar-width: none;
}

.drawer-toggles button.tb.icon-only {
	flex: none;
	width: var(--hit);
	height: var(--hit);
	padding: 0;
	display: inline-grid;
	place-items: center;
	background: transparent;
	border-color: transparent;
}

.drawer-toggles button.tb.icon-only.on {
	background: color-mix(in srgb, var(--accent) 20%, transparent);
	color: var(--accent);
}

/* On a tablet the cards' buttons are on the top clusters, so the bar is the
   selection's actions alone, and only while there is a bar of them. Spelled
   as specifically as the rule that draws the bar's buttons, which otherwise
   wins and put both cards' buttons back on every iPad. */
@media (min-width: 700px) {
	.drawer-toggles button.tb.icon-only.drawer-toggle {
		display: none;
	}

	.drawer-toggles:not(:has(.touch-bar)) {
		display: none;
	}
}

/* While the bar is up, the status pill stands above it, as it does on a phone:
   beside it on an iPad held upright, the two overlapped, and an open card
   covered the bar either way up. */
.workspace.compact:has(.drawer-toggles .touch-bar) .dock.bottom {
	bottom: calc(var(--edge) * 2 + var(--hit) + 8px + env(safe-area-inset-bottom, 0px));
}

/* And the Code panel's strip stands a row above the lifted pill, as it stands
   above the pill's row everywhere else: on the same row, the pill lay over the
   code being typed. A phone lifts the pill always, so it lifts the strip too. */
.workspace.compact:has(.drawer-toggles .touch-bar) .dock.foot {
	bottom: calc(var(--edge) * 3 + var(--hit) * 2 + 14px + env(safe-area-inset-bottom, 0px));
}

.workspace.compact:has(.drawer-toggles .touch-bar) .dock.foot:not(.full) {
	max-height: calc(100% - var(--chrome-top) - var(--edge) * 4 - var(--hit) * 2 - 14px);
}

@media (max-width: 699px) {
	.dock.foot {
		bottom: calc(var(--edge) * 3 + var(--hit) * 2 + 14px + env(safe-area-inset-bottom, 0px));
	}

	.dock.foot:not(.full) {
		max-height: calc(100% - var(--chrome-top) - var(--edge) * 4 - var(--hit) * 2 - 14px);
	}
}

.touch-bar {
	display: flex;
	align-items: center;
}

.touch-bar-group {
	display: flex;
	flex-wrap: nowrap;
	gap: 2px;
}

/* In the bar, the touch bar's buttons are the bar's own: flat until pressed. */
.touch-bar-group.unified {
	padding: 0;
	border: 0;
	background: none;
	box-shadow: none;
}

.touch-bar button.tb {
	flex: none;
	border-color: transparent;
	background: transparent;
	box-shadow: none;
}

@media (hover: none) {
	.touch-bar .touch-bar-group button.tb:hover:not(:disabled):not(.on) {
		border-color: transparent;
		background: transparent;
	}
}

.touch-bar .touch-bar-group button.tb.icon-only {
	width: var(--hit);
	height: var(--hit);
	padding: 0;
	display: inline-grid;
	place-items: center;
	line-height: 0;
}

/* A phone: the drawers are sheets from the bottom, over the bar, and the
   status pill sits above the bar rather than on it. */
@media (max-width: 699px) {
	.dock.bottom {
		bottom: calc(var(--edge) * 2 + var(--hit) + 8px + env(safe-area-inset-bottom, 0px));
	}

	.dock.drawer.left,
	.dock.drawer.right {
		left: 6px;
		right: 6px;
		top: auto;
		bottom: calc(6px + env(safe-area-inset-bottom, 0px));
		width: auto;
		max-height: 66%;
		border-radius: 18px;
		box-shadow: var(--shadow-modal);
	}

	.dock.drawer.left:not(.drawer-open),
	.dock.drawer.right:not(.drawer-open) {
		transform: translateY(24px);
	}

	/* The graph's name is in its tab at the top; in the corner it sat under
	   the bar along the bottom. */
	.canvas .watermark {
		display: none;
	}
}

@media (prefers-reduced-motion: reduce) {
	.dock.drawer {
		transition: none;
	}
}

/* Node Design's node list, the same way: a drawer over the node editor on a
   phone or a tablet, opened from a bar that also names the node that is open. */
.pack-view.compact {
	position: relative;
	grid-template-columns: minmax(0, 1fr);
	grid-template-rows: auto minmax(0, 1fr);
}

/* Above the drawer and its scrim, so the button that opened it closes it. */
.pack-compact-bar {
	position: relative;
	z-index: 68;
	display: flex;
	align-items: center;
	gap: 10px;
	min-width: 0;
	padding: 6px 10px;
	border-bottom: 1px solid var(--border);
	background: var(--bg-panel);
}

/* The open node's switches, pushed to the end of the bar. */
.pack-compact-slot {
	margin-left: auto;
	display: flex;
	gap: 8px;
	flex: none;
}

.pack-compact-slot:empty {
	display: none;
}

.pack-compact-current {
	flex: 1;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	color: var(--fg-muted);
}

/* Placed in the row under the bar, which makes that row its containing
   block: it and the scrim cover the editor and never the bar. */
.pack-view.compact .pack-nodes.drawer {
	grid-row: 2;
	grid-column: 1;
	position: absolute;
	top: 0;
	bottom: 0;
	left: 0;
	z-index: 67;
	width: min(320px, 86vw);
	box-sizing: border-box;
	border-right: 1px solid var(--border-strong);
	box-shadow: var(--shadow-modal);
	transition: transform 160ms ease-out, visibility 160ms;
}

.pack-view.compact .pack-nodes.drawer:not(.drawer-open) {
	visibility: hidden;
	box-shadow: none;
	transform: translateX(-100%);
}

.pack-scrim {
	grid-row: 2;
	grid-column: 1;
	position: absolute;
	inset: 0;
	z-index: 66;
	background: rgba(0, 0, 0, 0.35);
}

@media (prefers-reduced-motion: reduce) {
	.pack-view.compact .pack-nodes.drawer { transition: none; }
}

/* Node Design's node and its logic, one at a time on a phone or a tablet:
   see `split` in NodeEditor.tsx. */
.node-editor-views {
	display: flex;
	justify-content: center;
	padding: 6px 10px;
	border-bottom: 1px solid var(--border);
	background: var(--bg-panel);
}

/* With its title, hint and Luau/Nodes elsewhere, the logic's head holds at most
   a pure node's choice of output -- and is not drawn with nothing in it. */
.logic-head.logic-head-slim:not(:has(> *)) {
	display: none;
}

.node-editor.split .logic-splitter {
	display: none;
}

.node-editor.split.view-preview .node-logic,
.node-editor.split.view-logic .node-editor-stage {
	display: none;
}

.node-editor.split.view-logic .node-logic {
	flex: 1;
}

/* The graph over the Luau it compiles to, rather than beside it: side by side,
   an iPad in portrait gave each half too little width to read. */
.node-editor.split .logic-split {
	grid-template-columns: minmax(0, 1fr);
	grid-template-rows: minmax(0, 1fr) minmax(120px, 30%);
}

/* ----------------------------------------------------- dialogs, on a phone */

@media (max-width: 640px) {
	.docs-backdrop {
		padding: 8px;
	}

	/* The pages along the top rather than down the side: a 236px column of
	   short words left the page beside it a hundred pixels wide. */
	.settings-sheet {
		grid-template-columns: minmax(0, 1fr);
		grid-template-rows: auto minmax(0, 1fr);
	}

	.settings-nav {
		flex-direction: row;
		align-items: center;
		gap: 4px;
		padding: 8px;
		border-right: 0;
		border-bottom: 1px solid var(--border);
	}

	.settings-brand,
	.settings-keeper-head,
	.settings-elsewhere {
		display: none;
	}

	.settings-search {
		flex: none;
		width: 150px;
		margin: 0;
	}

	.settings-keeper,
	.settings-keeper.keeper-about {
		flex-direction: row;
		margin: 0;
		padding: 0;
	}

	.settings-link {
		white-space: nowrap;
	}

	.settings-current,
	.settings-results {
		padding: 14px 14px 28px;
	}

	.setting {
		grid-template-columns: minmax(0, 1fr);
		gap: 8px;
	}

	.setting-control {
		justify-content: flex-start;
	}
}

/* ------------------------------------------------------ the docs, narrow */

.docs-nav-check {
	position: absolute;
	width: 1px;
	height: 1px;
	opacity: 0;
	pointer-events: none;
}

/* `.tb` in both, to outrank `.docs-page-head button.tb`, which makes every
   header button a flex box -- and made this one visible everywhere, with or
   without the script it needs. */
.docs-page-head .tb.docs-nav-toggle,
.docs-page-head .tb.docs-search-toggle {
	display: none;
}

.docs-page-head label.tb {
	cursor: pointer;
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	padding: 4px 9px;
	/* The cluster's height, as its buttons are: 30px with a mouse and 38px to
	   touch. A fixed 29px left Contents short of the rest on an iPad. */
	height: var(--hit);
	box-sizing: border-box;
	align-items: center;
}

/* The contents as a drawer: on anything narrow, and on a touch screen of any
   size. A column of links held open beside the page is a desktop's way of
   reading; on an iPad it is a third of the screen spent on a list you use
   once per page. Keep the query in step with `COMPACT_QUERY` in panels.ts,
   which asks the same question of the editor. */
@media (max-width: 760px), (hover: none) and (pointer: coarse) {
	.docs-page-head .tb.docs-nav-toggle,
	.docs-page-head .tb.docs-search-toggle:not([hidden]) {
		display: inline-flex;
	}

	/* The nav is out of the grid, so the reading column is the first track.
	   The outline keeps its own track wherever it is shown. */
	.docs-page .docs-body {
		grid-template-columns: minmax(0, 1fr);
		position: relative;
	}

	.docs-page .docs-nav {
		display: none;
	}

	.docs-page.nav-open .docs-nav,
	.docs-page:has(.docs-nav-check:checked) .docs-nav {
		display: flex;
		position: absolute;
		top: 0;
		bottom: 0;
		left: 0;
		width: min(360px, 100%);
		box-sizing: border-box;
		z-index: 5;
		background: var(--bg-panel);
		border-right: 1px solid var(--border-strong);
		box-shadow: var(--shadow-modal);
	}

	/* Tapping the page beside the drawer puts it away. A label for the same
	   checkbox, so the static site needs no script for it. */
	.docs-page.nav-open .docs-nav-scrim,
	.docs-page:has(.docs-nav-check:checked) .docs-nav-scrim {
		display: block;
		position: absolute;
		inset: 0;
		z-index: 4;
		background: rgba(0, 0, 0, 0.35);
		cursor: pointer;
	}

	.docs-page:has(.docs-nav-check:checked) .docs-nav-toggle,
	.docs-page.nav-open .docs-nav-toggle {
		background: color-mix(in srgb, var(--accent) 18%, var(--bg-input));
		border-color: var(--accent);
		color: var(--accent);
	}
}

@media (hover: none) and (pointer: coarse) and (min-width: 1101px) {
	.docs-page .docs-body { grid-template-columns: minmax(0, 1fr) 210px; }
}

@media (hover: none) and (pointer: coarse) and (min-width: 1500px) {
	.docs-page .docs-body { grid-template-columns: minmax(0, 1fr) 240px; }
}

.docs-nav-scrim {
	display: none;
}

@media (max-width: 760px) {
	.docs-page-head {
		flex-wrap: wrap;
		row-gap: 6px;
		padding: 8px 10px;
	}

	.docs-page .docs-content {
		padding: 18px 16px 60px;
	}
}

/* Arrow keys, Enter and Escape, on a screen with no keys. */
@media (hover: none) and (pointer: coarse) {
	.docs-palette-foot {
		display: none;
	}
}

/* iOS zooms the page in on any field under 16px when it takes focus, and the
   docs let a page zoom. Focusing the search did exactly that: the palette ran
   off the right-hand edge and the page stayed zoomed after it closed. */
@media (pointer: coarse) {
	.docs-static :is(input, select, textarea),
	.docs-palette-field input {
		font-size: 16px;
	}
}

/* Over everything on the page, whatever it has positioned. The app's copies
   set this inline; the static site's did not set it at all. */
.docs-palette-backdrop,
.docs-static .docs-backdrop {
	z-index: 100;
}

/* No grey flash where a tap lands on a picture that lights what was tapped:
   the lit state is the answer, and iOS drawing its own box over it first
   read as the picture flickering. */
.docs-bar.linked,
.docs-map.linked {
	-webkit-tap-highlight-color: transparent;
}

@media (max-width: 560px) {
	/* One row on a phone: a second one took a sixth of the screen from every
	   page. The build's number, Source and half of Try it make the room. */
	.docs-static .docs-page-head {
		flex-wrap: nowrap;
	}

	.docs-page-head .logo .version,
	.docs-page-head .docs-try-more,
	.docs-page-head a.tb.docs-try .version.preview-chip,
	.docs-page-head a.tb.docs-source {
		display: none;
	}
}

/* --------------------------------------------------------------------------
   Dropdowns, drawn by us everywhere.

   A <select> left to the browser is three different controls: Chrome on
   Windows draws something close to the app's buttons, and Safari on a Mac
   draws its own -- a double chevron, its own height, its padding ignored --
   so the tools over the graph came out a different size from the buttons
   beside them, and from the picture of that bar in the docs. This is the
   box `select.tb` already paints, with the triangle `docs-bar-select` draws,
   made the same way the node's own dropdowns make theirs.

   Last in the file, and specific enough, because every `background:`
   shorthand above -- `select.tb:hover` among them -- resets the image. The
   list a select opens is still the system's, which is right: it is the one
   part a browser does well on every platform.
   ----------------------------------------------------------------------- */
select:not(.literal):not([multiple]) {
	-webkit-appearance: none;
	appearance: none;
	padding-right: 24px;
	background-image:
		linear-gradient(45deg, transparent 50%, var(--fg-muted) 50%),
		linear-gradient(135deg, var(--fg-muted) 50%, transparent 50%);
	background-position: calc(100% - 13px) 50%, calc(100% - 9px) 50%;
	background-size: 4px 4px, 4px 4px;
	background-repeat: no-repeat;
	text-overflow: ellipsis;
}

/* --------------------------------------------------------------------------
   A window as a diagram, on The Interface. See `src/core/docs/layouts.ts`.

   Boxes in the places the real regions occupy, each numbered as the legend
   under it numbers them. Recognisable rather than faithful: the bars are drawn
   control by control on Toolbars, and a picture of every button at this size
   would be a picture of nothing in particular.
   ----------------------------------------------------------------------- */
.docs-layout-frame {
	display: grid;
	gap: 3px;
	padding: 6px;
	margin: 14px;
	border: 1px solid var(--border-strong);
	border-radius: var(--radius-md);
	background: var(--bg-app);
	font-size: var(--text-sm);
	line-height: 1.3;
}

.docs-layout-frame.device-desktop {
	aspect-ratio: 16 / 10;
}

/* A tablet held sideways: its bezel, so the two tabs are two devices at a
   glance rather than two sizes of one box. */
.docs-layout-frame.device-tablet {
	aspect-ratio: 4 / 3;
	max-width: 620px;
	margin-inline: auto;
	border: 10px solid var(--border-strong);
	border-radius: 22px;
}

/* A phone held upright: taller than wide, and narrow enough to sit in the
   reading column beside its legend's first rows rather than fill it. */
.docs-layout-frame.device-phone {
	aspect-ratio: 9 / 19;
	font-size: 9.5px;
	max-width: 280px;
	margin-inline: auto;
	border: 9px solid var(--border-strong);
	border-radius: 28px;
}

.docs-layout-region {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	align-content: flex-start;
	gap: 4px 6px;
	min-width: 0;
	min-height: 0;
	padding: 5px 7px;
	overflow: hidden;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background-color: var(--bg-panel);
	color: var(--fg-muted);
}

.docs-layout-region.kind-bar {
	align-items: center;
	align-content: center;
}

/* The graph, drawn as the graph's own grid so it reads as the canvas. */
.docs-layout-region.kind-canvas {
	background-color: var(--bg-canvas);
	/* A window drawn small: its grid is drawn at the drawing's scale. */
	--grid-step: 14px;
	align-content: flex-end;
}

.docs-layout-region.number-middle-end {
	align-content: center;
	justify-content: flex-end;
}

/* Over the graph: in the graph's cells, sized to what is on it, raised. */
.docs-layout-region.kind-float {
	flex-wrap: nowrap;
	z-index: 2;
	align-self: center;
	align-items: center;
	margin: 0 6px;
	box-shadow: var(--shadow-raised);
}

.docs-layout-region.kind-float.place-start { justify-self: start; }
.docs-layout-region.kind-float.place-center { justify-self: center; }
.docs-layout-region.kind-float.place-end { justify-self: end; }

/* A panel slid out over the graph: above it, with the shadow the real one has. */
.docs-layout-region.kind-drawer {
	z-index: 3;
	border-color: var(--border-strong);
	box-shadow: -8px 0 20px rgba(0, 0, 0, 0.3);
}

.docs-layout-num {
	display: inline-grid;
	place-items: center;
	flex: none;
	width: 17px;
	height: 17px;
	border-radius: 50%;
	background: var(--accent);
	color: var(--bg-panel);
	font-size: var(--text-xs);
	font-weight: 700;
	line-height: 1;
}

.docs-layout-icons,
.docs-layout-chips {
	display: inline-flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 4px;
}

.docs-layout-icons.docs-layout-end {
	margin-left: auto;
}

.docs-layout-chips > span,
.docs-layout-chip {
	padding: 1px 6px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--bg-input);
	white-space: nowrap;
}

.docs-bar.docs-layout .docs-bar-picture {
	overflow: visible;
}

.docs-layout-frame [data-control] {
	cursor: pointer;
}

.docs-layout-frame [data-control].lit {
	box-shadow: 0 0 0 2px var(--accent);
}

.docs-layout-legend {
	list-style: none;
}

/* The number beside the name, as it sits in the picture; the when-it-is-there
   line keeps its own row under both. */
.docs-layout-legend .docs-bar-name {
	flex-direction: row;
	flex-wrap: wrap;
	align-items: center;
	column-gap: 7px;
}

.docs-layout-legend .docs-bar-where {
	flex-basis: 100%;
	padding-left: 24px;
}

/* A phone draws the diagram small: the numbers carry it, and the names are
   in the legend beneath. */
@media (max-width: 560px) {
	.docs-layout-frame .docs-layout-chips,
	.docs-layout-frame .docs-layout-icons {
		display: none;
	}

	.docs-layout-frame {
		margin: 8px;
	}
}

/* --------------------------------------------------------------------------
   Bars short of room: words become glyphs.

   The top bar and the graph's tools are one row each on a computer. On an iPad
   in portrait they wrapped into two, and the graph lost the height of both.
   So where they are short of room the buttons that carry a word -- Compile
   project, Straighten, Compile script -- show only their glyph, and the
   Compile caption goes. The word stays as the button's accessible name, and
   the title says it on a long press.
   ----------------------------------------------------------------------- */
.tb-icon-when-narrow {
	display: none;
}

/* The top bar measures the window: it always spans it. */
@media (max-width: 899px) {
	.toolbar .group-label,
	.toolbar .logo .version {
		display: none;
	}

	.toolbar .tb-collapsible .tb-icon-when-narrow {
		display: inline-block;
	}

	.toolbar .tb-collapsible .tb-label {
		position: absolute;
		width: 1px;
		height: 1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}
}

/* The graph's tools measure the graph, which is narrower than the window
   whenever a panel is docked beside it. */
.centre {
	container: centre / inline-size;
}

@container centre (max-width: 819px) {
	.floating-tools .tb-collapsible .tb-icon-when-narrow {
		display: inline-block;
	}

	.floating-tools .tb-collapsible .tb-label {
		position: absolute;
		width: 1px;
		height: 1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}
}

/* The graph's settings behind a button each, on a phone: see `Popout` in
   Toolbar.tsx. The panel hangs under its button, over the graph. */
.tool-popout {
	position: relative;
}

.tool-popout-panel {
	position: absolute;
	top: calc(100% + 8px);
	left: 0;
	z-index: 20;
	display: flex;
	flex-direction: column;
	gap: 6px;
	min-width: 190px;
	padding: 8px;
	border: 1px solid var(--border-strong);
	border-radius: var(--radius-md);
	background: var(--bg-panel);
	box-shadow: var(--shadow-popover);
}

.tool-popout-end .tool-popout-panel {
	left: auto;
	right: 0;
}

.tool-popout-panel select.tb {
	width: 100%;
}

/* A phone's graph tools, drawn a little tighter so the three groups share one
   row: with two of them behind buttons they are about 400px wide, and a phone
   has 373 once the edges are taken. */
@media (max-width: 699px) {
	.floating-tools {
		left: 6px;
		right: 6px;
		gap: 5px;
	}

	.floating-tools .tool-group {
		gap: 4px;
		padding: 4px;
	}
}

/* The compile target, with Lune's warning after its name rather than the word
   "experimental" in it: drawn over the select, clear of its chevron, and
   passing taps through to it. */
.target-pick {
	position: relative;
	display: inline-flex;
}

.target-pick select.doc-target.lune {
	padding-right: 44px;
}

.target-pick .target-warn {
	position: absolute;
	right: 24px;
	top: 50%;
	transform: translateY(-50%);
	color: var(--warning);
	pointer-events: none;
}

.target-warn-inline {
	color: var(--warning);
}

/* Node Design's node tools, folded on a phone. The type chips wrap into a
   block rather than a line; the pin counts and the node's kind keep a row. */
.pin-chip-grid {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
	max-width: 260px;
}

.pin-counts {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 6px;
}

/* Words a screen reader needs and the row has no room to show. */
.visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

/* A window's header on a phone: a button that carries a word beside its icon
   keeps only the icon, so the header holds one row -- Node Design's Settings
   had wrapped under the rest. */
@media (max-width: 699px) {
	.docs-page-head .tb-collapsible .tb-label {
		position: absolute;
		width: 1px;
		height: 1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}
}

/* Node Design's logic graph with its Luau put away: the graph has the room. */
.logic-split.luau-hidden,
.node-editor.split .logic-split.luau-hidden {
	grid-template-columns: minmax(0, 1fr);
	grid-template-rows: minmax(0, 1fr);
}

/* A phone's picture is small: its glyphs are too, so a row holds what the real
   row holds. */
.docs-layout-frame.device-phone .icon {
	width: 11px;
	height: 11px;
}

.docs-layout-frame.device-phone .docs-layout-region {
	gap: 3px 4px;
	padding: 4px 5px;
}

.docs-layout-frame.device-phone .docs-layout-num {
	width: 14px;
	height: 14px;
	font-size: 9px;
}

.docs-layout-frame.device-phone .docs-layout-region.kind-float {
	margin: 0 3px;
}

.docs-layout-frame.device-phone .docs-layout-chips > span,
.docs-layout-frame.device-phone .docs-layout-chip {
	padding: 0 4px;
}

.docs-layout-frame.device-phone .docs-layout-icons {
	gap: 3px;
}

/* A bar drawn at a tablet's or a phone's width: the real one's width, so it
   wraps where the real one does. Only these wrap; a computer's bar is drawn as
   one line whatever the page's width. */
.docs-bar-frame.device-tablet {
	max-width: 760px;
}

.docs-bar-frame.device-phone {
	max-width: 409px;
}

.docs-bar-frame.device-tablet .toolbar,
.docs-bar-frame.device-phone .toolbar,
.docs-bar-frame.device-tablet .docs-page-head,
.docs-bar-frame.device-phone .docs-page-head,
.docs-bar-frame.device-tablet .floating-tools,
.docs-bar-frame.device-phone .floating-tools {
	flex-wrap: wrap;
}

.docs-bar-frame .docs-bar-break {
	flex-basis: 100%;
	height: 0;
}

/* A walkthrough: one drawing of the screen, the steps under it lit in turn.
   See `src/app/docsWalk.ts`. Without its script the first drawing shows over
   the ordinary numbered list, so nothing here hides a step. */
.docs-walk {
	margin: 16px 0;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	overflow: hidden;
	background: var(--bg-panel);
}

.docs-walk-window {
	overflow-x: auto;
	background: var(--bg-app);
}

.docs-walk-frame[hidden] {
	display: none;
}

/* A drawing keeps its real shape -- one row, words unbroken -- and the script
   scales it to fit the window instead. */
.docs-walk-frame {
	width: max-content;
}

.docs-walk-frame .tb {
	white-space: nowrap;
}

/* Two bars in one frame, as the Project menu sits over the panel's footer. */
.docs-walk-frame .docs-bar-frame + .docs-bar-frame {
	padding-top: 0;
}

.docs-walk-nav {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 8px 12px;
	border-top: 1px solid var(--border);
	border-bottom: 1px solid var(--border);
}

.docs-walk:not(.walk-on) .docs-walk-nav {
	display: none;
}

.docs-walk-count {
	flex: 1;
	text-align: center;
	font-size: var(--text-md);
	color: var(--fg-faint);
}

.docs-walk-steps {
	margin: 0;
	padding: 10px 14px 12px 36px;
}

.docs-walk-steps > li {
	padding: 6px 8px;
	border-radius: var(--radius-sm);
}

.walk-on .docs-walk-steps > li {
	cursor: pointer;
	transition: background-color 120ms ease, color 120ms ease;
}

.walk-on .docs-walk-steps > li.walk-next {
	color: var(--fg-faint);
}

.walk-on .docs-walk-steps > li.walk-done {
	color: var(--fg-faint);
}

.walk-on .docs-walk-steps > li.walk-done::marker {
	color: var(--ok);
}

.walk-on .docs-walk-steps > li.walk-current {
	color: var(--fg);
	background: color-mix(in srgb, var(--accent) 12%, transparent);
	box-shadow: inset 2px 0 0 var(--accent);
}

/* The control to press, ringed, and pressable: it moves to the next step. */
.docs-walk-frame .walk-here {
	box-shadow: 0 0 0 2px var(--accent);
	animation: walk-here 1.6s ease-in-out infinite;
	cursor: pointer;
}

@keyframes walk-here {
	50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 35%, transparent); }
}

@media (prefers-reduced-motion: reduce) {
	.docs-walk-frame .walk-here {
		animation: none;
	}
}

/* An open menu, drawn where it sits rather than hung from a button. */
/* A menu drawn in a page rather than over one: where the figure puts it, and
   without the shadow of something floating. */
.docs-bar-frame.popmenu > .docs-menu-shot {
	position: static;
	box-shadow: none;
}

/* Home's arrow points back, as the projects panel turns it. */
.docs-bar-frame [data-control="home"] .icon {
	transform: rotate(90deg);
}

/* A text box, with its placeholder. */
.docs-bar-field {
	display: inline-flex;
	align-items: center;
	min-width: 220px;
	color: var(--fg-faint);
	cursor: text;
}

/* A drawn Inspector's Luau, as the real one shows it: code, in the mono face. */
.docs-bar-frame.inspector .docs-bar-code {
	font-family: var(--mono);
	font-size: var(--text-sm);
}

/* The code editor's hover: a signature, a sentence, and where to read more.
   The signature is a band of its own: code, in the code editor's face, bold,
   on a shade darker than the body, so it reads as the thing and the sentence
   under it as what is said about it. */
.luau-hover {
	max-width: min(480px, calc(100vw - 24px));
	border-radius: var(--radius-sm);
	overflow: hidden;
	font-size: var(--text-md);
	line-height: 1.45;
	color: var(--fg);
}

.luau-hover-head {
	padding: 7px 10px;
	background: color-mix(in srgb, var(--bg-canvas) 70%, var(--bg-panel));
	border-bottom: 1px solid var(--border);
}

.luau-hover-head:last-child {
	border-bottom: 0;
}

/* What kind of name it is, under the name: quieter than the code, which is
   the thing the hover is about. */
.luau-hover-role {
	display: block;
	margin-top: 2px;
	font-size: var(--text-sm);
	color: var(--fg-faint);
}

.luau-hover code {
	display: block;
	font-family: "Cascadia Mono", Consolas, ui-monospace, monospace;
	font-size: var(--text-lg);
	font-weight: 700;
	color: var(--fg);
	white-space: pre-wrap;
}

.luau-hover p {
	margin: 0;
	padding: 7px 10px 0;
	color: var(--fg-muted);
}

/* A sentence that ends the card -- a type's card under its chip, or a hover
   with no docs and no link -- keeps the room below it that the docs and the
   link keep, rather than sitting on the card's edge. */
.luau-hover > p:last-child {
	padding-bottom: 8px;
}

/* The code's own doc comment: prose, examples and tags, scrolling past a
   screenful rather than covering the file. Its code is inline and plain,
   where the heading's is the signature in bold. */
.luau-hover-doc {
	max-height: min(360px, 50vh);
	overflow: auto;
	padding-bottom: 8px;
}

.luau-hover .luau-hover-doc code {
	display: inline;
	font-size: var(--text-sm);
	font-weight: 400;
	white-space: pre-wrap;
}

.luau-hover-doc pre {
	margin: 7px 10px 0;
	padding: 6px 8px;
	border-radius: var(--radius-sm);
	background: color-mix(in srgb, var(--bg-canvas) 70%, var(--bg-panel));
	overflow-x: auto;
}

.luau-hover .luau-hover-doc pre code {
	display: block;
	white-space: pre;
}

.luau-hover .luau-hover-doc a {
	display: inline;
	margin: 0;
}

.luau-hover-tags {
	margin: 7px 10px 0;
}

.luau-hover-tags dt {
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--fg-faint);
}

.luau-hover-tags dd {
	margin: 2px 0 0;
	color: var(--fg-muted);
}

.luau-hover-deprecated,
.luau-hover-yields {
	font-weight: 600;
}

.luau-hover .luau-hover-deprecated {
	color: var(--warning);
}

.luau-hover .luau-hover-note {
	margin: 7px 10px 0;
	padding: 5px 8px;
	border-left: 2px solid var(--warning);
	background: color-mix(in srgb, var(--warning) 8%, transparent);
}

.luau-hover a {
	display: inline-block;
	margin: 6px 10px 8px;
	color: var(--accent);
}

/* The call being typed, above the cursor: its parameters, the one the cursor
   is on picked out. Code, like the hover's signature line. */
.luau-signature {
	padding: 5px 9px;
	border-radius: var(--radius-sm);
}

.luau-signature code {
	font-family: "Cascadia Mono", Consolas, ui-monospace, monospace;
	font-size: var(--text-md);
	color: var(--fg-muted);
	white-space: pre-wrap;
}

.luau-signature .param.active {
	color: var(--fg);
	font-weight: 700;
	text-decoration: underline;
	text-underline-offset: 3px;
}

/* A finger needs a bigger target than a pointer. */
@media (hover: none) and (pointer: coarse) {
	:root {
		--hit: 38px;
	}
}

/* ------------------------------------------------------- floating chrome */

/* The clusters along the top of the window: over the centre and the cards,
   taking clicks only on the clusters themselves. */
.workspace-chrome {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	z-index: 5;
	pointer-events: none;
}

.workspace {
	container: workspace / inline-size;
}

/* One row, always: a cluster that wrapped would land on the cards below. What
   does not fit is folded into More -- see ProjectBar. */
.workspace-chrome .floating-tools {
	flex-wrap: nowrap;
}

.workspace-chrome .tool-group {
	flex: none;
	flex-wrap: nowrap;
}

.workspace-chrome .tool-group.graph-tabs {
	flex: 0 1 auto;
}

/* The mark, as a button in its own cluster. */
.window-mark {
	display: inline-flex;
	align-items: center;
	gap: 7px;
	height: var(--hit);
	padding: 0 8px;
	background: none;
	border: 0;
	border-radius: var(--radius-sm);
	color: var(--fg);
	font-family: inherit;
	font-weight: 600;
	cursor: pointer;
}

.window-mark:hover,
.window-mark:focus-visible {
	background: var(--bg-hover);
}

.window-mark .version {
	font-weight: 400;
	font-size: var(--text-xs);
	color: var(--fg-faint);
	font-variant-numeric: tabular-nums;
}

/* The window's glyph, grey, beside the mark: a phone's name for the window. */
.window-mark .window-glyph {
	display: none;
	align-items: center;
	padding-left: 7px;
	border-left: 1px solid var(--border-strong);
	color: var(--fg-faint);
}

@media (max-width: 699px) {
	.window-mark .window-glyph {
		display: inline-flex;
	}

	.window-mark .window-name,
	.window-mark .version {
		display: none;
	}
}

/* The modes, one tab: every page the tab has shown stays laid out at full
   size, and the ones behind are invisible and inert. Invisible rather than
   gone, so a canvas behind is never measured at nothing. See `pageHost.tsx`. */
#root {
	position: relative;
}

.page-layer {
	position: absolute;
	inset: 0;
}

/* Every descendant too: a child that sets `visibility: visible` -- the close
   button on the graph tab in front -- would otherwise show through. The page
   being left stays drawn while the next one comes in. */
.page-layer:not([data-showing]):not([data-leaving]),
.page-layer:not([data-showing]):not([data-leaving]) * {
	visibility: hidden !important;
}

/* A switch: the page being left stays as it was, under the page coming, which
   is drawn at nothing until it is ready and then fades in. See `PageHost`. */
.page-layer[data-leaving] {
	z-index: 1;
}

.page-layer[data-entering] {
	z-index: 2;
	opacity: 0;
}

.page-layer[data-entering][data-fading] {
	opacity: 1;
	transition: opacity 200ms ease-out;
}

/* Meanwhile the mark and the strip are a copy over both pages (`Flight`), and
   each page's own are hidden under it. The copy's box slides as the new page
   fades in. */
.page-layer[data-leaving] .mark-group:not(.docs-bar-frame *),
.page-layer[data-leaving] .mode-strip:not(.docs-bar-frame *),
.page-layer[data-entering] .mark-group:not(.docs-bar-frame *),
.page-layer[data-entering] .mode-strip:not(.docs-bar-frame *) {
	visibility: hidden !important;
}

.mode-flight {
	position: fixed;
	inset: 0;
	z-index: 3;
	pointer-events: none;
}

.mode-flight > * {
	position: absolute;
	margin: 0;
	box-sizing: border-box;
}

.mode-flight .mode-box {
	transition: left 320ms cubic-bezier(0.32, 0.72, 0, 1);
}

@media (prefers-reduced-motion: reduce) {
	.page-layer[data-entering][data-fading],
	.mode-flight .mode-box {
		transition: none;
	}
}

/* The editor's first frame while it finds its project (`StartingFrame`), and
   what it fades in once it has: the canvas, the mark and the strip stay put,
   and the panels and the graph arrive. */
.app.starting .workspace {
	flex: 1;
}

.app.arriving .centre-body,
.app.arriving .dock,
.app.arriving .float-layer {
	animation: app-arrive 180ms ease-out both;
}

@keyframes app-arrive {
	from {
		opacity: 0;
	}
}

@media (prefers-reduced-motion: reduce) {
	.app.arriving .centre-body,
	.app.arriving .dock,
	.app.arriving .float-layer {
		animation: none;
	}
}

/* The mode strip: Editor, Design and Docs after the mark, and a box behind
   the one you are in that slides over from the one you left. */
.mode-strip {
	flex-wrap: nowrap;
}

.mode-slots {
	position: relative;
	display: grid;
	grid-template-columns: repeat(3, var(--hit));
	gap: 2px;
}

.mode-box {
	position: absolute;
	top: 0;
	left: calc(var(--mode-at, 0) * (var(--hit) + 2px));
	width: var(--hit);
	height: var(--hit);
	border-radius: var(--radius-sm);
	background: color-mix(in srgb, var(--accent) 22%, transparent);
	box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent);
	pointer-events: none;
}

.mode-slot {
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: var(--hit);
	height: var(--hit);
	border-radius: var(--radius-sm);
	color: var(--fg-muted);
	text-decoration: none;
}

.mode-slot:hover {
	background: var(--bg-hover);
	color: var(--fg);
}

.mode-slot[aria-current="page"],
.mode-slot[aria-current="page"]:hover {
	background: none;
	color: var(--accent);
}

.mode-slot:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: -2px;
}

@media (max-width: 699px) {
	.mode-strip:not(.docs-bar-frame *) {
		display: none;
	}
}

/* The docs' Try it is the strip's Editor where the strip is drawn. */
@media (min-width: 700px) {
	.docs-page-head a.tb.docs-try {
		display: none;
	}
}

/* Leaving the static docs for the app is a page load, and the browser
   crossfades it where it can. The whole page, not the mark and the strip on
   their own: a browser draws a named element as a picture by itself, and a
   frosted cluster pictured without what is behind it is a flat, pale box.
   They sit on the same pixels on both, so they hold still in the crossfade,
   and the strip's box slides on the page arriving. A switch in the app is
   not a page load and needs none of this: see `PageHost`. */
@view-transition {
	navigation: auto;
}

::view-transition-old(root),
::view-transition-new(root) {
	animation-duration: 200ms;
}

@media (prefers-reduced-motion: reduce) {
	::view-transition-group(*),
	::view-transition-old(*),
	::view-transition-new(*) {
		animation: none !important;
	}
}

/* The open document's name and kind, where it has no tab: a node map. As
   tall as every other cluster on the row: a button's height, the 3px of
   padding and the 1px of border each side, the border counted because the box
   is sized by its border. */
.tool-group.doc-group {
	padding: 0 12px;
	height: calc(var(--hit) + 8px);
	box-sizing: border-box;
	gap: 8px;
}

.tool-group .doc-name {
	font-weight: 600;
}

.tool-group .doc-kind {
	font-size: var(--text-sm);
	color: var(--fg-faint);
}

/* Not a graph tab: a tab carries its own dot, `.tab-dirty`, and a second one
   here landed after its close button. */
.tool-group .dirty:not(.graph-tab)::after {
	content: "•";
	color: var(--warning);
	margin-left: 5px;
}

/* Where the row is short of room, the buttons that carry a word show only
   their glyph. The word stays as the button's accessible name. */
@container workspace (max-width: 1099px) {
	.workspace-chrome .tb-collapsible .tb-icon-when-narrow {
		display: inline-block;
	}

	.workspace-chrome .tb-collapsible .tb-label {
		position: absolute;
		width: 1px;
		height: 1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}

	.workspace-chrome .window-mark .version {
		display: none;
	}
}

/* The More menu, on a phone: the switch fills its row. */
.tool-popout-panel .segmented {
	display: flex;
}

.tool-popout-panel .segmented button {
	flex: 1;
	min-height: var(--hit);
}

/* --------------------------------------------------------------- side strip */

/* Zoom, undo and redo, and fit, down the left edge of the graph. */
.side-strip {
	position: absolute;
	left: var(--edge);
	top: 50%;
	transform: translateY(-50%);
	z-index: 4;
	flex-direction: column;
	flex-wrap: nowrap;
	padding: 4px 3px;
}

.side-strip .strip-zoom {
	writing-mode: vertical-lr;
	direction: rtl;
	width: 22px;
	height: 120px;
	margin: 4px 0;
	accent-color: var(--accent);
	cursor: pointer;
}

.side-strip .strip-percent {
	background: none;
	border: 0;
	padding: 2px 0;
	font: inherit;
	font-size: var(--text-xs);
	color: var(--fg-muted);
	font-variant-numeric: tabular-nums;
	cursor: pointer;
}

.side-strip .strip-percent:hover {
	color: var(--fg);
}

.side-strip .strip-rule {
	width: 60%;
	height: 1px;
	margin: 3px 0;
	background: var(--border);
}

/* A phone has the bottom bar, and no room down the side. */
@media (max-width: 699px) {
	.side-strip {
		display: none;
	}
}

/* With a mouse, the strip lies along the bottom centre instead: a desk's
   screen is short of width before height, and a thumb has no edge to rest on. */
@media (hover: hover) and (pointer: fine) and (min-width: 700px) {
	.workspace.has-strip {
		--strip-w: 0px;
	}

	.side-strip {
		top: auto;
		left: 50%;
		bottom: var(--edge);
		transform: translateX(-50%);
		flex-direction: row;
		padding: 3px 6px;
	}

	.side-strip .strip-zoom {
		writing-mode: horizontal-tb;
		direction: ltr;
		width: 120px;
		height: 22px;
		margin: 0 4px;
	}

	.side-strip .strip-percent {
		min-width: 3.4em;
		padding: 0 4px;
	}

	.side-strip .strip-rule {
		width: 1px;
		height: 60%;
		margin: 0 3px;
	}
}

/* ------------------------------------------------------- graph settings */

.graph-settings .field select.tb,
.graph-settings .target-pick {
	width: 100%;
}

.graph-settings .target-pick {
	display: flex;
	align-items: center;
	gap: 6px;
}

.graph-settings .target-pick select.tb {
	flex: 1;
	font-size: inherit;
	padding: 4px 9px;
}

.graph-settings .graph-figures {
	display: flex;
	gap: 16px;
	margin: 10px 0 4px;
	color: var(--fg-muted);
	font-size: var(--text-sm);
}

.graph-settings .graph-figures b {
	color: var(--fg);
	font-variant-numeric: tabular-nums;
}

/* ----------------------------------------------------- views under chrome */

/* What the centre shows when it is not a canvas -- a node map, a source file,
   the aliases -- is a sheet on the canvas: below the top clusters, clear of the
   cards either side and of Script analysis at the foot, by its measured height
   (--pill-h), folded or open. Sized by the grid, not `height: 100%`, which
   with the top margin ran the sheet off the bottom and under the card. */
.workspace {
	--left-w: 0px;
	--right-w: 0px;
}

.centre-body > .map-editor,
.centre-body > .source,
.centre-body > .alias-doc {
	margin: var(--chrome-top) calc(var(--edge) * 2 + var(--right-w))
		calc(var(--pill-h, var(--hit)) + var(--edge) * 2) calc(var(--edge) * 2 + var(--left-w));
	height: auto;
	min-width: 0;
	min-height: 0;
	background: var(--bg-panel);
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-raised);
	overflow: hidden;
}


.centre-body > .placeholder {
	margin-top: var(--chrome-top);
}

/* ------------------------------------------- a window's floating chrome */

/* Node Design's and the docs' clusters along the top, as the editor's are:
   over everything in the window, taking clicks only on the clusters. */
.window-chrome {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	z-index: 30;
	pointer-events: none;
}

.window-chrome .floating-tools {
	flex-wrap: nowrap;
}

.window-chrome .tool-group {
	flex: none;
	flex-wrap: nowrap;
}

/* The open node's actions are drawn into this, as a cluster of the row. */
.window-chrome .tool-slot {
	display: contents;
}

.window-chrome a.tb.icon-only {
	display: inline-grid;
	place-items: center;
	width: var(--hit);
	height: var(--hit);
	padding: 0;
	box-sizing: border-box;
}

/* ------------------------------------------------ Node Design, floating */

.designer {
	position: relative;
	height: 100%;
	overflow: hidden;
}

.designer > .pack-view,
.designer > .pack-browser {
	position: relative;
	flex: 1;
	min-height: 0;
}

/* The node list, a card at the left; the editor fills the window behind it. */
.pack-view:not(.compact) {
	display: block;
	--list-w: calc(260px + var(--edge));
}

.pack-view:not(.compact) > .pack-nodes {
	position: absolute;
	left: var(--edge);
	top: var(--chrome-top);
	z-index: 10;
	width: 260px;
	max-height: calc(100% - var(--chrome-top) - var(--edge));
	box-sizing: border-box;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-raised);
}

.pack-view > .node-editor:not(.split),
.pack-view:not(.compact) > .designer-stage {
	position: absolute;
	inset: 0;
}

.pack-view:not(.compact) > .designer-stage {
	padding-top: var(--chrome-top);
	padding-left: calc(var(--list-w) + var(--edge));
}

/* On a tablet the list is a drawer, opened from a cluster below the chrome. */
.pack-view.compact {
	display: block;
	--list-w: 0px;
}

.pack-view.compact > .pack-compact-bar {
	position: absolute;
	top: var(--chrome-top);
	left: var(--edge);
	right: auto;
	max-width: calc(100% - var(--edge) * 2);
	box-sizing: border-box;
	padding: 3px 8px 3px 3px;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	background: var(--glass);
	box-shadow: var(--shadow-raised);
	backdrop-filter: blur(14px) saturate(1.2);
	-webkit-backdrop-filter: blur(14px) saturate(1.2);
}

.pack-view.compact .pack-nodes.drawer {
	top: calc(var(--chrome-top) + var(--hit) + 18px);
	bottom: var(--edge);
	left: var(--edge);
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-popover);
}

.pack-view.compact .pack-nodes.drawer:not(.drawer-open) {
	transform: translateX(-16px);
	opacity: 0;
}

/* The logic fills the window; the node floats over it on its plate. */
.node-editor:not(.split) {
	display: block;
}

/* The canvas grid under Luau too, so the window reads the same whichever way
   the logic is written; the node canvas draws its own over it. */
.node-editor:not(.split) > .node-logic {
	position: absolute;
	inset: 0;
	background-color: var(--bg-canvas);
}

.node-editor:not(.split) .logic-split {
	position: absolute;
	inset: 0;
	display: block;
}

.node-editor:not(.split) .logic-canvas {
	position: absolute;
	inset: 0;
}

/* The logic's tools, and how it is written, at the bottom left: the top is
   the window's chrome and the node's plate. */
.node-editor:not(.split) .logic-canvas .floating-tools {
	top: auto;
	bottom: var(--edge);
	left: calc(var(--list-w, 0px) + var(--edge));
	right: auto;
}

.node-editor:not(.split) .logic-head.logic-head-folded {
	display: none;
}

/* The how-to is the head's tooltip here: a sentence in a floating group runs
   into the palette beside it. */
.node-editor:not(.split) .logic-head .hint {
	display: none;
}

/* How a node's logic is written, in Node Design's top row: drawn as it was
   in the logic's head -- the word in bold, the switch on its own track --
   rather than as two of the row's buttons, so it reads as one setting. */
.tool-group.logic-mode-group {
	align-items: center;
	gap: 8px;
	padding-left: 10px;
}

.tool-group.logic-mode-group .group-label {
	margin: 0;
	font-size: var(--text-md);
	font-weight: 700;
	color: var(--fg);
	white-space: nowrap;
}

/* Node Design's pack bar on a phone: Preview or Logic, then Luau or Nodes.
   Two of the cluster's flat switches side by side read as one switch of four,
   so here each is its own box, as Logic's is on a wider screen, a little apart.
   `PackView` marks its slot, and `docs/toolbars.ts` the drawing of it. */
.tool-group .pack-switches .segmented,
.tool-group.pack-switches .segmented {
	padding: 2px;
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
}

.tool-group .pack-switches .segmented + .segmented,
.tool-group.pack-switches .segmented + .segmented {
	margin-left: 8px;
}

.tool-group .pack-switches .segmented button,
.tool-group.pack-switches .segmented button {
	min-height: calc(var(--hit) - 6px);
	padding: 0 10px;
	border-radius: var(--radius-xs);
}

.tool-group.logic-mode-group .segmented {
	padding: 2px;
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
}

.tool-group.logic-mode-group .segmented button {
	min-height: calc(var(--hit) - 6px);
	padding: 0 10px;
	border-radius: var(--radius-xs);
}

.tool-group.logic-mode-group .segmented button.on {
	font-weight: 400;
}

.node-editor:not(.split) .logic-head {
	position: absolute;
	left: calc(var(--list-w, 0px) + var(--edge));
	bottom: var(--edge);
	z-index: 7;
	padding: 3px 8px;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	background: var(--glass);
	box-shadow: var(--shadow-raised);
}

/* The Luau the logic compiles to: a card at the bottom right, when asked for. */
.node-editor:not(.split) .logic-compiled {
	position: absolute;
	right: var(--edge);
	bottom: calc(var(--edge) * 3 + var(--hit) + 48px);
	z-index: 6;
	width: min(420px, 40%);
	max-height: 40%;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	background: var(--bg-panel);
	box-shadow: var(--shadow-popover);
}

/* Luau written by hand: a sheet below the plate, clear of the list. */
.node-editor:not(.split) .luau-field,
.node-editor:not(.split) .logic-empty {
	position: absolute;
	top: var(--chrome-top);
	left: calc(var(--list-w, 0px) + var(--edge));
	right: calc(var(--plate-w, 0px) + var(--edge) * 2);
	bottom: calc(var(--edge) * 3 + var(--hit) + 48px);
	margin: 0;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	background: var(--bg-panel);
	box-shadow: var(--shadow-raised);
}

/* The node's plate: the face, on the grid it is drawn on, floating over the
   logic at the top centre. */
/* The top right: the node's plate, the selected pin's settings under it, and
   the node's details under those -- one column, each as tall as it needs. */
.node-editor:not(.split) > .plate-column {
	position: absolute;
	top: var(--chrome-top);
	right: var(--edge);
	z-index: 5;
	display: flex;
	flex-direction: column;
	align-items: stretch;
	gap: var(--edge);
	width: min(var(--plate-w, 400px), calc(100% - var(--edge) * 2));
	max-height: calc(100% - var(--chrome-top) - var(--edge) * 4 - var(--hit) - 48px);
	overflow-y: auto;
	pointer-events: none;
}

.node-editor:not(.split) > .plate-column > * {
	pointer-events: auto;
	flex: none;
}

.node-editor.split > .plate-column {
	display: contents;
}

.node-editor:not(.split) .plate-column > .pin-popover.docked,
.node-editor:not(.split) .plate-column > .node-details {
	position: static;
	width: auto;
	max-height: none;
	box-shadow: var(--shadow-raised);
}

/* The pin's card gives way when the column runs out of room, its body
   scrolling, rather than running off the column's foot. */
.node-editor:not(.split) .plate-column > .pin-popover.docked {
	flex: 0 1 auto;
	min-height: calc(var(--hit) * 3 + 24px);
}

.pin-popover.folded > :not(.pin-popover-head) {
	display: none;
}

.node-editor:not(.split) .plate-column > .node-editor-stage {
	position: relative;
	z-index: 5;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-raised);
}

/* The plate's title bar: the node's name, and the fold. */
.plate-head {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	z-index: 6;
	display: flex;
	align-items: center;
	gap: 6px;
	height: 34px;
	padding: 0 4px 0 12px;
	box-sizing: border-box;
	border-bottom: 1px solid var(--border);
	background: color-mix(in srgb, var(--bg-panel) 92%, transparent);
	border-radius: var(--radius-md) var(--radius-md) 0 0;
}

.plate-head .plate-title {
	flex: 1;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	font-weight: 600;
}

.plate-head button.tb.icon-only {
	width: 28px;
	height: 28px;
	background: transparent;
	border-color: transparent;
}

/* Folded: the title bar alone. */
.node-editor-stage.folded {
	overflow: hidden;
}

.node-editor-stage.folded > :not(.plate-head) {
	display: none;
}

.node-editor-stage.folded .plate-head {
	border-bottom: 0;
	border-radius: var(--radius-md);
}

/* How many inputs and outputs, under the node on its plate. */
.plate-counts {
	position: absolute;
	left: 50%;
	bottom: 8px;
	transform: translateX(-50%);
	z-index: 6;
	/* At 50% it may only shrink into the stage's right half, and with a
	   touch screen's 38px buttons it wrapped onto two lines under the node. */
	width: max-content;
	flex-wrap: nowrap;
}

/* Each side's count as a stepper: the word, then − count + on one track, as
   the Logic switch in the top row is drawn. */
.pin-stepper {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	padding-left: 6px;
}

.pin-stepper .group-label {
	font-size: var(--text-md);
	font-weight: 700;
	color: var(--fg);
	white-space: nowrap;
}

.pin-stepper-track {
	display: inline-flex;
	align-items: center;
	padding: 1px;
	background: var(--bg-input);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
}

/* `button` so it outranks the tool group's own button size. */
.pin-stepper-track button.tb.icon-only {
	width: calc(var(--hit) - 6px);
	min-width: 0;
	height: calc(var(--hit) - 8px);
	min-height: 0;
	padding: 0;
	border: 0;
	border-radius: var(--radius-xs);
	background: none;
	font-size: var(--text-lg);
	line-height: 1;
}

.pin-stepper-track .tb.icon-only:hover:not(:disabled) {
	background: var(--bg-hover);
}

.pin-stepper-count {
	min-width: 2ch;
	text-align: center;
	font-variant-numeric: tabular-nums;
	font-weight: 600;
	color: var(--accent);
}

/* The types to drag onto the node, docked along the foot of the window at
   the right, as a painting app's colours are; the logic's tools have the left. */
.node-editor > .pin-palette {
	position: absolute;
	right: var(--edge);
	bottom: var(--edge);
	z-index: 7;
	justify-content: flex-end;
	max-width: calc(100% - var(--list-w, 0px) - var(--edge) * 3 - var(--tools-w, 190px));
}

.node-editor.split > .pin-palette {
	position: static;
	transform: none;
	align-self: center;
	margin: 6px 0;
	max-width: calc(100% - var(--edge) * 2);
}

/* The node's details, a card at the right; its problems, a pill below. */
.node-editor .plate-column > .node-details {
	box-sizing: border-box;
}

.node-editor > .node-problems {
	left: auto;
	right: var(--edge);
	bottom: calc(var(--edge) * 2 + var(--hit) + 8px);
	z-index: 8;
	max-width: min(360px, calc(100% - var(--edge) * 2));
}

/* Without the window's chrome to sit in: over the editor's top right. */
.node-editor > .node-actions {
	position: absolute;
	top: var(--edge);
	right: var(--edge);
	z-index: 9;
}

/* On a phone the node and its logic take turns, below the chrome. */
.node-editor.split {
	position: absolute;
	inset: calc(var(--chrome-top) + var(--hit) + 18px) 0 0 0;
}

/* The docs window's search, beside the mark. Its own box in the contents
   column would be a second way to the same search. */
.docs-page-head .tb.docs-search-field {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	min-width: 220px;
	margin-left: 4px;
	background: var(--bg-input);
	border: 1px solid var(--border);
	color: var(--fg-faint);
	cursor: text;
}

.docs-page-head .tb.docs-search-field:hover {
	background: var(--bg-input);
	border-color: var(--border-strong);
}

.docs-search-field kbd {
	margin-left: auto;
	padding: 0 5px;
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	font: inherit;
	font-size: var(--text-xs);
}

.docs-window .docs-nav > .search {
	display: none;
}

@media (max-width: 760px), (hover: none) and (pointer: coarse) {
	.docs-page-head .tb.docs-search-field {
		min-width: 0;
	}

	.docs-search-field .docs-search-label,
	.docs-search-field kbd {
		display: none;
	}
}

/* The docs window's header in the editor's design language: no bar, only the
   floating groups, each glass, with the page reading under them. */
.docs-page-head.docs-clusters {
	gap: 8px;
	padding: 0;
	background: none;
	border: 0;
	box-shadow: none;
	backdrop-filter: none;
	-webkit-backdrop-filter: none;
}

.docs-clusters .spacer {
	flex: 1;
}

/* Each group keeps its one line, as the editor's do. */
.docs-clusters .tool-group {
	flex-wrap: nowrap;
}

.docs-clusters .tool-group a.tb.icon-only {
	display: inline-grid;
	place-items: center;
	width: var(--hit);
	height: var(--hit);
	padding: 0;
	box-sizing: border-box;
}

/* The search field fills its group. */
.docs-clusters .search-group {
	padding: 3px;
}

.docs-clusters .search-group .tb.docs-search-field {
	margin-left: 0;
	min-height: var(--hit);
}

/* Contents is a drawer's button: absent while the contents are beside the page. */
.docs-clusters .contents-group {
	display: none;
}

@media (max-width: 760px), (hover: none) and (pointer: coarse) {
	.docs-clusters .contents-group {
		display: flex;
	}
}

/* ------------------------------------- the docs window's contents and outline */

/* Each a card, as the editor's panels are, with a title bar that folds it. A
   folded card says where you are: the contents the section and the page, the
   outline the heading being read. */
.docs-window .docs-nav,
.docs-window .docs-toc-card {
	align-self: start;
	box-sizing: border-box;
	max-height: calc(100% - var(--edge) * 2);
	overflow: auto;
	background: var(--bg-panel);
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-raised);
}

.docs-window .docs-nav {
	margin: var(--edge) 0 var(--edge) var(--edge);
	padding: 0 6px 8px;
}

.docs-window .docs-toc {
	border-left: 0;
	padding: var(--edge) var(--edge) var(--edge) 0;
}

.docs-window .docs-toc-card {
	display: flex;
	flex-direction: column;
	gap: 2px;
	padding: 0 6px 8px;
}

.docs-card-head {
	position: sticky;
	top: 0;
	z-index: 1;
	display: flex;
	align-items: center;
	gap: 8px;
	min-width: 0;
	margin: 0 -6px 6px;
	padding: 4px 4px 4px 12px;
	border-bottom: 1px solid var(--border);
	background: var(--bg-panel);
}

.docs-card-title {
	flex: none;
	font-size: var(--text-xs);
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.09em;
	color: var(--fg-faint);
}

/* Where you are: the card's label, open or folded. */
.docs-card-where {
	display: block;
	flex: 1;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	font-size: var(--text-md);
}

.docs-card-where .crumb {
	color: var(--fg-faint);
}

.docs-card-where .crumb::after {
	content: " › ";
}

.docs-card-fold {
	flex: none;
	margin-left: auto;
}

.docs-card-head button.tb.icon-only {
	width: 28px;
	height: 28px;
	background: transparent;
	border-color: transparent;
}

.docs-nav.folded .docs-card-where,
.docs-toc-card.folded .docs-card-where {
	display: block;
}

.docs-nav.folded > :not(.docs-card-head),
.docs-toc-card.folded > :not(.docs-card-head) {
	display: none;
}

.docs-nav.folded .docs-card-head,
.docs-toc-card.folded .docs-card-head {
	margin-bottom: -8px;
	border-bottom: 0;
}

/* The section being read, lit in the outline. */
.docs-toc-link.on {
	color: var(--accent);
	font-weight: 600;
}

/* The outline's own heading is the card's title bar now. */
.docs-toc-card .docs-toc-head {
	display: none;
}

/* As a drawer, the contents are already a sheet: no second card. */
@media (max-width: 760px), (hover: none) and (pointer: coarse) {
	.docs-window .docs-nav {
		margin: 0;
		border-radius: 0;
		box-shadow: none;
		max-height: none;
	}

	.docs-window .docs-nav .docs-card-fold {
		display: none;
	}
}

/* The page scrolls the window's whole width, its scrollbar at the right edge,
   and the contents and the outline float over it as cards. The article keeps
   clear of them. A drawer's layout, on a touch screen or a narrow window, is
   left as it was. */
@media (min-width: 761px) and (pointer: fine) {
	.docs-window .docs-body {
		display: block;
		position: relative;
		overflow: hidden;
	}

	.docs-window .docs-content {
		position: absolute;
		inset: 0;
		max-width: none;
		overflow: auto;
		box-sizing: border-box;
		/* The same room both sides, so the page is centred in the window and
		   both cards sit over its margins, not beside it. */
		padding: var(--edge) calc(260px + var(--edge) * 3) 60px;
	}

	.docs-window .docs-nav {
		position: absolute;
		left: var(--edge);
		top: var(--edge);
		z-index: 5;
		width: 260px;
		margin: 0;
	}

	/* Clear of the page's scrollbar at the right edge. */
	.docs-window .docs-toc {
		position: absolute;
		right: calc(var(--edge) + 12px);
		top: var(--edge);
		z-index: 5;
		width: 220px;
		max-height: calc(100% - var(--edge) * 2);
		padding: 0;
		overflow: visible;
		pointer-events: none;
	}

	.docs-window .docs-toc > * {
		pointer-events: auto;
	}

	/* As wide as its column and no wider: a long section title is cut short
	   rather than pushing the card over the page's scrollbar. */
	.docs-window .docs-toc-card,
	.docs-window .docs-nav {
		max-height: 100%;
		max-width: 100%;
		min-width: 0;
		box-sizing: border-box;
	}

	.docs-window .docs-toc-card {
		width: 100%;
	}

	.docs-window .docs-card-head,
	.docs-window .docs-card-where {
		min-width: 0;
	}

	/* With the contents folded, the page has the window. */
	.docs-window .docs-body:has(> .docs-nav.folded) > .docs-content {
		padding-left: calc(var(--edge) * 2);
		padding-right: calc(var(--edge) * 2 + 12px);
	}

	/* Glass, as the clusters are: what scrolls beneath reads as beneath. */
	.docs-window .docs-nav,
	.docs-window .docs-toc-card {
		background: var(--glass);
		backdrop-filter: blur(14px) saturate(1.2);
		-webkit-backdrop-filter: blur(14px) saturate(1.2);
	}

	/* The title bar stays over the list as it scrolls, so it is solid: the
	   list sliding through the title read as a clipping fault. */
	.docs-window .docs-card-head {
		background: color-mix(in srgb, var(--bg-panel) 97%, transparent);
		border-radius: var(--radius-md) var(--radius-md) 0 0;
	}
}

/* Too narrow for both margins: the contents keep theirs and the outline goes,
   as it always has below this width. */
@media (min-width: 761px) and (max-width: 1100px) and (pointer: fine) {
	.docs-window .docs-content {
		padding-right: calc(var(--edge) * 2 + 12px);
	}
}

/* ---------------------------------------------------------------- scrollbars */

/* Thin, round, and the theme's own grey on a clear track, everywhere: the
   browser's wide grey bars with arrows were the one piece of chrome nothing
   else looked like. `scrollbar-color` is inherited; the width is not. */
:root {
	scrollbar-color: color-mix(in srgb, var(--fg-faint) 55%, transparent) transparent;
}

* {
	scrollbar-width: thin;
}

/* Where the standard properties are not read. */
::-webkit-scrollbar {
	width: 10px;
	height: 10px;
}

::-webkit-scrollbar-track,
::-webkit-scrollbar-corner {
	background: transparent;
}

::-webkit-scrollbar-thumb {
	background: color-mix(in srgb, var(--fg-faint) 55%, transparent);
	border: 3px solid transparent;
	border-radius: 999px;
	background-clip: padding-box;
}

::-webkit-scrollbar-thumb:hover {
	background-color: var(--fg-muted);
}

::-webkit-scrollbar-button {
	display: none;
}

/* The palette is the bottom row, beside the logic's tools at the bottom
   left, above the home indicator. The action row the node logic has rises
   over that row rather than sitting on it -- wherever the row shows, not on
   a touch screen alone: a tablet with a trackpad, or a short window, has it
   too. */
.node-editor:not(.split) > .pin-palette {
	bottom: calc(var(--edge) + env(safe-area-inset-bottom, 0px));
}

.node-editor:not(.split) .logic-canvas .drawer-toggles {
	bottom: calc(var(--edge) * 2 + var(--hit) + 8px + env(safe-area-inset-bottom, 0px));
}

/* The bottom row's other clusters keep the same inset, so the row lines up:
   Logic's switch in Luau, and the logic's tools with nodes. */
.node-editor:not(.split) .logic-head,
.node-editor:not(.split) .logic-canvas .floating-tools {
	bottom: calc(var(--edge) + env(safe-area-inset-bottom, 0px));
}

.node-editor:not(.split) > .node-problems {
	bottom: calc(var(--edge) * 2 + var(--hit) + 8px + env(safe-area-inset-bottom, 0px));
}

.node-editor:not(.split):has(.touch-bar) > .node-problems {
	bottom: calc(var(--edge) * 3 + var(--hit) * 2 + 16px + env(safe-area-inset-bottom, 0px));
}

/* On a tablet the node list's bar floats at the top left, so the Luau sheet
   starts below it rather than under it. */
.pack-view.compact .node-editor:not(.split) .luau-field,
.pack-view.compact .node-editor:not(.split) .logic-empty {
	top: calc(var(--chrome-top) + var(--hit) + 8px + var(--edge));
}

/* Written in Luau, the bottom row has no logic's tools: the palette keeps
   room only for Logic's switch where that sits there, upright, and otherwise
   starts at the left. The sheet comes down to just above the row. */
.node-editor:not(.split):where(:not(:has(.logic-canvas .floating-tools))) > .pin-palette {
	max-width: calc(100% - var(--list-w, 0px) - var(--edge) * 2);
}

.node-editor:not(.split):where(:not(:has(.logic-canvas .floating-tools))):where(:has(.logic-head:not(.logic-head-folded))) > .pin-palette {
	max-width: calc(100% - var(--list-w, 0px) - var(--edge) * 3 - 180px);
}

.node-editor:not(.split):where(:not(:has(.logic-head:not(.logic-head-folded)))) .luau-field,
.node-editor:not(.split):where(:not(:has(.logic-head:not(.logic-head-folded)))) .logic-empty {
	bottom: calc(var(--edge) * 2 + var(--hit) + 8px + env(safe-area-inset-bottom, 0px));
}

/* Upright -- a tablet held portrait, or a window taller than it is wide --
   the node's column runs across the top, under the node list's bar when
   there is one, and the Luau sheet takes the whole width beneath it rather
   than a strip beside it. */
@media (orientation: portrait) and (min-width: 700px) {
	.node-editor:not(.split) {
		--stack-top: var(--chrome-top);
	}

	.pack-view.compact .node-editor:not(.split) {
		--stack-top: calc(var(--chrome-top) + var(--hit) + 8px + var(--edge));
	}

	.node-editor:not(.split) > .plate-column {
		top: var(--stack-top);
		left: calc(var(--list-w, 0px) + var(--edge));
		width: auto;
		max-height: 65%;
	}

	.node-editor:not(.split) .luau-field,
	.node-editor:not(.split) .logic-empty,
	.pack-view.compact .node-editor:not(.split) .luau-field,
	.pack-view.compact .node-editor:not(.split) .logic-empty {
		top: calc(var(--stack-top) + var(--column-h, 0px) + var(--edge));
		right: var(--edge);
		bottom: calc(var(--edge) * 3 + var(--hit) + 48px + env(safe-area-inset-bottom, 0px));
	}
}

/* The palette is one row that scrolls sideways, at any width: wrapped, it
   climbed into the logic it docks over. As tall as the clusters beside it on
   the bottom row, its chips centred, so the row reads as one line. */
.node-editor > .pin-palette {
	flex-wrap: nowrap;
	overflow-x: auto;
	scrollbar-width: none;
	min-height: calc(var(--hit) + 8px);
	box-sizing: border-box;
	align-items: center;
}

.node-editor > .pin-palette > * {
	flex: none;
}

/* On a phone the problems sit at the top of the editor, clear of the node's
   pin counts at its foot, and the details' switches wrap rather than spill. */
.node-editor.split > .node-problems {
	top: var(--edge);
	bottom: auto;
	left: var(--edge);
	right: var(--edge);
	max-width: none;
}

@media (max-width: 699px) {
	.node-details .segmented {
		flex-wrap: wrap;
	}

	.node-details .segmented button {
		white-space: normal;
	}
}

/* With a mouse the strip is at the bottom centre: the status pill keeps to its
   half of the edge and does not slide under it. */
@media (hover: hover) and (pointer: fine) and (min-width: 700px) {
	.dock.bottom {
		max-width: min(640px, calc(50% - 210px));
	}
}

/* ------------------------------------------------- window diagrams, scaled */

/* A window diagram is the window at its real size, scaled by `--z` to fill
   the column it is in: the column is measured as a container and the scale
   stepped every 25px. A phone keeps its own scale, being tall rather than
   wide. Below the narrowest step the picture scrolls sideways. */
.docs-layout-fit {
	container: docs-layout / inline-size;
	overflow-x: auto;
	padding: 4px 6px;
}

.docs-layout-screen.dev-phone {
	--z: 0.68;
}

.docs-layout-screen.dev-desktop {
	--z: 0.085;
}

.docs-layout-screen.dev-tablet {
	--z: 0.131;
}

@container docs-layout (min-width: 200px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.085;
	}
}

@container docs-layout (min-width: 225px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.101;
	}
}

@container docs-layout (min-width: 250px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.116;
	}
}

@container docs-layout (min-width: 275px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.132;
	}
}

@container docs-layout (min-width: 300px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.147;
	}
}

@container docs-layout (min-width: 325px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.163;
	}
}

@container docs-layout (min-width: 350px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.179;
	}
}

@container docs-layout (min-width: 375px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.194;
	}
}

@container docs-layout (min-width: 400px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.21;
	}
}

@container docs-layout (min-width: 425px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.226;
	}
}

@container docs-layout (min-width: 450px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.241;
	}
}

@container docs-layout (min-width: 475px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.257;
	}
}

@container docs-layout (min-width: 500px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.273;
	}
}

@container docs-layout (min-width: 525px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.288;
	}
}

@container docs-layout (min-width: 550px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.304;
	}
}

@container docs-layout (min-width: 575px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.319;
	}
}

@container docs-layout (min-width: 600px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.335;
	}
}

@container docs-layout (min-width: 625px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.351;
	}
}

@container docs-layout (min-width: 650px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.366;
	}
}

@container docs-layout (min-width: 675px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.382;
	}
}

@container docs-layout (min-width: 700px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.398;
	}
}

@container docs-layout (min-width: 725px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.413;
	}
}

@container docs-layout (min-width: 750px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.429;
	}
}

@container docs-layout (min-width: 775px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.444;
	}
}

@container docs-layout (min-width: 800px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.46;
	}
}

@container docs-layout (min-width: 825px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.476;
	}
}

@container docs-layout (min-width: 850px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.491;
	}
}

@container docs-layout (min-width: 875px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.507;
	}
}

@container docs-layout (min-width: 900px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.522;
	}
}

@container docs-layout (min-width: 925px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.538;
	}
}

@container docs-layout (min-width: 950px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.554;
	}
}

@container docs-layout (min-width: 975px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.569;
	}
}

@container docs-layout (min-width: 1000px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.585;
	}
}

@container docs-layout (min-width: 1025px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.601;
	}
}

@container docs-layout (min-width: 1050px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.616;
	}
}

@container docs-layout (min-width: 1075px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.632;
	}
}

@container docs-layout (min-width: 1100px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.647;
	}
}

@container docs-layout (min-width: 1125px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.663;
	}
}

@container docs-layout (min-width: 1150px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.679;
	}
}

@container docs-layout (min-width: 1175px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.694;
	}
}

@container docs-layout (min-width: 1200px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.71;
	}
}

@container docs-layout (min-width: 1225px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.726;
	}
}

@container docs-layout (min-width: 1250px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.741;
	}
}

@container docs-layout (min-width: 1275px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.757;
	}
}

@container docs-layout (min-width: 1300px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.772;
	}
}

@container docs-layout (min-width: 1325px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.788;
	}
}

@container docs-layout (min-width: 1350px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.804;
	}
}

@container docs-layout (min-width: 1375px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.819;
	}
}

@container docs-layout (min-width: 1400px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.835;
	}
}

@container docs-layout (min-width: 1425px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.851;
	}
}

@container docs-layout (min-width: 1450px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.866;
	}
}

@container docs-layout (min-width: 1475px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.882;
	}
}

@container docs-layout (min-width: 1500px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.897;
	}
}

@container docs-layout (min-width: 1525px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.913;
	}
}

@container docs-layout (min-width: 1550px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.929;
	}
}

@container docs-layout (min-width: 1575px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.944;
	}
}

@container docs-layout (min-width: 1600px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.96;
	}
}

@container docs-layout (min-width: 1625px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.976;
	}
}

@container docs-layout (min-width: 1650px) {
	.docs-layout-screen.dev-desktop {
		--z: 0.991;
	}
}

@container docs-layout (min-width: 1675px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1700px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1725px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1750px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1775px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1800px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1825px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1850px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1875px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1900px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1925px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1950px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1975px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2000px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2025px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2050px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2075px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2100px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2125px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2150px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2175px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2200px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2225px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2250px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2275px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2300px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2325px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2350px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2375px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2400px) {
	.docs-layout-screen.dev-desktop {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 200px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.131;
	}
}

@container docs-layout (min-width: 225px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.153;
	}
}

@container docs-layout (min-width: 250px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.175;
	}
}

@container docs-layout (min-width: 275px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.197;
	}
}

@container docs-layout (min-width: 300px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.219;
	}
}

@container docs-layout (min-width: 325px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.241;
	}
}

@container docs-layout (min-width: 350px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.263;
	}
}

@container docs-layout (min-width: 375px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.285;
	}
}

@container docs-layout (min-width: 400px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.307;
	}
}

@container docs-layout (min-width: 425px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.329;
	}
}

@container docs-layout (min-width: 450px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.351;
	}
}

@container docs-layout (min-width: 475px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.373;
	}
}

@container docs-layout (min-width: 500px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.395;
	}
}

@container docs-layout (min-width: 525px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.417;
	}
}

@container docs-layout (min-width: 550px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.44;
	}
}

@container docs-layout (min-width: 575px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.462;
	}
}

@container docs-layout (min-width: 600px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.484;
	}
}

@container docs-layout (min-width: 625px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.506;
	}
}

@container docs-layout (min-width: 650px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.528;
	}
}

@container docs-layout (min-width: 675px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.55;
	}
}

@container docs-layout (min-width: 700px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.572;
	}
}

@container docs-layout (min-width: 725px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.594;
	}
}

@container docs-layout (min-width: 750px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.616;
	}
}

@container docs-layout (min-width: 775px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.638;
	}
}

@container docs-layout (min-width: 800px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.66;
	}
}

@container docs-layout (min-width: 825px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.682;
	}
}

@container docs-layout (min-width: 850px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.704;
	}
}

@container docs-layout (min-width: 875px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.726;
	}
}

@container docs-layout (min-width: 900px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.748;
	}
}

@container docs-layout (min-width: 925px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.771;
	}
}

@container docs-layout (min-width: 950px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.793;
	}
}

@container docs-layout (min-width: 975px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.815;
	}
}

@container docs-layout (min-width: 1000px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.837;
	}
}

@container docs-layout (min-width: 1025px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.859;
	}
}

@container docs-layout (min-width: 1050px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.881;
	}
}

@container docs-layout (min-width: 1075px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.903;
	}
}

@container docs-layout (min-width: 1100px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.925;
	}
}

@container docs-layout (min-width: 1125px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.947;
	}
}

@container docs-layout (min-width: 1150px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.969;
	}
}

@container docs-layout (min-width: 1175px) {
	.docs-layout-screen.dev-tablet {
		--z: 0.991;
	}
}

@container docs-layout (min-width: 1200px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1225px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1250px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1275px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1300px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1325px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1350px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1375px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1400px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1425px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1450px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1475px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1500px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1525px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1550px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1575px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1600px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1625px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1650px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1675px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1700px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1725px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1750px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1775px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1800px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1825px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1850px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1875px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1900px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1925px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1950px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 1975px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2000px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2025px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2050px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2075px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2100px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2125px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2150px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2175px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2200px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2225px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2250px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2275px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2300px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2325px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2350px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2375px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

@container docs-layout (min-width: 2400px) {
	.docs-layout-screen.dev-tablet {
		--z: 1.0;
	}
}

/* The desktop, on a monitor: a bezel with a chin, a neck and a foot. Dark in
   either theme, as the thing it stands for is. */
.docs-layout-frame.monitor {
	position: relative;
	border: 12px solid #0d0f13;
	border-bottom-width: 24px;
	border-radius: 14px;
	margin-bottom: 64px !important;
	box-shadow:
		0 0 0 1px #2a2e36,
		var(--shadow-raised);
}

.docs-layout-frame.monitor::before {
	content: "";
	position: absolute;
	left: 50%;
	top: calc(100% + 24px);
	width: 72px;
	height: 34px;
	transform: translateX(-50%);
	background: linear-gradient(#15181d, #0d0f13);
	clip-path: polygon(16% 0, 84% 0, 100% 100%, 0 100%);
}

.docs-layout-frame.monitor::after {
	content: "";
	position: absolute;
	left: 50%;
	top: calc(100% + 56px);
	width: 220px;
	height: 8px;
	transform: translateX(-50%);
	border-radius: 4px 4px 2px 2px;
	background: #15181d;
	box-shadow: 0 0 0 1px #2a2e36;
}

/* A lit part keeps its outline's weight at any scale: drawn in the screen's
   own pixels, a 2px ring came out one pixel wide at half size. */
.docs-layout-screen [data-control].lit {
	box-shadow:
		0 0 0 calc(3px / var(--z)) var(--accent),
		0 0 0 calc(8px / var(--z)) color-mix(in srgb, var(--accent) 28%, transparent) !important;
}

/* A drawn floating row measures its own frame, and where it is short of room
   its buttons fold to their glyphs as the real row's do below 1100px: the
   editor's row fits a reading column without scrolling, and shows what that
   window width would show. */
.docs-bar-frame.float {
	container: docs-bar / inline-size;
}

/* Not in a walkthrough, whose frame takes its picture's own width and scales
   it to fit. A size container gives no width of its own, so the frame shrank
   to its padding and painted the grid as a strip beside the bar. */
.docs-walk-frame .docs-bar-frame.float {
	container-type: normal;
}

/* Its buttons fold to their glyphs, as they would in a narrow column, so the
   whole row fits at a scale that can still be read. */
.docs-walk-frame .docs-bar-frame .tb-collapsible .tb-icon-when-narrow {
	display: inline-block;
}

.docs-walk-frame .docs-bar-frame .tb-collapsible .tb-label {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

@container docs-bar (max-width: 1060px) {
	.docs-bar-frame .tb-collapsible .tb-icon-when-narrow {
		display: inline-block;
	}

	.docs-bar-frame .tb-collapsible .tb-label {
		position: absolute;
		width: 1px;
		height: 1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}
}

/* A walkthrough that steps through whole windows: the frame takes the
   column's width, which the window diagram measures and fills, and the ring
   on the control to press keeps its weight at the diagram's scale. */
.docs-walk-frame.walk-window,
.docs-walk-frame.walk-map {
	width: auto;
}

.docs-layout-screen .walk-here {
	box-shadow: 0 0 0 calc(3px / var(--z)) var(--accent);
	animation-name: walk-here-window;
}

@keyframes walk-here-window {
	50% {
		box-shadow:
			0 0 0 calc(3px / var(--z)) var(--accent),
			0 0 0 calc(9px / var(--z)) color-mix(in srgb, var(--accent) 35%, transparent);
	}
}

/* ------------------------------------------------- phones and short screens */

/* iOS enlarges text it judges too small in a wide block, so a note in the
   selection preview came out half as big again as the header over it. */
html {
	-webkit-text-size-adjust: 100%;
	text-size-adjust: 100%;
}

/* The pill keeps to the right of the side strip, which stands down the left
   edge on a touch screen; with a mouse the strip is along the bottom and
   `--strip-w` is nothing. */
.workspace.has-strip .dock.bottom {
	left: calc(var(--edge) + var(--strip-w));
}

@media (max-width: 699px) {
	.workspace.has-strip {
		--strip-w: 0px;
	}
}

/* The strip stands between the top row and the bottom edge, centred in what
   is left rather than in the window, so it never runs under the row. */
.side-strip {
	top: var(--chrome-top);
	bottom: var(--edge);
	height: max-content;
	margin-block: auto;
	transform: none;
}

@media (hover: hover) and (pointer: fine) and (min-width: 700px) {
	.side-strip {
		top: auto;
		margin-block: 0;
		transform: translateX(-50%);
	}
}

/* A short screen -- a tablet sideways with Safari's tabs showing -- has no
   room for the slider: the buttons either side of it still zoom. */
@media (max-height: 640px) {
	.side-strip .strip-zoom {
		display: none;
	}
}

/* A phone's sheets: one column, the window's width, ending above the pill
   and the bar. The map's Inspector beside its tree was 380px of a 393px
   screen, and ran off the edge. */
@media (max-width: 699px) {
	.centre-body > .map-editor,
	.centre-body > .source,
	.centre-body > .alias-doc {
		margin: var(--chrome-top) 6px
			calc(var(--hit) + var(--edge) * 3 + 46px + env(safe-area-inset-bottom, 0px)) 6px;
	}

	.map-editor,
	.map-editor.map-editor-resizable {
		grid-template-columns: minmax(0, 1fr);
		grid-template-rows: minmax(0, 36%) minmax(0, 1fr);
	}

	/* Stacked, the handle moves the line between tree and Inspector up and down. */
	.map-editor.map-editor-resizable {
		grid-template-rows:
			min(var(--map-tree-h, 36%), calc(100% - 180px))
			0 minmax(0, 1fr);
	}

	.map-splitter {
		cursor: row-resize;
	}

	.map-splitter::before {
		inset: -10px 0;
	}

	.map-splitter::after {
		top: -1px;
		bottom: auto;
		left: 0;
		right: 0;
		width: auto;
		height: 2px;
	}

	.map-side {
		border-left: none;
		border-top: 1px solid var(--border);
	}

	/* Headers wrap whole buttons onto a second line, never a word at a time. */
	.source-head,
	.preview-luau .docs-head {
		flex-wrap: wrap;
		row-gap: 6px;
	}

	/* What this host cannot do is a disabled button on a computer, with its
	   reason in the tooltip; a phone has no tooltip and no room. */
	.source-head .tb:disabled {
		display: none;
	}

	.source-head .tb,
	.preview-luau .docs-head .sub,
	.preview-toggle {
		white-space: nowrap;
	}

	.docs-backdrop {
		padding: 10px;
	}

	/* The preview's lines wrap under themselves, past their number. */
	.preview-code {
		white-space: pre-wrap;
		overflow-wrap: anywhere;
	}

	.preview-code .ln {
		padding-left: calc(3.5ch + 12px);
		text-indent: calc(-3.5ch - 12px);
	}

	.preview-code .num {
		text-indent: 0;
	}
}

/* A touch screen's buttons are 38px, so its row runs out of room sooner: on
   an iPad held sideways the open graphs' tabs were cut to a few letters. */
@media (hover: none) and (pointer: coarse) {
	@container workspace (max-width: 1299px) {
		.workspace-chrome .tb-collapsible .tb-icon-when-narrow {
			display: inline-block;
		}

		.workspace-chrome .tb-collapsible .tb-label {
			position: absolute;
			width: 1px;
			height: 1px;
			overflow: hidden;
			clip-path: inset(50%);
			white-space: nowrap;
		}
	}
}

/* A window diagram is drawn at the device's own button size, not the
   reader's: read on an iPad, the computer's row took 38px buttons and ran
   out of its frame, and the node's pin counts wrapped. */
.docs-layout-screen.dev-desktop {
	--hit: 30px;
}

.docs-layout-screen.dev-tablet,
.docs-layout-screen.dev-phone {
	--hit: 38px;
}

/* A tablet's compile buttons show their glyphs, as its row does. */
.docs-layout-screen.dev-tablet .tb-collapsible .tb-icon-when-narrow {
	display: inline-block;
}

.docs-layout-screen.dev-tablet .tb-collapsible .tb-label {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

/* What a diagram stacks stays inside it: its lit row was drawn over the
   docs' contents drawer. And room at its edges for the numbers there. */
.docs-layout-frame,
.docs-walk-window {
	isolation: isolate;
}

.docs-layout-fit {
	padding: 6px 12px;
}

/* ------------------------------------------------------------------- cards */

/* A card: one or more panels as tabs under a header, which is the card's
   handle and carries what the panel put there. The header is a band of its
   own, and the card last used is marked: its header in the accent's tint.
   See `Cards.tsx` and `docs/PANELS.md`. */
:root {
	--card-head: color-mix(in srgb, var(--bg-input) 70%, var(--bg-panel));
	--card-head-focus: color-mix(in srgb, var(--accent) 16%, var(--card-head));
}

/* The line between two cards is a handle of its own, so the cards sit on it
   rather than on a gap. */
.dock:not(.drawer) {
	gap: 0;
}

/* Two or more open cards share the column, top to bottom. */
.dock.left.shared {
	bottom: calc(var(--pill-h, var(--hit)) + var(--edge) * 2);
	max-height: none;
}

.dock.right.shared {
	bottom: var(--edge);
	max-height: none;
}

.card {
	display: flex;
	flex-direction: column;
	min-width: 0;
	min-height: 0;
}

.card.folded {
	flex: none !important;
}

.card-head {
	flex: none;
	display: flex;
	align-items: center;
	gap: 6px;
	min-height: var(--hit);
	padding: 0 3px 0 10px;
	box-sizing: border-box;
	background: var(--card-head);
	border-bottom: 1px solid var(--border);
	cursor: grab;
	user-select: none;
	-webkit-user-select: none;
	touch-action: none;
}

.card-head:active {
	cursor: grabbing;
}

.card.folded > .card-head {
	border-bottom-color: transparent;
}

.card-title {
	flex: none;
	font-size: var(--text-md);
	font-weight: 600;
	color: var(--fg-muted);
	white-space: nowrap;
}

/* What the panel put in its header: a word beside the title, its controls at
   the far end. */
.card-slot {
	flex: 1;
	min-width: 0;
	/* The header's full height, so what runs on from the title -- the Code
	   panel's tabs -- can fill it; the rest is centred in it as before. */
	align-self: stretch;
	display: flex;
	align-items: center;
	gap: 6px;
}

.card-slot[hidden],
.card-body[hidden] {
	display: none !important;
}

.card-sub {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	font-size: var(--text-md);
	color: var(--fg-faint);
}

.card-tools {
	flex: none;
	display: inline-flex;
	align-items: center;
	gap: 4px;
	margin-left: auto;
}

/* The header's controls are a size smaller than a toolbar's, so they sit
   inside its band rather than filling it: 20px with a mouse, 28px to touch. */
.card-tools .tb {
	min-height: 0;
	height: calc(var(--hit) - 10px);
	padding: 0 8px;
	font-size: var(--text-sm);
	line-height: 1;
}

.card-tools .segmented {
	padding: 1px;
}

.card-tools .segmented button {
	min-height: 0;
	height: calc(var(--hit) - 12px);
	padding: 0 8px;
	font-size: var(--text-sm);
	line-height: 1;
}

.card-head > .tb.icon-only {
	flex: none;
	width: calc(var(--hit) - 8px);
	height: calc(var(--hit) - 8px);
	padding: 0;
	display: inline-grid;
	place-items: center;
	background: transparent;
	border-color: transparent;
	color: var(--fg-muted);
}

.card-head > .tb.icon-only:hover {
	background: var(--bg-hover);
	color: var(--fg);
}

/* Tabs: two or more panels sharing a card. The one showing is underlined. */
.card-tabs {
	display: inline-flex;
	align-self: stretch;
	gap: 2px;
	min-width: 0;
	overflow: hidden;
}

.card-tab {
	position: relative;
	display: inline-flex;
	align-items: center;
	padding: 0 9px;
	border: 0;
	border-radius: var(--radius-sm) var(--radius-sm) 0 0;
	background: none;
	color: var(--fg-muted);
	font: inherit;
	font-size: var(--text-md);
	font-weight: 600;
	white-space: nowrap;
	cursor: grab;
	touch-action: none;
}

.card-tab:hover {
	color: var(--fg);
}

.card-tab.on {
	color: var(--fg);
	background: color-mix(in srgb, var(--bg-panel) 98%, transparent);
}

.card-tab.on::after {
	content: "";
	position: absolute;
	left: 8px;
	right: 8px;
	bottom: 0;
	height: 2px;
	border-radius: 1px;
	background: var(--accent);
}

/* The card in use. */
.card.focus {
	border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
}

.card.focus > .card-head {
	background: var(--card-head-focus);
}

.card.focus > .card-head .card-title,
.card.focus > .card-head .card-tab.on {
	color: var(--accent);
}

/* A panel scrolls inside its card. */
.card-body {
	flex: 1 1 auto;
	min-height: 0;
	overflow: auto;
}

/* Script analysis's own bar is its header, in the same band. */
.card.headless .status > .bar {
	background: var(--card-head);
}

.card.headless.focus .status > .bar {
	background: var(--card-head-focus);
}

/* The line between two cards in a dock: drag to share their height. */
.card-split {
	flex: 0 0 8px;
	position: relative;
	pointer-events: auto;
}

.card-split.live {
	cursor: row-resize;
	touch-action: none;
}

.card-split.live::after {
	content: "";
	position: absolute;
	left: 50%;
	top: 50%;
	width: 44px;
	height: 4px;
	border-radius: 2px;
	transform: translate(-50%, -50%);
	background: var(--border-strong);
	opacity: 0;
	transition: opacity 120ms;
}

.card-split.live:hover::after,
.card-split.live.active::after {
	opacity: 1;
	background: var(--accent);
}

/* A card in a window: every edge and corner resizes it. */
.card-size {
	position: absolute;
	z-index: 4;
	touch-action: none;
}

.card-size.n,
.card-size.s {
	left: 10px;
	right: 10px;
	height: 8px;
	cursor: ns-resize;
}

.card-size.e,
.card-size.w {
	top: 10px;
	bottom: 10px;
	width: 8px;
	cursor: ew-resize;
}

.card-size.n { top: -2px; }
.card-size.s { bottom: -2px; }
.card-size.e { right: -2px; }
.card-size.w { left: -2px; }

.card-size.ne,
.card-size.nw,
.card-size.se,
.card-size.sw {
	width: 14px;
	height: 14px;
}

.card-size.ne { top: -2px; right: -2px; cursor: nesw-resize; }
.card-size.sw { bottom: -2px; left: -2px; cursor: nesw-resize; }
.card-size.nw { top: -2px; left: -2px; cursor: nwse-resize; }
.card-size.se { bottom: -2px; right: -2px; cursor: nwse-resize; }

/* Where a dragged card would land: a dashed outline for somewhere new, a solid
   one for the card it would join, a line for where it would go in. */
.drop-preview {
	border-radius: var(--radius-md);
	transition: left 90ms, top 90ms, width 90ms, height 90ms;
}

.drop-preview.dashed {
	border-style: dashed;
	background: color-mix(in srgb, var(--accent) 8%, transparent);
}

.drop-preview.line {
	border: 0;
	border-radius: 2px;
	background: var(--accent);
}

.drop-label {
	position: absolute;
	z-index: 31;
	pointer-events: none;
	padding: 2px 8px;
	border-radius: var(--radius-sm);
	background: var(--accent);
	color: var(--bg-panel);
	font-size: var(--text-sm);
	font-weight: 600;
	white-space: nowrap;
}

/* A drawer's header says which panel is out and carries its controls: a band
   across its top, as a card's is, and held there while the panel scrolls. It
   is not a handle, as a drawer does not move. */
.dock.drawer .card-head {
	position: sticky;
	top: 0;
	z-index: 1;
	cursor: default;
	border-radius: 0;
}

@media (prefers-reduced-motion: reduce) {
	.drop-preview,
	.card-split.live::after {
		transition: none;
	}
}

/* The published docs' folds: the chevron turns as the Docs window's does. */
.docs-static .docs-card-fold svg {
	transition: transform 140ms;
}

.docs-static .docs-nav.folded .docs-card-fold svg,
.docs-static .docs-toc-card.folded .docs-card-fold svg {
	transform: rotate(-90deg);
}

@media (prefers-reduced-motion: reduce) {
	.docs-static .docs-card-fold svg {
		transition: none;
	}
}

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

/* A section's heading, shared by the Inspector, Properties, the Project panel
   and the type picker (PanelParts.tsx; docs/toolbars.ts draws the same): its
   name in the accent colour with a line running on from it, a count, a fold,
   and its buttons at the end of the line. 24px tall, 3px corners. */
.pane-head {
	display: flex;
	align-items: center;
	gap: 6px;
	min-height: 24px;
	margin-top: 4px;
}

.pane-toggle {
	flex: none;
	display: inline-flex;
	align-items: center;
	gap: 6px;
	min-width: 0;
	height: 24px;
	padding: 0 4px;
	border: 0;
	border-radius: var(--radius-xs);
	background: transparent;
	color: var(--accent);
	font: inherit;
	/* 12px beside the editor's 13px, and in proportion inside a docs drawing,
	   which draws its text smaller. */
	font-size: calc(12em / 13);
	font-weight: 650;
	text-align: left;
	white-space: nowrap;
}

button.pane-toggle {
	cursor: pointer;
}

button.pane-toggle:hover {
	background: var(--bg-hover);
}

button.pane-toggle:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: -2px;
}

.pane-fold {
	flex: none;
	transition: transform 120ms;
}

.pane-toggle[aria-expanded="false"] .pane-fold {
	transform: rotate(-90deg);
}

.pane-title {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
}

.pane-count {
	font-weight: 400;
	color: var(--fg-faint);
	font-variant-numeric: tabular-nums;
}

.pane-line {
	flex: 1;
	min-width: 12px;
	height: 1px;
	background: color-mix(in srgb, var(--accent) 35%, var(--border));
}

.pane-tools {
	flex: none;
	display: inline-flex;
	align-items: center;
	gap: 4px;
}

.tb.pane-add {
	display: inline-flex;
	align-items: center;
	gap: 3px;
	min-height: 0;
	height: 22px;
	padding: 0 7px 0 4px;
	font-size: var(--text-sm);
}

@media (prefers-reduced-motion: reduce) {
	.pane-fold,
	.status .status-fold {
		transition: none;
	}
}

/* What a panel is showing: a badge in its colour, its name, and its kind.
   Tinted rather than see-through, as cards over the grid are. */
.ident {
	display: grid;
	grid-template-columns: 30px minmax(0, 1fr) auto;
	gap: 10px;
	align-items: center;
	padding: 8px 8px 8px 9px;
	border: 1px solid var(--border);
	border-radius: var(--radius-md);
	background: color-mix(in srgb, var(--accent) 5%, var(--bg-panel));
}

.ident-badge {
	width: 30px;
	height: 30px;
	border-radius: 8px;
	display: grid;
	place-items: center;
	color: #fff;
}

.ident-text {
	display: flex;
	flex-direction: column;
	min-width: 0;
}

.ident-name {
	font-weight: 650;
	font-size: 1em;
	line-height: 1.25;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.ident-kind {
	font-size: calc(11em / 13);
	color: var(--fg-faint);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.ident-tools {
	display: inline-flex;
	gap: 2px;
}

.ident-tools .tb.icon-only {
	width: 26px;
	height: 26px;
	min-height: 0;
	padding: 0;
	display: inline-grid;
	place-items: center;
	background: transparent;
	border-color: transparent;
	color: var(--fg-muted);
	text-decoration: none;
}

.ident-tools .tb.icon-only:hover {
	background: var(--bg-hover);
	color: var(--fg);
}

/* A type, a class or a place, as a small chip. */
.chip-type {
	display: inline-block;
	max-width: 100%;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	padding: 0 7px;
	border: 1px solid var(--border);
	border-radius: 999px;
	background: var(--bg-input);
	color: var(--fg-muted);
	font: var(--text-xs) / 1.5 "Cascadia Mono", Consolas, monospace;
}

/* ------------------------------------------------------------ text fields */

/* A field reads as somewhere to type, set into its card rather than raised
   from it as a button is: a well a shade darker than the input colour, a soft
   inner shadow along the top, the text cursor, a border that firms on hover,
   and an accent ring when it has the caret.

   Colour, edge and cursor only. No padding or height here, so a field with a
   glyph inside it (the place filter, the searches) keeps the inset that
   clears its glyph. Every condition sits in :where(), so these weigh no more
   than input.tb itself and a component's own field rules still win. */
input.tb:where(:not([type="checkbox"], [type="radio"], [type="range"])),
textarea.tb {
	background: color-mix(in srgb, var(--bg-input) 72%, var(--bg-canvas));
	box-shadow: inset 0 1px 2px color-mix(in srgb, #000 14%, transparent);
	color: var(--fg);
	cursor: text;
	outline: none;
	transition:
		border-color 120ms,
		box-shadow 120ms,
		background 120ms;
}

input.tb::placeholder,
textarea.tb::placeholder {
	color: var(--fg-faint);
	opacity: 1;
}

input.tb:where(:hover:not(:disabled, [readonly])),
textarea.tb:where(:hover:not(:disabled)) {
	border-color: var(--border-strong);
}

input.tb:where(:focus:not([readonly])),
textarea.tb:where(:focus) {
	background: var(--bg-input);
	border-color: var(--accent);
	box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 24%, transparent);
}

input.tb:where([aria-invalid="true"]) {
	border-color: var(--danger);
}

input.tb:where([aria-invalid="true"]:focus) {
	box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 24%, transparent);
}

input.tb:where([readonly]) {
	background: transparent;
	box-shadow: none;
	border-style: dashed;
	cursor: default;
}

input.tb:where(:disabled),
textarea.tb:where(:disabled) {
	opacity: 0.5;
	cursor: not-allowed;
}

@media (prefers-reduced-motion: reduce) {
	input.tb,
	textarea.tb {
		transition: none;
	}
}

/* A type named in the docs, as the type field shows it: its wire colour,
   then its name in the code face. See docs/typeCell.ts. */
.type-chip {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	margin: 2px 0;
	padding: 1px 8px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--bg-input);
	white-space: nowrap;
	vertical-align: middle;
}

.type-chip .type-dot {
	flex: none;
	width: 8px;
	height: 8px;
	border-radius: 50%;
}

.docs-content .type-chip code {
	padding: 0;
	border: 0;
	background: none;
	color: var(--fg);
	font-size: var(--text-sm);
	font-weight: 600;
}

/* ---------------------------------------------------------------- dialogs */

/* Every prompt Dialog.tsx asks, drawn as the panels are: a header band with a
   badge in the colour of the kind of question, the body, and a foot with the
   keys on the left and the answers on the right. The Export panel keeps the
   plain .dialog it is laid out on. */
.dialog.dialog-panel {
	width: min(440px, 100%);
	padding: 0;
	gap: 0;
	overflow: hidden;
}

.dialog.dialog-panel.dialog-wide {
	width: min(500px, 100%);
}

.dialog-panel {
	--tone: var(--accent);
}

.dialog-panel.dialog-danger {
	--tone: var(--danger);
}

.dialog-panel.dialog-warning {
	--tone: var(--warning);
}

.dialog-panel.dialog-info {
	--tone: var(--fg-faint);
}

.dialog-head {
	display: grid;
	grid-template-columns: 30px minmax(0, 1fr) auto;
	gap: 10px;
	align-items: center;
	padding: 12px 12px 12px 14px;
	background: var(--card-head);
	border-bottom: 1px solid var(--border);
}

.dialog-badge {
	width: 30px;
	height: 30px;
	border-radius: 8px;
	display: grid;
	place-items: center;
	color: #fff;
	background: var(--tone);
}

.dialog-panel .dialog-head h3 {
	margin: 0;
	font-size: 1em;
	font-weight: 650;
	line-height: 1.3;
	text-wrap: balance;
}

.dialog-head .dialog-close {
	width: 26px;
	height: 26px;
	min-height: 0;
	padding: 0;
	display: inline-grid;
	place-items: center;
	background: transparent;
	border-color: transparent;
	color: var(--fg-muted);
}

.dialog-head .dialog-close:hover:not(:disabled) {
	background: var(--bg-hover);
	border-color: transparent;
	color: var(--fg);
}

.dialog-body {
	display: grid;
	gap: 12px;
	padding: 14px 14px 16px;
	/* An error can carry a path with no spaces in it; it wraps rather than
	   running out of the box. */
	overflow-wrap: anywhere;
}

/* A field beside its label, in bold, as the Inspector's are. A prompt's one
   field takes the width under its label, with where it goes in a line below. */
.dialog-panel .field {
	display: grid;
	grid-template-columns: minmax(90px, 32%) minmax(0, 1fr);
	gap: 10px;
	align-items: center;
}

.dialog-panel .field.wide {
	grid-template-columns: minmax(0, 1fr);
	gap: 6px;
}

.dialog-panel .field > span {
	font-size: var(--text-sm);
	font-weight: 600;
	color: var(--fg);
}

.dialog-panel .field > small {
	display: flex;
	align-items: center;
	gap: 5px;
	min-width: 0;
	font-size: var(--text-xs);
	color: var(--fg-faint);
	overflow-wrap: anywhere;
}

.dialog-panel .field input.tb,
.dialog-panel .field select.tb {
	min-height: 30px;
	box-sizing: border-box;
}

/* One of several, as rows to pick: the picked one tinted. A check under them
   stays a plain row. */
.dialog-panel .dialog-choices legend {
	padding: 0 0 6px;
	font-size: var(--text-sm);
	font-weight: 600;
	color: var(--fg);
}

.dialog-panel .dialog-option {
	gap: 10px;
	padding: 8px 10px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--bg-panel);
}

.dialog-panel .dialog-option:hover {
	border-color: var(--border-strong);
}

.dialog-panel .dialog-option.on {
	border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
	background: color-mix(in srgb, var(--accent) 16%, transparent);
}

.dialog-panel .dialog-option.dialog-check {
	padding: 2px 0;
	border: 0;
	background: transparent;
}

/* What a question is about, by name: files and paths, in the code face. */
.dialog-panel .dialog-list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: 4px;
	max-height: 160px;
	overflow: auto;
}

.dialog-panel .dialog-list li {
	display: flex;
	align-items: center;
	gap: 7px;
	min-width: 0;
	padding: 4px 8px;
	border-radius: var(--radius-xs);
	background: var(--bg-input);
	font: var(--text-sm) "Cascadia Mono", Consolas, monospace;
	color: var(--fg);
}

.dialog-panel .dialog-list li .icon {
	flex: none;
	color: var(--accent);
}

.dialog-panel .dialog-list li span {
	min-width: 0;
	overflow-wrap: anywhere;
}

/* The foot. It wraps, keys first, so three answers never clip: when they do
   not fit beside the keys, the keys take a line above them. */
.dialog-panel .dialog-actions {
	flex-wrap: wrap;
	align-items: center;
	justify-content: flex-end;
	gap: 8px;
	padding: 10px 12px 10px 14px;
	border-top: 1px solid var(--border);
	background: color-mix(in srgb, var(--accent) 3%, var(--bg-panel));
}

.dialog-keys {
	flex: 1 0 auto;
	font-size: var(--text-xs);
	color: var(--fg-faint);
	white-space: nowrap;
}

.dialog-keys kbd {
	padding: 0 5px;
	border: 1px solid var(--border);
	border-radius: var(--radius-xs);
	background: var(--bg-input);
	font: inherit;
}

.dialog-panel .dialog-actions .tb {
	flex: none;
	min-width: 72px;
	min-height: 30px;
	white-space: nowrap;
}

/* A touch screen has no Enter or Esc to name. */
@media (hover: none) and (pointer: coarse) {
	.dialog-keys {
		visibility: hidden;
	}
}

@media (max-width: 699px) {
	.dialog-panel .field {
		grid-template-columns: minmax(0, 1fr);
		gap: 6px;
	}

	.dialog-keys {
		display: none;
	}
}

/* ---------------------------------------------- specification blocks */
/* The technical specification's own blocks: requirement rows, key words as
   pins, cards side by side, and framed examples. Drawn the same in the Docs
   panel, the static docs and spec.roswaal.app: `scripts/build-spec.mjs` copies
   this section, from here to its end marker, into the specification site's
   stylesheet, so the two cannot drift apart. Only tokens a theme sets. */

/* A key word of RFC 2119, as a pin coloured by how hard it binds. */
.kw {
	display: inline-block;
	padding: 1px 5px;
	border-radius: 4px;
	font: 700 0.72em/1.35 "Cascadia Mono", Consolas, ui-monospace, monospace;
	letter-spacing: 0.04em;
	vertical-align: 0.08em;
	white-space: nowrap;
}

.kw.kw-must {
	background: color-mix(in srgb, var(--danger) 16%, transparent);
	color: color-mix(in srgb, var(--danger) 78%, var(--fg));
}

.kw.kw-should {
	background: color-mix(in srgb, var(--warning) 20%, transparent);
	color: color-mix(in srgb, var(--warning) 62%, var(--fg));
}

.kw.kw-may {
	background: color-mix(in srgb, var(--fg) 10%, transparent);
	color: color-mix(in srgb, var(--fg) 80%, transparent);
}

/* A numbered requirement: its number, what it requires, and whether Roswaal
   meets it. Consecutive ones join into one card. */
.spec-req {
	display: grid;
	grid-template-columns: 76px minmax(0, 1fr) auto;
	gap: 6px 14px;
	align-items: start;
	margin: 0 0 16px;
	padding: 12px 16px;
	border: 1px solid var(--border);
	border-radius: 10px;
	background: var(--bg-input);
}

.spec-req:has(+ .spec-req) {
	margin-bottom: 0;
	border-bottom-left-radius: 0;
	border-bottom-right-radius: 0;
}

.spec-req + .spec-req {
	border-top: 0;
	border-top-left-radius: 0;
	border-top-right-radius: 0;
}

.spec-req:target {
	box-shadow: inset 3px 0 0 var(--accent);
}

.spec-req-id {
	padding-top: 2px;
	font: 600 12px/1.7 "Cascadia Mono", Consolas, ui-monospace, monospace;
	color: var(--fg-muted);
	text-decoration: none;
	white-space: nowrap;
}

.spec-req-id:hover {
	color: var(--accent);
}

.spec-req-text p {
	margin: 0;
}

.spec-req-text .spec-req-gap {
	margin-top: 6px;
	font-size: 0.9em;
	color: var(--fg-muted);
}

.spec-req-status {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding-top: 3px;
	font-size: 12px;
	font-weight: 650;
	white-space: nowrap;
}

.spec-req-status::before {
	content: "";
	width: 8px;
	height: 8px;
	border-radius: 50%;
	background: currentColor;
}

.spec-req-status.st-meets {
	color: var(--ok);
}

.spec-req-status.st-partly {
	color: var(--warning);
}

.spec-req-status.st-not-yet {
	color: var(--danger);
}

@media (max-width: 640px) {
	.spec-req {
		grid-template-columns: minmax(0, 1fr) auto;
	}

	.spec-req-text {
		grid-column: 1 / -1;
		grid-row: 2;
	}
}

/* Statements side by side, each in a card of its own under a small label. */
.docs-compare {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
	gap: 10px;
	margin: 0 0 18px;
}

.docs-compare-card {
	display: flex;
	flex-direction: column;
	gap: 3px;
	padding: 10px 12px;
	border-radius: 8px;
	background: color-mix(in srgb, var(--fg) 5%, var(--bg-input));
	line-height: 1.5;
}

.docs-compare-card.tone-warn {
	background: color-mix(in srgb, var(--danger) 9%, var(--bg-input));
	color: color-mix(in srgb, var(--danger) 45%, var(--fg));
}

.docs-compare-card .label {
	font-size: 10.5px;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	opacity: 0.75;
}

/* An example in a frame that says whose it is. */
.docs-preview.framed {
	padding-top: 0;
	overflow: hidden;
}

.docs-frame-head {
	display: flex;
	justify-content: space-between;
	gap: 12px;
	margin: 0 -16px 16px;
	padding: 7px 14px;
	border-bottom: 1px solid var(--border);
	background: var(--bg-panel);
	font-size: 12px;
	color: var(--fg-muted);
}

.docs-frame-head .aside {
	font-style: italic;
}

/* ------------------------------------------ end specification blocks */
