/*
 * Telescope View Simulator.
 *
 * Observatory cockpit: near-black, warm amber instrument light, monospace
 * numerics. The palette is not a style choice pulled from nowhere -- observers
 * work under red light because it preserves dark adaptation, so an interface
 * wrapped around a simulated eyepiece belongs in the same register. It also
 * means the chrome never competes with the view, which is the thing the page
 * exists to show.
 *
 * No url() anywhere: this file is served from the plugin directory, and
 * relative paths would break if anything ever relocated it.
 */

:root {
	--ts-void: #05060a;
	--ts-panel: rgba(14, 16, 22, 0.86);
	--ts-panel-solid: #0e1016;
	--ts-line: rgba(232, 215, 195, 0.13);
	--ts-line-strong: rgba(232, 215, 195, 0.26);
	--ts-text: #e8d7c3;
	--ts-dim: #93857a;
	--ts-faint: #8a7d71;       /* 4.8:1 on the panel; #6a6058 was 3.1:1 and fails AA */
	--ts-amber: #ff8a4c;
	--ts-amber-dim: #b45f33;
	--ts-warn: #ffb057;
	--ts-good: #7fd4a8;

	--ts-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas,
		"Liberation Mono", monospace;
	--ts-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
		"Helvetica Neue", Arial, sans-serif;

	/* ---------------------------------------------------------------- *
	 * Size
	 *
	 * One knob. Every base below is the literal this file was originally
	 * drawn at, so --ts-ui-scale: 1 reproduces the old layout exactly --
	 * which is what makes the knob safe to turn, because any difference
	 * you see afterwards is the scale and nothing else.
	 *
	 * Deliberately not scaled: hairline borders, --ts-radius, focus-ring
	 * widths and every reserved min-height further down. A 1px rule is not
	 * proportional to type, a 2px focus ring is a floor rather than a
	 * ratio, and the reservations are measurements.
	 * ---------------------------------------------------------------- */
	--ts-ui-scale: 1.12;

	--ts-fs-micro:    calc(9px    * var(--ts-ui-scale));  /* readout dt, target kind */
	--ts-fs-label:    calc(10px   * var(--ts-ui-scale));  /* field labels */
	--ts-fs-eyebrow:  calc(10.5px * var(--ts-ui-scale));  /* panel titles */
	--ts-fs-caption:  calc(11px   * var(--ts-ui-scale));  /* slider value, seg, spec */
	--ts-fs-ui:       calc(11.5px * var(--ts-ui-scale));  /* buttons, notes */
	--ts-fs-headline: calc(12px   * var(--ts-ui-scale));  /* dock headline */
	--ts-fs-body:     calc(12.5px * var(--ts-ui-scale));  /* selects, target names */
	--ts-fs-lead:     calc(13.5px * var(--ts-ui-scale));  /* verdict */
	--ts-fs-data:     calc(17px   * var(--ts-ui-scale));  /* readout values */

	--ts-sp-2xs: calc(2px  * var(--ts-ui-scale));
	--ts-sp-xs:  calc(5px  * var(--ts-ui-scale));
	--ts-sp-sm:  calc(8px  * var(--ts-ui-scale));
	--ts-sp-md:  calc(10px * var(--ts-ui-scale));
	--ts-sp-lg:  calc(14px * var(--ts-ui-scale));
	--ts-sp-xl:  calc(20px * var(--ts-ui-scale));

	/*
	 * Component geometry. Each of these had a second value derived from it
	 * by hand somewhere below -- the slider thumb's negative margin, the
	 * toggle's travel, the rail's top offset. Those derivations are now
	 * written as calc() so they cannot drift apart when one side moves.
	 */
	--ts-rail:         calc(268px * var(--ts-ui-scale));
	--ts-gutter:       calc(20px  * var(--ts-ui-scale));
	--ts-btn-py:       calc(9px   * var(--ts-ui-scale));
	--ts-btn-px:       calc(13px  * var(--ts-ui-scale));
	--ts-slider-track: 2px;
	--ts-slider-thumb: calc(13px  * var(--ts-ui-scale));
	--ts-toggle-w:     calc(30px  * var(--ts-ui-scale));
	--ts-toggle-h:     calc(16px  * var(--ts-ui-scale));
	--ts-toggle-knob:  calc(10px  * var(--ts-ui-scale));
	--ts-toggle-inset: 2px;
	--ts-logo-h:       calc(26px  * var(--ts-ui-scale));
	--ts-logo-py:      calc(7px   * var(--ts-ui-scale));
	--ts-masthead-py:  calc(16px  * var(--ts-ui-scale));

	/*
	 * The rails clear the masthead, and the masthead is as tall as the logo
	 * chip inside it. This was 74px: 26 logo + 14 chip padding + 2 chip
	 * border + 32 masthead padding, summed once by hand and then frozen.
	 * The logo is about to change size, so the sum has to be live.
	 */
	--ts-masthead-h: calc(
		var(--ts-logo-h) + (var(--ts-logo-py) * 2) + 2px +
		(var(--ts-masthead-py) * 2)
	);

	/*
	 * Two breakpoints, written down. A media query condition cannot take a
	 * custom property, so the queries below still spell the numbers out;
	 * these exist so that anything outside CSS reads them from here rather
	 * than keeping a second copy, which is what js/ui.js used to do.
	 *
	 * stack: below this the two rails and the dock stop being a fixed-height
	 * cockpit and become ordinary flow. It was 900px, which left a band from
	 * 901 to about 1100 where two 300px rails and their gutters ate 690px of
	 * a 1000px viewport: the dock was 310px wide, the verdict wrapped to
	 * eleven lines, and the reserved dock height came out at 597px inside an
	 * 800px window. The stacked layout is simply better there.
	 *
	 * compact: below this the action buttons use their short labels.
	 * Separate from stack because the stacked masthead is full-width, so a
	 * 1000px tablet has room for "Copy link to this view" and a phone does
	 * not.
	 */
	--ts-bp-stack: 1100px;
	--ts-bp-compact: 700px;

	--ts-radius: 3px;
}

