/**
 * XOR theme — component styles.
 *
 * Design tokens live in theme.json; this file covers what theme.json cannot
 * express: the CRT treatment, the navigation dropdowns, the NerdSEQ screen
 * overlay, and the card/section furniture used by the patterns.
 */

:root {
	/* Page scanline strength. Drop toward 0 if body text gets hard to read. */
	--xor-scan: .22;

	/* The rolling hum bar — how long it takes to sweep the full height once.
	   This is the beat frequency between an imaginary camera shutter and the
	   tube's refresh, so it is slow: 5–9s looks filmed, under ~2s looks like a
	   fault. Strength is a separate knob so the speed can be tuned without
	   making it heavier. */
	--xor-roll: 6.5s;
	--xor-roll-strength: 1;

	/* Where the screen sits inside the NerdSEQ photograph, as percentages of
	   the image. All three colourway photos (2026-11 shoot) are perspective-
	   corrected and cropped to the panel edge at the same 3600x2850 frame, so
	   one set of numbers fits all three to within ~0.2%: the 3.5" LCD's 3:2
	   active area (73.4 x 49.0 mm) centred in the measured cut-out. Override
	   per-image with an inline style on .xor-stage if a photo is reshot. */
	--xor-scr-left: 18.30%;
	--xor-scr-top: 12.96%;
	--xor-scr-width: 45.37%;
	--xor-scr-height: 38.21%;
	/* The START, STOP and cursor keys on the panel (nerdseq-screen.js), same
	   percentages-of-the-photo scheme: one size, a top-left corner each. */
	--xor-key-width: 9.80%;
	--xor-key-height: 12.40%;
	--xor-key-start-left: 10.40%;  --xor-key-start-top: 67.30%;
	--xor-key-stop-left: 20.40%;   --xor-key-stop-top: 67.30%;
	--xor-key-up-left: 52.27%;     --xor-key-up-top: 67.30%;
	--xor-key-left-left: 42.41%;   --xor-key-left-top: 79.98%;
	--xor-key-down-left: 52.27%;   --xor-key-down-top: 79.98%;
	--xor-key-right-left: 62.13%;  --xor-key-right-top: 79.98%;

	/* The panel's OUT LED, same measuring approach as the screen above —
	   percentages of the photo, off its own bounding box. Flashed by
	   nerdseq-screen.js on every step of the pattern. */
	--xor-led-left: 75.10%;
	--xor-led-top: 85.80%;
	--xor-led-width: 3.00%;
	--xor-led-height: 3.90%;
}

/* ---------------------------------------------------------------- CRT ---- */

body::before {
	content: "";
	position: fixed;
	pointer-events: none;
	z-index: 9;
	inset: 0;
	background: repeating-linear-gradient(180deg, rgba(0, 0, 0, 0) 0 2px, rgba(0, 0, 0, var(--xor-scan)) 2px 4px);
	mix-blend-mode: multiply;
}

/* ------------------------------------------------- CRT: rolling hum bar ---- */

/* The signature of a screen filmed off a camera. The shutter and the tube's
   refresh run at slightly different rates, so the part of the frame the camera
   catches mid-refresh reads brighter and the part it misses reads darker — and
   because the two rates only *nearly* match, that band walks slowly down the
   picture instead of holding still.
 *
 * It hangs off `html` rather than `body` because body's two pseudo-elements are
 * already spoken for (scanlines and vignette), and a third fixed layer needs no
 * markup if it comes from the root element.
 *
 * **Front page only.** A band sweeping across a paragraph someone is trying to
 * read is genuinely tiring, and every other page on this site exists to be read
 * — legal, release notes, product specs. The homepage is the one page whose job
 * is to make an impression in a few seconds, so it can carry the effect.
 *
 * `:has()` is what makes that possible: the `.home` class WordPress sets lives
 * on `body`, and this pseudo-element hangs off its parent. Where `:has()` is
 * unsupported the rule simply never matches and no page gets the bar — the
 * right way round for a decorative effect to fail.
 */
html:has(body.home)::before {
	content: "";
	position: fixed;
	left: 0;
	right: 0;
	pointer-events: none;

	/* Above the photography (10), unlike the scanlines. The two artefacts are
	   not the same kind of thing: fine line texture over a product shot just
	   makes the hardware look dirty, but the hum bar is a *camera exposure*
	   effect, so a photo it visibly refuses to cross announces itself as an
	   overlay. It stays below the header (40) so the bar never sweeps across
	   the navigation while someone is reading a menu. */
	z-index: 11;

	/* Two viewport heights tall, hung one above the viewport, so translating a
	   full period never uncovers an edge. */
	top: -100vh;
	height: 200vh;

	/* One band per viewport height, tiled. Because the pattern repeats on
	   exactly the distance the animation travels, the wrap is invisible. The
	   stops are asymmetric — a narrow bright leading edge and a wider, softer
	   dark trailing region — because that is how the real artefact looks; a
	   symmetric band reads as a decorative gradient sliding past. */
	background-image: linear-gradient(180deg,
		rgba(255, 255, 255, 0)                                    0%,
		rgba(255, 255, 255, calc(.030 * var(--xor-roll-strength))) 9%,
		rgba(255, 255, 255, calc(.060 * var(--xor-roll-strength))) 15%,
		rgba(255, 255, 255, calc(.026 * var(--xor-roll-strength))) 21%,
		rgba(255, 255, 255, 0)                                   28%,
		rgba(0, 0, 0, calc(.050 * var(--xor-roll-strength)))      40%,
		rgba(0, 0, 0, calc(.085 * var(--xor-roll-strength)))      50%,
		rgba(0, 0, 0, calc(.050 * var(--xor-roll-strength)))      60%,
		rgba(0, 0, 0, 0)                                         72%,
		rgba(255, 255, 255, 0)                                  100%);
	background-size: 100% 100vh;
	background-repeat: repeat-y;

	animation: xor-roll var(--xor-roll) linear infinite;
	will-change: transform;
}

@keyframes xor-roll {
	from { transform: translateY(0); }
	to   { transform: translateY(100vh); }
}

/* A band sweeping the whole viewport is exactly the kind of motion that
   triggers nausea, and it carries no information. Park it mid-sweep rather than
   at the wrap, so the still version keeps a trace of the effect instead of
   looking like a flat wash. */
@media (prefers-reduced-motion: reduce) {
	html:has(body.home)::before {
		animation: none;
		transform: translateY(40vh);
	}
}

body::after {
	content: "";
	position: fixed;
	inset: 0;
	pointer-events: none;
	z-index: 8;
	background: radial-gradient(ellipse at 50% 42%, rgba(0, 0, 0, 0) 52%, rgba(0, 0, 0, .5) 100%);
}

/* FX toggle (.xor-fx-toggle in parts/header.html): drops the scanline and
   rolling-bar overlays, the blinking terminal cursor and the NerdSEQ screen's
   own animation for people who find the motion distracting. The vignette in
   body::after is left alone — it's a static darkening, not a flicker. The
   class lands on <html> from an inline script in wp_head, ahead of first
   paint, and again from assets/js/fx-toggle.js once it can bind the switch;
   that same script also stops/restarts the screen's JS-driven animation via
   nerdseq-screen.js, which CSS alone cannot reach. */
html.xor-fx-off body::before,
html.xor-fx-off::before {
	display: none !important;
}
html.xor-fx-off .xor-cursor {
	display: none;
}

/* Photography stays crisp: lift it above the scanline layer. */
.xor-stage,
.xor-shot,
.xor-card figure,
.wp-block-post-featured-image {
	position: relative;
	z-index: 10;
}

.xor-glow { text-shadow: 0 0 7px rgba(51, 255, 102, .4); }

/* --------------------------------------------------------- navigation ---- */

.wp-block-navigation .wp-block-navigation-item__label { text-transform: uppercase; letter-spacing: .06em; }

.wp-block-navigation .wp-block-navigation__submenu-container {
	background: var(--wp--preset--color--base-2);
	border: 1px solid var(--wp--preset--color--line);
	box-shadow: 0 20px 44px -18px rgba(0, 0, 0, .95);
	padding: .4rem 0;
	min-width: 14rem;
}

.wp-block-navigation .wp-block-navigation__submenu-container .wp-block-navigation-item__content {
	padding: .55rem 1.1rem;
	color: var(--wp--preset--color--muted);
	text-transform: none;
	letter-spacing: .02em;
}

.wp-block-navigation .wp-block-navigation__submenu-container .wp-block-navigation-item__content:hover {
	background: var(--wp--preset--color--panel);
	color: var(--wp--preset--color--green);
}

