/* Color palette is duplicated from ops.js COLORS — keep in sync. */

/* Drop the site-wide voronoi background — this app fills the viewport so
   the texture doesn't show anyway and just adds noise behind translucent
   surfaces. */
html { background-image: none; }

article {
	max-width: none;
	min-width: 0;
	margin: 0;
	/* Top padding only; no bottom padding so the horizontal scrollbar sits
	   flush with the viewport bottom. */
	padding: 1rem 0 0;
	position: relative;
	/* Fixed height (not min-height) so the inner flex children bound to
	   the viewport — otherwise tall subpage content would push the article
	   beyond 100vh and the horizontal scrollbar would no longer sit at the
	   bottom of the viewport. */
	height: 100dvh;
	display: flex;
	flex-direction: column;
	box-sizing: border-box;
}
#viz-mount {
	flex: 1;
	min-height: 0;
	display: flex;
	flex-direction: column;
}
.viz-app {
	flex: 1;
	min-height: 0;
	display: flex;
	flex-direction: column;
	width: 100%;
}
article p { text-align: left; hyphens: none; }

/* Narrow viewport: drop article horizontal padding so the narrow-nav can
   reach the screen edges. Hide the fixed help button (it'd overlap with
   the narrow-nav). */
@media (max-width: 700px) {
	article { padding: 0; }
	.viz-row--narrow .narrow-nav { padding-right: 0.3rem; }
}

/* Single-control picker (used inside element subpages for variant selection).
   The unified subpage-settings modal uses .settings-* classes below. */