* { box-sizing: border-box; }

html, body {
	margin: 0;
	padding: 0;
	background: var(--ts-void);
	color: var(--ts-text);
	font-family: var(--ts-sans);
	-webkit-font-smoothing: antialiased;
}

body { overflow-x: hidden; }

.ts-app {
	position: relative;
	/* svh keeps the stage from jumping when mobile browser chrome slides away. */
	height: 100svh;
	min-height: 520px;
	overflow: hidden;
	background: var(--ts-void);
}

/* ---------------------------------------------------------------- *
 * The eyepiece
 * ---------------------------------------------------------------- */

.ts-stage {
	position: absolute;
	inset: 0;
}

.ts-canvas {
	display: block;
	width: 100%;
	height: 100%;
	/* The canvas is not the Largest Contentful Paint element: the poster
	 * underneath it is, and it is 1KB. */
	background: var(--ts-void) center / cover no-repeat;
}

.ts-poster {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	opacity: 0.55;
	transition: opacity 600ms ease;
}

[data-ts-ready] .ts-poster { opacity: 0; pointer-events: none; }

/* ---------------------------------------------------------------- *
 * Masthead
 * ---------------------------------------------------------------- */

.ts-masthead {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: var(--ts-sp-lg);
	padding: var(--ts-masthead-py) var(--ts-gutter);
	pointer-events: none;
	z-index: 6;
}

.ts-masthead > * { pointer-events: auto; }

.ts-logo {
	display: inline-flex;
	align-items: center;
	gap: var(--ts-sp-md);
	text-decoration: none;
	color: var(--ts-text);
	padding: var(--ts-logo-py) calc(12px * var(--ts-ui-scale))
		var(--ts-logo-py) var(--ts-sp-sm);
	background: var(--ts-panel);
	border: 1px solid var(--ts-line);
	border-radius: var(--ts-radius);
	backdrop-filter: blur(9px);
	-webkit-backdrop-filter: blur(9px);
	transition: border-color 160ms ease;
}

.ts-logo:hover { border-color: var(--ts-line-strong); }
.ts-logo img { display: block; height: var(--ts-logo-h); width: auto; }

.ts-logo-back {
	font-family: var(--ts-mono);
	font-size: var(--ts-fs-caption);
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--ts-dim);
	white-space: nowrap;
}

.ts-actions { display: flex; gap: var(--ts-sp-sm); flex-wrap: wrap; justify-content: flex-end; }

/* ---------------------------------------------------------------- *
 * Buttons
 * ---------------------------------------------------------------- */

.ts-btn {
	font-family: var(--ts-mono);
	font-size: var(--ts-fs-ui);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ts-text);
	background: var(--ts-panel);
	border: 1px solid var(--ts-line);
	border-radius: var(--ts-radius);
	padding: var(--ts-btn-py) var(--ts-btn-px);
	cursor: pointer;
	backdrop-filter: blur(9px);
	-webkit-backdrop-filter: blur(9px);
	transition: border-color 150ms ease, color 150ms ease, background 150ms ease;
}