/* External links get a marker without needing a separate block. */
.xor-external .wp-block-navigation-item__label::after,
a.xor-external::after { content: " ↗"; font-size: .8em; opacity: .7; }

/* -------------------------------------------------- the NerdSEQ screen ---- */

.xor-stage {
	position: relative;
	width: 100%;
	max-width: 660px;
	margin-left: auto;
	border: 1px solid var(--wp--preset--color--line);
	background: linear-gradient(165deg, var(--wp--preset--color--raise), #070c08);
	padding: 14px;
	box-shadow: 0 34px 80px -30px rgba(0, 0, 0, .95), 0 0 60px -26px rgba(51, 255, 102, .16);
}

.xor-stage__photo { position: relative; line-height: 0; }
.xor-stage__photo img { display: block; width: 100%; height: auto; }
.xor-stage__photo figure { margin: 0; }

/* Corner ticks drawn as four backgrounds on ONE inset box. Four separately
   positioned spans kept drifting apart; a single box with `inset` is
   symmetric by construction, so it cannot happen again. */
.xor-stage::before {
	content: "";
	position: absolute;
	inset: 5px;
	pointer-events: none;
	z-index: 6;
	--c: var(--wp--preset--color--green-dim);
	--h: linear-gradient(var(--c), var(--c)) no-repeat;
	background:
		var(--h) 0 0 / 12px 1px,        var(--h) 0 0 / 1px 12px,
		var(--h) 100% 0 / 12px 1px,     var(--h) 100% 0 / 1px 12px,
		var(--h) 0 100% / 12px 1px,     var(--h) 0 100% / 1px 12px,
		var(--h) 100% 100% / 12px 1px,  var(--h) 100% 100% / 1px 12px;
}

/* Panel colourway picker, under the hero photo (nerdseq-colorway.js). Hovering
   (or clicking, for anyone without a mouse) swaps the photo and stays there —
   there is no "revert on mouseleave", it is a choice, not a preview. */
/* Same width and right edge as .xor-stage, so the row of buttons reads as
   part of the same card rather than a small control floating off its
   corner. */
.xor-colorway {
	display: flex;
	gap: 10px;
	margin-top: 14px;
	width: 100%;
	max-width: 660px;
	margin-left: auto;
}
.xor-colorway__btn {
	flex: 1;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 7px;
	border: 1px solid var(--wp--preset--color--line);
	background: var(--wp--preset--color--panel);
	color: var(--wp--preset--color--muted);
	padding: 9px 12px;
	font-family: inherit;
	font-size: 11px;
	letter-spacing: .08em;
	text-transform: uppercase;
	cursor: pointer;
	transition: border-color .14s, color .14s;
}
.xor-colorway__btn:hover,
.xor-colorway__btn:focus-visible { border-color: var(--wp--preset--color--green-dim); color: var(--wp--preset--color--green); }
.xor-colorway__btn.is-active { border-color: var(--wp--preset--color--green); color: var(--wp--preset--color--green); }
.xor-colorway__swatch {
	width: 13px;
	height: 13px;
	border: 1px solid var(--wp--preset--color--line);
	flex: none;
}
.xor-colorway__swatch--black { background: #131313; }
.xor-colorway__swatch--grey { background: linear-gradient(155deg, #d8d8d8, #9a9a9a); }
.xor-colorway__swatch--mix { background: linear-gradient(90deg, #131313 50%, #b7b7b7 50%); }

.xor-pin {
	position: absolute;
	bottom: -11px;
	left: 18px;
	z-index: 7;
	background: var(--wp--preset--color--base-2);
	border: 1px solid var(--wp--preset--color--green-dim);
	padding: 5px 12px;
	font-size: 10.5px;
	letter-spacing: .14em;
	color: var(--wp--preset--color--green);
	white-space: nowrap;
}

/* The live pattern, injected by nerdseq-screen.js.

   Column ratios, colours and panel layout are traced from a photograph of a
   real NerdSEQ screen. The columns are `fr` rather than fixed ems so the whole
   pattern keeps the proportions of the original at any size the photo is drawn
   at — only the font size is computed in JavaScript. --side is the pattern
   panel, and matches the 21.5% it occupies on the real screen. */
/* Everything here is measured in percentages of the screen, so the boxes have
   to agree on what a percentage covers. Without this, `width: var(--side)` on
   the header's PATTERN cell resolved to 21.5% PLUS its border and padding,
   while the pattern panel below it — whose padding sits on its inner sections —
   resolved to 21.5% plus a border only. The header strip came out 3.65px
   narrower than the rows, so every header label drifted a little further left
   than the column under it, worst at the right-hand end. */
.xor-ov,
.xor-ov *,
.xor-ov *::before,
.xor-ov *::after { box-sizing: border-box; }

.xor-ov {
	--cols: 3.4fr 3.9fr 2.9fr 3.9fr 2.9fr 2.9fr 8.9fr 1.9fr;
	--side: 21.5%;
	--scr-line: #8f8f8f;
	--scr-green: #21d421;
	--scr-white: #fff;
	--scr-amber: #f5f0a2;
	--scr-dim: #8a8a8a;
	position: absolute;
	left: var(--xor-scr-left);
	top: var(--xor-scr-top);
	width: var(--xor-scr-width);
	height: var(--xor-scr-height);
	background: #000;
	overflow: hidden;
	display: flex;
	flex-direction: column;
	z-index: 2;
	font-size: var(--xor-ovfs, 9px);
	line-height: 1.22;
	letter-spacing: .06em;
	color: var(--scr-green);
	/* A plain border, not the photographed bezel's own reflective highlight —
	   that highlight is a coral/pink line only on some edges, not a rule we
	   want to reproduce. This is a complete rectangle in the same grey as the
	   column and row rules inside, so the screen reads as one framed panel. */
	border: 3px solid var(--scr-line);
	box-shadow: 0 0 12px rgba(80, 220, 140, .14);
}

/* The panel's OUT LED. Sits over the photographed LED and brightens on each
   step of the pattern, driven by nerdseq-screen.js toggling .is-lit. Blend
   mode "screen" only ever lightens what's under it, so it reads as the LED
   itself getting brighter rather than a disc drawn on top of the photo. */
.xor-led {
	position: absolute;
	border-radius: 50%;
	pointer-events: none;
	z-index: 3;
	background: radial-gradient(circle, rgba(255, 150, 110, .95) 0%, rgba(255, 70, 40, .6) 45%, rgba(255, 70, 40, 0) 75%);
	mix-blend-mode: screen;
	opacity: 0;
	transition: opacity .1s ease-out;
}
.xor-led.is-lit { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
	.xor-led { display: none; }
}

.xor-ov::after {
	content: "";
	position: absolute;
	inset: 0;
	pointer-events: none;
	z-index: 5;
	background: repeating-linear-gradient(180deg, rgba(0, 0, 0, .22) 0 1px, transparent 1px 2px);
}

/* Header and rows share one wrapper so a single set of column rules can be
   drawn over both — see .xor-ov__rules below. */
.xor-ov__main { flex: 1; min-height: 0; position: relative; display: flex; flex-direction: column; }

/* header */
.xor-ov__head { display: flex; border-bottom: 1px solid var(--scr-line); color: var(--scr-amber); }
.xor-ov__hcols { flex: 1; min-width: 0; display: grid; grid-template-columns: var(--cols); }
.xor-ov__hcols > span { padding: 0 .25em; white-space: nowrap; overflow: hidden; text-align: center; }
.xor-ov__hside { width: var(--side); padding: 0 .3em; border-left: 1px solid var(--scr-line); }
.xor-ov__hcols em { font-style: normal; color: var(--scr-dim); }

/* The two header glyphs that no font can be relied on for: the transport
   marker and the table column's grid. The transport is a play triangle rather
   than the photograph's stop block, because the screen is playing. */
.xor-ov__play { display: inline-block; width: 0; height: 0; vertical-align: -.08em;
	border-left: .78em solid var(--scr-amber);
	border-top: .45em solid transparent;
	border-bottom: .45em solid transparent; }
/* The table glyph is an inline SVG built in nerdseq-screen.js — see the note
   there for why it is not a border or a background. Its offset is in em, not
   px, because the screen renders at anything from about 4px to 8px of type, so
   a fixed 1px nudge is either invisible or a lurch; -.2em drops it a pixel
   below the baseline of the labels beside it. */
.xor-ov__grid { display: inline-block; width: 1.15em; height: 1em; vertical-align: -.2em; }

/* GROOVE is set in two stacked lines on the real screen, in a column one
   character wide. */
.xor-ov__gr { font-size: .52em; line-height: 1.05; letter-spacing: 0; }
.xor-ov__gr i { font-style: normal; display: block; }

/* rows. The sixteen rows share out the whole panel height rather than sitting
   at their natural line height, because on the real screen they fill it — a
   gap under row 15 is the giveaway that this is a web page.

   The reel holds the pattern twice over at 200% height, so one row is 1/32 of
   it, and the scroll travels exactly -50% — one whole pattern — before it
   restarts. The seam therefore falls between two identical rows and cannot be
   seen, which is what makes the wrap endless rather than a jump back to the
   top. Transform percentages resolve against the element's own box, so this
   holds at any size without measuring anything.

   It steps a whole row at a time rather than gliding, which is what makes it
   read as a tracker. The stepping is driven from JavaScript rather than a
   keyframe animation so that the panel's note readout cannot drift out of step
   with the row actually under the cursor — one counter feeds the transform, the
   readout, the meters and the clock. */
.xor-ov__body { flex: 1; display: flex; min-height: 0; }
.xor-ov__rows { flex: 1; min-width: 0; position: relative; overflow: hidden; }
.xor-ov__reel { position: absolute; inset: 0 0 auto 0; z-index: 1; height: 200%; display: flex; flex-direction: column;
	will-change: transform; }
/* min-height: 0 overrides the flex default of "auto", which floors a flex
   item at its content's line-height and refuses to shrink it further. Without
   it, all 32 rows in the doubled reel sit a hair taller than their equal
   1/32 share, the reel ends up taller than the (fixed, overflow: hidden)
   rows box, and the bottom row is clipped — 16 rows reading as "15 and a
   half". */
.xor-ov__row { flex: 1; min-height: 0; position: relative; z-index: 1; display: grid; grid-template-columns: var(--cols); align-items: center; }
.xor-ov__row > span { padding: 0 .25em; white-space: nowrap; overflow: hidden; text-align: center; }
/* The row number is two digits, visually narrower than the play triangle
   centred above it in the same column — nudge it left to sit under it. */
.xor-ov__row > span:first-child { transform: translateX(-.06em); }
.xor-ov__row.is-beat { color: var(--scr-white); }

/* The edit cursor belongs to the screen, not to a row, so it is a fixed block
   in the Y column and the pattern steps underneath it — the value it sits on
   changes with every row that passes. Its own grid means it needs no measuring:
   column 5 is Y, and row 9 of 16 is the middle of the panel. It sits BEHIND the
   reel so the passing value stays legible on top of the red, which is how the
   photograph has it. */
.xor-ov__cur { position: absolute; inset: 0; z-index: 0; pointer-events: none;
	display: grid; grid-template-columns: var(--cols); grid-template-rows: repeat(16, 1fr); }
.xor-ov__cur i { grid-column: 5; grid-row: 9; background: #e21414; }
/* In the FX column the cursor covers one half of the 8-character command, the
   way the module edits it. The text is monospace and centred in the cell, so
   each half is 4ch either side of the centre line. */
.xor-ov__cur i.is-fx1,
.xor-ov__cur i.is-fx2 { justify-self: center; width: 4.2ch; }
.xor-ov__cur i.is-fx1 { transform: translateX(-47.6%); }
.xor-ov__cur i.is-fx2 { transform: translateX(47.6%); }


/* The column rules are drawn once over the header AND the rows, so each is one
   unbroken line from the top of the screen down to row 15. Drawing them per
   cell instead gives a rule that restarts under the header and stops wherever
   the last row stops. The right inset is the pattern panel, which draws its own
   edge. */
.xor-ov__rules { position: absolute; inset: 0 var(--side) 0 0; z-index: 2; pointer-events: none; display: grid; grid-template-columns: var(--cols); }
.xor-ov__rules span + span { border-left: 1px solid var(--scr-line); }

/* Position scrollbar, sitting just inside the row-number column: that column is
   3.4 of the 30.7 column units, so its right edge is at 11.1%. */
.xor-ov__sb { position: absolute; left: 9.3%; top: 0; width: 1.4%; height: 26%; z-index: 3; }
.xor-ov__sb i { position: absolute; left: 0; right: 0; }
.xor-ov__sb .tr { top: 0; bottom: 0; background: repeating-linear-gradient(180deg, #7d7d7d 0 1px, transparent 1px 3px); }
.xor-ov__sb .th { top: 0; height: 60%; background: #9a9a9a; }
.xor-ov__sb .dot { top: 54%; height: 7%; background: var(--scr-amber); }

/* pattern panel */
.xor-ov__side { width: var(--side); border-left: 1px solid var(--scr-line); display: flex; flex-direction: column; color: var(--scr-amber); }
.xor-ov__side section { padding: 0 .3em; position: relative; }
.xor-ov__side section + section { border-top: 1px solid var(--scr-line); }
.xor-ov__side .grow { flex: 1; }
.xor-ov__side p { margin: 0; display: flex; justify-content: space-between; align-items: center; gap: .3em; white-space: nowrap; }
.xor-ov__bar { flex: 0 0 46%; height: .6em; background: #9a9a9a; position: relative; }
.xor-ov__bar i { position: absolute; inset: 0 auto 0 0; background: var(--scr-amber); transition: width .18s linear; }
.xor-ov__box { position: absolute; right: 0; bottom: 0; width: 52%; height: 34%; border: 1px solid var(--scr-line); border-right: 0; border-bottom: 0; }

/* footer */
.xor-ov__foot { border-top: 1px solid var(--scr-line); padding: 0 .3em; display: flex; justify-content: space-between; color: #eaeaea; }

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

.xor-boot { font-size: 13px; color: var(--wp--preset--color--muted); line-height: 2; }
.xor-boot b { color: var(--wp--preset--color--green-dim); font-weight: 500; }
.xor-boot .ok { color: var(--wp--preset--color--green); }

.xor-cursor {
	display: inline-block;
	width: .58em;
	height: 1em;
	background: var(--wp--preset--color--green);
	vertical-align: -.14em;
	box-shadow: 0 0 8px rgba(51, 255, 102, .7);
	animation: xor-blink 1.05s steps(1) infinite;
}
@keyframes xor-blink { 50% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .xor-cursor { animation: none; } }

/* Section rule: a label followed by a hairline. */
.xor-sechead { display: flex; align-items: baseline; gap: 14px; }
.xor-sechead > h2 {
	font-size: 13px;
	letter-spacing: .22em;
	text-transform: uppercase;
	color: var(--wp--preset--color--green);
	font-weight: 600;
	white-space: nowrap;
	margin: 0;
}
.xor-sechead::after { content: ""; flex: 1; height: 1px; background: var(--wp--preset--color--line); }
/* A long label ("Expanders without their own firmware") is wider than a phone
   on one line, and pushed the page sideways. */
@media (max-width: 600px) { .xor-sechead > h2 { white-space: normal; } }

/* Cards used by the product and ecosystem grids. */
.xor-card {
	position: relative;
	border: 1px solid var(--wp--preset--color--line);
	background: linear-gradient(170deg, var(--wp--preset--color--panel), var(--wp--preset--color--base-2));
	display: flex;
	flex-direction: column;
	overflow: hidden;
	transition: border-color .16s, box-shadow .16s;
}
/* No transform on hover, on purpose: it would create a stacking context and
   drag the card photo back under the scanline overlay. */
.xor-card:hover { border-color: var(--wp--preset--color--green-dim); box-shadow: 0 0 42px -18px rgba(51, 255, 102, .42); }

.xor-card__photo { background: linear-gradient(170deg, var(--wp--preset--color--raise), #070c08); padding: 18px; display: flex; justify-content: center; border-bottom: 1px solid var(--wp--preset--color--line); }
.xor-card__photo img { max-height: 158px; width: auto; }
.xor-card__body { padding: 18px; flex: 1; display: flex; flex-direction: column; }
.xor-card__kicker { font-size: 10.5px; color: var(--wp--preset--color--amber); letter-spacing: .14em; text-transform: uppercase; }
.xor-card__adds { margin-top: auto; padding-top: 13px; border-top: 1px dashed var(--wp--preset--color--line); font-size: 12px; color: var(--wp--preset--color--green); }

/* Cards that link to a product page: the title is the real, announced link;
   .xor-card__stretch is a second, aria-hidden and untabbable copy of the same
   href sized to the whole card, so clicking anywhere (not just the title)
   goes to the product without a screen reader hearing the link twice. */
.xor-card h4 a { color: inherit; text-decoration: none; }
.xor-card:has(.xor-card__stretch) { cursor: pointer; }
.xor-card__stretch { position: absolute; inset: 0; z-index: 2; }

/* Spec / fact tables. */
.xor-specs { border: 1px solid var(--wp--preset--color--line); background: var(--wp--preset--color--base-2); }
.xor-specs__title { padding: 9px 14px; border-bottom: 1px solid var(--wp--preset--color--line); font-size: 11px; letter-spacing: .16em; text-transform: uppercase; color: var(--wp--preset--color--green); background: var(--wp--preset--color--panel); }
.xor-specs table { width: 100%; border-collapse: collapse; font-size: 13px; border: 0; }
/* Core's table block sets `border: 1px solid` on every cell, which drew a
   divider down the middle of the panel. Only the horizontal rule is wanted,
   so clear all four sides first — setting border-bottom alone left the rest. */
.xor-specs table td,
.xor-specs table th {
	border: 0;
	border-bottom: 1px solid rgba(21, 58, 34, .55);
	padding: 9px 16px;
}
.xor-specs table td:first-child { color: var(--wp--preset--color--muted); }
.xor-specs table td:last-child { text-align: right; }
.xor-specs table tr:last-child td { border-bottom: 0; }

/* Comparison table across related products (xor_compare_table): a row label
   and one column per product. Values read left to right, so none of the spec
   panel's right-aligned last column; the product being viewed is the bold,
   unlinked header. */
.xor-compare { margin-top: var(--wp--preset--spacing--30); }
.xor-specs.xor-compare table td:last-child { text-align: left; }
.xor-compare table th {
	text-align: left; font-weight: 500; font-size: 11px; letter-spacing: .12em; text-transform: uppercase;
	color: var(--wp--preset--color--green); background: var(--wp--preset--color--panel);
}
.xor-compare table th strong { color: var(--wp--preset--color--amber); font-weight: 700; }
.xor-compare table td:not(:first-child) { color: var(--wp--preset--color--contrast); }
.xor-compare table td:first-child { white-space: nowrap; }
/* On a phone all four columns must fit rather than scroll the product being
   compared out of view: tighter cells, and labels may wrap. */
@media (max-width: 700px) {
	.xor-specs.xor-compare table { font-size: 12px; table-layout: fixed; }
	.xor-specs.xor-compare table td,
	.xor-specs.xor-compare table th { padding: 8px 6px; }
	.xor-compare table th { font-size: 10px; letter-spacing: .06em; }
	.xor-compare table td:first-child { white-space: normal; }
}

/* Wide tables must scroll rather than push the page sideways. */
.wp-block-table { overflow-x: auto; }

@media (max-width: 940px) {
	.xor-stage { margin-left: 0; }
}

/* Card grid used by the ecosystem and product patterns. */
.xor-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(256px, 1fr));
	gap: 18px;
}
.xor-card h4 { font-size: 15.5px; font-weight: 600; margin: 9px 0; letter-spacing: -0.005em; }
.xor-card p { font-size: 13px; color: var(--wp--preset--color--muted); line-height: 1.62; margin: 0 0 14px; }
.xor-card figure { margin: 0; }
.xor-card--soon { border-style: dashed; opacity: .7; }
/* Cut-out modules are tall and narrow, so height is what limits them. */
.xor-card__photo { min-height: 196px; align-items: center; }
.xor-card--soon .xor-card__photo span { font-size: 12px; letter-spacing: .2em; color: var(--wp--preset--color--dim); }

/* ============================================================ fixes ==== */

/* --- brand lockup ------------------------------------------------------ */
.xor-brand { font-weight: 700; font-size: 17px; letter-spacing: .16em; color: var(--wp--preset--color--green); text-decoration: none; }
/* The inner span only — the wordmark is now wrapped in .xor-brand__text so the
   round mark can sit beside it, and a bare `.xor-brand span` caught that
   wrapper too, dimming "XOR" along with "::ELECTRONICS". */
.xor-brand__text > span { color: var(--wp--preset--color--muted); font-weight: 400; }
.xor-brand:hover { text-decoration: none; }

/* --- boot block: leaders drawn, not typed, so they always line up ------ */
.xor-boot__line {
	flex-wrap: wrap;
	/* Each line is an ordinary paragraph so the text is editable. The leader is
	   a flex-growing pseudo-element ordered between the label and the status,
	   so it always reaches exactly as far as it needs to. */
	--gap: .6em;    /* space between dots — one character advance */
	--dot: .9px;    /* dot radius */
	--lift: 1px;    /* how far above the baseline the dots sit */
	display: flex;
	align-items: baseline;
	gap: 0;
	margin: 0;
	font-size: 13px;
	color: var(--wp--preset--color--muted);
}
/* The label is wrapped so it stays a single flex item — otherwise <b> becomes
   its own item and the space after it collapses inconsistently. If the wrapper
   is ever deleted while editing, the bare text is still order 0 and the layout
   survives. */
.xor-boot__line .xor-boot__cmd {
	order: 0;
	/* pre-wrap, not pre: multiple spaces are still honoured, but a long line
	   can wrap instead of shoving the leader and the status off the end. */
	white-space: pre-wrap;
	min-width: 0;
}
.xor-boot__line::after {
	content: "";
	order: 2;
	flex: 1;
	margin: 0 .55em;
	align-self: baseline;
	height: .72em;
	color: var(--wp--preset--color--green-fade);
	background-image: radial-gradient(circle at 50% 50%, currentColor var(--dot), transparent calc(var(--dot) + .5px));
	background-repeat: repeat-x;
	background-size: var(--gap) calc(var(--dot) * 2);
	background-position: 0 calc(100% - var(--lift));
}
/* The status sits after the leader. Editable text — change OK to FAIL and it
   still lines up. */
.xor-boot__line strong {
	order: 3;
	/* Keeps the status hard right even on the rare line long enough to wrap;
	   on a normal line the leader has already taken the slack. */
	margin-left: auto;
	font-weight: 400;
	color: var(--wp--preset--color--green);
	letter-spacing: .1em;
}
.xor-boot__line b,
.xor-boot__line strong.xor-cmd { color: var(--wp--preset--color--green-dim); font-weight: 500; }
.xor-boot__line .ok { color: var(--wp--preset--color--green); letter-spacing: .1em; }

/* --- navigation dropdown ---------------------------------------------- *
 * Core ships a white submenu at .wp-block-navigation .has-child
 * .wp-block-navigation__submenu-container. Repeat the class to outrank it. */
.wp-block-navigation .wp-block-navigation__submenu-container.wp-block-navigation__submenu-container {
	background-color: var(--wp--preset--color--base-2);
	color: var(--wp--preset--color--contrast);
	border: 1px solid var(--wp--preset--color--line);
	border-radius: 0;
	box-shadow: 0 20px 44px -18px rgba(0, 0, 0, .95);
	padding: .4rem 0;
	min-width: 15rem;
}
.wp-block-navigation .wp-block-navigation__submenu-container .wp-block-navigation-item__content {
	color: var(--wp--preset--color--muted);
	padding: .55rem 1.1rem;
}
.wp-block-navigation .wp-block-navigation__submenu-container .wp-block-navigation-item__content:hover,
.wp-block-navigation .wp-block-navigation__submenu-container .wp-block-navigation-item__content:focus {
	background-color: var(--wp--preset--color--panel);
	color: var(--wp--preset--color--green);
}

/* Section headings and rules inside the dropdown. */
.wp-block-navigation .xor-subhead .wp-block-navigation-item__content {
	pointer-events: none;
	color: var(--wp--preset--color--green-fade);
	font-size: 10px;
	letter-spacing: .18em;
	text-transform: uppercase;
	padding: .7rem 1.1rem .3rem;
}
.wp-block-navigation .xor-subhead + li .wp-block-navigation-item__content { padding-top: .35rem; }
/* A heading that is not the first one gets the separator, so no empty
   divider items are needed — core drops nav links with an empty label. */
.wp-block-navigation__submenu-container > .xor-subhead:not(:first-child) {
	border-top: 1px solid var(--wp--preset--color--line);
	margin-top: .45rem;
}
/* "All products" closes the Products dropdown after a run of ordinary links
   with no subhead of its own, so it needs the same rule drawn directly. */
.xor-navdivider .wp-block-navigation-item__content {
	border-top: 1px solid var(--wp--preset--color--line);
	margin-top: .45rem;
	padding-top: calc(.55rem + .35rem);
}

/* --- hero stage: bezel must hug the photograph ------------------------- */
.xor-stage > .wp-block-group,
.xor-stage figure,
.xor-stage .wp-block-image { margin: 0; }
.xor-stage.wp-block-group { gap: 0; display: block; }
.xor-stage__photo.wp-block-group { gap: 0; display: block; }
.xor-stage__photo > figure,
.xor-stage__photo > .wp-block-image > img { display: block; }

/* --- buttons ----------------------------------------------------------- */
.wp-block-button.is-style-outline > .wp-block-button__link {
	border-width: 1px;
	border-style: solid;
	border-color: var(--wp--preset--color--line);
	color: var(--wp--preset--color--muted);
	background: transparent;
	padding: .75rem 1.25rem;
}
.wp-block-button.is-style-outline > .wp-block-button__link:hover {
	background-color: var(--wp--preset--color--panel);
	border-color: var(--wp--preset--color--green-dim);
	color: var(--wp--preset--color--green);
}
.wp-block-button:not(.is-style-outline) > .wp-block-button__link {
	transition: background-color .14s, color .14s, box-shadow .14s, border-color .14s;
}
.wp-block-button:not(.is-style-outline) > .wp-block-button__link:hover {
	box-shadow: 0 0 20px rgba(51, 255, 102, .35);
}

/* Full-width bands must meet edge to edge: the section rule at the bottom of
   one band is the top edge of the next, with no page background between.
   Needs to outrank core's `.wp-container-xxx > * + *` block-gap margin. */
main.wp-block-group > .wp-block-group,
main .wp-block-post-content > .wp-block-group { margin-block: 0; }

/* The sticky header is z-index:10 from core, the same value the photos use to
   sit above the scanline layer — and the photo comes later in the DOM, so the
   dropdowns were being painted behind it. Put the header above both. */
.xor-header.is-position-sticky { z-index: 40; }
.wp-block-navigation .wp-block-navigation__submenu-container { z-index: 41; }

/* A sticky element can only travel inside its parent's box, and the
   template-part wrapper is exactly as tall as the header — so the header had
   nowhere to stick to. Take the wrapper out of layout and the header sticks
   against the page instead. */
.wp-block-template-part:has(> .xor-header) { display: contents; }

/* The specs panel and cards are core blocks now, so neutralise the margins
   and gaps WordPress adds between them. */
.xor-specs .wp-block-table,
.xor-specs .wp-block-table table { margin: 0; }
.xor-specs.wp-block-group { gap: 0; }
.xor-specs__title { margin: 0; }
.xor-grid.wp-block-group { gap: 18px; }
.xor-card.wp-block-group,
.xor-card__photo.wp-block-group { gap: 0; }
.xor-card__body.wp-block-group { gap: 0; }
.xor-card__body > * { margin-block: 0; }
.xor-card__body h4 { margin: 9px 0; }
.xor-card__body p { margin: 0 0 14px; }
.xor-card__body .xor-card__kicker { margin: 0; }
.xor-card__photo .wp-block-image,
.xor-card__photo figure { margin: 0; }

/* ------------------------------------------------ ecosystem alignment ---- */

/* Core's flow layout adds margin-block-start to every child after the first
   (`.wp-block-group.is-layout-flow > * + *`, specificity 0,2,0). Inside the
   grid that pushed each card down by a stray 24px and broke the rows; inside
   the cards it broke the internal rhythm. Match the specificity to clear it. */
.wp-block-group.xor-grid > *,
.wp-block-group.xor-card > *,
.wp-block-group.xor-card__photo > *,
.wp-block-group.xor-card__split > *,
.wp-block-group.xor-card__body > * { margin-block: 0; }

.xor-card__split.wp-block-group { gap: 0; }

/* ---------------------------------------- ecosystem: photo beside the text ---- */

/* The expanders are 2–8 HP modules: tall, narrow cut-outs. Stacked above the
   text they leave most of the card's width empty and push the reading matter
   down, so the wide cards put the photograph in a column of its own. */

.xor-grid--wide {
	/* Three to a row. The content column is 1140px with an 18px gap, so a
	   3-across track is (1140 − 36) / 3 = 368px: any minimum from ~272px to
	   368px produces exactly three, and 340px sits in the middle of that
	   window rather than on its edge. Narrower viewports drop to two and then
	   to the stacked card at the media query below. */
	grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
}

.xor-card--wide .xor-card__split {
	display: flex;
	align-items: stretch;
	flex: 1;
}

.xor-card--wide .xor-card__photo {
	/* Fixed proportion rather than auto, so every photo column lines up down
	   the grid regardless of how wide the module in it happens to be. At a
	   368px card this is ~110px, which still clears the widest panel here
	   (Dualchord) once the 18px padding is taken off. */
	flex: 0 0 30%;
	min-height: 0;
	padding: 16px 12px;
	/* The divider turns 90°: it separated photo from text when they were
	   stacked, and has to do the same job now they are side by side. */
	border-bottom: 0;
	border-right: 1px solid var(--wp--preset--color--line);
}

.xor-card--wide .xor-card__photo img {
	/* Height is the binding constraint for these cut-outs, not width. Pulled
	   back from the two-across version: at three per row the card is shorter,
	   and a 215px photo left the text column with an obvious gap under it. */
	max-height: 190px;
}

/* min-width: 0 because a flex item defaults to min-width: auto, which refuses
   to shrink below its longest unbreakable word — one long spec string would
   otherwise push the photo column narrower than its 30%. */
.xor-card--wide .xor-card__body {
	flex: 1;
	min-width: 0;
	padding: 16px;
}

/* Kicker and heading live in the text column with the description, so each card
   reads as one block set against its photo. */
.xor-card--wide .xor-card__body h4 { margin: 6px 0 9px; }

@media (max-width: 620px) {
	/* Two columns inside a phone-width card leaves a ~90px photo column and a
	   measure too narrow to read. Fall back to the stacked card, divider and
	   photo height included. */
	.xor-card--wide .xor-card__split { flex-direction: column; }
	.xor-card--wide .xor-card__photo {
		flex: 0 0 auto;
		min-height: 196px;
		border-right: 0;
		border-bottom: 1px solid var(--wp--preset--color--line);
	}
	.xor-card--wide .xor-card__photo img { max-height: 158px; }
}

/* `.xor-card__body p` (0,1,1) was beating `.xor-card__adds` (0,1,0), so the
   footer line lost its `margin-top: auto` and stopped sitting at the bottom —
   which is why the dashed rules didn't line up across cards. */
.xor-card__body .xor-card__adds {
	margin-top: auto;
	margin-bottom: 0;
}

/* Same photo height on every card, so all the titles start on one line. */
/* Cut-out modules are tall and narrow, so height is what limits them. */
.xor-card__photo { min-height: 196px; align-items: center; }

/* Downloads tables: four columns, last one right-aligned for the link. */
.xor-dl table { table-layout: auto; }
.xor-dl td:nth-child(2) { color: var(--wp--preset--color--amber); white-space: nowrap; }
.xor-dl td:nth-child(3) { color: var(--wp--preset--color--muted); }
.xor-dl td:last-child { text-align: right; white-space: nowrap; }
.xor-specs .xor-dl td:first-child { color: var(--wp--preset--color--contrast); }

/* The overlay is injected as the second child of a flow-layout group, so core
   gives it margin-block-start: 1.25rem (20px) — which shifted the screen 20px
   down the panel. Same cause as the corner-tick offset. The LED is injected
   after it (third child) and inherits the same gap. */
.wp-block-group.xor-stage__photo > .xor-ov,
.wp-block-group.xor-stage__photo > .xor-led { margin: 0; }

/* The boot group is a flow-layout group, so clear core's block gap between
   the lines and keep them tight like a log. */
.wp-block-group.xor-boot > * { margin-block: 0; }
.xor-boot.wp-block-group { gap: 0; }
.xor-boot { line-height: 2; }

/* The closing line's status: brighter, so the log reads as having arrived. */
.xor-boot__line strong.xor-ready {
	color: var(--wp--preset--color--green);
	text-shadow: 0 0 7px rgba(51, 255, 102, .55);
	letter-spacing: .14em;
}

/* ---------------------------------------------------------------------------
   Long-form pages: about, legal, contact, tutorials.

   These are the first pages here that are mostly text, so they need a reading
   measure and vertical rhythm the homepage bands never asked for.
   --------------------------------------------------------------------------- */

.xor-prose { font-size: 15px; line-height: 1.85; }
.xor-prose h1 { margin-bottom: .4em; }

/* A reading measure, but only on the prose itself — never on panels or tables,
   whose own children are paragraphs too and must fill their box.

   The direct-child scoping matters: a max-width narrower than the layout's
   content size would otherwise be centred by core's constrained layout, which
   reads as a stray indent rather than a column. xor_band_open() gives the prose
   group flow layout for the same reason. */
.xor-prose > p,
.xor-prose > ul,
.xor-prose > ol,
.xor-prose > .wp-block-quote,
.xor-prose .wp-block-column > p,
.xor-prose .wp-block-column > ul { max-width: 68ch; }

/* A short tagline tucked under the H1 — its own line rather than sharing the
   heading's baseline, which a display-size H1 would dwarf it against. Pulled
   up against the heading and given its own bottom margin so it reads as
   attached to the title rather than another paragraph in the flow. */
.xor-prose > .xor-tagline {
	margin-top: -12px;
	margin-bottom: 22px;
	font-size: 14px;
	letter-spacing: .04em;
	color: var(--wp--preset--color--muted);
}

/* Opening paragraph, one step up so the page has a way in. */
.xor-prose > .xor-lede {
	font-size: 18px;
	line-height: 1.7;
	color: var(--wp--preset--color--contrast);
	max-width: 62ch;
}

/* Core gives flow children a uniform block gap; prose wants headings to bind
   to the text under them instead. */
.xor-prose > h2,
.xor-prose > .xor-sechead { margin-top: var(--wp--preset--spacing--60); }
.xor-prose h3 {
	margin-top: var(--wp--preset--spacing--50);
	margin-bottom: .5em;
	color: var(--wp--preset--color--green);
	text-transform: uppercase;
}
.xor-prose h3 + p { margin-top: 0; }

.xor-prose ul { padding-left: 1.1em; }
.xor-prose li { margin-bottom: .45em; }
.xor-prose li::marker { color: var(--wp--preset--color--green-dim); }
/* Buttons are excluded: `.xor-prose a` outranks theme.json's `.wp-element-button`
   rules, so a button dropped into prose kept the green link colour on hover and
   went invisible against the green hover fill. */
.xor-prose a:not(.wp-element-button) { color: var(--wp--preset--color--green); text-decoration: none; border-bottom: 1px solid var(--wp--preset--color--green-fade); }
.xor-prose a:not(.wp-element-button):hover { border-bottom-color: var(--wp--preset--color--green); }

/* Anchors land under the sticky header without the heading hiding behind it. */
.xor-prose :where( h1, h2, h3 )[id] { scroll-margin-top: 90px; }

/* In-page section index. Reads like a row of terminal tabs. */
.xor-jump.wp-block-group {
	gap: 0;
	margin: var(--wp--preset--spacing--40) 0 var(--wp--preset--spacing--50);
	border: 1px solid var(--wp--preset--color--line);
	background: var(--wp--preset--color--base-2);
}
.xor-jump > p { margin: 0; }
.xor-jump a {
	display: block;
	padding: 9px 16px;
	border: 0;
	border-right: 1px solid var(--wp--preset--color--line);
	font-size: 12px;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: var(--wp--preset--color--muted);
}
.xor-jump > p:last-child a { border-right: 0; }
.xor-jump a:hover { color: var(--wp--preset--color--green); background: var(--wp--preset--color--panel); }

/* The model withdrawal wording: quoted, so it reads as something to copy. */
.xor-prose .xor-quote {
	margin: var(--wp--preset--spacing--40) 0;
	padding: 14px 18px;
	border: 0;
	border-left: 2px solid var(--wp--preset--color--green-dim);
	background: var(--wp--preset--color--base-2);
	font-style: normal;
}
.xor-prose .xor-quote p { margin: 0; font-size: 14px; color: var(--wp--preset--color--muted); }

/* Timeline reuses the specs panel, but the right column is prose, not a value,
   so it must not be right-aligned or squeezed. */
.xor-specs .xor-timeline td:first-child { white-space: nowrap; width: 1%; color: var(--wp--preset--color--green); }
.xor-specs .xor-timeline td:last-child { text-align: left; line-height: 1.6; }

/* Label column in a facts panel: keep it on one line once there is room.

   Without this, a long value (the postal address on /contact/) makes the
   browser hand almost all the table width to the value column and wrap the
   label mid-word — "Telepho / ne". Left wrapping below 600px, where the panel
   is narrow enough that a one-line label would squeeze the value instead. */
@media (min-width: 600px) {
	.xor-specs table td:first-child { white-space: nowrap; }
}

/* News list: each post separated by a hairline, date above title. */
.xor-news__list > li {
	list-style: none;
	padding: var(--wp--preset--spacing--40) 0;
	border-bottom: 1px solid var(--wp--preset--color--line);
}
.xor-news__list { margin: var(--wp--preset--spacing--50) 0 0; padding: 0; }
.xor-news__list > li:first-child { border-top: 1px solid var(--wp--preset--color--line); }
.xor-news .xor-news__date { margin: 0 0 .3em; letter-spacing: .1em; text-transform: uppercase; }
.xor-news h2 { margin: 0 0 .5em; }
.xor-news h2 a { border-bottom: 0; }
.xor-news .wp-block-post-excerpt { margin: 0; max-width: 68ch; }
.xor-news .wp-block-post-excerpt p { margin: 0; }

/* --------------------------------------------------- products archive ---- */

/* Each card is core's auto-generated .wp-block-post wrapper around a query
   loop entry, styled to match .xor-card on the homepage ecosystem grid —
   same panel, same hover glow — so the two feel like one system even though
   this one is a live query and that one is hand-authored patterns. */
.xor-product-list .wp-block-post-template {
	gap: 18px;
}
.xor-product-list .wp-block-post {
	position: relative;
	display: flex;
	flex-direction: column;
	border: 1px solid var(--wp--preset--color--line);
	background: linear-gradient(170deg, var(--wp--preset--color--panel), var(--wp--preset--color--base-2));
	overflow: hidden;
	transition: border-color .16s, box-shadow .16s;
}
.xor-product-list .wp-block-post:hover {
	border-color: var(--wp--preset--color--green-dim);
	box-shadow: 0 0 42px -18px rgba(51, 255, 102, .42);
}
.xor-product-list .wp-block-post-featured-image {
	margin: 0;
	min-height: 150px;
	display: flex;
	align-items: center;
	justify-content: center;
	background: linear-gradient(170deg, var(--wp--preset--color--raise), #070c08);
	border-bottom: 1px solid var(--wp--preset--color--line);
	padding: 18px;
}
.xor-product-list .wp-block-post-featured-image img { max-height: 130px; width: auto; }
.xor-product-list .wp-block-post-title {
	margin: 16px 16px 0;
	font-size: 17px;
}
.xor-product-list .wp-block-post-title a { color: inherit; text-decoration: none; }
.xor-product-list .wp-block-post-excerpt {
	margin: 8px 16px 18px;
	font-size: 13px;
	line-height: 1.62;
	color: var(--wp--preset--color--muted);
}
.xor-product-list .wp-block-post-excerpt p { margin: 0; }
.xor-product-list .wp-block-post .wp-block-read-more {
	margin: auto 16px 16px;
	padding-top: 12px;
	border-top: 1px solid var(--wp--preset--color--line);
	font-size: 12px;
	letter-spacing: .04em;
	text-transform: uppercase;
	color: var(--wp--preset--color--green-dim);
	text-decoration: none;
}
.xor-product-list .wp-block-post .wp-block-read-more:hover { color: var(--wp--preset--color--green); }

/* Kicker above a page title: category and width, or "legacy". */
.xor-prose > .xor-kicker {
	margin-bottom: .3em;
	font-size: 11px;
	letter-spacing: .18em;
	text-transform: uppercase;
}

/* Release notes. 30 collapsed releases, so the summary row is the interface. */
.xor-prose > .xor-release {
	margin: 0;
	border-bottom: 1px solid var(--wp--preset--color--line);
}
.xor-prose > .xor-release:first-of-type { border-top: 1px solid var(--wp--preset--color--line); }
.xor-release > summary {
	padding: 11px 2px;
	cursor: pointer;
	font-size: 14px;
	letter-spacing: .06em;
	color: var(--wp--preset--color--green);
	list-style-position: outside;
}
.xor-release > summary:hover { color: var(--wp--preset--color--contrast); }
.xor-release[open] > summary { border-bottom: 1px solid var(--wp--preset--color--line); }
.xor-release > *:not(summary) { max-width: 80ch; }
.xor-release__sec {
	margin: var(--wp--preset--spacing--30) 0 .2em;
	font-size: 12px;
	letter-spacing: .14em;
	text-transform: uppercase;
	color: var(--wp--preset--color--amber);
}
.xor-release__list { margin: .2em 0 var(--wp--preset--spacing--30); font-size: 13.5px; line-height: 1.7; }
.xor-release__list li { margin-bottom: .1em; color: var(--wp--preset--color--muted); }
/* The leading verb — added / fixed / updated — is the thing you scan for. */
.xor-release__list strong { font-weight: 400; color: var(--wp--preset--color--green-dim); }

/* Dealer and tutorial tables reuse the specs panel but hold links, not values. */
.xor-specs .xor-dealers td:last-child,
.xor-specs .xor-tuts td:last-child { text-align: left; }
.xor-specs .xor-dealers td:first-child { white-space: normal; }
.xor-specs .xor-dealers td:last-child { color: var(--wp--preset--color--muted); text-align: right; }
.xor-specs .xor-tuts td:first-child { color: var(--wp--preset--color--green); vertical-align: top; }
.xor-tuts__note { color: var(--wp--preset--color--muted); font-size: 12.5px; }

/* Product photography panel, above the spec table. */
.xor-shot {
	display: flex;
	align-items: center;
	justify-content: center;
	min-height: 240px;
	margin-bottom: var(--wp--preset--spacing--40);
	padding: 18px;
	border: 1px solid var(--wp--preset--color--line);
	background: linear-gradient(170deg, var(--wp--preset--color--panel), var(--wp--preset--color--base-2));
}
.xor-shot figure { margin: 0; }
/* 330px rather than a larger cap because at the column widths this box
   actually gets, it is the point where the NerdSEQ photo — the widest in the
   range — becomes bound by the column's width instead, so it fills the box and
   stops there. Every product photo is height-bound in this box, so the cap is
   what sizes all of them; raising it further would only stretch the tall narrow
   ones (xor-xor is a 66x710 sliver) without making the wide ones any bigger. */
.xor-shot img { max-height: 330px; max-width: 100%; width: auto; display: block; }

/* Anchor for nerdseq-slideshow.js — same role .xor-stage__photo plays for
   nerdseq-screen.js, see that file. */
.xor-shot__photo { position: relative; line-height: 0; }
.xor-shot__photo figure { margin: 0; }

/* One photographed screen capture, cycled in over the panel photo. Positioned
   from the photo's own rendered box in JS, using the same --xor-scr-* percentages
   as .xor-ov; sized in CSS (not max-height:300px/width:auto like .xor-shot img
   above) because it has to fill that box exactly rather than keep its own ratio. */
/* Two classes, not one: .xor-shot img above also matches this element (it is
   an img inside .xor-shot) and its max-height:300px/width:auto would
   otherwise win on specificity and stop the inline pixel sizing from JS. */
.xor-shot .xor-shot__slide {
	position: absolute;
	/* Core's flow-layout block gap (:where(.is-layout-flow) > :not(:first-child))
	   gives any non-first child of .xor-shot__photo a margin-block-start — and
	   this element, appended by JS after the figure, is that second child. That
	   margin still applies to an absolutely positioned box, so left uncancelled
	   it silently pushed the whole overlay down from the top/left JS computes. */
	margin: 0;
	max-height: none;
	width: auto;
	height: auto;
	/* fill, not cover. The capture is exactly 3:2 while the screen as
	   photographed measures 1.473 (lens perspective), so cover scaled to the
	   height and overflowed the width by ~4px a side — which is the width of
	   the tracker's own grey frame in the capture, so the left and right frame
	   lines were the one thing it cropped away. The 1.8% horizontal squeeze
	   fill applies instead is imperceptible, and the capture is meant to map
	   onto the screen area exactly rather than keep its own ratio. */
	object-fit: fill;
	/* No border of our own: the captures carry the tracker's own grey frame,
	   and the photographed bezel frames the box outside that. A default
	   content-box border here would also add its width outside the JS-computed
	   size, expanding past the true screen edge onto the panel. */
	z-index: 2;
}

/* Photographs in the narrative pages. Lifted above the scanline overlay along
   with the rest of the photography — see the z-index block near the top. */
.xor-figure {
	position: relative;
	z-index: 10;
	/* Shrink to the photograph rather than stretching it: several of these are
	   phone shots from 2014 and only 600-730px wide, and width:100% in a
	   1140px column upscaled them into mush. */
	width: fit-content;
	max-width: 100%;
	margin: var(--wp--preset--spacing--40) 0;
	padding: 10px;
	border: 1px solid var(--wp--preset--color--line);
	background: var(--wp--preset--color--base-2);
}
.xor-figure img { display: block; max-width: 100%; height: auto; }
.xor-figure figcaption { max-width: 62ch; }

/* Size carries meaning: a supporting shot should not compete with the one the
   section is about. Capping HEIGHT rather than width means a portrait and a
   landscape photo of the same variant still line up on one row. */
.xor-figure img            { max-height: 300px; width: auto; }
.xor-figure--md img        { max-height: 380px; }
.xor-figure--lg img        { max-height: 480px; }

/* A row of photographs sharing a height. Widths differ, which is the point —
   equal-width columns left the tops and bottoms ragged, because these are phone
   shots from 2014 with no two aspect ratios alike. */
.xor-figures.wp-block-group {
	gap: var(--wp--preset--spacing--30);
	align-items: flex-start;
	margin: var(--wp--preset--spacing--40) 0;
}
.xor-figures .xor-figure { margin: 0; flex: 0 1 auto; min-width: 0; }
.xor-figures .xor-figure img { max-height: 240px; }
.xor-figures .xor-figure figcaption { max-width: 34ch; }

@media (max-width: 700px) {
	.xor-figure img,
	.xor-figure--md img,
	.xor-figure--lg img,
	.xor-figures .xor-figure img { max-height: none; width: 100%; }
	.xor-figures .xor-figure figcaption { max-width: 62ch; }
}
.xor-figure figcaption {
	margin: 10px 2px 2px;
	font-size: 12.5px;
	line-height: 1.6;
	color: var(--wp--preset--color--muted);
	text-align: left;
}

/* "legacy" marker beside a footer link. */
.xor-tag {
	margin-left: .45em;
	padding: 1px 5px;
	border: 1px solid var(--wp--preset--color--green-fade);
	font-size: 9.5px;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: var(--wp--preset--color--muted);
}

/* --------------------------------------------------------- social links ---- */

/* Nav and social share the right-hand end of the header. */
.xor-headright.wp-block-group {
	gap: 22px;
	align-items: center;
	flex-wrap: nowrap;
}

/* Inline SVG, not an icon font: a webfont from a CDN would put a third-party
   request on every page view and undo the no-consent-banner position that the
   privacy section now states. Line art rather than brand colour, so the icons
   sit with the CRT treatment instead of on top of it. */
.xor-social {
	display: flex;
	align-items: center;
	gap: 2px;
	padding-left: 20px;
	border-left: 1px solid var(--wp--preset--color--line);
}

.xor-social a {
	display: flex;
	align-items: center;
	justify-content: center;
	/* 44px keeps the tap target usable on a phone while the glyph stays small. */
	width: 34px;
	height: 34px;
	color: var(--wp--preset--color--green-dim);
	transition: color .16s;
}
.xor-social a:hover,
.xor-social a:focus-visible { color: var(--wp--preset--color--green); }

.xor-social svg {
	width: 18px;
	height: 18px;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.6;
	stroke-linecap: round;
	stroke-linejoin: round;
}

/* No external-link arrow here — it would collide with the glyph. */
.xor-social a.xor-external::after { content: none; }

@media (max-width: 600px) {
	.xor-headright.wp-block-group { gap: 10px; }
	.xor-social { padding-left: 10px; gap: 0; }
	.xor-social a { width: 30px; height: 30px; }
}

/* FX switch: turns off the scanline and rolling-bar overlays (xor-fx-off,
   defined up in the CRT section) without touching the screen animation.
   Styled to match .xor-social beside it — same divider, same dim/bright
   green states — rather than a generic form control. */
.xor-fx-toggle {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 0 0 0 20px;
	margin: 0;
	border: 0;
	border-left: 1px solid var(--wp--preset--color--line);
	background: none;
	cursor: pointer;
	font: inherit;
	font-size: 10.5px;
	letter-spacing: .14em;
	text-transform: uppercase;
	color: var(--wp--preset--color--green-dim);
	transition: color .16s;
}
.xor-fx-toggle:hover,
.xor-fx-toggle:focus-visible { color: var(--wp--preset--color--green); }

.xor-fx-toggle__track {
	position: relative;
	flex: none;
	box-sizing: border-box;
	width: 30px;
	height: 16px;
	border-radius: 8px;
	background: var(--wp--preset--color--base-2);
	border: 1px solid var(--wp--preset--color--line);
	transition: border-color .16s, box-shadow .16s;
}
.xor-fx-toggle__thumb {
	position: absolute;
	top: 50%;
	left: 1px;
	width: 12px;
	height: 12px;
	border-radius: 50%;
	background: var(--wp--preset--color--green-dim);
	/* translateY keeps the thumb centred regardless of the track's border;
	   translateX slides it across, so both live on the one transform. */
	transform: translateY(-50%);
	transition: transform .16s, background .16s;
}
/* Checked (FX on) is the default and needs no attribute selector, so the
   switch still reads correctly before fx-toggle.js has run. */
.xor-fx-toggle[aria-checked="true"] .xor-fx-toggle__track {
	border-color: var(--wp--preset--color--green-dim);
	box-shadow: 0 0 8px -2px rgba(51, 255, 102, .5);
}
.xor-fx-toggle[aria-checked="true"] .xor-fx-toggle__thumb {
	transform: translateY(-50%) translateX(14px);
	background: var(--wp--preset--color--green);
}

@media (max-width: 600px) {
	.xor-fx-toggle { padding-left: 10px; gap: 6px; }
	.xor-fx-toggle__label { display: none; }
}

/* Footer brand mark. Kept at the dim green rather than the bright one: the logo
   is a negative — the slab is filled and the letters are knocked out of it — so
   at full green it becomes a solid block that shouts down the whole footer. */
.xor-footer-logo {
	display: block;
	width: 190px;
	max-width: 100%;
	margin-bottom: 16px;
	color: var(--wp--preset--color--green-dim);
	transition: color .16s;
}
.xor-footer-logo:hover { color: var(--wp--preset--color--green); }
/* Inlined, not <img src="...svg">: an external SVG document cannot inherit
   currentColor, so as an <img> the logo rendered in its own near-black and was
   all but invisible on the dark footer. */
.xor-footer-logo svg { display: block; width: 100%; height: auto; }

/* Round mark beside the wordmark in the header. The mark is the checkerboard X
   in a circle — square, so unlike the wide logo it survives being small, which
   is also why the favicon is cut from this file rather than the other one. */
.xor-brand { display: inline-flex; align-items: center; gap: 11px; }
.xor-brand__mark {
	display: block;
	width: 26px;
	height: 26px;
	flex: none;
	/* Slightly dimmer than the wordmark so it reads as a mark, not a second
	   bright element competing with the name. */
	color: var(--wp--preset--color--green-dim);
	transition: color .16s;
}
.xor-brand:hover .xor-brand__mark { color: var(--wp--preset--color--green); }
.xor-brand__text { display: inline-block; }

@media (max-width: 600px) {
	.xor-brand { gap: 8px; }
	.xor-brand__mark { width: 22px; height: 22px; }
}

/* --- forms: contact and newsletter (plugins/xor-products/forms.php) ------ */
.xor-form { display: grid; gap: 1.1rem; }
.xor-form p { margin: 0; }
.xor-form__row { display: grid; grid-template-columns: 1fr 1fr; gap: 1.1rem; }
@media (max-width: 640px) { .xor-form__row { grid-template-columns: 1fr; } }
.xor-form__field { display: grid; gap: .4rem; }
.xor-form__label {
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: .04em;
	color: var(--wp--preset--color--contrast);
}
.xor-form__req { margin-left: .5em; color: var(--wp--preset--color--muted); }
.xor-form input[type="email"],
.xor-form input[type="text"],
.xor-form textarea {
	width: 100%;
	min-width: 0;
	box-sizing: border-box;
	padding: .7rem .8rem;
	font: inherit;
	color: var(--wp--preset--color--contrast);
	background: var(--wp--preset--color--base-2);
	border: 1px solid var(--wp--preset--color--line);
	border-radius: 0;
}
.xor-form textarea { min-height: 12rem; resize: vertical; }
.xor-form input:focus,
.xor-form textarea:focus {
	outline: 1px solid var(--wp--preset--color--green);
	outline-offset: 0;
	border-color: var(--wp--preset--color--green-dim);
}
.xor-form [aria-invalid="true"] { border-color: var(--wp--preset--color--amber); }
.xor-form__error { font-size: var(--wp--preset--font-size--small); color: var(--wp--preset--color--amber); }
.xor-form__ok { color: var(--wp--preset--color--green); }
.xor-form__note { font-size: var(--wp--preset--font-size--small); color: var(--wp--preset--color--muted); }
.xor-form__foot { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem 1.4rem; }
.xor-form--done { padding: 1.2rem; border: 1px solid var(--wp--preset--color--green-dim); background: var(--wp--preset--color--base-2); gap: .6rem; }

/* The honeypot: present for bots, absent for people and screen readers. */
.xor-form__trap { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }

/* Newsletter: field and button joined into one control. */
.xor-form--newsletter { gap: .5rem; }
.xor-form__inline { display: flex; }
.xor-form__inline input { flex: 1 1 auto; }
.xor-form__inline .wp-element-button { flex: none; margin-left: -1px; white-space: nowrap; }
.xor-form--page {
	max-width: 36rem;
	margin: var(--wp--preset--spacing--50) 0;
	padding: 1.2rem;
	border: 1px solid var(--wp--preset--color--line);
	background: var(--wp--preset--color--base-2);
}

/* Downloads: a row can have several links (.hex and .zip, the guide's two
   PDFs) — keep them on one line, spaced. A placeholder row has none. */
.xor-dl td:last-child a + a { margin-left: 1.1em; }
.xor-dl td:last-child:not(:has(a)) { color: var(--wp--preset--color--muted); }
/* Firmware intro: text left, the support panel beside it. */
.xor-dl-intro { margin-block: 1.1rem 1.5rem; gap: 2.5rem; align-items: flex-start; }
.xor-dl-intro .wp-block-column > p:first-child { margin-top: 0; }
.xor-support {
	border: 1px solid var(--wp--preset--color--line);
	background: var(--wp--preset--color--base-2);
	font-size: 13px;
	line-height: 1.7;
}
/* Title bar edge to edge, as on the tables; the rest inset. */
.xor-support > p { margin: 0; padding: 12px 16px 0; color: var(--wp--preset--color--muted); }
.xor-support > p.xor-specs__title { padding: 9px 14px; }
.xor-support > .wp-block-buttons { margin: 0; padding: 14px 16px 16px; }

/* Same column widths in every Downloads table, so the tables line up. */
.xor-dl table { table-layout: fixed; width: 100%; }
.xor-dl td:nth-child(1) { width: 20%; }
.xor-dl td:nth-child(2) { width: 6.5rem; }
.xor-dl td:nth-child(4) { width: 9.5rem; }

/* On a phone four columns do not fit: each row becomes name + version on one
   line, then the note, then the links. */
@media (max-width: 700px) {
	.xor-dl table, .xor-dl tbody { display: block; }
	.xor-dl tr {
		display: grid;
		grid-template-columns: 1fr auto;
		column-gap: 1rem;
		padding-block: .6rem;
	}
	.xor-dl tr + tr { border-top: 1px solid rgba(21, 58, 34, .55); }
	/* Matches the shape of the .xor-specs table td rule, or it loses. */
	.xor-specs .xor-dl table td { display: block; width: auto; border: 0; padding: 0 16px; }
	.xor-specs .xor-dl table td:nth-child(3) { grid-column: 1 / -1; }
	.xor-specs .xor-dl table td:last-child { grid-column: 1 / -1; text-align: left; padding-top: .35rem; }
}

/* START / STOP and the cursor keys over the hero photograph (nerdseq-screen.js). Invisible until
   used: the photographed key is the button. A press darkens it for a moment,
   like a key going down; keyboard focus gets the site's green ring. Above the
   photo (10) and below the sticky header (40) on the z-index ladder. */
.xor-key {
	position: absolute;
	z-index: 11;
	margin: 0;
	padding: 0;
	border: 0;
	border-radius: 14%;
	background: transparent;
	cursor: pointer;
	-webkit-tap-highlight-color: transparent;
	transition: background-color .12s, box-shadow .12s;
}
.xor-key:hover { box-shadow: inset 0 0 0 1px rgba(51, 255, 102, .35); }
.xor-key:active,
.xor-key.is-pressed { background: rgba(0, 0, 0, .35); box-shadow: inset 0 2px 6px rgba(0, 0, 0, .6); }
.xor-key:focus-visible { outline: 2px solid var(--wp--preset--color--green); outline-offset: 2px; }

