/*
 * Nivoli Games front end.
 *
 * Hand-written, no build step, no framework, no external font and no request to
 * anything but this file. Loaded only on the plugin's own templates (see
 * Template\Assets), so the rest of the site is untouched.
 *
 * Everything below is scoped under `.ng`, the wrapper each template opens with.
 * The one exception is the fixed reading-progress bar, which cannot live inside
 * a scrolled container, and the two `html.ng-js` hooks the script sets.
 *
 * The custom properties are the whole design system. Retune the six at the top
 * and the rest follows: every colour, rule, panel and shadow is derived from
 * them with color-mix, and each derivation ships a flat hex fallback first for
 * browsers without it.
 */

:root {
	/* ---- the six ---------------------------------------------------------- */
	--ng-bg: #262320;        /* page background, inherited from the site       */
	--ng-fg: #ddd6c6;        /* body text: 10.8:1 on the background           */
	--ng-accent: #e8b45e;    /* links, focus rings, counts: 8.28:1            */
	--ng-muted: #a89f8e;     /* secondary text: 5.96:1, AA at any size        */
	--ng-rule: #423d36;      /* decorative hairlines: row dividers, underlines */
	--ng-control-border: var(--ng-muted); /* control edges: az links, selects, page numbers, expander summaries. --ng-rule is only 1.45:1 against the background, a divider you are not meant to notice, not the 3:1 floor a control's own edge needs; --ng-muted is the same colour already verified at 5.96:1 for text, comfortably clear of that floor as a border */
	--ng-measure: 70ch;      /* prose column width                            */

	/* ---- derived ---------------------------------------------------------- */
	--ng-panel: #2f2b27;
	--ng-panel: color-mix(in srgb, var(--ng-fg) 7%, var(--ng-bg));
	--ng-panel-2: #35302b;
	--ng-panel-2: color-mix(in srgb, var(--ng-fg) 12%, var(--ng-bg));
	--ng-code-bg: #1e1b19;
	--ng-code-bg: color-mix(in srgb, #000 22%, var(--ng-bg));
	--ng-accent-ink: #1a1816;
	--ng-accent-ink: color-mix(in srgb, #000 30%, var(--ng-bg));
	--ng-accent-soft: #3a3227;
	--ng-accent-soft: color-mix(in srgb, var(--ng-accent) 14%, var(--ng-bg));

	/* ---- type and rhythm -------------------------------------------------- */
	--ng-font: Inter, "Inter var", "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
	--ng-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "DejaVu Sans Mono", monospace;
	/* The plugin's own root. `rem` is relative to the document root, and a
	   theme is free to set that to anything: newsgamer ships the old 62.5%
	   trick, `html { font-size: 10px }`, which rendered every size below at
	   62% of its intended value. Sizes are derived from this token instead,
	   so no theme can rescale the plugin's templates again. */
	--ng-root: 16px;
	--ng-size: calc(var(--ng-root) * 1.1875);   /* 19px */
	--ng-leading: 1.7;
	--ng-gap: calc(var(--ng-root) * 1.25);
	--ng-radius: 4px;
	/* The widest a game cover is ever rendered, and so the width of the card
	   that frames one. It matches the width of the guide page's own side
	   column, so the cap never binds there and only takes effect where that
	   column is not a column: below 960px the aside stacks above the prose at
	   the full width of the shell, and without this a cover was rendered at
	   whatever that came to. On a 900px window a 795x1060 cover was being
	   blown up to roughly 870x1160, a page and a half of upscaled artwork
	   before the first line of the guide, which is the "huge zoomed crop" this
	   token exists to prevent. */
	--ng-cover-max: calc(var(--ng-root) * 15);
}

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

.ng {
	box-sizing: border-box;
	max-width: calc(var(--ng-root) * 74);
	margin: 0 auto;
	padding: calc(var(--ng-root) * 1.5) clamp(calc(var(--ng-root) * 1), 4vw, calc(var(--ng-root) * 2)) calc(var(--ng-root) * 4);
	color: var(--ng-fg);
	font-family: var(--ng-font);
	font-size: var(--ng-size);
	line-height: var(--ng-leading);
	/* Nothing inside a plugin template may widen the page. Long unbroken
	   strings (1990s URLs, file paths) wrap instead of pushing the layout. */
	overflow-wrap: break-word;
}

.ng *,
.ng *::before,
.ng *::after {
	box-sizing: inherit;
}

.ng img {
	max-width: 100%;
	height: auto;
}

.ng a {
	color: var(--ng-accent);
	text-decoration-thickness: 1px;
	text-underline-offset: 0.15em;
}

.ng a:hover {
	text-decoration-thickness: 2px;
}

/* One focus treatment for everything interactive, including the scrollable
   `pre` containers, which are focusable so a keyboard can scroll them. */
.ng a:focus,
.ng button:focus,
.ng select:focus,
.ng summary:focus,
.ng [tabindex]:focus {
	outline: 2px solid var(--ng-accent);
	outline-offset: 2px;
	border-radius: 2px;
}

.ng a:focus:not(:focus-visible),
.ng button:focus:not(:focus-visible),
.ng select:focus:not(:focus-visible),
.ng summary:focus:not(:focus-visible) {
	outline: none;
}

.ng-sr {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
	border: 0;
}

/* ----------------------------------------------------------- typography ---- */

.ng-title {
	margin: 0 0 calc(var(--ng-root) * 0.5);
	font-size: clamp(calc(var(--ng-root) * 1.75), 4vw, calc(var(--ng-root) * 2.5));
	line-height: 1.15;
	letter-spacing: -0.01em;
}

.ng-eyebrow {
	margin: 0 0 calc(var(--ng-root) * 0.25);
	color: var(--ng-muted);
	font-size: calc(var(--ng-root) * 0.8);
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

.ng-section {
	margin: calc(var(--ng-root) * 2.5) 0 0;
	/* A wide table or map inside a section must not stretch the section. */
	min-width: 0;
}

.ng-section-heading {
	display: flex;
	align-items: baseline;
	gap: calc(var(--ng-root) * 0.5);
	margin: 0 0 calc(var(--ng-root) * 0.75);
	padding-bottom: calc(var(--ng-root) * 0.4);
	border-bottom: 1px solid var(--ng-rule);
	font-size: calc(var(--ng-root) * 1.25);
	line-height: 1.3;
}

.ng-empty,
.ng-notice {
	margin: calc(var(--ng-root) * 0.5) 0;
	color: var(--ng-muted);
}

.ng-backlink {
	margin: calc(var(--ng-root) * 3) 0 0;
	padding-top: calc(var(--ng-root) * 1);
	border-top: 1px solid var(--ng-rule);
	font-size: calc(var(--ng-root) * 0.95);
}

/* ---------------------------------------------------------------- prose ---- */

.ng-prose {
	max-width: var(--ng-measure);
	min-width: 0;
}

.ng-prose > * + * {
	margin-top: 1.15em;
}

.ng-prose p,
.ng-prose li {
	margin-top: 0;
	margin-bottom: 0;
}

.ng-prose h2,
.ng-prose h3,
.ng-prose h4 {
	margin: 2.2em 0 0.6em;
	line-height: 1.25;
	/* Anchor targets sit below the sticky game bar rather than under it. */
	scroll-margin-top: calc(var(--ng-root) * 5);
}

.ng-prose h2 {
	font-size: 1.4em;
}

.ng-prose h3 {
	font-size: 1.15em;
}

.ng-prose h4 {
	font-size: 1em;
	color: var(--ng-muted);
	text-transform: uppercase;
	letter-spacing: 0.06em;
}

.ng-prose ul,
.ng-prose ol {
	padding-left: 1.4em;
}

.ng-prose li + li {
	margin-top: 0.4em;
}

.ng-prose blockquote {
	margin-left: 0;
	margin-right: 0;
	padding: 0.2em 0 0.2em 1em;
	border-left: 3px solid var(--ng-rule);
	color: var(--ng-muted);
}

.ng-prose hr {
	border: 0;
	border-top: 1px solid var(--ng-rule);
}

.ng-prose img {
	border-radius: var(--ng-radius);
}

/*
 * Neutralise legacy inline typography carried over by the same Joomla/EE
 * migration the theme's `.ja-prose` rule fixes for ordinary posts (see
 * wp-nivoli-ja/assets/css/theme.css): measured across published posts and
 * walkthroughs, 2,414 carry a dark inline `color` and 1,892 carry an inline
 * `pt` font size, and `post_content` is never rewritten, so this has to run
 * at render time. Same reasoning as the theme's rule: an inline style
 * outranks any selector, so only `!important` wins here; scoped to text
 * level elements only, never `img` (its inline `style` carries real
 * `max-width` sizing); `font-weight`/`font-style` are left out so semantic
 * bold and italic keep rendering.
 */
.ng-prose span, .ng-prose p, .ng-prose div, .ng-prose td, .ng-prose th,
.ng-prose li, .ng-prose b, .ng-prose i, .ng-prose strong, .ng-prose em {
	color: inherit !important;
	background: inherit !important;
	font-family: inherit !important;
	font-size: inherit !important;
}

.ng-prose code {
	padding: 0.1em 0.35em;
	background: var(--ng-code-bg);
	border-radius: 3px;
	font-family: var(--ng-mono);
	font-size: max(12px, 0.9em);
}

/* The 838 guides that converted to paragraphs with no headings at all. Slightly
   tighter than the outlined ones: with nothing to break up the page, generous
   paragraph spacing reads as a list of unrelated sentences. */
.ng-prose--flat > * + * {
	margin-top: 1em;
}

/* --------------------------------------- preserved monospace and scrollers -- */

/*
 * The 123 guides kept byte for byte. This is the intended rendering, not a
 * fallback: they contain ASCII maps and space-aligned tables, so the text is
 * monospace, it does NOT wrap, and the block scrolls sideways on its own instead
 * of making the page scroll. Comfortable line height and a low-contrast panel
 * make a wall of fixed-width text readable rather than merely preserved.
 */
.ng-scroller {
	max-width: 100%;
	margin: calc(var(--ng-root) * 1.5) 0;
	overflow-x: auto;
	overflow-y: hidden;
	background: var(--ng-code-bg);
	border: 1px solid var(--ng-rule);
	border-left: 3px solid var(--ng-accent-soft);
	border-radius: var(--ng-radius);
	/* Scroll one cell at a time on touch, and tell the browser this element
	   owns horizontal gestures so a swipe does not navigate back. */
	overscroll-behavior-x: contain;
}

.ng-scroller pre,
.ng-prose pre {
	margin: 0;
	padding: calc(var(--ng-root) * 1) calc(var(--ng-root) * 1.15);
	background: none;
	border: 0;
	color: var(--ng-fg);
	font-family: var(--ng-mono);
	font-size: calc(var(--ng-root) * 0.86);
	line-height: 1.55;
	/* Never `pre-wrap`: wrapping is what destroys the maps. */
	white-space: pre;
	tab-size: 8;
	-moz-tab-size: 8;
}

.ng-scroller--table {
	padding: 0;
}

.ng-scroller table {
	margin: 0;
	border-collapse: collapse;
	font-size: calc(var(--ng-root) * 0.95);
}

.ng-scroller th,
.ng-scroller td {
	padding: calc(var(--ng-root) * 0.4) calc(var(--ng-root) * 0.7);
	border: 1px solid var(--ng-rule);
	text-align: left;
	vertical-align: top;
}

.ng-notice--preserved {
	max-width: var(--ng-measure);
	margin: 0 0 calc(var(--ng-root) * 1.5);
	padding: calc(var(--ng-root) * 0.75) calc(var(--ng-root) * 1);
	background: var(--ng-panel);
	border-left: 3px solid var(--ng-accent-soft);
	border-radius: var(--ng-radius);
	color: var(--ng-muted);
	font-size: calc(var(--ng-root) * 0.9);
	line-height: 1.55;
}

/* -------------------------------------------------------------- spoilers ---- */

.ng-spoiler {
	max-width: var(--ng-measure);
	margin: calc(var(--ng-root) * 1.5) 0;
	padding: calc(var(--ng-root) * 0.25) calc(var(--ng-root) * 1);
	background: var(--ng-panel);
	border: 1px dashed var(--ng-rule);
	border-radius: var(--ng-radius);
}

.ng-spoiler-summary {
	padding: calc(var(--ng-root) * 0.6) 0;
	color: var(--ng-accent);
	cursor: pointer;
	font-size: calc(var(--ng-root) * 0.95);
	font-weight: 600;
}

.ng-spoiler[open] .ng-spoiler-summary {
	margin-bottom: calc(var(--ng-root) * 0.5);
	border-bottom: 1px solid var(--ng-rule);
}

.ng-spoiler-body > *:first-child {
	margin-top: 0;
}

/* ---------------------------------------------------------- breadcrumbs ---- */

.ng-crumbs {
	margin: 0 0 calc(var(--ng-root) * 1.5);
	/* .ng-crumb-current below is muted-coloured, and --ng-muted is only used
	   at 14px and larger; 0.875 * the 16px root is exactly that floor. */
	font-size: calc(var(--ng-root) * 0.875);
}

.ng-crumbs-list {
	display: flex;
	flex-wrap: wrap;
	gap: calc(var(--ng-root) * 0.35) calc(var(--ng-root) * 0.6);
	margin: 0;
	padding: 0;
	list-style: none;
	color: var(--ng-muted);
}

.ng-crumb + .ng-crumb::before {
	content: "/";
	margin-right: calc(var(--ng-root) * 0.6);
	color: var(--ng-rule);
}

.ng-crumb-current {
	color: var(--ng-muted);
}

/* -------------------------------------------- buttons, chips and badges ---- */

/*
 * Two selectors on every button rule, on purpose. A button is usually an `<a>`,
 * and `.ng a` (one class, one element) outranks a bare `.ng-button` (one class):
 * with a single selector the label inherits the link colour and renders amber on
 * amber. `.ng .ng-button` outranks it and covers `<button>` too.
 */
.ng-button,
.ng .ng-button {
	display: inline-block;
	padding: calc(var(--ng-root) * 0.5) calc(var(--ng-root) * 0.95);
	background: var(--ng-accent);
	border: 1px solid var(--ng-accent);
	border-radius: var(--ng-radius);
	color: var(--ng-accent-ink);
	font: inherit;
	font-size: calc(var(--ng-root) * 0.95);
	font-weight: 600;
	text-decoration: none;
	cursor: pointer;
}

.ng-button:hover,
.ng .ng-button:hover {
	background: color-mix(in srgb, var(--ng-accent) 85%, #fff);
	color: var(--ng-accent-ink);
}

.ng-button--quiet,
.ng .ng-button--quiet {
	background: none;
	border-color: var(--ng-control-border);
	color: var(--ng-fg);
	font-weight: 400;
}

.ng-button--quiet:hover,
.ng .ng-button--quiet:hover {
	background: var(--ng-panel);
	color: var(--ng-fg);
}

.ng-chips {
	display: flex;
	flex-wrap: wrap;
	column-gap: calc(var(--ng-root) * 0.4);
	/*
	 * Taller than the column gap on purpose: .ng-chip a below extends its own
	 * hit area about 15px above and below the pill it wraps, invisibly. Rows
	 * closer together than twice that would let one chip's hit area reach
	 * into the row next to it.
	 */
	row-gap: calc(var(--ng-root) * 1.75);
	margin: calc(var(--ng-root) * 0.75) 0 0;
	padding: 0;
	list-style: none;
}

.ng-chip {
	padding: calc(var(--ng-root) * 0.2) calc(var(--ng-root) * 0.6);
	background: var(--ng-panel);
	border: 1px solid var(--ng-rule);
	border-radius: 999px;
	font-size: calc(var(--ng-root) * 0.85);
}

/*
 * The audit measured this pill at 17px tall: a platform tag reads better
 * small, so rather than pad the pill itself up to 44px and inflate the type
 * with it, the link's own hit area is grown around the same visible text.
 * `min-height` plus flex centring sets the box that actually gets hit
 * tested to the 44px floor; the matching negative margin (half of the
 * height this adds beyond one line of text) folds the extra space back out
 * of the flow so the visible pill, and the row height .ng-chips reserves
 * for it, are unchanged.
 */
.ng-chip a {
	display: flex;
	align-items: center;
	justify-content: center;
	line-height: calc(var(--ng-root) * 0.85);
	min-height: calc(var(--ng-root) * 2.75);
	margin: calc(var(--ng-root) * -0.95) 0;
	text-decoration: none;
}

.ng-chip a:hover {
	text-decoration: underline;
}

.ng-chip--active {
	display: inline-flex;
	align-items: center;
	gap: calc(var(--ng-root) * 0.4);
	background: var(--ng-accent-soft);
}

.ng-chip-remove {
	text-decoration: none;
	font-size: 1.1em;
	line-height: 1;
}

/* Full body colour rather than muted: at 0.75em on the second panel tone the
   muted value lands at 4.38:1, which is under AA, and a count is information. */
.ng-count {
	padding: calc(var(--ng-root) * 0.05) calc(var(--ng-root) * 0.45);
	background: var(--ng-panel-2);
	border-radius: 999px;
	color: var(--ng-fg);
	font-size: max(12px, 0.75em);
	font-weight: 600;
	font-variant-numeric: tabular-nums;
}

.ng-tag {
	display: inline-block;
	margin-left: calc(var(--ng-root) * 0.35);
	padding: 0 calc(var(--ng-root) * 0.4);
	border: 1px solid var(--ng-rule);
	border-radius: 3px;
	color: var(--ng-muted);
	font-family: var(--ng-mono);
	/* --ng-muted is only used at 14px and larger; 0.875 * the 16px root is
	   exactly that floor. Every "pre" guide in the library index carries one
	   of these, so this is not a rare corner. */
	font-size: calc(var(--ng-root) * 0.875);
	text-transform: lowercase;
	white-space: nowrap;
}

/* Search results. These two are deliberately not scoped to `.ng`: they render
   inside the theme's own search template, where there is no `.ng` wrapper. */
.ng-badge {
	display: inline-block;
	margin-right: calc(var(--ng-root) * 0.4);
	padding: calc(var(--ng-root) * 0.05) calc(var(--ng-root) * 0.45);
	background: var(--ng-accent-soft);
	border: 1px solid var(--ng-rule);
	border-radius: 3px;
	color: var(--ng-accent);
	font-size: max(12px, 0.75em);
	font-weight: 600;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	vertical-align: 0.08em;
}

.ng-result-meta {
	color: var(--ng-muted);
	font-size: max(12px, 0.85em);
}

/* ------------------------------------------------------- game page hero ---- */

.ng-hero {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: calc(var(--ng-root) * 1.5);
	margin: 0 0 calc(var(--ng-root) * 2);
}

.ng-hero-art {
	max-width: calc(var(--ng-root) * 16);
}

.ng-cover-img {
	display: block;
	width: 100%;
	background: var(--ng-panel);
	border: 1px solid var(--ng-rule);
	border-radius: var(--ng-radius);
}

.ng-cover-noart {
	display: grid;
	place-items: center;
	aspect-ratio: 3 / 4;
	background: var(--ng-panel);
	border: 1px solid var(--ng-rule);
	border-radius: var(--ng-radius);
	color: var(--ng-rule);
	font-size: calc(var(--ng-root) * 4);
	font-weight: 700;
}

.ng-hero-facts {
	margin: 0 0 calc(var(--ng-root) * 0.75);
	color: var(--ng-muted);
}

.ng-hero-guides {
	margin: calc(var(--ng-root) * 1.25) 0 0;
}

.ng-stores {
	margin-top: calc(var(--ng-root) * 1.25);
}

.ng-stores-list {
	display: flex;
	flex-wrap: wrap;
	gap: calc(var(--ng-root) * 0.5);
	margin: 0;
	padding: 0;
	list-style: none;
}

/*
 * The audit measured this at 38px tall. Unlike the small pill controls
 * elsewhere on this page, a store link is meant to read as a real button, so
 * it grows to the 44px floor directly rather than through an invisible hit
 * area: min-height plus flex centring, not more vertical padding, so the
 * horizontal padding (and the button's width) is untouched.
 */
.ng-store {
	display: inline-flex;
	align-items: center;
	min-height: calc(var(--ng-root) * 2.75);
	padding: 0 calc(var(--ng-root) * 0.75);
	background: var(--ng-panel);
	border: 1px solid var(--ng-rule);
	border-radius: var(--ng-radius);
	font-size: calc(var(--ng-root) * 0.9);
	text-decoration: none;
}

.ng-store:hover {
	background: var(--ng-panel-2);
	text-decoration: underline;
}

.ng-jump {
	margin: calc(var(--ng-root) * 2) 0 0;
	padding: calc(var(--ng-root) * 0.75) calc(var(--ng-root) * 1);
	background: var(--ng-panel);
	border-radius: var(--ng-radius);
}

.ng-jump-list {
	display: flex;
	flex-wrap: wrap;
	gap: calc(var(--ng-root) * 0.5) calc(var(--ng-root) * 1.25);
	margin: 0;
	padding: 0;
	list-style: none;
	font-size: calc(var(--ng-root) * 0.95);
}

.ng-jump-list a {
	display: inline-flex;
	align-items: baseline;
	gap: calc(var(--ng-root) * 0.4);
	text-decoration: none;
}

.ng-jump-list a:hover {
	text-decoration: underline;
}

/* ------------------------------------------------------- lists and cards ---- */

.ng-list {
	margin: 0;
	padding: 0;
	list-style: none;
}

.ng-list--parts {
	counter-reset: ng-part;
	list-style: none;
}

.ng-item {
	padding: calc(var(--ng-root) * 0.75) 0;
	border-bottom: 1px solid var(--ng-rule);
}

.ng-item:last-child {
	border-bottom: 0;
}

.ng-item-title {
	font-size: calc(var(--ng-root) * 1.05);
	font-weight: 600;
	text-decoration: none;
}

.ng-item-title:hover {
	text-decoration: underline;
}

.ng-item--current .ng-item-title {
	color: var(--ng-fg);
	font-weight: 700;
}

.ng-item-meta {
	margin: calc(var(--ng-root) * 0.2) 0 0;
	color: var(--ng-muted);
	/* --ng-muted is only used at 14px and larger; 0.875 * the 16px root is
	   exactly that floor. */
	font-size: calc(var(--ng-root) * 0.875);
	line-height: 1.5;
}

.ng-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(calc(var(--ng-root) * 9.5), 1fr));
	gap: calc(var(--ng-root) * 1.25);
	margin: 0;
	padding: 0;
	list-style: none;
}

.ng-card {
	min-width: 0;
}

.ng-card-link {
	display: flex;
	flex-direction: column;
	gap: calc(var(--ng-root) * 0.5);
	height: 100%;
	color: var(--ng-fg);
	text-decoration: none;
}

.ng-card-link:hover .ng-card-title {
	color: var(--ng-accent);
	text-decoration: underline;
}

.ng-card-art {
	display: block;
	aspect-ratio: 3 / 4;
	overflow: hidden;
	background: var(--ng-panel);
	border: 1px solid var(--ng-rule);
	border-radius: var(--ng-radius);
}

/*
 * The grid keeps a fixed 3 / 4 shape, unlike the single card above, because it
 * is what puts every title on a row at the same height: covers of three
 * different shapes on one row would stagger the titles down the page.
 *
 * The ratio is on the image and not inherited from the frame through
 * `height: 100%`, which is what this rule used to say and which never took
 * effect. A percentage height resolves against the parent's specified height,
 * and `.ng-card-art`'s height is specified `auto` with only `aspect-ratio`
 * giving it a used value, so `height: 100%` computed to `auto` and every cover
 * rendered at its intrinsic ratio. Measured on the catalogue's A page: a
 * 616x353 cover in a 170x227 frame came out 168x96, leaving 130px of empty
 * panel hanging below it.
 *
 * A census of the 1,404 covers readable on disk says what that cost: 1,032
 * (73.5%) are exactly 3 / 4 and were unaffected, 139 (9.9%) are taller and were
 * having their bottoms clipped by the frame's `overflow: hidden`, 5% of the
 * image at the median and 14% at the worst, and 233 (16.6%) are wider and were
 * leaving that hole. Stating the ratio here fixes all three: `contain` shows
 * every cover whole, a taller one gains a hairline band at the sides and a wider
 * one a letterbox above and below, both on the frame's own panel tone.
 */
.ng-card-img {
	display: block;
	width: 100%;
	aspect-ratio: 3 / 4;
	object-fit: contain;
}

/*
 * The audit measured this at 1.5:1 (--ng-rule on --ng-panel, a hairline
 * colour never meant to carry text): it read as an empty box, exactly the
 * "no image" complaint this fallback exists to avoid. --ng-accent on
 * --ng-accent-soft instead: a warm, tinted tile that reads as a deliberate
 * monogram rather than a missing cover, at 6.1:1, well clear of the 4.5:1
 * floor for this size of text.
 */
.ng-card-noart {
	display: grid;
	place-items: center;
	height: 100%;
	background: var(--ng-accent-soft);
	color: var(--ng-accent);
	font-size: calc(var(--ng-root) * 2.5);
	font-weight: 700;
}

.ng-card-title {
	display: block;
	font-size: calc(var(--ng-root) * 0.95);
	font-weight: 600;
	line-height: 1.35;
}

.ng-card-meta {
	display: block;
	color: var(--ng-muted);
	font-size: calc(var(--ng-root) * 0.8);
	line-height: 1.4;
}

/* ------------------------------------------------------ archive controls ---- */

.ng-archive-head {
	margin-bottom: calc(var(--ng-root) * 1.5);
}

.ng-archive-intro {
	max-width: var(--ng-measure);
	margin: 0;
	color: var(--ng-muted);
}

.ng-az {
	margin: 0 0 calc(var(--ng-root) * 1.25);
}

.ng-az-list {
	display: flex;
	flex-wrap: wrap;
	gap: calc(var(--ng-root) * 0.3);
	margin: 0;
	padding: 0;
	list-style: none;
}

/*
 * The audit measured this at 36px tall. Like .ng-store above, this is a real
 * button (a jump link), not a delicate pill, so it grows to the 44px floor
 * directly: min-height plus flex centring rather than more padding, so the
 * grid of letters does not get visibly wider or unevenly spaced.
 */
.ng-az-link {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: calc(var(--ng-root) * 2.75);
	min-height: calc(var(--ng-root) * 2.75);
	padding: 0 calc(var(--ng-root) * 0.45);
	border: 1px solid var(--ng-control-border);
	border-radius: var(--ng-radius);
	font-size: calc(var(--ng-root) * 0.9);
	font-variant-numeric: tabular-nums;
	text-align: center;
	text-decoration: none;
}

.ng-az-link:hover {
	background: var(--ng-panel-2);
	text-decoration: none;
}

.ng-az-link.is-current {
	background: var(--ng-accent);
	border-color: var(--ng-accent);
	color: var(--ng-accent-ink);
	font-weight: 700;
}

/* Letters with no games. Muted and borderless, so plainly not a link, but at
   full opacity: dimming it further would drop below AA, and a letter you cannot
   read is not a useful way of saying "nothing here". */
.ng-az-link--off {
	border-color: transparent;
	color: var(--ng-muted);
}

.ng-filters {
	margin: 0 0 calc(var(--ng-root) * 1.5);
	padding: calc(var(--ng-root) * 1);
	background: var(--ng-panel);
	border: 1px solid var(--ng-control-border);
	border-radius: var(--ng-radius);
}

.ng-filters-heading {
	margin: 0 0 calc(var(--ng-root) * 0.75);
	/* --ng-muted is only used at 14px and larger; 0.875 * the 16px root is
	   exactly that floor. Shared with the guide library's filter form, which
	   is why this moved up from 0.8 rather than only being fixed there. */
	font-size: calc(var(--ng-root) * 0.875);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--ng-muted);
}

.ng-filters-row {
	display: flex;
	flex-wrap: wrap;
	gap: calc(var(--ng-root) * 1);
	align-items: flex-end;
}

.ng-field {
	display: flex;
	flex-direction: column;
	gap: calc(var(--ng-root) * 0.3);
	margin: 0;
	min-width: 0;
	flex: 1 1 calc(var(--ng-root) * 11);
}

.ng-field--actions {
	flex: 0 0 auto;
	flex-direction: row;
	gap: calc(var(--ng-root) * 0.5);
	align-items: center;
}

.ng-label {
	color: var(--ng-muted);
	/* --ng-muted is only used at 14px and larger; 0.875 * the 16px root is
	   exactly that floor. Shared with the guide library's filter form, which
	   is why this moved up from 0.8 rather than only being fixed there. */
	font-size: calc(var(--ng-root) * 0.875);
}

.ng-select {
	max-width: 100%;
	padding: calc(var(--ng-root) * 0.45) calc(var(--ng-root) * 0.5);
	background: var(--ng-bg);
	border: 1px solid var(--ng-control-border);
	border-radius: var(--ng-radius);
	color: var(--ng-fg);
	font: inherit;
	font-size: calc(var(--ng-root) * 0.95);
}

.ng-results {
	display: flex;
	flex-wrap: wrap;
	gap: calc(var(--ng-root) * 0.5) calc(var(--ng-root) * 1);
	align-items: center;
	margin: 0 0 calc(var(--ng-root) * 1.25);
}

.ng-results-count {
	margin: 0;
	color: var(--ng-muted);
	font-size: calc(var(--ng-root) * 0.9);
	font-variant-numeric: tabular-nums;
}

.ng-chips--active {
	margin: 0;
}

/*
 * WordPress's own pagination markup, toned to match. `Template\Library`
 * builds the same class names by hand for the guide library (its main query
 * is short-circuited, so `the_posts_pagination()` has nothing to paginate),
 * which is why this is not scoped to `.navigation.pagination` alone.
 */
.ng .navigation.pagination {
	margin-top: calc(var(--ng-root) * 2.5);
}

.ng .nav-links {
	display: flex;
	flex-wrap: wrap;
	gap: calc(var(--ng-root) * 0.4);
}

.ng .page-numbers {
	display: inline-block;
	padding: calc(var(--ng-root) * 0.35) calc(var(--ng-root) * 0.7);
	border: 1px solid var(--ng-control-border);
	border-radius: var(--ng-radius);
	font-size: calc(var(--ng-root) * 0.9);
	text-decoration: none;
}

.ng .page-numbers.current {
	background: var(--ng-accent);
	border-color: var(--ng-accent);
	color: var(--ng-accent-ink);
	font-weight: 700;
}

/* The elision ellipsis: not a control, so no edge to draw. */
.ng .page-numbers.dots {
	border-color: transparent;
	padding-left: 0;
	padding-right: 0;
	color: var(--ng-muted);
}

/* --------------------------------------------------- guide library groups -- */

.ng-guide-groups {
	margin: 0;
	padding: 0;
	list-style: none;
}

.ng-guide-group-item {
	border-bottom: 1px solid var(--ng-rule);
}

.ng-guide-group-item:first-child {
	border-top: 1px solid var(--ng-rule);
}

/*
 * The whole row is the disclosure control. A native `<details>`/`<summary>`
 * already has an accessible name (its text content) and default keyboard
 * behaviour (Enter and Space toggle it, Tab reaches it in document order), so
 * there is nothing to add here but layout, the 44px tap-target floor and a
 * chevron that rotates on `[open]`. `assets/js/guides-index.js` keeps an
 * explicit `aria-expanded` on the summary in sync with `[open]` on the
 * `toggle` event; the markup always starts closed with `aria-expanded="false"`,
 * so the state is correct before the script has run at all.
 */
.ng-guide-group-summary {
	display: flex;
	align-items: center;
	gap: calc(var(--ng-root) * 0.6);
	min-height: calc(var(--ng-root) * 2.75);
	padding: calc(var(--ng-root) * 0.4) 0;
	cursor: pointer;
	list-style: none;
}

.ng-guide-group-summary::-webkit-details-marker {
	display: none;
}

.ng-guide-group-chevron {
	flex: 0 0 auto;
	width: calc(var(--ng-root) * 0.55);
	height: calc(var(--ng-root) * 0.55);
	border-right: 2px solid var(--ng-control-border);
	border-bottom: 2px solid var(--ng-control-border);
	transform: rotate(-45deg);
	transition: transform 120ms ease;
}

@media (prefers-reduced-motion: reduce) {
	.ng-guide-group-chevron {
		transition: none;
	}
}

.ng-guide-group[open] > .ng-guide-group-summary .ng-guide-group-chevron {
	transform: rotate(45deg);
}

.ng-guide-group-title {
	flex: 1 1 auto;
	min-width: 0;
	font-weight: 600;
}

.ng-guide-group-count {
	flex: 0 0 auto;
	color: var(--ng-muted);
	/* --ng-muted is only used at 14px and larger; 0.875 * the 16px root is
	   exactly that floor. */
	font-size: calc(var(--ng-root) * 0.875);
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}

.ng-guide-group-body {
	padding: 0 0 calc(var(--ng-root) * 1) calc(var(--ng-root) * 1.2);
}

.ng-guide-group-gamelink {
	margin: 0 0 calc(var(--ng-root) * 0.75);
	font-size: calc(var(--ng-root) * 0.9);
}

.ng-list--grouped {
	margin: 0;
	padding: 0;
	list-style: none;
}

.ng-list--grouped > .ng-item {
	padding: calc(var(--ng-root) * 0.5) 0;
	border-top: 1px solid var(--ng-rule);
}

.ng-list--grouped > .ng-item:first-child {
	border-top: 0;
}

/* The "needs a game link" editor list: visually set apart with a dashed rule
   rather than a colour that would need its own contrast check, and its title
   reads muted so it does not compete with the guides above it for attention. */
.ng-guide-group--editor {
	margin-top: calc(var(--ng-root) * 2);
	padding-top: calc(var(--ng-root) * 1);
	border-top: 1px dashed var(--ng-rule);
}

.ng-guide-group--editor .ng-guide-group-title {
	color: var(--ng-muted);
}

/* --------------------------------------------- walkthrough page furniture -- */

/* Fixed, so it cannot be inside `.ng`. Hidden until the script announces
   itself: a progress bar frozen at zero is worse than no progress bar. */
.ng-progress {
	display: none;
}

html.ng-js .ng-progress {
	position: fixed;
	top: 0;
	right: 0;
	left: 0;
	z-index: 60;
	display: block;
	height: 3px;
	background: transparent;
	pointer-events: none;
}

.ng-progress-bar {
	width: 100%;
	height: 100%;
	background: var(--ng-accent);
	transform: scaleX(var(--ng-progress, 0));
	transform-origin: 0 50%;
	transition: transform 80ms linear;
}

/*
 * The game card beside a walkthrough (single-walkthrough.php's aside, above
 * the contents list). Replaces the old sticky horizontal game bar: that bar
 * showed a thumbnail and "Walkthrough for <title>" only, which this card
 * covers along with year, developer, platform chips, a store link and the
 * other-guides count, so the bar would have been showing a subset of this
 * card's own facts for no reason.
 */
.ng-gamecard {
	/* Art plus the padding on either side of it, so the panel is exactly as
	   wide as the cover it frames and stays a card when the aside stops being
	   a column. The contents list beside it is deliberately not capped: a list
	   of links wants the full width, a poster does not. */
	max-width: calc(var(--ng-cover-max) + var(--ng-root) * 2);
	margin: 0 0 calc(var(--ng-root) * 1.5);
	padding: calc(var(--ng-root) * 1);
	background: var(--ng-panel);
	border: 1px solid var(--ng-rule);
	border-radius: var(--ng-radius);
}

.ng-gamecard-link {
	display: block;
	color: var(--ng-fg);
	text-decoration: none;
}

/*
 * The cover keeps its own shape here. A census of the 1,403 stored covers that
 * are readable on disk says 1,283 are portrait, and the median ratio is exactly
 * 0.750, so a 3 / 4 box is right for most of the catalogue; but 109 are
 * landscape and 68 of those are wider than 1.5:1, and forcing one of those into
 * a 3 / 4 box with `object-fit: cover` showed a zoomed centre slice of a banner.
 * The filename is no help in telling the two apart: 1,259 of the 1,356 files
 * named `-header.*` are portrait, so the suffix says where the image came from
 * and nothing about its shape.
 *
 * A single image with nothing to line up against does not need a fixed box, so
 * it does not get one: the intrinsic ratio is kept, bounded on both axes. The
 * height cap is a 3 / 4 poster at the maximum width, which means no poster is
 * ever letterboxed and only a taller-than-poster outlier (the tallest cover in
 * the catalogue is 0.646) is bounded at all. `contain` and the panel tone are
 * for that case: a letterbox on the card's own surface, never a crop.
 */
.ng-gamecard-img {
	display: block;
	width: 100%;
	max-width: var(--ng-cover-max);
	height: auto;
	max-height: calc(var(--ng-cover-max) * 4 / 3);
	margin: 0 0 calc(var(--ng-root) * 0.75);
	background: var(--ng-panel-2);
	object-fit: contain;
	border-radius: var(--ng-radius);
}

.ng-gamecard-noart {
	display: grid;
	place-items: center;
	aspect-ratio: 3 / 4;
	margin: 0 0 calc(var(--ng-root) * 0.75);
	background: var(--ng-panel-2);
	border-radius: var(--ng-radius);
	color: var(--ng-rule);
	font-size: calc(var(--ng-root) * 2.5);
	font-weight: 700;
}

.ng-gamecard-title {
	display: block;
	font-size: calc(var(--ng-root) * 1.1);
	font-weight: 700;
	line-height: 1.3;
}

.ng-gamecard-link:hover .ng-gamecard-title {
	color: var(--ng-accent);
	text-decoration: underline;
}

.ng-gamecard-meta {
	margin: calc(var(--ng-root) * 0.4) 0 0;
	color: var(--ng-muted);
	font-size: calc(var(--ng-root) * 0.9);
}

.ng-gamecard-store {
	margin: calc(var(--ng-root) * 0.75) 0 0;
}

.ng-gamecard-guides {
	margin: calc(var(--ng-root) * 0.75) 0 0;
	padding-top: calc(var(--ng-root) * 0.75);
	border-top: 1px solid var(--ng-rule);
	color: var(--ng-muted);
	font-size: calc(var(--ng-root) * 0.9);
}

.ng-head {
	margin: 0 0 calc(var(--ng-root) * 1.5);
}

.ng-head-meta {
	display: flex;
	flex-wrap: wrap;
	gap: calc(var(--ng-root) * 0.3) calc(var(--ng-root) * 1);
	margin: 0;
	color: var(--ng-muted);
	font-size: calc(var(--ng-root) * 0.9);
}

.ng-head-by {
	color: var(--ng-fg);
	font-weight: 600;
}

/* Provenance badge. Reuses .ng-badge for the pill itself; this adds only the
   line it sits on above the headline, and the one distinction worth drawing:
   a guide written for this site is filled, an imported one is outlined. Both
   keep .ng-badge's 12px floor and its accent-on-accent-soft pairing. */
.ng-origin {
	margin: 0 0 calc(var(--ng-root) * 0.4);
}

.ng-badge--origin {
	text-transform: none;
	letter-spacing: 0.02em;
}

.ng-badge--ja-original {
	background: var(--ng-accent);
	border-color: var(--ng-accent);
	color: var(--ng-accent-ink);
}

/* A title attribute is the only affordance on the badge, so say so: a cursor
   that does not change makes the tooltip undiscoverable. */
.ng-badge--origin[title] {
	cursor: help;
}

/* ------------------------------------------------------------- credit ---- */

.ng-credit {
	max-width: var(--ng-measure);
	margin: 0 0 calc(var(--ng-root) * 2);
	padding: calc(var(--ng-root) * 1) calc(var(--ng-root) * 1.15);
	background: var(--ng-panel);
	border-left: 3px solid var(--ng-accent);
	border-radius: var(--ng-radius);
}

.ng-credit-heading {
	margin: 0;
	color: var(--ng-muted);
	font-size: calc(var(--ng-root) * 0.75);
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

.ng-credit-name {
	margin: calc(var(--ng-root) * 0.2) 0 0;
	font-size: calc(var(--ng-root) * 1.2);
	font-weight: 700;
	line-height: 1.3;
}

.ng-credit-note,
.ng-credit-origin,
.ng-credit-notice,
.ng-credit-thanks {
	margin: calc(var(--ng-root) * 0.5) 0 0;
	color: var(--ng-muted);
	font-size: calc(var(--ng-root) * 0.88);
	line-height: 1.55;
}

.ng-credit-thanks {
	font-size: calc(var(--ng-root) * 0.8);
	font-style: italic;
}

.ng-credit-action {
	margin: calc(var(--ng-root) * 0.75) 0 0;
}

/* No byline recorded. Same weight of component, not a hole in the page: the
   accent border stays, the copy explains, nothing says "Anonymous". */
.ng-credit--uncredited {
	border-left-color: var(--ng-rule);
}

.ng-credit--uncredited .ng-credit-heading {
	color: var(--ng-fg);
	font-size: calc(var(--ng-root) * 0.95);
	letter-spacing: 0;
	text-transform: none;
}

/* ------------------------------------------------- layout and contents ---- */

.ng-layout {
	min-width: 0;
}

.ng-main {
	/* Without this a wide `pre` inside a grid item blows the column out and
	   the page scrolls sideways: `minmax(0, 1fr)` alone is not enough. */
	min-width: 0;
}

.ng-toc {
	margin: 0 0 calc(var(--ng-root) * 1.5);
	padding: calc(var(--ng-root) * 0.5) calc(var(--ng-root) * 0.9);
	background: var(--ng-panel);
	border: 1px solid var(--ng-rule);
	border-radius: var(--ng-radius);
}

.ng-toc-summary {
	padding: calc(var(--ng-root) * 0.4) 0;
	color: var(--ng-muted);
	cursor: pointer;
	font-size: calc(var(--ng-root) * 0.78);
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

/*
 * Numbered by CSS counter rather than by the list marker. The h3 entries live in
 * a nested `ol` inside a wrapper `li`, and a marker would number that wrapper
 * too: the outline would read 1, 2 (nothing), 3. The counter is incremented by
 * the real entries only, so the top level counts 1 to n and the sub-entries are
 * shown by indentation.
 */
.ng-toc-list {
	counter-reset: ng-toc;
	margin: calc(var(--ng-root) * 0.4) 0 calc(var(--ng-root) * 0.5);
	padding: 0;
	list-style: none;
	font-size: calc(var(--ng-root) * 0.9);
	line-height: 1.45;
}

.ng-toc-list--sub {
	margin: calc(var(--ng-root) * 0.3) 0;
	padding-left: calc(var(--ng-root) * 1.1);
	color: var(--ng-muted);
}

.ng-toc-item {
	margin: calc(var(--ng-root) * 0.3) 0;
}

.ng-toc-item--h2::before {
	content: counter(ng-toc) ". ";
	color: var(--ng-muted);
	font-variant-numeric: tabular-nums;
}

.ng-toc-item--h2 {
	counter-increment: ng-toc;
}

.ng-toc-item--h3::before {
	content: "";
}

.ng-toc-link {
	text-decoration: none;
}

.ng-toc-link:hover {
	text-decoration: underline;
}

/* Set by the script as the reader passes each heading. */
.ng-toc-link.is-current {
	color: var(--ng-fg);
	font-weight: 700;
}

.ng-partnav {
	display: flex;
	flex-wrap: wrap;
	gap: calc(var(--ng-root) * 1);
	justify-content: space-between;
	margin: calc(var(--ng-root) * 2.5) 0 0;
	padding-top: calc(var(--ng-root) * 1.25);
	border-top: 1px solid var(--ng-rule);
}

.ng-partnav-link {
	flex: 1 1 calc(var(--ng-root) * 12);
	padding: calc(var(--ng-root) * 0.75) calc(var(--ng-root) * 1);
	background: var(--ng-panel);
	border: 1px solid var(--ng-rule);
	border-radius: var(--ng-radius);
	text-decoration: none;
}

.ng-partnav-link--next {
	text-align: right;
}

.ng-partnav-label {
	display: block;
	color: var(--ng-muted);
	font-size: calc(var(--ng-root) * 0.75);
	letter-spacing: 0.06em;
	text-transform: uppercase;
}

.ng-partnav-title {
	display: block;
	color: var(--ng-accent);
	font-weight: 600;
}

/* ------------------------------------------------------ contributor page ---- */

.ng-contributor-head {
	max-width: var(--ng-measure);
	margin: 0 0 calc(var(--ng-root) * 2);
}

.ng-stats {
	display: flex;
	flex-wrap: wrap;
	gap: calc(var(--ng-root) * 1.5);
	margin: calc(var(--ng-root) * 1.25) 0;
	padding: calc(var(--ng-root) * 1) 0;
	border-top: 1px solid var(--ng-rule);
	border-bottom: 1px solid var(--ng-rule);
	list-style: none;
}

.ng-stat {
	display: flex;
	flex-direction: column;
}

.ng-stat-value {
	font-size: calc(var(--ng-root) * 1.5);
	font-weight: 700;
	font-variant-numeric: tabular-nums;
	line-height: 1.1;
}

.ng-stat-label {
	color: var(--ng-muted);
	font-size: calc(var(--ng-root) * 0.8);
	letter-spacing: 0.04em;
	text-transform: uppercase;
}

.ng-contributor-thanks {
	color: var(--ng-muted);
	font-size: calc(var(--ng-root) * 0.9);
}

/* ---------------------------------------------------------- wide screens ---- */

@media (min-width: 704px) {
	.ng-hero {
		grid-template-columns: calc(var(--ng-root) * 14) minmax(0, 1fr);
		align-items: start;
		gap: calc(var(--ng-root) * 2);
	}

	.ng-hero-art {
		max-width: none;
	}
}

@media (min-width: 960px) {
	.ng-layout--sided {
		display: grid;
		grid-template-columns: minmax(0, 1fr) calc(var(--ng-root) * 15);
		gap: calc(var(--ng-root) * 2.5);
		align-items: start;
	}

	.ng-layout--sided .ng-main {
		grid-column: 1;
		grid-row: 1;
	}

	/* The card and the contents list are one element in the DOM, ordered for a
	   phone (title, credit, card, contents, prose) and moved into the
	   right-hand column here. No duplicate markup, no duplicate anchor ids. */
	.ng-layout--sided .ng-side {
		grid-column: 2;
		grid-row: 1;
		/* The whole aside stays put while the prose scrolls, card and contents
		   list together: this is what used to be the sticky game bar's job,
		   now done by the panel that replaced it. Scrolls internally rather
		   than growing past the viewport, for a guide with a long outline. */
		position: sticky;
		top: calc(var(--ng-root) * 4.5);
		max-height: calc(100vh - calc(var(--ng-root) * 6));
		overflow-y: auto;
		overscroll-behavior: contain;
	}

	/* On a wide screen the list is simply there: the disclosure control would
	   be furniture. It stays open (the script only collapses it on narrow
	   viewports) and the fallback below covers the case where it does not. */
	.ng-toc > .ng-toc-summary {
		display: none;
	}

	.ng-toc:not([open]) > .ng-toc-body {
		display: block;
	}
}

/* --------------------------------------------------------- small screens ---- */

@media (max-width: 480px) {
	.ng {
		--ng-size: calc(var(--ng-root) * 1.0625); /* 17px: 19px at 375px is a 32-character line */
		padding-bottom: calc(var(--ng-root) * 3);
	}

	.ng-grid {
		grid-template-columns: repeat(auto-fill, minmax(calc(var(--ng-root) * 7.5), 1fr));
		gap: calc(var(--ng-root) * 1);
	}

	.ng-partnav-link--next {
		text-align: left;
	}
}

/* ------------------------------------------------------- reduced motion ---- */

@media (prefers-reduced-motion: reduce) {
	.ng-progress-bar {
		transition: none;
	}

	.ng *,
	.ng *::before,
	.ng *::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}