.ts-btn:hover { border-color: var(--ts-amber-dim); color: #fff; }
.ts-btn:focus-visible { outline: 2px solid var(--ts-amber); outline-offset: 2px; }

.ts-btn--primary {
	border-color: var(--ts-amber-dim);
	color: var(--ts-amber);
}


.ts-btn--ghost {
	background: none;
	border-color: transparent;
	color: var(--ts-dim);
	padding: var(--ts-sp-xs) 0;
	text-align: left;
}

.ts-btn--ghost:hover { color: var(--ts-amber); border-color: transparent; }

/* ---------------------------------------------------------------- *
 * Rails
 * ---------------------------------------------------------------- */

.ts-rail {
	position: absolute;
	top: var(--ts-masthead-h);
	bottom: var(--ts-gutter);
	width: var(--ts-rail);
	overflow-y: auto;
	overscroll-behavior: contain;
	scrollbar-width: thin;
	scrollbar-color: var(--ts-line-strong) transparent;
	z-index: 5;
	padding-bottom: 8px;
}

.ts-rail::-webkit-scrollbar { width: 6px; }
.ts-rail::-webkit-scrollbar-thumb {
	background: var(--ts-line-strong);
	border-radius: 3px;
}

.ts-rail--left { left: var(--ts-gutter); }
.ts-rail--right { right: var(--ts-gutter); }

.ts-panel {
	background: var(--ts-panel);
	border: 1px solid var(--ts-line);
	border-radius: var(--ts-radius);
	padding: var(--ts-sp-lg);
	margin-bottom: var(--ts-sp-md);
	backdrop-filter: blur(11px);
	-webkit-backdrop-filter: blur(11px);
}

.ts-panel-title {
	margin: 0 0 var(--ts-sp-lg);
	font-family: var(--ts-mono);
	font-size: var(--ts-fs-eyebrow);
	font-weight: 600;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--ts-amber);
}

/* ---------------------------------------------------------------- *
 * Target picker
 * ---------------------------------------------------------------- */

.ts-targets {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: var(--ts-sp-xs);
}

.ts-target {
	display: flex;
	flex-direction: column;
	gap: var(--ts-sp-2xs);
	text-align: left;
	padding: var(--ts-sp-sm) calc(9px * var(--ts-ui-scale));
	background: rgba(255, 255, 255, 0.022);
	border: 1px solid transparent;
	border-radius: var(--ts-radius);
	color: var(--ts-dim);
	cursor: pointer;
	font-family: var(--ts-sans);
	transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}

.ts-target:focus-visible { outline: 2px solid var(--ts-amber); outline-offset: 2px; }

.ts-target:hover { background: rgba(255, 138, 76, 0.08); color: var(--ts-text); }

.ts-target.is-active {
	background: rgba(255, 138, 76, 0.13);
	border-color: var(--ts-amber-dim);
	color: var(--ts-text);
}

.ts-target-name { font-size: var(--ts-fs-body); font-weight: 600; }

.ts-target-kind {
	font-family: var(--ts-mono);
	font-size: var(--ts-fs-micro);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ts-faint);
}

/* --ts-amber-dim is 4.2:1 here, which is under AA for text this size. The
 * full amber is 8.1:1 and is what the rest of the selected state already
 * uses. */
.ts-target.is-active .ts-target-kind { color: var(--ts-amber); }

/* ---------------------------------------------------------------- *
 * Fields
 * ---------------------------------------------------------------- */

.ts-field { display: block; margin-bottom: calc(13px * var(--ts-ui-scale)); }
.ts-field:last-child { margin-bottom: 0; }

.ts-field-label {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--ts-sp-sm);
	margin-bottom: calc(6px * var(--ts-ui-scale));
	font-family: var(--ts-mono);
	font-size: var(--ts-fs-label);
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--ts-dim);
}

.ts-slider-value {
	color: var(--ts-text);
	text-transform: none;
	letter-spacing: 0.02em;
	font-size: var(--ts-fs-caption);
}

.ts-select {
	width: 100%;
	font-family: var(--ts-sans);
	font-size: var(--ts-fs-body);
	color: var(--ts-text);
	background: rgba(0, 0, 0, 0.42);
	border: 1px solid var(--ts-line);
	border-radius: var(--ts-radius);
	padding: var(--ts-sp-sm) calc(9px * var(--ts-ui-scale));
	cursor: pointer;
	appearance: none;
	-webkit-appearance: none;
}

.ts-select:focus-visible { outline: 2px solid var(--ts-amber); outline-offset: 1px; }
.ts-select option { background: var(--ts-panel-solid); color: var(--ts-text); }

/*
 * Sliders styled as machined instrument dials: a thin track with a knurled
 * thumb, rather than the default control. The two vendor blocks cannot be
 * merged -- a browser drops the whole rule if it does not recognise every
 * selector in it.
 */
.ts-slider {
	width: 100%;
	height: calc(var(--ts-slider-thumb) + var(--ts-sp-sm));
	appearance: none;
	-webkit-appearance: none;
	background: none;
	cursor: pointer;
}

.ts-slider::-webkit-slider-runnable-track {
	height: var(--ts-slider-track);
	background: var(--ts-line-strong);
	border-radius: var(--ts-slider-track);
}