.config-picker {
	background: #16243a;
	color: #e0e0e0;
	border: 1px solid #4888dd;
	border-radius: 4px;
	padding: 0.3rem 0.6rem;
	font-size: 0.85rem;
	font-family: inherit;
	cursor: pointer;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 0.5rem;
	width: 100%;
	text-align: left;
}
.config-picker:hover { background: #1d3870; color: #fff; }
.config-picker-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.config-picker-caret { color: #6a8ec0; font-size: 1rem; line-height: 1; flex-shrink: 0; }

/* Option picker modal: same overlay style as the help modal. */
.option-modal-backdrop {
	position: fixed;
	inset: 0;
	background: rgba(0, 0, 0, 0.55);
	z-index: 200;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 2rem;
}
.option-modal {
	background: #1a1a1a;
	border: 1px solid #3a3a3a;
	border-radius: 8px;
	padding: 1.4rem 1.6rem;
	max-width: 34rem;
	width: 100%;
	max-height: 85vh;
	overflow-y: auto;
	color: #d7d7d7;
	font-size: 0.9rem;
	line-height: 1.5;
	position: relative;
}
.option-modal h2 {
	margin: 0 0 0.8rem 0;
	font-size: 1rem;
	color: #d7d7d7;
}
.option-modal-close {
	position: absolute;
	top: 0.6rem;
	right: 0.8rem;
	background: none;
	border: none;
	color: #888;
	font-size: 1.4rem;
	cursor: pointer;
	line-height: 1;
}
.option-modal-close:hover { color: #ddd; }
.option-list {
	display: flex;
	flex-direction: column;
	gap: 0.4rem;
}
.option-item {
	display: flex;
	flex-direction: column;
	gap: 0.3rem;
	background: #14181c;
	border: 1px solid #2a3340;
	border-radius: 6px;
	padding: 0.6rem 0.8rem;
	color: #d7d7d7;
	font-family: inherit;
	font-size: 0.85rem;
	text-align: left;
	cursor: pointer;
	transition: border-color 0.1s, background 0.1s;
}
.option-item:hover { border-color: #4888dd; background: #16243a; }
.option-item.current { border-color: #7ab8ff; background: #1d3870; }
.option-head {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	flex-wrap: wrap;
}
.option-label {
	font-weight: 600;
	font-size: 0.92rem;
	color: #e6e6e6;
}
.option-desc {
	color: #999;
	font-size: 0.82rem;
	line-height: 1.45;
}
/* Qualifier tags now appear as plain inline text after the description, in
   a muted italic style. No per-tag colors. */
.option-quals,
.settings-group-quals {
	color: #777;
	font-style: italic;
}

/* Compact chips inside each diagram subpage's header. Each chip is its
   own inline button (rather than wrapped in a single row-button) so chips
   wrap individually inline with the desc text — the first chip can slot
   onto the desc's last line instead of the whole row breaking together.
   Clicking any chip opens the per-subpage settings modal. */
.settings-chip {
	display: inline-block;
	padding: 0 0.22rem;
	border-radius: 999px;
	background: #1a2230;
	border: 1px solid #232c3a;
	color: #c0d0e0;
	font-family: inherit;
	font-size: 0.65rem;
	line-height: 1.5;
	white-space: nowrap;
	vertical-align: baseline;
	cursor: pointer;
	transition: border-color 0.1s, background 0.1s;
}
.settings-chip:hover { border-color: #4888dd; background: #16243a; }
.settings-chip:not(:last-child) { margin-right: 0.08rem; }
.settings-row:hover .settings-chip { border-color: #4888dd; background: #16243a; }

/* Settings sidebar: fixed-positioned, full viewport height. When open, the
   <article> gets a `has-sidebar` class that pads its content right by the
   sidebar's width — pushing the back-link, intro and subpage stack so
   nothing is ever covered. */
.settings-sidebar {
	position: fixed;
	top: 0;
	left: 0;
	width: 26rem;
	height: 100dvh;
	z-index: 100;
	background: #161616;
	border-right: 1px solid #2a2a2a;
	color: #d7d7d7;
	font-size: 0.9rem;
	line-height: 1.5;
	overflow-y: auto;
	padding: 1rem 1.2rem 1.5rem;
	box-sizing: border-box;
}
article.has-sidebar { padding-left: 26rem; }
.settings-sidebar-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 0.5rem;
	margin-bottom: 0.8rem;
}
.settings-sidebar h2 {
	margin: 0;
	font-size: 1rem;
	color: #d7d7d7;
}
.settings-sidebar-close {
	background: none;
	border: none;
	color: #888;
	font-size: 1.5rem;
	line-height: 1;
	cursor: pointer;
	padding: 0;
}
.settings-sidebar-close:hover { color: #ddd; }
.settings-groups {
	display: flex;
	flex-direction: column;
	gap: 0.85rem;
}
.settings-group-label {
	font-size: 0.88rem;
	font-weight: 600;
	color: #d2d8e0;
	margin-bottom: 0.35rem;
}
.settings-group-options {
	display: flex;
	flex-wrap: wrap;
	gap: 0.3rem;
}
.settings-option {
	background: #14181c;
	border: 1px solid #2a3340;
	border-radius: 6px;
	color: #d7d7d7;
	font-family: inherit;
	font-size: 0.82rem;
	padding: 0.25rem 0.6rem;
	cursor: pointer;
	transition: border-color 0.1s, background 0.1s, color 0.1s;
}
.settings-option:hover,
.settings-option:focus { border-color: #4888dd; background: #16243a; outline: none; }
.settings-option.current { border-color: #7ab8ff; background: #1d3870; color: #cfe1ff; }
/* Display-only marker (e.g. the "Custom" entry that appears when overrides
   are active). Rendered as a span, not a button — non-interactive, no hover
   effect. */
.settings-option-static { cursor: default; }
.settings-option-static:hover,
.settings-option-static:focus { border-color: #7ab8ff; background: #1d3870; }
/* Stack: every option's description occupies the same grid cell so the cell
   sizes to the tallest one. Switching between hovered options swaps which
   description is visible without resizing — no layout shift propagates to
   the rest of the modal. */
.settings-group-desc-stack {
	margin-top: 0.35rem;
	display: grid;
	grid-template-columns: 1fr;
}
.settings-group-desc-stack > * {
	grid-column: 1;
	grid-row: 1;
}
.settings-group-desc {
	color: #999;
	font-size: 0.78rem;
	line-height: 1.45;
	margin: 0;
}
.settings-group-desc.hidden {
	visibility: hidden;
	pointer-events: none;
}

@media (max-width: 700px) {
	/* Narrow viewports: sidebar takes the full screen. */
	.settings-sidebar {
		width: 100vw;
		border-right: none;
	}
	article.has-sidebar { padding-left: 0; }
}

/* All subpages live in a single horizontally-scrollable row. Native
   scroll; the user freely picks what's centered. JS observes scroll
   position to keep the URL in sync with the centered subpage and to
   smooth-scroll on click. Scroll bounds at the content edges — first
   and last subpages can't actually scroll to viewport center (they sit
   flush with the row edge), but `scrollIntoView({ inline: "center" })`
   still clamps gracefully. */
/* Positioned wrapper around the row so the hover-arrow can sit at the
   row's visible right edge without scrolling along with the content. */
.viz-row-wrap {
	position: relative;
	flex: 1;
	min-height: 0;
	display: flex;
	flex-direction: column;
}
.viz-row {
	display: flex;
	margin-top: 1rem;
	flex: 1;
	min-height: 0;
	overflow-x: auto;
	overflow-y: hidden;
	scrollbar-width: thin;
}
.viz-row::-webkit-scrollbar { height: 8px; }
.viz-row::-webkit-scrollbar-thumb { background: #444; border-radius: 4px; }
.viz-row::-webkit-scrollbar-thumb:hover { background: #666; }
.viz-row::-webkit-scrollbar-track { background: transparent; }
/* Hover-arrow: appears at the right edge of the visible row when the
   preview-target subpage of the currently-hovered op is mostly off-screen
   to the right. Click (or double-click on the source op) scrolls the
   target subpage into view. Pinned to the wrapper, NOT inside `.viz-row`
   itself, so it stays at the visible right edge regardless of scroll. */
.viz-row-hover-arrow {
	position: absolute;
	top: 50%;
	right: 0.8rem;
	transform: translateY(-50%);
	width: 2.6rem;
	height: 2.6rem;
	border-radius: 50%;
	border: 1px solid rgba(122, 184, 248, 0.55);
	background: rgba(20, 30, 50, 0.88);
	color: rgba(122, 184, 248, 0.95);
	display: flex;
	align-items: center;
	justify-content: center;
	cursor: pointer;
	z-index: 5;
	transition: background 0.15s, border-color 0.15s, color 0.15s;
	animation: viz-row-hover-arrow-pulse 1.2s ease-in-out infinite;
}
.viz-row-hover-arrow svg {
	width: 1.4rem;
	height: 1.4rem;
	display: block;
}
.viz-row-hover-arrow:hover {
	background: rgba(40, 60, 100, 0.95);
	border-color: rgba(122, 184, 248, 0.9);
	color: #ffffff;
	animation: none;
}
@keyframes viz-row-hover-arrow-pulse {
	0%, 100% { transform: translate(0, -50%); }
	50%      { transform: translate(5px, -50%); }
}

/* Narrow-viewport layout: one subpage at a time, with explicit nav
   buttons. Cascade previews and the horizontal stack are bypassed
   entirely (the App component renders NarrowVizRow instead of VizRow). */
.viz-row--narrow {
	display: flex;
	flex-direction: column;
	/* Override .viz-row's `flex-start`: in column flex this controls the
	   cross-axis (horizontal) sizing of children. Without `stretch`, the
	   narrow-nav shrinks to its content width and the title takes no
	   remaining space, so the settings button ends up next to it. */
	align-items: stretch;
	overflow: visible;
	margin-top: 0;
}
.narrow-nav {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	padding: 0.4rem 0.6rem;
	border-bottom: 1px solid #2a2a2a;
}
.narrow-nav-btn {
	background: #16243a;
	color: #cfe1ff;
	border: 1px solid #4888dd;
	border-radius: 50%;
	width: 2rem;
	height: 2rem;
	font-size: 1rem;
	font-family: inherit;
	cursor: pointer;
	flex-shrink: 0;
	display: inline-flex;
	align-items: center;
	justify-content: center;
}
.narrow-nav-btn:hover { background: #1d3870; color: #fff; }
.narrow-nav-btn:disabled {
	opacity: 0.3;
	cursor: default;
	background: transparent;
}
.narrow-title {
	flex: 1;
	text-align: center;
	color: #d7d7d7;
	font-size: 0.95rem;
	font-weight: 500;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.narrow-body {
	padding: 0.8rem 0.6rem 1.5rem;
	overflow-x: auto;
	display: flex;
	justify-content: center;
}
/* In narrow mode subpages take full available width. Element and config
   subpages are capped so they sit centered in the viewport (centered by
   .narrow-body's justify-content). */
.viz-row--narrow .subpage {
	width: 100%;
	max-width: 100%;
	max-height: none;
}
.viz-row--narrow .subpage-scroll {
	overflow: visible;
	min-height: auto;
	flex: 0 0 auto;
}
.viz-row--narrow .subpage--element { max-width: 22rem; }
.viz-row--narrow .subpage-header { max-width: 100%; }
.viz-row--narrow .subpage-desc,
.viz-row--narrow .subpage-formula { max-width: 100%; }
/* The narrow-nav already shows the active subpage's title — hide the
   duplicate inside the subpage header so we don't render it twice. */
.viz-row--narrow .subpage-title { display: none; }
/* Allow the SVG to scale up modestly on narrow viewports so it actually
   fills the screen, but cap at 1.15× its natural viewBox size — beyond
   that text labels (sized in user units) start looking absurdly large. */
.viz-row--narrow .attn-svg {
	width: 100%;
	max-width: calc(var(--natural-w, 100%) * 1.15);
	height: auto;
}
.viz-stack {
	display: flex;
	align-items: stretch;
	gap: 0;
	/* Small breathing room at the scroll bounds so the leftmost and
	   rightmost subpages aren't flush with the viewport edges when
	   scrolled all the way. */
	padding-inline: 1rem;
	/* Cross-subpage arrow overlay positions itself absolutely against
	   the stack so it scrolls horizontally with the content. */
	position: relative;
}
/* Cross-subpage arrows: each child subpage's left border curves back to
   the parent op/value it drilled down from. Rendered behind the subpages
   (z-index: 0; subpages get z-index: 1) so the line passes under text
   and SVG content rather than over it. Pointer events disabled. */
.cross-arrows {
	position: absolute;
	left: 0;
	top: 0;
	pointer-events: none;
	z-index: 0;
	overflow: visible;
}
.cross-arrow {
	fill: none;
	stroke: rgba(255, 255, 255, 0.05);
	stroke-width: 1;
	transition: stroke 0.12s;
}
/* Highlight matches the .subpage--hover-source border tint so the arrow
   reads as the same "this is the source op" cue. */
.cross-arrow--hot {
	stroke: rgba(122, 184, 255, 0.3);
}

/* Every subpage is a flex column: headers on top, body below. Diagram
   subpages' headers are uniform height (1-line title + 5lh desc), so all
   SVGs naturally start at the same y across the cascade with no cross-
   subpage alignment scaffolding needed. Leaves (config, element) lay out
   independently — their height doesn't affect diagram positioning. */
.subpage {
	flex-shrink: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	max-height: 100%;
	padding-top: 0.8rem;
	/* Lift each subpage above the cross-arrows overlay (z=0) so the curves
	   pass behind subpage text and SVG content. */
	position: relative;
	z-index: 1;
	/* Inter-subpage whitespace lives inside each subpage as horizontal
	   padding (viz-stack gap is 0) so neighbouring border edges sit flush. */
	padding-inline: 0.75rem;
	/* Subtle default left border so cross-subpage arrows can be read as
	   "exiting" the upper portion of it. Top border stays transparent
	   until hover-source highlights both. Layout never shifts because the
	   border is always 1px. */
	border-left: 1px solid rgba(255, 255, 255, 0.05);
	border-top:  1px solid transparent;
	border-top-left-radius: 16px;
}
/* Inner scroll container: only the area below the title scrolls, so the
   title stays visible without needing sticky/halo tricks. */
.subpage-scroll {
	width: 100%;
	flex: 1 1 auto;
	min-height: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	overflow-y: auto;
	/* Hide the per-subpage vertical scrollbar — the row's horizontal
	   scrollbar is the only chrome we want. Content still scrolls. */
	scrollbar-width: none;
	padding-bottom: 1.5rem;
}
.subpage-scroll::-webkit-scrollbar { display: none; }

/* Left/top borders + horizontal-gradient background tint when the source
   op is hovered (fades out toward the right edge so it reads as "anchored"
   to the highlighted top-left corner). */
.subpage--hover-source {
	border-left-color: rgba(122, 184, 255, 0.3);
	border-top-color:  rgba(122, 184, 255, 0.3);
	background:        linear-gradient(to right, rgba(122, 184, 255, 0.035) 0%, rgba(122, 184, 255, 0.018) 60%, rgba(122, 184, 255, 0) 100%);
}
/* One-shot flash on click — solid border for the timer's duration, then
   snaps off. No fade, no keyframes; feels like a single blink. */
.subpage--flash {
	border-left-color: rgba(122, 184, 255, 0.6);
	border-top-color:  rgba(122, 184, 255, 0.6);
	background:        linear-gradient(to right, rgba(122, 184, 255, 0.035) 0%, rgba(122, 184, 255, 0.018) 60%, rgba(122, 184, 255, 0) 100%);
}
/* `display: contents` flattens the header so its children (desc, formula)
   become direct flex items of `.subpage-scroll`. */
.subpage-header {
	display: contents;
	text-align: center;
}
.subpage-title {
	width: fit-content;
	font-size: 0.95rem;
	line-height: 1.2;
	color: #d7d7d7;
	font-weight: 600;
	margin: 0 0 0.4rem 0;
	padding: 0;
	flex-shrink: 0;
}
.subpage-title a { color: #7a9fcc; text-decoration: none; }
.subpage-title a:hover { text-decoration: underline; }
/* Function signature shown next to the drill-down title (e.g. "block: x → x'").
   De-emphasised vs. the title text and indented so it reads as a label. */
.subpage-title-fn { margin-left: 0.7em; color: #999; font-weight: 400; font-size: 0.92em; }
.subpage-desc {
	max-width: 22rem;
	width: 100%;
	font-size: 0.78rem;
	color: #888;
	margin: 0;
	line-height: 1.4;
	/* Fixed 6-line height so headers align across the subpage chain.
	   `flex-shrink: 0` keeps the box at its full height when the subpage
	   itself is height-constrained — otherwise the SVG (flex-shrink: 0)
	   wins all the space and the desc collapses to nothing. */
	flex-shrink: 0;
	height: 6lh;
	/* Very subtle border to set the explanation off from the diagram. */
	border: 1px solid #1f1f1f;
	border-radius: 4px;
	padding: 0.4rem 0.55rem;
	/* `scroll` reserves a gutter unconditionally; combined with the
	   transparent track + visible thumb below, the gutter is invisible when
	   the content fits and shows a permanent thumb when it overflows. */
	overflow-y: scroll;
}
.subpage-desc-text { margin: 0; }
/* Inline math in prose should match the surrounding text colour, not the
   global `.katex { color: #d7d7d7 }` from article.css (which makes math
   pop brighter than the prose around it). `\textcolor{...}` spans set
   their own colour inline so they still override this. */
.subpage-desc .katex,
.subpage-element-body p .katex,
.subpage-element-body .op-desc .katex { color: inherit; }
/* The modern `scrollbar-color`/`scrollbar-width` properties are deliberately
   omitted: when set, Chromium 121+ uses them in place of the
   ::-webkit-scrollbar rules, and on macOS that ends up rendering the
   overlay-style auto-hiding scrollbar. -webkit-appearance:none forces the
   classic always-visible scrollbar. */
.subpage-desc::-webkit-scrollbar {
	-webkit-appearance: none;
	width: 8px;
	background: transparent;
}
.subpage-desc::-webkit-scrollbar-thumb {
	background: #666;
	border-radius: 4px;
}
.subpage-desc::-webkit-scrollbar-thumb:hover { background: #888; }
.subpage-desc::-webkit-scrollbar-track { background: transparent; }
.subpage-formula {
	max-width: 22rem;
	width: 100%;
	margin-top: 0.4rem;
	display: flex;
	justify-content: center;
	flex-shrink: 0;
}
.subpage-formula .katex-display {
	margin: 0;
	font-size: 0.85em;
}

/* Main rich-content body for a subpage: multi-paragraph prose with
   interspersed display formulas (`$$...$$`). Used primarily on card
   subpages where there's no diagram. */
.subpage-body {
	max-width: 32rem;
	width: 100%;
	margin-top: 0.6rem;
	color: #c0c0c0;
	font-size: 0.85rem;
	line-height: 1.5;
	text-align: left;
	flex-shrink: 0;
}
.subpage-body p { margin: 0; }
.subpage-body p + p,
.subpage-body p + .subpage-body-display,
.subpage-body-display + p,
.subpage-body-display + .subpage-body-display {
	margin-top: 0.7rem;
}
.subpage-body-display {
	text-align: center;
	overflow-x: auto;
}
.subpage-body-display .katex-display { margin: 0.2rem 0; }
.subpage-body .katex { color: inherit; }
.subpage--math .subpage-body { max-width: 100%; }
/* Inline aside: supplementary, ignorable info nested inside a body. */
.subpage-body-aside {
	padding: 0.45rem 0.65rem;
	border: 1px solid #1f1f1f;
	border-radius: 4px;
	background: rgba(255, 255, 255, 0.012);
	color: #7a7a7a;
	font-size: 0.72rem;
	line-height: 1.5;
}
.subpage-body p + .subpage-body-aside,
.subpage-body-display + .subpage-body-aside,
.subpage-body-aside + p,
.subpage-body-aside + .subpage-body-display,
.subpage-body-aside + .subpage-body-aside {
	margin-top: 0.7rem;
}

/* Supplementary, non-essential prose for a subpage. Renders below the
   diagram/math view in a muted, smaller-font box so users can ignore it. */
.subpage-notes {
	max-width: 28rem;
	width: 100%;
	margin-top: 1.2rem;
	padding: 0.55rem 0.75rem;
	border: 1px solid #1f1f1f;
	border-radius: 4px;
	background: rgba(255, 255, 255, 0.012);
	color: #7a7a7a;
	font-size: 0.72rem;
	line-height: 1.5;
	flex-shrink: 0;
}
.subpage-notes p { margin: 0; }
.subpage-notes p + p { margin-top: 0.45rem; }
.subpage-notes .katex { color: inherit; }
.subpage--math .subpage-notes { max-width: 100%; }

/* All subpages render uniformly — there's no concept of a "main" subpage
   visually; the user's scroll position picks what they're looking at. */

/* Element subpages stretch to fit their content (e.g. wide aligned-block
   formulas like top-p truncation), but never below 22rem so short content
   keeps a uniform column look against neighbouring subpages. */
.subpage--element { min-width: 22rem; width: fit-content; max-width: 32rem; }
.subpage-element-body {
	width: 100%;
	font-size: 0.85rem;
	color: #b8b8b8;
	margin-top: 0.5rem;
}
.subpage-element-body .shape-line {
	color: #888;
	font-size: 0.8rem;
	margin: 0 0 0.4rem 0;
	font-family: sans-serif;
	text-align: center;
}
/* Tensor element subpages are description-only — match the small/dim
   styling used by `.subpage-desc` on diagram pages so the prose reads
   the same in both contexts. Op desc and the dims/shape lines override
   below. */
.subpage-element-body p {
	margin: 0 0 0.5rem 0;
	text-align: center;
	font-size: 0.78rem;
	color: #888;
	line-height: 1.4;
}
.subpage-element-body .op-desc { font-size: 0.82rem; color: #999; line-height: 1.45; }
.subpage-element-body .op-variant { display: none; }
.subpage-element-body .op-variant.op-variant-active { display: block; }
.subpage-element-body .op-variant-bar {
	display: flex;
	justify-content: center;
	margin: 0.4rem 0;
}
.subpage-element-body .op-variant-bar .config-picker { width: auto; min-width: 8rem; }
.subpage-element-body p:last-child { margin-bottom: 0; }
.subpage-element-body .katex-display {
	margin: 0.5rem 0;
}
.idx-active, .idx-active * { color: #ff6b6b; }
.idx-free,   .idx-free   * { color: #5cd884; }
.subpage-element-body ul.values {
	list-style: none;
	padding: 0;
	margin: 0.5rem 0 0 0;
	font-size: 0.78rem;
	text-align: center;
}
.subpage-element-body ul.values li { padding: 0.1rem 0; color: #b8b8b8; }
.subpage-element-body p.dims,
.subpage > .subpage-scroll > p.dims {
	font-size: 0.75rem;
	color: #8a8a8a;
	margin: 0.4rem 0 0 0;
	text-align: center;
	max-width: 32rem;
	width: 100%;
	flex-shrink: 0;
}
/* On card subpages the dims line sits below a tall body of prose+formulas
   rather than below the small element-body block, so it needs more breathing
   room above to read as a separate metadata footer. */
.subpage > .subpage-scroll > p.dims { margin-top: 1.4rem; }
/* Dotted underline cues that the entry has a hover tooltip. */
p.dims .has-tooltip {
	cursor: help;
}
p.dims .has-tooltip:hover { color: #c0c0c0; }

.attn-svg {
	width: auto;
	height: auto;
	display: block;
	flex-shrink: 0;
}

.node { cursor: pointer; }

.tensor rect {
	fill: #1c1c1c;
	stroke: #4a4a4a;
	stroke-opacity: 0.5;
	stroke-width: 1.4;
	transition: stroke 0.1s, fill 0.1s, stroke-width 0.1s, stroke-opacity 0.1s;
}
.tensor:hover rect, .tensor.current rect, .tensor.related rect { stroke-opacity: 1; }
.tensor:hover rect { stroke: #8ab4f8; }
.tensor:hover .tensor-name,
.tensor:hover .label,
.tensor:hover .shape { fill: #8ab4f8; }
.tensor:hover .shape-katex,
.tensor:hover .shape-katex .katex { color: #8ab4f8; }
.tensor.current rect { stroke: #7ab8ff; stroke-width: 3; fill: #1d3870; }
.tensor.current .tensor-name,
.tensor.current .label,
.tensor.current .shape { fill: #7ab8ff; }
.tensor.current .shape-katex,
.tensor.current .shape-katex .katex { color: #7ab8ff; }
/* `.related` (input/output of the focused op) keeps its text/shape at their
   default greys — only the border tint signals the relationship, so related
   nodes stay quiet next to the saturated `.current` highlight. */

.tensor .label {
	fill: #777;
	font-family: "KaTeX_Math", "Times New Roman", serif;
	font-style: italic;
	font-size: 15px;
	pointer-events: none;
	paint-order: stroke;
	stroke: #1a1a1a;
	stroke-width: 4px;
	stroke-linejoin: round;
}
.tensor .label .sup {
	font-size: 0.65em;
	baseline-shift: super;
	font-style: normal;
}
.tensor .label .sub {
	font-size: 0.65em;
	baseline-shift: sub;
	font-style: normal;
}
.shape-fo { overflow: visible; pointer-events: none; }
.shape-katex {
	color: #777;
	font-size: 10px;
	text-align: center;
	line-height: 16px;
	/* Halo against the diagram lines / box borders. Mirrors the SVG
	   paint-order:stroke trick used by tensor labels. */
	text-shadow:
		-1px -1px 0 #1a1a1a,  1px -1px 0 #1a1a1a,
		-1px  1px 0 #1a1a1a,  1px  1px 0 #1a1a1a,
		 0   -1px 0 #1a1a1a,  0    1px 0 #1a1a1a,
		-1px  0   0 #1a1a1a,  1px  0   0 #1a1a1a;
}
.shape-katex .katex { font-size: 1em; color: #777; }
/* Mute KaTeX's inline-styled hyperparameter colour for shape labels —
   the full #c08fd8 is too loud at this size against the dark bg. */
.shape-katex .katex [style*="color:#c08fd8"],
.shape-katex .katex [style*="color: #c08fd8"] {
	color: #9a8aa8 !important;
}
.tensor:hover .shape-katex .katex [style*="color:#c08fd8"],
.tensor:hover .shape-katex .katex [style*="color: #c08fd8"] {
	color: #8ab4f8 !important;
}
.tensor.current .shape-katex .katex [style*="color:#c08fd8"],
.tensor.current .shape-katex .katex [style*="color: #c08fd8"] {
	color: #7ab8ff !important;
}
.tensor .tensor-name {
	fill: #555;
	font-family: sans-serif;
	font-size: 9.5px;
	letter-spacing: 0.02em;
	pointer-events: none;
	paint-order: stroke;
	stroke: #1a1a1a;
	stroke-width: 4px;
	stroke-linejoin: round;
}

.wire {
	fill: none;
	stroke: #5a5a5a;
	stroke-width: 1.4;
	transition: stroke 0.1s, stroke-width 0.1s;
	pointer-events: none;
}
.wire.related { stroke: #4888dd; }
.tensor.related rect { stroke: #4888dd; }

.op .op-bg {
	fill: #14181c;
	stroke: #5a7088;
	stroke-width: 1.2;
	transition: stroke 0.1s, fill 0.1s, stroke-width 0.1s;
}
.op.drillable .op-bg { stroke-dasharray: 4 2; }
.op.decorative { cursor: default; }
.op.decorative .op-bg { fill: none; stroke: none; }
.op.decorative .op-sketch { font-size: 14px; }
.op.decorative .op-sketch .vdots {
	font-family: sans-serif;
	font-style: normal;
}
.op .op-name {
	fill: #5a7088;
	font-family: sans-serif;
	font-size: 9.5px;
	letter-spacing: 0.02em;
	pointer-events: none;
	paint-order: stroke;
	stroke: #1a1a1a;
	stroke-width: 4px;
	stroke-linejoin: round;
}
.op .op-sketch {
	fill: #aabbcc;
	font-family: "KaTeX_Math", "Times New Roman", serif;
	font-style: italic;
	font-size: 12px;
	pointer-events: none;
}
.op .op-sketch .sub { font-size: 0.7em; baseline-shift: sub; }
.op .op-sketch .sup { font-size: 0.7em; baseline-shift: super; }
.op .op-sketch .dots { font-size: 0.75em; }
/* KaTeX_Math ships only an italic variant — force KaTeX_Main (which has
   upright glyphs) so `font-style: normal` actually renders upright. */
.op .op-sketch .kw { font-style: normal; font-family: "KaTeX_Main", "Times New Roman", serif; }
.op .op-sketch .trainable { fill: #d8a878; }
.op .op-sketch .hyperparam { fill: #c08fd8; }
.op .op-sketch .idx { fill: #ff6b6b; }
.op .op-sketch .idx-free { fill: #5cd884; }

.op:hover .op-bg { stroke: #8ab4f8; }
.op.current .op-bg { stroke: #7ab8ff; stroke-width: 2.6; fill: #1d3870; }
.op.current .op-name { fill: #7ab8ff; }
.op.drill-pinned .op-bg { stroke: #4888dd; fill: #16243a; }

#formula-store { display: none; }

.help-button,
.view-toggle-button {
	position: fixed;
	top: 1rem;
	width: 2.2rem;
	height: 2.2rem;
	border-radius: 50%;
	background: #16243a;
	color: #cfe1ff;
	border: 1px solid #4888dd;
	font-size: 1.1rem;
	font-family: inherit;
	cursor: pointer;
	z-index: 100;
	display: inline-flex;
	align-items: center;
	justify-content: center;
}
.help-button { right: 1rem; }
.view-toggle-button { right: 3.6rem; }
.help-button:hover,
.view-toggle-button:hover { background: #1d3870; color: #fff; }
/* Narrow viewport: the floating buttons would overlap the narrow-nav,
   which carries its own copies of these controls. Declared here (after
   the base rule) so source order beats the earlier @media block. */
@media (max-width: 700px) {
	.help-button,
	.view-toggle-button { display: none; }
}

.help-modal-backdrop {
	position: fixed;
	inset: 0;
	background: rgba(0, 0, 0, 0.55);
	z-index: 200;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 2rem;
}
.help-modal {
	background: #1a1a1a;
	border: 1px solid #3a3a3a;
	border-radius: 8px;
	padding: 1.5rem 1.8rem;
	max-width: 32rem;
	width: 100%;
	color: #d7d7d7;
	font-size: 0.9rem;
	line-height: 1.5;
	position: relative;
}
.help-modal h2 {
	margin: 0 0 0.4rem 0;
	font-size: 1rem;
	color: #d7d7d7;
}
/* One-paragraph tagline below the modal title. Smaller, dimmer, and italic so
   it reads as orientation, not content. */
.help-tagline {
	margin: 0 0 1rem 0;
	color: #a0a0a0;
	font-size: 0.82rem;
	font-style: italic;
	line-height: 1.2;
}
.help-modal-close {
	position: absolute;
	top: 0.6rem;
	right: 0.8rem;
	background: none;
	border: none;
	color: #888;
	font-size: 1.4rem;
	cursor: pointer;
	line-height: 1;
}
.help-modal-close:hover { color: #ddd; }
.help-legend {
	display: grid;
	grid-template-columns: max-content 1fr;
	gap: 0.4rem 1rem;
	margin: 0;
}
.help-legend dt {
	font-family: "KaTeX_Math", "Times New Roman", serif;
	font-style: italic;
	font-size: 1rem;
	white-space: nowrap;
}
.help-legend dt sup { font-style: normal; font-size: 0.7em; }
.help-legend dd {
	margin: 0;
	color: #b8b8b8;
	font-size: 0.85rem;
}
.help-note {
	margin: 0.8rem 0 0 0;
	color: #8a8a8a;
	font-size: 0.8rem;
}
.help-modal h3 {
	margin: 1rem 0 0.4rem 0;
	font-size: 0.9rem;
	color: #c0c0c0;
	font-weight: 600;
}
.help-row {
	display: flex;
	align-items: center;
	gap: 1rem;
	margin: 0.6rem 0;
}
.help-row > div { flex: 1; min-width: 0; }
.help-row .help-label {
	color: #d7d7d7;
	font-weight: 600;
	font-size: 0.85rem;
	margin-bottom: 0.15rem;
}
.help-row p {
	margin: 0;
	color: #a8a8a8;
	font-size: 0.82rem;
	line-height: 1.45;
}
.help-block {
	flex: 0 0 auto;
	overflow: visible;
}
/* Hovering the modal's example blocks applies the same full-highlight
   styling as a selected node in the diagrams (.current state). */
.help-block .tensor:hover rect {
	stroke: #7ab8ff;
	stroke-width: 3;
	fill: #1d3870;
}
.help-block .tensor:hover .tensor-name,
.help-block .tensor:hover .shape { fill: #7ab8ff; }
.help-block .op:hover .op-bg {
	stroke: #7ab8ff;
	stroke-width: 2.6;
	fill: #1d3870;
}
.help-block .op:hover .op-name { fill: #7ab8ff; }
.help-narrow-note {
	margin: -0.4rem 0 0.8rem 0;
	color: #d8a878;
	font-size: 0.78rem;
	font-style: italic;
}
/* Plain-flex demo slot for non-SVG help-row examples (e.g. the Settings row's
   sample chips). Same outer dimensions as `.help-block` so the row's left gutter
   stays aligned with the SVG-based rows above. */
/* Stack of example chips for the Settings help row. Same outer dimensions as
   the SVG demos so the text column on the right starts at a consistent
   x-position across rows. The `.help-row >` prefix wins specificity against
   `.help-row > div { flex: 1 }`, which would otherwise stretch this div. */
.help-row > .help-chips-demo {
	flex: 0 0 auto;
	width: 120px;
	height: 60px;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 0.3rem;
}
.help-footer {
	margin-top: 1.2rem;
	padding-top: 0.7rem;
	border-top: 1px solid #2a2a2a;
	color: #6a6a6a;
	font-size: 0.72rem;
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: 1rem;
}
.help-footer .help-back {
	color: #6a8ec0;
	font-size: 0.85rem;
	text-decoration: none;
}
.help-footer .help-back:hover { color: #8ab4f8; text-decoration: underline; }
@media (max-width: 700px) {
	.help-modal { padding: 1.1rem 1.2rem; font-size: 0.85rem; }
	.help-row { gap: 0.7rem; }
	.help-block { width: 90px; height: 50px; }
}

/* ==========================================================================
   Math view — code-style listing replacing the SVG data-flow graph.
   Each line is one inline row: `[label] symbol [shape] = rhs`, with the
   producing tensor's `desc` rendered as italic prose below. Highlighting
   is targeted at the LHS symbol (the value being defined) rather than the
   whole line, so hovering a downstream op underlines the SOURCE of each
   input rather than flooding the page with rectangles.
   ========================================================================== */
.subpage--math { width: 36rem; }
.subpage--math .subpage-desc { max-width: 100%; }
.subpage--math .subpage-formula { max-width: 100%; }
.viz-row--narrow .subpage--math { max-width: 100%; }

.math-view {
	width: 100%;
	font-size: 0.85rem;
	color: #c8c8c8;
	box-sizing: border-box;
	padding: 0 0.4rem;
	display: flex;
	flex-direction: column;
	gap: 0.65rem;
	/* Anchor for the arrow overlay and a fresh stacking context so its
	   z-index stays scoped to this subpage. */
	position: relative;
	isolation: isolate;
}
/* Behind-text arrow overlay. Sits at z-index 0 while .math-line is forced
   to z-index 1, so glyphs naturally cover any path crossing them. Pointer
   events disabled so hover passes through to the math lines underneath. */
.math-arrows {
	position: absolute;
	left: 0;
	top: 0;
	pointer-events: none;
	z-index: 0;
	overflow: visible;
}
.math-arrow {
	fill: none;
	stroke: rgba(122, 184, 248, 0.07);
	stroke-width: 1;
	transition: stroke 0.12s, stroke-width 0.12s;
}
/* Incoming arrows (hovered line is the consumer): blue, matches the existing
   `.related .math-lhs` colour palette so producer LHS + arrows read as one
   highlight set. */
.math-arrow--in {
	stroke: rgba(122, 184, 248, 0.85);
	stroke-width: 1.6;
}
/* Outgoing arrows (hovered line is the producer): yellow, distinguishes
   "where this value flows TO" from "where this value comes FROM". */
.math-arrow--out {
	stroke: rgba(232, 196, 104, 0.85);
	stroke-width: 1.6;
}
/* Halo around every math glyph in this view, painted in the page background
   colour. `filter: drop-shadow` (rather than text-shadow) renders the
   shadow from the line's combined alpha buffer in one pass, so adjacent
   glyphs don't paint their halos over each other's fills — which text-shadow
   does once the offset gets large enough to reach the next glyph. The shadow
   sits under the line content but above the arrow overlay (z=0), so arrows
   crossing text get masked. Multiple drop-shadows are stacked to fake a
   wider halo without leaning on a large blur (which softens the glyphs). */
.math-view .math-line {
	filter:
		drop-shadow( 2px  0   0 #1a1a1a)
		drop-shadow(-2px  0   0 #1a1a1a)
		drop-shadow( 0    2px 0 #1a1a1a)
		drop-shadow( 0   -2px 0 #1a1a1a);
}
.math-section {
	display: flex;
	flex-direction: column;
	gap: 0.55rem;
}
.math-line {
	display: block;
	padding: 0.2rem 0.5rem;
	border-radius: 4px;
	cursor: default;
	transition: background 0.1s;
	/* Lifted above the arrow overlay so glyphs occlude path segments crossing
	   the line. The line itself stays transparent — only text glyphs cover. */
	position: relative;
	z-index: 1;
}
.math-line.fn-op .math-fn-call { cursor: pointer; }
.math-line:hover { background: rgba(72, 136, 221, 0.06); }
.math-line.math-decorative {
	cursor: default;
	padding: 0.1rem 0.5rem;
	text-align: center;
}
.math-line.math-decorative:hover { background: transparent; }
.math-decorative-text {
	color: #6b7a8a;
	font-size: 0.78rem;
}

.math-formula-row {
	display: flex;
	align-items: baseline;
	gap: 0.45rem;
}

/* Pre-equals: a fixed-width slot whose RIGHT edge defines where `=` lands.
   The vlabel sticks to the LEFT (`margin-right: auto`) while the symbol
   and shape pack against the right next to `=`. Empty space appears in
   the MIDDLE for short rows. Only the vlabel shrinks (with ellipsis) and
   only when ITS OWN row would overflow; other rows' labels are
   unaffected. */
.math-pre-equals {
	flex: 0 0 9.9rem;
	display: flex;
	align-items: baseline;
	gap: 0.45rem;
	min-width: 0;
	overflow: hidden;
}
.math-lhs-cell, .math-shape-cell { flex: 0 0 auto; }
.math-eq { color: #8a99a8; flex: 0 0 auto; }
/* Post + cell--full hug their content so the description (last flex item)
   takes the remaining row width. */
.math-post { flex: 0 1 auto; min-width: 0; }
/* Side condition appended to a loop op's RHS (e.g. "l ∈ {1, 5, 9, …, L}").
   Subdued like the `=` and slightly smaller so it reads as auxiliary info,
   not part of the formula. */
.math-range { flex: 0 1 auto; min-width: 0; color: #8a99a8; font-size: 0.88em; }

.math-cell--full {
	flex: 0 1 auto;
	min-width: 0;
	overflow-x: auto;
	/* Persistent thin scrollbar so wide aligned blocks are obviously
	   scrollable (default overlay scrollbars are invisible at rest). */
	scrollbar-width: thin;
	scrollbar-color: #444 transparent;
}
.math-cell--full::-webkit-scrollbar { height: 6px; }
.math-cell--full::-webkit-scrollbar-thumb { background: #444; border-radius: 3px; }
.math-cell--full::-webkit-scrollbar-thumb:hover { background: #666; }
.math-cell--full::-webkit-scrollbar-track { background: transparent; }

/* Wrapper holding `=`, post, and desc as a single wrappable group. Sits
   to the right of the fixed-width pre-equals slot, takes the remaining
   row width, and wraps internally — when the row gets narrow, desc
   drops to its own line that starts at this container's left edge,
   which lines up with the `=` column. */
.math-after-equals {
	flex: 1 1 0;
	min-width: 0;
	display: flex;
	align-items: baseline;
	flex-wrap: wrap;
	gap: 0.45rem;
}
.math-cell--full .katex-display {
	text-align: left;
	margin: 0;
	font-size: 0.95em;
}

/* Short prose label preceding the symbol (e.g. "probabilities" for `p`).
   In the per-row flex layout the vlabel is the only shrinkable item, so
   when a row's content would overflow the pre-equals slot the label is
   what truncates (via overflow + ellipsis) — other rows are unaffected. */
.math-vlabel {
	flex: 0 1 auto;
	margin-right: auto;
	min-width: 0;
	text-align: left;
	color: #8a99a8;
	opacity: 0.5;
	font-size: 0.62rem;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.math-line:hover .math-vlabel { opacity: 0.9; color: #a8b8c8; }

/* Highlight intensity hierarchy:
   - The whole fn call (.math-fn-call) is the strongest target on fn ops —
     when current, name + parens + args all sit in a saturated bg.
   - LHS symbols (.math-lhs, the produced value) are the softer target — they
     mark "this is what you're looking at" or "this fed your hovered op".
   - The line itself only gets a faint hover tint to anchor the cursor. */
.math-lhs, .math-fn-call {
	display: inline-block;
	padding: 0 0.15rem;
	border-radius: 3px;
	transition: background 0.12s, color 0.12s, box-shadow 0.12s, border-color 0.12s;
}
/* LHS highlight on tensor lines: a hovered/selected tensor IS its lhs symbol,
   so highlighting it makes sense. Op lines deliberately do NOT highlight their
   own lhs when current — selecting an op should focus on the action it
   performs (.math-fn-call) and the values it consumes (.related on inputs),
   not the value it produces. */
.math-line.math-tensor.current .math-lhs {
	background: rgba(72, 136, 221, 0.18);
	box-shadow: inset 0 0 0 1px rgba(122, 184, 248, 0.7);
}
/* Producer ops + sibling input tensors of the current op show a softer
   highlight on their lhs (the value being consumed). */
.math-line.related .math-lhs {
	background: rgba(72, 136, 221, 0.10);
	box-shadow: inset 0 0 0 1px rgba(72, 136, 221, 0.50);
}

/* Strong fn-call highlight — covers name + parens + args. Related lines
   intentionally don't highlight their own fn call; the producer's LHS is the
   relevant "this is your input" cue, and adding fn-call colour would just
   create noise. */
.math-line.fn-op.current .math-fn-call {
	background: #1d3870;
	color: #fff;
	box-shadow: inset 0 0 0 1px #7ab8ff;
}
.math-line.fn-op.drill-pinned .math-fn-call {
	box-shadow: inset 0 0 0 1px #4888dd;
}

/* Inline shape annotation, e.g. "[T × V]". Smaller and dimmer than the
   formula so it reads as metadata, not part of the equation. */
.math-shape-inline {
	color: #6f7d8b;
	opacity: 0.6;
	font-size: 0.72rem;
	white-space: nowrap;
	margin: 0 0.1rem;
}
.math-shape-inline .katex { font-size: 0.95em; color: #6f7d8b; }
/* Mute KaTeX inline-styled hyperparameter colour same as shape-fo in graph
   view — full purple is too loud at this small size. */
.math-shape-inline .katex [style*="color:#c08fd8"],
.math-shape-inline .katex [style*="color: #c08fd8"] {
	color: #9a82b0 !important;
}

.math-rhs { display: inline-block; }

/* Click affordance on fn ops: dashed underline on the function name. Both
   with-graph drill-downs (open the sub-diagram) and graph-less fn ops (open
   the element subpage with the actual definition) are clickable. Hidden
   when the call is `current` — the saturated bg already signals "this is
   the focus" and a dashed line on top would be visual noise. */
.math-line.fn-op .math-fn-name {
	cursor: pointer;
	border-bottom: 1px dashed #4a5a70;
	padding-bottom: 1px;
}
.math-line.fn-op:hover .math-fn-name {
	border-bottom-color: #8ab4f8;
}
.math-line.fn-op.current .math-fn-name {
	border-bottom-color: transparent;
}

/* Prose annotation below the formula — the `desc` field of the produced
   tensor or the op. Renders below the formula row as a regular block,
   indented ~4 spaces so it reads as a continuation of the line above. */
/* Desc is the last flex item inside `.math-after-equals`. It sits to the
   right of the equation when the row is wide enough; if not, flex-wrap
   drops it to a new line whose left edge aligns with the `=` column. Its
   inner per-line `<div>`s stack vertically inside it. The min-width acts
   as the wrap threshold: as long as ≥10rem (≈ a quarter of the 36rem
   subpage) is available, desc stays on the line; below that, it wraps. */
.math-desc {
	flex: 1 1 10rem;
	min-width: 10rem;
	margin-left: 0.6rem;
	/* Override parent's baseline alignment so multi-line descs sit centered
	   against the (often taller) equation row instead of hanging from the
	   equation's baseline and growing downward. */
	align-self: center;
	color: #7a8898;
	opacity: 0.55;
	font-size: 0.68rem;
	line-height: 1.4;
}
.math-line:hover .math-desc { color: #95a3b3; opacity: 0.8; }
.math-desc .katex { color: inherit; }


@media (max-width: 700px) {
	.subpage--math { width: 100%; }
	.math-view { padding: 0 0.2rem; font-size: 0.8rem; }
	.math-cell--full .katex-display { font-size: 0.85em; }
}