.ts-slider::-moz-range-track {
	height: var(--ts-slider-track);
	background: var(--ts-line-strong);
	border-radius: var(--ts-slider-track);
}

.ts-slider::-webkit-slider-thumb {
	appearance: none;
	-webkit-appearance: none;
	width: var(--ts-slider-thumb);
	height: var(--ts-slider-thumb);
	/* Centre the thumb on the track. The old -5.5px was this sum, frozen. */
	margin-top: calc((var(--ts-slider-track) - var(--ts-slider-thumb)) / 2);
	border-radius: 50%;
	background: var(--ts-amber);
	border: 2px solid #14100c;
	box-shadow: 0 0 0 1px var(--ts-amber-dim), 0 0 12px rgba(255, 138, 76, 0.45);
}

.ts-slider::-moz-range-thumb {
	width: var(--ts-slider-thumb);
	height: var(--ts-slider-thumb);
	border-radius: 50%;
	background: var(--ts-amber);
	border: 2px solid #14100c;
	box-shadow: 0 0 0 1px var(--ts-amber-dim), 0 0 12px rgba(255, 138, 76, 0.45);
}

.ts-slider:focus-visible { outline: 2px solid var(--ts-amber); outline-offset: 4px; }

/* Segmented control, for the Barlow. */
.ts-seg { display: flex; gap: calc(4px * var(--ts-ui-scale)); }

.ts-seg-btn {
	flex: 1;
	font-family: var(--ts-mono);
	font-size: var(--ts-fs-caption);
	color: var(--ts-dim);
	background: rgba(0, 0, 0, 0.42);
	border: 1px solid var(--ts-line);
	border-radius: var(--ts-radius);
	padding: calc(7px * var(--ts-ui-scale)) calc(4px * var(--ts-ui-scale));
	cursor: pointer;
	transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}

.ts-seg-btn:focus-visible { outline: 2px solid var(--ts-amber); outline-offset: 2px; }

.ts-seg-btn:hover { color: var(--ts-text); }

.ts-seg-btn.is-active {
	background: rgba(255, 138, 76, 0.16);
	border-color: var(--ts-amber-dim);
	color: var(--ts-amber);
}

/* Toggle switch. */
.ts-toggle {
	display: flex;
	align-items: center;
	gap: calc(9px * var(--ts-ui-scale));
	margin-bottom: var(--ts-sp-md);
	cursor: pointer;
	user-select: none;
}

.ts-toggle input {
	position: absolute;
	opacity: 0;
	width: 0;
	height: 0;
}

.ts-toggle-track {
	position: relative;
	flex: 0 0 auto;
	width: var(--ts-toggle-w);
	height: var(--ts-toggle-h);
	border-radius: 999px;
	background: rgba(0, 0, 0, 0.55);
	border: 1px solid var(--ts-line-strong);
	transition: background 160ms ease, border-color 160ms ease;
}

.ts-toggle-track::after {
	content: "";
	position: absolute;
	top: var(--ts-toggle-inset);
	left: var(--ts-toggle-inset);
	width: var(--ts-toggle-knob);
	height: var(--ts-toggle-knob);
	border-radius: 50%;
	background: var(--ts-faint);
	transition: transform 160ms ease, background 160ms ease;
}

.ts-toggle input:checked + .ts-toggle-track {
	background: rgba(255, 138, 76, 0.22);
	border-color: var(--ts-amber-dim);
}

.ts-toggle input:checked + .ts-toggle-track::after {
	/* The track's padding box, less both insets, less the knob. The old
	 * translateX(14px) was that arithmetic, done once and frozen. */
	transform: translateX(calc(
		var(--ts-toggle-w) - 2px - (var(--ts-toggle-inset) * 2) - var(--ts-toggle-knob)
	));
	background: var(--ts-amber);
}

.ts-toggle input:focus-visible + .ts-toggle-track {
	outline: 2px solid var(--ts-amber);
	outline-offset: 2px;
}

.ts-toggle-label { font-size: var(--ts-fs-body); color: var(--ts-text); }

.ts-note {
	margin: calc(6px * var(--ts-ui-scale)) 0 0;
	font-size: var(--ts-fs-ui);
	line-height: 1.5;
	color: var(--ts-dim);
}

.ts-note--warn { color: var(--ts-warn); }
.ts-spec {
	margin: var(--ts-sp-sm) 0 0;
	font-family: var(--ts-mono);
	font-size: var(--ts-fs-caption);
	color: var(--ts-dim);
}

/* ---------------------------------------------------------------- *
 * Dock
 * ---------------------------------------------------------------- */

.ts-dock {
	position: absolute;
	/* Clear of both rails, or the readout bar sits on top of the controls. */
	left: calc(var(--ts-rail) + (var(--ts-gutter) * 2));
	right: calc(var(--ts-rail) + (var(--ts-gutter) * 2));
	bottom: 0;
	z-index: 5;
	/* The scroll cue is absolutely positioned against the bottom of the
	 * stage, so the dock has to leave it room or the verdict's last line
	 * runs into it at the widths where the verdict is tallest. */
	padding: var(--ts-sp-lg) var(--ts-gutter) calc(var(--ts-sp-xl) + var(--ts-sp-lg));
	background: linear-gradient(to top, rgba(5, 6, 10, 0.97) 40%, rgba(5, 6, 10, 0));
	pointer-events: none;
}

.ts-dock > * { pointer-events: auto; }

/*
 * The dock is filled by JavaScript, so every one of these boxes starts empty
 * and then grows. Without reserved space that lands as cumulative layout shift
 * -- measured at 0.133 on desktop before these min-heights, which is above the
 * threshold Google treats as good and would drag down the article underneath.
 */
.ts-headline {
	min-height: 15px;
	margin: 0 0 var(--ts-sp-sm);
	font-family: var(--ts-mono);
	font-size: var(--ts-fs-headline);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--ts-amber);
}

.ts-readouts {
	min-height: 114px;
	display: flex;
	flex-wrap: wrap;
	gap: 0;
	margin: 0 0 var(--ts-sp-md);
	border: 1px solid var(--ts-line);
	border-radius: var(--ts-radius);
	background: var(--ts-panel);
	backdrop-filter: blur(11px);
	-webkit-backdrop-filter: blur(11px);
	overflow: hidden;
}

.ts-readout {
	/*
	 * Six readouts across the dock, on one row. The basis is what decides
	 * that: flex-wrap moves an item to the next line rather than shrinking
	 * it below its basis, so 120px was enough when the rails were 268 wide
	 * and one row too many once they grew.
	 */
	flex: 1 1 calc(100px * var(--ts-ui-scale));
	padding: var(--ts-btn-py) var(--ts-btn-px);
	border-right: 1px solid var(--ts-line);
}

.ts-readout:last-child { border-right: none; }

.ts-readout dt {
	font-family: var(--ts-mono);
	font-size: var(--ts-fs-micro);
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--ts-faint);
	margin-bottom: calc(3px * var(--ts-ui-scale));
}

.ts-readout dd {
	margin: 0;
	font-family: var(--ts-mono);
	font-size: var(--ts-fs-data);
	font-variant-numeric: tabular-nums;
	color: var(--ts-text);
}

.ts-verdict {
	min-height: 163px;
	max-width: 760px;
	font-size: var(--ts-fs-lead);
	line-height: 1.55;
	color: var(--ts-dim);
}

.ts-verdict p { margin: 0 0 var(--ts-sp-xs); }
.ts-verdict p:last-child { margin-bottom: 0; }
.ts-verdict-lead { color: var(--ts-text); font-weight: 600; }

/*
 * The one instrument warning that survived the "Worth knowing" panel. It
 * belongs against the view it describes rather than in a list of six.
 */
.ts-verdict-warn {
	color: var(--ts-warn);
	margin-top: var(--ts-sp-sm);
	padding-left: var(--ts-sp-md);
	border-left: 2px solid var(--ts-warn);
}

/* ---------------------------------------------------------------- *
 * Compare divider
 * ---------------------------------------------------------------- */

.ts-divider {
	position: absolute;
	top: 0;
	bottom: 0;
	left: 50%;
	width: 2px;
	margin-left: -1px;
	background: var(--ts-amber);
	box-shadow: 0 0 18px rgba(255, 138, 76, 0.55);
	cursor: ew-resize;
	z-index: 4;
	display: none;
}

[data-ts-compare] .ts-divider { display: block; }

.ts-divider:focus-visible { outline: 2px solid var(--ts-amber); outline-offset: 4px; }

/*
 * The drag handle sits low, not centred. Centred, it lands exactly on the
 * object being compared and hides the thing the comparison is about -- an
 * opaque disc straight over Saturn.
 */
.ts-divider::after {
	content: "";
	position: absolute;
	top: 82%;
	left: 50%;
	width: calc(26px * var(--ts-ui-scale));
	height: calc(26px * var(--ts-ui-scale));
	margin: calc(-13px * var(--ts-ui-scale)) 0 0 calc(-13px * var(--ts-ui-scale));
	border: 1px solid var(--ts-amber);
	border-radius: 50%;
	background: rgba(5, 6, 10, 0.88);
}

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

[data-ts-buy] { min-height: 160px; }

.ts-buy-row {
	display: flex;
	flex-direction: column;
	gap: var(--ts-sp-2xs);
	padding: var(--ts-sp-sm) 0;
	border-bottom: 1px solid var(--ts-line);
}

.ts-buy-label {
	font-family: var(--ts-mono);
	font-size: var(--ts-fs-micro);
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--ts-faint);
}

.ts-buy-link { font-size: var(--ts-fs-body); color: var(--ts-amber); text-decoration: none; }
.ts-buy-link:hover { text-decoration: underline; }

.ts-buy-guide {
	display: inline-block;
	margin-top: var(--ts-sp-md);
	font-family: var(--ts-mono);
	font-size: var(--ts-fs-eyebrow);
	letter-spacing: 0.07em;
	text-transform: uppercase;
	color: var(--ts-dim);
	text-decoration: none;
	border-bottom: 1px solid var(--ts-line-strong);
}

.ts-buy-guide:hover { color: var(--ts-amber); border-color: var(--ts-amber-dim); }

.ts-disclosure {
	margin: var(--ts-sp-md) 0 0;
	font-size: var(--ts-fs-eyebrow);
	line-height: 1.5;
	color: var(--ts-faint);
}

/* ---------------------------------------------------------------- *
 * Scroll cue and fallback
 * ---------------------------------------------------------------- */

.ts-scroll-cue {
	position: absolute;
	left: 50%;
	bottom: 4px;
	transform: translateX(-50%);
	font-family: var(--ts-mono);
	font-size: calc(9.5px * var(--ts-ui-scale));
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--ts-dim);
	text-decoration: none;
	z-index: 6;
	padding: var(--ts-sp-xs) var(--ts-sp-md);
	transition: color 150ms ease;
}

/*
 * It is a link now, not a caption. The article below is what this page is
 * meant to rank on, and the only route to it was a line of grey text that
 * did nothing when clicked and was hidden outright on mobile.
 */
.ts-scroll-cue:hover { color: var(--ts-amber); }
.ts-scroll-cue:focus-visible { outline: 2px solid var(--ts-amber); outline-offset: 2px; }

.ts-fallback {
	display: none;
	position: absolute;
	left: 50%;
	top: 50%;
	transform: translate(-50%, -50%);
	max-width: 420px;
	padding: calc(18px * var(--ts-ui-scale)) var(--ts-gutter);
	text-align: center;
	font-size: var(--ts-fs-lead);
	line-height: 1.6;
	color: var(--ts-dim);
	background: var(--ts-panel);
	border: 1px solid var(--ts-line);
	border-radius: var(--ts-radius);
}

[data-ts-fallback] .ts-fallback { display: block; }

/*
 * Without WebGL there is no canvas to photograph and nothing to save, but
 * both buttons stayed on screen: Save was bound only when a renderer
 * existed, so it was a visible control that did nothing at all, and the
 * photo toggle flipped a flag with no frame behind it.
 */
[data-ts-fallback] [data-ts-photo],
[data-ts-fallback] [data-ts-save] { display: none; }

/* ---------------------------------------------------------------- *
 * Action labels
 *
 * Both lengths ship in the markup and CSS picks one, so the breakpoint
 * lives here rather than in a matchMedia() call that js/ui.js read once
 * at init and never revisited.
 * ---------------------------------------------------------------- */

.ts-btn-short { display: none; }

/* ---------------------------------------------------------------- *
 * Photo mode
 *
 * data-ts-mode was already being set on the mount and read by nothing.
 * The button label says which way the toggle will go; this says which
 * side you are currently looking at, which is not the same question.
 * ---------------------------------------------------------------- */

[data-ts-mode="photo"] .ts-stage::after {
	content: "Long exposure";
	position: absolute;
	top: var(--ts-masthead-h);
	left: 50%;
	transform: translateX(-50%);
	font-family: var(--ts-mono);
	font-size: var(--ts-fs-micro);
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--ts-amber);
	background: rgba(5, 6, 10, 0.72);
	border: 1px solid var(--ts-amber-dim);
	border-radius: var(--ts-radius);
	padding: var(--ts-sp-2xs) var(--ts-sp-sm);
	pointer-events: none;
	z-index: 6;
}

[data-ts-mode="photo"] .ts-btn--primary {
	background: var(--ts-amber);
	color: #14100c;
	border-color: var(--ts-amber);
}

/* ---------------------------------------------------------------- *
 * The article beneath
 * ---------------------------------------------------------------- */

.ts-article {
	max-width: 760px;
	margin: 0 auto;
	padding: 72px 20px 96px;
	font-size: 17px;
	line-height: 1.72;
	color: #c9bcaf;
}

.ts-article h2 {
	margin: 52px 0 16px;
	font-size: 27px;
	line-height: 1.24;
	color: var(--ts-text);
	letter-spacing: -0.012em;
}

.ts-article h3 {
	margin: 34px 0 10px;
	font-size: 19px;
	color: var(--ts-text);
}

.ts-article h2:first-child { margin-top: 0; }
.ts-article p { margin: 0 0 18px; }
.ts-article strong { color: var(--ts-text); }
.ts-article a { color: var(--ts-amber); text-decoration: none; border-bottom: 1px solid var(--ts-amber-dim); }
.ts-article a:hover { color: #fff; }
.ts-article ul, .ts-article ol { margin: 0 0 18px; padding-left: 22px; }
.ts-article li { margin-bottom: 7px; }

.ts-article-lede {
	font-size: 19.5px;
	line-height: 1.62;
	color: var(--ts-text);
}

.ts-table-wrap { overflow-x: auto; margin: 0 0 24px; -webkit-overflow-scrolling: touch; }

.ts-article table {
	width: 100%;
	min-width: 520px;
	border-collapse: collapse;
	font-size: 14px;
	font-variant-numeric: tabular-nums;
}

.ts-article th, .ts-article td {
	padding: 9px 12px;
	text-align: left;
	border-bottom: 1px solid var(--ts-line);
}

.ts-article th {
	font-family: var(--ts-mono);
	font-size: 10px;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--ts-amber);
	white-space: nowrap;
}

.ts-article td:first-child { color: var(--ts-text); }

.ts-callout {
	margin: 0 0 24px;
	padding: 16px 18px;
	border-left: 2px solid var(--ts-amber-dim);
	background: rgba(255, 138, 76, 0.045);
	font-size: 15.5px;
}

.ts-callout p:last-child { margin-bottom: 0; }

.ts-faq-q { margin: 28px 0 8px; font-size: 17.5px; color: var(--ts-text); font-weight: 600; }

.ts-credits {
	max-width: 760px;
	margin: 0 auto;
	padding: 0 20px 64px;
	font-size: 12.5px;
	line-height: 1.65;
	color: var(--ts-faint);
}

.ts-credits a { color: var(--ts-dim); }

/* ---------------------------------------------------------------- *
 * Reserved space on desktop
 * ---------------------------------------------------------------- *
 *
 * Every panel here is built by JavaScript, so the browser paints an empty
 * shell first and then fills it. Measured filled heights are reserved up front
 * so nothing moves when that happens.
 *
 * This was worth chasing properly rather than guessing. The first attempt put
 * min-heights on the dock's children and changed nothing, because what the
 * browser was reporting was the dock itself: anchored to the bottom, growing
 * from 157 to 265 pixels moved its top edge up by more than a hundred pixels.
 * Reserving the final height is what actually fixes it.
 *
 * The values are the largest measured across all ten targets, in the state
 * the page can actually load in -- a shift after the visitor touches a control
 * is not scored, so reserving for every combination they could later reach
 * would only cover the eyepiece view in dead scrim.
 *
 * Re-derive with tools/measure-reservations.js, which neutralises these rules
 * from the harness and sweeps every target at every breakpoint. Do not adjust
 * them by hand and do not scale them: they are measurements.
 *
 * Two desktop bands because the dock is what is left over after both rails,
 * so it narrows as the viewport does and the verdict inside it wraps further.
 * At 1280 the readouts need a second row and the verdict runs three lines
 * longer than at 1440.
 */

@media (min-width: 1101px) {
	.ts-readouts { min-height: 120px; }
	.ts-dock { min-height: 418px; }
	.ts-verdict { min-height: 210px; }
	[data-ts-buy] { min-height: 195px; }
	[data-ts-controls-left] { min-height: 795px; }
	[data-ts-controls-sky] { min-height: 217px; }
	[data-ts-controls-view] { min-height: 594px; }
}

@media (min-width: 1441px) {
	.ts-readouts { min-height: 61px; }
	.ts-dock { min-height: 313px; }
	.ts-verdict { min-height: 163px; }
}

/* ---------------------------------------------------------------- *
 * Mobile
 * ---------------------------------------------------------------- */

@media (max-width: 1100px) {
	/*
	 * Source order is written for the desktop cockpit, where everything is
	 * absolutely positioned and order does not matter. In flow it does, and
	 * it put the readouts and the verdict below every control -- so on a
	 * phone you looked at Saturn, then scrolled past four panels of settings
	 * to find out what you were looking at. The answer belongs against the
	 * picture.
	 */
	.ts-app {
		height: auto;
		min-height: 0;
		overflow: visible;
		display: flex;
		flex-direction: column;
	}

	.ts-stage      { order: 1; }
	.ts-masthead   { order: 2; }
	.ts-dock       { order: 3; }
	.ts-rail--left { order: 4; }
	.ts-rail--right { order: 5; }
	.ts-scroll-cue { order: 6; }

	.ts-stage { position: relative; inset: auto; height: 62svh; min-height: 340px; }

	.ts-rail {
		position: static;
		width: auto;
		top: auto;
		bottom: auto;
		max-height: none;
		overflow: visible;
		padding: 0 14px;
	}

	.ts-rail--left { margin-top: 12px; }
	.ts-rail--right { margin-bottom: 20px; }

	.ts-dock {
		position: static;
		left: auto;
		right: auto;
		background: none;
		padding: 12px 14px 0;
	}

	.ts-readouts { flex-wrap: wrap; min-height: 114px; }
	.ts-verdict { min-height: 163px; }
	[data-ts-buy] { min-height: 160px; }
	[data-ts-controls-left] { min-height: 653px; }
	[data-ts-controls-sky] { min-height: 198px; }
	[data-ts-controls-view] { min-height: 516px; }
	.ts-readout { flex: 1 1 33%; border-bottom: 1px solid var(--ts-line); }
	.ts-readout dd { font-size: calc(15px * var(--ts-ui-scale)); }

	/*
	 * On a phone the masthead joins the flow instead of floating over the view.
	 * Absolutely positioned, the three action buttons wrapped into a stack that
	 * covered a third of the eyepiece.
	 */
	.ts-masthead {
		position: static;
		flex-wrap: wrap;
		gap: var(--ts-sp-md);
		padding: calc(12px * var(--ts-ui-scale)) var(--ts-sp-lg);
	}

	.ts-actions {
		width: 100%;
		justify-content: flex-start;
		flex-wrap: nowrap;
	}

	/*
	 * min-width: 0 is load-bearing. Flex items default to min-width: auto,
	 * so a label wider than its share cannot shrink and pushes the document
	 * sideways instead. The ellipsis is the visible failure mode if a label
	 * ever gets long again.
	 */
	.ts-actions .ts-btn {
		flex: 1 1 0;
		min-width: 0;
		text-align: center;
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
	}

	.ts-logo-back { display: none; }

	/*
	 * The cue stays. It used to be hidden here, which meant the handoff to
	 * the article disappeared on most of the traffic. In static flow it is
	 * a normal link under the tool rather than an overlay.
	 */
	.ts-scroll-cue {
		position: static;
		display: block;
		transform: none;
		text-align: center;
		margin: var(--ts-sp-md) 0 var(--ts-sp-xl);
	}

	/* Duplicate height on .ts-stage: this one wins over the 62svh above. */
	.ts-stage { height: 56svh; }

	.ts-targets { grid-template-columns: repeat(3, 1fr); }

	.ts-article { padding: 40px 18px 72px; font-size: 16.5px; }
	.ts-article h2 { font-size: 23px; margin-top: 40px; }
}

/* ---------------------------------------------------------------- *
 * Compact: phones
 * ---------------------------------------------------------------- */

@media (max-width: 700px) {
	/*
	 * Scaled a little less than the desktop cockpit. Here the layout is
	 * static flow, so vertical space is cheap and horizontal space is the
	 * scarce thing, which is the exact inverse of the fixed-height layout.
	 */
	:root { --ts-ui-scale: 1.08; }

	.ts-btn-long { display: none; }
	.ts-btn-short { display: inline; }

	/*
	 * Two per row, always. With a px basis the number of columns depends on
	 * the viewport, so the readouts took two rows at 390 and three at 360 --
	 * and a reservation that has to cover both stretches the cells at the
	 * width where it is not needed. A percentage basis makes the row count
	 * the same everywhere, so the reserved height is exact rather than a
	 * worst case. Two columns also gives "MAGNIFICATION" room to sit on one
	 * line, which three does not.
	 */
	.ts-readout { flex: 1 1 50%; }

	/* The dock itself needs no reservation in flow: its three children add
	 * up to it. */
	.ts-readouts { min-height: 164px; }
	.ts-verdict { min-height: 248px; }
	[data-ts-buy] { min-height: 169px; }
	[data-ts-controls-left] { min-height: 697px; }
	[data-ts-controls-sky] { min-height: 210px; }
	[data-ts-controls-view] { min-height: 579px; }
}

@media (max-width: 420px) {
	.ts-targets { grid-template-columns: repeat(2, 1fr); }
	.ts-actions { gap: calc(6px * var(--ts-ui-scale)); }
	.ts-btn {
		padding: calc(8px * var(--ts-ui-scale)) calc(10px * var(--ts-ui-scale));
		font-size: calc(10.5px * var(--ts-ui-scale));
	}
	/* Three buttons across 390px with nowrap is the tightest row in the
	 * layout. Tracking is the cheapest 6% of width available. */
	.ts-actions .ts-btn { letter-spacing: 0; }
}

/* ---------------------------------------------------------------- *
 * Reduced motion
 * ---------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
	*, *::before, *::after {
		animation-duration: 0.001ms !important;
		transition-duration: 0.001ms !important;
	}
}
