UNPKG

webpack

Version:

Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.

20,874 lines • 794 kB
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

const LocConverter = require("../util/LocConverter");
const { haltVisitors, resumeVisitors } = require("../util/haltVisitors");

const { makeCacheable } = require("../util/identifier");

/**
 * Renders source this stylesheet embeds — a `data:` URL's payload today.
 * Returning it unchanged, or anything but text, declines it, and the URL is
 * emitted as written.
 * @typedef {(source: string, info: { type: string, hostType: string }) => string | undefined} EmbeddedSourceRenderer
 */

/**
 * One embedded source recorded for a caller that can only answer
 * asynchronously, and the text to print once it has.
 * @typedef {import("../util/dataURL").DeferredEmbeddedSource} DeferredEmbeddedSource
 */
const {
	ABSOLUTE_UNIT_SCALE,
	ANGLE_UNITS,
	ANIMATION_KEYWORD_SLOTS,
	BOX_FAMILY_PREFIX,
	BOX_LONGHANDS,
	CALC_CONSTANTS,
	COLOR_FUNCTIONS,
	COLOR_KEYWORDS,
	COMPOUND_CONTINUATIONS,
	CSS_WIDE_KEYWORDS,
	CUSTOM_IDENT_LIST_PROPERTIES,
	DROPPABLE_WHEN_EMPTY_AT_RULES,
	EASING_KEYWORDS,
	ENCODED_ALREADY,
	ENGINE_TRANSFER_DIFFERS,
	FAMILY_LONGHANDS,
	FAMILY_SLOT_CLASSES,
	FAMILY_SLOT_INITIALS,
	FAMILY_SLOT_KEYWORDS,
	FEATURELESS_PSEUDO_CLASSES,
	FLEX_KEYWORDS,
	LATER_COLOR_NAMES,
	LEGACY_BOX_DISPLAYS,
	LEGACY_BOX_SPELLINGS,
	LEGACY_PSEUDO_ELEMENTS,
	LINEAR_SRGB_TO_P3,
	MATH_FUNCTIONS,
	MERGEABLE_AT_RULES,
	MERGE_LONGHANDS,
	NEVER,
	NTH_PSEUDO_FUNCTIONS,
	ONE_VALUE_PAIR_SHORTHANDS,
	ORDERED_LONGHANDS,
	PAIR_LONGHANDS,
	PLACE_SHORTHANDS,
	PREDEFINED_COLOR_SPACES,
	PREFIXED_AT_RULES,
	PREFIXED_SELECTORS,
	PREFIXED_SPELLING_NUMBERS,
	PREFIX_WINDOW_STARTS,
	RGB_TO_NAME,
	SLASH_LONGHANDS,
	SRGB_SPACE,
	STEPPED_FUNCTIONS,
	SUBSTITUTION_FUNCTIONS,
	SUPPORTED_FROM,
	SUPPORT_BROWSERS,
	THROUGH_MATRIX,
	THROUGH_TRANSFER,
	TRANSITION_BEHAVIORS,
	UNSHARED_LONGHAND_KEYWORDS,
	UNSTATED_BROWSER_PREFIXES,
	foldAdd,
	foldDivide,
	foldMultiply,
	getColorNameToRgb,
	getColorSpaceModel,
	getDeclarationRanks,
	getPrefixWindows,
	getPrefixedProperties,
	getPrefixedSpellingKeywords,
	getPrefixedValues,
	getPropertyAliases,
	getSelectorSupportedFrom,
	getShorthandLonghands,
	getSupportProfiles,
	getValueSupportPacked
} = require("./data");

// spec: https://drafts.csswg.org/css-syntax/

/**
 * @typedef {object} CssWhitespaceToken
 * @property {number} type
 * @property {number} start byte offset of the first whitespace code point
 * @property {number} end byte offset just past the last whitespace code point
 */
/**
 * @typedef {object} CssCommentToken
 * @property {number} type
 * @property {number} start byte offset of the opening `/`
 * @property {number} end byte offset just past the closing `/`
 */
/**
 * @typedef {object} CssStringToken
 * @property {number} type
 * @property {number} start byte offset of the opening quote
 * @property {number} end byte offset just past the closing quote (or EOF for unterminated strings)
 */
/**
 * @typedef {object} CssBadStringToken
 * @property {number} type
 * @property {number} start byte offset of the opening quote
 * @property {number} end byte offset where parsing gave up (typically the newline that broke the string)
 */
/**
 * @typedef {object} CssLeftCurlyBracketToken
 * @property {number} type
 * @property {number} start byte offset of `{`
 * @property {number} end `start + 1`
 */
/**
 * @typedef {object} CssRightCurlyBracketToken
 * @property {number} type
 * @property {number} start byte offset of `}`
 * @property {number} end `start + 1`
 */
/**
 * @typedef {object} CssLeftSquareBracketToken
 * @property {number} type
 * @property {number} start byte offset of `[`
 * @property {number} end `start + 1`
 */
/**
 * @typedef {object} CssRightSquareBracketToken
 * @property {number} type
 * @property {number} start byte offset of `]`
 * @property {number} end `start + 1`
 */
/**
 * @typedef {object} CssLeftParenthesisToken
 * @property {number} type
 * @property {number} start byte offset of `(`
 * @property {number} end `start + 1`
 */
/**
 * @typedef {object} CssRightParenthesisToken
 * @property {number} type
 * @property {number} start byte offset of `)`
 * @property {number} end `start + 1`
 */
/**
 * @typedef {object} CssFunctionToken
 * @property {number} type
 * @property {number} start byte offset of the function name's first code point
 * @property {number} end byte offset just past the `(` that closes the function token
 */
/**
 * @typedef {object} CssUrlToken
 * @property {number} type
 * @property {number} start byte offset of the `url(` keyword (i.e. the `u`)
 * @property {number} end byte offset just past the closing `)` (or EOF)
 * @property {number} contentStart byte offset of the first code point of the unquoted URL content (post leading whitespace)
 * @property {number} contentEnd byte offset just past the last code point of the unquoted URL content (pre trailing whitespace / `)` / EOF)
 */
/**
 * @typedef {object} CssBadUrlToken
 * @property {number} type
 * @property {number} start byte offset of the `url(` keyword
 * @property {number} end byte offset where parsing gave up (past the recovery `)` or EOF)
 */
/**
 * @typedef {object} CssColonToken
 * @property {number} type
 * @property {number} start byte offset of `:`
 * @property {number} end `start + 1`
 */
/**
 * @typedef {object} CssAtKeywordToken
 * @property {number} type
 * @property {number} start byte offset of `@`
 * @property {number} end byte offset just past the last ident-sequence code point
 */
/**
 * @typedef {object} CssDelimToken
 * @property {number} type
 * @property {number} start byte offset of the delim code point
 * @property {number} end `start + 1`
 */
/**
 * @typedef {object} CssIdentToken
 * @property {number} type
 * @property {number} start byte offset of the first ident code point
 * @property {number} end byte offset just past the last ident-sequence code point
 */
/**
 * @typedef {object} CssPercentageToken
 * @property {number} type
 * @property {number} start byte offset of the first numeric code point
 * @property {number} end byte offset just past the `%`
 */
/**
 * @typedef {object} CssNumberToken
 * @property {number} type
 * @property {number} start byte offset of the first numeric code point
 * @property {number} end byte offset just past the last numeric code point
 */
/**
 * @typedef {object} CssDimensionToken
 * @property {number} type
 * @property {number} start byte offset of the first numeric code point
 * @property {number} end byte offset just past the last unit ident code point
 * @property {number} unitStart byte offset of the first unit-ident code point (== end of the numeric run)
 */
/**
 * @typedef {object} CssHashToken
 * @property {number} type
 * @property {number} start byte offset of `#`
 * @property {number} end byte offset just past the last ident-sequence code point
 * @property {boolean} isId true when the hash starts an ident sequence (`#foo`), false for non-ident hashes (`#1abc`)
 */
/**
 * @typedef {object} CssSemicolonToken
 * @property {number} type
 * @property {number} start byte offset of `;`
 * @property {number} end `start + 1`
 */
/**
 * @typedef {object} CssCommaToken
 * @property {number} type
 * @property {number} start byte offset of `,`
 * @property {number} end `start + 1`
 */
/**
 * @typedef {object} CssCdoToken
 * @property {number} type
 * @property {number} start byte offset of `<`
 * @property {number} end byte offset just past `<!--`
 */
/**
 * @typedef {object} CssCdcToken
 * @property {number} type
 * @property {number} start byte offset of `-`
 * @property {number} end byte offset just past `-->`
 */
/**
 * @typedef {CssWhitespaceToken | CssCommentToken | CssStringToken | CssBadStringToken | CssLeftCurlyBracketToken | CssRightCurlyBracketToken | CssLeftSquareBracketToken | CssRightSquareBracketToken | CssLeftParenthesisToken | CssRightParenthesisToken | CssFunctionToken | CssUrlToken | CssBadUrlToken | CssColonToken | CssAtKeywordToken | CssDelimToken | CssIdentToken | CssPercentageToken | CssNumberToken | CssDimensionToken | CssHashToken | CssSemicolonToken | CssCommaToken | CssCdoToken | CssCdcToken} CssToken
 */

const CC_LINE_FEED = "\n".charCodeAt(0);
const CC_CARRIAGE_RETURN = "\r".charCodeAt(0);
const CC_FORM_FEED = "\f".charCodeAt(0);

const CC_TAB = "\t".charCodeAt(0);
const CC_SPACE = " ".charCodeAt(0);

const CC_SOLIDUS = "/".charCodeAt(0);
const CC_REVERSE_SOLIDUS = "\\".charCodeAt(0);
const CC_ASTERISK = "*".charCodeAt(0);
const CC_VERTICAL_LINE = "|".charCodeAt(0);

const CC_LEFT_PARENTHESIS = "(".charCodeAt(0);
const CC_RIGHT_PARENTHESIS = ")".charCodeAt(0);
const CC_LEFT_CURLY = "{".charCodeAt(0);
const CC_RIGHT_CURLY = "}".charCodeAt(0);
const CC_LEFT_SQUARE = "[".charCodeAt(0);
const CC_RIGHT_SQUARE = "]".charCodeAt(0);

const CC_QUOTATION_MARK = '"'.charCodeAt(0);
const CC_APOSTROPHE = "'".charCodeAt(0);

const CC_FULL_STOP = ".".charCodeAt(0);
const CC_COLON = ":".charCodeAt(0);
const CC_SEMICOLON = ";".charCodeAt(0);
const CC_COMMA = ",".charCodeAt(0);
const CC_PERCENTAGE = "%".charCodeAt(0);
const CC_AT_SIGN = "@".charCodeAt(0);

const CC_LOW_LINE = "_".charCodeAt(0);
const CC_LOWER_A = "a".charCodeAt(0);
const CC_LOWER_D = "d".charCodeAt(0);
const CC_LOWER_F = "f".charCodeAt(0);
const CC_LOWER_I = "i".charCodeAt(0);
const CC_LOWER_S = "s".charCodeAt(0);
const CC_LOWER_E = "e".charCodeAt(0);
const CC_LOWER_U = "u".charCodeAt(0);
const CC_LOWER_R = "r".charCodeAt(0);
const CC_LOWER_L = "l".charCodeAt(0);
const CC_LOWER_T = "t".charCodeAt(0);
const CC_LOWER_X = "x".charCodeAt(0);
const CC_LOWER_Z = "z".charCodeAt(0);
const CC_EXCLAMATION = "!".charCodeAt(0);
const CC_UPPER_A = "A".charCodeAt(0);
const CC_UPPER_F = "F".charCodeAt(0);
const CC_UPPER_E = "E".charCodeAt(0);
const CC_UPPER_Z = "Z".charCodeAt(0);
const CC_0 = "0".charCodeAt(0);
const CC_9 = "9".charCodeAt(0);

const CC_NUMBER_SIGN = "#".charCodeAt(0);
const CC_PLUS_SIGN = "+".charCodeAt(0);
const CC_HYPHEN_MINUS = "-".charCodeAt(0);

const CC_LESS_THAN_SIGN = "<".charCodeAt(0);
const CC_GREATER_THAN_SIGN = ">".charCodeAt(0);
const CC_TILDE = "~".charCodeAt(0);
const CC_EQUALS_SIGN = "=".charCodeAt(0);

// Lexer token types (CSS Syntax Level 3 §4) plus the `<eof-token>`, numeric so
// the `type` slot stays compact and the consume algorithms dispatch on integer
// `===`. Plain constants, not `TokenType` reads, keep the lexer's hot path cheap.
const TT_COMMENT = 1;
const TT_WHITESPACE = 2;
const TT_STRING = 3;
const TT_BAD_STRING_TOKEN = 4;
const TT_HASH = 5;
const TT_DELIM = 6;
// The three opening brackets are kept contiguous (7..9) so "is this an opening
// bracket?" is a single range check (`>= TT_LEFT_PARENTHESIS && <= TT_LEFT_CURLY_BRACKET`).
const TT_LEFT_PARENTHESIS = 7;
const TT_LEFT_SQUARE_BRACKET = 8;
const TT_LEFT_CURLY_BRACKET = 9;
const TT_RIGHT_PARENTHESIS = 10;
const TT_RIGHT_SQUARE_BRACKET = 11;
const TT_RIGHT_CURLY_BRACKET = 12;
const TT_COMMA = 13;
const TT_COLON = 14;
const TT_SEMICOLON = 15;
const TT_AT_KEYWORD = 16;
const TT_FUNCTION = 17;
const TT_URL = 18;
const TT_BAD_URL_TOKEN = 19;
const TT_IDENTIFIER = 20;
const TT_NUMBER = 21;
const TT_PERCENTAGE = 22;
const TT_DIMENSION = 23;
const TT_CDO = 24;
const TT_CDC = 25;
const TT_EOF = 26;

/**
 * The lexer token types by spec name, as `readToken` and `TokenStream` callers
 * read them.
 * @type {{ Comment: 1, Whitespace: 2, String: 3, BadString: 4, Hash: 5, Delim: 6, LeftParenthesis: 7, LeftSquareBracket: 8, LeftCurlyBracket: 9, RightParenthesis: 10, RightSquareBracket: 11, RightCurlyBracket: 12, Comma: 13, Colon: 14, Semicolon: 15, AtKeyword: 16, Function: 17, Url: 18, BadUrl: 19, Ident: 20, Number: 21, Percentage: 22, Dimension: 23, CDO: 24, CDC: 25, EOF: 26 }}
 */
const TokenType = {
	Comment: TT_COMMENT,
	Whitespace: TT_WHITESPACE,
	String: TT_STRING,
	BadString: TT_BAD_STRING_TOKEN,
	Hash: TT_HASH,
	Delim: TT_DELIM,
	LeftParenthesis: TT_LEFT_PARENTHESIS,
	LeftSquareBracket: TT_LEFT_SQUARE_BRACKET,
	LeftCurlyBracket: TT_LEFT_CURLY_BRACKET,
	RightParenthesis: TT_RIGHT_PARENTHESIS,
	RightSquareBracket: TT_RIGHT_SQUARE_BRACKET,
	RightCurlyBracket: TT_RIGHT_CURLY_BRACKET,
	Comma: TT_COMMA,
	Colon: TT_COLON,
	Semicolon: TT_SEMICOLON,
	AtKeyword: TT_AT_KEYWORD,
	Function: TT_FUNCTION,
	Url: TT_URL,
	BadUrl: TT_BAD_URL_TOKEN,
	Ident: TT_IDENTIFIER,
	Number: TT_NUMBER,
	Percentage: TT_PERCENTAGE,
	Dimension: TT_DIMENSION,
	CDO: TT_CDO,
	CDC: TT_CDC,
	EOF: TT_EOF
};

// The opening bracket types (7..9) and their closers (10..12) are laid out so a
// closer is always `opener + 3`, which `consumeASimpleBlock` uses directly. The
// block char is a dense array indexed by the offset from `TT_LEFT_PARENTHESIS`.
/** @type {SimpleBlockToken[]} */
const BLOCK_TOKEN_CHAR = ["(", "[", "{"];

/**
 * @param {number} cc char code
 * @returns {boolean} true, if cc is a newline (per the spec: LF, CR, or FF)
 */
const _isNewline = (cc) =>
	cc === CC_LINE_FEED || cc === CC_CARRIAGE_RETURN || cc === CC_FORM_FEED;

/**
 * If the source had a CR followed by an LF, advance past the LF —
 * the spec normalises CRLF to LF during preprocessing.
 * @param {number} cc char code already consumed (the CR)
 * @param {string} input input
 * @param {number} pos position just past `cc`
 * @returns {number} position past the CRLF pair (or unchanged for bare CR)
 */
const consumeExtraNewline = (cc, input, pos) => {
	if (cc === CC_CARRIAGE_RETURN && input.charCodeAt(pos) === CC_LINE_FEED) {
		pos++;
	}
	return pos;
};

/**
 * @param {number} cc char code
 * @returns {boolean} true, if cc is space or tab
 */
const _isSpace = (cc) => cc === CC_SPACE || cc === CC_TAB;

/**
 * CSS Syntax §4.2 whitespace (newline, tab, space), the same set as Infra's
 * ASCII whitespace.
 * @param {number} cc char code
 * @returns {boolean} true, if cc is whitespace (space/tab/newline)
 */
// Space-first: U+0020 is the common case, so it short-circuits before the
// rarer tab / newline tests.
const isAsciiWhitespace = (cc) => _isSpace(cc) || _isNewline(cc);

/**
 * Trim the whitespace CSS itself skips. `String#trim` also eats U+00A0 and the
 * other Unicode spaces, which an identifier carries as ordinary characters, so
 * a layer named `\u00A0x` would compare equal to one named `x`.
 * @param {string} text the text to trim
 * @returns {string} the text without its leading and trailing CSS whitespace
 */
const _trimCssWhiteSpace = (text) => {
	let start = 0;
	let end = text.length;
	while (start < end && isAsciiWhitespace(text.charCodeAt(start))) start++;
	while (end > start && isAsciiWhitespace(text.charCodeAt(end - 1))) end--;
	return start === 0 && end === text.length ? text : text.slice(start, end);
};

/**
 * A digit (CSS Syntax §4.2): U+0030 to U+0039.
 * @param {number} cc char code
 * @returns {boolean} true, if cc is a digit
 */
const isDigit = (cc) => cc >= CC_0 && cc <= CC_9;

/**
 * Whether a number's digits start at `at`: a digit, or a `.` with one after it.
 * @param {string} text the text
 * @param {number} at the offset
 * @returns {boolean} true when the digits of a number start there
 */
const _startsDigits = (text, at) =>
	isDigit(text.charCodeAt(at)) ||
	(text.charCodeAt(at) === CC_FULL_STOP && isDigit(text.charCodeAt(at + 1)));

/**
 * A hex digit (CSS Syntax §4.2): a digit, or A to F in either case.
 * @param {number} cc char code
 * @returns {boolean} true, if cc is a hex digit
 */
const isHexDigit = (cc) =>
	isDigit(cc) ||
	(cc >= CC_UPPER_A && cc <= CC_UPPER_F) ||
	(cc >= CC_LOWER_A && cc <= CC_LOWER_F);

/**
 * A letter (CSS Syntax §4.2): A to Z in either case.
 * @param {number} cc char code
 * @returns {boolean} is letter (a-z / A-Z)
 */
const isLetter = (cc) =>
	(cc >= CC_LOWER_A && cc <= CC_LOWER_Z) ||
	(cc >= CC_UPPER_A && cc <= CC_UPPER_Z);

/**
 * An ident-start code point (CSS Syntax §4.2): a letter, a non-ASCII code
 * point or `_`.
 * @param {number} cc char code
 * @returns {boolean} true, if cc is an ident-start code point
 */
const isIdentStartCodePoint = (cc) =>
	isLetter(cc) || cc >= 0x80 || cc === CC_LOW_LINE;

// The class every per-code-point loop in the tokenizer reads: one table load
// answers whichever of the three it asks (spec: ident-code = ident-start /
// digit / hyphen-minus).
const BC_OTHER = 0;
const BC_IDENT = 1;
const BC_WHITESPACE = 2;
// What a skip-mode selector prelude cannot step over in bytes, read by
// `TokenStream#skipPreludeLeaves`: the structure its rule loop stops at, the
// openers it still materializes, and what can hide one of those from a scan.
const BC_PRELUDE_STOP = 3;
// WHY: The three sets are disjoint — nothing is both an ident code point and
// whitespace, and no stop is either — so one value a row carries all three and
// no read has to mask. The table spans the whole `charCodeAt` range so each test
// is one load with no `cc < 128` branch, every non-ASCII code unit being an
// ident code point per spec. Callers must index with `cc | 0`: a raw NaN index
// (EOF) reads out of range and permanently degrades the load site's IC. Row 0
// is a stop, so that `NaN | 0` is also what ends the prelude scan at EOF.
const _byteClass = new Uint8Array(0x10000).fill(BC_IDENT);
for (let i = 0; i < 128; i++) {
	_byteClass[i] =
		isLetter(i) || i === CC_LOW_LINE || isDigit(i) || i === CC_HYPHEN_MINUS
			? BC_IDENT
			: isAsciiWhitespace(i)
				? BC_WHITESPACE
				: BC_OTHER;
}
for (const cc of [
	0,
	CC_LEFT_CURLY,
	CC_RIGHT_CURLY,
	CC_SEMICOLON,
	CC_LEFT_PARENTHESIS,
	CC_LEFT_SQUARE,
	CC_QUOTATION_MARK,
	CC_APOSTROPHE,
	CC_REVERSE_SOLIDUS,
	CC_SOLIDUS
]) {
	_byteClass[cc] = BC_PRELUDE_STOP;
}
/**
 * @param {number} cc char code
 * @returns {boolean} true, if cc is an ident-sequence code point
 */
const _isIdentCodePoint = (cc) => _byteClass[cc | 0] === BC_IDENT;

/**
 * Infra's ASCII case-insensitive match against a lowercase literal — avoids the
 * `toLowerCase()` allocation and matches CSS's ASCII case-insensitive keyword
 * matching. `lit` must be lowercase ASCII.
 * @param {string} s string to test
 * @param {string} lit lowercase ASCII literal to match
 * @returns {boolean} true, if `s` equals `lit` ignoring ASCII case
 */
const asciiCaseInsensitiveMatch = (s, lit) => {
	if (s.length !== lit.length) return false;
	for (let i = 0; i < lit.length; i++) {
		let c = s.charCodeAt(i);
		if (c >= CC_UPPER_A && c <= CC_UPPER_Z) c |= 0x20;
		if (c !== lit.charCodeAt(i)) return false;
	}
	return true;
};

/**
 * Case-sensitive equality of a source range against a literal — no slice.
 * @param {string} input source
 * @param {number} start range start
 * @param {number} end range end (exclusive)
 * @param {string} lit literal to match
 * @returns {boolean} true when the range equals `lit`
 */
const rangeEquals = (input, start, end, lit) =>
	end - start === lit.length && input.startsWith(lit, start);

/**
 * ASCII case-insensitive equality of a source range against a lowercase ASCII literal — no slice.
 * @param {string} input source
 * @param {number} start range start
 * @param {number} end range end (exclusive)
 * @param {string} lit lowercase ASCII literal to match
 * @returns {boolean} true when the range equals `lit` ignoring ASCII case
 */
const rangeAsciiCaseInsensitiveMatch = (input, start, end, lit) => {
	if (end - start !== lit.length) return false;
	for (let i = 0; i < lit.length; i++) {
		let c = input.charCodeAt(start + i);
		if (c >= CC_UPPER_A && c <= CC_UPPER_Z) c |= 0x20;
		if (c !== lit.charCodeAt(i)) return false;
	}
	return true;
};

/**
 * `s.toLowerCase()` that returns `s` itself (no allocation) when it can't
 * change — no ASCII uppercase and no non-ASCII (whose Unicode case mapping is
 * left to the real `toLowerCase`).
 * @param {string} s string
 * @returns {string} lowercased string
 */
const toLowerCaseIfNeeded = (s) => {
	for (let i = 0; i < s.length; i++) {
		const c = s.charCodeAt(i);
		if ((c >= CC_UPPER_A && c <= CC_UPPER_Z) || c > 127) return s.toLowerCase();
	}
	return s;
};

// Every ASCII uppercase letter in the tail of a name that carries one; the fold
// below is the only place either is used, and only for such a name.
const _ASCII_UPPER_RE = /[A-Z]/g;
/** @type {(one: string) => string} */
const _toAsciiLower = (one) => String.fromCharCode(one.charCodeAt(0) | 0x20);

/**
 * A name CSS matches ASCII case-insensitively — a property, at-keyword,
 * function, pseudo, media feature or unit — written lowercase, so the same name
 * is the same bytes wherever it is spelled. Only ASCII letters map, the matching
 * being ASCII-only; a name carrying an escape is written back as authored, since
 * `\G` and `\g` name different characters.
 * @param {string} s the name
 * @returns {string} it, lowercased
 */
const asciiLowercaseName = (s) => {
	// Only an ASCII capital folds, so the scan for one is the whole test: the
	// native `toLowerCase` copies any one-byte string of eight characters or more.
	let at = -1;
	for (let i = 0; i < s.length; i++) {
		const c = s.charCodeAt(i);
		if (c >= CC_UPPER_A && c <= CC_UPPER_Z) {
			at = i;
			break;
		}
	}
	if (at === -1) return s;
	// An escape names a character by case (`\G` is not `\g`), so a name carrying
	// one is written back as authored.
	if (s.includes("\\")) return s;
	return s.slice(0, at) + s.slice(at).replace(_ASCII_UPPER_RE, _toAsciiLower);
};

/**
 * A custom property name (`<dashed-ident>`): a `--`-prefixed identifier other than bare `--`.
 * @param {string} identifier identifier
 * @returns {boolean} true when identifier is dashed, otherwise false
 */
const isDashedIdentifier = (identifier) =>
	identifier.startsWith("--") && identifier.length >= 3;

// One unescaped `<ident-token>` spelling: no escapes, whitespace or punctuation.
const SIMPLE_IDENTIFIER =
	/^-?(?:[A-Za-z_]|[\u0080-\uFFFF])(?:[\w-]|[\u0080-\uFFFF])*$/;

/**
 * Whether a string is one plain identifier, as a `@value` must be to name a class, at-rule or JS export.
 * @param {string} value candidate text, already trimmed
 * @returns {boolean} true when the whole string is one unescaped identifier
 */
const isSimpleIdentifier = (value) => SIMPLE_IDENTIFIER.test(value);

/**
 * Consume an escaped code point.
 * @param {string} input input
 * @param {number} pos position just past the `\`
 * @returns {number} position past the escape sequence
 */
const _consumeAnEscapedCodePoint = (input, pos) => {
	// The caller has verified the `\` and the next code point form a valid escape.
	// Hex consumes up to 6 digits and one optional whitespace, non-hex one code
	// point, and a `\` at EOF returns `pos` so callers do not overrun.
	if (pos >= input.length) return pos;
	const cc = input.charCodeAt(pos);
	pos++;
	if (pos === input.length) return pos;
	if (isHexDigit(cc)) {
		for (let i = 0; i < 5; i++) {
			if (!isHexDigit(input.charCodeAt(pos))) break;
			pos++;
		}
		const trail = input.charCodeAt(pos);
		if (isAsciiWhitespace(trail)) {
			pos++;
			pos = consumeExtraNewline(trail, input, pos);
		}
	}
	return pos;
};

/**
 * CSS Syntax §4.3.7, entered at the `\` so a caller scanning raw source can
 * step over an escape without re-deriving what it spans.
 * @param {string} input input
 * @param {number} pos position of the `\`
 * @returns {number} position past the escape sequence
 */
const skipEscape = (input, pos) => _consumeAnEscapedCodePoint(input, pos + 1);

/**
 * Spec: "two code points are a valid escape" — first is `\`, second is
 * not a newline.
 * @param {string} input input
 * @param {number} pos position of the second code point
 * @param {number=} f first code point (defaults to `input.charCodeAt(pos - 1)`)
 * @param {number=} s second code point (defaults to `input.charCodeAt(pos)`)
 * @returns {boolean} true, if the two code points form a valid escape
 */
const _ifTwoCodePointsAreValidEscape = (input, pos, f, s) => {
	const first = f || input.charCodeAt(pos - 1);
	const second = s || input.charCodeAt(pos);
	if (first !== CC_REVERSE_SOLIDUS) return false;
	if (_isNewline(second)) return false;
	return true;
};

/**
 * Spec: "three code points would start an ident sequence".
 * @param {string} input input
 * @param {number} pos position
 * @param {number=} f first code point (defaults to `input.charCodeAt(pos - 1)`)
 * @param {number=} s second code point (defaults to `input.charCodeAt(pos)`)
 * @param {number=} t third code point (defaults to `input.charCodeAt(pos + 1)`)
 * @returns {boolean} true, if the three code points start an ident sequence
 */
const _ifThreeCodePointsWouldStartAnIdentSequence = (input, pos, f, s, t) => {
	const first = f || input.charCodeAt(pos - 1);
	const second = s || input.charCodeAt(pos);
	const third = t || input.charCodeAt(pos + 1);
	if (first === CC_HYPHEN_MINUS) {
		return (
			isIdentStartCodePoint(second) ||
			second === CC_HYPHEN_MINUS ||
			_ifTwoCodePointsAreValidEscape(input, pos, second, third)
		);
	}
	if (isIdentStartCodePoint(first)) return true;
	if (first === CC_REVERSE_SOLIDUS) {
		return _ifTwoCodePointsAreValidEscape(input, pos, first, second);
	}
	return false;
};

/**
 * Spec: "three code points would start a number".
 * @param {string} input input
 * @param {number} pos position
 * @param {number=} f first code point
 * @param {number=} s second code point
 * @param {number=} t third code point
 * @returns {boolean} true, if the three code points start a number
 */
const _ifThreeCodePointsWouldStartANumber = (input, pos, f, s, t) => {
	const first = f || input.charCodeAt(pos - 1);
	const second = s || input.charCodeAt(pos);
	const third = t || input.charCodeAt(pos + 1);
	if (first === CC_PLUS_SIGN || first === CC_HYPHEN_MINUS) {
		if (isDigit(second)) return true;
		return second === CC_FULL_STOP && isDigit(third);
	}
	if (first === CC_FULL_STOP) return isDigit(second);
	/* istanbul ignore next -- @preserve: spec-general; every caller passes `pos` just past a +/-/. so `first` is never a bare digit here */
	return isDigit(first);
};

/**
 * Consume an ident sequence (no validation of the first code points).
 * @param {string} input input
 * @param {number} pos position
 * @returns {number} position just past the last ident-sequence code point
 */
const _consumeAnIdentSequence = (input, pos) => {
	// Hot loop: every ident, at-keyword, hash, function name and unit. Both checks
	// are inlined — the ident test to one full-range table load, and the escape
	// test to a following read only where `cc` is a `\`.
	for (;;) {
		const cc = input.charCodeAt(pos) | 0;
		pos++;
		if (_byteClass[cc] === BC_IDENT) {
			continue;
		}
		if (cc === CC_REVERSE_SOLIDUS && !_isNewline(input.charCodeAt(pos))) {
			pos = _consumeAnEscapedCodePoint(input, pos);
			continue;
		}
		return pos - 1;
	}
};

/**
 * @param {number} cc char code
 * @returns {boolean} true, if cc is a non-printable code point
 */
const _isNonPrintableCodePoint = (cc) =>
	(cc >= 0x00 && cc <= 0x08) ||
	cc === 0x0b ||
	(cc >= 0x0e && cc <= 0x1f) ||
	cc === 0x7f;

/**
 * Consume the body of a number per the spec (does not classify integer
 * vs number — caller / token type handles that).
 * @param {string} input input
 * @param {number} pos position at the first numeric / sign code point
 * @returns {number} position just past the number
 */
const _consumeANumber = (input, pos) => {
	let cc = input.charCodeAt(pos);
	if (cc === CC_HYPHEN_MINUS || cc === CC_PLUS_SIGN) {
		pos++;
	}
	while (isDigit(input.charCodeAt(pos))) pos++;
	if (
		input.charCodeAt(pos) === CC_FULL_STOP &&
		isDigit(input.charCodeAt(pos + 1))
	) {
		pos++;
		while (isDigit(input.charCodeAt(pos))) pos++;
	}
	cc = input.charCodeAt(pos);
	if (
		(cc === CC_LOWER_E || cc === CC_UPPER_E) &&
		(((input.charCodeAt(pos + 1) === CC_HYPHEN_MINUS ||
			input.charCodeAt(pos + 1) === CC_PLUS_SIGN) &&
			isDigit(input.charCodeAt(pos + 2))) ||
			isDigit(input.charCodeAt(pos + 1)))
	) {
		pos++;
		cc = input.charCodeAt(pos);
		if (cc === CC_PLUS_SIGN || cc === CC_HYPHEN_MINUS) {
			pos++;
		}
		while (isDigit(input.charCodeAt(pos))) pos++;
	}
	return pos;
};

/**
 * Spec recovery: when the tokenizer realises it's mid-bad-url, consume
 * until `)` or EOF.
 * @param {string} input input
 * @param {number} pos position
 * @returns {number} position past the recovery `)` or EOF
 */
const _consumeTheRemnantsOfABadUrl = (input, pos) => {
	for (;;) {
		if (pos === input.length) return pos;
		const cc = input.charCodeAt(pos);
		pos++;
		if (cc === CC_RIGHT_PARENTHESIS) return pos;
		if (_ifTwoCodePointsAreValidEscape(input, pos)) {
			pos = _consumeAnEscapedCodePoint(input, pos);
		}
	}
};

/**
 * A mutable lexer token. The `next` / `consume` hot path reuses a single
 * instance per `TokenStream` (the lexer writes into it instead of allocating
 * one object per token), which also keeps the parser's `t.type` reads
 * monomorphic. All fields are present from construction so the shape never
 * transitions; type-specific fields (`isId` / `contentStart` / `contentEnd` /
 * `unitStart`) carry stale values for unrelated token types and are only read
 * by `tokenToNode` for the matching type. Pass a fresh one per `readToken` call
 * to collect the raw token list (e.g. tests).
 * @typedef {object} MutableToken
 * @property {number} type a `TokenType`
 * @property {number} start byte offset of the token's first code point
 * @property {number} end byte offset just past the token's last code point
 * @property {boolean} isId hash tokens: starts an ident sequence
 * @property {number} contentStart url tokens: first content code point
 * @property {number} contentEnd url tokens: just past the last content code point
 * @property {number} unitStart dimension tokens: first unit-ident code point
 */

/**
 * @returns {MutableToken} a fresh lexer token with the canonical shape
 */
const createToken = () => ({
	type: TT_EOF,
	start: 0,
	end: 0,
	isId: false,
	contentStart: 0,
	contentEnd: 0,
	unitStart: 0
});

/**
 * Populate `out`'s common fields and return it — the lexer functions' return
 * statement (kept tiny so V8 can inline it).
 * @param {MutableToken} out token to populate
 * @param {number} type a `TokenType`
 * @param {number} start byte offset of the token's first code point
 * @param {number} end byte offset just past the token's last code point
 * @returns {MutableToken} `out`
 */
const fill = (out, type, start, end) => {
	out.type = type;
	out.start = start;
	out.end = end;
	return out;
};

/**
 * Whitespace token. Caller advances past the leading code point so
 * `start = pos - 1`.
 * @param {string} input input
 * @param {number} pos position just past the first whitespace code point
 * @param {MutableToken} out token to populate
 * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
 */
function consumeSpace(input, pos, out) {
	const start = pos - 1;
	while (_byteClass[input.charCodeAt(pos) | 0] === BC_WHITESPACE) pos++;
	return fill(out, TT_WHITESPACE, start, pos);
}

// Sticky fast-forward classes: a native run-skip over the ordinary characters of
// a string or url token, so a long data: URI costs no per-char read. The negated
// classes match exactly the terminators the loops below handle.
const _STRING_SAFE = /[^"'\\\n\r\f]+/y;
// eslint-disable-next-line no-control-regex -- url terminators include the control range and DEL (they make a bad-url)
const _URL_SAFE = /[^\u0000-\u0020\u007F"'()\\]+/y;

/**
 * Consume a string token. Caller advanced past the opening quote so
 * `pos - 1` holds the ending code point and `pos - 1` is the start.
 * @param {string} input input
 * @param {number} pos position just past the opening quote
 * @param {MutableToken} out token to populate
 * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
 */
function consumeAStringToken(input, pos, out) {
	const start = pos - 1;
	const endingCodePoint = input.charCodeAt(pos - 1);
	for (;;) {
		_STRING_SAFE.lastIndex = pos;
		if (_STRING_SAFE.test(input)) pos = _STRING_SAFE.lastIndex;
		if (pos === input.length) {
			return fill(out, TT_STRING, start, pos);
		}
		const cc = input.charCodeAt(pos);
		pos++;
		if (cc === endingCodePoint) {
			return fill(out, TT_STRING, start, pos);
		}
		if (_isNewline(cc)) {
			pos--;
			return fill(out, TT_BAD_STRING_TOKEN, start, pos);
		}
		if (cc === CC_REVERSE_SOLIDUS) {
			// `\` at EOF: string ends here; emit the token so ranges cover all input.
			if (pos === input.length) return fill(out, TT_STRING, start, pos);
			if (_isNewline(input.charCodeAt(pos))) {
				const ccNl = input.charCodeAt(pos);
				pos++;
				pos = consumeExtraNewline(ccNl, input, pos);
			} else if (_ifTwoCodePointsAreValidEscape(input, pos)) {
				pos = _consumeAnEscapedCodePoint(input, pos);
			}
		}
	}
}

/**
 * `#` — hash or delim.
 * @param {string} input input
 * @param {number} pos position just past `#`
 * @param {MutableToken} out token to populate
 * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
 */
function consumeNumberSign(input, pos, out) {
	const start = pos - 1;
	const first = input.charCodeAt(pos);
	const second = input.charCodeAt(pos + 1);
	if (
		_isIdentCodePoint(first) ||
		_ifTwoCodePointsAreValidEscape(input, pos, first, second)
	) {
		const third = input.charCodeAt(pos + 2);
		out.isId = _ifThreeCodePointsWouldStartAnIdentSequence(
			input,
			pos,
			first,
			second,
			third
		);
		pos = _consumeAnIdentSequence(input, pos);
		return fill(out, TT_HASH, start, pos);
	}
	return fill(out, TT_DELIM, start, pos);
}

/**
 * `-` — number / cdc / ident / delim.
 * @param {string} input input
 * @param {number} pos position just past `-`
 * @param {MutableToken} out token to populate
 * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
 */
function consumeHyphenMinus(input, pos, out) {
	// Read the two lookahead code points once; the lead is the known `-`.
	const second = input.charCodeAt(pos);
	const third = input.charCodeAt(pos + 1);
	if (
		_ifThreeCodePointsWouldStartANumber(
			input,
			pos,
			CC_HYPHEN_MINUS,
			second,
			third
		)
	) {
		pos--;
		return consumeANumericToken(input, pos, out);
	}
	if (second === CC_HYPHEN_MINUS && third === CC_GREATER_THAN_SIGN) {
		return fill(out, TT_CDC, pos - 1, pos + 2);
	}
	if (
		_ifThreeCodePointsWouldStartAnIdentSequence(
			input,
			pos,
			CC_HYPHEN_MINUS,
			second,
			third
		)
	) {
		pos--;
		return consumeAnIdentLikeToken(input, pos, out);
	}
	return fill(out, TT_DELIM, pos - 1, pos);
}

/**
 * `.` — number or delim.
 * @param {string} input input
 * @param {number} pos position just past `.`
 * @param {MutableToken} out token to populate
 * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
 */
function consumeFullStop(input, pos, out) {
	const start = pos - 1;
	if (_ifThreeCodePointsWouldStartANumber(input, pos)) {
		pos--;
		return consumeANumericToken(input, pos, out);
	}
	return fill(out, TT_DELIM, start, pos);
}

/**
 * `+` — number or delim.
 * @param {string} input input
 * @param {number} pos position just past `+`
 * @param {MutableToken} out token to populate
 * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
 */
function consumePlusSign(input, pos, out) {
	const start = pos - 1;
	if (_ifThreeCodePointsWouldStartANumber(input, pos)) {
		pos--;
		return consumeANumericToken(input, pos, out);
	}
	return fill(out, TT_DELIM, start, pos);
}

/**
 * Numeric token: number / percentage / dimension.
 * @param {string} input input
 * @param {number} pos position at the first numeric/sign code point
 * @param {MutableToken} out token to populate
 * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
 */
function consumeANumericToken(input, pos, out) {
	const start = pos;
	pos = _consumeANumber(input, pos);
	const first = input.charCodeAt(pos);
	// A unit can only begin with `-`, `\` or an ident-start code point, exactly
	// where §4's "would start an ident sequence" can hold. A plain number, the
	// common case, skips the two lookahead reads and the call.
	if (
		(first === CC_HYPHEN_MINUS ||
			first === CC_REVERSE_SOLIDUS ||
			isIdentStartCodePoint(first)) &&
		_ifThreeCodePointsWouldStartAnIdentSequence(
			input,
			pos,
			first,
			input.charCodeAt(pos + 1),
			input.charCodeAt(pos + 2)
		)
	) {
		out.unitStart = pos;
		pos = _consumeAnIdentSequence(input, pos);
		return fill(out, TT_DIMENSION, start, pos);
	}
	if (first === CC_PERCENTAGE) {
		return fill(out, TT_PERCENTAGE, start, pos + 1);
	}
	return fill(out, TT_NUMBER, start, pos);
}

/**
 * Consume an unquoted url token. Caller has already eaten `url(` and
 * any leading whitespace.
 * @param {string} input input
 * @param {number} pos position at the first content code point
 * @param {number} fnStart byte offset of the `u` in `url(`
 * @param {MutableToken} out token to populate
 * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
 */
function consumeAUrlToken(input, pos, fnStart, out) {
	while (isAsciiWhitespace(input.charCodeAt(pos))) pos++;
	const contentStart = pos;
	out.contentStart = contentStart;
	for (;;) {
		_URL_SAFE.lastIndex = pos;
		if (_URL_SAFE.test(input)) pos = _URL_SAFE.lastIndex;
		if (pos === input.length) {
			out.contentEnd = pos;
			return fill(out, TT_URL, fnStart, pos);
		}
		const cc = input.charCodeAt(pos);
		pos++;
		if (cc === CC_RIGHT_PARENTHESIS) {
			out.contentEnd = pos - 1;
			return fill(out, TT_URL, fnStart, pos);
		}
		if (isAsciiWhitespace(cc)) {
			const end = pos - 1;
			while (isAsciiWhitespace(input.charCodeAt(pos))) pos++;
			if (pos === input.length) {
				out.contentEnd = end;
				return fill(out, TT_URL, fnStart, pos);
			}
			if (input.charCodeAt(pos) === CC_RIGHT_PARENTHESIS) {
				pos++;
				out.contentEnd = end;
				return fill(out, TT_URL, fnStart, pos);
			}
			pos = _consumeTheRemnantsOfABadUrl(input, pos);
			return fill(out, TT_BAD_URL_TOKEN, fnStart, pos);
		}
		if (
			cc === CC_QUOTATION_MARK ||
			cc === CC_APOSTROPHE ||
			cc === CC_LEFT_PARENTHESIS ||
			_isNonPrintableCodePoint(cc)
		) {
			pos = _consumeTheRemnantsOfABadUrl(input, pos);
			return fill(out, TT_BAD_URL_TOKEN, fnStart, pos);
		}
		if (cc === CC_REVERSE_SOLIDUS) {
			if (_ifTwoCodePointsAreValidEscape(input, pos)) {
				pos = _consumeAnEscapedCodePoint(input, pos);
			} else {
				pos = _consumeTheRemnantsOfABadUrl(input, pos);
				return fill(out, TT_BAD_URL_TOKEN, fnStart, pos);
			}
		}
	}
}

/** Longest `url` spelling: each code point as `\` + 6 hex digits + CRLF. */
const MAX_ESCAPED_URL_LENGTH = 3 * (1 + 6 + 2);

/**
 * Whether an ident spans `url` — `\75 rl` names it too, so the unescaped value
 * decides. The gates keep every other function name off the slice.
 * @param {string} input input
 * @param {number} start ident start offset
 * @param {number} end ident end offset (exclusive)
 * @returns {boolean} true when the ident names `url`
 */
const _identNamesUrl = (input, start, end) => {
	const length = end - start;
	if (length === 3) {
		return (
			(input.charCodeAt(start) | 0x20) === CC_LOWER_U &&
			(input.charCodeAt(start + 1) | 0x20) === CC_LOWER_R &&
			(input.charCodeAt(start + 2) | 0x20) === CC_LOWER_L
		);
	}
	if (length < 4 || length > MAX_ESCAPED_URL_LENGTH) return false;
	const first = input.charCodeAt(start);
	if ((first | 0x20) !== CC_LOWER_U && first !== CC_REVERSE_SOLIDUS) {
		return false;
	}
	return asciiCaseInsensitiveMatch(
		unescapeIdentifier(input.slice(start, end)),
		"url"
	);
};

/**
 * Consume an ident-like token: ident / function / url / bad-url.
 * @param {string} input input
 * @param {number} pos position at the first ident-start code point
 * @param {MutableToken} out token to populate
 * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
 */
function consumeAnIdentLikeToken(input, pos, out) {
	const start = pos;
	pos = _consumeAnIdentSequence(input, pos);
	if (
		input.charCodeAt(pos) === CC_LEFT_PARENTHESIS &&
		_identNamesUrl(input, start, pos)
	) {
		pos++;
		const end = pos;
		while (
			isAsciiWhitespace(input.charCodeAt(pos)) &&
			isAsciiWhitespace(input.charCodeAt(pos + 1))
		) {
			pos++;
		}
		if (
			input.charCodeAt(pos) === CC_QUOTATION_MARK ||
			input.charCodeAt(pos) === CC_APOSTROPHE ||
			(isAsciiWhitespace(input.charCodeAt(pos)) &&
				(input.charCodeAt(pos + 1) === CC_QUOTATION_MARK ||
					input.charCodeAt(pos + 1) === CC_APOSTROPHE))
		) {
			// End at `end`, the `(`'s closer, rather than at `pos`: the reader
			// resumes at `token.end`, so the lookahead-eaten whitespace is
			// re-tokenized rather than swallowed.
			return fill(out, TT_FUNCTION, start, end);
		}
		return consumeAUrlToken(input, pos, start, out);
	}
	if (input.charCodeAt(pos) === CC_LEFT_PARENTHESIS) {
		pos++;
		return fill(out, TT_FUNCTION, start, pos);
	}
	return fill(out, TT_IDENTIFIER, start, pos);
}

/**
 * `<` — CDO or delim.
 * @param {string} input input
 * @param {number} pos position just past `<`
 * @param {MutableToken} out token to populate
 * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
 */
function consumeLessThan(input, pos, out) {
	if (
		input.charCodeAt(pos) === CC_EXCLAMATION &&
		input.charCodeAt(pos + 1) === CC_HYPHEN_MINUS &&
		input.charCodeAt(pos + 2) === CC_HYPHEN_MINUS
	) {
		return fill(out, TT_CDO, pos - 1, pos + 3);
	}
	return fill(out, TT_DELIM, pos - 1, pos);
}

/**
 * `@` — at-keyword or delim.
 * @param {string} input input
 * @param {number} pos position just past `@`
 * @param {MutableToken} out token to populate
 * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
 */
function consumeCommercialAt(input, pos, out) {
	const start = pos - 1;
	if (
		_ifThreeCodePointsWouldStartAnIdentSequence(
			input,
			pos,
			input.charCodeAt(pos),
			input.charCodeAt(pos + 1),
			input.charCodeAt(pos + 2)
		)
	) {
		pos = _consumeAnIdentSequence(input, pos);
		return fill(out, TT_AT_KEYWORD, start, pos);
	}
	return fill(out, TT_DELIM, start, pos);
}

/**
 * `\` — escape starts an ident-like token, otherwise it's a delim.
 * @param {string} input input
 * @param {number} pos position just past `\`
 * @param {MutableToken} out token to populate
 * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
 */
function consumeReverseSolidus(input, pos, out) {
	if (_ifTwoCodePointsAreValidEscape(input, pos)) {
		pos--;
		return consumeAnIdentLikeToken(input, pos, out);
	}
	return fill(out, TT_DELIM, pos - 1, pos);
}

// WHY: tokenizer lead-character dispatch (CSS Syntax Level 3 §4 "consume a
// token"). `consumeAToken` picks a sub-routine from each token's first code
// point. §4 keys its rules on code points sitting sparsely across ASCII, so a
// plain `switch (cc)` compiles to a jump table spanning U+0009..U+007D in which
// the commonest lead — an ident-start letter — is not a case at all and arrives
// only once the digit and whitespace tests miss. `_charClass` precomputes a
// dense handler id (`HC_*`, 0..12) per ASCII code point, so the dispatch is one
// array load into a 13-entry jump table and idents reach their handler
// directly. Non-ASCII is always ident-start per §4 and skips the table.
//
// For a spec change, repoint the code point in the build loop below; a new
// sub-routine also needs an `HC_*` id, a `case` in `consumeAToken` and a row
// here. This list is the authoritative map of lead code point to handler:
//
//   HC_WHITESPACE      whitespace      U+0009 TAB  U+000A LF  U+000C FF  U+000D CR  U+0020 SPACE
//   HC_STRING          string start    U+0022 "    U+0027 '
//   HC_SINGLE          one-char token  ( ) , : ; [ ] { }   (its token type comes from `_singleTT`)
//   HC_NUMBER_SIGN     hash / delim    U+0023 #
//   HC_PLUS_SIGN       number / delim  U+002B +
//   HC_HYPHEN_MINUS    number / CDC / ident / delim   U+002D -
//   HC_FULL_STOP       number / delim  U+002E .
//   HC_LESS_THAN       CDO / delim     U+003C <
//   HC_AT_SIGN         at-keyword / delim   U+0040 @
//   HC_REVERSE_SOLIDUS escape / delim  U+005C \
//   HC_DIGIT           number          U+0030..U+0039 0-9
//   HC_IDENT           ident-like      U+0041..U+005A A-Z  U+0061..U+007A a-z  U+005F _  (plus cc >= 128)
//   HC_DELIM           anything else   -> a single <delim-token>
//
// `_singleTT[cc]` is the token type for the HC_SINGLE code points (a second table
// so they share one handler instead of one `case` each). The default class 0 is
// the delim handler (anything not matched below), so it needs no named constant.
const HC_WHITESPACE = 1;
const HC_STRING = 2;
const HC_SINGLE = 3;
const HC_NUMBER_SIGN = 4;
const HC_PLUS_SIGN = 5;
const HC_HYPHEN_MINUS = 6;
const HC_FULL_STOP = 7;
const HC_LESS_THAN = 8;
const HC_AT_SIGN = 9;
const HC_REVERSE_SOLIDUS = 10;
const HC_DIGIT = 11;
const HC_IDENT = 12;
// Full `charCodeAt` range, so the dispatch is one table load with no `cc < 128`
// branch. Every non-ASCII code point is an ident-start lead per §4 and is seeded
// to `HC_IDENT`; the rows below overwrite 0..127 with their real class.
const _charClass = new Uint8Array(0x10000).fill(HC_IDENT, 128);
const _singleTT = new Uint8Array(128);
_singleTT[CC_LEFT_PARENTHESIS] = TT_LEFT_PARENTHESIS;
_singleTT[CC_RIGHT_PARENTHESIS] = TT_RIGHT_PARENTHESIS;
_singleTT[CC_COMMA] = TT_COMMA;
_singleTT[CC_COLON] = TT_COLON;
_singleTT[CC_SEMICOLON] = TT_SEMICOLON;
_singleTT[CC_LEFT_SQUARE] = TT_LEFT_SQUARE_BRACKET;
_singleTT[CC_RIGHT_SQUARE] = TT_RIGHT_SQUARE_BRACKET;
_singleTT[CC_LEFT_CURLY] = TT_LEFT_CURLY_BRACKET;
_singleTT[CC_RIGHT_CURLY] = TT_RIGHT_CURLY_BRACKET;
// Each ASCII code point belongs to exactly one class; HC_SINGLE is seeded from
// `_singleTT` above, the rest follow §4's lead-code-point rules, and everything
// unmatched stays the delim class (0). Keep this in sync with the table above.
for (let i = 0; i < 128; i++) {
	if (_singleTT[i] !== 0) {
		_charClass[i] = HC_SINGLE;
	} else if (isAsciiWhitespace(i)) {
		_charClass[i] = HC_WHITESPACE;
	} else if (i === CC_QUOTATION_MARK || i === CC_APOSTROPHE) {
		_charClass[i] = HC_STRING;
	} else if (i === CC_NUMBER_SIGN) {
		_charClass[i] = HC_NUMBER_SIGN;
	} else if (i === CC_PLUS_SIGN) {
		_charClass[i] = HC_PLUS_SIGN;
	} else if (i === CC_HYPHEN_MINUS) {
		_charClass[i] = HC_HYPHEN_MINUS;
	} else if (i === CC_FULL_STOP) {
		_charClass[i] = HC_FULL_STOP;
	} else if (i === CC_LESS_THAN_SIGN) {
		_charClass[i] = HC_LESS_THAN;
	} else if (i === CC_AT_SIGN) {
		_charClass[i] = HC_AT_SIGN;
	} else if (i === CC_REVERSE_SOLIDUS) {
		_charClass[i] = HC_REVERSE_SOLIDUS;
	} else if (isDigit(i)) {
		_charClass[i] = HC_DIGIT;
	} else if (isIdentStartCodePoint(i)) {
		_charClass[i] = HC_IDENT;
	}
	// else stays the delim class (0)
}

/**
 * Per-character dispatcher. The outer loop has already advanced past
 * the lead code point (`pos - 1` is the lead).
 * @param {string} input input
 * @param {number} pos position just past the lead code point
 * @param {number} cc the lead code point (`input.charCodeAt(pos - 1)`, already read by the caller)
 * @param {MutableToken} out token to populate
 * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
 */
function consumeAToken(input, pos, cc, out) {
	// `u` / `U` would start a unicode-range token in the spec; those are not
	// produced, so they map to HC_IDENT and fall through to ident-like.
	switch (_charClass[cc]) {
		// Run of whitespace → one <whitespace-token>.
		case HC_WHITESPACE:
			return consumeSpace(input, pos, out);
		// `"` / `'` → <string-token> (or <bad-string-token> on a raw newline).
		case HC_STRING:
			return consumeAStringToken(input, pos, out);
		// One-code-point token: its type is looked up in `_singleTT` (the `(` `)`
		// `,` `:` `;` `[` `]` `{` `}` set), so all of them share this arm.
		case HC_SINGLE:
			return fill(out, _singleTT[cc], pos - 1, pos);
		// `#` → <hash-token> if an ident/escape follows, else a <delim-token>.
		case HC_NUMBER_SIGN:
			return consumeNumberSign(input, pos, out);
		// `+` → <number-token> if it starts a number, else a <delim-token>.
		case HC_PLUS_SIGN:
			return consumePlusSign(input, pos, out);
		// `-` → number / <CDC-token> (`-->`) / ident / <delim-token>.
		case HC_HYPHEN_MINUS:
			return consumeHyphenMinus(input, pos, out);
		// `.` → <number-token> if a digit follows, else a <delim-token>.
		case HC_FULL_STOP:
			return consumeFullStop(input, pos, out);
		// `<` → <CDO-token> (`<!--`), else a <delim-token>.
		case HC_LESS_THAN:
			return consumeLessThan(input, pos, out);
		// `@` → <at-keyword-token> if an ident follows, else a <delim-token>.
		case HC_AT_SIGN:
			return consumeCommercialAt(input, pos, out);
		// `\` → ident-like token if it's a valid escape, else a <delim-token>.
		case HC_REVERSE_SOLIDUS:
			return consumeReverseSolidus(input, pos, out);
		// Digit → numeric token; `pos - 1` re-includes the digit the caller passed.
		case HC_DIGIT:
			return consumeANumericToken(input, pos - 1, out);
		// Ident-start (letter / `_` / non-ASCII, incl. `u`/`U`) → ident / function /
		// url token; `pos - 1` re-includes the lead code point.
		case HC_IDENT:
			return consumeAnIdentLikeToken(input, pos - 1, out);
		default:
			// HC_DELIM. EOF is impossible here (caller guarded with the outer
			// loop's `pos < input.length` check). Anything else: a <delim-token>.
			return fill(out, TT_DELIM, pos - 1, pos);
	}
}

/**
 * Read one raw token (comment / whitespace / value token) starting at byte
 * `pos`, writing it into the caller-supplied `out` and returning `out`. The
 * token's `end` is the next read position. Returns `undefined` at end-of-input —
 * `pos >= length`, an unterminated comment, or a string ending on a trailing
 * escape. This is the one tokenizer entry point: `TokenStream#next` reuses a
 * single `out` across calls so the parse hot path allocates no per-token object,
 * while `HtmlGenerator`'s `<style>` scan and the tests loop over it with their
 * own `out`. Comment tokens are returned here; `next` filters them.
 * @param {string} input input
 * @param {number} pos byte offset to read from
 * @param {MutableToken} out token to populate
 * @returns {MutableToken | undefined} the token, or undefined at EOF
 */
function readToken(input, pos, out) {
	if (pos >= input.length) return undefined;
	const cc = input.charCodeAt(pos);
	// One-code-point token: filled without reaching the dispatch, mirroring
	// `TokenStream#next`. `/` is a delim, never HC_SINGLE, so this stays ahead of
	// the comment check.
	if (_charClass[cc] === HC_SINGLE) {
		return fill(out, _singleTT[cc], pos, pos + 1);
	}
	// Comment: `/*…*/` is yielded as a token (`TokenStream#next` steps over it).
	if (cc === CC_SOLIDUS && input.charCodeAt(pos + 1) === CC_ASTERISK) {
		const start = pos;
		// Jump to the closing `*/` in one native scan instead of a per-character
		// loop — comment bodies (license banners, source comments) can be long.
		// No close: unterminated comment runs to EOF so ranges cover all input.
		const close = input.indexOf("*/", pos + 2);
		return fill(
			out,
			TT_COMMENT,
			start,
			close === -1 ? input.length : close + 2
		);
	}
	// `consumeAToken` dispatches on the lead code point at `pos` (it expects the
	// position just past the lead and the already-read lead code point).
	return consumeAToken(input, pos + 1, cc, out);
}

// AST shape mirrors tabatkins/parse-css (the CSS Syntax Level 3 reference), with two deviations: nodes carry a `range` byte offset pair + a lazy `loc` getter, and have no methods beyond it.

/**
 * AST node / leaf-token `type` discriminators (spec name where it has one, else
 * parse-css's PascalCase). Numeric for the same reasons as the `TT_*` token
 * constants: a compact `Node#type` slot and integer `===` / `Map` keys on the
 * visitor hot path. Kept as a `NodeType` namespace (not bare constants) because
 * consumers reference members as `NodeType.AtRule`; exported so visitor maps
 * (`SourceProcessor#use`) and `CssParser` name nodes instead of a string
 * literal. A lexer token type never reaches a `Node#type`.
 * @type {{ Ident: 1, Function: 2, AtKeyword: 3, Hash: 4, String: 5, BadString: 6, Url: 7, BadUrl: 8, Delim: 9, Number: 10, Percentage: 11, Dimension: 12, Whitespace: 13, Colon: 14, Semicolon: 15, Comma: 16, RightParenthesis: 17, RightSquareBracket: 18, RightCurlyBracket: 19, CDO: 20, CDC: 21, SimpleBlock: 22, Declaration: 23, AtRule: 24, QualifiedRule: 25, Stylesheet: 26, Comment: 27, Raw: 28 }}
 */
const NodeType = {
	Ident: 1,
	Function: 2,
	AtKeyword: 3,
	Hash: 4,
	String: 5,
	BadString: 6,
	Url: 7,
	BadUrl: 8,
	Delim: 9,
	Number: 10,
	Percentage: 11,
	Dimension: 12,
	Whitespace: 13,
	Colon: 14,
	Semicolon: 15,
	Comma: 16,
	// Preserved tokens for stray closers / CDO / CDC (kept as component values per §5.4.8 "consume a token and return it").
	RightParenthesis: 17,
	RightSquareBracket: 18,
	RightCurlyBracket: 19,
	CDO: 20,
	CDC: 21,
	SimpleBlock: 22,
	Declaration: 23,
	AtRule: 24,
	QualifiedRule: 25,
	Stylesheet: 26,
	// Comments are never tree nodes; this type exists only so a `NodeType.Comment`
	// visitor can be registered (fired during tokenization — see `grammar`).
	Comment: 27,
	// Not a spec node but a workaround: §5.4 discards input a block's contents
	// reject, and keeping the source is what stops minifying from losing what the
	// unminified asset shows. Built while printing, so a walk never sees one.
	Raw: 28
};

/**
 * The parts of a node read with `field`, `fieldCount` and `fieldNamed`, named
 * as CSS Syntax §5 names them. All four are lists.
 * @type {{ prelude: 1, value: 2, declarations: 3, rules: 4 }}
 */
const Field = {
	// An at-rule's or qualified rule's component values before its block.
	prelude: 1,
	// A declaration's, function's or simple block's component values.
	value: 2,
	// A rule's block: its declarations, then the rules nested in it.
	declarations: 3,
	// A stylesheet's top-level rules, or the rules nested in a rule's block.
	rules: 4
};

/**
 * What `flag` answers: a declaration's `!important`, a number's `integer` type
 * flag and a hash's `id` type flag (CSS Syntax §4.2).
 * @type {{ important: 1, integer: 2, id: 4 }}
 */
const Flag = {
	important: 1,
	integer: 2,
	id: 4
};

/**
 * The parts of a node read with `rangeOf` and `textOf`.
 * @type {{ name: 1, value: 2, block: 3 }}
 */
const Part = {
	// An at-rule's (after the `@`), declaration's or function's name; a dimension's unit.
	name: 1,
	// A `url()`'s contents.
	value: 2,
	// A rule's `{…}` block.
	block: 3
};

/**
 * Base AST node — the property-accessor view the `parseA*` entry points return
 * (see `_makeReader`). Every concrete node carries the `[start, end)` byte
 * `range` of the source slice it covers; `loc` is computed on demand from the
 * shared `LocConverter`, so line/column conversion is only paid when a consumer
 * needs it. The concrete node typedefs below extend this via `&`.
 *
 * Inside the parser a node ref is an integer id into the columns; the reader
 * exposes this property shape over a retained snapshot of those columns.
 * @typedef {object} Node
 * @property {number} type node-type discriminator
 * @property {number} start byte offset of the node's first code point
 * @property {number} end byte offset just past the node's last code point
 * @property {[number, number]} range the `[start, end)` byte range
 * @property {{ start: { line: number, column: number }, end: { line: number, column: number } }} loc source location (1-based line, 0-based column)
 * @property {() => string} toString source slice for this node
 * @property {string} unescapedName name with CSS escapes resolved (name-bearing nodes only)
 */

/**
 * @param {string} s numeric text
 * @returns {"+" | "-" | ""} the spec sign ("" when unsigned)
 */
const _signOf = (s) => {
	const c = s.charCodeAt(0);
	return c === CC_PLUS_SIGN ? "+" : c === CC_HYPHEN_MINUS ? "-" : "";
};

/**
 * @param {string} s numeric text (no unit / `%`)
 * @returns {"integer" | "number"} the spec type flag
 */
const _typeFlagOf = (s) =>
	s.includes(".") || s.includes("e") || s.includes("E") ? "number" : "integer";

/**
 * @param {number} i a number, percentage or dimension's index
 * @returns {boolean} whether its type flag is "integer"
 */
const _isIntegerToken = (i) => {
	const v = _valueOf(i);
	return (
		_typeFlagOf(
			_types[i] === NodeType.Dimension ? v.slice(0, _consumeANumber(v, 0)) : v
		) === "integer"
	);
};

/**
 * @param {number} i a hash's index
 * @returns {boolean} whether its type flag is "id" (it would start an ident)
 */
const _isIdHash = (i) => {
	const input = _S._input;
	const p = _starts[i] + 1;
	return _ifThreeCodePointsWouldStartAnIdentSequence(
		input,
		p,
		input.charCodeAt(p),
		input.charCodeAt(p + 1),
		input.charCodeAt(p + 2)
	);
};

/**
 * @param {number} i a dimension's index
 * @returns {string} its unit, escapes resolved
 */
const _unitOf = (i) => {
	const v = _valueOf(i);
	const unit = v.slice(_consumeANumber(v, 0));
	const ranOut = _ends[i] === _S._input.length;
	return unescapeIdentifier(
		ranOut ? _resolveTrailingEscape(unit, false) : unit
	);
};

/**
 * @param {Node} n node
 * @returns {SimpleBlockToken} a simple block's opening bracket ("" for other nodes)
 */
const _blockTokenOf = (n) => {
	const i = _nodeIndex(n);
	return _types[i] === NodeType.SimpleBlock
		? /** @type {SimpleBlockToken} */ (_S._input[_starts[i]])
		: /** @type {SimpleBlockToken} */ ("");
};

/**
 * Leaf token node (property-accessor view) — `value` is the raw source slice
 * (identifier text, quoted string including quotes, a dimension's full `123px`,
 * …; hash / at-keyword drop their `#` / `@` prefix, url uses its content range).
 * The `NumberToken` / `HashToken` / `UrlToken` / `DimensionToken` typedefs below
 * narrow the value accessors. `numericValue` / `typeFlag` / `sign` / `unit` are
 * derived from the source on read and are only meaningful on the matching token
 * type; `contentStart` / `contentEnd` mark a url token's inner content range.
 * @typedef {Node & { value: string, unescaped: string, numericValue: number, typeFlag: "integer" | "number" | "id" | "unrestricted", sign: "+" | "-" | "", unit: string, contentStart: number, contentEnd: number }} Token
 */

/**
 * Number token (`123`, `-1.5`, `+2e3`). `value` is the raw source slice (the spec's "value"); `numericValue` / `typeFlag` / `sign` are lazy getters derived from it (see `Token`).
 * @typedef {Token & { numericValue: number, typeFlag: "integer" | "number", sign: "+" | "-" | "" }} NumberToken
 */

/**
 * Percentage token (`50%`). `value` is the raw slice including `%`; `numericValue` (without `%`) and `sign` are lazy getters.
 * @typedef {Token & { numericValue: number, sign: "+" | "-" | "" }} PercentageToken
 */

/**
 * Dimension token (`100px`, `1.5em`). `value` is the raw slice (number + unit); `numericValue` / `typeFlag` / `sign` (of the numeric part) and `unit` (lower-cased) are lazy getters.
 * @typedef {Token & { numericValue: number, typeFlag: "integer" | "number", sign: "+" | "-" | "", unit: string }} DimensionToken
 */

// Spec "Assert: …" preconditions are comments only (callers satisfy them); a future `strict` option could reinstate them as throws.

/**
 * Hash token (`#foo`). `value` is the name without the leading `#`; `typeFlag` is the spec type flag ("id" when the name forms a valid `<id>` selector, "unrestricted" otherwise).
 * @typedef {Token & { typeFlag: "id" | "unrestricted" }} HashToken
 */

/**
 * Old-style unquoted URL token (`url(unquoted)`). `value` is the unquoted body;
 * `contentStart` / `contentEnd` mark the inner content range in the source.
 * @typedef {Token & { contentStart: number, contentEnd: number }} UrlToken
 */

/**
 * Function node: `name(component-values...)`. `name` is the raw source slice
 * before the `(` (callers lowercase / unescape as needed); `nameStart` / `nameEnd`
 * are its `[start, end)` byte offsets; `value` is the component values inside the parentheses.
 * @typedef {Node & { name: string, nameStart: number, nameEnd: number, value: ComponentValue[] }} FunctionNode
 */

/** @typedef {"[" | "(" | "{"} SimpleBlockToken */

/**
 * Simple block (`[...]`, `(...)` not preceded by an ident, `{...}`). `token` is
 * the opening character. `value` is the component values inside. This shape is
 * produced by `consumeASimpleBlock` (§5.4.9) and appears in preludes.
 *
 * Note: `consumeABlock` (§5.4.4) returns the parsed block's separate `decls` /
 * `rules` lists (per §5.4.5), not a SimpleBlock wrapper — see
 * `AtRule` / `QualifiedRule`'s `declarations` and `childRules` fields.
 * @typedef {Node & { token: SimpleBlockToken, value: ComponentValue[] }} SimpleBlock
 */

/**
 * A CSS component value (CSS Syntax §5.4.8): a preserved token, a function, or
 * a simple block (`Token` also covers `HashToken` / `UrlToken`).
 * @typedef {Token | FunctionNode | SimpleBlock} ComponentValue
 */

/**
 * A CSS rule — an at-rule or a qualified rule.
 * @typedef {AtRule | QualifiedRule} Rule
 */

/**
 * Declaration: `name: value [!important][;]`. `name` is the raw property-name
 * slice; `value` is the trimmed component-value list (whitespace stripped from
 * both ends); `important` records a stripped `!important`.
 * @typedef {Node & { name: string, nameStart: number, nameEnd: number, value: ComponentValue[], important: boolean }} Declaration
 */

/**
 * At-rule: `@name <prelude> ;` or `@name <prelude> { ... }`. `name` is the
 * at-keyword without the leading `@`; `prelude` is the component values up to
 * the at-rule's `;` / block / enclosing `}`. Per §5.4.2 the block is consumed
 * into separate `declarations` (a `Declaration[]`) and `childRules` (a `Rule[]`,
 * each an at-rule or qualified rule); both are `null` for a `;`-terminated
 * at-rule. `blockStart` / `blockEnd` are the `{` start / `}` end offsets
 * (webpack extension, not in spec; the spec doesn't track brace positions), or
 * `-1` / `-1` when there is no block. `range[1]` points past `}` for a block, or
 * at the `;` / `}` / EOF position otherwise (callers check the byte at `range[1]`
 * to tell them apart).
 * @typedef {Node & { name: string, nameStart: number, nameEnd: number, prelude: ComponentValue[], declarations: Declaration[] | null, childRules: Rule[] | null, blockStart: number, blockEnd: number }} AtRule
 */

/**
 * Qualified rule: `<prelude> { <block> }`. `prelude` is the component values
 * before the `{` (selectors, keyframe parameters, …); `declarations` and
 * `childRules` are the parsed `{ ... }` body (split per tabatkins/parse-css.js
 * reference impl), or both `null` when EOF was hit before `{`. `blockStart` /
 * `blockEnd` are the `{` start / `}` end offsets (webpack extension), or `-1` /
 * `-1` when there is no block.
 * @typedef {Node & { prelude: ComponentValue[], declarations: Declaration[] | null, childRules: Rule[] | null, blockStart: number, blockEnd: number }} QualifiedRule
 */

/**
 * Stylesheet (CSS Syntax §5.3.4): the result of `parseAStylesheet`. `rules`
 * holds the top-level at-rules / qualified rules (top-level declarations are
 * parse errors and never produced).
 * @typedef {Node & { rules: Rule[] }} Stylesheet
 */

// Lexer-token-type to AST-node-type map. One `_makeLeaf` call site, rather than
// a ~20-case switch allocating in each arm, keeps V8 monomorphic — the switch
// form showed up as generic stubs in profiles. URL carries extra state.
const _ttToNodeType = new Uint8Array(27);
_ttToNodeType[TT_WHITESPACE] = NodeType.Whitespace;
_ttToNodeType[TT_IDENTIFIER] = NodeType.Ident;
_ttToNodeType[TT_STRING] = NodeType.String;
_ttToNodeType[TT_DELIM] = NodeType.Delim;
_ttToNodeType[TT_NUMBER] = NodeType.Number;
_ttToNodeType[TT_PERCENTAGE] = NodeType.Percentage;
_ttToNodeType[TT_DIMENSION] = NodeType.Dimension;
_ttToNodeType[TT_HASH] = NodeType.Hash;
_ttToNodeType[TT_AT_KEYWORD] = NodeType.AtKeyword;
_ttToNodeType[TT_BAD_STRING_TOKEN] = NodeType.BadString;
_ttToNodeType[TT_BAD_URL_TOKEN] = NodeType.BadUrl;
_ttToNodeType[TT_COLON] = NodeType.Colon;
_ttToNodeType[TT_COMMA] = NodeType.Comma;
_ttToNodeType[TT_SEMICOLON] = NodeType.Semicolon;
_ttToNodeType[TT_RIGHT_PARENTHESIS] = NodeType.RightParenthesis;
_ttToNodeType[TT_RIGHT_SQUARE_BRACKET] = NodeType.RightSquareBracket;
_ttToNodeType[TT_RIGHT_CURLY_BRACKET] = NodeType.RightCurlyBracket;
_ttToNodeType[TT_CDO] = NodeType.CDO;
_ttToNodeType[TT_CDC] = NodeType.CDC;

// WHY: AST construction. Nodes live in one struct-of-arrays store — a node ref
// is an integer id into parallel typed-array columns — so nothing is allocated
// per node, and the `_make*` / `_set*` primitives below write those columns
// directly. The streaming `grammar` walks each top-level node and recycles the
// columns, while the `parseA*` entry points retain them as a snapshot and hand
// back property-accessor nodes over it. Child lists are arrays of ids in both.

// WHY: the active skip state from `CssProcessOptions.skip`, applied by the
// grammar. `_skipTypes` is indexed by `NodeType`, dropping that component value
// from declaration value and function-arg lists, and the two prelude flags scan
// a rule's prelude without materializing its tree — url tokens and functions
// stay, so `url()` in a selector still resolves. A skipped node is still
// tokenized, keeping positions correct, but never pushed, so it is never walked
// or read: a caller must only skip what nothing reads. `parseA*` leave these at
// their no-skip defaults and build the full tree.
const _NO_SKIP_TYPES = new Uint8Array(32);
// Shared frozen empty list for a block body with no declarations or no child
// rules, the common case. Every consumer reads these lists read-only, so one
// instance replaces an empty array per rule, and freezing makes a push fail.
const _EMPTY_LIST = /** @type {Rule[]} */ (
	/** @type {Rule[] | ReadonlyArray<never>} */ (Object.freeze([]))
);
// `consumeABlock`'s and `consumeABlocksContentsInto`'s results, written instead
// of returned — see there. Read them immediately; the next block overwrites them.
/** @type {Declaration[]} */
let _blockDecls = /** @type {Declaration[]} */ (
	/** @type {Declaration[] | Rule[]} */ (_EMPTY_LIST)
);
/** @type {Rule[]} */
let _blockRules = _EMPTY_LIST;
let _blockStart = 0;
let _blockEnd = 0;
/** @type {Declaration[]} */
let _bcDecls = /** @type {Declaration[]} */ (
	/** @type {Declaration[] | Rule[]} */ (_EMPTY_LIST)
);
/** @type {Rule[]} */
let _bcRules = _EMPTY_LIST;
// Whether the block just consumed streamed: read with `_bcDecls` / `_bcRules`.
// A frame slot cannot answer this, because a block that never streamed may never
// have written one (see `_streamPublishFrame`).
let _bcStreamed = false;
/** @type {Uint8Array} */
let _skipTypes = _NO_SKIP_TYPES;
// Fast-path flag: true only when a real skip set is active, so the (dominant)
// no-skip parses pay one boolean test instead of a node-type lookup per value.
let _skipActive = false;
let _skipSelectorPrelude = false;
let _skipAtRulePrelude = false;

// Scratch content-list pool: a container's value or prelude is built in a plain
// array, then sealed into the flat value buffer by `_setValue`, which returns it
// to the pool. A list that is never sealed simply falls out of the pool.
/** @type {Node[][]} */
const _listPool = [];
const _takeList = () =>
	_listPool.length > 0
		? /** @type {Node[]} */ (_listPool.pop())
		: /** @type {Node[]} */ ([]);

// WHY: the struct-of-arrays store — nodes live in reused typed-array columns. A
// node ref is its integer id and fields live in parallel arrays indexed by it,
// with two reused int slots (`_aux0/1`) and a flags byte for the per-type
// extras, and child lists on three object arrays. Aux slot meaning by type:
//   url:         aux0 contentStart, aux1 contentEnd
//   function:    aux0 nameEnd
//   declaration: aux0 nameEnd, flags bit0 important
//   at-rule:     aux0 nameEnd, aux1 blockStart (blockEnd == end)
//   qualified:   aux1 blockStart (blockEnd == end)
// `name` / `nameStart` / a simple block's `token` are derived from the source
// on read (see the accessors), so they need no slot. A node's main content
// (value | prelude | stylesheet rules) is a `_flat` span (see below).
// `grammar` resets `_nodeCount` to 0 after each top-level rule's walk, so the
// buffers are reused across rules and the parse allocates almost nothing.
let _capacity = 0;
let _nodeCount = 0;
// Released columns all share these: growth replaces a column before any write.
const EMPTY_U8 = new Uint8Array(0);
const EMPTY_I32 = new Int32Array(0);
let _types = EMPTY_U8;
let _starts = EMPTY_I32;
let _ends = EMPTY_I32;
let _aux0 = EMPTY_I32;
let _aux1 = EMPTY_I32;
let _flags = EMPTY_U8;
// Content-list spans: a container's value / prelude is `_flat[start, start+len)`
// (node refs), recycled per top-level rule like the node columns.
let _listStarts = EMPTY_I32;
let _listLens = EMPTY_I32;
let _flat = EMPTY_I32;
let _flatTop = 0;
// Peak usage of the current parse, and use-once regrow hints: after an
// over-capacity shrink the next grow jumps straight back to the previous
// parse's peak (one exact-fit allocation instead of re-doubling up).
let _peak = 0;
let _flatPeak = 0;
let _growHint = 0;
let _flatGrowHint = 0;

/** @param {number} need minimum flat-buffer capacity */
const _flatGrow = (need) => {
	let cap = _flat.length || 4096;
	if (_flatGrowHint > cap) cap = _flatGrowHint;
	_flatGrowHint = 0;
	while (cap < need) cap *= 2;
	const next = new Int32Array(cap);
	next.set(_flat);
	_flat = next;
};
// Rule bodies sit behind an id-indexed Int32 column holding `1 + index` into two
// dense append-only arrays, since scattered id-indexed stores degrade a plain
// array to dictionary elements. Reassigned, not mutated, on a `parseA*`.
let _bodyIdx = EMPTY_I32;
/** @type {Node[][]} */
let _declBodies = [];
/** @type {Node[][]} */
let _ruleBodies = [];

let _locConverter = /** @type {LocConverter} */ (
	/** @type {LocConverter | null} */ (null)
);
// The caller's input before §3.3 replaced its NULs, the same length, which
// `A.source` slices so a node reads exactly as written.
let _originalInput = "";

// Node refs are integers here but typed `Node` across the parser; these are
// identity casts that just satisfy the type system at the boundary.
/** @type {(n: Node) => number} */
const _nodeIndex = (n) =>
	/** @type {number} */ (/** @type {number | Node} */ (n));
/** @type {(i: number) => Node} */
const _nodeRef = (i) => /** @type {Node} */ (/** @type {Node | number} */ (i));

/** @param {number} need minimum capacity */
const _grow = (need) => {
	let cap = _capacity || 4096;
	if (_growHint > cap) cap = _growHint;
	_growHint = 0;
	while (cap < need) cap *= 2;
	const ty = new Uint8Array(cap);
	ty.set(_types);
	_types = ty;
	const st = new Int32Array(cap);
	st.set(_starts);
	_starts = st;
	const en = new Int32Array(cap);
	en.set(_ends);
	_ends = en;
	const a0 = new Int32Array(cap);
	a0.set(_aux0);
	_aux0 = a0;
	const a1 = new Int32Array(cap);
	a1.set(_aux1);
	_aux1 = a1;
	const fl = new Uint8Array(cap);
	fl.set(_flags);
	_flags = fl;
	const ls = new Int32Array(cap);
	ls.set(_listStarts);
	_listStarts = ls;
	const ll = new Int32Array(cap);
	ll.set(_listLens);
	_listLens = ll;
	const bi = new Int32Array(cap);
	bi.set(_bodyIdx);
	_bodyIdx = bi;
	// Grown by pushing so it stays a fast holey array however far apart ids are.
	while (_ruleEntries.length < cap) _ruleEntries.push(undefined);
	_capacity = cap;
};
/** @type {(type: number, start: number, end: number) => Node} */
const _makeLeaf = (type, start, end) => {
	// Ids are 1-based, since a node ref is used in truthiness checks and 0 has to
	// stay "no node". Leaves never read the flag or list slots, which
	// `_makeContainer` clears instead, keeping a leaf at three writes.
	const i = _nodeCount + 1;
	if (i >= _capacity) _grow(i + 1);
	_types[i] = type;
	_starts[i] = start;
	_ends[i] = end;
	_nodeCount = i;
	return _nodeRef(i);
};
/** @type {(type: number, start: number, end: number) => Node} */
const _makeContainer = (type, start, end) => {
	const r = _makeLeaf(type, start, end);
	const i = _nodeIndex(r);
	_flags[i] = 0;
	// WHY: clearing the content-span length stops a reused id exposing a previous
	// node's children, and suffices because content lists are flat spans.
	// `blockStart` (aux1) is deliberately not defaulted: only at-rules and
	// qualified rules carry a block and they set it on every return path.
	// `blockEnd` is not stored at all — a block rule's `end` is its `blockEnd`.
	_listLens[i] = 0;
	// The body slot is read only for rules (the walk guards on type), so only
	// rules clear it — a recycled id must never expose a previous rule's body.
	if (type === NodeType.AtRule || type === NodeType.QualifiedRule) {
		_bodyIdx[i] = 0;
	}
	return r;
};
// The types `_makeContainer` builds, the only nodes whose list slots are
// written: a leaf's are a recycled id's leftovers, so it reads no children.
const _HAS_CHILD_LIST = new Uint8Array(32);
// The types whose name slot (`_aux0`) holds where their name ends.
const _HAS_NAME = new Uint8Array(32);
_HAS_NAME[NodeType.AtRule] = 1;
_HAS_NAME[NodeType.Declaration] = 1;
_HAS_NAME[NodeType.Function] = 1;
for (const type of [
	NodeType.Stylesheet,
	NodeType.AtRule,
	NodeType.QualifiedRule,
	NodeType.Declaration,
	NodeType.SimpleBlock,
	NodeType.Function
]) {
	_HAS_CHILD_LIST[type] = 1;
}
// The `Field` a type's child list is read as (0 for the types without one).
const _LIST_FIELD = new Uint8Array(32);
_LIST_FIELD[NodeType.Stylesheet] = Field.rules;
_LIST_FIELD[NodeType.AtRule] = Field.prelude;
_LIST_FIELD[NodeType.QualifiedRule] = Field.prelude;
_LIST_FIELD[NodeType.Declaration] = Field.value;
_LIST_FIELD[NodeType.SimpleBlock] = Field.value;
_LIST_FIELD[NodeType.Function] = Field.value;

/**
 * A rule's block declarations as the array the parser holds; for the minifier
 * and printer, which reorder and merge them. Empty on a streamed block.
 * @param {Node} n node
 * @returns {Declaration[] | null} the declarations (null without a block)
 */
const _declarationsOf = (n) => {
	const bi = _bodyIdx[_nodeIndex(n)];
	return bi === 0 ? null : /** @type {Declaration[]} */ (_declBodies[bi - 1]);
};

/**
 * The rules nested in a rule's block, as {@link _declarationsOf} reads them.
 * @param {Node} n node
 * @returns {Rule[] | null} the rules (null without a block)
 */
const _rulesOf = (n) => {
	const bi = _bodyIdx[_nodeIndex(n)];
	return bi === 0 ? null : /** @type {Rule[]} */ (_ruleBodies[bi - 1]);
};

/**
 * @param {Node} n node
 * @returns {boolean} whether a declaration is `!important`
 */
const _isImportant = (n) => (_flags[_nodeIndex(n)] & Flag.important) !== 0;
// Raw token value (the lazy `Token.value` form): hash / at-keyword drop their
// one-char prefix, url uses its content range. Shared by the parser's
// mid-parse reads and the accessor.
/**
 * @param {number} i node id
 * @returns {string} raw token value
 */
const _valueOf = (i) => {
	const ty = _types[i];
	if (ty === NodeType.Hash || ty === NodeType.AtKeyword) {
		return _S._input.slice(_starts[i] + 1, _ends[i]);
	}
	if (ty === NodeType.Url) return _S._input.slice(_aux0[i], _aux1[i]);
	return _S._input.slice(_starts[i], _ends[i]);
};
// Module-level constants (not per-parse closures), so each consume-algorithm
// call site keeps one function identity and stays monomorphic.
/** @type {(start: number, end: number, contentStart: number, contentEnd: number) => Node} */
const _makeUrl = (start, end, cs, ce) => {
	const r = _makeLeaf(NodeType.Url, start, end);
	_aux0[_nodeIndex(r)] = cs;
	_aux1[_nodeIndex(r)] = ce;
	return r;
};
/** @type {(start: number) => Node} */
const _makeStylesheet = (start) =>
	_makeContainer(NodeType.Stylesheet, start, start);
// Workaround node (see `NodeType.Raw`), trimmed of the whitespace the block's
// own separators already cover; an all-whitespace span yields no node.
/** @type {(start: number, end: number) => Node | undefined} */
const _makeRaw = (start, end) => {
	let from = start;
	let to = end;
	while (from < to && isAsciiWhitespace(_S._input.charCodeAt(to - 1))) to--;
	while (from < to && isAsciiWhitespace(_S._input.charCodeAt(from))) from++;
	return from === to ? undefined : _makeLeaf(NodeType.Raw, from, to);
};
// name / nameStart are derived from start + nameEnd; only nameEnd is stored.
/** @type {(r: Node, nameStart: number, nameEnd: number) => void} */
const _setName = (r, ns, ne) => {
	_aux0[_nodeIndex(r)] = ne;
};
/** @type {(r: Node, v: number) => void} */
const _setEnd = (r, v) => {
	_ends[_nodeIndex(r)] = v;
};
// `blockEnd` is not stored: for a block rule the parser sets `end` to the
// block end right after this (so `A.blockEnd` reads `end`); a `-1` blockStart
// marks the no-block forms, where `blockEnd` derives to `-1`.
/** @type {(r: Node, blockStart: number) => void} */
const _setBlock = (r, bs) => {
	_aux1[_nodeIndex(r)] = bs;
};
/** @type {(r: Node) => void} */
const _setImportant = (r) => {
	_flags[_nodeIndex(r)] |= Flag.important;
};
// A simple block's token is derived from its opening char on read.
/** @type {(r: Node, ch: SimpleBlockToken) => void} */
const _setToken = (r, ch) => {};
/** @type {(r: Node, list: Node[]) => void} */
const _setValue = (r, list) => {
	// Seal the finished list: copy its refs into the flat buffer and hand the
	// scratch array back to the pool. The caller never touches `list` again.
	const len = list.length;
	// Empty seal (common in non-modules skip mode, where value/prelude leaves are
	// dropped): `_makeContainer` already left `_listLens` at 0, so skip the flat
	// writes entirely and just recycle the scratch array.
	if (len !== 0) {
		const i = _nodeIndex(r);
		const start = _flatTop;
		if (start + len > _flat.length) _flatGrow(start + len);
		for (let k = 0; k < len; k++) {
			_flat[start + k] = _nodeIndex(list[k]);
		}
		_flatTop = start + len;
		_listStarts[i] = start;
		_listLens[i] = len;
		// Emptied by popping, not `length = 0`: the latter drops the array's
		// backing store, so every pooled list reallocates one on its next push.
		for (let k = len; k > 0; k--) list.pop();
	}
	_listPool.push(list);
};
/**
 * Empties the body arrays between top-level nodes by popping: `length = 0`
 * drops the backing store, so the next rule's first push reallocates it.
 */
const _dropBodies = () => {
	for (let k = _declBodies.length; k > 0; k--) {
		_declBodies.pop();
		_ruleBodies.pop();
	}
};
/** @type {(r: Node, decls: Node[], childRules: Node[]) => void} */
const _setBody = (r, decls, childRules) => {
	_bodyIdx[_nodeIndex(r)] = _declBodies.length + 1;
	_declBodies.push(decls);
	_ruleBodies.push(childRules);
};
/** @type {(r: Node) => number} */
const _nodeTypeOf = (r) => _types[_nodeIndex(r)];
/** @type {(r: Node) => number} */
const _nodeStartOf = (r) => _starts[_nodeIndex(r)];
/** @type {(r: Node) => string} */
const _nodeValueOf = (r) => _valueOf(_nodeIndex(r));
/** @type {(r: Node) => SimpleBlockToken} */
const _nodeTokenOf = (r) =>
	/** @type {SimpleBlockToken} */ (_S._input[_starts[_nodeIndex(r)]]);
// A container's value, a rule's prelude, and a stylesheet's rules all seal into
// the one content-list writer, so `_setPrelude` / `_setRules` are named views of
// `_setValue` that keep the consume algorithms reading in spec terms.
const _setPrelude = _setValue;
const _setRules = _setValue;

/**
 * Start a parse into the store: point it at this source, reset the node /
 * flat cursors so nodes accumulate from id 1, and clear skip state (`grammar`
 * sets its own afterwards; `parseA*` leave it off to build the full tree).
 * @param {string} input source
 * @param {LocConverter} lc loc converter
 */
const _setupParse = (input, lc) => {
	_S._input = input;
	_originalInput = input;
	_S._openCommentStart = -1;
	_locConverter = lc;
	_nodeCount = 0;
	_flatTop = 0;
	_skipTypes = _NO_SKIP_TYPES;
	_skipActive = false;
	_skipSelectorPrelude = false;
	_skipAtRulePrelude = false;
	// Only `grammar` re-enables it (after this call); the standalone `parseA*`
	// entry points never print, so they must not inherit a previous parse's flag.
	_printing = false;
	// A visitor throw or `stop()` mid-walk can leave a count set; never carry it over.
	_within.fill(0);
	_frameWithin.fill(0);
	_S._inSupportsPrelude = false;
	_S._inSupportsSelector = false;
	_S._inStyleAttribute = false;
	_S._inMediaConditionPrelude = false;
	_S._inPropertyRule = false;
	_S._inFunctionRule = false;
	_S._inFeatureValuesRule = false;
	_S._inCustomProperty = false;
	_S._inSubstitutedValue = false;
	_S._inColorOperand = false;
	_substitutionSpanFrom = -1;
	_substitutionSpanTo = -1;
	_substitutionSpanHas = false;
	_substitutionSearchFrom = -1;
	_substitutionSearchOpening = -1;
	_S._mathFunctionDepth = 0;
	_S._steppedFunctionDepth = 0;
	_S._convertLengthUnits = false;
	_S._assumeUtf8 = false;
	_convertApproximateColors = false;
	_unusedSymbols = null;
	_unusedRules = null;
	_pseudoClasses = null;
	_mergingDistantRules = false;
	// Cleared rather than replaced: the print's own `finally` empties them, so
	// this is the belt to that brace and allocates nothing per parse.
	_distantRules.clear();
	_distantAtRules.clear();
	_distantAtRuleFolded = false;
	_distantDeclaredAt.clear();
	_distantTypedDeclaredAt.clear();
	_distantTypeDeclaredAt.clear();
	_distantSeq = 0;
	_S._transforms = _DEFAULT_TRANSFORMS;
	_commentsKept = "some";
	_S._rewriteCustomProperties = false;
	_S._openCustomValue = null;
	_S._eofPrinted = "";
	_S._eofOpen = "";
	_dropOverriddenDeclarations = false;
	_S._unitScale = ABSOLUTE_UNIT_SCALE;
	// Back to what the module loads with, so no parse inherits a target of its own.
	_S._hexAlphaAllowed = true;
	_S._doublePositionAllowed = true;
	_S._displayTwoValuesAllowed = true;
	_S._systemUiAllowed = true;
	_textDecorationColorStyleAllowed = true;
	_textDecorationThicknessAllowed = true;
	_oldTokenizer = false;
	_tridentTarget = false;
	_colorFunctionAllowed = true;
	_colorMixAllowed = true;
	_hwbColorsAllowed = true;
	_labColorsAllowed = true;
	_lightDarkAllowed = true;
	_whereSelectorAllowed = true;
	_dirSelectorAllowed = true;
	_nestingAllowed = true;
	_S._customMediaAllowed = true;
	_S._loweringNesting = false;
	_pendingNested.length = 0;
	_customMedia.clear();
	_customSelectors.clear();
	_S._customAtRulesFixed =
		_customAtRulesSeed !== null && !_collectingCustomAtRules;
	if (_customAtRulesSeed !== null) {
		for (const [name, query] of _customAtRulesSeed.media) {
			_customMedia.set(name, query);
		}
		for (const [name, list] of _customAtRulesSeed.selectors) {
			_customSelectors.set(name, list);
		}
		_customAtRulesSeed = null;
	}
	_S._isSelectorAllowed = true;
	_langArgumentListAllowed = true;
	_notSelectorListAllowed = true;
	_S._loweringLightDark = false;
	_S._lightDarkUsed = false;
	_S._charsetPiece = -1;
	_oklabColorsAllowed = true;
	_colorFallbacksNeeded = false;
	_insetShorthandAllowed = true;
	_S._rangeSpellingAllowed = true;
	_placeShorthandAllowed = true;
	// Only a stylesheet naming one has an empty rule worth keeping, and a match
	// inside a comment or a string costs those bytes rather than changing meaning.
	_S._namespacePrologueOpen = _namesNamespaceAtRule(input);
	// Read only while lowering `light-dark()`, which is where it is computed.
	_S._sourceNamesLightDark = false;
	_overflowTwoValuesAllowed = true;
	_S._valueDeclaration = null;
	_S._keywordOnlyFor = null;
	_S._keywordOnly = false;
};

/**
 * Materialize a single non-block, non-function lexer token as its leaf AST node — the spec's "consume a token" result (§5.4.8 "anything else"), preserving stray closers / CDO / CDC.
 * @param {MutableToken} t token from the lexer
 * @returns {Node} the leaf token node
 */
const tokenToNode = (t) => {
	const tt = t.type;
	// URL is the only leaf with own state (its content range); all others are a
	// plain leaf whose node type comes from the map.
	if (tt === TT_URL) {
		const ut = /** @type {CssUrlToken} */ (t);
		return _makeUrl(t.start, t.end, ut.contentStart, ut.contentEnd);
	}
	return _makeLeaf(_ttToNodeType[tt], t.start, t.end);
};

/**
 * Position-based view over the lexer — webpack's stand-in for the spec's
 * "normalize into a token stream" (CSS Syntax §9). It unifies the lexer and the
 * stream in one class: the `readToken` primitive lexes one token (the CSS
 * tokenizer), and the spec token-stream operations `next` / `consume` /
 * `discard` / `mark` / `restoreMark` / `discardMark` drive it from a byte
 * cursor. `parse*` entry points wrap a source string in one of these and every
 * `consume*` algorithm reads tokens from it.
 *
 * No token buffer is kept: the cursor is a byte offset and the only state is
 * the next token (lazily tokenized once and cached until consumed). The
 * declaration-vs-qualified-rule backtracking in `consumeABlocksContents`
 * rewinds by `mark`ing / `restoreMark`ing that byte offset, which simply
 * re-tokenizes the rewound span — comment tokens are filtered here and fire
 * `onComment` once each, tracked by a monotonic high-water mark so a
 * re-tokenized span never re-fires them.
 *
 * `SourceProcessor` is handed this class (not an instance) and threads it to
 * the grammar, so a different language can drive the same visitor machinery by
 * swapping the tokenizer — the per-token `readToken` primitive — for its own.
 */
class TokenStream {
	/**
	 * @param {string} input source
	 * @param {number=} pos start byte offset (default `0`)
	 * @param {LocConverter=} locConverter shared loc converter (default a fresh one over `input`)
	 * @param {((input: string, start: number, end: number) => number)=} onComment comment-token callback
	 */
	constructor(
		input,
		pos = 0,
		locConverter = new LocConverter(input),
		onComment = undefined
	) {
		/** @type {string} */
		this.input = input;
		/** @type {LocConverter} */
		this.locConverter = locConverter;
		this._onComment = onComment;
		// Byte offset where the next token is tokenized from.
		/** @type {number} */
		this._pos = pos;
		// Comments before this offset have already fired `onComment`; a
		// re-tokenized (backtracked) span never re-fires them.
		/** @type {number} */
		this._commentHigh = pos;
		// Single reused token the lexer writes into on the `next` path — see
		// `MutableToken`. `_hasNext` marks it cached — a boolean instead of an
		// object slot, so caching a token never pays a GC write barrier.
		/** @type {MutableToken} */
		this._tok = createToken();
		/** @type {boolean} whether `_tok` holds the (lazily tokenized) next token */
		this._hasNext = false;
		/** @type {number[]} byte offsets to rewind to */
		this._marks = [];
	}

	/**
	 * The next token (CSS Syntax §3 "next token") — the upcoming token without
	 * consuming it; the `<eof-token>` once the source is exhausted. This is the
	 * token the consume algorithms dispatch on (the spec's "process"). Tokenized
	 * from `_pos` on first use and cached until consumed; comment tokens are
	 * skipped here, firing `onComment` once each.
	 * @returns {MutableToken} the next token
	 */
	next() {
		if (!this._hasNext) {
			const input = this.input;
			const tok = this._tok;
			let pos = this._pos;
			for (;;) {
				const t = readToken(input, pos, tok);
				if (t === undefined) {
					fill(tok, TT_EOF, input.length, input.length);
					break;
				}
				if (t.type === TT_COMMENT) {
					// A closed comment is `/**/` at the shortest; anything else running to
					// the end never closed, and the printer writes its `*/` back.
					if (
						t.end === input.length &&
						(t.end - t.start < 4 ||
							input.charCodeAt(t.end - 2) !== CC_ASTERISK ||
							input.charCodeAt(t.end - 1) !== CC_SOLIDUS)
					) {
						_S._openCommentStart = t.start;
					}
					if (t.start >= this._commentHigh) {
						if (this._onComment) this._onComment(input, t.start, t.end);
						this._commentHigh = t.end;
					}
					pos = t.end;
					continue;
				}
				break;
			}
			this._hasNext = true;
		}
		return this._tok;
	}

	/**
	 * Consume a token (CSS Syntax §3 "consume a token") — return the next token
	 * and advance the cursor past it. The returned token is valid until the next
	 * `next` re-tokenizes (the reused instance is not cleared by advancing).
	 * @returns {MutableToken} the consumed token
	 */
	consume() {
		const t = this.next();
		if (t.type !== TT_EOF) {
			this._pos = t.end;
			this._hasNext = false;
		}
		return t;
	}

	/**
	 * Discard a token (CSS Syntax §3 "discard a token") — advance the cursor past
	 * the next token without returning it.
	 * @returns {void}
	 */
	discard() {
		const t = this.next();
		if (t.type !== TT_EOF) {
			this._pos = t.end;
			this._hasNext = false;
		}
	}

	/**
	 * Advance past the already-peeked next token, skipping the redundant `next()`
	 * re-check `consume` / `discard` pay. Precondition: the caller has just called
	 * `next()` (so `_tok` is the cached next token and `_hasNext` is true) and that
	 * token is not the `<eof-token>` — the hot "peek, decide, advance" sites where
	 * both always hold. Callers that can't guarantee a non-EOF cached token use
	 * `consume` / `discard` instead.
	 * @returns {void}
	 */
	advance() {
		this._pos = this._tok.end;
		this._hasNext = false;
	}

	/**
	 * Step over the whitespace the next token would be, without tokenizing it —
	 * for the sites that discard whitespace tokens. No-op while a token is cached.
	 * @returns {void}
	 */
	skipWhitespace() {
		if (this._hasNext) return;
		const input = this.input;
		let pos = this._pos;
		while (_byteClass[input.charCodeAt(pos) | 0] === BC_WHITESPACE) pos++;
		this._pos = pos;
	}

	/**
	 * Step over the run of selector-prelude leaves a skip-mode parse drops (CSS
	 * Syntax §5.4.3's "append to prelude", which skip mode does not do), without
	 * lexing one token each. Only valid where every leaf up to the next
	 * `BC_PRELUDE_STOP` code point is dropped whatever it is — which in
	 * `consumeAQualifiedRule` is once the two tokens the `--foo: {`
	 * disambiguation reads have both been seen. No-op while a token is cached.
	 * @returns {void}
	 */
	skipPreludeLeaves() {
		if (this._hasNext) return;
		const input = this.input;
		const from = this._pos;
		let pos = from;
		while (_byteClass[input.charCodeAt(pos) | 0] !== BC_PRELUDE_STOP) pos++;
		// A `(` closes a function when a name runs into it, so the token is lexed
		// from that name instead, for `url(` and `:not(` to stay one. The run holds
		// no escape, a `\` being a stop, so it starts where ident code points do.
		if (input.charCodeAt(pos) === CC_LEFT_PARENTHESIS) {
			while (pos > from && _byteClass[input.charCodeAt(pos - 1)] === BC_IDENT) {
				pos--;
			}
		}
		this._pos = pos;
	}

	/**
	 * Mark (CSS Syntax §3 "mark") — push the current cursor position.
	 * @returns {void}
	 */
	mark() {
		this._marks.push(this._pos);
	}

	/**
	 * Restore a mark (CSS Syntax §3 "restore a mark") — pop the last mark and
	 * rewind the cursor to it. The rewound span is re-tokenized on the next read;
	 * already-fired comments are not re-fired (`_commentHigh`).
	 * @returns {void}
	 */
	restoreMark() {
		this._pos = /** @type {number} */ (this._marks.pop());
		this._hasNext = false;
	}

	/**
	 * Discard a mark (CSS Syntax §3 "discard a mark") — pop without rewinding.
	 * @returns {void}
	 */
	discardMark() {
		this._marks.pop();
	}
}

/**
 * Preprocess the input stream, CSS Syntax 3
 * [§3.3](https://drafts.csswg.org/css-syntax/#input-preprocessing): a NUL
 * stands for the replacement character, which — unlike a NUL — is an ident code
 * point, so `a\0b` is the one identifier `a\uFFFDb` rather than three tokens.
 * @param {string} input source text
 * @returns {string} the text the tokenizer reads
 */
const _preprocessInputStream = (input) =>
	// WHY: §3.3 names surrogates alongside NUL, and they are left as written.
	// A lone one is an ident code point here, so it tokenizes to the same stream
	// the replacement character does — measured over every context one can sit
	// in — and encoding the asset as UTF-8 writes it out as that character. The
	// range test that would find one costs 3% of the walk, for no difference.
	!input.includes("\u0000") ? input : input.split("\u0000").join("\uFFFD");

/**
 * Normalize a `parse*` entry point's first argument into a `TokenStream`
 * (CSS Syntax §9 "normalize into a token stream"). An existing `TokenStream`
 * is returned as-is (consumed from its current position — it already carries
 * the shared `LocConverter` and comment hook), so `pos` / `onComment` are
 * ignored. A raw source string is tokenized from `pos` with a fresh
 * `LocConverter`; pass a `TokenStream` instead to share one converter across
 * sub-parses.
 * @param {string | TokenStream} input source string or an existing stream
 * @param {number=} pos start byte offset (string input only; default `0`)
 * @param {((input: string, start: number, end: number) => number)=} onComment comment callback (string input only)
 * @returns {TokenStream} the stream to consume from
 */
const normalizeIntoTokenStream = (input, pos, onComment) => {
	if (input instanceof TokenStream) return input;
	// The replacement is one code unit for one, so every offset a caller already
	// holds — a `LocConverter` among them — still names what it named.
	const text = _preprocessInputStream(input);
	return new TokenStream(text, pos || 0, new LocConverter(text), onComment);
};

// === Parser entry points (CSS Syntax Level 3 §5.3) ===

// Each `parseA*` wraps a `consumeA*` algorithm from §5.4, taking raw source and
// a start position in place of the spec's "normalize into a token stream". The
// split mirrors tabatkins/parse-css, `consume*` driving the tokenizer.

/**
 * @typedef {object} ParseOptions
 * @property {((input: string, start: number, end: number) => number)=} comment optional comment-token callback; the public `parse*` entry points use it to build the `TokenStream` so the outer parser's comment tracker still sees magic comments inside the consumed range
 */

// WHY: the `parseA*` retained store and its property-accessor readers.
// `grammar` recycles the columns per top-level rule, but a `parseA*` has to hand
// back a tree outliving the parse: it parses into the columns without recycling,
// then `_finishStore` hands them to a snapshot and resets the module columns to
// fresh arrays so the next parse cannot clobber it. Nodes are exposed as
// readers, plain objects over one module-level prototype whose getters index the
// snapshot by node id — no per-node class, no eager slices, child readers built
// on access. One shared prototype rather than one per store keeps every reader
// monomorphic across parses.

/**
 * @typedef {object} NodeReader
 * @property {NodeStore} _store snapshot this reader indexes
 * @property {number} _i node id into the snapshot columns
 * @property {number} type node type
 * @property {number} start start offset
 * @property {number} end end offset
 * @property {[number, number]} range start / end offsets
 * @property {{ start: { line: number, column: number }, end: { line: number, column: number } }} loc source location
 * @property {() => string} toString source slice
 * @property {string | ComponentValue[]} value token value (leaf) or component-value list (function / block / declaration)
 * @property {string} unescaped unescaped token value
 * @property {number} numericValue parsed numeric value
 * @property {"integer" | "number" | "id" | "unrestricted"} typeFlag spec type flag
 * @property {"+" | "-" | ""} sign spec sign
 * @property {string} unit dimension unit (lower-cased)
 * @property {number} contentStart url content start offset
 * @property {number} contentEnd url content end offset
 * @property {string} name rule / declaration / function name
 * @property {number} nameStart name start offset
 * @property {number} nameEnd name end offset
 * @property {string} unescapedName unescaped name
 * @property {ComponentValue[]} prelude rule prelude
 * @property {Declaration[] | null} declarations block declarations
 * @property {Rule[] | null} childRules block child rules
 * @property {number} blockStart `{` start offset
 * @property {number} blockEnd `}` end offset
 * @property {boolean} important `!important` flag
 * @property {SimpleBlockToken} token simple-block opening char
 * @property {Rule[]} rules stylesheet rules
 */

/**
 * @typedef {object} NodeStore
 * @property {string} input source
 * @property {LocConverter} lc loc converter
 * @property {Uint8Array} types node-type column
 * @property {Int32Array} starts start-offset column
 * @property {Int32Array} ends end-offset column
 * @property {Int32Array} aux0 aux slot 0
 * @property {Int32Array} aux1 aux slot 1
 * @property {Uint8Array} flags flags column
 * @property {Int32Array} listStarts content-span start column
 * @property {Int32Array} listLens content-span length column
 * @property {Int32Array} flat flat node-ref buffer
 * @property {Int32Array} bodyIdx per-node `1 + body index` (0 = no body)
 * @property {Node[][]} declBodies dense per-body declaration lists
 * @property {Node[][]} ruleBodies dense per-body child-rule lists
 */

// Shared frozen empty list for a block body with no declarations / child rules,
// so equal-empty reads return one reference (mirrors `_EMPTY_LIST`).
const _READER_EMPTY = /** @type {Rule[]} */ (
	/** @type {Rule[] | ReadonlyArray<never>} */ (Object.freeze([]))
);

/**
 * Raw token value over a snapshot (the lazy `Token.value` form): hash / at-keyword
 * drop their one-char prefix, url uses its content range.
 * @param {NodeStore} store snapshot
 * @param {number} i node id
 * @returns {string} raw token value
 */
const _storeValueOf = (store, i) => {
	const ty = store.types[i];
	if (ty === NodeType.Hash || ty === NodeType.AtKeyword) {
		return store.input.slice(store.starts[i] + 1, store.ends[i]);
	}
	if (ty === NodeType.Url) {
		return store.input.slice(store.aux0[i], store.aux1[i]);
	}
	return store.input.slice(store.starts[i], store.ends[i]);
};

/**
 * @param {NodeStore} store snapshot
 * @param {number} i node id
 * @returns {Node} reader over node `i`
 */
const _readerAt = (store, i) => {
	const o = Object.create(NODE_READER_PROTO);
	o._store = store;
	o._i = i;
	return /** @type {Node} */ (o);
};

/**
 * Readers over a container's flat content span (value / prelude / rules).
 * @param {NodeStore} store snapshot
 * @param {number} i container id
 * @returns {Node[]} child readers
 */
const _readList = (store, i) => {
	const s = store.listStarts[i];
	const len = store.listLens[i];
	const flat = store.flat;
	/** @type {Node[]} */
	const out = [];
	for (let k = 0; k < len; k++) out.push(_readerAt(store, flat[s + k]));
	return out;
};

/**
 * Readers over a node-id list (declarations / child rules).
 * @param {NodeStore} store snapshot
 * @param {Node[]} list node-id list
 * @returns {Node[]} child readers
 */
const _readRefList = (store, list) => {
	/** @type {Node[]} */
	const out = [];
	for (let k = 0; k < list.length; k++) {
		out.push(_readerAt(store, _nodeIndex(list[k])));
	}
	return out;
};

// Module-level prototype shared by every `parseA*` reader, its getters indexing
// that reader's `_store` snapshot by `_i`. Created once rather than per store,
// so all readers share one hidden map and stay monomorphic across parses.
const NODE_READER_PROTO = /** @type {NodeReader} */ ({
	_store: /** @type {NodeStore} */ (/** @type {NodeStore | null} */ (null)),
	_i: 0,
	get type() {
		return this._store.types[this._i];
	},
	get start() {
		return this._store.starts[this._i];
	},
	get end() {
		return this._store.ends[this._i];
	},
	get range() {
		const store = this._store;
		const i = this._i;
		return /** @type {[number, number]} */ ([store.starts[i], store.ends[i]]);
	},
	get loc() {
		const store = this._store;
		const i = this._i;
		const lc = store.lc;
		// `LocConverter#get` mutates and returns itself, so snapshot the first.
		const s = lc.get(store.starts[i]);
		const sl = s.line;
		const sc = s.column;
		const e = lc.get(store.ends[i]);
		return {
			start: { line: sl, column: sc },
			end: { line: e.line, column: e.column }
		};
	},
	toString() {
		const store = this._store;
		const i = this._i;
		return store.input.slice(store.starts[i], store.ends[i]);
	},
	get value() {
		const store = this._store;
		const i = this._i;
		const ty = store.types[i];
		// function / simple-block / declaration expose their component-value
		// list; every leaf token exposes its raw string value.
		return ty === NodeType.Function ||
			ty === NodeType.SimpleBlock ||
			ty === NodeType.Declaration
			? /** @type {ComponentValue[]} */ (_readList(store, i))
			: _storeValueOf(store, i);
	},
	get unescaped() {
		const store = this._store;
		const i = this._i;
		const v = _storeValueOf(store, i);
		if (store.types[i] === NodeType.String) {
			return _unescapeStringValue(_stringContent(v));
		}
		const ranOut = store.ends[i] === store.input.length;
		return unescapeIdentifier(ranOut ? _resolveTrailingEscape(v, false) : v);
	},
	get numericValue() {
		const store = this._store;
		const i = this._i;
		const v = _storeValueOf(store, i);
		if (store.types[i] === NodeType.Dimension) {
			return Number(v.slice(0, _consumeANumber(v, 0)));
		}
		if (store.types[i] === NodeType.Percentage) return Number(v.slice(0, -1));
		return Number(v);
	},
	get typeFlag() {
		const store = this._store;
		const i = this._i;
		if (store.types[i] === NodeType.Hash) {
			const input = store.input;
			const p = store.starts[i] + 1;
			return _ifThreeCodePointsWouldStartAnIdentSequence(
				input,
				p,
				input.charCodeAt(p),
				input.charCodeAt(p + 1),
				input.charCodeAt(p + 2)
			)
				? "id"
				: "unrestricted";
		}
		const v = _storeValueOf(store, i);
		return _typeFlagOf(
			store.types[i] === NodeType.Dimension
				? v.slice(0, _consumeANumber(v, 0))
				: v
		);
	},
	get sign() {
		return _signOf(_storeValueOf(this._store, this._i));
	},
	get unit() {
		const v = _storeValueOf(this._store, this._i);
		return v.slice(_consumeANumber(v, 0)).toLowerCase();
	},
	get contentStart() {
		return this._store.aux0[this._i];
	},
	get contentEnd() {
		return this._store.aux1[this._i];
	},
	get name() {
		const store = this._store;
		const i = this._i;
		const end = store.aux0[i];
		// An at-rule's name skips its `@`; others start at the node.
		const text = store.input.slice(
			store.types[i] === NodeType.AtRule
				? store.starts[i] + 1
				: store.starts[i],
			end
		);
		if (end !== store.input.length) return text;
		return _resolveTrailingEscape(text, false);
	},
	get nameStart() {
		return this._store.starts[this._i];
	},
	get nameEnd() {
		return this._store.aux0[this._i];
	},
	get unescapedName() {
		return unescapeIdentifier(this.name);
	},
	get prelude() {
		return /** @type {ComponentValue[]} */ (_readList(this._store, this._i));
	},
	get declarations() {
		const store = this._store;
		// Only rules populate the body slot (see `_makeContainer`); 0 means no
		// block — same `null` contract as before.
		const bi = store.bodyIdx[this._i];
		if (bi === 0) return null;
		const list = store.declBodies[bi - 1];
		return /** @type {Declaration[]} */ (
			list.length > 0 ? _readRefList(store, list) : _READER_EMPTY
		);
	},
	get childRules() {
		const store = this._store;
		const bi = store.bodyIdx[this._i];
		if (bi === 0) return null;
		const list = store.ruleBodies[bi - 1];
		return /** @type {Rule[]} */ (
			list.length > 0 ? _readRefList(store, list) : _READER_EMPTY
		);
	},
	get blockStart() {
		return this._store.aux1[this._i];
	},
	get blockEnd() {
		const store = this._store;
		const i = this._i;
		return store.aux1[i] !== -1 ? store.ends[i] : -1;
	},
	get important() {
		return (this._store.flags[this._i] & 1) !== 0;
	},
	get token() {
		const store = this._store;
		return /** @type {SimpleBlockToken} */ (store.input[store.starts[this._i]]);
	},
	get rules() {
		return /** @type {Rule[]} */ (_readList(this._store, this._i));
	}
});

/**
 * Hand the node columns back: each is replaced by an empty view, so what this
 * parse grew is collectable and every column goes together — one place to add
 * a new one to, rather than two that have to agree.
 * @returns {void}
 */
const _releaseColumns = () => {
	_types = EMPTY_U8;
	_starts = EMPTY_I32;
	_ends = EMPTY_I32;
	_aux0 = EMPTY_I32;
	_aux1 = EMPTY_I32;
	_flags = EMPTY_U8;
	_listStarts = EMPTY_I32;
	_listLens = EMPTY_I32;
	_bodyIdx = EMPTY_I32;
	_clearRuleEntries();
	_ruleEntries = [];
	_hoistedRuns = [];
};

/**
 * Hand the module's live columns to a retained snapshot, then reset the
 * module columns to fresh empty arrays so the next parse starts clean and can't
 * mutate this store. Called once per `parseA*` after all consuming is done.
 * @returns {NodeStore} the retained snapshot
 */
const _finishStore = () => {
	/** @type {NodeStore} */
	const store = {
		input: _S._input,
		lc: _locConverter,
		types: _types,
		starts: _starts,
		ends: _ends,
		aux0: _aux0,
		aux1: _aux1,
		flags: _flags,
		listStarts: _listStarts,
		listLens: _listLens,
		flat: _flat,
		bodyIdx: _bodyIdx,
		declBodies: _declBodies,
		ruleBodies: _ruleBodies
	};
	// Carry this parse's size forward as a one-shot grow hint so the next parse
	// allocates its columns once instead of re-doubling from scratch (node ids
	// are 1-based, so `+1`).
	_growHint = _nodeCount + 1;
	_flatGrowHint = _flatTop;
	// Reset module state — the columns now belong to `store`.
	_capacity = 0;
	_nodeCount = 0;
	_flatTop = 0;
	_releaseColumns();
	_flat = EMPTY_I32;
	_declBodies = [];
	_ruleBodies = [];
	_listPool.length = 0;
	_S._input = "";
	_locConverter = /** @type {LocConverter} */ (
		/** @type {LocConverter | null} */ (null)
	);
	return store;
};

/**
 * Finish the parse and wrap one consumed node ref as a `parseA*` reader, keeping
 * the caller's node type.
 * @template {Node} T
 * @param {T} ref consumed node ref
 * @returns {T} reader over the retained snapshot
 */
const _finishOne = (ref) =>
	/** @type {T} */ (_readerAt(_finishStore(), _nodeIndex(ref)));

/**
 * Parse a stylesheet, CSS Syntax Level 3
 * [§5.3.4](https://drafts.csswg.org/css-syntax/#parse-stylesheet).
 * @param {string | TokenStream} input source string or an existing token stream
 * @param {number=} pos start position (string input only)
 * @param {ParseOptions=} options optional comment-token callback (string input only)
 * @returns {Stylesheet} the parsed stylesheet
 */
const parseAStylesheet = (input, pos = 0, options = {}) => {
	// 1. If input is a byte stream for a stylesheet, decode bytes from input, and set input to the result.
	// 2. Normalize input, and set input to the result.
	const ts = normalizeIntoTokenStream(input, pos, options.comment);
	_setupParse(ts.input, ts.locConverter);
	// 3. Create a new stylesheet, with its location set to location (or null, if location was not passed).
	const start = ts.next().start;
	const stylesheet = _makeStylesheet(start);
	// 4. Consume a stylesheet's contents from input, and set the stylesheet's rules to the result.
	_setRules(stylesheet, consumeAStylesheetsContents(ts));
	_setEnd(stylesheet, ts.next().start);
	// 5. Return the stylesheet.
	return /** @type {Stylesheet} */ (
		_readerAt(_finishStore(), _nodeIndex(stylesheet))
	);
};

/**
 * Parse a stylesheet's contents, CSS Syntax Level 3
 * [§5.3.5](https://drafts.csswg.org/css-syntax/#parse-stylesheets-contents) —
 * the top-level rule list via `consumeAStylesheetsContents` (§5.4.1): top-level
 * declarations are parse errors (never produced) and top-level CDO (`<!--`) /
 * CDC (`-->`) tokens are discarded.
 * @param {string | TokenStream} input source string or an existing token stream
 * @param {number=} pos start position (string input only)
 * @param {ParseOptions=} options optional comment-token callback (string input only)
 * @returns {Rule[]} top-level rules
 */
const parseAStylesheetsContents = (input, pos = 0, options = {}) => {
	// 1. Normalize input, and set input to the result.
	const ts = normalizeIntoTokenStream(input, pos, options.comment);
	_setupParse(ts.input, ts.locConverter);
	// 2. Consume a stylesheet’s contents from input, and return the result.
	const rules = consumeAStylesheetsContents(ts);
	const store = _finishStore();
	return /** @type {Rule[]} */ (_readRefList(store, rules));
};

/**
 * Parse a block's contents, CSS Syntax Level 3
 * [§5.3.6](https://drafts.csswg.org/css-syntax/#parse-block-contents).
 * @param {string | TokenStream} input source string or an existing token stream
 * @param {number=} pos start position (string input only; just past the opening `{`, or 0)
 * @param {ParseOptions=} options optional comment-token callback (string input only)
 * @returns {{ decls: Declaration[], rules: Rule[] }} block decls + rules
 */
const parseABlocksContents = (input, pos = 0, options = {}) => {
	// 1. Normalize input, and set input to the result.
	const ts = normalizeIntoTokenStream(input, pos, options.comment);
	_setupParse(ts.input, ts.locConverter);
	// 2. Consume a block’s contents from input, and return the result.
	const { decls, rules } = consumeABlocksContents(ts);
	const store = _finishStore();
	return {
		decls: /** @type {Declaration[]} */ (_readRefList(store, decls)),
		rules:
			rules.length > 0
				? /** @type {Rule[]} */ (_readRefList(store, rules))
				: _READER_EMPTY
	};
};

/**
 * Parse a rule, CSS Syntax Level 3
 * [§5.3.7](https://drafts.csswg.org/css-syntax/#parse-rule) — discards leading
 * whitespace, consumes one at-rule / qualified rule, and requires only trailing
 * whitespace; `undefined` (syntax error) otherwise.
 * @param {string | TokenStream} input source string or an existing token stream
 * @param {number=} pos start position (string input only)
 * @param {ParseOptions=} options optional comment-token callback (string input only)
 * @returns {Rule | undefined} the parsed rule
 */
const parseARule = (input, pos = 0, options = {}) => {
	// 1. Normalize input, and set input to the result.
	const ts = normalizeIntoTokenStream(input, pos, options.comment);
	_setupParse(ts.input, ts.locConverter);
	// 2. Discard whitespace from input.
	while (ts.next().type === TT_WHITESPACE) ts.advance();
	// 3. An <EOF-token> is a syntax error; an <at-keyword-token> consumes an
	// at-rule and anything else a qualified rule. Nothing, or an invalid rule
	// error, is a syntax error too.
	const head = ts.next();
	if (head.type === TT_EOF) return undefined;
	const rule =
		head.type === TT_AT_KEYWORD
			? consumeAnAtRule(ts)
			: consumeAQualifiedRule(ts);
	if (!rule) return undefined;
	// 4. Discard whitespace from input.
	while (ts.next().type === TT_WHITESPACE) ts.advance();
	// 5. If the next token from input is an <EOF-token>, return rule. Otherwise, return a syntax error.
	return ts.next().type === TT_EOF ? _finishOne(rule) : undefined;
};

/**
 * Parse a declaration, CSS Syntax Level 3
 * [§5.3.8](https://drafts.csswg.org/css-syntax/#parse-declaration).
 * @param {string | TokenStream} input source string or an existing token stream
 * @param {number=} pos start position (string input only)
 * @param {ParseOptions=} options optional comment-token callback (string input only)
 * @returns {Declaration | undefined} the parsed declaration, or undefined
 */
const parseADeclaration = (input, pos = 0, options = {}) => {
	// 1. Normalize input, and set input to the result.
	const ts = normalizeIntoTokenStream(input, pos, options.comment);
	_setupParse(ts.input, ts.locConverter);
	// 2. Discard whitespace from input.
	while (ts.next().type === TT_WHITESPACE) ts.advance();
	// 3. Consume a declaration from input. If anything was returned, return it. Otherwise, return a syntax error.
	const decl = consumeADeclaration(ts);
	return decl === undefined ? undefined : _finishOne(decl);
};

/**
 * Parse a component value, CSS Syntax Level 3 [§5.3.9](https://drafts.csswg.org/css-syntax/#parse-component-value) — strict entry point that consumes one value and returns `undefined` if non-whitespace input trails (use `consumeAComponentValue` for "one value, ignore the rest").
 * @param {string | TokenStream} input source string or an existing token stream
 * @param {number=} pos start position (string input only)
 * @param {ParseOptions=} options optional comment-token callback (string input only)
 * @returns {ComponentValue | undefined} the parsed component value, or `undefined` on empty / trailing-garbage input
 */
const parseAComponentValue = (input, pos = 0, options = {}) => {
	// 1. Normalize input, and set input to the result.
	const ts = normalizeIntoTokenStream(input, pos, options.comment);
	_setupParse(ts.input, ts.locConverter);
	// 2. Discard whitespace from input.
	while (ts.next().type === TT_WHITESPACE) ts.advance();
	// 3. If input is empty, return a syntax error.
	if (ts.next().type === TT_EOF) return undefined;
	// 4. Consume a component value from input and let value be the return value.
	const result = consumeAComponentValue(ts);
	// 5. Discard whitespace from input.
	while (ts.next().type === TT_WHITESPACE) ts.advance();
	// 6. If input is empty, return value. Otherwise, return a syntax error.
	return ts.next().type === TT_EOF ? _finishOne(result) : undefined;
};

/**
 * Parse a list of component values, CSS Syntax Level 3
 * [§5.3.10](https://drafts.csswg.org/css-syntax/#parse-list-of-components).
 * @param {string | TokenStream} input source string or an existing token stream
 * @param {number=} pos start position (string input only)
 * @param {ParseOptions=} options comment callback
 * @returns {ComponentValue[]} component values
 */
const parseAListOfComponentValues = (input, pos = 0, options = {}) => {
	// 1. Normalize input, and set input to the result.
	const ts = normalizeIntoTokenStream(input, pos, options.comment);
	_setupParse(ts.input, ts.locConverter);
	// 2. Consume a list of component values from input, and return the result.
	// (`null` needs `bailOnCurly`, which is not passed here.)
	const values = /** @type {Node[]} */ (consumeAListOfComponentValues(ts));
	const store = _finishStore();
	return /** @type {ComponentValue[]} */ (_readRefList(store, values));
};

/**
 * Parse a comma-separated list of component values, CSS Syntax Level 3 [§5.3.11](https://drafts.csswg.org/css-syntax/#parse-comma-list) — consumes one `<comma-token>`-stopped group of component values per iteration until EOF.
 * @param {string | TokenStream} input source string or an existing token stream
 * @param {number=} pos start position (string input only)
 * @param {ParseOptions=} options comment callback
 * @returns {ComponentValue[][]} comma-separated groups of component values
 */
const parseACommaSeparatedListOfComponentValues = (
	input,
	pos = 0,
	options = {}
) => {
	// 1. Normalize input, and set input to the result.
	const ts = normalizeIntoTokenStream(input, pos, options.comment);
	_setupParse(ts.input, ts.locConverter);
	// 2. Let groups be an empty list.
	/** @type {Node[][]} */
	const groups = [];
	// 3. While input is not empty:
	while (ts.next().type !== TT_EOF) {
		// 3.1. Consume a list of component values from input, with <comma-token> as the stop token, and append the result to groups.
		groups.push(
			/** @type {Node[]} */ (consumeAListOfComponentValues(ts, TT_COMMA))
		);
		// 3.2 Discard a token from input.
		ts.discard();
	}
	// 4. Return groups — wrap each group's refs against the retained snapshot.
	const store = _finishStore();
	return groups.map(
		(g) => /** @type {ComponentValue[]} */ (_readRefList(store, g))
	);
};

// === Parser algorithms (CSS Syntax Level 3 §5.4) ===

// The mutually-recursive consume algorithms the `parse*` entry points drive:
// each reads a `TokenStream` and reuses `consumeAComponentValue` for nested
// values, mirroring tabatkins/parse-css.

/**
 * Consume a stylesheet's contents, CSS Syntax Level 3 [§5.4.1](https://drafts.csswg.org/css-syntax/#consume-stylesheet-contents) — the top-level rule list: whitespace and CDO (`<!--`) / CDC (`-->`) tokens are discarded, an at-keyword starts an at-rule, and anything else starts a qualified rule (so top-level declarations are parse errors and never produced).
 *
 * `onRule` is a webpack extension to the algorithm's output: when given, each
 * consumed rule is handed to it immediately and not collected, so the walker can
 * process one top-level rule at a time without materializing the whole
 * stylesheet (the returned list is then empty). When omitted the rules are
 * collected and returned as the spec specifies.
 * @param {TokenStream} ts token stream
 * @param {((rule: Rule) => void)=} onRule optional per-rule sink (streaming); rules are not collected when given
 * @returns {Rule[]} top-level rules (empty when `onRule` is given)
 */
const consumeAStylesheetsContents = (ts, onRule) => {
	// Let rules be an initially empty list of rules.
	/** @type {Rule[]} */
	const rules = [];

	// Process input
	for (;;) {
		ts.skipWhitespace();
		const t = ts.next();
		// <whitespace-token> / <CDO-token> / <CDC-token>
		// Discard a token from input.
		if (t.type === TT_WHITESPACE || t.type === TT_CDO || t.type === TT_CDC) {
			ts.discard();
		}
		// <EOF-token>
		// Return rules.
		else if (t.type === TT_EOF) {
			return rules;
		}
		// <at-keyword-token>
		// Consume an at-rule from input. If anything is returned, append it to rules.
		else if (t.type === TT_AT_KEYWORD) {
			const at = consumeAnAtRule(ts);
			if (at) {
				if (onRule) onRule(at);
				else rules.push(at);
			}
		}
		// anything else
		// Consume a qualified rule from input. If a rule is returned, append it to rules.
		else {
			const rule = consumeAQualifiedRule(ts);
			if (rule) {
				if (onRule) onRule(rule);
				else rules.push(rule);
			}
		}
	}
};

/**
 * Consume an at-rule, CSS Syntax Level 3 [§5.4.2](https://drafts.csswg.org/css-syntax/#consume-at-rule) — the next token must be an <at-keyword-token> (asserted); consumes the prelude up to `;` / `{` / `}` / EOF; `{` consumes the block (§5.4.4) onto `.block`, `;` / EOF is discarded, a top-level `}` (when not `nested`) is appended via `consumeAComponentValue`.
 * @param {TokenStream} ts token stream
 * @param {boolean=} nested true inside a `{}` block — a top-level `}` ends the at-rule (left for the caller)
 * @returns {AtRule | undefined} the parsed at-rule
 */
const consumeAnAtRule = (ts, nested = false) => {
	// Assert (spec): the next token is an <at-keyword-token>.
	// Consume a token from input, and let rule be a new at-rule with its name set to the returned token’s value, its prelude initially set to an empty list, and no declarations or child rules.
	const head = ts.consume();
	const rule = /** @type {AtRule} */ (
		_makeContainer(NodeType.AtRule, head.start, head.end)
	);
	_setName(rule, head.start, head.end);
	// Sealed (`_setPrelude`) at each return — the store consumes the
	// scratch array when sealing, so it must be complete by then.
	const prelude = _takeList();
	// declarations / childRules stay null (no block); the `;` / EOF / nested-`}`
	// forms set blockStart / blockEnd to -1 explicitly at their return below.

	// Like `consumeAQualifiedRule`: skip mode scans the prelude without
	// materializing it (url tokens / functions kept so `@import url(…)` still
	// resolves); the block boundary is found by scanning, not the prelude nodes.
	const skip = _skipAtRulePrelude;

	// Process input
	for (;;) {
		const t = ts.next();

		// <semicolon-token>
		// <EOF-token>
		// Discard a token from input. If rule is valid in the current context, return it; otherwise return nothing.
		if (t.type === TT_SEMICOLON || t.type === TT_EOF) {
			ts.discard();
			_setPrelude(rule, prelude);
			_setBlock(rule, -1);
			_setEnd(rule, t.start);
			return rule;
		}
		// <}-token>
		// If nested is true: if rule is valid in the current context, return it; otherwise return nothing.
		// Otherwise, consume a token and append the result to rule’s prelude.
		else if (t.type === TT_RIGHT_CURLY_BRACKET) {
			if (nested) {
				_setPrelude(rule, prelude);
				_setBlock(rule, -1);
				_setEnd(rule, t.start);
				return rule;
			}
			const node = consumeATokenAsNode(ts);
			if (!skip) prelude.push(node);
			continue;
		}
		// <{-token>
		// Consume a block from input, and assign the result to rule's declarations and child rules.
		else if (t.type === TT_LEFT_CURLY_BRACKET) {
			_setPrelude(rule, prelude);
			if (_streamBlocks) {
				_streamConsumeBlock(ts, rule);
				return rule;
			}
			consumeABlock(ts);
			_setBody(rule, _blockDecls, _blockRules);
			_setBlock(rule, _blockStart);
			_setEnd(rule, _blockEnd);
			return rule;
		}

		// anything else
		// Consume a component value from input and append the returned value to rule’s prelude.
		const node = consumeAComponentValue(ts, t);
		if (!skip) {
			prelude.push(node);
		} else if (
			_nodeTypeOf(node) === NodeType.Function ||
			_nodeTypeOf(node) === NodeType.Url
		) {
			prelude.push(node);
		}
	}
};

/**
 * Consume a token (CSS Syntax §3 "consume a token"): advance past the next
 * token and return it as a leaf AST node. Used directly where the spec says
 * "consume a token from input" (e.g. the parse-error branches in §5.4.7 /
 * §5.4.2 / §5.4.3), distinct from `consumeAComponentValue` which would recurse
 * into a simple block / function.
 * @param {TokenStream} ts token stream
 * @returns {Token} the consumed token as a leaf node
 */
const consumeATokenAsNode = (ts) => {
	const t = ts.consume();
	return /** @type {Token} */ (tokenToNode(t));
};

/**
 * Consume a qualified rule, CSS Syntax Level 3 [§5.4.3](https://drafts.csswg.org/css-syntax/#consume-qualified-rule) — consumes the prelude (each component value via `consumeAComponentValue`) up to its `{` block; EOF, the optional `stopToken`, or a nested top-level `}` is a parse error returning nothing (the block-less prelude is dropped), while a non-nested top-level `}` is consumed as a parse error and the prelude continues. A returned rule always has a block.
 * @param {TokenStream} ts token stream
 * @param {number=} stopToken token type that ends the prelude (parse error → nothing)
 * @param {boolean=} nested true inside a `{}` block — a top-level `}` ends the rule (left for the caller)
 * @returns {QualifiedRule | undefined} parsed qualified rule, or `undefined` on a parse error
 */
const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
	const start = ts.next().start;
	// Let rule be a new qualified rule with its prelude, declarations, and child rules all initially set to empty lists.
	const rule = /** @type {QualifiedRule} */ (
		_makeContainer(NodeType.QualifiedRule, start, start)
	);
	// Sealed by `_setPrelude` at the `{` exit, the only path returning the rule;
	// a parse-error exit abandons the scratch unsealed. Allocated on first push,
	// since skip mode usually pushes nothing and a null prelude reads as empty.
	/** @type {Node[] | null} */
	let prelude = null;
	// A returned qualified rule always has a block (only the `{` exit returns a
	// rule), so `_setBlock` always runs — no blockStart / blockEnd default needed.

	// Skip mode leaves `prelude` empty (selector text is recovered from the
	// rule's byte range, not its nodes); `first`/`second` still track the first
	// two non-whitespace tokens the `--foo: {` disambiguation below needs.
	const skip = _skipSelectorPrelude;
	// Non-skip: first two non-whitespace prelude nodes (computed at the `{`).
	let first = /** @type {Node} */ (/** @type {Node | number} */ (0));
	let second = /** @type {Node} */ (/** @type {Node | number} */ (0));
	// Skip mode: the same two tokens tracked by token type + start, so a dropped
	// leaf selector token needs no materialized node just for the disambiguation.
	// `0` (no token type) = unset.
	let firstTT = 0;
	let firstStart = 0;
	let secondTT = 0;

	// Process input
	for (;;) {
		// Past the two tokens the `--foo: {` disambiguation reads, a skip-mode
		// prelude drops every leaf it consumes, so step over the run of them in
		// bytes rather than lexing each one only to advance past it.
		if (skip && secondTT !== 0) ts.skipPreludeLeaves();
		const t = ts.next();
		// <EOF-token>
		// stop token (if passed)
		// This is a parse error. Return nothing.
		if (t.type === TT_EOF || t.type === stopToken) {
			return undefined;
		}
		// <}-token>
		// This is a parse error. If nested is true, return nothing. Otherwise, consume a token and append the result to rule’s prelude.
		else if (t.type === TT_RIGHT_CURLY_BRACKET) {
			if (nested) return undefined;
			if (skip) {
				// Stray `}` is a non-ws token; record its type/start, drop the node.
				if (firstTT === 0) {
					firstTT = t.type;
					firstStart = t.start;
				} else if (secondTT === 0) {
					secondTT = t.type;
				}
				ts.advance();
			} else {
				(prelude || (prelude = _takeList())).push(consumeATokenAsNode(ts));
			}
			continue;
		}
		// <{-token>: a prelude opening with a `--` <ident-token> and a <colon-token>
		// returns nothing, which is what tells `--foo: { … }` from a nested rule.
		// Anything else consumes a block as the child rules.
		else if (t.type === TT_LEFT_CURLY_BRACKET) {
			// `--foo: {` disambiguation: are the first two non-ws prelude tokens an
			// ident starting with `--` followed by a colon? Skip mode reads the
			// tracked token type/start; non-skip reads the materialized prelude.
			let dashedDeclaration;
			if (skip) {
				dashedDeclaration =
					firstTT === TT_IDENTIFIER &&
					ts.input.startsWith("--", firstStart) &&
					secondTT === TT_COLON;
			} else if (prelude === null) {
				dashedDeclaration = false;
			} else {
				let firstIdx = 0;
				/* istanbul ignore next -- @preserve: leading whitespace is discarded before the rule, so the prelude never starts with it */
				while (
					firstIdx < prelude.length &&
					_nodeTypeOf(prelude[firstIdx]) === NodeType.Whitespace
				) {
					firstIdx++;
				}
				let secondIdx = firstIdx + 1;
				while (
					secondIdx < prelude.length &&
					_nodeTypeOf(prelude[secondIdx]) === NodeType.Whitespace
				) {
					secondIdx++;
				}
				first = prelude[firstIdx];
				second = prelude[secondIdx];
				dashedDeclaration =
					first &&
					_nodeTypeOf(first) === NodeType.Ident &&
					// Test the source bytes directly — avoids forcing the lazy `value`
					// slice just to check the `--` custom-property prefix.
					ts.input.startsWith("--", _nodeStartOf(first)) &&
					second &&
					_nodeTypeOf(second) === NodeType.Colon;
			}
			if (dashedDeclaration) {
				/* istanbul ignore if -- @preserve: when nested, `declarationStartLikely` routes every `--x:` to consumeADeclaration (which accepts custom properties), so this fallthrough is unreachable */
				if (nested) {
					consumeTheRemnantsOfABadDeclaration(ts, true);
				} else {
					consumeABlock(ts);
				}
				return undefined;
			}
			if (prelude !== null) _setPrelude(rule, prelude);
			if (_streamBlocks) {
				_streamConsumeBlock(ts, rule);
				return rule;
			}
			consumeABlock(ts);
			_setBody(rule, _blockDecls, _blockRules);
			_setBlock(rule, _blockStart);
			_setEnd(rule, _blockEnd);
			return rule;
		}

		// anything else
		// Consume a component value from input and append the result to rule’s prelude.
		if (skip) {
			const tt = t.type;
			// Track the first two non-whitespace tokens for the disambiguation above.
			if (tt !== TT_WHITESPACE) {
				if (firstTT === 0) {
					firstTT = tt;
					firstStart = t.start;
				} else if (secondTT === 0) {
					secondTT = tt;
				}
			}
			// Only functions, which may hold a url like `:unknown(url(x))`, and the
			// `(` / `[` blocks needing balance are materialized. Every other selector
			// leaf has no non-modules consumer and is dropped without a node.
			if (
				tt === TT_FUNCTION ||
				(tt >= TT_LEFT_PARENTHESIS && tt <= TT_LEFT_CURLY_BRACKET)
			) {
				const node = consumeAComponentValue(ts, t);
				const ty = _nodeTypeOf(node);
				if (ty === NodeType.Function || ty === NodeType.Url) {
					(prelude || (prelude = _takeList())).push(node);
				}
			} else {
				ts.advance();
			}
		} else {
			(prelude || (prelude = _takeList())).push(consumeAComponentValue(ts, t));
		}
	}
};

/**
 * Consume a block, CSS Syntax Level 3 [§5.4.4](https://drafts.csswg.org/css-syntax/#consume-block) — the next token must be `<{-token>`; discards it, consumes the block's contents (§5.4.5), discards the closing `}`, and returns its `decls` / `rules` pair. We also return the `[start of {, end of }]` offsets so callers can record the block's source position.
 *
 * Results go into the `_block*` slots rather than a returned object: there is one
 * block per rule, and every caller reads all four immediately, so the object was
 * pure garbage. Read them before parsing anything else.
 * @param {TokenStream} ts token stream
 * @returns {void}
 */
const consumeABlock = (ts) => {
	// Capture the opening `{`'s start before advancing — the stream reuses one
	// token instance, so `consumeABlocksContents` below would overwrite it.
	const blockStart = ts.next().start;
	// Assert (spec): the next token is <{-token>.
	// Discard a token from input. Consume a block's contents from input and let result be the result. Discard a token from input.
	ts.discard();
	consumeABlocksContentsInto(ts);
	// Read before anything else runs: a nested block would overwrite these.
	const decls = _bcDecls;
	const rules = _bcRules;
	const close = ts.next();
	const end = close.type === TT_RIGHT_CURLY_BRACKET ? close.end : close.start;
	ts.discard();
	_blockDecls = decls;
	_blockRules = rules;
	_blockStart = blockStart;
	_blockEnd = end;
};

/**
 * 2-token lookahead: is the next non-whitespace pair `<ident> <colon>`?
 * Peeks raw code points without advancing; comments still fire `onComment` later.
 * @param {TokenStream} ts token stream
 * @returns {boolean} true if consume-a-declaration's step 1 + step 3 would both succeed on the current input
 */
const declarationStartLikely = (ts) => {
	const t = ts.next();
	if (t.type !== TT_IDENTIFIER) return false;
	const input = ts.input;
	const len = input.length;
	let pos = t.end;
	for (;;) {
		if (pos >= len) return false;
		const cc = input.charCodeAt(pos);
		if (isAsciiWhitespace(cc)) {
			pos++;
			continue;
		}
		// Skip a `/* … */` comment (the tokenizer filters comments between tokens).
		if (cc === CC_SOLIDUS && input.charCodeAt(pos + 1) === CC_ASTERISK) {
			pos += 2;
			while (
				pos < len &&
				!(
					input.charCodeAt(pos) === CC_ASTERISK &&
					input.charCodeAt(pos + 1) === CC_SOLIDUS
				)
			) {
				pos++;
			}
			pos += 2;
			continue;
		}
		// `:` is always a standalone <colon-token>, so the next significant char
		// being `:` is equivalent to the next token being a <colon-token>.
		return cc === CC_COLON;
	}
};

/**
 * Consume a block's contents, CSS Syntax Level 3 [§5.4.5](https://drafts.csswg.org/css-syntax/#consume-block-contents). Per tabatkins/parse-css.js reference impl: returns separate `decls` and `rules` flat lists, both preserved on EOF / `}` (the spec text's "Return rules" single-list model drops trailing decls because there's no implicit flush before EOF / `}`).
 *
 * `onNode` is the same streaming extension `consumeAStylesheetsContents` exposes:
 * when given, each consumed declaration / rule is handed to it immediately (in
 * source order) instead of being collected, so the returned lists are empty.
 *
 * `streamDepth` / `streamRule` are the other half of block streaming (see
 * `_streamConsumeBlock`): the block collects into its own arrays exactly as it
 * always has, and only once it has grown past `_STREAM_MIN_NODES` does it
 * activate and switch to the sink. Keeping that check here rather than behind
 * `onNode` is what makes a small block cost the same as it does with nothing
 * streaming — no call, no type test per child — and the block's frame is written
 * only when something could still read it (see `_streamPublishFrame`), so a block
 * of nothing but declarations never touches one.
 * @param {TokenStream} ts token stream
 * @param {((node: Declaration | Rule) => void)=} onNode optional per-node sink (streaming); nodes are not collected when given
 * @param {number=} streamDepth this block's frame depth while the walk streams, else undefined
 * @param {Rule=} streamRule the rule this block belongs to, needed only to stream it
 * @param {boolean=} nested whether a `{` opened this — true inside a block, where a `}` closes it; false for the whole input, where one closes nothing (default true)
 * Results go into `_bcDecls` / `_bcRules` rather than a returned object — one
 * block's contents per rule made that object pure garbage. `consumeABlocksContents`
 * below wraps this for the callers that do want an object.
 * @returns {void}
 */
const consumeABlocksContentsInto = (
	ts,
	onNode,
	streamDepth,
	streamRule,
	nested = true
) => {
	/** @type {Declaration[]} */
	const decls = [];
	// Child rules are the common empty case, so `rules` is allocated lazily and
	// returned as the shared `_EMPTY_LIST` when nothing was appended. `decls`
	// stays eager, keeping the hot declaration append a branch-free `push`.
	/** @type {Rule[] | null} */
	let rules = null;
	let sink = onNode;
	// -1 = nothing to stream, and then the growth check below can never fire.
	const d = streamDepth === undefined ? -1 : streamDepth;
	let limit = _STREAM_NO_LIMIT;
	// Where this block began, kept in locals until something needs the frame.
	let markNode = 0;
	let markFlat = 0;
	let published = false;
	// Read and cleared here, before any child block sets it for itself: a block
	// held for the gather never reaches the threshold, so it is handed back whole.
	const held = _holdForLayerGather;
	_holdForLayerGather = false;
	if (d >= 0) {
		markNode = _nodeCount;
		markFlat = _flatTop;
		limit = held ? _STREAM_NO_LIMIT : markNode + _STREAM_MIN_NODES;
		_bcStreamed = false;
	}

	// Process input:
	for (;;) {
		ts.skipWhitespace();
		const t = ts.next();

		// <whitespace-token> / <semicolon-token>
		// Discard a token from input (`t` was just peeked and is non-EOF).
		if (t.type === TT_WHITESPACE || t.type === TT_SEMICOLON) {
			ts.advance();
			continue;
		}
		// <}-token>: return decls and rules, but only nested, where it closes the
		// block. At top level nothing opened one, so it is a parse error running to
		// the next `;` — which is how a browser reads a `style=""` holding one.
		if (t.type === TT_RIGHT_CURLY_BRACKET && !nested) {
			consumeTheRemnantsOfABadDeclaration(ts, false);
			continue;
		}
		// <EOF-token> / <}-token>
		// Return decls and rules.
		if (t.type === TT_EOF || t.type === TT_RIGHT_CURLY_BRACKET) {
			_bcDecls = decls;
			_bcRules = rules || _EMPTY_LIST;
			// Read from the frame, not from `sink`: a descendant can activate this
			// block during a child rule that then fails to parse.
			if (d >= 0) _bcStreamed = published && _frameActive[d] === 1;
			return;
		}
		/** @type {Rule | undefined} */
		let childRule;
		// <at-keyword-token>
		// Consume an at-rule from input, with nested set to true. If a rule was returned, append it to rules.
		if (t.type === TT_AT_KEYWORD) {
			if (d >= 0 && !published && sink === undefined) {
				published = true;
				_streamPublishFrame(d, streamRule, markNode, markFlat, decls, rules);
			}
			childRule = consumeAnAtRule(ts, nested);
		}
		// Anything else: mark, then consume a nested declaration, appending it to
		// decls and discarding the mark. Failing that, restore the mark and consume
		// a nested qualified rule stopping at <semicolon-token>, into rules.
		else {
			// 2-token peek: consume-a-declaration's steps 1 / 3 require `<ident> <colon>`; if absent it would call consume-the-remnants-of-a-bad-declaration (potentially the rest of the enclosing block) only for the restoreMark to undo it (O(N²) on flat blocks of qualified rules). Skip straight to consume-a-qualified-rule — same observable result.
			if (declarationStartLikely(ts)) {
				ts.mark();
				const decl = consumeADeclaration(ts, nested);
				if (decl) {
					ts.discardMark();
					// A declaration opens no block, so nothing can have activated this
					// one behind our back — only its own growth has to be checked.
					if (sink) {
						sink(decl);
					} else {
						decls.push(decl);
						if (_nodeCount > limit) {
							if (_stream.writer !== undefined && rules === null) {
								// The longhand merge needs every declaration at once; a child
								// rule is where that is given up rather than hold the block.
								limit = _nodeCount + _STREAM_MIN_NODES;
							} else {
								if (!published) {
									published = true;
									// Grew past the threshold on declarations alone, so the
									// frame has to be written here instead.
									_streamPublishFrame(
										d,
										streamRule,
										markNode,
										markFlat,
										decls,
										rules
									);
								}
								_streamActivate(d);
								sink = _streamOnNode;
								limit = _STREAM_NO_LIMIT;
							}
						}
					}
					continue;
				}
				ts.restoreMark();
			}
			// A child rule opens a block, and a block is the only thing that can reach
			// back up and activate this one — so this is the last moment the frame has
			// to be readable, and a block of nothing but declarations never gets here.
			if (d >= 0 && !published && sink === undefined) {
				published = true;
				_streamPublishFrame(d, streamRule, markNode, markFlat, decls, rules);
			}
			const rawStart = t.start;
			childRule = consumeAQualifiedRule(ts, TT_SEMICOLON, true);
			if (!childRule && _printing) {
				// Deliberately off-spec: §5.4 drops input both productions reject, which
				// would silently delete IE hacks and template placeholders, so the source
				// is kept. Printing only, so every consumer sees the spec-exact tree.
				childRule = /** @type {Rule | undefined} */ (
					_makeRaw(rawStart, ts.next().start)
				);
			}
		}
		// WHY: a child rule holds a block, so a descendant of it may have activated
		// this block while being consumed — `decls` and `rules` are then already
		// walked and recycled, and everything from here belongs to the sink.
		// Checked even where the rule did not parse, since the block it opened is
		// what activated us and the next child must not reach a recycled list.
		if (!sink && published && _frameActive[d] === 1) sink = _streamOnNode;
		if (!childRule) continue;
		if (sink) {
			sink(childRule);
			continue;
		}
		if (rules === null) {
			rules = [];
			// The frame is written before any child rule is consumed, so it is
			// already pointing at this block and needs the list it did not yet have.
			if (published) _frameRules[d] = rules;
		}
		rules.push(childRule);
		if (_nodeCount > limit) {
			_streamActivate(d);
			sink = _streamOnNode;
			limit = _STREAM_NO_LIMIT;
		}
	}
};

/**
 * Object-returning wrapper over `consumeABlocksContentsInto` for the callers that
 * are not per-rule (the `parseABlocksContents` entry point and the printer map).
 * @param {TokenStream} ts token stream
 * @param {((node: Declaration | Rule) => void)=} onNode optional per-node sink (streaming); nodes are not collected when given
 * @returns {{ decls: Declaration[], rules: Rule[] }} consumed decls + rules
 */
const consumeABlocksContents = (ts, onNode) => {
	consumeABlocksContentsInto(ts, onNode);
	return { decls: _bcDecls, rules: _bcRules };
};

/**
 * The same production read as the whole input rather than as a block's inside —
 * what an HTML `style=""` holds. Nothing opened a block, so a `}` closes none.
 * @param {TokenStream} ts token stream
 * @param {((node: Rule | Declaration) => void)=} onNode streaming sink
 * @returns {void}
 */
const consumeADeclarationList = (ts, onNode) => {
	consumeABlocksContentsInto(ts, onNode, undefined, undefined, false);
};

/**
 * Consume the remnants of a bad declaration, CSS Syntax Level 3 [§5.4.11](https://drafts.csswg.org/css-syntax/#consume-the-remnants-of-a-bad-declaration). Advances the stream past a malformed declaration's tail so the caller (`consumeABlocksContents`) can resume cleanly.
 * @param {TokenStream} ts token stream
 * @param {boolean} nested whether the call originates from inside a `{}` block
 * @returns {void}
 */
const consumeTheRemnantsOfABadDeclaration = (ts, nested) => {
	// Process input:
	for (;;) {
		const t = ts.next();
		// <eof-token> / <semicolon-token>
		// Discard a token from input, and return.
		if (t.type === TT_EOF || t.type === TT_SEMICOLON) {
			ts.discard();
			return;
		}
		// <}-token>
		// If nested is true, return. Otherwise, discard a token.
		if (t.type === TT_RIGHT_CURLY_BRACKET) {
			if (nested) return;
			ts.discard();
			continue;
		}
		// anything else
		// Consume a component value from input, and do nothing.
		consumeAComponentValue(ts);
	}
};

/**
 * Consume a declaration, CSS Syntax Level 3 [§5.4.6](https://drafts.csswg.org/css-syntax/#consume-declaration).
 * @param {TokenStream} ts token stream
 * @param {boolean=} nested true inside a `{}` block — a top-level `}` ends the value
 * @returns {Declaration | undefined} parsed declaration, or `undefined` on the spec's "return nothing" branches (steps 1, 3, 8)
 */
const consumeADeclaration = (ts, nested = false) => {
	const { input } = ts;
	// Let decl be a new declaration, with an initially empty name and a value set to an empty list.
	const start = ts.next().start;
	// nameEnd (= start) / important (unset) keep their container defaults;
	// `value` is set unconditionally at step 5 below.
	const decl = /** @type {Declaration} */ (
		_makeContainer(NodeType.Declaration, start, start)
	);

	// 1. If the next token is an <ident-token>, consume a token from input and set decl's name to the returned token's value.
	// Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
	if (ts.next().type === TT_IDENTIFIER) {
		const head = ts.consume();
		_setName(decl, head.start, head.end);
	} else {
		consumeTheRemnantsOfABadDeclaration(ts, nested);
		return undefined;
	}

	// 2. Discard whitespace from input.
	ts.skipWhitespace();
	while (ts.next().type === TT_WHITESPACE) ts.advance();

	// 3. If the next token is a <colon-token>, discard a token from input.
	//    Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
	if (ts.next().type === TT_COLON) {
		ts.advance();
	} else {
		consumeTheRemnantsOfABadDeclaration(ts, nested);
		return undefined;
	}

	// 4. Discard whitespace from input.
	ts.skipWhitespace();
	while (ts.next().type === TT_WHITESPACE) ts.advance();

	// Step 8's custom-property test, computed early so the value parse can bail.
	const isCustomProperty = input.startsWith("--", start);

	// 5. Consume a nested list of component values, stopping at <semicolon-token>,
	//    as decl's value. A nested non-custom declaration bails on a top-level `{`,
	//    which step 8 rejects and the caller re-parses as a qualified rule anyway.
	const value = consumeAListOfComponentValues(
		ts,
		TT_SEMICOLON,
		nested,
		nested && !isCustomProperty
	);
	if (value === null) return undefined;
	// `_setValue` waits until step 9: steps 6-8 still trim / scan the scratch,
	// and the store consumes it when sealing.
	_setEnd(decl, ts.next().start);

	// 6. If the last two non-<whitespace-token>s in decl's value are a <delim-token> with the value "!" followed by an <ident-token> with a value that is an ASCII case-insensitive match for "important", remove them from decl's value and set decl's important flag.
	{
		let last = value.length - 1;
		while (last >= 0 && _nodeTypeOf(value[last]) === NodeType.Whitespace) {
			last--;
		}
		let prev = last - 1;
		while (prev >= 0 && _nodeTypeOf(value[prev]) === NodeType.Whitespace) {
			prev--;
		}
		// `!` delim first: it's almost always absent, and `_nodeValueOf` allocates
		// a slice — this order pays it only for genuine `!important` candidates.
		if (
			prev >= 0 &&
			_nodeTypeOf(value[prev]) === NodeType.Delim &&
			input.charCodeAt(_nodeStartOf(value[prev])) === CC_EXCLAMATION &&
			_nodeTypeOf(value[last]) === NodeType.Ident &&
			asciiCaseInsensitiveMatch(
				unescapeIdentifier(_nodeValueOf(value[last])),
				"important"
			)
		) {
			_setImportant(decl);
			// Trimmed by popping so the pooled list keeps its backing store.
			while (value.length > prev) value.pop();
		}
	}

	// 7. While the last item in decl's value is a <whitespace-token>, remove that token.
	while (
		value.length > 0 &&
		_nodeTypeOf(value[value.length - 1]) === NodeType.Whitespace
	) {
		value.pop();
	}

	// WHY: step 8. A `--` custom property takes any value, a top-level `{}` block
	// included. Otherwise such a block is the whole value or nothing, and anything
	// beside it returns nothing, which is what has `consumeABlocksContents` retry
	// the input as a qualified rule. The spec's further "no non-whitespace token
	// at the top level" check is dropped, because callers rely on `@value name:;`.
	if (!isCustomProperty) {
		let block = false;
		let beside = false;
		for (let i = 0; i < value.length && !(block && beside); i++) {
			const v = value[i];
			const type = _nodeTypeOf(v);
			if (type === NodeType.SimpleBlock && _nodeTokenOf(v) === "{") {
				// A second one stands beside the first.
				if (block) beside = true;
				block = true;
			} else if (type !== NodeType.Whitespace) {
				beside = true;
			}
		}
		if (block && beside) return undefined;
	}

	// 9. Return decl.
	_setValue(decl, value);
	return decl;
};

/**
 * Consume a list of component values, CSS Syntax Level 3 [§5.4.7](https://drafts.csswg.org/css-syntax/#consume-list-of-components) — consumes component values until EOF, the optional `stopToken`, or — when `nested` — a top-level `}` (left in the stream); a non-nested `}` is a parse error appended as a token.
 * @param {TokenStream} ts token stream
 * @param {number=} stopToken token type that terminates the list (left unconsumed)
 * @param {boolean=} nested true inside a `{}` block — a top-level `}` ends the list (left unconsumed)
 * @param {boolean=} bailOnCurly abort with `null` on a top-level `{` that something non-whitespace already stands before (left unconsumed) — for callers that would reject the list anyway (consume-a-declaration step 8) and restore a mark; a `{}` block alone is the whole value there, so that one is consumed
 * @returns {ComponentValue[] | null} consumed component values, or `null` when `bailOnCurly` hit
 */
const consumeAListOfComponentValues = (
	ts,
	stopToken,
	nested = false,
	bailOnCurly = false
) => {
	const values = /** @type {ComponentValue[]} */ (_takeList());
	// Process input
	for (;;) {
		const t = ts.next();

		// <eof-token>
		// stop token (if passed)
		// Return values.
		if (t.type === TT_EOF || t.type === stopToken) {
			return values;
		}
		// <}-token>
		// If nested is true, return values.
		// Otherwise, this is a parse error. Consume a token from input and append the result to values.
		if (t.type === TT_RIGHT_CURLY_BRACKET) {
			if (nested) return values;
			const closer = consumeATokenAsNode(ts);
			// Keep unless the type is explicitly marked skip (1); an out-of-range
			// lookup on a short `skip.types` yields `undefined`, which must not drop.
			if (!_skipActive || _skipTypes[_nodeTypeOf(closer)] !== 1) {
				values.push(closer);
			}
			continue;
		}
		// A top-level `{` dooms the list for a bailing caller, so stop before the
		// block is parsed only to be thrown away on the restore. Only once something
		// stands before it: a lone `{}` block is the whole value (§5.4.6 step 8).
		if (bailOnCurly && t.type === TT_LEFT_CURLY_BRACKET) {
			let doomed = false;
			for (let i = 0; i < values.length; i++) {
				if (_nodeTypeOf(values[i]) !== NodeType.Whitespace) {
					doomed = true;
					break;
				}
			}
			if (doomed) return null;
		}
		// Anything else: consume a component value and append it to values. A
		// skipped leaf type short-circuits first, writing no column slot and
		// building no node; a block or function never skips here.
		const tt = t.type;
		if (
			_skipActive &&
			tt !== TT_FUNCTION &&
			!(tt >= TT_LEFT_PARENTHESIS && tt <= TT_LEFT_CURLY_BRACKET) &&
			_skipTypes[_ttToNodeType[tt]] === 1
		) {
			// `t` was just peeked and is a skipped value leaf (never EOF).
			ts.advance();
			continue;
		}
		const node = consumeAComponentValue(ts, t);
		if (!_skipActive || _skipTypes[_nodeTypeOf(node)] !== 1) values.push(node);
	}
};

/**
 * Consume a component value, CSS Syntax Level 3 [§5.4.8](https://drafts.csswg.org/css-syntax/#consume-component-value) — consumes the next value (simple block, function, or single token); callers guard against EOF before calling.
 * @param {TokenStream} ts token stream
 * @param {MutableToken=} t the next token, if the caller already peeked it (defaults to `ts.next()`)
 * @returns {SimpleBlock | FunctionNode | ComponentValue} the consumed component value
 */
const consumeAComponentValue = (ts, t = ts.next()) => {
	// `t` is the next token, which hot callers pass in to skip a redundant
	// `ts.next()` per component value. The three contiguous opening brackets
	// consume a simple block and return it.
	if (t.type >= TT_LEFT_PARENTHESIS && t.type <= TT_LEFT_CURLY_BRACKET) {
		return /** @type {SimpleBlock} */ (consumeASimpleBlock(ts));
	}
	// <function-token>
	// Consume a function from input and return the result.
	if (t.type === TT_FUNCTION) {
		return /** @type {FunctionNode} */ (consumeAFunction(ts));
	}
	// Anything else: consume a token and return it, asserted not EOF. Inlines
	// `consumeATokenAsNode`, since `t` is the peeked token — a call saved per leaf
	// component value, the bulk of the nodes on a large stylesheet.
	ts.advance();
	return /** @type {ComponentValue} */ (tokenToNode(t));
};

/**
 * Consume a simple block, CSS Syntax Level 3 [§5.4.9](https://drafts.csswg.org/css-syntax/#consume-simple-block) — the next token must be `(`, `[`, or `{` (asserted); consumes component values via `consumeAComponentValue` until the mirror closing token (`)`, `]`, `}`) or EOF, returning the partial block on EOF (parse error).
 * @param {TokenStream} ts token stream
 * @returns {SimpleBlock | undefined} the parsed simple block
 */
const consumeASimpleBlock = (ts) => {
	const open = ts.next();
	// Assert (spec): the next token of input is <{-token>, <[-token>, or <(-token>.
	// Mirror closing token (`opener + 3`) and the associated block char.
	const ending = open.type + 3;
	const token = BLOCK_TOKEN_CHAR[open.type - TT_LEFT_PARENTHESIS];

	// Let block be a new simple block with its associated token set to the next token and with its value initially set to an empty list.
	const block = /** @type {SimpleBlock} */ (
		_makeContainer(NodeType.SimpleBlock, open.start, open.end)
	);
	_setToken(block, token);
	// Sealed (`_setValue`) at the return, once complete.
	const val = _takeList();

	// Discard a token from input.
	ts.discard();

	// Process input
	for (;;) {
		const t = ts.next();

		// <eof-token>
		// ending token
		// Discard a token from input. Return block.
		if (t.type === TT_EOF || t.type === ending) {
			ts.discard();
			_setValue(block, val);
			_setEnd(block, t.end);
			return block;
		}

		// anything else
		// Consume a component value from input and append the result to block’s value.
		val.push(consumeAComponentValue(ts, t));
	}
};

/**
 * Consume a function, CSS Syntax Level 3 [§5.4.10](https://drafts.csswg.org/css-syntax/#consume-function) — consumes component values up to the matching `)` or EOF (the partial function on EOF is a parse error).
 * @param {TokenStream} ts token stream
 * @returns {FunctionNode | undefined} the consumed function node
 */
const consumeAFunction = (ts) => {
	// Assert (spec): the next token is a <function-token>.
	// Consume a token from input, and let function be a new function with its name equal the returned token’s value, and a value set to an empty list.
	const tFn = ts.consume();
	const fn = /** @type {FunctionNode} */ (
		_makeContainer(NodeType.Function, tFn.start, tFn.end)
	);
	_setName(fn, tFn.start, tFn.end - 1);
	// Sealed (`_setValue`) at the return, once complete.
	const val = _takeList();

	// Process input
	for (;;) {
		const t = ts.next();

		if (t.type === TT_EOF || t.type === TT_RIGHT_PARENTHESIS) {
			// <eof-token>
			// <)-token>
			// Discard a token from input. Return function.
			ts.discard();
			_setValue(fn, val);
			_setEnd(fn, t.end);
			return fn;
		}

		// anything else
		// Consume a component value from input and append the result to function’s value.
		// Same pre-materialization skip as `consumeAListOfComponentValues`.
		const tt = t.type;
		if (
			_skipActive &&
			tt !== TT_FUNCTION &&
			!(tt >= TT_LEFT_PARENTHESIS && tt <= TT_LEFT_CURLY_BRACKET) &&
			_skipTypes[_ttToNodeType[tt]] === 1
		) {
			// `t` was just peeked and is a skipped value leaf (never EOF).
			ts.advance();
			continue;
		}
		const node = consumeAComponentValue(ts, t);
		if (!_skipActive || _skipTypes[_nodeTypeOf(node)] !== 1) val.push(node);
	}
};

// Identifier escape and unescape, over the raw text of an `<ident-token>` or any
// slice carrying escapes: `escapeIdentifier` makes a conformant token out of any
// string, and `unescapeIdentifier` reverses tokenizer-time escapes.

const regexSingleEscape = /[ -,./:-@[\]^`{-~]/;
const regexExcessiveSpaces = /(^|\\+)?(\\[A-F0-9]{1,6}) (?![a-fA-F0-9 ])/g;
// ASCII escape class per char code: 0 = pass through, 1 = `\<char>` single
// escape, 2 = `\HEX ` (control chars). Built from the original predicates so
// behaviour is identical; replaces two regex tests per character with one load.
const ESCAPE_CLASS_HEX = 2;
const ESCAPE_CLASS_SINGLE = 1;
const _escapeClassTable = new Uint8Array(128);
for (let i = 0; i < 128; i++) {
	const ch = String.fromCharCode(i);
	_escapeClassTable[i] = /[\t\n\f\r\v]/.test(ch)
		? ESCAPE_CLASS_HEX
		: ch === "\\" || regexSingleEscape.test(ch)
			? ESCAPE_CLASS_SINGLE
			: 0;
}

/**
 * Returns escaped identifier.
 * @param {string} str string
 * @returns {string} escaped identifier
 */
const _escapeIdentifier = (str) => {
	let output = "";
	// Flush safe runs in bulk: only escaped chars break the run, so an
	// identifier needing no escapes returns `str` unchanged (no allocation).
	let lastFlush = 0;
	let needSpaceFix = false;
	for (let i = 0; i < str.length; i++) {
		const cc = str.charCodeAt(i);
		const cls = cc < 128 ? _escapeClassTable[cc] : 0;
		if (cls === 0) continue;
		output += str.slice(lastFlush, i);
		if (cls === ESCAPE_CLASS_SINGLE) {
			output += `\\${str[i]}`;
		} else {
			output += `\\${cc.toString(16).toUpperCase()} `;
			needSpaceFix = true;
		}
		lastFlush = i + 1;
	}
	output = lastFlush === 0 ? str : output + str.slice(lastFlush);

	// `-` and digits are class 0 (never escaped above), so testing `str`'s lead
	// char codes is equivalent to regexes over `output` — and keeps the common
	// nothing-to-do call regex-free.
	const first = str.charCodeAt(0);
	if (
		first === CC_HYPHEN_MINUS &&
		(str.charCodeAt(1) === CC_HYPHEN_MINUS || isDigit(str.charCodeAt(1)))
	) {
		output = `\\-${output.slice(1)}`;
	} else if (isDigit(first)) {
		// A leading digit becomes `\3<digit> `, another `\HEX ` run to clean up.
		output = `\\3${str.charAt(0)} ${output.slice(1)}`;
		needSpaceFix = true;
	}

	// A space after a `\HEX` escape not followed by a hex digit is redundant. Only
	// the `\HEX ` runs above emit one, so the scan is skipped when none were, and
	// it needs an even number of preceding backslashes.
	if (needSpaceFix) {
		output = output.replace(regexExcessiveSpaces, ($0, $1, $2) => {
			/* istanbul ignore if -- @preserve: this escaper never emits an odd run of backslashes before a `\HEX` escape (literal `\` is doubled) */
			if ($1 && $1.length % 2) {
				// It’s not safe to remove the space, so don’t.
				return $0;
			}

			// Strip the space.
			return ($1 || "") + $2;
		});
	}

	return output;
};

/**
 * Returns hex. Reads up to six hex digits from `str` starting at `start` —
 * indexed rather than sliced, and case-folded inline, so the common
 * non-hex escape (e.g. `\:` in `focus\:sr-only`) allocates nothing.
 * @param {string} str string
 * @param {number} start index just past the `\`
 * @returns {[string, number] | undefined} hex
 */
const gobbleHex = (str, start) => {
	let hex = "";

	for (let i = 0; i < 6; i++) {
		const code = str.charCodeAt(start + i);
		// valid hex char [0-9 | A-F | a-f]; out-of-range reads NaN -> invalid
		const valid =
			(code >= 48 && code <= 57) ||
			(code >= 65 && code <= 70) ||
			(code >= 97 && code <= 102);
		if (!valid) break;
		// parseInt below is case-insensitive, so keep the original char.
		hex += str[start + i];
	}

	if (hex.length === 0) return undefined;

	// One trailing whitespace terminates the escape, as in the tokenizer's
	// `_consumeAnEscapedCodePoint`: after a full 6-digit escape too, for any CSS
	// whitespace, plus the extra LF of a CRLF pair.
	let consumed = hex.length;
	const trail = str.charCodeAt(start + hex.length);
	if (isAsciiWhitespace(trail)) {
		consumed = consumeExtraNewline(trail, str, start + hex.length + 1) - start;
	}

	const codePoint = Number.parseInt(hex, 16);
	const isSurrogate = codePoint >= 0xd800 && codePoint <= 0xdfff;

	// Add special case for
	// "If this number is zero, or is for a surrogate, or is greater than the maximum allowed code point"
	// https://drafts.csswg.org/css-syntax/#maximum-allowed-code-point
	if (isSurrogate || codePoint === 0x0000 || codePoint > 0x10ffff) {
		return ["�", consumed];
	}

	return [String.fromCodePoint(codePoint), consumed];
};

/**
 * Unescape identifier.
 * @param {string} str string
 * @returns {string} unescaped string
 */
const _unescapeIdentifier = (str) => {
	// `indexOf` is the no-escape fast path and the start offset in one — the
	// leading safe run is skipped and an unescaped ident returns as-is.
	const first = str.indexOf("\\");
	if (first === -1) return str;
	let ret = "";
	// Flush safe runs in bulk instead of appending char by char.
	let lastFlush = 0;
	for (let i = first; i < str.length; i++) {
		if (str[i] !== "\\") continue;
		ret += str.slice(lastFlush, i);
		const gobbled = gobbleHex(str, i + 1);
		if (gobbled !== undefined) {
			ret += gobbled[0];
			i += gobbled[1];
		} else if (str[i + 1] === "\\") {
			// Retain one `\` of an escaped `\\` pair.
			// https://github.com/postcss/postcss-selector-parser/commit/268c9a7656fb53f543dc620aa5b73a30ec3ff20e
			ret += "\\";
			i += 1;
		} else if (str.length === i + 1) {
			// A trailing lone `\` is retained.
			// https://github.com/postcss/postcss-selector-parser/commit/01a6b346e3612ce1ab20219acc26abdc259ccefb
			ret += "\\";
		}
		// Otherwise the lone `\` is dropped; the next char flushes with its run.
		lastFlush = i + 1;
	}
	ret += str.slice(lastFlush);

	return ret;
};

// Cacheable per `compiler.root` — CssParser binds once per parse via
// `.bindCache(...)` and reuses for every identifier.
const escapeIdentifier = makeCacheable(_escapeIdentifier);

/**
 * Write a name the shortest way that still names it: as `escapeIdentifier` does,
 * except that a leading `--` starts an identifier by itself and needs no escape.
 * @param {string} name the name, escapes resolved
 * @returns {string} the name, escaped where it has to be
 */
const escapeName = (name) =>
	name.startsWith("--")
		? `--${escapeIdentifier(name.slice(2))}`
		: escapeIdentifier(name);
const unescapeIdentifier = makeCacheable(_unescapeIdentifier);

/**
 * A string token's text with its quotes taken off. §4.3.5 returns the token at
 * end of input too, and that one has no closing quote to take off — nor does one
 * an odd run of backslashes escaped.
 * @param {string} v the string token's raw text
 * @returns {string} the text the quotes hold
 */
const _stringContent = (v) => {
	let end = v.length;
	if (end > 1 && v.charCodeAt(end - 1) === v.charCodeAt(0)) {
		let slashes = 0;
		while (
			end - 2 - slashes >= 1 &&
			v.charCodeAt(end - 2 - slashes) === CC_REVERSE_SOLIDUS
		) {
			slashes++;
		}
		if (slashes % 2 === 0) end--;
	}
	return v.slice(1, end);
};

/**
 * A string token's value — `_unescapeIdentifier` with the two meanings §4.3.5
 * gives a `\` that §4.3.7 does not: before a newline it is a line continuation
 * and at end of input it names nothing, so both spell nothing rather than a `\`.
 * @param {string} str the string's text, its quotes removed
 * @returns {string} the value the string holds
 */
const _unescapeStringValue = (str) => {
	const first = str.indexOf("\\");
	if (first === -1) return str;
	let ret = "";
	let lastFlush = 0;
	for (let i = first; i < str.length; i++) {
		if (str.charCodeAt(i) !== CC_REVERSE_SOLIDUS) continue;
		ret += str.slice(lastFlush, i);
		// Read before the newline test, so the `\` closing a hex escape is the one
		// that opens a continuation rather than the digits' own terminator.
		const gobbled = gobbleHex(str, i + 1);
		const next = str.charCodeAt(i + 1);
		if (gobbled !== undefined) {
			ret += gobbled[0];
			i += gobbled[1];
		} else if (next === CC_REVERSE_SOLIDUS) {
			ret += "\\";
			i += 1;
		} else if (next === CC_CARRIAGE_RETURN) {
			// A CRLF is the one newline the pair spells.
			i += str.charCodeAt(i + 2) === CC_LINE_FEED ? 2 : 1;
		} else if (next === CC_LINE_FEED || next === CC_FORM_FEED) {
			i += 1;
		}
		// Otherwise the `\` is dropped — an escaped character flushes with the run
		// after it, and one the input ran out of leaves nothing to flush.
		lastFlush = i + 1;
	}
	return ret + str.slice(lastFlush);
};

/**
 * Fold an at-keyword's source spelling to the name the tokenizer read it as, so
 * a name written with escapes is looked up as the one it spells. Unescaping
 * comes first because `asciiLowercaseName` refuses a name still carrying one.
 * @param {string} raw the name as written, the `@` excluded
 * @returns {string} the name a table is looked up by
 */
const _atKeyword = (raw) => asciiLowercaseName(unescapeIdentifier(raw));

// An at-keyword as it may be written: a hex escape ends at a whitespace the name
// does not carry, so this is no single class — and that whitespace is CSS's five
// rather than `\s`, which swallows U+00A0, an ident code point a name keeps.

// WHY: the hex escape is pinned to six digits or to fewer with no hex digit
// after it, and the other escape excludes hex, so the two cannot repartition
// the same run — ambiguous alternatives took 8s on a 60-character opener.
const AT_RULE_NAME_SOURCE = String.raw`(?:\\(?:[\da-f]{6}|[\da-f]{1,5}(?![\da-f]))[ \t\n\r\f]?|\\[^\n\da-f]|[^ \t\n\r\f({;\\])+`;

/**
 * Whether a text writes an at-rule one of `names` names, under any spelling of
 * it. Every `@` is reached by a native search, so the bytes between them cost
 * nothing here and an at-keyword is a few hundred per sheet.
 * @param {string} text the text to scan
 * @param {Set<string>} names the names asked about, unescaped and lowercased
 * @returns {boolean} true where the text names one of them
 */
const _namesAtRule = (text, names) => {
	for (let at = text.indexOf("@"); at !== -1; at = text.indexOf("@", at + 1)) {
		let pos = at + 1;
		for (;;) {
			const cc = text.charCodeAt(pos);
			if (cc === CC_REVERSE_SOLIDUS) {
				const gobbled = gobbleHex(text, pos + 1);
				pos += gobbled === undefined ? 2 : 1 + gobbled[1];
				continue;
			}
			if (
				!isLetter(cc) &&
				!isDigit(cc) &&
				cc !== CC_LOW_LINE &&
				cc !== CC_HYPHEN_MINUS
			) {
				break;
			}
			pos++;
		}
		if (names.has(_atKeyword(text.slice(at + 1, pos)))) return true;
	}
	return false;
};

// A url-token / url-string value's escaped newlines (`url("im\<newline>g.png")`).
const STRING_MULTILINE = /\\[\n\r\f]/g;
// Leading / trailing CSS whitespace inside a quoted url value.
const TRIM_WHITE_SPACES = /(^[ \t\n\r\f]*|[ \t\n\r\f]*$)/g;
// One CSS escape: `\` + up to 6 hex digits (+ optional whitespace) or any char.
const UNESCAPE = /\\([0-9a-f]{1,6}[ \t\n\r\f]?|[\s\S])/gi;

/**
 * Normalize a url value (a url-token's content or a url string's body) into
 * the form requests are resolved from: escaped newlines removed (string form),
 * edge whitespace trimmed, CSS escapes and percent-encoding decoded
 * (`data:` URIs excepted).
 * @param {string} str url string
 * @param {boolean} isString is url wrapped in quotes
 * @returns {string} normalized url
 */
const normalizeUrl = (str, isString) => {
	// Fast paths skipping the regex engine for the common URL with no escape and
	// no edge whitespace, each guard being equivalent to the regex matching
	// nothing. The regex removes an escaped newline from a string-token url.
	if (isString && str.includes("\\")) {
		str = str.replace(STRING_MULTILINE, "");
	}

	// Remove unnecessary spaces from `url("   img.png	 ")`
	if (
		str.length !== 0 &&
		(isAsciiWhitespace(str.charCodeAt(0)) ||
			isAsciiWhitespace(str.charCodeAt(str.length - 1)))
	) {
		str = str.replace(TRIM_WHITE_SPACES, "");
	}

	// Unescape
	if (str.includes("\\")) {
		str = str.replace(UNESCAPE, (match) => {
			if (match.length > 2) {
				return String.fromCharCode(Number.parseInt(match.slice(1).trim(), 16));
			}
			return match[1];
		});
	}

	// Char-code gate so the dominant non-`data:` url skips the regex test.
	if ((str.charCodeAt(0) | 0x20) === CC_LOWER_D && /^data:/i.test(str)) {
		return str;
	}

	if (str.includes("%")) {
		// Convert `url('%2E/img.png')` -> `url('./img.png')`
		try {
			str = decodeURIComponent(str);
		} catch (_err) {
			// Ignore
		}
	}

	return str;
};

// CSS-typed views over the generic visitor machinery (`util/SourceProcessor`),
// re-exported so consumers keep importing them from this module.
/**
 * @typedef {import("../util/SourceProcessor").VisitorFn<CssPath>} VisitorFn
 * @typedef {import("../util/SourceProcessor").VisitorBucket<CssPath>} VisitorBucket
 * @typedef {import("../util/SourceProcessor").VisitorMap<CssPath>} VisitorMap
 * @typedef {import("../util/SourceProcessor").CompiledVisitorMap<CssPath>} CompiledVisitorMap
 */

/**
 * A CSS Syntax §5.4 top-level consumer that streams each top-level node it
 * produces to `onNode` (in source order) rather than collecting it. Every entry
 * in `TOP_LEVEL_CONSUMERS` shares this shape, so the walk's `grammar` drives any
 * `as` mode through one call — a future mode is just another map entry.
 * @typedef {(ts: TokenStream, onNode: (node: Rule | Declaration) => void) => void} TopLevelConsumer
 */

/**
 * `as` value → the §5.4 consumer that streams its top-level nodes. Keyed by the
 * public `CssParserOptions.as` enum.
 * @type {Record<string, TopLevelConsumer>}
 */
const TOP_LEVEL_CONSUMERS = {
	stylesheet: /** @type {TopLevelConsumer} */ (consumeAStylesheetsContents),
	"block-contents": consumeADeclarationList
};

/**
 * What the minifying printer may rewrite. Every entry is on unless it is
 * `false`, so a document one transform breaks can still be minified by the rest.
 * @typedef {object} CssTransformOptions
 * @property {boolean=} colorFallbacks write a color the target cannot read as an extra declaration before it, in a spelling it does read
 * @property {boolean=} lowerUnsupported write a spelling the target cannot read as one it can — the same value, said another way
 * @property {boolean=} foldCase write a name that matches ASCII case-insensitively — an at-rule, property, pseudo, function, unit or keyword — in lowercase
 * @property {boolean=} rewriteEscapes write an escaped identifier the shortest way that names it, wherever it stands
 * @property {(boolean | "all" | "some" | string | RegExp | ((comment: string) => boolean))=} comments which comments survive: `"some"` (the default) the ones that carry something, `true` / `"all"` every one, `false` none, or the ones a pattern matches / a predicate accepts, over the comment's own text
 * @property {boolean=} customMedia write the query a `@custom-media` names wherever a condition asks for it, and drop the rule that named it
 * @property {boolean=} customSelectors write the selector list a `@custom-selector` names as `:is(…)` wherever a selector asks for it, and drop the rule that named it
 * @property {boolean=} mergeLonghands write a family of longhands as the one shorthand that sets them
 * @property {boolean=} mergeRules join rules that print the same block, at-rules that share a prelude, and a named `@layer` block a later sibling opens again
 * @property {boolean=} normalizeQuotes normalize a string's, `url()`'s, font family's and attribute value's quoting
 * @property {boolean=} reduceFunctions compute a call into the shorter call naming the same value (`calc()` and the math functions, transforms, gradients, easing functions, filters)
 * @property {boolean=} removeDeadRules drop a rule or declaration nothing can read: an empty rule, and one an identical later one supersedes
 * @property {boolean=} reorderDeclarations print a style rule's declarations in one order by family, so blocks setting the same things read alike; only declarations whose order nothing can see move
 * @property {boolean=} rewriteDirSelector write a `:dir()` the target cannot read as the `[dir]` attribute selector, which reads the attribute rather than the directionality an element may inherit
 * @property {boolean=} shortenColors write each color in the shortest spelling of the same value
 * @property {boolean=} shortenMediaQueries write a media feature in its range spelling and collapse an `and` of two into the interval
 * @property {boolean=} shortenNumbers write each number in its shortest equal spelling
 * @property {boolean=} shortenSelectors rewrite a selector into a shorter equal one
 * @property {boolean=} shortenValues write a value the shortest way its property's own grammar allows
 */

/**
 * @typedef {object} CssProcessOptions
 * @property {LocConverter=} locConverter shared loc converter (default a fresh one over the input)
 * @property {boolean=} recurseBlocks walk into block bodies' nested rules (default true); ignored while printing, as `skip` is, since a block is printed from the children it would leave unread
 * @property {("stylesheet" | "block-contents")=} as which top-level production to consume the source as (see `TOP_LEVEL_CONSUMERS`): `"stylesheet"` (default) or `"block-contents"` (a block's contents, e.g. an HTML `style` attribute)
 * @property {SkipOptions=} skip what the grammar may leave un-materialized to go faster — safe only for parts nothing reads in the active parse; default skip nothing. Ignored while printing (`minimize`), which needs every node
 * @property {boolean=} minimize print the safely-minified serialization (collapsed whitespace, dropped redundant separators, the `printer`'s value transforms) as `process` walks and return `{ code, map }` (default false = walk only, return `undefined`)
 * @property {string=} source name of the input in the emitted source map (`sources[0]` / `file`); only read while printing
 * @property {string=} content the input's contents for the map's `sourcesContent`; only read while printing
 * @property {CssEnvironment=} environment what the target can read (the CSS entries of `output.environment`), so a spelling it would not understand is never reached for; only read while printing, and an absent entry means the modern spelling is available
 * @property {boolean=} declareCharset open the stylesheet with `@charset "UTF-8"` exactly when its output holds a non-ASCII character, so it is read as UTF-8 wherever it is served, and write a string's hex escape of a character past U+FFFF as the character, which costs fewer bytes once compressed; for a stylesheet that is a file of its own, as `@charset` is read only as one's opening bytes. Off by default, and only read while printing
 * @property {("auto" | false)=} charset `false` when the stylesheet is read as UTF-8 wherever it is served, so no `@charset` is written for it and a string's hex escape of a character past U+FFFF is written as the character, as `declareCharset` writes it. `"auto"` by default, and only read while printing
 * @property {boolean=} convertLengthUnits rewrite a length into a shorter unit it is exactly equal in (`16px` -> `1pc`); off by default because it earns nothing once the asset is compressed, and only read while printing. A time is always rewritten
 * @property {boolean=} convertApproximateColors write a polar or Lab color as the nearest hex even where that hex only approximates it: a channel too near a `.5` boundary for two engines to round it alike, and a color outside the sRGB gamut, which hex can only clip. Off by default, and only read while printing; a space engines read through transfers of their own (`a98-rgb`, `prophoto-rgb`) is left alone either way
 * @property {boolean=} mergeDistantRules give a rule the selectors of a later one printing the same block, and an at-rule the block of a later one stating the same condition, past the rules between them; off by default, since it reorders the cascade and is sound only where nothing between declares what the block being moved does for an element it can match — a condition between counting for what its own rules declare
 * @property {boolean=} dropOverriddenDeclarations drop a declaration a later one in the same block overrides even where no target is selected, wherever Chromium, Gecko and WebKit each read the later value; off by default, and only read while printing. With a `browserslist` selection the drop is already made wherever every browser it names is known to read the later value, so this widens the case where no target is selected — a selection naming a browser the tables do not cover still answers for the whole of it
 * @property {boolean=} rewriteCustomProperties shorten a custom property's value the way any other value is shortened (`--x:#ffffff` -> `#fff`); off by default because `getPropertyValue()` hands that text back, and only read while printing. What it may rewrite is what any other value's tokens may be, a color in a substitution's fallback included — that being the property's value rather than the function's own argument
 * @property {EmbeddedSourceRenderer=} renderEmbeddedSource renders source this stylesheet embeds: the payload of a `url()` `data:` URL whose media type names a language webpack knows (SVG, CSS, HTML, JSON, JavaScript). Absent, a data URL is emitted exactly as written
 * @property {{ [name: string]: string }=} pseudoClasses each pseudo-class to write as a class instead (`{ "focus-visible": "focus-visible" }`), so a script can apply it where the engine does not. Only read while printing
 * @property {string[]=} unusedSymbols names a whole-project analysis found unused, which the print takes out: a bare name is a class, an id and an `@keyframes` name, and a `--`-prefixed one is a custom property. Only read while printing
 * @property {CssTransformOptions=} transforms which of the meaning-preserving rewrites the minifying print makes; each is on unless it is `false`
 * @property {DeferredEmbeddedSource[]=} deferEmbeddedSource collects what `renderEmbeddedSource` would be offered instead of offering it, for a caller whose renderer is asynchronous: the print leaves a marker for each and `finish` puts the answers in their place, so one parse serves both. Takes precedence over `renderEmbeddedSource`
 */

/**
 * The environment the stylesheet is built for. Every CSS ability the printer
 * reaches for is read off this selection, so nothing states one separately.
 * @typedef {object} CssEnvironment
 * @property {string[]=} browsers the browserslist selection (`["chrome 100", "safari 15"]`), so vendor prefixes and every spelling a target has to be able to read are decided for exactly these browsers; absent leaves prefixes untouched and assumes every ability
 * @property {boolean=} vendorPrefixes whether prefixes are written at all; false leaves them alone while the selection still decides which spellings a target reads
 */

/**
 * `CssProcessOptions.skip`: two independent axes, so each reads unambiguously.
 * @typedef {object} SkipOptions
 * @property {Uint8Array=} types component-value node types to drop from declaration value / function-arg lists (indexed by `NodeType`, 1 = skip; build with `buildSkipSet`)
 * @property {boolean=} selectorPrelude drop qualified-rule (selector) preludes — the rule and its block are still produced (default false)
 * @property {boolean=} atRulePrelude drop at-rule preludes — the at-rule and its block are still produced (default false)
 */

// Per-parse walk state in module slots, as with `_skip*`, so the walk functions
// below are module-level constants: one identity across parses keeps the
// recursive call sites monomorphic and allocates no closure per parse.
/** @typedef {import("../util/SourceProcessor").CompiledVisitorBucket<CssPath>} CompiledVisitorBucket */
/** @type {CompiledVisitorMap} */
let _visitors = /** @type {CompiledVisitorMap} */ (
	/** @type {CompiledVisitorMap | never[]} */ ([])
);
let _recurseBlocks = true;
// WHY: a block of declarations alone prints the same wherever its source text
// and the few rules read around it are the same, and half the blocks a sheet
// holds are written again; each body is composed once and handed back after.
/** @type {Map<string, string> | null} */
let _blockMemo = null;
/** @type {Node | null} the rule `_blockMemoBody` was found for */
let _blockMemoNode = null;
/** @type {string | undefined} the body found for it, undefined to compose one */
let _blockMemoBody;
/** @type {string} the key a miss stores its body under, "" for none */
let _blockMemoKey = "";
// The tables a block is looked up in, one per what encloses it: the top level's
// is `_blockMemo`. `_blockMemoTable` is the last one keyed, and
// `_blockMemoKeyTable` the one `_blockMemoKey` belongs to.
/** @type {Map<string, Map<string, string>>} */
const _blockMemoScopes = new Map();
/** @type {Map<string, string>} */
let _blockMemoTable = new Map();
/** @type {Map<string, string>} */
let _blockMemoKeyTable = _blockMemoTable;
// The same for one declaration, where its block is not one written before.
/** @type {Map<string, string> | null} */
let _declarationMemo = null;
/** @type {string} the key the declaration being walked stores its text under */
let _declarationMemoKey = "";
// The declaration memo's keys whose value is one component.
/** @type {Set<string> | null} */
let _singleValueKeys = null;
// Printing needs a faithful serialization, so it keeps dropped input as `NodeType.Raw`
// nodes; a walk-only parse leaves this false and allocates none.
let _printing = false;
// WHY: the selectors and at-rules met so far in each open block, so a prefixed
// rule is added or dropped against the sibling it needs without buffering the
// stylesheet, of which streaming holds about one node. Keyed by the block's
// rule, null for the stylesheet itself, and dropped as that block finishes, so
// it holds one set per open ancestor and never a recycled node. Null unless
// minifying for a target. The markers are `signature`, meaning the unprefixed
// rule is present, and `signature\0prefix`.
/** @type {Map<Node | null, PrefixScope> | null} */
let _seenPrefixRules = null;
// The names `unusedSymbols` gave, and the rules this print has decided name
// nothing else — both null unless the option is set, which keeps every read of
// them off a print that was given none.
/** @type {Set<string> | null} */
let _unusedSymbols = null;
// Each pseudo-class to write as a class instead, or null where none is asked for.
/** @type {Map<string, string> | null} */
let _pseudoClasses = null;
/** @type {Set<Node> | null} */
let _unusedRules = null;

// The browserslist selection this parse prefixes for, as the versions selected
// for each browser by its `SUPPORT_BROWSERS` slot; null unless minifying for
// one, which is what turns prefixing on.
/** @type {(number[] | undefined)[] | null} */
let _prefixBrowsers = null;
/** @type {CompiledVisitorBucket | undefined} */
let _commentBucket;

// Comments reach the visitor map as `NodeType.Comment` rather than through a
// side callback. They fire during tokenization, in source order among comments
// rather than interleaved with the walk, on a transient store node.
/** @type {(input: string, start: number, end: number) => number} */
const _grammarOnComment = (_src, start, end) => {
	if (_commentBucket === undefined) return end;
	const node = _makeLeaf(NodeType.Comment, start, end);
	_S._currentNode = node;
	_currentParent = null;
	_currentIndex = 0;
	const bucket = _commentBucket;
	const e = bucket.enter;
	for (let i = 0; i < e.length; i++) e[i](A);
	const x = bucket.exit;
	for (let i = 0; i < x.length; i++) x[i](A);
	return end;
};

/**
 * A node's post-order tail, shared by `_walkValue` / `_walkRule`: fire its `exit`
 * visitors, then — when printing — its printer, so the print step lives in one
 * place instead of being repeated in each walker. Rebinds the path onto `node`
 * (descending into children moved it). `writer` undefined = walk only, no print.
 * @param {Node} node the finished node
 * @param {Node | null} parent enclosing node
 * @param {number} index node's index within its sibling list
 * @param {CompiledVisitorBucket | undefined} b the node's visitor bucket
 * @param {PrintContext | undefined} writer print context when printing, else undefined
 */
const _exitNode = (node, parent, index, b, writer) => {
	if (b === undefined && writer === undefined) return;
	_S._currentNode = node;
	_currentParent = parent;
	_currentIndex = index;
	if (b !== undefined) {
		const x = b.exit;
		for (let i = 0; i < x.length; i++) x[i](A);
	}
	if (writer !== undefined) writer.printNode(node, A);
};

// The letters those two sets' names start with, as a mask over `a`-`z`. Derived
// from the sets, so a name added to either is covered without touching this.
const _MATH_NAME_FIRST_LETTERS = (() => {
	let mask = 0;
	for (const name of MATH_FUNCTIONS) mask |= 1 << (name.charCodeAt(0) - 0x61);
	for (const name of STEPPED_FUNCTIONS) {
		mask |= 1 << (name.charCodeAt(0) - 0x61);
	}
	return mask;
})();

/**
 * @param {number} start the name's first byte offset in `_input`
 * @returns {boolean} whether the name could be a math or stepped function's
 */
const _mayBeMathFunction = (start) => {
	// ASCII-lowercased in place: only a letter lands in `a`-`z` this way, and an
	// escape or a digit starts neither set's names.
	const first = _S._input.charCodeAt(start) | 0x20;
	if (first < 0x61 || first > 0x7a) return false;
	return (_MATH_NAME_FIRST_LETTERS & (1 << (first - 0x61))) !== 0;
};

// The leaves the CSS printer writes as their source text, whitespace as `_SEP`
// and an identifier or hash outside a value (`_printVerbatimLeaf`).
const _VERBATIM_LEAVES = new Uint8Array(32);
_VERBATIM_LEAVES[NodeType.Whitespace] = 1;
_VERBATIM_LEAVES[NodeType.Delim] = 1;
_VERBATIM_LEAVES[NodeType.Comma] = 1;
_VERBATIM_LEAVES[NodeType.Colon] = 1;
_VERBATIM_LEAVES[NodeType.Ident] = 1;
_VERBATIM_LEAVES[NodeType.Hash] = 1;
/** @type {EXPECTED_FUNCTION | null} */
let _verbatimLeafPrinter = null;

/**
 * Name the printer that writes `_VERBATIM_LEAVES` as their source text, so a
 * walk printing through it stores their text without calling it.
 * @param {EXPECTED_FUNCTION} printer the CSS printer
 * @returns {void}
 */
const setVerbatimLeafPrinter = (printer) => {
	_verbatimLeafPrinter = printer;
};

/**
 * Store what the CSS printer writes for a `_VERBATIM_LEAVES` node, without a
 * call through it: they are half the nodes a stylesheet holds. Most print as
 * their source, which the writer reads only once something asks for it.
 * @param {number} ty the node's type
 * @param {Node} node the node
 * @param {Node | null} parent its parent
 * @param {PrintContext} writer the print context
 * @returns {boolean} false to call the printer instead
 */
const _printVerbatimLeaf = (ty, node, parent, writer) => {
	if (ty === NodeType.Whitespace) {
		writer.printedAs(node, _SEP);
		return true;
	}
	// An identifier is a keyword and a hash a color the printer may rewrite only
	// in a value, and their escapes are respelled anywhere.
	const ident = ty === NodeType.Ident || ty === NodeType.Hash;
	if (ident && parent !== null && _within[NodeType.Declaration] !== 0) {
		return false;
	}
	const i = _nodeIndex(node);
	const start = _starts[i];
	const end = _ends[i];
	// The end of the input is where `_printedSource` resolves a trailing escape.
	if (end === _S._input.length) {
		const text = _printedSource(node);
		if (ident) {
			if (text.includes("\\")) return false;
			writer.printedAs(node, text);
		} else {
			writer.printedAs(node, text === "\\" ? "\\\n" : text);
		}
		return true;
	}
	if (ident) {
		if (_rangeHasEscape(start, end)) return false;
	} else if (
		end - start === 1 &&
		_S._input.charCodeAt(start) === CC_REVERSE_SOLIDUS
	) {
		writer.printedAs(node, "\\\n");
		return true;
	}
	writer.printedSource(node, _printedSource);
	return true;
};

/**
 * Walk a component-value subtree; children are already materialized. Fetches
 * the node's visitor bucket once (reused for enter + exit) and uses index
 * loops — `for…of` would allocate an iterator per node on this hot path. When
 * `writer` is given, fires this node's printer once its children and visitors are
 * done (post-order); the generic context owns everything the printer then does.
 * @param {Node} node component-value root
 * @param {Node | null} parent enclosing node
 * @param {number} index node's index within its sibling list
 * @param {PrintContext | undefined} writer print context when printing, else undefined
 */
const _walkValue = (node, parent, index, writer) => {
	const ty = _types[_nodeIndex(node)];
	const b = _visitors[ty];
	let skip = false;
	if (b !== undefined && b.enter.length !== 0) {
		_walkSkip = false;
		_S._currentNode = node;
		_currentParent = parent;
		_currentIndex = index;
		const e = b.enter;
		for (let i = 0; i < e.length; i++) e[i](A);
		skip = _walkSkip;
		_walkSkip = false;
	}
	// Everything below a math function is a math expression, `(…)` groups
	// included, so the depth rides the recursion as `_within` does — and still
	// counts this node while its own printer runs, which joins its children.
	let enteredMath = false;
	let enteredStepped = false;
	let enteredSupports = false;
	let previousMedia = false;
	let enteredSelector = false;
	if (!skip && (ty === NodeType.Function || ty === NodeType.SimpleBlock)) {
		const i0 = _nodeIndex(node);
		const vs = _listStarts[i0];
		const ve = vs + _listLens[i0];
		// Read off the source before the name is cut out of it: most calls in a
		// stylesheet start with a letter neither set uses, `var()` above all.
		if (
			ty === NodeType.Function &&
			(_mayBeMathFunction(_starts[i0]) ||
				_S._input.charCodeAt(_starts[i0]) === CC_REVERSE_SOLIDUS)
		) {
			const written = _S._input.slice(_starts[i0], _aux0[i0]);
			// An escape spells the same name: `c\61 lc(` is `calc()` (§4.3.7).
			const name = asciiLowercaseName(
				written.includes("\\") ? unescapeIdentifier(written) : written
			);
			if (MATH_FUNCTIONS.has(name)) {
				enteredMath = true;
				_S._mathFunctionDepth++;
			}
			if (STEPPED_FUNCTIONS.has(name)) {
				enteredStepped = true;
				_S._steppedFunctionDepth++;
			}
		}
		const previousColorOperand = _S._inColorOperand;
		if (
			ty === NodeType.Function &&
			!_S._inColorOperand &&
			_readsColorsAsNumbers(i0, vs, ve)
		) {
			_S._inColorOperand = true;
		}
		if (
			ty === NodeType.Function &&
			!_S._inSupportsPrelude &&
			_isImportSupports(i0, /** @type {Node} */ (parent))
		) {
			enteredSupports = true;
			previousMedia = _S._inMediaConditionPrelude;
			_S._inSupportsPrelude = true;
			// Its `(…)` tests a declaration, which no media feature spelling reaches.
			_S._inMediaConditionPrelude = false;
		}
		if (
			ty === NodeType.Function &&
			_S._inSupportsPrelude &&
			!_S._inSupportsSelector &&
			_namesFunction(i0, "selector")
		) {
			enteredSelector = true;
			_S._inSupportsSelector = true;
		}
		_within[ty]++;
		for (let i = vs; i < ve; i++) {
			_walkValue(_nodeRef(_flat[i]), node, i - vs, writer);
		}
		_within[ty]--;
		_S._inColorOperand = previousColorOperand;
	}
	if (
		b !== undefined ||
		writer === undefined ||
		_VERBATIM_LEAVES[ty] === 0 ||
		writer._printer !== _verbatimLeafPrinter ||
		!_printVerbatimLeaf(ty, node, parent, writer)
	) {
		_exitNode(node, parent, index, b, writer);
	}
	if (enteredMath) _S._mathFunctionDepth--;
	if (enteredStepped) _S._steppedFunctionDepth--;
	if (enteredSupports) {
		_S._inSupportsPrelude = false;
		_S._inMediaConditionPrelude = previousMedia;
	}
	if (enteredSelector) _S._inSupportsSelector = false;
};

/**
 * Whether a function is named `name` under any spelling of it.
 * @param {number} i0 the function's node index
 * @param {string} name the lowercase name
 * @returns {boolean} true when the function's name spells `name`
 */
const _namesFunction = (i0, name) => {
	const written = _S._input.slice(_starts[i0], _aux0[i0]);
	return asciiCaseInsensitiveMatch(
		written.includes("\\") ? unescapeIdentifier(written) : written,
		name
	);
};

/**
 * Whether a function is an `@import`'s `supports()`, whose condition is a feature
 * test like a `@supports` prelude's and so is read the same way.
 * @param {number} i0 the function's node index
 * @param {Node} parent the node it sits in
 * @returns {boolean} true for `supports()` at the top of an `@import` prelude
 */
const _isImportSupports = (i0, parent) => {
	const p = _nodeIndex(parent);
	if (_types[p] !== NodeType.AtRule) return false;
	return (
		_namesFunction(i0, "supports") &&
		_atKeyword(_S._input.slice(_starts[p] + 1, _aux0[p])) === "import"
	);
};

// A gradient, whatever it is prefixed with.
const GRADIENT_FUNCTION_RE = /(?:^|-)(?:linear|radial|conic)-gradient$/i;

/**
 * A function's name, for the gradient test.
 * @param {number} i0 the function's node index
 * @returns {string} the name as written
 */
const _gradientName = (i0) => _S._input.slice(_starts[i0], _aux0[i0]);

/**
 * Whether a source range holds an escape, so its unescaped spelling is the one
 * a name has to be read from.
 * @param {number} start range start
 * @param {number} end range end (exclusive)
 * @returns {boolean} true when the range holds a `\`
 */
const _rangeHasEscape = (start, end) => {
	for (let i = start; i < end; i++) {
		if (_S._input.charCodeAt(i) === CC_REVERSE_SOLIDUS) return true;
	}
	return false;
};

/**
 * Whether a declaration names a custom property, however its `--` is spelled.
 * @param {number} i0 the declaration's node index
 * @returns {boolean} true for a name spelling `--` first
 */
const _namesCustomProperty = (i0) => {
	const start = _starts[i0];
	const first = _S._input.charCodeAt(start);
	if (first !== CC_REVERSE_SOLIDUS) {
		if (first !== CC_HYPHEN_MINUS) return false;
		const second = _S._input.charCodeAt(start + 1);
		if (second === CC_HYPHEN_MINUS) return true;
		if (second !== CC_REVERSE_SOLIDUS) return false;
	}
	// `\2d -x` is `--x` (§4.3.7), and an engine keeps its value as written.
	return unescapeIdentifier(_S._input.slice(start, _aux0[i0])).startsWith("--");
};

/**
 * Whether a declaration's name is one ASCII case-insensitively, escapes resolved.
 * @param {number} i0 the declaration's node index
 * @param {string} name the lowercase name
 * @returns {boolean} true when it names `name`
 */
const _declarationNamed = (i0, name) => {
	const start = _starts[i0];
	const end = _aux0[i0];
	if (rangeAsciiCaseInsensitiveMatch(_S._input, start, end, name)) return true;
	return (
		_rangeHasEscape(start, end) &&
		asciiCaseInsensitiveMatch(
			unescapeIdentifier(_S._input.slice(start, end)),
			name
		)
	);
};

/** Longest `from` spelling: each code point as `\` + 6 hex digits + CRLF. */
const MAX_ESCAPED_FROM_LENGTH = 4 * (1 + 6 + 2);

/**
 * Whether an ident names the relative-color keyword — `\66 rom` names it too,
 * and only an escaped ident is longer than the four bytes it spells.
 * @param {number} start ident start offset
 * @param {number} end ident end offset (exclusive)
 * @returns {boolean} true when the ident names `from`
 */
const _identNamesFrom = (start, end) => {
	const length = end - start;
	if (length === 4) {
		return rangeAsciiCaseInsensitiveMatch(_S._input, start, end, "from");
	}
	if (length < 4 || length > MAX_ESCAPED_FROM_LENGTH) return false;
	return (
		_rangeHasEscape(start, end) &&
		asciiCaseInsensitiveMatch(
			unescapeIdentifier(_S._input.slice(start, end)),
			"from"
		)
	);
};

/**
 * Whether a function reads the colors below it as numbers rather than showing
 * them: a gradient interpolates its stops, `color-mix()` its arguments, and a
 * relative color reads its origin's channels off as the `r` / `l` / `h`
 * keywords. The origin is the `from` its arguments open with, so a function
 * webpack knows nothing about guards its origin too.
 * @param {number} i0 the function's node index
 * @param {number} vs its first child's slot
 * @param {number} ve one past its last child's slot
 * @returns {boolean} true when a color below it is an operand, not a value
 */
const _readsColorsAsNumbers = (i0, vs, ve) => {
	const start = _starts[i0];
	const end = _aux0[i0];
	if (_rangeHasEscape(start, end)) {
		// An escape spells either name too, so the unescaped spelling decides.
		const name = unescapeIdentifier(_S._input.slice(start, end));
		if (
			GRADIENT_FUNCTION_RE.test(name) ||
			asciiCaseInsensitiveMatch(name, "color-mix")
		) {
			return true;
		}
	} else {
		// Every gradient name ends in `gradient` and `color-mix` in `mix`, so one
		// code point rejects the rest before either name is sliced.
		const last = _S._input.charCodeAt(end - 1) | 0x20;
		if (last === CC_LOWER_T) {
			if (GRADIENT_FUNCTION_RE.test(_gradientName(i0))) return true;
		} else if (
			last === CC_LOWER_X &&
			rangeAsciiCaseInsensitiveMatch(_S._input, start, end, "color-mix")
		) {
			return true;
		}
	}
	for (let i = vs; i < ve; i++) {
		const child = _flat[i];
		const childType = _types[child];
		if (childType === NodeType.Whitespace) continue;
		return (
			childType === NodeType.Ident &&
			_identNamesFrom(_starts[child], _ends[child])
		);
	}
	return false;
};

/**
 * Set the flags an at-rule's own name decides, on the way into its prelude:
 * which of them is set is the same question wherever the walk asks it, and the
 * collected and streaming paths would otherwise have to agree by hand. The
 * caller puts them back — each rides its own recursion.
 * @param {number} i0 the at-rule's node index
 * @returns {void}
 */
const _enterAtRulePrelude = (i0) => {
	const atName = _atKeyword(_S._input.slice(_starts[i0] + 1, _aux0[i0]));
	if (atName === "property") {
		_S._inPropertyRule = true;
	} else if (atName === "function") {
		_S._inFunctionRule = true;
	} else if (atName === "font-feature-values") {
		_S._inFeatureValuesRule = true;
	}
	if (atName === "supports") {
		_S._inSupportsPrelude = true;
	}
	// `@media`, `@container` and the media queries of `@import` and `@custom-media`
	// are where a `(…)` holds a media feature, the one place range spelling is.
	else if (
		atName === "media" ||
		atName === "container" ||
		atName === "import" ||
		atName === "custom-media"
	) {
		_S._inMediaConditionPrelude = true;
	}
};

/**
 * The key a declaration prints under: its source, with the at-rules that read
 * one as a descriptor.
 * @param {Node} node the declaration
 * @param {number} i0 its node index
 * @returns {string} the key, "" where it is not one to reuse
 */
const _declarationMemoKeyOf = (node, i0) => {
	const start = _starts[i0];
	const end = _ends[i0];
	const input = _S._input;
	if (end === input.length) return "";
	const text = input.slice(start, end);
	// A comment kept in the value is written where it stands, once.
	if (text.includes("/*")) return "";
	// Bare where no descriptor at-rule is around, which no other key can equal:
	// those start with a letter naming one, and no source holds a NUL.
	return !_S._inPropertyRule && !_S._inFunctionRule && !_S._inFeatureValuesRule
		? text
		: `${_S._inPropertyRule ? "p" : ""}${_S._inFunctionRule ? "f" : ""}${
				_S._inFeatureValuesRule ? "v" : ""
			}\0${text}`;
};

/**
 * The key a qualified rule's block of declarations is composed under: its
 * source, with what the walk around it decides of how it prints.
 * @param {number} i0 the rule's node index
 * @param {Node | null} parent the node it sits in
 * @returns {string} the key, "" where the block is not one to reuse
 */
const _blockMemoKeyOf = (i0, parent) => {
	const start = _aux1[i0];
	const end = _ends[i0];
	const input = _S._input;
	if (start === -1 || end === input.length) return "";
	// A comment kept in the block is written where it stands, once.
	const block = input.slice(start, end);
	if (block.includes("/*")) return "";
	let around = "";
	if (parent !== null) {
		const p = _nodeIndex(parent);
		if (_types[p] !== NodeType.AtRule) return "";
		around = input.slice(_starts[p], _aux0[p]);
	}
	// What is around it picks the table, so the key is the block alone and no
	// copy of it is built per lookup.
	if (
		around.length === 0 &&
		!_S._inPropertyRule &&
		!_S._inFunctionRule &&
		!_S._inFeatureValuesRule
	) {
		_blockMemoTable = /** @type {Map<string, string>} */ (_blockMemo);
		return block;
	}
	const scope = `${_S._inPropertyRule ? "p" : ""}${_S._inFunctionRule ? "f" : ""}${
		_S._inFeatureValuesRule ? "v" : ""
	}${around}`;
	let table = _blockMemoScopes.get(scope);
	if (table === undefined) {
		table = new Map();
		_blockMemoScopes.set(scope, table);
	}
	_blockMemoTable = table;
	return block;
};

/**
 * Walk a structural subtree; an at-rule / qualified-rule's block was parsed
 * eagerly (§5.4.4), so its `value` holds the nested rules / declarations. When
 * `writer` is given, fires this node's printer once its children and visitors are
 * done (post-order); the generic context owns everything the printer then does.
 * @param {Node} node structural-tree root
 * @param {Node | null} parent enclosing node
 * @param {number} index node's index within its sibling list (declarations and child rules index independently)
 * @param {PrintContext | undefined} writer print context when printing, else undefined
 */
const _walkRule = (node, parent, index, writer) => {
	const i0 = _nodeIndex(node);
	const ty = _types[i0];
	const b = _visitors[ty];
	let skip = false;
	if (b !== undefined && b.enter.length !== 0) {
		_walkSkip = false;
		_S._currentNode = node;
		_currentParent = parent;
		_currentIndex = index;
		const e = b.enter;
		for (let i = 0; i < e.length; i++) e[i](A);
		skip = _walkSkip;
		_walkSkip = false;
	}
	if (!skip) {
		_within[ty]++;
		if (ty === NodeType.AtRule || ty === NodeType.QualifiedRule) {
			const ps = _listStarts[i0];
			const pe = ps + _listLens[i0];
			// A `@supports` prelude holds a declaration being *tested*, not applied,
			// so rewriting its value would change what the test asks — the flag rides
			// the recursion, since the conditions nest.
			const prevSupports = _S._inSupportsPrelude;
			const prevMedia = _S._inMediaConditionPrelude;
			const prevProperty = _S._inPropertyRule;
			const prevFunction = _S._inFunctionRule;
			const prevFeatureValues = _S._inFeatureValuesRule;
			if (ty === NodeType.AtRule) _enterAtRulePrelude(i0);
			for (let i = ps; i < pe; i++) {
				_walkValue(_nodeRef(_flat[i]), node, i - ps, writer);
			}
			_S._inSupportsPrelude = prevSupports;
			_S._inMediaConditionPrelude = prevMedia;
			if (_recurseBlocks) {
				// Declarations then child rules — downstream consumers don't need them strictly interleaved in source order.
				const bi = _bodyIdx[i0];
				if (bi !== 0) {
					const decls = _declBodies[bi - 1];
					const key =
						_blockMemo !== null &&
						ty === NodeType.QualifiedRule &&
						decls.length !== 0 &&
						_ruleBodies[bi - 1].length === 0
							? _blockMemoKeyOf(i0, parent)
							: "";
					const table = _blockMemoTable;
					const memo = key === "" ? undefined : table.get(key);
					if (memo === undefined) {
						for (let i = 0; i < decls.length; i++) {
							_walkRule(decls[i], node, i, writer);
						}
					}
					_blockMemoNode = node;
					_blockMemoBody = memo;
					_blockMemoKey = memo === undefined ? key : "";
					_blockMemoKeyTable = table;
					const ch = _ruleBodies[bi - 1];
					for (let i = 0; i < ch.length; i++) _walkRule(ch[i], node, i, writer);
				}
			}
			_S._inPropertyRule = prevProperty;
			_S._inFunctionRule = prevFunction;
			_S._inFeatureValuesRule = prevFeatureValues;
		} else if (ty === NodeType.Declaration) {
			const key =
				_declarationMemo !== null && b === undefined
					? _declarationMemoKeyOf(node, i0)
					: "";
			if (key !== "") {
				const memo = /** @type {Map<string, string>} */ (_declarationMemo).get(
					key
				);
				if (memo !== undefined) {
					_within[ty]--;
					// Its value's tokens print no text, so a box merge reads what it
					// needs of them off the flags.
					if (_mayBeMergeableName(node)) {
						_flags[i0] |= /** @type {Set<string>} */ (_singleValueKeys).has(key)
							? _FLAG_MEMO_PRINTED | _FLAG_SINGLE_VALUE
							: _FLAG_MEMO_PRINTED;
					}
					/** @type {PrintContext} */ (writer).printedAs(node, memo);
					return;
				}
				_declarationMemoKey = key;
			}
			const vs = _listStarts[i0];
			const ve = vs + _listLens[i0];
			// A declaration's value is a value context (its hashes are colors, not
			// ids); the flag rides the recursion so a hash at any depth (e.g. inside a
			// gradient) knows it, while selector-prelude hashes never see it set.
			const prevDeclaration = _S._valueDeclaration;
			const prevCustom = _S._inCustomProperty;
			const prevSubstituted = _S._inSubstitutedValue;
			if (_printing) {
				_S._valueDeclaration = node;
				// A node is an index into the flat arrays, and the printer hands one
				// back for reuse, so the memo below is answered per declaration.
				_S._keywordOnlyFor = null;
			}
			// WHY: a custom property's value is the one an engine hands back verbatim,
			// so its tokens print squeezed but as written. `@property`'s
			// `initial-value` is typed by its sibling `syntax` and is opaque the same
			// way — `0px` there is not the `0` a length accepts. Only the custom
			// property itself is what `rewriteCustomProperties` asks for, and those
			// two stay opaque whatever it says.
			if (
				(!_S._rewriteCustomProperties && _namesCustomProperty(i0)) ||
				(_S._inPropertyRule && _declarationNamed(i0, "initial-value")) ||
				(_S._inFunctionRule && _declarationNamed(i0, "result"))
			) {
				_S._inCustomProperty = true;
			}
			// WHY: so is a value holding a substitution — the engine keeps it as the
			// token stream it was written as until that resolves, so no rewrite inside
			// one prints the value it would hand back. Read once per declaration,
			// before its children print, since a `var()` sibling of the token being
			// rewritten has not been visited yet. Every substitution is a function
			// call, so a value with no `(` cannot hold one; the span is checked in
			// place, slicing it out costing a string per declaration.
			if (_printing && _hasSubstitutionInSpan(_starts[i0], _ends[i0])) {
				_S._inSubstitutedValue = true;
			}
			for (let i = vs; i < ve; i++) {
				_walkValue(_nodeRef(_flat[i]), node, i - vs, writer);
			}
			_S._valueDeclaration = prevDeclaration;
			_S._inCustomProperty = prevCustom;
			_S._inSubstitutedValue = prevSubstituted;
		}
		_within[ty]--;
	}
	_exitNode(node, parent, index, b, writer);
	if (_declarationMemoKey !== "") {
		const context = /** @type {PrintContext} */ (writer);
		const printed = context.get(node);
		if (!_mayBeMergeableName(node)) {
			/** @type {Map<string, string>} */ (_declarationMemo).set(
				_declarationMemoKey,
				printed
			);
		} else if (printed.includes(":")) {
			/** @type {Map<string, string>} */ (_declarationMemo).set(
				_declarationMemoKey,
				printed
			);
			if (_valueComponents(A, node, context).length === 1) {
				/** @type {Set<string>} */ (_singleValueKeys).add(_declarationMemoKey);
			}
		}
		_declarationMemoKey = "";
	}
};

// What a declaration printed from the memo carries in its flags, past the
// public `Flag` bits: that its value's tokens printed nothing, and whether that
// value is one component, which is all a box merge reads of them.
const _FLAG_MEMO_PRINTED = 8;
const _FLAG_SINGLE_VALUE = 16;

/**
 * Whether a declaration's value is one component, as `_valueComponents` splits
 * it, answered off its flags where it printed from the memo.
 * @param {CssPath} path the accessor
 * @param {Node} node the declaration
 * @param {PrintContext} writer the print context (children's printed text)
 * @returns {boolean} true when it is one component
 */
const _isSingleValue = (path, node, writer) => {
	const flags = _flags[_nodeIndex(node)];
	if ((flags & _FLAG_MEMO_PRINTED) !== 0) {
		return (flags & _FLAG_SINGLE_VALUE) !== 0;
	}
	return _valueComponents(path, node, writer).length === 1;
};

// WHY: nested-block streaming.
// Top-level nodes already stream, but inside one big block nothing could be
// released until the whole subtree finished. Enter an open rule once its block
// has grown enough to be worth it, then walk and recycle each later child as it
// completes: peak storage becomes the open path rather than the block.
// Printing streams with it: a rule's `prelude{` is held back when the block
// opens (`_streamOpen`), each finished child is emitted straight after it
// (`_streamEmitChild`), and the `}` closes it (`_streamClose`) — so the printer
// never assembles a parent from children a streamed body has already released.
// The two things that need a whole block at once still hold: a longhand family
// only merges in a block with no child rule, which is exactly the block this
// declines to stream, and the last of a set of identical declarations is reached
// by taking the earlier ones back out of the output rather than by looking ahead.
// On for every walk, and not an option: a block under the threshold is collected
// and walked in one batch exactly as it always was, so there is nothing to trade
// off. It is still a flag because the standalone `parseA*` entry points hand back
// a materialized tree and must not stream — `_setupParse` leaves it off for them,
// and only `grammar` turns it on.
let _streamBlocks = false;
/** @type {Node | null} node already walked inline, so its sink only recycles */
let _streamWalked = null;
// WHY: one frame per open block, indexed by depth rather than pushed and
// popped. Nesting is shallow and strictly LIFO, so an indexed store costs a
// write where parallel stacks cost bounds and capacity checks on every rule,
// and activation has to reach every ancestor, which locals could not offer.
//
// A frame is written lazily, by `_streamPublishFrame`, and only where an
// ancestor could still be reached for: just before a child rule — the one thing
// that can open a block that reaches back up — and on the block's own growth
// past the threshold. Every other block, which is every leaf rule in a
// stylesheet, keeps its state in `consumeABlocksContentsInto`'s locals and never
// touches these at all. A node is an id, so every slot but the two buffers is
// numeric: typed columns keep a write off the GC's books.
const _STREAM_MAX_DEPTH = 64;
let _depth = 0;
// The deepest frame this parse wrote, so the reset clears what was used rather
// than all `_STREAM_MAX_DEPTH` slots — nesting is 1 deep in almost every sheet.
let _frameHighWater = 0;
/** @type {Int32Array} node id of the open rule */
const _frameRule = new Int32Array(_STREAM_MAX_DEPTH);
const _frameMark = new Int32Array(_STREAM_MAX_DEPTH);
const _frameFlatMark = new Int32Array(_STREAM_MAX_DEPTH);
const _frameBodyMark = new Int32Array(_STREAM_MAX_DEPTH);
const _frameActive = new Uint8Array(_STREAM_MAX_DEPTH);
// Sibling counters, kept apart because the walk indexes declarations and child
// rules independently (see `_walkRule`) and a streamed block must agree with it.
const _frameDeclIndex = new Int32Array(_STREAM_MAX_DEPTH);
const _frameRuleIndex = new Int32Array(_STREAM_MAX_DEPTH);
// Whether a finished child of this frame is walked at all: `skipChildren()` and
// `recurseBlocks: false` both decline them, folded into one test per child.
const _frameWalk = new Uint8Array(_STREAM_MAX_DEPTH);
// What the block buffered before it activated. `consumeABlocksContentsInto`
// collects into these very arrays, so a descendant that crosses the threshold
// first can still drain what its ancestors hold.
/** @type {(Declaration[] | null)[]} */
const _frameDecls = Array.from({ length: _STREAM_MAX_DEPTH }, () => null);
/** @type {(Rule[] | null)[]} */
const _frameRules = Array.from({ length: _STREAM_MAX_DEPTH }, () => null);
const _frameParentIndex = new Int32Array(_STREAM_MAX_DEPTH);
// The `@property` / `@function` / `@font-feature-values` state this frame was
// entered under. A streamed block outlives the call that set it, so it is
// restored when the frame closes.
const _framePrevProperty = new Uint8Array(_STREAM_MAX_DEPTH);
const _framePrevFunction = new Uint8Array(_STREAM_MAX_DEPTH);
const _framePrevFeatureValues = new Uint8Array(_STREAM_MAX_DEPTH);
// Whether a streamed rule counts in `_within` until its block closes (not skipped).
const _frameWithin = new Uint8Array(_STREAM_MAX_DEPTH);
// Printing a streamed block: where its first item landed, how deep its opener
// sits in the writer's pending stack, and the direct declarations emitted so
// far, the earlier of a set of identical ones being taken back.
const _frameFirstChunk = new Int32Array(_STREAM_MAX_DEPTH);
const _framePendingDepth = new Int32Array(_STREAM_MAX_DEPTH);
/** @type {(Map<string, number> | null)[]} */
const _frameSeenDeclarations = Array.from(
	{ length: _STREAM_MAX_DEPTH },
	() => null
);
// The child rules a streamed block has emitted, by their printed text: the last
// of a set of identical ones is the only one read, and the earlier are taken
// back as each later one arrives.
/** @type {(Map<string, number> | null)[]} */
const _frameSeenRules = Array.from({ length: _STREAM_MAX_DEPTH }, () => null);
// The rule a streamed block wrote last, so the next one printing the same block
// can put its selectors into it — `_mergeAdjacentRules` for a collected body.
/** @type {(PriorTopLevel | null)[]} */
const _framePrior = Array.from({ length: _STREAM_MAX_DEPTH }, () => null);

// The openers enclosing each streamed depth, joined: what a rule there is read
// under, so one elsewhere under the same conditions keys the same.
/** @type {(string | null)[]} */
const _frameChainKey = Array.from({ length: _STREAM_MAX_DEPTH }, () => null);
/**
 * A rule the printer has written, at `[at, at + len)` of the piece holding it.
 * `key` is the text it printed to and `scope` what encloses it; a later rule
 * with both the same is this one written again. Neither is rewritten as the span
 * travels outward — only `scope` is replaced, by one standing for more — so the
 * collected and the streamed path split a rule the same way, which is what makes
 * the two agree on what a duplicate is.
 * @typedef {{ scope: RuleScope, key: string, at: number, len: number, taken: TakenPiece | null }} RuleSpan
 * @typedef {{ prefix: string, rules: Map<string, RuleSpan>, selectors: Map<string, RuleSpan | RuleSpan[]>, inner: Map<string, RuleScope>, noShadow: boolean }} RuleScope
 * @typedef {{ bodyAt: number, prelude: string, keyPrelude: string, qualified: boolean, spans: RuleSpan[] }} BlockSpans
 */
// A span no later copy is read against: it is there to be cut, not to key a
// rule. Every rule prints to a text of its own, so no rule's key is this.
const UNKEYED = "";
// Where each finished block's rules landed in it, youngest last — by print
// order, since a node id is a slot the next top-level node reuses.
/** @type {BlockSpans[]} */
const _blockSpans = [];
const _NO_RULE_SPANS = /** @type {RuleSpan[]} */ (
	/** @type {RuleSpan[] | ReadonlyArray<never>} */ (Object.freeze([]))
);
const _NO_BLOCK_SPANS = /** @type {BlockSpans[]} */ (
	/** @type {BlockSpans[] | ReadonlyArray<never>} */ (Object.freeze([]))
);
/**
 * A top-level node's text as written out, which later rules cut dead rules from.
 * A cut is recorded against the text the piece last held, so a run of cuts builds
 * the text once, when something reads it, rather than once per cut.
 */
class TakenPiece {
	/**
	 * @param {number} piece the writer's index for it
	 * @param {string} text the text it went out as
	 * @param {number} empties its length with every rule it holds cut, less one, or -1
	 */
	constructor(piece, text, empties) {
		/** @type {number} */
		this.piece = piece;
		/** @type {RuleSpan[]} shared and empty until `_noteSpans` gives it its own */
		this.spans = _NO_RULE_SPANS;
		/** @type {number} */
		this.empties = empties;
		/** @type {string} */
		this._text = text;
		/** @type {number[] | null} the ranges of `_text` cut since, as sorted start, end pairs */
		this._cuts = null;
		/** @type {number} */
		this.length = text.length;
	}

	/**
	 * @returns {string} what the piece reads now
	 */
	get text() {
		const cuts = this._cuts;
		if (cuts !== null) {
			/** @type {string[]} */
			const parts = [];
			let from = 0;
			for (let i = 0; i < cuts.length; i += 2) {
				parts.push(this._text.slice(from, cuts[i]));
				from = cuts[i + 1];
			}
			parts.push(this._text.slice(from));
			this._text = parts.join("");
			this._cuts = null;
		}
		return this._text;
	}

	/**
	 * @param {string} value what the piece reads from now on
	 */
	set text(value) {
		this._text = value;
		this._cuts = null;
		this.length = value.length;
	}

	/**
	 * @param {number} at an offset in what the piece reads now
	 * @returns {number} the code unit there, read without building the text
	 */
	charCodeAt(at) {
		return this._text.charCodeAt(this._uncut(at));
	}

	/**
	 * Take `len` code units out at `from`, offsets in what the piece reads now.
	 * @param {number} from where the cut starts
	 * @param {number} len how much it takes
	 * @returns {void}
	 */
	cut(from, len) {
		const start = this._uncut(from);
		const end = this._uncut(from + len - 1) + 1;
		const cuts = this._cuts === null ? [] : this._cuts;
		// The ranges cut before inside this one are taken with it.
		let at = 0;
		while (at < cuts.length && cuts[at] < start) at += 2;
		let past = at;
		while (past < cuts.length && cuts[past + 1] <= end) past += 2;
		cuts.splice(at, past - at, start, end);
		this._cuts = cuts;
		this.length -= len;
	}

	/**
	 * @param {number} at an offset in what the piece reads now
	 * @returns {number} the same offset in `_text`
	 */
	_uncut(at) {
		const cuts = this._cuts;
		if (cuts === null) return at;
		let offset = at;
		for (let i = 0; i < cuts.length && cuts[i] <= offset; i += 2) {
			offset += cuts[i + 1] - cuts[i];
		}
		return offset;
	}
}
// Every rule taken so far, sheet-wide, by what encloses it and then by the text
// it printed to — neither of which changes once written (see `RuleSpan`).
/** @type {Map<string, RuleScope> | null} */
let _ruleScopes = null;
/** @type {RuleScope | null} */
let _rootRuleScope = null;

/**
 * The scope for `prefix`, made once and shared — so a chain reached a level at a
 * time and the same one reached in a single step are the one scope.
 * @param {string} prefix what encloses the rules in it, "" at the top level
 * @returns {RuleScope} the scope
 */
const _ruleScopeFor = (prefix) => {
	let scopes = _ruleScopes;
	if (scopes === null) {
		scopes = new Map();
		_ruleScopes = scopes;
	}
	let scope = scopes.get(prefix);
	if (scope === undefined) {
		scope = {
			prefix,
			rules: new Map(),
			selectors: new Map(),
			inner: new Map(),
			noShadow: _NO_SHADOW_SCOPE_RE.test(prefix)
		};
		scopes.set(prefix, scope);
	}
	return scope;
};

/**
 * The top-level scope, where a rule nothing encloses lands.
 * @returns {RuleScope} the root scope
 */
const _rootScope = () => {
	let root = _rootRuleScope;
	if (root === null) {
		root = _ruleScopeFor("");
		_rootRuleScope = root;
	}
	return root;
};

/**
 * The scope a scope's rules land in once `head` encloses them. Memoized on the
 * scope, so the join runs once per block rather than once per rule in it.
 * @param {RuleScope} scope the scope the rules are in now
 * @param {string} head what now encloses them
 * @returns {RuleScope} the enclosing scope
 */
const _enclosingRuleScope = (scope, head) => {
	if (head.length === 0) return scope;
	let inner = scope.inner.get(head);
	if (inner === undefined) {
		inner = _ruleScopeFor(`${head}${scope.prefix}`);
		scope.inner.set(head, inner);
	}
	return inner;
};
// CSS Cascade 5 §6.4.1: an `!important` declaration is read from the earliest
// layer, so a copy in a later `@layer {` makes nothing dead.
let _anonymousLayers = 0;
const BARE_AT_RULE_RE = new RegExp(
	`^@(${AT_RULE_NAME_SOURCE})[ \\t\\n\\r\\f]*\\{$`,
	"i"
);

/**
 * What an opener contributes to the key the rules under it are read by. The
 * name is compared unescaped, since `@l\61yer {` opens one of these too.
 * @param {string} opener the block's prelude, its `{` included
 * @returns {string} the opener, or a token no other occurrence shares
 */
const _openerKey = (opener) => {
	const bare = BARE_AT_RULE_RE.exec(opener);
	return bare !== null && _atKeyword(bare[1]) === "layer"
		? `\u0000${++_anonymousLayers}@layer{`
		: opener;
};
// The named layer blocks a streamed block has emitted, by their opener: a later
// one of the same name is folded into the piece the first went out as.
/** @type {(Map<string, SeenLayer> | null)[]} */
const _frameSeenLayers = Array.from({ length: _STREAM_MAX_DEPTH }, () => null);

// Where each rule of a streamed block stands, as `_topLevelRules` holds the
// sheet's own — the list a cut's neighbors are put back together from.
/** @type {((PriorTopLevel | null)[] | null)[]} */
const _frameRuleRun = Array.from({ length: _STREAM_MAX_DEPTH }, () => null);

/**
 * Note where one rule of a streamed block stands, or that the run parts there.
 * Null for anything the join cannot read, which nothing folds across.
 * @param {number} d the block's depth
 * @param {PriorTopLevel | null} rule the rule written out, or null
 * @returns {void}
 */
const _noteFrameRule = (d, rule) => {
	const list = _frameRuleRun[d];
	if (list !== null) list.push(rule);
};
// The `@layer <name>{` a streamed block opens, read again when it closes: what
// it wrote is never gathered, so only that layer's record ends with it.
/** @type {(string | null)[]} */
const _frameLayerOpener = Array.from({ length: _STREAM_MAX_DEPTH }, () => null);
// Set for the one block about to be consumed, and read as it opens: a named
// `@layer` block a sibling already opened that layer in is held rather than
// streamed, so the gather still reads its body whole (`_heldForLayerGather`).
let _holdForLayerGather = false;
// The named layers each block has already opened among its children, read off
// the source as they are consumed. The printer's own record is written as a
// child is emitted, which a parent that has not activated yet has not done.
/** @type {(Set<string> | null)[]} */
const _frameSourceLayers = Array.from(
	{ length: _STREAM_MAX_DEPTH },
	() => null
);
/** @type {Set<string> | null} the same, for what the stylesheet itself opens */
let _sheetSourceLayers = null;
// A comment in a layer prelude, including one the sheet never closes.
const _LAYER_COMMENT_RE = /\/\*[^]*?(?:\*\/|$)/g;
// WHY: one object rather than one binding each, so the printer module reads
// the same fields the walk writes — every slot is initialized here, so the
// shape never transitions (see AGENTS.md "Keep instance shapes stable").
const _stream = {
	/** @type {PrintContext | undefined} the print context while streaming, else undefined */
	writer: undefined
};
// WHY: a block earns the streaming machinery only once it holds enough to be
// worth releasing. `_nodeCount` restarts at each top-level node, so
// `_nodeCount - mark` is how far this block has grown; under the threshold
// `consumeABlocksContentsInto` collects into its own arrays as it does when
// nothing streams, and the rule is walked once. Tuned so that a block crossing
// only just, near its own `}`, does not activate for almost nothing back: the
// threshold sits above the few-hundred-rule `@media` a stylesheet tends to
// hold, which is what bounds what is ever buffered.
const _STREAM_MIN_NODES = 16384;
// `_nodeCount` never reaches this, so a non-streaming block's per-child growth
// check is one compare that can never fire — no second code path to maintain.
const _STREAM_NO_LIMIT = 0x7fffffff;
// How many finished bodies a streamed block lets pile up before handing them
// back in one go (see `_streamOnNode`).
const _STREAM_BODY_SLACK = 64;

/**
 * Make an open block's frame readable, so a descendant that crosses the
 * threshold can enter and drain it on the way in. Called at most once per block,
 * and only by blocks that could still need it.
 * @param {number} d the block's depth
 * @param {Rule | undefined} rule the rule whose block this is
 * @param {number} markNode `_nodeCount` when the block opened
 * @param {number} markFlat `_flatTop` when the block opened
 * @param {Declaration[]} decls the block's declarations so far
 * @param {Rule[] | null} rules the block's child rules so far
 */
const _streamPublishFrame = (d, rule, markNode, markFlat, decls, rules) => {
	_frameRule[d] = _nodeIndex(/** @type {Rule} */ (rule));
	_frameMark[d] = markNode;
	_frameFlatMark[d] = markFlat;
	_frameActive[d] = 0;
	// The very arrays the block is collecting into, so a descendant can drain what
	// it buffered without it being on the stack.
	_frameDecls[d] = decls;
	_frameRules[d] = rules;
};

/**
 * Hold back the opener of a streamed rule — `prelude{`, which its prelude
 * already determines. Held rather than emitted so a block that prints to nothing
 * can still be dropped whole at its `}`.
 * @param {number} d the block's depth
 * @param {Rule} rule the open rule
 */
const _streamOpen = (d, rule) => {
	const writer = /** @type {PrintContext} */ (_stream.writer);
	const minify = writer.options.mode === "minify";
	// Bind the whole path rather than the node, since a prelude can be read in
	// terms of what encloses it: a keyframe selector is `from` only inside
	// `@keyframes`, and `_streamEnterRule` binds the parent only for an `enter`.
	_S._currentNode = rule;
	_currentParent = d === 0 ? null : _nodeRef(_frameRule[d - 1]);
	_currentIndex = _frameParentIndex[d];
	const opener = `${_rulePrelude(A, writer, minify)}${minify ? "" : " "}{`;
	// A streamed rule writes straight through, so the rule held back for a join
	// lands before its opener — after the opener is read, since taking one clears
	// the store the prelude's own children were printed into.
	_flushTopLevel(writer);
	_framePendingDepth[d] = writer.pushPending(opener);
	_frameSeenDeclarations[d] = null;
	_frameSeenRules[d] = null;
	_framePrior[d] = null;
	_frameSeenLayers[d] = null;
	_frameRuleRun[d] = minify && _S._transforms.mergeRules ? [] : null;
	const layerOpener = minify ? NAMED_LAYER_BLOCK_RE.exec(opener) : null;
	_frameLayerOpener[d] = layerOpener === null ? null : layerOpener[0];
	_frameFirstChunk[d] = writer.markCut();
	const above = d === 0 ? "" : _frameChainKey[d - 1];
	_frameChainKey[d] =
		!minify || above === null ? null : `${above}${_openerKey(opener)}`;
	// The outermost opener carries the whole run's source anchor: it is the one
	// the kept comments precede and the mapping points at.
	if (d === 0) {
		const i0 = _nodeIndex(rule);
		const start = _starts[i0];
		const loc = _locConverter.get(start);
		writer.anchorPending(start, loc.line - 1, loc.column);
	}
};

/**
 * Emit one finished child of a streamed block, and with it every opener still
 * held back — something inside them printed, so they are not empty after all.
 * @param {number} d the block's depth
 * @param {Node} child the finished child
 */
const _streamEmitChild = (d, child) => {
	const writer = /** @type {PrintContext} */ (_stream.writer);
	const text = writer.get(child);
	// A declaration the minifier dropped prints to nothing, and an empty rule to
	// nothing as well: neither makes the block it sits in non-empty.
	if (text.length === 0) return;
	writer.flushPending();
	const minify = writer.options.mode === "minify";
	// Taking a dead rule or declaration back and folding two named layer blocks
	// into one are two options, so each gather below runs only where its own
	// is on.
	const dropping = minify && _S._transforms.removeDeadRules;
	const merging = minify && _S._transforms.mergeRules;
	if (!minify || _nodeTypeOf(child) !== NodeType.Declaration) {
		const body = minify ? text : `\n${text}`;
		// Drained here and nowhere else: every path out of this branch has to take
		// the child's entry with it, or the next child reads it as its own.
		const own = _drainTopLevelSpans(child);
		// A named layer block is gathered rather than written out, and a rule still
		// open to being taken back must stay one rule, so neither is offered a join.
		const positional = merging && _namesPositionalAtRule(text);
		const candidate = _S._prefixDropCandidate;
		const retractable = candidate !== null && candidate.node === child;
		const entry = minify ? _ruleEntryOf(child, text) : null;
		// Read and cleared together: the rule before this one is offered it, and
		// anything the offer cannot take parts the run, settling the list it grew.
		const prior = _framePrior[d];
		_framePrior[d] = null;
		if (prior !== null) {
			if (
				dropping &&
				!positional &&
				!retractable &&
				entry !== null &&
				_joinsPriorEntry(writer, prior, entry, false)
			) {
				// The grown rule is still the one the rule after it is offered.
				_framePrior[d] = prior;
				return;
			}
			if (prior.grown) _orderRuleList(writer, prior);
		}
		// The one child rule that can be taken back: a prefixed one an unprefixed
		// twin later in this block would make dead weight. Its parent never
		// assembles a body here, so the piece is what the twin drops.
		if (retractable) {
			_S._prefixDropCandidate = null;
			const scope = _prefixScope(_nodeRef(_frameRule[d]));
			// A list waiting on a twin per selector is held as the node its parent
			// would skip, and a streamed parent assembles no body — so hold the piece.
			const covering = scope.covering;
			if (covering !== null) {
				for (const one of covering) {
					if (one.node !== child) continue;
					one.piece = writer.emitRetractable(body);
					one.node = null;
					_noteFrameRule(d, null);
					return;
				}
			}
			const pending = scope.pending;
			const held =
				pending === null ? undefined : pending.get(candidate.signature);
			const at = held === undefined ? -1 : held.indexOf(child);
			if (at !== -1) {
				/** @type {Node[]} */ (held).splice(at, 1);
				if (/** @type {Node[]} */ (held).length === 0) {
					/** @type {Map<string, Node[]>} */ (pending).delete(
						candidate.signature
					);
				}
				if (scope.retractable === null) scope.retractable = new Map();
				_holdBySignature(
					scope.retractable,
					candidate.signature,
					writer.emitRetractable(body)
				);
				_noteFrameRule(d, null);
				return;
			}
		}
		// WHY: a named layer block is one layer however far its siblings stand
		// apart, so a repeat of one folds into the first, `_mergeNamedLayerBlocks`
		// being that same gather for a body assembled in one go. A sibling naming a
		// layer any other way writes into one of these, and order within a layer is
		// the cascade's, so nothing folds back over it.
		if (positional) {
			// Where a layer is first named fixes its order, so nothing folds over it.
			_noteFrameRule(d, null);
			const opener = _namedLayerOpener(text);
			if (opener === null) {
				if (_frameSeenLayers[d] !== null) {
					_forgetLayersWritten(_frameSeenLayers[d], text);
				}
			} else {
				let layers = _frameSeenLayers[d];
				if (layers === null) {
					layers = new Map();
					_frameSeenLayers[d] = layers;
				}
				const first = layers.get(opener);
				const deep = _opensNestedLayer(text, opener);
				_noteLayerBlock(
					layers,
					_trimCssWhiteSpace(opener.slice(NAMED_LAYER_OPENER_HEAD, -1)),
					deep
				);
				const chain = _frameChainKey[d];
				const inner = chain === null ? null : _topLevelSpans(own, text);
				if (first !== undefined && !(deep && first.subtree)) {
					const grown = first.taken;
					if (grown === undefined || inner === null || chain === null) {
						writer.foldIntoRetractable(first.at, text.slice(opener.length));
						return;
					}
					// The body lands in front of the piece's own `}`, past everything
					// already keyed in it, so only what arrives now needs moving.
					const base = grown.text.length - 1 - opener.length;
					grown.text = `${grown.text.slice(0, -1)}${text.slice(opener.length)}`;
					writer.rewriteRetractable(grown.piece, grown.text);
					for (const span of inner) span.at += base;
					_noteSpans(writer, grown, inner, chain);
					_joinGatheredSeam(writer, grown, base + opener.length, entry);
					return;
				}
				const at = writer.emitRetractable(body);
				layers.set(opener, {
					at,
					subtree: false,
					taken:
						inner === null || chain === null
							? undefined
							: _takeDeduped(writer, at, body, inner, own, chain)
				});
				return;
			}
		}
		// The same rule twice in a block is read once: the later writes the same
		// declarations to the same elements and wins the tie, so it takes the
		// earlier back — as each identical declaration does below.
		if (dropping && !positional) {
			const chain = _frameChainKey[d];
			const piece = writer.emitRetractable(body);
			if (chain !== null) {
				// Sheet-wide, keyed on what the rule is read under: the same map the
				// collected path registers into, so the two agree across a sheet that
				// takes both.
				const spans = _topLevelSpans(own, text);
				if (spans !== null) {
					const taken = _takeDeduped(writer, piece, text, spans, own, chain);
					const next = entry === null ? null : _priorOf(taken, entry);
					_framePrior[d] = next;
					_noteFrameRule(d, next);
					return;
				}
			}
			let rules = _frameSeenRules[d];
			if (rules === null) {
				rules = new Map();
				_frameSeenRules[d] = rules;
			}
			const previous = rules.get(text);
			if (previous !== undefined) writer.retract(previous);
			rules.set(text, piece);
			_noteFrameRule(d, null);
			return;
		}
		writer._emit(body);
		_noteFrameRule(d, null);
		return;
	}
	// A declaration is read at its own place, so nothing folds across it.
	_noteFrameRule(d, null);
	// A declaration is read at its own place, so it parts the run of rules a join
	// gathers — as it does in a block whose body is assembled in one go.
	const parted = _framePrior[d];
	if (parted !== null) {
		_framePrior[d] = null;
		if (parted.grown) _orderRuleList(writer, parted);
	}
	// Only the last of a set of identical declarations can be read, so each takes
	// back the one it repeats — the only thing here needing a piece of its own.
	// Keyed on the printed text, as the collected printer keys its own pass.
	if (!dropping) {
		writer._emit(text);
		return;
	}
	const at = writer.emitRetractable(text);
	let seen = _frameSeenDeclarations[d];
	if (seen === null) {
		seen = new Map();
		_frameSeenDeclarations[d] = seen;
	}
	const previous = seen.get(text);
	if (previous !== undefined) writer.retract(previous);
	seen.set(text, at);
};

/**
 * Close a streamed rule: emit its `}`, or drop the whole rule when its block
 * printed to nothing and the block itself carries no meaning.
 * @param {number} d the block's depth
 * @param {Rule} rule the rule being closed
 */
const _streamClose = (d, rule) => {
	const writer = /** @type {PrintContext} */ (_stream.writer);
	const minify = writer.options.mode === "minify";
	// The list a join grew reads the same however its rules were written, which is
	// settled once the block has no further selector to put into it.
	const prior = _framePrior[d];
	if (prior !== null && prior.grown) _orderRuleList(writer, prior);
	_framePrior[d] = null;
	_frameSeenDeclarations[d] = null;
	_frameSeenRules[d] = null;
	_frameSeenLayers[d] = null;
	const run = _frameRuleRun[d];
	_frameRuleRun[d] = null;
	// Every cut this block's rules make has been made, so two a cut left side by
	// side are neighbors now — as the sheet's own are once it ends.
	if (run !== null) _reconcileRun(writer, run);
	// A streamed block is one its parent's gather never saw, so nothing folds
	// back over it — it may write into a layer already noted there.
	const layerOpener = _frameLayerOpener[d];
	_frameLayerOpener[d] = null;
	const above = d === 0 ? _seenTopLevelLayers : _frameSeenLayers[d - 1];
	if (above !== null) _closeStreamedLayer(above, layerOpener);
	if (writer.isPending(_framePendingDepth[d])) {
		// Nothing inside printed. An empty rule paints nothing, so dropping it
		// leaves the cascade as it was — but only where the block itself carries no
		// meaning (see `DROPPABLE_WHEN_EMPTY_AT_RULES`).
		const i0 = _nodeIndex(rule);
		if (
			minify &&
			_S._transforms.removeDeadRules &&
			// Not while a `@namespace` after it could still be read: taking this
			// rule out would move one up to where the engine honours it.
			!(d === 0 && _S._namespacePrologueOpen) &&
			// The mark is read first so it is cleared whichever reason drops the
			// rule: an unread one would ride the node back into the pool.
			(_takeUnusedRule(rule) ||
				_types[i0] === NodeType.QualifiedRule ||
				DROPPABLE_WHEN_EMPTY_AT_RULES.has(
					_atKeyword(_S._input.slice(_starts[i0] + 1, _aux0[i0]))
				))
		) {
			writer.dropPending();
			return;
		}
		writer.flushPending();
	} else if (minify) {
		// The `;` the last declaration carries is the one a `}` makes redundant.
		writer.dropTrailing(_frameFirstChunk[d], CC_SEMICOLON);
	}
	// Its `}` goes out as a piece of its own, so a later block of the layer it
	// opened folds in front of that — its own body is long since written out.
	if (minify && layerOpener !== null && _S._transforms.mergeRules) {
		_noteStreamedLayerClose(d, layerOpener, writer.emitRetractable("}"));
		return;
	}
	writer._emit(minify ? "}" : "\n}");
};

/**
 * Fire an open rule's `enter` and walk its prelude — both are final at `{`.
 * @param {Rule} rule the open rule
 * @param {Node | null} parent enclosing rule
 * @param {number} index the rule's index among its siblings
 * @param {number} d the block's frame depth
 * @returns {boolean} whether `skipChildren()` declined its children
 */
const _streamEnterRule = (rule, parent, index, d) => {
	const i0 = _nodeIndex(rule);
	const ty = _types[i0];
	const b = _visitors[ty];
	if (b !== undefined && b.enter.length !== 0) {
		_walkSkip = false;
		_S._currentNode = rule;
		_currentParent = parent;
		_currentIndex = index;
		const e = b.enter;
		for (let i = 0; i < e.length; i++) e[i](A);
		const skip = _walkSkip;
		_walkSkip = false;
		if (skip) return true;
	}
	_within[ty]++;
	_frameWithin[d] = 1;
	const ps = _listStarts[i0];
	const pe = ps + _listLens[i0];
	const prevSupports = _S._inSupportsPrelude;
	const prevMedia = _S._inMediaConditionPrelude;
	// Body-scoped, unlike the two prelude flags: the block streams past this call,
	// so `_streamConsumeBlock` puts them back once its `}` is reached.
	_framePrevProperty[d] = _S._inPropertyRule ? 1 : 0;
	_framePrevFunction[d] = _S._inFunctionRule ? 1 : 0;
	_framePrevFeatureValues[d] = _S._inFeatureValuesRule ? 1 : 0;
	if (ty === NodeType.AtRule) _enterAtRulePrelude(i0);
	for (let i = ps; i < pe; i++) {
		_walkValue(_nodeRef(_flat[i]), rule, i - ps, _stream.writer);
	}
	_S._inSupportsPrelude = prevSupports;
	_S._inMediaConditionPrelude = prevMedia;
	return false;
};

/**
 * A block at `d` has grown past the threshold. Every ancestor still buffering
 * must be entered first, outermost in, so `enter` stays in source order; each
 * one then walks what it buffered (its two lists are each source-ordered, so
 * merging on start offset restores source order) before the deeper level does.
 * @param {number} d depth of the block that crossed the threshold
 */
const _streamActivate = (d) => {
	for (let k = 0; k <= d; k++) {
		if (_frameActive[k] === 1) continue;
		_frameActive[k] = 1;
		const rule = /** @type {Rule} */ (_nodeRef(_frameRule[k]));
		// Claim the body slot before `enter` reads it: a streamed rule hands its
		// children to the visitors, so its block reports as empty rather than the
		// `null` of a rule with none. A later child claims above this.
		_setBody(rule, _EMPTY_LIST, _EMPTY_LIST);
		_frameBodyMark[k] = _declBodies.length;
		// The enclosing block has just been activated and drained, so its rule
		// counter is exactly how many siblings precede this one.
		const parentIndex = k === 0 ? 0 : _frameRuleIndex[k - 1]++;
		_frameParentIndex[k] = parentIndex;
		const skipped = _streamEnterRule(
			rule,
			k === 0 ? null : _nodeRef(_frameRule[k - 1]),
			parentIndex,
			k
		);
		if (_stream.writer !== undefined) _streamOpen(k, rule);
		const walk = !skipped && _recurseBlocks;
		_frameWalk[k] = walk ? 1 : 0;
		const decls = _frameDecls[k];
		const rules = _frameRules[k];
		_frameDecls[k] = null;
		_frameRules[k] = null;
		const dn = decls === null ? 0 : decls.length;
		const rn = rules === null ? 0 : rules.length;
		// Both counters end at what was buffered, so the children still to come
		// carry on from there whether or not this block's body was walked.
		_frameDeclIndex[k] = dn;
		_frameRuleIndex[k] = rn;
		if (!walk) continue;
		// Merging the two source-ordered lists on start offset restores source
		// order, while each child keeps the sibling index the batch walk gives it.
		let di = 0;
		let ri = 0;
		while (di < dn || ri < rn) {
			/** @type {Node} */
			let child;
			if (
				ri >= rn ||
				(di < dn &&
					_starts[_nodeIndex(/** @type {Declaration[]} */ (decls)[di])] <
						_starts[_nodeIndex(/** @type {Rule[]} */ (rules)[ri])])
			) {
				child = /** @type {Declaration[]} */ (decls)[di];
				_walkRule(child, rule, di, _stream.writer);
				di++;
			} else {
				child = /** @type {Rule[]} */ (rules)[ri];
				_walkRule(child, rule, ri, _stream.writer);
				ri++;
			}
			if (_stream.writer !== undefined) {
				_streamEmitChild(k, child);
				_stream.writer.dropStore();
			}
		}
	}
	// Only the deepest block may release: an ancestor's mark sits below the ids
	// its still-open descendants are using.
	if (_nodeCount > _peak) _peak = _nodeCount;
	if (_flatTop > _flatPeak) _flatPeak = _flatTop;
	_nodeCount = _frameMark[d];
	_flatTop = _frameFlatMark[d];
};

/**
 * Per-child sink of an *activated* block: walk the finished child, then release
 * every id it used. Module-level (no per-rule closure) — the depth-indexed frame
 * carries the open rule, its sibling counters and its mark. A block only reaches
 * for this once it has activated, so a small block never calls it.
 * @param {Rule | Declaration} node the finished child
 */
const _streamOnNode = (node) => {
	const d = _depth - 1;
	// A child rule that streamed on its own already entered, walked and exited
	// inline and released its ids; it reaches the sink already finished.
	if (node === _streamWalked) {
		// Already entered, printed and closed itself inline, straight into the
		// output — there is nothing left of it to emit here.
		_streamWalked = null;
	} else if (_frameWalk[d] === 1) {
		const index =
			_nodeTypeOf(node) === NodeType.Declaration
				? _frameDeclIndex[d]++
				: _frameRuleIndex[d]++;
		_walkRule(node, _nodeRef(_frameRule[d]), index, _stream.writer);
		if (_stream.writer !== undefined) {
			_streamEmitChild(d, node);
			_stream.writer.dropStore();
		}
	}
	if (_nodeCount > _peak) _peak = _nodeCount;
	if (_flatTop > _flatPeak) _flatPeak = _flatTop;
	_nodeCount = _frameMark[d];
	_flatTop = _frameFlatMark[d];
	// The child is walked, so the body slots it and its subtree claimed go back
	// too — otherwise the one thing a streamed block still grew per child. In
	// batches, shortening the arrays costing more than the compare.
	const bodyMark = _frameBodyMark[d];
	if (_declBodies.length >= bodyMark + _STREAM_BODY_SLACK) {
		_declBodies.length = bodyMark;
		_ruleBodies.length = bodyMark;
	}
};

/**
 * Whether this block is a named `@layer` one whose layer a sibling already
 * opened. Such a block gathers into that sibling once it finishes, and the
 * gather reads its body whole — so a streamed one, whose text is out before it
 * closes, never reaches it. Holding it keeps which blocks gather off whether one
 * was small enough to assemble, as `_mergeNamedLayerBlocks` says.
 *
 * Read off the source, the block's own text not being printed yet: a name the
 * printer writes differently is simply not matched, and the block streams as it
 * did before.
 * @param {Rule} rule the rule whose block follows
 * @param {number} blockStart offset of the block's `{`
 * @returns {boolean} true when the block is held for the gather
 */
const _heldForLayerGather = (rule, blockStart) => {
	if (_stream.writer === undefined || !_S._transforms.mergeRules) return false;
	if (!_isNamedLayerAtRule(rule)) return false;
	let written = _S._input.slice(_aux0[_nodeIndex(rule)], blockStart);
	// A comment stands where a space may and says nothing about the name, which
	// carries no whitespace of its own — so one is taken out rather than spelled.
	// Tested for before it is taken, the great majority of preludes carrying none.
	if (written.includes("/*")) written = written.replace(_LAYER_COMMENT_RE, "");
	const name = _trimCssWhiteSpace(written);
	// An anonymous `@layer {` is a layer of its own and gathers with nothing.
	if (name.length === 0) return false;
	const d = _depth;
	let opened = d === 0 ? _sheetSourceLayers : _frameSourceLayers[d - 1];
	if (opened === null) {
		opened = new Set();
		if (d === 0) _sheetSourceLayers = opened;
		else _frameSourceLayers[d - 1] = opened;
	}
	// Noted whether or not it is held, since the block after it reads this.
	if (opened.has(name)) return true;
	opened.add(name);
	return false;
};

/**
 * Consume a rule's block, streaming its children once the block grows past
 * `_STREAM_MIN_NODES`. A block that stays small is handed back materialized for
 * the ordinary walk, so it costs a collect and nothing else.
 * @param {TokenStream} ts token stream
 * @param {Rule} rule the rule whose block follows
 */
const _streamConsumeBlock = (ts, rule) => {
	const d = _depth;
	const blockStart = ts.next().start;
	ts.discard();
	// Set before the descent: a streamed rule's `enter` runs inside it.
	_setBlock(rule, blockStart);
	if (d >= _STREAM_MAX_DEPTH) {
		// Deeper than the frame table: fall back to the materializing path.
		consumeABlocksContentsInto(ts);
		const decls = _bcDecls;
		const rules = _bcRules;
		const c = ts.next();
		ts.discard();
		_setBody(rule, decls, rules);
		_setEnd(rule, c.type === TT_RIGHT_CURLY_BRACKET ? c.end : c.start);
		return;
	}
	// Nothing is written to the frame here: the block keeps its state in locals
	// and writes one only if it could still be read (see `_streamPublishFrame`),
	// so a rule whose block is all declarations costs what it always did.

	// Read before the descent, which is where a child block writes it next. The
	// helper notes this block in its parent's set; the set at this depth is what
	// this block's own children write into, so it starts empty.
	_holdForLayerGather = _heldForLayerGather(rule, blockStart);
	_frameSourceLayers[d] = null;
	_depth = d + 1;
	if (_depth > _frameHighWater) _frameHighWater = _depth;
	consumeABlocksContentsInto(ts, undefined, d, rule);
	_depth = d;
	const streamed = _bcStreamed;
	const decls = _bcDecls;
	const rules = _bcRules;
	const close = ts.next();
	const end = close.type === TT_RIGHT_CURLY_BRACKET ? close.end : close.start;
	ts.discard();
	_setEnd(rule, end);
	if (!streamed) {
		// Never grew: hand the body back, so the ordinary walk visits it and
		// `A.declarations` / `A.childRules` still read it.
		_setBody(rule, decls, rules);
		return;
	}
	// Streamed: `_streamActivate` already claimed the body slot, empty. The exit
	// visitors still run here; the rule's own `}` follows them, where the printer
	// would have run for a rule printed in one piece.
	if (_frameWithin[d] === 1) {
		_within[_types[_nodeIndex(rule)]]--;
		_frameWithin[d] = 0;
	}
	_exitNode(
		rule,
		d === 0 ? null : _nodeRef(_frameRule[d - 1]),
		_frameParentIndex[d],
		_visitors[_types[_nodeIndex(rule)]],
		undefined
	);
	if (_stream.writer !== undefined) _streamClose(d, rule);
	_S._inPropertyRule = _framePrevProperty[d] === 1;
	_S._inFunctionRule = _framePrevFunction[d] === 1;
	_S._inFeatureValuesRule = _framePrevFeatureValues[d] === 1;
	_streamWalked = rule;
};

/** @typedef {{ node: Node, offset: number, line: number | undefined, column: number | undefined, entry: RuleEntry, owned: boolean, own: BlockSpans | undefined, parted: boolean }} HeldTopLevel one top-level rule held back for a join, with where it stood, what it recorded, and whether a kept comment stands above it */

// The one top-level rule held back, so the next can be joined onto it (see
// `_mergeAdjacentRules` — the same merge, across nodes the writer takes one at a
// time). Null when nothing is held.
/** @type {HeldTopLevel | null} */
let _heldTopLevel = null;

/** @typedef {{ taken: TakenPiece, span: RuleSpan, entry: RuleEntry, prelude: number, grown: boolean }} PriorTopLevel the top-level rule last written out, the piece it stands in, how far into it its block starts, and whether a later rule's selectors joined its list */

// The rule written out before the held one, kept so a join only its settled
// block qualifies for can still be made. Null when that was not one whole rule.
/** @type {PriorTopLevel | null} */
let _priorTopLevel = null;

// Every top-level rule written out, in order: the piece anything else went out
// as, or null where there is none. A cut lands late, so what ends up beside what
// is known only at the end — and a piece a cut emptied parts nothing.
/** @type {(PriorTopLevel | TakenPiece | null)[] | null} */
let _topLevelRules = null;

// The pieces prefixed rules went out as at the top level, by piece: the twin that
// takes one back empties its entry, so the run reads it as gone, not standing.
/** @type {Map<number, TakenPiece> | null} */
let _prefixPieces = null;

/**
 * Note that a piece was taken back, where a top-level prefixed rule went out as it.
 * @param {number} piece the piece taken back
 * @returns {void}
 */
const _noteRetractedPiece = (piece) => {
	if (_prefixPieces === null) return;
	const taken = _prefixPieces.get(piece);
	if (taken !== undefined) taken.text = "";
};

// The named layer blocks the stylesheet's own children have emitted, by their
// opener: a later one of the same name is folded into the piece the first went
// out as, as `_frameSeenLayers` does inside a block.
/** @type {Map<string, SeenLayer> | null} */
let _seenTopLevelLayers = null;

const AT_RULE_NAME_RE = new RegExp(`^@(${AT_RULE_NAME_SOURCE})`, "i");

/**
 * The name an at-rule prelude opens with, lowercased.
 * @param {string} prelude the prelude, `@` included
 * @returns {string} the name, or "" when it names none
 */
const _atRuleName = (prelude) => {
	const match = AT_RULE_NAME_RE.exec(prelude);
	return match === null ? "" : _atKeyword(match[1]);
};

// Stands for a top-level node that is one rule whatever its text ends up being,
// a qualified rule joined onto the one beside it included.
/** @type {BlockSpans} */
const _WHOLE_TEXT_SPANS = Object.freeze({
	bodyAt: 0,
	prelude: "",
	keyPrelude: "",
	qualified: true,
	spans: _NO_RULE_SPANS
});

/**
 * What a finished top-level node recorded while it printed. A node id is a slot
 * the next top-level node reuses, so this reads at the one moment the node still
 * names itself — the writer holds a rule back a node, and by then it does not.
 * @param {Node} node the top-level node, freshly printed
 * @returns {BlockSpans | undefined} its entry, or undefined when it records none
 */
const _drainTopLevelSpans = (node) => {
	// A child's entries were spliced off as their parent assembled its body, but a
	// block a streamed parent emitted leaves its entry behind, so only a lone
	// entry is this node's own.
	const own = _blockSpans.length === 1 ? _blockSpans[0] : undefined;
	_blockSpans.length = 0;
	if (own !== undefined) return own;
	return _nodeTypeOf(node) === NodeType.QualifiedRule
		? _WHOLE_TEXT_SPANS
		: undefined;
};

/**
 * The rules a finished top-level node carries, where they land in its text. A
 * qualified rule is one rule however it nests; an at-rule states conditions its
 * body is read under, so its spans come out with its prelude on their keys.
 * @param {BlockSpans | undefined} own what it recorded, from {@link _drainTopLevelSpans}
 * @param {string} text its printed text
 * @returns {RuleSpan[] | null} the spans, or null when it carries no rule
 */
const _topLevelSpans = (own, text) => {
	if (own === undefined) return null;
	if (own === _WHOLE_TEXT_SPANS) {
		return [
			{ scope: _rootScope(), key: text, at: 0, len: text.length, taken: null }
		];
	}
	/** @type {RuleSpan[]} */
	const out = own.qualified
		? [{ scope: _rootScope(), key: text, at: 0, len: text.length, taken: null }]
		: [];
	const spans = own.spans;
	for (let i = 0; i < spans.length; i++) {
		const span = spans[i];
		out.push({
			scope: _enclosingRuleScope(span.scope, own.keyPrelude),
			key: span.key,
			at: own.bodyAt + span.at,
			len: span.len,
			taken: null
		});
	}
	return out;
};

/**
 * Take a top-level node, and with it every rule an identical later one has yet
 * to make dead. A rule already taken whose key comes round again is read for
 * nothing, so the piece holding it is written again without it — pieces are
 * joined only when the sheet ends, so one taken long ago is still reachable.
 * @param {PrintContext} writer the print context
 * @param {number} piece the piece the node was taken as
 * @param {string} text the node's printed text
 * @param {RuleSpan[]} spans where its rules land in it
 * @param {BlockSpans | undefined} own what the node recorded while printing
 * @param {string} chain what encloses it, when it is not a top-level node
 * @returns {TakenPiece} the piece, for a fold that grows it
 */
const _takeDeduped = (writer, piece, text, spans, own, chain) => {
	// A block the cuts empty is one written empty here, which the printer drops —
	// but only where the block itself carries no meaning.
	const empties =
		own !== undefined &&
		own !== _WHOLE_TEXT_SPANS &&
		_S._transforms.removeDeadRules &&
		(own.qualified ||
			(own.prelude.charCodeAt(0) === CC_AT_SIGN &&
				DROPPABLE_WHEN_EMPTY_AT_RULES.has(_atRuleName(own.prelude))))
			? own.bodyAt
			: -1;
	const taken = new TakenPiece(piece, text, empties);
	_noteSpans(writer, taken, spans, chain);
	return taken;
};

/**
 * Note the rules a piece carries, taking back the piece each identical earlier
 * one went out as. Called again for the same piece as a fold grows it.
 * @param {PrintContext} writer the print context
 * @param {TakenPiece} taken the piece they landed in
 * @param {RuleSpan[]} spans where they land in it
 * @param {string} chain what encloses them, "" at the top level
 * @returns {void}
 */
const _noteSpans = (writer, taken, spans, chain) => {
	// Every span joins the piece before any cutting: a cut here moves what
	// `taken.spans` holds, and one not yet pushed would keep its old offset.
	for (const span of spans) {
		if (chain.length !== 0) span.scope = _enclosingRuleScope(span.scope, chain);
		span.taken = taken;
	}
	// A copy its own size: a first push would give the list room for 17, and
	// the piece holds it until the sheet ends.
	if (taken.spans.length === 0) {
		taken.spans = [...spans];
	} else {
		for (const span of spans) taken.spans.push(span);
	}
	for (const span of spans) {
		if (span.key === UNKEYED) continue;
		const rules = span.scope.rules;
		const before = rules.get(span.key);
		if (before !== undefined) {
			_cutSpan(writer, /** @type {TakenPiece} */ (before.taken), before);
		}
		rules.set(span.key, span);
	}
	// After every copy is taken back, so an earlier rule a cut here left flat is
	// read as flat. A distant merge moves a block up past others, out of this order.
	if (!_S._transforms.removeDeadRules || _mergingDistantRules) return;
	// TODO add an option to make this opt-in: over the CSS corpus it costs ~10% of
	// minify CPU for 248 of 2,456,527 gzip bytes.
	for (const span of spans) {
		if (span.key !== UNKEYED && span.len !== 0) {
			_shadowEarlierRule(writer, taken, span);
		}
	}
};

// `revert`, `revert-layer`, `revert-rule`: the cascade rolled back past a rule.
const _ROLLBACK_RE = /^revert(?:-[a-z]+)?$/i;

// A keyframe's selector is an offset, and two blocks at one offset are not read
// as a cascade by every engine, so no keyframe is shadowed. A named layer's
// blocks are gathered into one without their rules noted, so none is there.
const _NO_SHADOW_SCOPE_RE = /@(?:-[a-z]+-)?keyframes|@layer/i;

// What each prior `_shadowEarlierRule` compared against declares, with the text
// that was read from, so a prior a later rule writes nothing of is passed over.
/** @type {Map<RuleSpan, { text: string, names: Set<string> }>} */
const _shadowDeclared = new Map();

/**
 * The names a printed flat rule's declarations write, as `_cascadeName` reads
 * them.
 * @param {string} rule the rule's printed text
 * @param {number} open where its `{` sits
 * @returns {Set<string>} the names
 */
const _declaredNames = (rule, open) => {
	/** @type {Set<string>} */
	const names = new Set();
	for (const declaration of _splitPrintedDeclarations(
		rule.slice(open + 1, -1)
	)) {
		if (declaration.indexOf(":") > 0) names.add(_cascadeName(declaration));
	}
	return names;
};

/**
 * The declarations of a printed block body, split on the `;` between them; a
 * nested block is a piece of its own.
 * @param {string} body the text between a rule's braces
 * @returns {string[]} each declaration, without its `;`
 */
const _splitPrintedDeclarations = (body) => {
	/** @type {string[]} */
	const out = [];
	let depth = 0;
	let quote = 0;
	let start = 0;
	for (let i = 0; i < body.length; i++) {
		const code = body.charCodeAt(i);
		if (code === CC_REVERSE_SOLIDUS) {
			i++;
		} else if (quote !== 0) {
			if (code === quote) quote = 0;
		} else if (code === CC_QUOTATION_MARK || code === CC_APOSTROPHE) {
			quote = code;
		} else if (
			code === CC_LEFT_PARENTHESIS ||
			code === CC_LEFT_SQUARE ||
			code === CC_LEFT_CURLY
		) {
			depth++;
		} else if (
			code === CC_RIGHT_PARENTHESIS ||
			code === CC_RIGHT_SQUARE ||
			code === CC_RIGHT_CURLY
		) {
			depth--;
			// A nested block ends a piece of its own, which no `;` follows.
			if (code === CC_RIGHT_CURLY && depth === 0) {
				out.push(body.slice(start, i + 1));
				start = i + 1;
			}
		} else if (code === CC_SEMICOLON && depth === 0) {
			out.push(body.slice(start, i));
			start = i + 1;
		}
	}
	if (start < body.length) out.push(body.slice(start));
	return out;
};

/**
 * The name a printed declaration is cascaded under: a custom property's as
 * written, any other lowercased.
 * @param {string} declaration a printed declaration
 * @returns {string} its name
 */
const _cascadeName = (declaration) => {
	const name = declaration.slice(0, declaration.indexOf(":"));
	return name.startsWith("--") ? name : toLowerCaseIfNeeded(name);
};

/**
 * Whether one of a later rule's values for a property is read wherever an
 * earlier one is: the same value, or one spelled from its vocabulary.
 * @param {Set<string>} values the later values
 * @param {string} earlier the earlier value
 * @returns {boolean} true when no engine falls back to the earlier one
 */
const _readWherever = (values, earlier) => {
	if (values.has(earlier)) return true;
	for (const value of values) {
		if (_sameVocabulary(value, earlier)) return true;
	}
	return false;
};

/**
 * Take out of the latest earlier rule with the same selector, in the same
 * scope, each declaration this one writes again: the later of two equally
 * specific declarations wins wherever both match, whatever stands between.
 * @param {PrintContext} writer the print context
 * @param {TakenPiece} taken the piece the rule landed in
 * @param {RuleSpan} span the rule, within that piece
 * @returns {void}
 */
const _shadowEarlierRule = (writer, taken, span) => {
	const whole = taken.text;
	const at = span.at;
	const brace = whole.indexOf("{", at);
	// Only a qualified rule: an at-rule is no one cascade. One a later cut
	// leaves flat is read as one, so every rule is noted, flat or not.
	if (
		brace <= at ||
		brace >= at + span.len ||
		whole.charCodeAt(at) === CC_AT_SIGN ||
		span.scope.noShadow
	) {
		return;
	}
	const open = brace - at;
	const selector = whole.slice(at, brace);
	const selectors = span.scope.selectors;
	const noted = selectors.get(selector);
	// A lone prior is held bare: almost every selector is written once.
	if (noted === undefined) {
		selectors.set(selector, span);
		return;
	}
	const priors = Array.isArray(noted) ? noted : [noted];
	const text = whole.slice(at, at + span.len);
	// Per name, the later values and whether one every target reads is among
	// them, and whether one of those is `!important`.
	/** @type {Map<string, { values: Set<string>, read: boolean, important: boolean }>} */
	const later = new Map();
	// Its own declarations: a rule nested in it matches other elements.
	for (const declaration of _splitPrintedDeclarations(
		text.slice(open + 1, -1)
	)) {
		if (declaration.includes("{")) continue;
		const colon = declaration.indexOf(":");
		if (colon <= 0) return;
		const name = _cascadeName(declaration);
		const end = _printedValueEnd(declaration, colon);
		const value = declaration.slice(colon + 1, end);
		// A rollback reads back past this rule, to the earlier one among others.
		if (_ROLLBACK_RE.test(value)) return;
		let entry = later.get(name);
		if (entry === undefined) {
			entry = { values: new Set(), read: false, important: false };
			later.set(name, entry);
		}
		entry.values.add(value);
		// A custom property's value is any token stream, so it always parses.
		if (name.startsWith("--") || _valueReadByTarget(name, value)) {
			entry.read = true;
			if (end !== declaration.length) entry.important = true;
		}
	}
	for (const prior of priors) {
		if (prior.len === 0) continue;
		const priorTaken = /** @type {TakenPiece} */ (prior.taken);
		const before = priorTaken.text.slice(prior.at, prior.at + prior.len);
		// A prior writing none of the names this rule writes keeps all it has, so
		// it is not split again for every later rule under its selector.
		let declared = _shadowDeclared.get(prior);
		if (declared === undefined || declared.text !== before) {
			declared = { text: before, names: _declaredNames(before, open) };
			_shadowDeclared.set(prior, declared);
		}
		let shared = false;
		for (const name of later.keys()) {
			if (declared.names.has(name)) {
				shared = true;
				break;
			}
		}
		if (!shared) continue;
		/* istanbul ignore if -- @preserve: a join grows a list only before its rule
		   is noted unless distant merges are on, which skip this; kept as the guard */
		if (
			!before.startsWith(selector) ||
			before.charCodeAt(open) !== CC_LEFT_CURLY
		) {
			continue;
		}
		// A rule still holding a nested one is left whole: only a flat one is
		// written again declaration by declaration.
		if (
			before.includes("{", open + 1) ||
			before.indexOf("}") !== before.length - 1
		) {
			continue;
		}
		/** @type {string[]} */
		const kept = [];
		let dropped = false;
		for (const declaration of _splitPrintedDeclarations(
			before.slice(open + 1, -1)
		)) {
			const colon = declaration.indexOf(":");
			const entry =
				colon > 0 ? later.get(_cascadeName(declaration)) : undefined;
			const end =
				entry === undefined ? 0 : _printedValueEnd(declaration, colon);
			// The same value, or one of its vocabulary, fails or parses alike in both.
			if (
				entry !== undefined &&
				(entry.read ||
					_readWherever(entry.values, declaration.slice(colon + 1, end))) &&
				(end === declaration.length || entry.important)
			) {
				dropped = true;
			} else {
				kept.push(declaration);
			}
		}
		if (!dropped) continue;
		if (kept.length === 0) {
			_cutSpan(writer, priorTaken, prior);
			continue;
		}
		_rewriteSpan(writer, priorTaken, prior, `${selector}{${kept.join(";")}}`);
		_rekeyShrunkSpan(writer, priorTaken, prior);
	}
	if (priors === noted) {
		priors.push(span);
	} else {
		selectors.set(selector, [/** @type {RuleSpan} */ (noted), span]);
	}
};

/**
 * Call `each` with every name a declaration of `property` could be shadowed
 * under: its own prefixes, since `margin-top` is reached by `margin`, and the
 * longhands it sets, since `inset` reaches `top` under no prefix at all. Both
 * sides of a comparison are walked this way, so either order answers alike.
 * @param {string} property a declared property name
 * @param {(name: string) => boolean | void} each stops the walk when it returns true
 * @returns {boolean} whether `each` stopped it
 */
const _eachPropertyName = (property, each) => {
	let from = 0;
	for (;;) {
		const cut = property.indexOf("-", from);
		if (each(cut === -1 ? property : property.slice(0, cut)) === true) {
			return true;
		}
		if (cut === -1) break;
		from = cut + 1;
	}
	const longhands = getShorthandLonghands().get(property);
	if (longhands === undefined) return false;
	for (const longhand of longhands) {
		if (each(longhand) === true) return true;
	}
	return false;
};

/**
 * Write a piece again with one rule's selector list grown, and move the spans
 * after it on by what was written, so a later cut still names its own text.
 * @param {PrintContext} writer the print context
 * @param {TakenPiece} taken the piece the rule was taken in
 * @param {RuleSpan} span the rule whose list grows
 * @param {number} prelude how far into the rule its block starts
 * @param {string} addition the selectors to write in, their `,` included
 * @returns {void}
 */
const _growSpanList = (writer, taken, span, prelude, addition) => {
	const at = span.at + prelude;
	taken.text = `${taken.text.slice(0, at)}${addition}${taken.text.slice(at)}`;
	writer.rewriteRetractable(taken.piece, taken.text);
	const len = addition.length;
	span.len += len;
	for (const other of taken.spans) {
		if (other === span) continue;
		if (other.at >= at) other.at += len;
		else if (other.at + other.len >= at) other.len += len;
	}
};

/**
 * Offer the pair a gathered layer block left side by side the join two siblings
 * are offered. The gather is what makes them neighbors, so nothing else ever
 * reads them as adjacent, and the same sheet minified twice would join them
 * only on the second pass.
 * @param {PrintContext} writer the print context
 * @param {TakenPiece} taken the piece the gather grew
 * @param {number} at where the gathered body starts in it
 * @param {RuleEntry | null} entry the gathered block's entry, where it kept one
 * @returns {void}
 */
const _joinGatheredSeam = (writer, taken, at, entry) => {
	if (entry === null || entry.children === null) return;
	const first = entry.children[0];
	// Only a plain qualified rule lends its selectors, and only the whole of one:
	// a block a nested rule split is described by spans of its own.
	if (first === undefined || first.atRule || first.prelude <= 0) return;
	if (!_entryListable(first)) return;
	const block = first.text.slice(first.prelude);
	/** @type {RuleSpan | undefined} */
	let before;
	/** @type {RuleSpan | undefined} */
	let after;
	// A rule nested in the one at the seam closes inside it, so only the seam's
	// own pair meets these two offsets.
	for (const span of taken.spans) {
		if (span.len === 0) continue;
		if (span.at + span.len === at) before = span;
		else if (span.at === at && span.len === first.text.length) after = span;
	}
	if (before === undefined || after === undefined) return;
	const prelude = before.len - block.length;
	if (prelude <= 0 || !taken.text.startsWith(block, before.at + prelude)) {
		return;
	}
	if (
		!_isJoinablePrelude(
			taken.text.slice(before.at, before.at + prelude),
			first.listKind === LIST_KIND_KEYFRAME,
			first.listKind === LIST_KIND_NESTED
		)
	) {
		return;
	}
	if (!_lendSelectorsTo(writer, taken, before, prelude, _entryPrelude(first))) {
		return;
	}
	_cutSpan(writer, taken, after);
};

/**
 * Cut the rules a cut left empty. A block a later rule emptied says nothing the
 * printer would have written it for, and emptying it can empty the one holding
 * it, so this runs until none is left.
 * @param {PrintContext} writer the print context
 * @param {TakenPiece} taken the piece the cut was made in
 * @param {RuleSpan[]} held the rules that held what was cut, the only ones it can have emptied
 * @returns {void}
 */
const _cutEmptiedSpans = (writer, taken, held) => {
	if (!_S._transforms.removeDeadRules) return;
	// Every span is a rule dropped empty wherever it stands: a qualified rule, or
	// one of the at-rules `DROPPABLE_WHEN_EMPTY_AT_RULES` names (`_collectRuleSpans`).
	for (const other of held) {
		const end = other.at + other.len;
		if (
			other.len > 2 &&
			taken.charCodeAt(end - 1) === CC_RIGHT_CURLY &&
			taken.charCodeAt(end - 2) === CC_LEFT_CURLY
		) {
			_cutSpan(writer, taken, other);
			return;
		}
	}
};

/**
 * Write a piece again without one rule, and move the spans after it back by
 * what went, so a later cut in the same piece still names its own text.
 * @param {PrintContext} writer the print context
 * @param {TakenPiece} taken the piece the rule was taken in
 * @param {RuleSpan} span the rule to cut
 * @returns {void}
 */
const _cutSpan = (writer, taken, span) => {
	if (span.len === 0) return;
	let from = span.at;
	let len = span.len;
	// The rule stood last in its block, so the `;` in front of it is one the
	// printer drops itself — it goes with the rule rather than being left behind.
	if (
		taken.charCodeAt(from + len) === CC_RIGHT_CURLY &&
		taken.charCodeAt(from - 1) === CC_SEMICOLON
	) {
		from -= 1;
		len += 1;
	}
	taken.cut(from, len);
	if (taken.empties !== -1 && taken.length === taken.empties + 1) {
		taken.text = "";
	}
	writer.deferRetractable(taken.piece, taken);
	const end = from + len;
	/** @type {RuleSpan[] | null} */
	let shrunk = null;
	for (const other of taken.spans) {
		if (other === span) continue;
		// One inside what went is gone with it. One that held it still describes
		// what is left of it, which a later copy of it restates in full, so it
		// only shrinks by what went.
		if (other.at >= from && other.at + other.len <= end) {
			other.len = 0;
		} else if (other.at <= from && other.at + other.len >= end) {
			other.len -= len;
			if (shrunk === null) shrunk = [];
			shrunk.push(other);
		} else if (other.at > from) {
			other.at -= len;
		}
	}
	span.len = 0;
	if (shrunk !== null) {
		for (const other of shrunk) _rekeyShrunkSpan(writer, taken, other);
		_cutEmptiedSpans(writer, taken, shrunk);
	}
};

/**
 * Key a rule a cut shrank by what it says now, which can restate another rule
 * the sheet already holds: of the two, the earlier one is dead, and goes.
 * @param {PrintContext} writer the print context
 * @param {TakenPiece} taken the piece the rule stands in
 * @param {RuleSpan} span the rule, within that piece
 * @returns {void}
 */
const _rekeyShrunkSpan = (writer, taken, span) => {
	if (span.key === UNKEYED || span.len === 0) return;
	const key = taken.text.slice(span.at, span.at + span.len);
	const rules = span.scope.rules;
	const other = rules.get(key);
	_rekeySpan(taken, span);
	if (other === undefined || other === span || other.len === 0) return;
	const otherTaken = /** @type {TakenPiece} */ (other.taken);
	const earlier =
		otherTaken.piece < taken.piece ||
		(otherTaken.piece === taken.piece && other.at < span.at);
	if (earlier) {
		rules.set(key, span);
		_cutSpan(writer, otherTaken, other);
	} else {
		_cutSpan(writer, taken, span);
	}
};

/**
 * Hand one finished top-level node to the writer, gathering a named `@layer`
 * block into the first sibling of its name — they are one layer however far
 * apart they stand, and what separates them is in another layer or in none,
 * ordered against these by the cascade rather than by where it sits, so moving
 * the later body up is not a move the cascade can see. `_mergeNamedLayerBlocks`
 * is the same gather one block down. The node goes out with whatever text it
 * ended up carrying, so a block a join already grew is the one gathered into.
 * @param {PrintContext} writer the print context
 * @param {Node} node the top-level node
 * @param {number} offset the node's source offset
 * @param {number | undefined} line 0-based source line of the node's start
 * @param {number | undefined} column 0-based source column of the node's start
 * @param {string} text the text the node goes out as
 * @param {BlockSpans=} own what it recorded while printing
 * @param {RuleEntry=} entry the entry it printed, where the caller still holds it
 * @returns {TakenPiece | null} the piece it went out as, when it can still be rewritten
 */
const _emitTopLevel = (
	writer,
	node,
	offset,
	line,
	column,
	text,
	own,
	entry
) => {
	const printing = writer.options.mode === "minify";
	// Folding two named layer blocks into one, and taking back a rule an
	// identical later one makes dead, each answer to their own option.
	const minify = printing && _S._transforms.removeDeadRules;
	// Read off the text, not the node: a block held back for a join goes out a
	// rule late, by when its node index may name the rule after it.
	const opener =
		printing && _S._transforms.mergeRules ? _namedLayerOpener(text) : null;
	// Nothing folds back over a kept comment — it was written above what follows
	// it — nor over a sibling naming a layer any other way, since it writes into
	// one of these and the order within a layer is the cascade's.
	const named =
		opener === null &&
		minify &&
		_S._transforms.mergeRules &&
		text.charCodeAt(0) === CC_AT_SIGN
			? LAYER_STATEMENT_RE.exec(text)
			: null;
	if (_seenTopLevelLayers !== null) {
		if (writer.hasInsertBefore(offset)) {
			_seenTopLevelLayers.clear();
		} else if (named !== null) {
			// A statement writes into no block, only names a layer in order — so a
			// block whose sublayer it names notes that, and the rest fold past it.
			_noteLayerBlock(_seenTopLevelLayers, named[1], false);
		} else if (opener === null && minify && _namesPositionalAtRule(text)) {
			_seenTopLevelLayers.clear();
		}
	}
	if (opener === null) {
		if (named !== null) {
			// Where a layer is first named is where its block may stand.
			if (_seenTopLevelLayers === null) _seenTopLevelLayers = new Map();
			const key = `@layer ${named[1]}{`;
			const at = writer.takeRetractable(node, offset, line, column, text);
			if (!_seenTopLevelLayers.has(key)) {
				_seenTopLevelLayers.set(key, { at, subtree: false, statement: true });
			}
			return null;
		}
		if (
			_mergingDistantRules &&
			entry !== undefined &&
			entry.text === text &&
			entry.atRule &&
			entry.prelude !== -1 &&
			_gatherDistantAtRule(writer, node, offset, line, column, text, entry)
		) {
			return null;
		}
		// Taking back a rule an identical later one makes dead is a rewrite, which
		// is minifying's to make: beautifying hands back every rule it was given.
		const spans = minify ? _topLevelSpans(own, text) : null;
		if (spans === null) {
			writer.take(node, offset, line, column, text);
			return null;
		}
		return _takeDeduped(
			writer,
			writer.takeRetractable(node, offset, line, column, text),
			text,
			spans,
			own,
			""
		);
	}
	if (_seenTopLevelLayers === null) _seenTopLevelLayers = new Map();
	const first = _seenTopLevelLayers.get(opener);
	const deep = _opensNestedLayer(text, opener);
	_noteLayerBlock(
		_seenTopLevelLayers,
		_trimCssWhiteSpace(opener.slice(NAMED_LAYER_OPENER_HEAD, -1)),
		deep
	);
	// Only the entry this block actually printed: the node index it was keyed under
	// is recycled, and emission lags a node, so reading it back would find another's.
	const ownEntry =
		entry !== undefined && entry.text === text ? entry : undefined;
	if (first === undefined || (deep && first.subtree)) {
		_seenTopLevelLayers.set(opener, {
			at: writer.takeRetractable(node, offset, line, column, text),
			subtree: false,
			entry: ownEntry
		});
		return null;
	}
	if (first.statement === true) {
		writer.rewriteRetractable(first.at, text);
		first.statement = false;
		first.entry = ownEntry;
		return null;
	}
	// Both bodies keep their order, so the layer reads as it was written.
	const grown = first.taken;
	if (grown === undefined) {
		// Read as rules where they can be, so the pair meeting at the seam joins too
		// — the gather is what makes those two neighbors.
		const joined =
			first.entry === undefined || ownEntry === undefined
				? null
				: _joinRuleEntries(first.entry, ownEntry, false);
		if (joined === null) {
			writer.foldIntoRetractable(first.at, text.slice(opener.length));
			// The piece says more than the entry does now, so it answers no later join.
			first.entry = undefined;
			return null;
		}
		// The block goes out as it stands, so the seam the gather made settles now.
		const merged = _settleJoinedSeams(joined);
		writer.rewriteRetractable(first.at, merged.text);
		first.entry = merged;
		return null;
	}
	// A streamed block is closed by a piece of its own, which carries what has
	// been folded into it so far rather than the block's own body.
	grown.text = `${grown.text.slice(0, -1)}${text.slice(opener.length)}`;
	writer.rewriteRetractable(grown.piece, grown.text);
	return null;
};

/**
 * Emit the rule held back for a join, as it stands. Its own text is what a
 * grown join left it with, and what it recorded while printing goes with it.
 * @param {PrintContext} writer the print context
 * @param {HeldTopLevel} held the held rule
 * @returns {TakenPiece | null} the piece it went out as
 */
const _emitHeld = (writer, held) => {
	// The run of joins that built it ends here, so its list is canonical now.
	if (held.owned) held.entry = _canonicalizeJoined(held.entry);
	const prior = _priorTopLevel;
	_priorTopLevel = null;
	// Its block is settled only here, so the rule beside it — offered this one
	// before it had grown — is offered it again against the block it ends up with.
	if (prior !== null && held.owned && _joinsPrior(writer, prior, held)) {
		writer.flushInsertsBefore(held.offset);
		_priorTopLevel = prior;
		return null;
	}
	const taken = _emitTopLevel(
		writer,
		held.node,
		held.offset,
		held.line,
		held.column,
		held.entry.text,
		held.own,
		held.entry
	);

	const next = _priorOf(taken, held.entry);
	// A block that folded into an earlier sibling stands nowhere now, so it is
	// numbered nowhere either: what it declares moved with it.
	if (_distantAtRuleFolded) {
		_distantAtRuleFolded = false;
	} else if (_mergingDistantRules) {
		_noteDistantRule(next, held.entry);
	}
	_settlePrior(writer);
	_priorTopLevel = next;
	if (_topLevelRules !== null) {
		// A kept comment was written above it, so it and the one before it are not
		// neighbors whatever a later cut leaves between them.
		if (held.parted) _topLevelRules.push(null);
		_topLevelRules.push(next === null ? taken : next);
	}

	return taken;
};

/**
 * The piece a rule went out as, when it went out as one whole qualified rule and
 * nothing else — the only shape a later rule can still write its selectors into.
 * @param {TakenPiece | null} taken the piece it went out as
 * @param {RuleEntry} entry the rule it went out as
 * @returns {PriorTopLevel | null} what to look back at, or null for neither
 */
const _priorOf = (taken, entry) => {
	if (taken === null || entry.prelude === -1 || entry.atRule) return null;
	// The whole rule is the piece's first span. The spans a nested rule adds ride
	// along: growing the list moves them, and cutting the rule takes them with it.
	const span = taken.spans[0];
	if (span === undefined || span.at !== 0 || span.len !== taken.text.length) {
		return null;
	}
	// A rule whose selectors may not join a list is still a neighbor: the copy
	// that only restates it goes whatever its block holds. Whether they may is
	// asked of `entry` once a later rule prints the same block.
	return {
		taken,
		span,
		entry,
		prelude: entry.prelude,
		grown: false
	};
};

/**
 * Order the list a run of look-back joins grew, once that run has ended — as
 * `_canonicalizeJoined` does for the run the held rule itself makes.
 * @param {PrintContext} writer the print context
 * @returns {void}
 */
const _settlePrior = (writer) => {
	const prior = _priorTopLevel;
	_priorTopLevel = null;
	if (prior !== null && prior.grown) _orderRuleList(writer, prior);
};

/**
 * Key a rule by the text it now goes out as: left under the text it was written
 * with, a later copy of that text would take back a rule that no longer says it.
 * One already standing under the new text is the later of the two, so the rule
 * only stops being one that copy can take.
 * @param {TakenPiece} taken the piece the rule stands in
 * @param {RuleSpan} span the rule, within that piece
 * @returns {void}
 */
const _rekeySpan = (taken, span) => {
	if (span.key === UNKEYED) return;
	const rules = span.scope.rules;
	if (rules.get(span.key) === span) rules.delete(span.key);
	span.key = taken.text.slice(span.at, span.at + span.len);
	if (!rules.has(span.key)) rules.set(span.key, span);
};

/**
 * Order the list a join grew, and key the rule by the text it now goes out as.
 * @param {PrintContext} writer the print context
 * @param {PriorTopLevel} rule the rule whose list grew
 * @returns {void}
 */
const _orderRuleList = (writer, rule) => {
	const span = rule.span;
	if (span.len === 0) return;
	const taken = rule.taken;
	const list = taken.text.slice(span.at, span.at + rule.prelude);
	const canonical = _canonicalSelectorList(list);
	if (canonical === list) return;
	taken.text = `${taken.text.slice(0, span.at)}${canonical}${taken.text.slice(
		span.at + rule.prelude
	)}`;
	writer.rewriteRetractable(taken.piece, taken.text);
	_rekeySpan(taken, span);
};

/**
 * A rule where it now stands, read as an entry a join can be offered. `plain` is
 * the fast path a join takes to the block, which it reads from the text again,
 * so the text alone still decides whether the two join.
 * @param {string} text the rule, as it now stands
 * @param {number} prelude how far into it its block starts
 * @returns {RuleEntry} the entry
 */
const _spanEntry = (text, prelude) => ({
	text,
	prelude,
	atRule: false,
	plain: true,
	listable: LIST_UNKNOWN,
	listKind: LIST_KIND_SELECTOR,
	children: null,
	head: "",
	unordered: false,
	list: null,
	block: null,
	declarations: null
});

/**
 * Write a piece again with one rule replaced, and move the spans after it by
 * what the new text is longer or shorter by.
 * @param {PrintContext} writer the print context
 * @param {TakenPiece} taken the piece the rule stands in
 * @param {RuleSpan} span the rule to write again
 * @param {string} text what it becomes
 * @returns {void}
 */
const _rewriteSpan = (writer, taken, span, text) => {
	const at = span.at;
	const end = at + span.len;
	taken.text = `${taken.text.slice(0, at)}${text}${taken.text.slice(end)}`;
	writer.rewriteRetractable(taken.piece, taken.text);
	const delta = text.length - span.len;
	span.len = text.length;
	for (const other of taken.spans) {
		if (other === span) continue;
		// One inside the rule named the text that went: it is no rule of the sheet's
		// any more, whatever the rewrite put in its place.
		if (other.at >= at && other.at + other.len <= end) {
			other.at = at;
			other.len = 0;
		} else if (other.at <= at && other.at + other.len >= end) {
			other.len += delta;
		} else if (other.at >= end) {
			other.at += delta;
		}
	}
};

/**
 * Fold the later rule's block into the earlier one's, where the two carry the
 * same selectors and stand side by side. Read where they now stand: a cut inside
 * the earlier block is what leaves it a declaration list again.
 * @param {PrintContext} writer the print context
 * @param {PriorTopLevel} before the earlier rule
 * @param {PriorTopLevel} rule the later rule, which prints nothing once folded
 * @returns {boolean} true when it was folded
 */
const _foldsNeighborBody = (writer, before, rule) => {
	const first = before.span;
	const second = rule.span;
	const merged = _joinRuleEntries(
		_spanEntry(
			before.taken.text.slice(first.at, first.at + first.len),
			before.prelude
		),
		_spanEntry(
			rule.taken.text.slice(second.at, second.at + second.len),
			rule.prelude
		),
		false
	);
	if (merged === null) return false;
	_rewriteSpan(writer, before.taken, first, merged.text);
	_rekeySpan(before.taken, first);
	_cutSpan(writer, rule.taken, second);
	return true;
};

/**
 * Join two top-level rules that print the same block, the later one's selectors
 * going to the earlier. Both are read where they now stand.
 * @param {PrintContext} writer the print context
 * @param {PriorTopLevel} before the earlier rule
 * @param {PriorTopLevel} rule the later rule, which prints nothing once taken
 * @returns {boolean} true when it was taken
 */
const _joinsNeighbor = (writer, before, rule) => {
	const first = before.span;
	const second = rule.span;
	if (first.len <= before.prelude || second.len <= rule.prelude) return false;
	const head = before.taken.text;
	const tail = rule.taken.text;
	// The same rule twice is one rule: nothing stands between them, so the later
	// copy only restates what the earlier already says.
	const same =
		before.prelude === rule.prelude &&
		_rangeEquals(
			head,
			first.at,
			first.at + before.prelude,
			tail,
			second.at,
			second.at + rule.prelude
		);
	if (
		!_rangeEquals(
			head,
			first.at + before.prelude,
			first.at + first.len,
			tail,
			second.at + rule.prelude,
			second.at + second.len
		)
	) {
		// Two blocks the same selectors read are one block, in the order they were
		// written — which is what the cut that brought them together left behind.
		return same && _foldsNeighborBody(writer, before, rule);
	}
	if (!same) {
		if (!_entryListable(before.entry) || !_entryListable(rule.entry)) {
			return false;
		}
		const selector = tail.slice(second.at, second.at + rule.prelude);
		if (
			!_lendSelectorsTo(writer, before.taken, first, before.prelude, selector)
		) {
			return false;
		}
		before.prelude += 1 + selector.length;
		before.grown = true;
	}
	_cutSpan(writer, rule.taken, second);
	return true;
};

/**
 * Join the top-level rules that ended up side by side. A rule a cut took out
 * parts nothing, so what it stood between only meets here, once every cut a
 * later rule makes has been made.
 * @param {PrintContext} writer the print context
 * @returns {void}
 */
const _reconcileTopLevel = (writer) => {
	if (_topLevelRules !== null) _reconcileRun(writer, _topLevelRules);
};

/**
 * Join the rules of one block that ended up side by side, once every cut a later
 * rule makes has been made. A rule a cut took out parts nothing.
 * @param {PrintContext} writer the print context
 * @param {(PriorTopLevel | TakenPiece | null)[]} list where each rule of the block stands, or the piece something else went out as
 * @returns {void}
 */
const _reconcileRun = (writer, list) => {
	// The rules still standing: one taken by the rule before it leaves that one
	// next to the rule before *it*, which the block it took on may be all it needed.
	/** @type {PriorTopLevel[]} */
	const run = [];
	for (const rule of list) {
		if (rule === null || !("span" in rule)) {
			if (rule === null || rule.text.length !== 0) run.length = 0;
			continue;
		}
		if (rule.span.len === 0) continue;
		if (
			run.length === 0 ||
			!_joinsNeighbor(writer, run[run.length - 1], rule)
		) {
			run.push(rule);
			continue;
		}
		while (
			run.length > 1 &&
			_joinsNeighbor(writer, run[run.length - 2], run[run.length - 1])
		) {
			run.pop();
		}
	}
	for (const rule of list) {
		if (rule !== null && "span" in rule && rule.grown) {
			_orderRuleList(writer, rule);
		}
	}
};

/**
 * Write the held rule's selectors into the rule before it, where the two print
 * the same block and stand side by side with nothing between.
 * @param {PrintContext} writer the print context
 * @param {PriorTopLevel} prior the rule written out before it
 * @param {HeldTopLevel} held the rule being released
 * @returns {boolean} true when they were taken, and the held rule prints nothing
 */
const _joinsPrior = (writer, prior, held) =>
	_joinsPriorEntry(writer, prior, held.entry, held.parted);

/**
 * Write one rule's selectors into the rule before it, where the two print the
 * same block and stand side by side with nothing between.
 * @param {PrintContext} writer the print context
 * @param {PriorTopLevel} prior the rule written out before it
 * @param {RuleEntry} entry the rule being released
 * @param {boolean} parted whether a kept comment stands between the two
 * @returns {boolean} true when they were taken, and the later rule prints nothing
 */
const _joinsPriorEntry = (writer, prior, entry, parted) => {
	if (!_S._transforms.mergeRules) return false;
	if (entry.prelude === -1 || entry.atRule) return false;
	// A kept comment between the two was written above the later rule, which is
	// read where it still stands rather than here, the flush having cleared it.
	if (parted) return false;
	const span = prior.span;
	// Read where the rule now stands, never where it was taken: a cut inside its
	// block, or a list this already grew, leaves the entry it went out as stale.
	if (span.len <= prior.prelude) return false;
	const text = prior.taken.text;
	if (
		!_rangeEquals(
			text,
			span.at + prior.prelude,
			span.at + span.len,
			entry.text,
			entry.prelude,
			entry.text.length
		)
	) {
		return false;
	}
	// After the blocks compare, which almost none do: the shape test is a regex.
	if (!_entryListable(entry) || !_entryListable(prior.entry)) return false;
	// The same rule twice is one rule: nothing stands between them, so the later
	// copy only restates what the earlier already says.
	if (
		prior.prelude === entry.prelude &&
		_rangeEquals(
			text,
			span.at,
			span.at + prior.prelude,
			entry.text,
			0,
			entry.prelude
		)
	) {
		return true;
	}
	if (
		!_lendSelectorsTo(
			writer,
			prior.taken,
			span,
			prior.prelude,
			_entryPrelude(entry)
		)
	) {
		return false;
	}
	prior.prelude += 1 + entry.prelude;
	prior.grown = true;
	return true;
};

/**
 * Hold a top-level at-rule stating a condition, or fold one into an earlier
 * sibling stating the same. Its block's rules move to where that one's end, so
 * only what stands between the two can read differently for them.
 * @param {PrintContext} writer the print context
 * @param {Node} node the at-rule
 * @param {number | undefined} offset its source offset
 * @param {number | undefined} line its 0-based source line
 * @param {number | undefined} column its 0-based source column
 * @param {string} text the at-rule as printed
 * @param {RuleEntry} entry the rule it goes out as
 * @returns {boolean} true when it was folded or held, so nothing else takes it
 */
const _gatherDistantAtRule = (
	writer,
	node,
	offset,
	line,
	column,
	text,
	entry
) => {
	// Only an at-rule whose block holds rules: one holding declarations says what
	// it says once, and joining two of those is not this rewrite.
	if (entry.children === null) return false;
	const prelude = text.slice(0, entry.prelude);
	// A layer is not a condition stated twice: a named one sits where its name
	// was first written, and an anonymous one opens a layer of its own. The join
	// refuses both, so this only keeps the piece from being held for one.
	if (_atRuleName(prelude) === "layer") return false;
	const seen = _distantAtRules.get(prelude);
	if (seen === undefined) {
		// `_noteDistantRule` numbers this node next, so that is where it stands.
		_distantAtRules.set(prelude, {
			at: writer.takeRetractable(node, offset, line, column, text),
			entry,
			seq: _distantSeq + 1
		});
		return true;
	}
	const declared = _atRuleDeclaredProperties(entry);
	if (declared === null) return false;
	// Anything the walk could not see into declares everything, which is how an
	// at-rule between two of these ends the gather.
	if (_shadowedSince(declared, seen.seq, _entrySubjectTypes(entry))) {
		return false;
	}
	const joined =
		seen.entry === undefined
			? null
			: _joinRuleEntries(seen.entry, entry, false);
	if (joined === null) return false;
	// The rule it folded into goes out as it stands, so its seam settles now.
	const merged = _settleJoinedSeams(joined);
	writer.rewriteRetractable(seen.at, merged.text);
	seen.entry = merged;
	_distantAtRuleFolded = true;
	return true;
};

/**
 * Whether a top-level rule after the one numbered `seq` declared `name` for an
 * element the subjects can be — any element, where they are null.
 * @param {string} name a property name, `all`, or `*` for any property
 * @param {number} seq where the candidate's block was held
 * @param {Set<string> | null} subjects the candidate's subject types
 * @returns {boolean} true when such a declaration came since
 */
const _declaredSince = (name, seq, subjects) => {
	const at = _distantDeclaredAt.get(name);
	if (at !== undefined && at > seq) return true;
	if (subjects === null) {
		const typed = _distantTypedDeclaredAt.get(name);
		return typed !== undefined && typed > seq;
	}
	for (const type of subjects) {
		const typed = _distantTypeDeclaredAt.get(`${type} ${name}`);
		if (typed !== undefined && typed > seq) return true;
	}
	return false;
};

/**
 * Whether a block declaring `properties` would read differently moved back to
 * where the one numbered `seq` was held: `all` there is shadowed by anything.
 * @param {Iterable<string>} properties the moved block's properties
 * @param {number} seq where the earlier block was held
 * @param {Set<string> | null} subjects the moved selectors' subject types
 * @returns {boolean} true when a rule since declares what it does
 */
const _shadowedSince = (properties, seq, subjects) => {
	const since = (/** @type {string} */ name) =>
		_declaredSince(name, seq, subjects);
	if (since("all")) return true;
	for (const property of properties) {
		if (property === "all" ? since("*") : _eachPropertyName(property, since)) {
			return true;
		}
	}
	return false;
};

/**
 * Note a top-level node the walk has just passed: what it declares, so a later
 * candidate knows whether anything since can shadow its block, and the node
 * itself when a later rule printing the same block could join its selectors.
 * Anything but a plain qualified rule is taken as declaring everything — an
 * at-rule holds rules this cannot see into.
 * @param {PriorTopLevel | null} rule the whole rule it went out as, when it did
 * @param {RuleEntry} entry the rule it went out as
 * @returns {void}
 */
const _noteDistantRule = (rule, entry) => {
	_distantSeq++;
	const plain = entry.prelude !== -1 && !entry.atRule && entry.plain;
	// A condition only narrows which elements read the block it holds, so an
	// at-rule this can read shadows what its own rules declare and no more.
	const written = plain
		? _blockProperties(entry.text.slice(entry.prelude + 1, -1))
		: entry.prelude !== -1 && entry.atRule && _readableCondition(entry)
			? _atRuleDeclaredProperties(entry)
			: null;
	if (written === null) {
		_distantDeclaredAt.set("all", _distantSeq);
		return;
	}
	const seq = _distantSeq;
	const subjects = _entrySubjectTypes(entry);
	const note = (/** @type {string} */ name) => {
		if (subjects === null) {
			_distantDeclaredAt.set(name, seq);
			return;
		}
		_distantTypedDeclaredAt.set(name, seq);
		for (const type of subjects) {
			_distantTypeDeclaredAt.set(`${type} ${name}`, seq);
		}
	};
	if (written.size !== 0) note("*");
	for (const property of written) {
		_eachPropertyName(property, note);
	}
	if (!plain) return;
	const block = entry.text.slice(entry.prelude);
	// One selector an engine cannot parse invalidates the list it stands in, so a
	// rule that cannot lend its selectors cannot borrow one either.
	if (rule === null || !_entryListable(entry) || _distantRules.has(block)) {
		return;
	}
	_distantRules.set(block, { rule, seq: _distantSeq });
};

/**
 * Give an earlier rule printing the same block this one's selectors, where the
 * cascade cannot tell: nothing between the two declares what the block does.
 * @param {PrintContext} writer the print context
 * @param {RuleEntry} entry the rule offering its selectors
 * @returns {boolean} true when it was taken, and this rule prints nothing
 */
const _mergeIntoDistantRule = (writer, entry) => {
	if (entry.prelude === -1 || entry.atRule || !_entryListable(entry)) {
		return false;
	}
	const block = entry.text.slice(entry.prelude);
	const found = _distantRules.get(block);
	if (found === undefined) return false;
	// A rule a later copy already took back carries nothing to join.
	const { taken, span } = found.rule;
	if (span.len === 0) {
		_distantRules.delete(block);
		return false;
	}
	// A block repeated at a distance already compresses on its own, so a merge
	// earns wire bytes only where the copy it drops is worth twice the selector.
	if (block.length <= 2 * entry.prelude) return false;
	if (
		_shadowedSince(
			_blockProperties(block.slice(1, -1)),
			found.seq,
			_entrySubjectTypes(entry)
		)
	) {
		return false;
	}
	// Where the block starts in the rule as it now stands — a list this already
	// grew is longer than the one that was taken.
	const preludeNow = span.len - block.length;
	if (
		!_lendSelectorsTo(writer, taken, span, preludeNow, _entryPrelude(entry))
	) {
		return false;
	}
	// The look-back join reads the list's length off this record, which would
	// otherwise still name the list as it was taken.
	found.rule.prelude = span.len - block.length;
	return true;
};

/**
 * Write one rule's selectors into a rule already out, and key that rule by the
 * list it now carries.
 * @param {PrintContext} writer the print context
 * @param {TakenPiece} taken the piece the earlier rule stands in
 * @param {RuleSpan} span the earlier rule, within that piece
 * @param {number} preludeNow how far into it its block starts, as it now stands
 * @param {string} selector the selectors offered, as one list
 * @returns {boolean} true when they were taken, and that rule prints nothing
 */
const _lendSelectorsTo = (writer, taken, span, preludeNow, selector) => {
	const list = taken.text.slice(span.at, span.at + preludeNow);
	// A selector the list already carries would only be written twice; read as
	// text, so a `,` inside one costs a join rather than correctness.
	if (`,${list},`.includes(`,${selector},`)) return false;
	_growSpanList(writer, taken, span, preludeNow, `,${selector}`);
	_rekeySpan(taken, span);
	return true;
};

/**
 * Order the selector list of every distant rule a merge grew. The merges
 * append to keep a run of them linear, so the list is ordered once, here,
 * where the sheet leaves no further selector to append.
 * @param {PrintContext} writer the print context
 * @returns {void}
 */
const _orderDistantLists = (writer) => {
	for (const [block, found] of _distantRules) {
		const { span, taken } = found.rule;
		if (span.len === 0) continue;
		const at = span.at;
		const prelude = span.len - block.length;
		const list = taken.text.slice(at, at + prelude);
		const canonical = _canonicalSelectorList(list);
		// A merge refuses a selector the list carries, so nothing is deduplicated
		// away here and the rule keeps the length it was written at.
		if (canonical === list) continue;
		taken.text = `${taken.text.slice(0, at)}${canonical}${taken.text.slice(
			at + prelude
		)}`;
		writer.rewriteRetractable(taken.piece, taken.text);
		span.key = taken.text.slice(at, at + span.len);
	}
};

/**
 * Merge the box families whose four longhands two joined blocks wrote between
 * them, as `_mergeBoxLonghands` does for one block, so the join prints what the
 * one rule written whole would.
 * @param {PrintedDeclaration[]} declarations the joined block's declarations
 * @returns {PrintedDeclaration[]} them, each family it could merge merged
 */
const _mergeJoinedBoxLonghands = (declarations) => {
	if (!_S._transforms.mergeLonghands) return declarations;
	/** @type {Map<string, number>} */
	const at = new Map();
	/** @type {Set<string>} */
	const repeated = new Set();
	for (let i = 0; i < declarations.length; i++) {
		const property = declarations[i].property;
		if (at.has(property)) repeated.add(property);
		at.set(property, i);
	}
	/** @type {string[] | null} */
	let texts = null;
	for (const [shorthand, longhands] of BOX_LONGHANDS) {
		if (shorthand === "inset" && !_insetShorthandAllowed) continue;
		/** @type {number[]} */
		const indexes = [];
		for (const longhand of longhands) {
			const index = at.get(longhand);
			if (index === undefined || repeated.has(longhand)) break;
			indexes.push(index);
		}
		if (indexes.length !== longhands.length) continue;
		const first = Math.min(...indexes);
		const last = Math.max(...indexes);
		// The merge moves what stands between the four below the shorthand, so
		// nothing between may be read against a later one (see `_mergeBoxLonghands`).
		const family = /** @type {string} */ (BOX_FAMILY_PREFIX.get(shorthand));
		let blocked = false;
		for (let i = first + 1; i < last && !blocked; i++) {
			if (indexes.includes(i)) continue;
			blocked = _blocksMerge(
				declarations[i].property,
				longhands.filter((_, k) => indexes[k] > i),
				[family]
			);
		}
		if (blocked) continue;
		const important = _PRINTED_IMPORTANT_RE.test(declarations[first].text);
		/** @type {string[]} */
		const values = [];
		for (const index of indexes) {
			const value = declarations[index].value;
			if (_PRINTED_IMPORTANT_RE.test(value) !== important) break;
			const bare = value.replace(_PRINTED_IMPORTANT_RE, "");
			if (_splitTopLevelSpaces(bare).length !== 1) break;
			values.push(bare);
		}
		if (values.length !== longhands.length) continue;
		if (_mixesBareNumberWithLength(values)) continue;
		const box = _collapseBox(values);
		if (box === null) continue;
		if (texts === null) texts = declarations.map((one) => one.text);
		texts[first] = `${shorthand}:${_joinSlots(box)}${
			important ? "!important" : ""
		}`;
		for (const index of indexes) if (index !== first) texts[index] = "";
	}
	if (texts === null) return declarations;
	/** @type {PrintedDeclaration[]} */
	const out = [];
	for (let i = 0; i < texts.length; i++) {
		const text = texts[i];
		if (text.length === 0) continue;
		if (text === declarations[i].text) {
			out.push(declarations[i]);
			continue;
		}
		const colon = text.indexOf(":");
		out.push({
			property: text.slice(0, colon),
			value: text.slice(colon + 1),
			text
		});
	}
	return out;
};

/**
 * Where the rules of an at-rule a join wrote stand in it, read off its children:
 * a later copy under the same condition takes one back as it would out of either
 * block standing alone.
 * @param {RuleEntry} merged the at-rule, or one block of it
 * @returns {BlockSpans} what the at-rule records
 */
const _joinedAtRuleSpans = (merged) => {
	// An at-rule's entry is always built with its children, never `null`.
	const children = /** @type {RuleEntry[]} */ (merged.children);
	const prelude = _entryPrelude(merged);
	/** @type {RuleSpan[]} */
	const spans = [];
	let at = 0;
	for (const child of children) {
		if (child.prelude === -1) {
			// What no join reads is noted as nothing to take back, which costs at most
			// a copy of what it holds; the rules beside it stay ones to take.
			at += child.text.length;
			continue;
		}
		// Each child lands as `_collectRuleSpans` notes it: a qualified rule is one
		// span whatever it nests, a block's own rules move in under its prelude.
		if (!child.atRule) {
			spans.push({
				scope: _rootScope(),
				key: child.text,
				at,
				len: child.text.length,
				taken: null
			});
		} else {
			const inner = _joinedAtRuleSpans(child);
			if (inner === _WHOLE_TEXT_SPANS) return _WHOLE_TEXT_SPANS;
			if (DROPPABLE_WHEN_EMPTY_AT_RULES.has(_atRuleName(inner.prelude))) {
				spans.push({
					scope: _rootScope(),
					key: UNKEYED,
					at,
					len: child.text.length,
					taken: null
				});
			}
			for (const span of inner.spans) {
				spans.push({
					scope: _enclosingRuleScope(span.scope, inner.keyPrelude),
					key: span.key,
					at: at + inner.bodyAt + span.at,
					len: span.len,
					taken: null
				});
			}
		}
		at += child.text.length;
	}
	// Rules sit side by side, but declarations written straight into the block
	// part with `;` — a layout read as the whole text rather than rebuilt.
	if (merged.text.length !== merged.prelude + at + 2) return _WHOLE_TEXT_SPANS;
	return {
		bodyAt: merged.prelude + 1,
		prelude,
		keyPrelude: _openerKey(`${prelude}{`),
		qualified: false,
		spans
	};
};

/**
 * Where the rules of two joined top-level rules stand in the rule they became.
 * The join writes a text of its own, so each side's rules are rebased into it —
 * or, for a shape whose geometry is not read here, given up for the whole rule,
 * which is then all a later copy can take back.
 * @param {RuleEntry} before the earlier rule, as it stood
 * @param {BlockSpans | undefined} beforeOwn what the earlier rule recorded
 * @param {RuleEntry} entry the later rule, as it stood
 * @param {BlockSpans | undefined} own what the later rule recorded
 * @param {RuleEntry} merged the one rule they joined into
 * @returns {BlockSpans | undefined} what the joined rule records
 */
const _joinedSpans = (before, beforeOwn, entry, own, merged) => {
	// The later rule only restated the earlier: nothing was written, so nothing
	// the earlier recorded has moved.
	if (merged === before) return beforeOwn;
	if (merged.atRule) return _joinedAtRuleSpans(merged);
	if (beforeOwn === undefined || beforeOwn === _WHOLE_TEXT_SPANS) {
		return _WHOLE_TEXT_SPANS;
	}
	// Only the block join, which appends the later body whole behind an earlier one
	// holding no rules: a list join lends to a block of declarations, which holds none.
	if (
		merged.prelude !== before.prelude ||
		own === undefined ||
		own === _WHOLE_TEXT_SPANS ||
		beforeOwn.spans.length !== 0
	) {
		return _WHOLE_TEXT_SPANS;
	}
	const prelude = _entryPrelude(merged);
	const length = entry.text.length - entry.prelude - 2;
	const at = merged.text.length - 1 - length - (merged.prelude + 1);
	const shift = at + own.bodyAt - (entry.prelude + 1);
	/** @type {RuleSpan[]} */
	const spans = [];
	for (const span of own.spans) {
		spans.push({
			scope: span.scope,
			key: span.key,
			at: span.at + shift,
			len: span.len,
			taken: null
		});
	}
	return {
		bodyAt: merged.prelude + 1,
		prelude,
		keyPrelude: _openerKey(`${prelude}{`),
		qualified: true,
		spans
	};
};

/**
 * Hand a finished top-level node to the writer, holding a qualified rule back
 * one node so an adjacent one printing the same block can join its selectors.
 * @param {Node} node the top-level node
 * @param {PrintContext} writer the print context
 * @param {number} offset the node's source offset
 * @param {number=} line 0-based source line of the node's start, when mapping
 * @param {number=} column 0-based source column of the node's start, when mapping
 * @returns {void}
 */
const _takeTopLevel = (node, writer, offset, line, column) => {
	const held = _heldTopLevel;
	// The node's own text first — taking the held one clears the store it sits in.
	const own = writer.get(node);
	// Read where the node stands rather than where it is emitted: the writer holds
	// a rule back one node, by when the prologue has closed behind it. A rule in
	// front of a `@namespace` is never offered, as taking it back moves one up.
	const drained = _drainTopLevelSpans(node);
	const spans = _S._namespacePrologueOpen ? undefined : drained;
	const entry = _ruleEntryOf(node, own);
	// A prefixed rule an unprefixed twin would make dead weight goes out as its own
	// piece, so the twin can take it back from wherever it stands. It is not
	// offered for joining: a rule that may be taken back is one piece.
	const candidate = _S._prefixDropCandidate;
	_S._prefixDropCandidate = null;
	if (candidate !== null && candidate.node === node) {
		if (held !== null) {
			_heldTopLevel = null;
			_emitHeld(writer, held);
		}
		const at = writer.takeRetractable(node, offset, line, column, own);
		_settlePrior(writer);
		if (_topLevelRules !== null) {
			const taken = new TakenPiece(at, own, -1);
			if (_prefixPieces === null) _prefixPieces = new Map();
			_prefixPieces.set(at, taken);
			_topLevelRules.push(taken);
		}
		// Noted without its piece: one still open to being retracted is not one a
		// later rule may put its selectors into.
		if (_mergingDistantRules) _noteDistantRule(null, entry);
		const scope = _prefixScope(null);
		// A list still short of a twin for some selector waits on all of them, so
		// its piece is held with what it is waiting for rather than by signature.
		if (candidate.missing !== null) {
			if (scope.covering === null) scope.covering = [];
			scope.covering.push({
				missing: candidate.missing,
				piece: at,
				node: null
			});
			return;
		}
		if (scope.retractable === null) scope.retractable = new Map();
		_holdBySignature(scope.retractable, candidate.signature, at);
		return;
	}
	const runs = entry.prelude === -1 ? _hoistedRunsOf(node, own) : null;
	if (runs !== null) {
		for (let i = 0; i < runs.length; i++) {
			// The first stands where the node does, so it is keyed as `spans` is; the
			// rest follow a rule of the node's own, which closes the prologue.
			_takeTopLevelEntry(
				node,
				writer,
				offset,
				line,
				column,
				runs[i],
				i === 0 && _S._namespacePrologueOpen ? undefined : _WHOLE_TEXT_SPANS
			);
		}
		return;
	}
	_takeTopLevelEntry(node, writer, offset, line, column, entry, spans);
};

/**
 * Take one top-level rule the writer can read an entry off: held for a join,
 * merged into a distant one, or emitted as it stands.
 * @param {Node} node the top-level node it came from
 * @param {PrintContext} writer the print context
 * @param {number} offset the node's source offset
 * @param {number | undefined} line 0-based source line of the node's start
 * @param {number | undefined} column 0-based source column of the node's start
 * @param {RuleEntry} entry the rule
 * @param {BlockSpans | undefined} spans what it recorded while printing
 * @returns {void}
 */
const _takeTopLevelEntry = (
	node,
	writer,
	offset,
	line,
	column,
	entry,
	spans
) => {
	const held = _heldTopLevel;
	const own = entry.text;
	if (entry.prelude === -1) {
		// Not a rule this can join: flush what is held, then take it as it stands. A
		// node printing nothing leaves the two around it adjacent, its kept comments
		// queued where it stood, blocking a join across that gap until they land.
		if (own.length === 0) return;
		if (held !== null) {
			_heldTopLevel = null;
			_emitHeld(writer, held);
		}
		const taken = _emitTopLevel(writer, node, offset, line, column, own, spans);
		_settlePrior(writer);
		if (_topLevelRules !== null) _topLevelRules.push(taken);
		// Anything this cannot see into stands in the way of every later merge,
		// which is what noting a node that is not a plain rule spells.
		if (_mergingDistantRules) _noteDistantRule(null, entry);
		return;
	}
	// A kept comment between the two was written above the second rule. Read once:
	// the flush below clears it, and the hold carries the answer on to its release.
	const parted = writer.hasInsertBefore(offset);
	if (held !== null && !parted) {
		const before = held.entry;
		const merged = _joinRuleEntries(before, entry, held.owned);
		if (merged !== null) {
			// The join writes a text of its own, so what either side recorded names a
			// text that is gone: rebased, a rule inside it is still one a copy can take.
			held.own = _joinedSpans(before, held.own, entry, spans, merged);
			held.entry = merged;
			held.owned = true;
			return;
		}
	}
	if (held !== null) {
		_emitHeld(writer, held);
	}
	if (_mergingDistantRules && _mergeIntoDistantRule(writer, entry)) {
		// Its selectors went into a rule already out, so it prints nothing itself
		// — and the one held is out too, so nothing is left to emit again.
		_heldTopLevel = null;
		writer.flushInsertsBefore(offset);
		return;
	}
	// What was written above this rule is emitted above it now, so the next
	// node's check sees only the gap between the two — and nothing folds back
	// over it, so the layer blocks above it stop being ones to gather into.
	if (_seenTopLevelLayers !== null && parted) {
		_seenTopLevelLayers.clear();
	}
	if (_distantAtRules.size !== 0 && parted) _distantAtRules.clear();
	// Nor does a later rule shadow one written above the comment.
	if (parted && _ruleScopes !== null) {
		for (const scope of _ruleScopes.values()) scope.selectors.clear();
	}
	writer.flushInsertsBefore(offset);
	_heldTopLevel = {
		node,
		offset,
		line,
		column,
		entry,
		owned: false,
		own: spans,
		parted
	};
};

/**
 * Emit the held top-level rule, if any. Called once the stylesheet is consumed.
 * @param {PrintContext=} writer the print context
 * @returns {void}
 */
const _flushTopLevel = (writer) => {
	const held = _heldTopLevel;
	_heldTopLevel = null;
	if (writer === undefined) return;
	if (held !== null) _emitHeld(writer, held);
	_settlePrior(writer);
};

/**
 * Emit a whole `block-contents` print: the held nodes are one declaration list,
 * so they go through the same composition a rule's block does and land as one
 * piece. The list is what a `style=""` attribute holds, so it is small.
 * @param {(Rule | Declaration)[]} nodes the top-level nodes, in source order
 * @param {PrintContext} writer the print context holding each one's text
 * @returns {void}
 */
const _emitBlockContents = (nodes, writer) => {
	/** @type {Declaration[]} */
	const decls = [];
	/** @type {Rule[]} */
	const rules = [];
	for (const node of nodes) {
		if (_types[_nodeIndex(node)] === NodeType.Declaration) {
			decls.push(/** @type {Declaration} */ (node));
		} else {
			rules.push(/** @type {Rule} */ (node));
		}
	}
	const minify = writer.options.mode === "minify";
	const { body } = _composeBlockBody(
		A,
		decls,
		rules.length === 0 ? null : rules,
		writer,
		minify,
		minify ? "" : "\n",
		null
	);
	// The list prints as one piece, so it anchors as one: at where it starts. A
	// declaration list is what an attribute holds, so that is the whole of it.
	const offset = nodes.length === 0 ? 0 : _startOf(nodes[0]);
	const at = writer.mapWanted ? _locConverter.get(offset) : undefined;
	// A leading separator only ever parts one item from the one before it, and at
	// top level there is nothing before the first.
	writer.take(
		undefined,
		offset,
		at === undefined ? undefined : at.line - 1,
		at === undefined ? undefined : at.column,
		minify ? body : body.slice(1)
	);
};

/**
 * Note that a top-level node stood here. Called once per top-level node, after
 * it has printed and before the next one does, so the empty-rule drop knows
 * whether taking a rule out could still move a `@namespace` up.
 * @param {Rule | Declaration} node the finished top-level node
 * @returns {void}
 */
const _closeNamespacePrologue = (node) => {
	if (
		_S._namespacePrologueOpen &&
		_types[_nodeIndex(node)] === NodeType.QualifiedRule
	) {
		_S._namespacePrologueOpen = false;
	}
};

/**
 * The `grammar` streaming sink: walk one top-level node, then recycle the
 * buffers for the next.
 * @param {Rule | Declaration} node top-level node
 * @param {PrintContext=} writer print context when printing, else undefined
 */
const _walkTopLevel = (node, writer) => {
	if (node === _streamWalked) {
		_streamWalked = null;
		// A streamed rule's text is already out, so nothing can take it back — and
		// the node is about to be recycled, which its candidacy must not outlive.
		_S._prefixDropCandidate = null;
		_closeNamespacePrologue(node);
		_recycleTopLevel(node);
		if (_walkStopped && writer === undefined) throw WALK_STOPPED;
		return;
	}
	_walkRule(node, null, 0, writer);
	// A declaration list is composed as a whole, so its nodes are kept — their
	// printed text stays reachable through the writer, which recycling would lose.
	if (_blockContentsNodes !== null) {
		_blockContentsNodes.push(node);
		return;
	}
	// The walk fired each node's printer into the context, so this finished
	// top-level node goes to the writer with its source position: it flushes the
	// kept comments before it, records the source-map anchor and appends the text.
	if (writer !== undefined) {
		// Only the start is wanted, and only for a map: `loc` would walk to the end
		// as well and box both, per top-level node, for a line nobody reads.
		if (writer.mapWanted) {
			const start = _locConverter.get(_starts[_nodeIndex(node)]);
			_takeTopLevel(node, writer, _startOf(node), start.line - 1, start.column);
		} else {
			_takeTopLevel(node, writer, _startOf(node));
		}
	}
	_closeNamespacePrologue(node);
	// The held rule keeps its own entry, so the rest are done with.
	_clearRuleEntries();
	if (_nodeCount > _peak) _peak = _nodeCount;
	if (_flatTop > _flatPeak) _flatPeak = _flatTop;
	_nodeCount = 0;
	_flatTop = 0;
	// Body indices recycle with node ids.
	_dropBodies();
	if (_walkStopped && writer === undefined) throw WALK_STOPPED;
};

/**
 * `_walkTopLevel` without the walk, for a visitor-less `process` call: the
 * walk's only effect is visitor dispatch, so just recycle the buffers.
 * @param {Rule | Declaration} node top-level node
 */
const _recycleTopLevel = (node) => {
	if (_nodeCount > _peak) _peak = _nodeCount;
	if (_flatTop > _flatPeak) _flatPeak = _flatTop;
	_nodeCount = 0;
	_flatTop = 0;
	_dropBodies();
};

// The store buffers grow to the largest single top-level rule ever parsed and
// live at module level; above this capacity they are re-shrunk after a parse
// so one pathological rule can't pin megabytes for the process lifetime.
const _SHRINK_CAPACITY = 65536;

// `@license` / `@preserve` mark a comment for preservation (terser keeps the
// same annotations for JS); the JS-only `@cc_on` is not meaningful in CSS.
const _KEEP_COMMENT_RE = /@(?:license|preserve)/i;

/**
 * A comment worth carrying through minification, matching terser's default set:
 * a `/*!` banner (fast char-code check, no allocation) or a `@license` /
 * `@preserve` annotated comment. `/*#` covers the `sourceMappingURL` /
 * `sourceURL` pragmas, which are a link to drop, not a comment.
 * @param {string} src source text
 * @param {number} start comment start offset (at `/`)
 * @param {number} end comment end offset (past the closing `/`)
 * @returns {boolean} whether the comment survives minification
 */
const _isKeptComment = (src, start, end) => {
	const marker = src.charCodeAt(start + 2);
	const kept = _commentsKept;
	if (typeof kept === "boolean") return kept;
	if (kept === "some") {
		return (
			marker === CC_EXCLAMATION || _KEEP_COMMENT_RE.test(src.slice(start, end))
		);
	}
	// A pattern or a predicate of the author's, over the comment's own text —
	// which stands in for the banner rule rather than beside it, as terser's
	// `format.comments` does. Its line and column follow, for a caller placing it.
	const loc = _locConverter.get(start);
	return kept(src.slice(start + 2, end - 2), loc.line, loc.column) === true;
};

// Every rewrite on, which is what a print with no `transforms` option makes and
// what the walk-only passes hold. Frozen, since it is shared rather than copied.
/** @type {Required<CssTransformOptions>} */
const _ALL_TRANSFORMS = Object.freeze({
	colorFallbacks: true,
	lowerUnsupported: true,
	foldCase: true,
	rewriteEscapes: true,
	comments: "some",
	customMedia: false,
	customSelectors: false,
	mergeLonghands: true,
	mergeRules: true,
	normalizeQuotes: true,
	reduceFunctions: true,
	removeDeadRules: true,
	reorderDeclarations: true,
	rewriteDirSelector: false,
	shortenColors: true,
	shortenMediaQueries: true,
	shortenNumbers: true,
	shortenSelectors: true,
	shortenValues: true
});

/**
 * The rewrites this print makes, resolved once: each name is what the options
 * set it to, and what `_ALL_TRANSFORMS` gives it where they set nothing — so no
 * read of the result asks whether the option was given. `comments` keeps
 * whatever it was given, which is still falsy only when it is `false`.
 *
 * Always a copy, and always made the one way: spreading the table and then
 * writing over a name it already has leaves the object's shape alone, so every
 * read of the result — and there is one per token — sees a single hidden class.
 * Handing the frozen table itself back where nothing is set would save this
 * object and cost each of those reads a second shape, `Object.freeze` giving a
 * frozen object a map of its own.
 * @param {CssTransformOptions | undefined} options the `transforms` option
 * @returns {Required<CssTransformOptions>} every rewrite resolved
 */
const _transformsFrom = (options) => {
	/** @type {Required<CssTransformOptions>} */
	const out = { ..._ALL_TRANSFORMS };
	if (options !== undefined) {
		for (const name of Object.keys(_ALL_TRANSFORMS)) {
			const value = options[/** @type {keyof CssTransformOptions} */ (name)];
			if (value !== undefined) {
				/** @type {{ [name: string]: CssTransformOptions[keyof CssTransformOptions] }} */ (
					out
				)[name] = value;
			}
		}
	}
	return out;
};

// The same table, as a copy, for the reason `_transformsFrom` always makes one.
/** @type {Required<CssTransformOptions>} */
const _DEFAULT_TRANSFORMS = _transformsFrom(undefined);

/**
 * The `comments` option resolved to what it means per comment: `true` every
 * one, `false` none, `"some"` the ones that carry something, or a predicate
 * over the comment's own text — which is what a pattern compiles to here, so no
 * print re-reads one per comment. A pattern is matched from the start each
 * time, since a `g` flag would otherwise carry an index between comments.
 * @param {Required<CssTransformOptions>["comments"]} comments the option
 * @returns {boolean | "some" | ((comment: string) => boolean)} what it keeps
 */
const _keptComments = (comments) => {
	if (comments === "all") return true;
	if (typeof comments === "boolean" || comments === "some") return comments;
	if (typeof comments === "function") return comments;
	const pattern =
		typeof comments === "string" ? new RegExp(comments) : comments;
	return (comment) => {
		pattern.lastIndex = 0;
		return pattern.test(comment);
	};
};

/**
 * The per-transform switches out of a wider options object — what
 * `optimization.minimize.css` names beside `environment` and the two rewrites
 * that are off until asked for. Exported so the minify functions the minimizer
 * plugin ships to its workers can pick them without repeating the names.
 * @param {CssTransformOptions} options an options object that may carry them
 * @returns {CssTransformOptions | undefined} the switches, or undefined when none is set
 */
const pickTransforms = (options) => {
	/** @type {CssTransformOptions | undefined} */
	let out;
	for (const name of Object.keys(_ALL_TRANSFORMS)) {
		const value = options[/** @type {keyof CssTransformOptions} */ (name)];
		if (value === undefined) continue;
		if (out === undefined) out = {};
		/** @type {{ [name: string]: CssTransformOptions[keyof CssTransformOptions] }} */ (
			out
		)[name] = value;
	}
	return out;
};

// What a stylesheet whose output holds a non-ASCII character opens with, so it
// is read as UTF-8 whatever the page around it is in.
const CHARSET_RULE = '@charset "UTF-8";';

/**
 * The CSS `SourceProcessor` grammar: consume top-level rules one at a time
 * (§5.4.1) and walk each immediately, firing `enter` / `exit` in source order
 * without building a whole-stylesheet array first. `recurseBlocks: false` skips
 * walking block bodies' (eagerly parsed) nested rules (caller drives nested
 * traversal itself). When `writer` is given the same walk also prints: it is
 * threaded down the walk and each node's printer builds its text into it as the
 * node finishes — one parse, no re-tokenization. `skip` is ignored while printing
 * (it needs every node).
 * @param {string} input source text
 * @param {CompiledVisitorMap} visitors compiled visitor map
 * @param {PrintContext | undefined} writer the print context to build output into, or undefined (walk only)
 * @param {CssProcessOptions} options process options
 */
const grammar = (input, visitors, writer, options) => {
	if (
		writer !== undefined &&
		!_collectingCustomAtRules &&
		writer.options.mode === "minify" &&
		writer.options.transforms !== undefined &&
		(writer.options.transforms.customMedia === true ||
			writer.options.transforms.customSelectors === true) &&
		_namesAtRule(input, _CUSTOM_AT_RULE_NAMES)
	) {
		_readCustomAtRulesAhead(input, writer);
	}
	const text = _preprocessInputStream(input);
	const locConverter = options.locConverter || new LocConverter(text);
	_setupParse(text, locConverter);
	_originalInput = input;
	// Printing (a writer) needs every node for a faithful serialization, so `skip`
	// is ignored while printing — it only applies to walk-only visitor passes.
	const skip = writer !== undefined ? undefined : options.skip;
	_skipTypes = (skip && skip.types) || _NO_SKIP_TYPES;
	_skipActive = _skipTypes !== _NO_SKIP_TYPES;
	_skipSelectorPrelude = skip !== undefined && skip.selectorPrelude === true;
	_skipAtRulePrelude = skip !== undefined && skip.atRulePrelude === true;
	// ...and `recurseBlocks` is ignored there for the same reason: a block body
	// the walk never enters holds no children, and a block is printed from them.
	_recurseBlocks = writer !== undefined || options.recurseBlocks !== false;
	// The walk always streams, there being nothing to trade off: a block under the
	// threshold is collected and walked in one batch as ever. Printing streams
	// with it, so no parent is assembled from text already released.
	_streamBlocks = true;
	_stream.writer = writer;
	_printing = writer !== undefined;
	_S._convertLengthUnits =
		writer !== undefined && writer.options.convertLengthUnits === true;
	_S._assumeUtf8 = writer !== undefined && writer.options.charset === false;
	_convertApproximateColors =
		writer !== undefined && writer.options.convertApproximateColors === true;
	const unused =
		writer === undefined ? undefined : writer.options.unusedSymbols;
	_unusedSymbols =
		unused === undefined || unused.length === 0 ? null : new Set(unused);
	_unusedRules = writer === undefined ? null : new Set();
	const pseudo =
		writer === undefined ? undefined : writer.options.pseudoClasses;
	const named = pseudo === undefined ? [] : Object.entries(pseudo);
	_pseudoClasses = named.length === 0 ? null : new Map(named);
	_mergingDistantRules =
		writer !== undefined && writer.options.mergeDistantRules === true;
	_S._rewriteCustomProperties =
		writer !== undefined && writer.options.rewriteCustomProperties === true;
	_dropOverriddenDeclarations =
		writer !== undefined && writer.options.dropOverriddenDeclarations === true;
	// `_setupParse` left the default in place: only a caller's switches resolve.
	if (writer !== undefined && writer.options.transforms !== undefined) {
		_S._transforms = _transformsFrom(writer.options.transforms);
	}
	_commentsKept = _keptComments(_S._transforms.comments);
	// Only a minifying print that may join rules records where each one stands,
	// which is the one that can put the two a cut leaves side by side together.
	_topLevelRules =
		writer !== undefined &&
		writer.options.mode === "minify" &&
		_S._transforms.mergeRules
			? []
			: null;
	_S._unitScale = _unitScaleFor(_S._convertLengthUnits);
	_S._renderEmbeddedSource =
		writer === undefined ? undefined : writer.options.renderEmbeddedSource;
	_S._deferEmbeddedSource =
		writer === undefined ? undefined : writer.options.deferEmbeddedSource;
	const environment =
		writer !== undefined ? writer.options.environment : undefined;
	// Vendor prefixing is on for a minifying print with a browserslist selection,
	// and off for everything else — an empty selection names no browser to
	// prefix for, so it leaves prefixes alone rather than dropping every one.
	const browsers =
		writer !== undefined &&
		writer.options.mode === "minify" &&
		environment !== undefined &&
		environment.browsers !== undefined &&
		environment.browsers.length !== 0
			? environment.browsers
			: undefined;
	if (browsers === undefined) {
		_seenPrefixRules = null;
		_prefixBrowsers = null;
		_someBrowserUnanswered = false;
		_S._prefixingOn = false;
	} else {
		_useBrowsers(browsers);
		// The selection answers two questions, and `vendorPrefixes` turns off only
		// the first: which prefixes to write, and which spellings a target reads.
		_S._prefixingOn =
			_prefixBrowsers !== null &&
			/** @type {CssEnvironment} */ (environment).vendorPrefixes !== false;
		_seenPrefixRules = _S._prefixingOn ? new Map() : null;
	}
	// Read once per print: the selection cannot change under a single stylesheet,
	// and each of these is asked per declaration.
	// No selection reads everything, which is what `_setupParse` just set.
	if (_prefixBrowsers !== null) {
		_S._hexAlphaAllowed = _targetSupports("colorHexAlpha");
		_S._doublePositionAllowed = _targetSupports("gradientDoublePosition");
		_S._displayTwoValuesAllowed = _targetSupports("displayTwoValues");
		_S._systemUiAllowed = _targetSupports("systemUiFont");
		_textDecorationColorStyleAllowed = _targetSupports(
			"textDecorationColorStyle"
		);
		_textDecorationThicknessAllowed = _targetSupports(
			"textDecorationThickness"
		);
		// WHY: an engine reading custom properties tokenizes by CSS Syntax 3, which
		// parts `.2em.4em`, `0-2px` and `)x`; IE, Presto and pre-Chromium Android
		// read none, and none of them is verifiable here, so their values keep spaces.
		_oldTokenizer =
			_someBrowserUnanswered || !_readsAll(_valueSupportedFrom("var"));
		// WHY: IE 10 and 11 read an omitted `flex` basis as `0px` where the spec
		// says `0%`, and a lone basis by the 2012 draft, so `flex` keeps its parts.
		const selected = _prefixBrowsers;
		_tridentTarget =
			_someBrowserUnanswered ||
			_TRIDENT_BROWSERS.some((at) => selected[at] !== undefined);
		_colorFunctionAllowed = _targetSupports("colorFunction");
		_colorMixAllowed = _targetSupports("colorMix");
		_hwbColorsAllowed = _targetSupports("hwbColors");
		_labColorsAllowed = _targetSupports("labColors");
		_lightDarkAllowed = _targetSupports("lightDark");
		// Both carry only a lowering, which leaves a browser nothing states no worse.
		_whereSelectorAllowed = _readsAllStated(
			SUPPORTED_FROM.get("whereSelector")
		);
		_dirSelectorAllowed = _targetSupports("dirSelector");
		_nestingAllowed = _targetSupports("nesting");
		_S._customMediaAllowed = _targetSupports("customMedia");
		// `&` is the parent selector list, which only `:is()` writes out — so a target
		// reading no `:is()` keeps the nesting the author wrote.
		_S._isSelectorAllowed = _readsAllStated(SUPPORTED_FROM.get("isSelector"));
		_S._loweringNesting =
			!_nestingAllowed &&
			_S._isSelectorAllowed &&
			_S._transforms.lowerUnsupported &&
			writer !== undefined &&
			writer.options.mode === "minify";
		_langArgumentListAllowed = _targetSupports("langArgumentList");
		_notSelectorListAllowed = _targetSupports("notSelectorList");
		// The pair needs a rule of no specificity to default them, so a target that
		// reads no `:where()` is one this leaves the function alone for.
		_S._loweringLightDark =
			!_lightDarkAllowed &&
			_whereSelectorAllowed &&
			_S._transforms.lowerUnsupported &&
			writer !== undefined &&
			writer.options.mode === "minify";
		// Which half of a `light-dark()` prints depends on a `color-scheme` that may
		// print long before the first call, so the source answers rather than the walk.

		// The function, not a name ending in it: `.scheme-light-dark` and
		// `my-light-dark()` both hold the text, and reading either as a call lowers
		// a sheet that has none. A function name is matched case-insensitively.
		_S._sourceNamesLightDark =
			_S._loweringLightDark && _LIGHT_DARK_CALL_RE.test(_S._input);
		_S._lightDarkUsed = false;
		_oklabColorsAllowed = _targetSupports("oklabColors");
		_colorFallbacksNeeded =
			!_hwbColorsAllowed ||
			!_labColorsAllowed ||
			!_oklabColorsAllowed ||
			!_colorFunctionAllowed ||
			!_colorMixAllowed;
		_insetShorthandAllowed = _targetSupports("insetShorthand");
		_S._rangeSpellingAllowed = _targetSupports("mediaQueryRange");
		_placeShorthandAllowed = _targetSupports("placeShorthand");
		_overflowTwoValuesAllowed = _targetSupports("overflowTwoValues");
	}
	_visitors = visitors;
	_commentBucket = visitors[NodeType.Comment];
	_blockMemo =
		writer !== undefined &&
		writer.options.mode === "minify" &&
		visitors.length === 0 &&
		_unusedSymbols === null &&
		!_S._prefixingOn &&
		!_colorFallbacksNeeded &&
		_S._renderEmbeddedSource === undefined &&
		_S._deferEmbeddedSource === undefined
			? new Map()
			: null;
	_declarationMemo = _blockMemo === null ? null : new Map();
	_blockMemoScopes.clear();
	_singleValueKeys = _blockMemo === null ? null : new Set();
	_declarationMemoKey = "";
	_walkStopped = false;

	// WHY: the comment sink fires the `Comment` visitor bucket and, while
	// printing, keeps the license comments `/*!`, `@license` and `@preserve` — the
	// ecosystem default, cssnano, clean-css and csso keeping `/*!` and terser the
	// annotations. A kept comment goes to the writer, which re-emits it before the
	// next top-level node, and both print modes keep the same ones so beautifying
	// and minifying carry the same banners. With no comment visitor and no
	// printing there is no callback at all, and comments are skipped.
	/** @type {((input: string, start: number, end: number) => number) | undefined} */
	let onComment;
	if (writer !== undefined) {
		const w = writer;
		onComment = (src, start, end) => {
			if (_commentBucket !== undefined) _grammarOnComment(src, start, end);
			if (_isKeptComment(src, start, end)) {
				w.insert(start, src.slice(start, end));
			}
			return end;
		};
	} else if (_commentBucket !== undefined) {
		onComment = _grammarOnComment;
	}

	// Stream each top-level node (selected by `as`) to the walker the moment it's
	// consumed, rather than collecting them first — so the whole AST is never
	// held at once; peak heap is ~one top-level node's subtree.
	const as = options.as || "stylesheet";
	// Decoding drops a leading BOM; printed after the `@charset` it would read as
	// part of the first selector.
	const ts = new TokenStream(
		text,
		writer !== undefined &&
			writer.options.mode === "minify" &&
			as === "stylesheet" &&
			text.charCodeAt(0) === 0xfeff
			? 1
			: 0,
		locConverter,
		onComment
	);
	const consume = TOP_LEVEL_CONSUMERS[as] || consumeAStylesheetsContents;
	_S._inStyleAttribute = as === "block-contents";
	// A block's contents are one declaration list, so printing them is composing
	// that list — the same production a rule's block is, and the only top-level
	// one that is held rather than streamed.
	if (as === "block-contents" && writer !== undefined) {
		_blockContentsNodes = [];
	}
	// Written first or not at all: `@charset` is read only as a file's opening
	// bytes, and is taken back below where the output holds no non-ASCII one.
	if (
		writer !== undefined &&
		writer.options.mode === "minify" &&
		writer.options.declareCharset === true &&
		writer.options.charset !== false &&
		as === "stylesheet"
	) {
		_S._charsetPiece = writer.emitRetractable(CHARSET_RULE);
	}
	// With zero registered buckets the walk is a pure no-op traversal — hand the
	// consumer a recycle-only sink instead.
	let anyVisitor = false;
	for (let i = 0; i < visitors.length; i++) {
		if (visitors[i] !== undefined) {
			anyVisitor = true;
			break;
		}
	}
	try {
		// Bind the writer into the per-node sink only while printing, so the
		// walk-only path keeps passing the module function with no closure. A printer
		// consumes the walk, so only a writer-less call takes the recycle-only sink.
		consume(
			ts,
			writer === undefined
				? anyVisitor
					? _walkTopLevel
					: _recycleTopLevel
				: (node) => _walkTopLevel(node, writer)
		);
		if (_blockContentsNodes === null) {
			_flushTopLevel(writer);
			if (writer !== undefined) _reconcileTopLevel(writer);
			if (_mergingDistantRules && writer !== undefined) {
				_orderDistantLists(writer);
			}
			// Last, and only where a `light-dark()` was written another way: the rule
			// has no specificity, so where it stands says nothing about which
			// declaration wins.
			if (_S._lightDarkUsed && writer !== undefined) {
				writer.emitStreamed(LIGHT_DARK_DEFAULTS);
			}
			// A value the input ran out in ends the output as it ended the input: the
			// engine keeps a custom property's value as written, left open.
			const open = _S._openCustomValue;
			if (open !== null && writer !== undefined) {
				writer.replaceEnd(open.printed, open.source, CC_RIGHT_CURLY);
			}
			if (
				_S._charsetPiece !== -1 &&
				!(/** @type {PrintContext} */ (writer).writesNonAscii(_S._charsetPiece))
			) {
				/** @type {PrintContext} */ (writer).retract(_S._charsetPiece);
			}
		} else {
			_emitBlockContents(
				_blockContentsNodes,
				/** @type {PrintContext} */ (writer)
			);
		}
	} catch (err) {
		if (err !== WALK_STOPPED) throw err;
	} finally {
		_walkStopped = false;
		if (_haltedVisitors !== null) {
			resumeVisitors(_haltedVisitors);
			_haltedVisitors = null;
		}
		_blockContentsNodes = null;
		// Each held write keeps its offered source — a decoded `data:` payload can
		// be a whole document — and a closure over it, so both go with the parse.
		_S._renderEmbeddedSource = undefined;
		_S._deferEmbeddedSource = undefined;
		_S._openCustomValue = null;
		_heldTopLevel = null;
		_priorTopLevel = null;
		_topLevelRules = null;
		_prefixPieces = null;
		_seenTopLevelLayers = null;
		_sheetSourceLayers = null;
		_clearRuleEntries();
		_seenPrefixRules = null;
		// The parsed selection itself stays in `_parsedBrowsersMemo`, which the next
		// asset of the same build reuses; only this parse's pointer is dropped.
		_prefixBrowsers = null;
		_someBrowserUnanswered = false;
		_S._prefixDropCandidate = null;
		// Drop the module-level column references so the last parsed source (and
		// its LocConverter / child lists / visitors) don't stay alive between
		// parses.
		_streamBlocks = false;
		_streamWalked = null;
		_stream.writer = undefined;
		_depth = 0;
		// A frame is left as the block closed it (nothing reads a closed one), so
		// the last open path's buffers are dropped here rather than per block.
		const used = _frameHighWater + 1;
		_frameDecls.fill(null, 0, used);
		_frameRules.fill(null, 0, used);
		_frameSeenDeclarations.fill(null, 0, used);
		_frameSeenRules.fill(null, 0, used);
		_frameChainKey.fill(null, 0, used);
		_frameSeenLayers.fill(null, 0, used);
		_frameSourceLayers.fill(null, 0, used);
		_framePrior.fill(null, 0, used);
		_frameLayerOpener.fill(null, 0, used);
		_frameRuleRun.fill(null, 0, used);
		_frameHighWater = 0;
		_blockSpans.length = 0;
		_ruleScopes = null;
		_rootRuleScope = null;
		_shadowDeclared.clear();
		_blockMemo = null;
		_blockMemoScopes.clear();
		_blockMemoTable = _blockMemoKeyTable = new Map();
		_declarationMemo = null;
		_singleValueKeys = null;
		_blockMemoNode = null;
		_blockMemoBody = undefined;
		_blockMemoKey = "";
		// Each holds a piece of the sheet just printed, so the last one printed
		// would be held until the next parse replaces them.
		_distantRules.clear();
		_distantAtRules.clear();
		_distantAtRuleFolded = false;
		_distantDeclaredAt.clear();
		_distantTypedDeclaredAt.clear();
		_distantTypeDeclaredAt.clear();
		_anonymousLayers = 0;
		_S._input = "";
		_originalInput = "";
		_locConverter = /** @type {LocConverter} */ (
			/** @type {LocConverter | null} */ (null)
		);
		_declBodies.length = 0;
		_ruleBodies.length = 0;
		_flatTop = 0;
		_listPool.length = 0;
		if (_flat.length > _SHRINK_CAPACITY) {
			_flatGrowHint = _flatPeak;
			_flat = EMPTY_I32;
		}
		_visitors = /** @type {CompiledVisitorMap} */ (
			/** @type {CompiledVisitorMap | never[]} */ ([])
		);
		_commentBucket = undefined;
		if (_capacity > _SHRINK_CAPACITY) {
			// +1: node ids are 1-based and grow fires at `id >= capacity`.
			_growHint = _peak + 1;
			_capacity = 0;
			_releaseColumns();
		}
		_peak = 0;
		_flatPeak = 0;
	}
};

/**
 * Whether a pending token separator is safe to drop *before* `cc`: next to
 * `{ } ; , )` it never changes meaning (`)` only ever closes a group).
 * @param {number} cc first code point of the text about to be written
 * @returns {boolean} true when the separator can be dropped
 */
const _dropSeparatorBefore = (cc) =>
	cc === CC_LEFT_CURLY ||
	cc === CC_RIGHT_CURLY ||
	cc === CC_SEMICOLON ||
	cc === CC_COMMA ||
	cc === CC_RIGHT_PARENTHESIS;

/**
 * Whether a pending token separator is safe to drop *after* `cc`: next to
 * `{ } ; , (` it never changes meaning. The parens are asymmetric on purpose —
 * dropping a space *before* `(` could turn `x (y)` into the function `x(y)`, and
 * one *after* `)` could turn `:not(a) b` into the compound `:not(a)b`, so `(` is
 * only safe on this (after) side and `)` only on the before side.
 * @param {number} cc last emitted code point
 * @returns {boolean} true when the separator can be dropped
 */
const _dropSeparatorAfter = (cc) =>
	cc === CC_LEFT_CURLY ||
	cc === CC_RIGHT_CURLY ||
	cc === CC_SEMICOLON ||
	cc === CC_COMMA ||
	cc === CC_LEFT_PARENTHESIS;

/**
 * What the CSS printer may be told, on top of the `mode` every language has —
 * the printing slice of `CssProcessOptions`, named once so nothing outside CSS
 * has to enumerate it.
 * @typedef {Pick<CssProcessOptions, "environment" | "declareCharset" | "charset" | "convertLengthUnits" | "convertApproximateColors" | "dropOverriddenDeclarations" | "mergeDistantRules" | "rewriteCustomProperties" | "renderEmbeddedSource" | "deferEmbeddedSource" | "transforms" | "unusedSymbols" | "pseudoClasses">} CssPrintOptions
 */

/** @typedef {import("../util/SourceProcessor").PrintContext<CssPath, Node, CssPrintOptions>} PrintContext */

// === Spacing: the whole safe-spacing discipline is CSS-specific (a token
// separator is safe to drop next to `{ } ; , ( )` — the asymmetric paren rule in
// `_dropSeparator*`), so it lives with `printer`, not the generic context.

// A lone space marker a whitespace token prints; `_join` resolves it. Real tokens
// print with their delimiters (a string keeps its quotes), so it never collides.
const _SEP = " ";

/**
 * Whether a token separator between two code points must be kept.
 * @param {number} lastCode last emitted code point
 * @param {number} nextCode first code point of the next fragment
 * @returns {boolean} true when the separator must be kept
 */
const _keepSeparator = (lastCode, nextCode) =>
	!_dropSeparatorAfter(lastCode) && !_dropSeparatorBefore(nextCode);

// A child, adjacent-sibling or general-sibling combinator. Whitespace around one
// is insignificant only at a selector's top level — inside `[…]` or `(…)` these
// are matchers or `An+B` — so trimming applies to a prelude join alone.
/**
 * @param {number} cc a code point
 * @returns {boolean} true for `>` / `+` / `~`
 */
const _isCombinator = (cc) =>
	cc === CC_GREATER_THAN_SIGN || cc === CC_PLUS_SIGN || cc === CC_TILDE;

// WHY: what separates the parts of a query condition — a media-feature range
// comparison, the `:` of a plain feature test and the `!` of a tested
// `!important`. Whitespace around one is insignificant there, but the same code
// points mean something else in a selector, as combinators and a pseudo-class
// `:`, and in a declaration value, as `calc()` operators, so this trims a `(…)`
// condition outside a value alone.
/**
 * @param {number} cc a code point
 * @returns {boolean} true for `<` / `>` / `=` / `:` / `!`
 */
const _isConditionSeparator = (cc) =>
	cc === CC_LESS_THAN_SIGN ||
	cc === CC_GREATER_THAN_SIGN ||
	cc === CC_EQUALS_SIGN ||
	cc === CC_COLON ||
	cc === CC_EXCLAMATION;

// What a `_join` may drop a separator next to.
const _TRIM_NOTHING = 0;
const _TRIM_COMBINATORS = 1; // a qualified rule's selector prelude
const _TRIM_CONDITIONS = 2; // a query condition's `(…)` block
const _TRIM_MATH = 3; // a math function's `*` / `/`, which need no whitespace
// A declaration value's whitespace only separates tokens, so it is kept exactly
// where joining would fuse two — unlike a selector, where it is a combinator.
const _TRIM_SEPARATORS = 4;

/**
 * @param {number} trim one of the `_TRIM_*` modes
 * @param {number} cc a code point
 * @returns {boolean} whether a separator next to `cc` may be dropped
 */
const _isTrimmable = (trim, cc) => {
	if (trim === _TRIM_COMBINATORS) return _isCombinator(cc);
	if (trim === _TRIM_CONDITIONS) return _isConditionSeparator(cc);
	// CSS Values 4 §10.1: `+` and `-` require whitespace on both sides — without
	// it the sign would read as part of the next number — but `*` and `/` do not.
	if (trim === _TRIM_MATH) {
		return cc === CC_ASTERISK || cc === CC_SOLIDUS;
	}
	return false;
};

/**
 * An ident code point (CSS Syntax §4.2): an ident-start code point, a digit or
 * `-`. The lookup table only spans ASCII, so non-ASCII is answered here.
 * @param {number} cc a code point
 * @returns {boolean} true when `cc` is an ident code point
 */
const isIdentCodePoint = (cc) => cc >= 128 || _isIdentCodePoint(cc);

/**
 * Whether what precedes `at` lets a number start there: an ident running into
 * the position carries the digits instead, and `#` and `@` carry their own.
 * @param {string} out everything emitted so far
 * @param {number} at index a number would start at
 * @returns {boolean} true when a number can start at `at`
 */
const _startsNumber = (out, at) => {
	if (at === 0) return true;
	const before = out.charCodeAt(at - 1);
	return !(
		isIdentCodePoint(before) ||
		before === CC_REVERSE_SOLIDUS ||
		before === CC_NUMBER_SIGN ||
		before === CC_AT_SIGN
	);
};

/**
 * Whether the run of digits `out` ends with is a number rather than the tail of
 * an ident: `1` is, the `1` of `.p1` is not, and only a number can take a `.` on.
 * @param {string} out everything emitted so far
 * @returns {boolean} true when `out` ends in a number
 */
const _endsWithNumber = (out) => {
	let at = out.length;
	while (at > 0 && isDigit(out.charCodeAt(at - 1))) at--;
	if (at === out.length) return false;
	// A sign signs a number only where a number could start: the `-1` of
	// `margin:-1` is signed, the `-1` of `.p-1` and the `-5` of `1e-5` are ident.
	const sign = at === 0 ? 0 : out.charCodeAt(at - 1);
	if (
		(sign === CC_HYPHEN_MINUS || sign === CC_PLUS_SIGN) &&
		_startsNumber(out, at - 1)
	) {
		return true;
	}
	return _startsNumber(out, at);
};

/**
 * Whether `out` ends with a hex escape and the one whitespace that terminates
 * it. That whitespace belongs to the escape, so anything identifier-like written
 * after it carries the same token on rather than starting a new one.
 * @param {string} out everything emitted so far
 * @returns {boolean} true when the trailing whitespace is an escape terminator
 */
const _endsWithEscapeTerminator = (out) => {
	let at = out.length - 1;
	// `consumeExtraNewline`: a CRLF pair is the one terminator.
	if (
		out.charCodeAt(at) === CC_LINE_FEED &&
		out.charCodeAt(at - 1) === CC_CARRIAGE_RETURN
	) {
		at--;
	}
	at--;
	let digits = 0;
	while (digits < 6 && at >= 0 && isHexDigit(out.charCodeAt(at))) {
		at--;
		digits++;
	}
	if (digits === 0 || out.charCodeAt(at) !== CC_REVERSE_SOLIDUS) return false;
	// An even run of `\` is escaped backslashes, and opens no escape.
	let slashes = 0;
	while (at >= 0 && out.charCodeAt(at) === CC_REVERSE_SOLIDUS) {
		at--;
		slashes++;
	}
	return slashes % 2 === 1;
};

/**
 * Whether emitting `fragment` directly after `out` would re-tokenize as one
 * token. Two printed siblings normally concatenate to exactly their source, but
 * not when a dropped comment was all that separated them, or when one was
 * rewritten (`1.0.5` is two numbers, printed as `1` and `.5`) — then the junction
 * can fuse and change the declaration. The test is deliberately exact rather than
 * conservative: a space inserted where none is needed would turn a compound
 * selector (`.a.b`) into a descendant one (`.a .b`), so over-separating is as
 * unsafe as under-separating.
 * Takes the preceding character rather than the text before it: what it is
 * appended to is a rope, and reading a character off one flattens the whole of
 * it — once per fragment, over text that grows with every fragment.
 * @param {number} last character code before the fragment
 * @param {string} fragment the fragment about to be emitted
 * @param {string} out everything emitted so far, read only where a digit meets a `.`
 * @returns {boolean} true when a separator has to be inserted between them
 */
const _wouldFuseTokens = (last, fragment, out) => {
	const next = fragment.charCodeAt(0);
	// An escape swallows whatever follows it.
	if (last === CC_REVERSE_SOLIDUS) return true;
	// A hex escape eats one whitespace as its terminator, so a run ending in one
	// parts nothing and the next identifier continues that token.
	if (
		isAsciiWhitespace(last) &&
		(isIdentCodePoint(next) || next === CC_REVERSE_SOLIDUS)
	) {
		return _endsWithEscapeTerminator(out);
	}
	// `/` + `*` opens a comment.
	if (last === CC_SOLIDUS && next === CC_ASTERISK) return true;
	if (isIdentCodePoint(last)) {
		// A number takes a `-` on as a unit only where an ident would start, so
		// `0` + `-2px` stays two tokens: a sign before a digit starts a number.
		if (
			isDigit(last) &&
			next === CC_HYPHEN_MINUS &&
			_startsDigits(fragment, 1) &&
			_endsWithNumber(out)
		) {
			return false;
		}
		// One ident / dimension / hash / at-keyword continues; `(` after an ident
		// makes it a function token instead.
		if (
			isIdentCodePoint(next) ||
			next === CC_LEFT_PARENTHESIS ||
			next === CC_REVERSE_SOLIDUS
		) {
			return true;
		}
		// `1` + `.5` reads back as the single number `1.5`. Only a number takes
		// the `.` on, and only when a digit follows it: the `1` ending the ident of
		// `.p1` does not, so `.p1` + `.c1` stays the one compound selector it is.
		if (isDigit(last) && next === CC_FULL_STOP) {
			return isDigit(fragment.charCodeAt(1)) && _endsWithNumber(out);
		}
		// `123` + `%` reads back as the one percentage token `123%`, and only a
		// number takes the `%` on — the `1` ending an ident does not.
		if (isDigit(last) && next === CC_PERCENTAGE) return _endsWithNumber(out);
		if (last === CC_HYPHEN_MINUS) {
			// A sign starting a token takes `.5` on as the number `-.5`.
			if (next === CC_FULL_STOP) {
				return (
					isDigit(fragment.charCodeAt(1)) && _startsNumber(out, out.length - 1)
				);
			}
			// A trailing `--` plus `>` would close a CDC.
			return next === CC_GREATER_THAN_SIGN;
		}
		return false;
	}
	// A number may start right after `.` or `+` (`-` is an ident code point, so it
	// is already covered above).
	if (last === CC_FULL_STOP) return isDigit(next);
	// CSS Syntax 3 §4.3.10: a `+` starts a number only before a digit, or before a
	// `.` that itself has one. `.a+.m` is the sibling combinator and a class.
	if (last === CC_PLUS_SIGN) {
		return (
			isDigit(next) ||
			(next === CC_FULL_STOP && isDigit(fragment.charCodeAt(1)))
		);
	}
	// `#` / `@` + ident starts a hash / at-keyword token.
	if (last === CC_NUMBER_SIGN || last === CC_AT_SIGN) {
		return isIdentCodePoint(next) || next === CC_REVERSE_SOLIDUS;
	}
	// `<` + `!` opens a CDO.
	return last === CC_LESS_THAN_SIGN && next === CC_EXCLAMATION;
};

/**
 * Whether a code point may start the token a space parts from a `)` or a string.
 * @param {number} cc a code point
 * @returns {boolean} true unless it closes, lists or ends what came before
 */
const _startsSpacedToken = (cc) =>
	cc !== CC_RIGHT_PARENTHESIS &&
	cc !== CC_COMMA &&
	cc !== CC_SEMICOLON &&
	cc !== CC_EXCLAMATION &&
	cc !== CC_SOLIDUS &&
	cc !== CC_RIGHT_CURLY &&
	!isAsciiWhitespace(cc);

// A declaration of `unicode-range`, read off its start.
const _UNICODE_RANGE_HEAD_RE = /^unicode-range:/i;

/**
 * Whether a number starts at a `-`: a digit, or a `.` and a digit, follows it.
 * @param {string} text the text
 * @param {number} at where the `-` stands
 * @returns {boolean} true when it is a sign
 */
const _startsNumberAt = (text, at) => {
	const next = text.charCodeAt(at + 1);
	return (
		isDigit(next) || (next === CC_FULL_STOP && isDigit(text.charCodeAt(at + 2)))
	);
};

/**
 * Printed declarations with a space back between two numbers that touch,
 * before a hash a name or number meets, and after a `)` or a string. CSS Syntax 3 reads the joined
 * forms as written; an engine older than it is not known to.
 * @param {string} text one or more printed declarations
 * @returns {string} the same, spaced where a token met another
 */
const _spaceForOldTokenizer = (text) => {
	let out = "";
	let from = 0;
	// Where the last numeric token ended, so one starting there touches it.
	let numberEnd = -1;
	let custom = false;
	for (let i = 0; i < text.length;) {
		const cc = text.charCodeAt(i);
		if (cc === CC_SEMICOLON || i === 0) {
			// A custom property's value is a token stream no old engine reads, and
			// a `unicode-range` one is ranges: `U+131-140` is one of them.
			const at = cc === CC_SEMICOLON ? i + 1 : i;
			custom =
				(text.charCodeAt(at) === CC_HYPHEN_MINUS &&
					text.charCodeAt(at + 1) === CC_HYPHEN_MINUS) ||
				_UNICODE_RANGE_HEAD_RE.test(text.slice(at, at + 14));
			if (cc === CC_SEMICOLON) {
				i++;
				continue;
			}
		}
		if (custom) {
			i++;
			continue;
		}
		if (cc === CC_QUOTATION_MARK || cc === CC_APOSTROPHE) {
			let j = i + 1;
			while (j < text.length && text.charCodeAt(j) !== cc) {
				if (text.charCodeAt(j) === CC_REVERSE_SOLIDUS) j++;
				j++;
			}
			i = j + 1;
			if (i < text.length && _startsSpacedToken(text.charCodeAt(i))) {
				out += `${text.slice(from, i)} `;
				from = i;
			}
			continue;
		}
		if (cc === CC_RIGHT_PARENTHESIS) {
			i++;
			if (i < text.length && _startsSpacedToken(text.charCodeAt(i))) {
				out += `${text.slice(from, i)} `;
				from = i;
			}
			continue;
		}
		const sign = cc === CC_PLUS_SIGN || cc === CC_HYPHEN_MINUS ? 1 : 0;
		const lead = text.charCodeAt(i + sign);
		if (
			isDigit(lead) ||
			(lead === CC_FULL_STOP && isDigit(text.charCodeAt(i + sign + 1)))
		) {
			if (i === numberEnd) {
				out += `${text.slice(from, i)} `;
				from = i;
			}
			let j = i + sign;
			while (isDigit(text.charCodeAt(j))) j++;
			if (
				text.charCodeAt(j) === CC_FULL_STOP &&
				isDigit(text.charCodeAt(j + 1))
			) {
				j++;
				while (isDigit(text.charCodeAt(j))) j++;
			}
			if (text.charCodeAt(j) === CC_PERCENTAGE) {
				j++;
			} else if (
				text.charCodeAt(j) !== CC_HYPHEN_MINUS ||
				!_startsNumberAt(text, j)
			) {
				// A unit; a `-` a number follows is that number's sign instead.
				while (j < text.length && isIdentCodePoint(text.charCodeAt(j))) j++;
			}
			numberEnd = j;
			i = j;
			if (text.charCodeAt(i) === CC_NUMBER_SIGN) {
				out += `${text.slice(from, i)} `;
				from = i;
			}
			continue;
		}
		if (isIdentCodePoint(cc) || cc === CC_NUMBER_SIGN) {
			// A name, a hash or a function's name: digits inside it are its own.
			let j = i + 1;
			while (j < text.length && isIdentCodePoint(text.charCodeAt(j))) j++;
			// `url(` holds a raw address, which may carry anything but a `)`.
			if (
				text.charCodeAt(j) === CC_LEFT_PARENTHESIS &&
				j - i === 3 &&
				asciiCaseInsensitiveMatch(text.slice(i, j), "url") &&
				text.charCodeAt(j + 1) !== CC_QUOTATION_MARK &&
				text.charCodeAt(j + 1) !== CC_APOSTROPHE
			) {
				j = text.indexOf(")", j);
				/* istanbul ignore if -- @preserve: the printer closes an unclosed url */
				if (j === -1) j = text.length;
			} else if (text.charCodeAt(j) === CC_NUMBER_SIGN) {
				out += `${text.slice(from, j)} `;
				from = j;
			}
			i = j;
			continue;
		}
		i++;
	}
	return from === 0 ? text : out + text.slice(from);
};

/**
 * Join sibling fragments (a prelude / value / args / block body), resolving each
 * `_SEP` marker into a single space kept only where dropping it would merge two
 * tokens (minifying) or unconditionally (beautifying). `trim` additionally drops
 * whitespace next to the code points that carry their own meaning in this
 * context — a selector's combinators, a query condition's comparisons.
 * Fragments that had no separator at all still get one where concatenating them
 * would fuse two tokens into one (see {@link _wouldFuseTokens}).
 * @param {string[]} parts printed fragments in source order
 * @param {boolean} beautify whether every separator is kept
 * @param {number=} trim one of the `_TRIM_*` modes (default `_TRIM_NOTHING`)
 * @returns {string} the joined text
 */
const _join = (parts, beautify, trim = _TRIM_NOTHING) => {
	let out = "";
	let last = -1;
	let pending = false;
	for (let i = 0; i < parts.length; i++) {
		const f = parts[i];
		if (f.length === 0) continue;
		if (f === _SEP) {
			pending = true;
			continue;
		}
		const first = f.charCodeAt(0);
		if (pending) {
			const keep =
				beautify ||
				(trim === _TRIM_SEPARATORS
					? _wouldFuseTokens(last, f, out)
					: _keepSeparator(last, first) &&
						// A condition still keeps what parts `<` from `!--` or `--` from `>`.
						((!_isTrimmable(trim, last) && !_isTrimmable(trim, first)) ||
							(trim === _TRIM_CONDITIONS && _wouldFuseTokens(last, f, out))));
			if (out.length !== 0 && keep) {
				out += " ";
			}
			pending = false;
		} else if (out.length !== 0 && _wouldFuseTokens(last, f, out)) {
			// Nothing separated these in source, yet joining them would read back as
			// one token. Whitespace is a descendant combinator in a selector, so there
			// only an empty comment parts them without adding to the source.
			out += trim === _TRIM_COMBINATORS ? "/**/" : " ";
		}
		out += f;
		last = f.charCodeAt(f.length - 1);
	}
	return out;
};

/**
 * Normalize a numeric string (no unit): drop a leading zero (`0.5`→`.5`) and
 * trailing fractional zeros (`1.50`→`1.5`, `1.0`→`1`). Value-preserving;
 * scientific notation is left untouched.
 * @param {string} num numeric text (may carry a sign)
 * @returns {string} the normalized number
 */
const _normalizeNumber = (num) => {
	if (num.includes("e") || num.includes("E")) return num;
	let sign = "";
	let s = num;
	const c0 = s.charCodeAt(0);
	if (c0 === CC_PLUS_SIGN || c0 === CC_HYPHEN_MINUS) {
		if (c0 === CC_HYPHEN_MINUS) sign = "-";
		s = s.slice(1);
	}
	const dot = s.indexOf(".");
	if (dot !== -1) {
		let end = s.length;
		while (end > dot + 1 && s.charCodeAt(end - 1) === CC_0) end--;
		if (end === dot + 1) end = dot; // fraction emptied → drop the dot too
		s = s.slice(0, end);
	}
	while (
		s.length > 1 &&
		s.charCodeAt(0) === CC_0 &&
		s.charCodeAt(1) !== CC_FULL_STOP
	) {
		s = s.slice(1); // redundant integer leading zeros
	}
	if (
		s.length > 1 &&
		s.charCodeAt(0) === CC_0 &&
		s.charCodeAt(1) === CC_FULL_STOP
	) {
		s = s.slice(1); // "0.5" → ".5"
	}
	if (s === "" || s === ".") s = "0";
	return sign + s;
};

/**
 * Write a number spelled with an exponent the shortest exact way: plainly where
 * that is no longer (`0.2e2`→`20`), else on an integer mantissa (`10E3`→`1e4`).
 * An exponent makes a `<number>`, never an `<integer>` (§4.3.12), so where that
 * is read `keepNumberType` keeps a spelling that is still not an integer.
 * @param {string} num numeric text with an exponent (may carry a sign)
 * @param {boolean} keepNumberType whether an integer spelling is ruled out
 * @returns {string} the normalized number
 */
const _normalizeExponent = (num, keepNumberType) => {
	let sign = "";
	let s = num;
	const c0 = s.charCodeAt(0);
	if (c0 === CC_PLUS_SIGN || c0 === CC_HYPHEN_MINUS) {
		if (c0 === CC_HYPHEN_MINUS) sign = "-";
		s = s.slice(1);
	}
	let e = s.indexOf("e");
	if (e === -1) e = s.indexOf("E");
	const mantissa = s.slice(0, e);
	const dot = mantissa.indexOf(".");
	let digits =
		dot === -1 ? mantissa : mantissa.slice(0, dot) + mantissa.slice(dot + 1);
	// Where the decimal point falls in `digits` once the exponent has moved it.
	let point = (dot === -1 ? mantissa.length : dot) + Number(s.slice(e + 1));
	let start = 0;
	while (start < digits.length && digits.charCodeAt(start) === CC_0) start++;
	let end = digits.length;
	while (end > start && digits.charCodeAt(end - 1) === CC_0) end--;
	if (start === end) return keepNumberType ? `${sign}0e0` : `${sign}0`;
	point -= start;
	digits = digits.slice(start, end);
	const scale = point - digits.length;
	const exponent = `${digits}e${scale}`;
	// Built only when it could win, so `1e999` never writes a thousand zeros.
	const plainLength =
		point <= 0
			? 1 - point + digits.length
			: scale >= 0
				? point
				: digits.length + 1;
	if (
		plainLength > exponent.length ||
		(keepNumberType && point >= digits.length)
	) {
		return sign + exponent;
	}
	const plain =
		point <= 0
			? `.${"0".repeat(-point)}${digits}`
			: scale >= 0
				? digits + "0".repeat(scale)
				: `${digits.slice(0, point)}.${digits.slice(point)}`;
	return sign + plain;
};

// WHY: Chromium serializes a computed number at 6 significant digits and lays a
// length out in 1/64px, so 6 digits sit below what a stylesheet can observe
// until the value reaches ~23000px, which no real one does. Measured across
// `width` in px and %, `opacity`, `scale()`, `letter-spacing`,
// `transition-duration` and `flex-grow`, at widths from 100px to 1000000px.
const _SIGNIFICANT_DIGITS = 6;

// WHY: a length the engine scales by a font size or viewport is often a ratio
// written to land on whole pixels (2/19em at 19px), which Chromium stores as a
// float and floors to 1/64px: measured, `.105263em` there is `1.98438px` where
// `0.1052631579em` is `2px`. Nine digits round-trip a float, so they land alike;
// fewer do where the float they make at each such scale is the same.
const _SCALED_LENGTH_DIGITS = 9;

// Above this the rounded absolute error would pass 1/64px, so the digits stay.
const _ROUNDING_LIMIT = 1e4;

// Blink and WebKit snap a length to 1/64px and Gecko to 1/60px; a float on one
// of their half-steps is where flooring or rounding to the nearest step turns.
const _LAYOUT_HALF_STEPS = [128, 120];
// The whole-pixel scales a font-relative length is written to land against.
const _LARGEST_WHOLE_SCALE = 256;

/**
 * Whether a computed length sits on a layout half-step.
 * @param {number} pixels the float an engine computes
 * @returns {boolean} true when a step's flooring or rounding turns there
 */
const _onLayoutStep = (pixels) => {
	for (const step of _LAYOUT_HALF_STEPS) {
		if (Number.isInteger(pixels * step)) return true;
	}
	return false;
};

/**
 * Whether a shorter spelling of a length computes the same float as the written
 * one at every whole-pixel scale where either float sits on a layout step. Blink
 * and WebKit scale the number as a double, Gecko as the float it parsed.
 * @param {number} value the length as written
 * @param {number} shorter the same length with fewer digits
 * @returns {boolean} true when no such scale tells the two apart
 */
const _landsAlike = (value, shorter) => {
	const parsedValue = Math.fround(value);
	const parsedShorter = Math.fround(shorter);
	for (let scale = 1; scale <= _LARGEST_WHOLE_SCALE; scale++) {
		const scaled = Math.fround(value * scale);
		const shorterScaled = Math.fround(shorter * scale);
		if (
			scaled !== shorterScaled &&
			(_onLayoutStep(scaled) || _onLayoutStep(shorterScaled))
		) {
			return false;
		}
		const parsedScaled = Math.fround(parsedValue * scale);
		const parsedShorterScaled = Math.fround(parsedShorter * scale);
		if (
			parsedScaled !== parsedShorterScaled &&
			(_onLayoutStep(parsedScaled) || _onLayoutStep(parsedShorterScaled))
		) {
			return false;
		}
	}
	return true;
};

/**
 * Round a numeric string to `_SIGNIFICANT_DIGITS`, or return it unchanged when
 * rounding would not shorten it (or would leave the range the measurement
 * covers). Scientific notation is left alone — `toPrecision` may produce it, and
 * an exponent is not shorter here anyway.
 * @param {string} num the normalized numeric text
 * @param {string=} unit its lowercased unit, where a scaled length keeps more digits
 * @returns {string} the rounded number, or `num`
 */
const _roundSignificant = (num, unit = "") => {
	// A number written in at most six characters carries at most six significant
	// digits, a sign and dot only taking away, so rounding hands back what it was
	// given. Almost every number is this short.
	if (num.length <= _SIGNIFICANT_DIGITS) return num;
	const digits =
		unit === "" ||
		unit === "%" ||
		ABSOLUTE_UNIT_SCALE.has(unit) ||
		ANGLE_UNITS.has(unit)
			? _SIGNIFICANT_DIGITS
			: _SCALED_LENGTH_DIGITS;
	if (num.includes("e") || num.includes("E")) return num;
	const value = Number(num);
	if (!Number.isFinite(value) || Math.abs(value) >= _ROUNDING_LIMIT) return num;
	let rounded = value.toPrecision(digits);
	if (unit === "%") {
		// Gecko parses a percentage as a single-precision fraction laid out in 1/60px,
		// so the digits must reach that float: `14.285714%` drifts, `14.2857143%` not.
		const parsed = Math.fround(value / 100);
		for (
			let more = digits;
			more < _SCALED_LENGTH_DIGITS &&
			Math.fround(Number(rounded) / 100) !== parsed;
			more++
		) {
			rounded = value.toPrecision(more + 1);
		}
		if (Math.fround(Number(rounded) / 100) !== parsed) return num;
	} else if (digits === _SCALED_LENGTH_DIGITS) {
		// The fewest digits landing where the written length does, asked again of
		// each answer until it holds, which is what a second pass would ask.
		let from = value;
		let limit = digits;
		for (;;) {
			let found = 0;
			for (let fewer = _SIGNIFICANT_DIGITS; fewer < limit; fewer++) {
				if (_landsAlike(from, Number(from.toPrecision(fewer)))) {
					found = fewer;
					break;
				}
			}
			if (found !== 0) {
				rounded = from.toPrecision(found);
				limit = found;
			}
			if (Number(rounded) === from) break;
			from = Number(rounded);
		}
	}
	if (rounded.includes("e") || rounded.includes("E")) return num;
	if (Number(rounded) === value) return num;
	const text = _normalizeNumber(rounded);
	return text.length < num.length ? text : num;
};

// The absolute units that still convert once `convertLengthUnits` is off: every
// group but `length`, which is the one that option gates. Built on first use and
// kept, so a parse points at one table or the other rather than testing per token.
/** @type {Map<string, [string, number]> | null} */
let _nonLengthUnitScale = null;

/**
 * The unit table a parse converts through, by what its options allow.
 * @param {boolean} lengths whether a length may be rewritten into another unit
 * @returns {Map<string, [string, number]>} the table to convert through
 */
const _unitScaleFor = (lengths) => {
	if (lengths) return ABSOLUTE_UNIT_SCALE;
	if (_nonLengthUnitScale === null) {
		_nonLengthUnitScale = new Map();
		for (const [unit, scale] of ABSOLUTE_UNIT_SCALE) {
			if (scale[0] !== "length") _nonLengthUnitScale.set(unit, scale);
		}
	}
	return _nonLengthUnitScale;
};

// One operand as the value printer leaves it: a number, a dimension or a
// percentage. Sticky, so the tokenizer walks the expression without slicing it.
const _CALC_OPERAND = /(?:\d*\.\d+|\d+)(?:e[+-]?\d+)?(%|[a-z]+)?/iy;

// `calc(` opening a nested expression. The inner one already printed, so it
// arrives here as text and is treated as the parentheses it is.
const _CALC_OPEN = /calc\(/iy;

/**
 * The key a unit accumulates under. Units fixed against each other share their
 * group's key and are counted in its base unit, so `1in + 1px` is one term;
 * everything else keys on the unit itself, so `1em + 1px` stays two and is
 * declined below.
 * @param {string} unit the operand's unit, `""` for a plain number
 * @returns {[string, number]} the key and how many base units one of it is
 */
const _calcUnitKey = (unit) => {
	if (unit === "") return ["", 1];
	const lower = unit.toLowerCase();
	const absolute = ABSOLUTE_UNIT_SCALE.get(lower);
	return absolute === undefined ? [lower, 1] : [absolute[0], absolute[1]];
};

// A constant a calculation may name, longest name first so none of them is
// matched as a prefix of another. The lookahead keeps `e` out of a unit.
const _CALC_CONSTANT = new RegExp(
	`(?:${[...CALC_CONSTANTS.keys()]
		.sort((a, b) => b.length - a.length)
		.join("|")})(?![-\\w.%])`,
	"giy"
);

/**
 * Tokenize one printed `calc()` body.
 * @param {string} text the body
 * @returns {{ type: string, value: number, unit: string }[] | null} the tokens, or `null` when something here is not arithmetic
 */
const _tokenizeCalc = (text) => {
	/** @type {{ type: string, value: number, unit: string }[]} */
	const tokens = [];
	let i = 0;
	let spaced = false;
	while (i < text.length) {
		const c = text[i];
		if (c === " ") {
			spaced = true;
			i++;
			continue;
		}
		if (c === "(" || c === ")") {
			tokens.push({ type: c, value: 0, unit: "" });
			spaced = false;
			i++;
			continue;
		}
		if (c === "*" || c === "/" || c === ",") {
			tokens.push({ type: c, value: 0, unit: "" });
			spaced = false;
			i++;
			continue;
		}
		// CSS Values 4 §10.1: `+` and `-` are operators only with whitespace on
		// both sides. Without it the sign belongs to the number, and two operands
		// with no operator between them is not an expression at all.
		if ((c === "+" || c === "-") && spaced && text[i + 1] === " ") {
			tokens.push({ type: c, value: 0, unit: "" });
			spaced = false;
			i++;
			continue;
		}
		_CALC_OPEN.lastIndex = i;
		if (_CALC_OPEN.test(text)) {
			tokens.push({ type: "(", value: 0, unit: "" });
			spaced = false;
			i = _CALC_OPEN.lastIndex;
			continue;
		}
		let sign = 1;
		if (c === "+" || c === "-") {
			if (c === "-") sign = -1;
			i++;
		}
		// The numeric constants a calculation may name (CSS Values 4 §10.7). Read
		// before the operand, whose own pattern would take `e` for a unit.
		_CALC_CONSTANT.lastIndex = i;
		const constant = _CALC_CONSTANT.exec(text);
		if (constant !== null && constant.index === i) {
			// `infinity` and `NaN` are named in the table with no value, since no
			// printed number spells either.
			const named = CALC_CONSTANTS.get(asciiLowercaseName(constant[0]));
			if (!named) return null;
			tokens.push({ type: "value", value: sign * named, unit: "" });
			spaced = false;
			i = _CALC_CONSTANT.lastIndex;
			continue;
		}
		_CALC_OPERAND.lastIndex = i;
		const match = _CALC_OPERAND.exec(text);
		if (match === null || match.index !== i) return null;
		const unit = match[1] === undefined ? "" : match[1];
		const value = Number(match[0].slice(0, match[0].length - unit.length));
		if (!Number.isFinite(value)) return null;
		tokens.push({ type: "value", value: sign * value, unit });
		spaced = false;
		i = _CALC_OPERAND.lastIndex;
	}
	return tokens;
};

/**
 * Evaluate a tokenized expression into `key -> coefficient`, the sum CSS Values
 * 4 §10.11 reduces a calculation to.
 * @param {{ type: string, value: number, unit: string }[]} tokens the tokens
 * @param {{ at: number }} cursor the read position, carried through the recursion
 * @returns {Map<string, number> | null} the sum, or `null` when it cannot be evaluated exactly
 */
const _evaluateCalcSum = (tokens, cursor) => {
	const sum = _evaluateCalcProduct(tokens, cursor);
	if (sum === null) return null;
	for (;;) {
		const token = tokens[cursor.at];
		if (token === undefined || (token.type !== "+" && token.type !== "-")) {
			return sum;
		}
		cursor.at++;
		const right = _evaluateCalcProduct(tokens, cursor);
		if (right === null) return null;
		const sign = token.type === "+" ? 1 : -1;
		for (const [key, coefficient] of right) {
			const scaled = foldMultiply(coefficient, sign);
			if (scaled === null) return null;
			const previous = sum.get(key);
			if (previous === undefined) {
				sum.set(key, scaled);
				continue;
			}
			const added = foldAdd(previous, scaled);
			if (added === null) return null;
			sum.set(key, added);
		}
	}
};

/**
 * `<calc-product>`: a chain of `*` and `/`. The grammar takes only a `<number>`
 * on the right of a `/`, and a product needs one side to be a plain number for
 * the result to stay a sum of the units already there.
 * @param {{ type: string, value: number, unit: string }[]} tokens the tokens
 * @param {{ at: number }} cursor the read position
 * @returns {Map<string, number> | null} the sum, or `null`
 */
const _evaluateCalcProduct = (tokens, cursor) => {
	let sum = _evaluateCalcValue(tokens, cursor);
	if (sum === null) return null;
	for (;;) {
		const token = tokens[cursor.at];
		if (token === undefined || (token.type !== "*" && token.type !== "/")) {
			return sum;
		}
		cursor.at++;
		const right = _evaluateCalcValue(tokens, cursor);
		if (right === null) return null;
		if (token.type === "/") {
			const divisor = right.get("");
			if (divisor === undefined || right.size !== 1) return null;
			const out = new Map();
			for (const [key, coefficient] of sum) {
				const quotient = foldDivide(coefficient, divisor);
				if (quotient === null) return null;
				out.set(key, quotient);
			}
			sum = out;
			continue;
		}
		const leftNumber = sum.size === 1 ? sum.get("") : undefined;
		const rightNumber = right.size === 1 ? right.get("") : undefined;
		// One side has to be a plain number; two dimensions multiply into a type no
		// property here accepts.
		const factor = rightNumber !== undefined ? rightNumber : leftNumber;
		const other = rightNumber !== undefined ? sum : right;
		if (factor === undefined) return null;
		const out = new Map();
		for (const [key, coefficient] of other) {
			const product = foldMultiply(coefficient, factor);
			if (product === null) return null;
			out.set(key, product);
		}
		sum = out;
	}
};

/**
 * `<calc-value>`: an operand, or a parenthesized sum.
 * @param {{ type: string, value: number, unit: string }[]} tokens the tokens
 * @param {{ at: number }} cursor the read position
 * @returns {Map<string, number> | null} the sum, or `null`
 */
const _evaluateCalcValue = (tokens, cursor) => {
	const token = tokens[cursor.at];
	if (token === undefined) return null;
	if (token.type === "(") {
		cursor.at++;
		const inner = _evaluateCalcSum(tokens, cursor);
		if (inner === null) return null;
		const close = tokens[cursor.at];
		if (close === undefined || close.type !== ")") return null;
		cursor.at++;
		return inner;
	}
	if (token.type !== "value") return null;
	cursor.at++;
	const [key, scale] = _calcUnitKey(token.unit);
	const value = foldMultiply(token.value, scale);
	return value === null ? null : new Map([[key, value]]);
};

/**
 * Split on the commas at the top of a function body, so a nested call's own
 * arguments stay in the piece that holds it.
 * @param {string} text the body between one call's parentheses
 * @returns {string[]} its arguments, still as written
 */
const _splitTopLevelArguments = (text) => {
	/** @type {string[]} */
	const parts = [];
	let depth = 0;
	let start = 0;
	for (let i = 0; i < text.length; i++) {
		const code = text.charCodeAt(i);
		if (code === 0x28) {
			depth++;
		} else if (code === 0x29) {
			depth--;
		} else if (code === 0x2c && depth === 0) {
			parts.push(text.slice(start, i));
			start = i + 1;
		}
	}
	parts.push(text.slice(start));
	return parts;
};

const _HEX = "0123456789abcdef";

/**
 * @param {number} n byte value 0..255
 * @returns {string} its two lowercase hex digits
 */
const _hex2 = (n) => _HEX[(n >> 4) & 15] + _HEX[n & 15];

/**
 * The shortest opaque color text for (r, g, b): a named color where one is
 * shorter than the hex (`RGB_TO_NAME` holds only those), else a collapsed 3-hex
 * or a 6-hex.
 * @param {number} r red 0..255
 * @param {number} g green 0..255
 * @param {number} b blue 0..255
 * @returns {string} the shortest form
 */
const _shortestColor = (r, g, b) => {
	const name = RGB_TO_NAME.get((r << 16) | (g << 8) | b);
	if (name !== undefined) return name;
	const rr = _hex2(r);
	const gg = _hex2(g);
	const bb = _hex2(b);
	return rr[0] === rr[1] && gg[0] === gg[1] && bb[0] === bb[1]
		? `#${rr[0]}${gg[0]}${bb[0]}`
		: `#${rr}${gg}${bb}`;
};

/**
 * The shortest text for (r, g, b, a) with an alpha below 1 — a 4- or 8-digit hex,
 * always shorter than the `rgba()` it came from. No named color has an alpha.
 * @param {number} r red 0..255
 * @param {number} g green 0..255
 * @param {number} b blue 0..255
 * @param {number} a alpha 0..255
 * @returns {string} the shortest form
 */
const _shortestAlphaColor = (r, g, b, a) => {
	const rr = _hex2(r);
	const gg = _hex2(g);
	const bb = _hex2(b);
	const aa = _hex2(a);
	return rr[0] === rr[1] &&
		gg[0] === gg[1] &&
		bb[0] === bb[1] &&
		aa[0] === aa[1]
		? `#${rr[0]}${gg[0]}${bb[0]}${aa[0]}`
		: `#${rr}${gg}${bb}${aa}`;
};

/**
 * The shortest `<alpha-value>` naming one of the 256 bytes a hex alpha spells.
 * The engine quantizes the channel to 8 bits, so any decimal that rounds back to
 * the same byte is the same color — `.5` for `80`. Every byte round-trips at
 * three decimals or fewer, so the search always ends. `255` is not one of them:
 * an opaque color is written without an alpha at all, so both callers take that
 * path before reaching here.
 * @param {number} a alpha 0..254
 * @returns {string} the alpha text
 */
const _alphaByteText = (a) => {
	if (a === 0) return "0";
	const exact = a / 255;
	let digits = 1;
	let rounded = exact.toFixed(digits);
	while (_clamp255(Number(rounded) * 255) !== a) {
		rounded = exact.toFixed(++digits);
	}
	// A `0.5` is `.5`, and a trailing zero says nothing.
	return rounded.replace(/0+$/, "").slice(1);
};

/**
 * @param {number} x a number
 * @returns {number} `x` rounded and clamped to a 0..255 byte
 */
const _clamp255 = (x) => (x < 0 ? 0 : x > 255 ? 255 : Math.round(x));

/**
 * Whether a channel lands a hair off a byte's half, where the precision an engine
 * computes it at picks the byte. An exact half is not one: it rounds up anywhere.
 * @param {number} channel the channel, on the 0..255 scale
 * @returns {boolean} true when engines may round it apart
 */
const _nearByteHalf = (channel) => {
	const off = Math.abs((channel % 1) - 0.5);
	return off !== 0 && off < 0.001;
};

/**
 * Minify an `rgb()` / `rgba()` color function to the shortest opaque form. Only
 * rgb/rgba (exact integer / percent math); returns null for anything else —
 * a different function, non-numeric args, or a partly-transparent alpha —
 * leaving it as its normalized function form. Transparent black collapses to the
 * `transparent` keyword (identical value, universally supported, shorter). hsl is
 * intentionally not converted: its hue math can round to a different byte, which
 * would change the color.
 * @param {string} fn the lowercased function name
 * @param {string} inner the already-joined argument text
 * @param {boolean} hexAlpha whether the target reads a 4-/8-digit hex (see `output.environment`)
 * @param {boolean} operand whether the color is read as numbers here, which only an exact conversion survives
 * @returns {string | null} the shortest color, or null to keep the function
 */
const _minifyColorFunction = (fn, inner, hexAlpha, operand) => {
	if ((fn !== "rgb" && fn !== "rgba") || !_S._transforms.shortenColors) {
		return null;
	}
	// Cut on the separators `rgb()` allows — whitespace, `,` and the `/` before the
	// alpha — in one pass. Every color reaches this, and the regex form built a
	// replaced copy, a split array and a filtered copy for the same few strings.
	/** @type {string[]} */
	const args = [];
	let from = -1;
	for (let i = 0; i <= inner.length; i++) {
		// Spelled out rather than asked for: a call per character loses to the regex
		// engine, and anything looser than the five whitespace code points parts a
		// token CSS does not, making an invalid declaration valid.
		const c = inner.charCodeAt(i);
		if (
			i === inner.length ||
			c === CC_SPACE ||
			c === CC_TAB ||
			c === CC_LINE_FEED ||
			c === CC_CARRIAGE_RETURN ||
			c === CC_FORM_FEED ||
			c === CC_COMMA ||
			c === CC_SOLIDUS
		) {
			if (from === -1) continue;
			// A fifth argument is not a color this rewrite is defined for.
			if (args.length === 4) return null;
			args.push(inner.slice(from, i));
			from = -1;
		} else if (from === -1) {
			from = i;
		}
	}
	if (args.length !== 3 && args.length !== 4) return null;
	// WHY: the scan above states the runs between the separators without saying
	// which separator stood there, so `rgb(1, 2 3)` and `rgb(1 2 3 .5)` read as
	// three or four arguments like any other spelling. CSS Color 4 gives `rgb()`
	// a modern `A B C` with an optional `/ D` and a legacy `A, B, C` with an
	// optional `, D`; a spelling matching neither is a declaration engines drop.
	if (!_COLOR_LEGACY_RE.test(inner) && !_COLOR_MODERN_RE.test(inner)) {
		return null;
	}
	/** @type {number[]} */
	const channels = [];
	// The channels as written, before the byte: what a mix or a relative
	// reference computes with is the number, not the pixel it would paint.
	/** @type {number[]} */
	const written = [];
	let alpha = 1;
	let percentChannelCount = 0;
	let roundedChannel = false;
	for (let i = 0; i < args.length; i++) {
		const s = args[i];
		if (!/^[+-]?(?:\d+\.?\d*|\.\d+)%?$/.test(s)) return null;
		const pct = s.endsWith("%");
		const v = Number.parseFloat(pct ? s.slice(0, -1) : s);
		if (Number.isNaN(v)) return null;
		if (i === 3) {
			alpha = pct ? v / 100 : v;
		} else {
			if (pct) percentChannelCount++;
			const channel = pct ? (v * 255) / 100 : v;
			// WHY: a percentage a hair off a byte's half is no byte engines agree on —
			// measured in Chromium, `66.862745098%` is 170.4999999999 and paints 171 —
			// so the spelling is kept. An exact half rounds up in every engine.
			if (pct && _nearByteHalf(channel)) return null;
			// A hex is the same color only where the channel is already a byte —
			// `rgb(50% 50% 50%)` is 127.5, not `gray`. One out of range is: it clamps.
			if (!Number.isInteger(channel)) roundedChannel = true;
			// Clamped but not rounded: a mix or a relative color reads its channels
			// from here, and the engine computes with the number, not the byte.
			written.push(channel < 0 ? 0 : channel > 255 ? 255 : channel);
			channels.push(_clamp255(channel));
		}
	}
	// Mixed number/percentage channels are invalid CSS (ignored by browsers);
	// rewriting would activate a dead declaration.
	if (percentChannelCount !== 0 && percentChannelCount !== 3) return null;
	if (operand && roundedChannel) return null;
	_fallbackChannels = written;
	_fallbackAlpha = alpha;
	_fallbackClipped = false;
	_fallbackConversion = ENCODED_ALREADY;
	// Fully transparent *black* only — `transparent` is `rgba(0,0,0,0)`, so a
	// non-black transparent (e.g. `rgba(255,0,0,0)`) is a different value.
	if (
		alpha === 0 &&
		channels[0] === 0 &&
		channels[1] === 0 &&
		channels[2] === 0
	) {
		return "transparent";
	}
	// WHY: a hex alpha is the same color, the engine quantizing the channel to 8
	// bits, so the byte a decimal alpha lands on is the one it already stores —
	// `.5`, `.501`, `.502` and `.5019` all compute to `rgba(0,0,0,0.5)` in headless
	// Chromium, and every one of the 256 bytes round-trips.
	if (alpha < 1) {
		return hexAlpha
			? _shortestAlphaColor(
					channels[0],
					channels[1],
					channels[2],
					_clamp255(alpha * 255)
				)
			: null;
	}
	return _shortestColor(channels[0], channels[1], channels[2]);
};

// WHY: a browser list's parse, and each list's per-property prefix decision,
// memoized on the list: one process minifies for one target, so the work is done
// once and every later lookup is a `Map.get`. A build minifies every asset
// against one browserslist selection, but in a worker each asset arrives with a
// freshly deserialized array — so the memo is keyed on the joined text, and
// holds one selection at a time.
/** @type {string[] | null} */
let _browsersArray = null;
/** @type {string | null} */
let _browsersKey = null;
/** @type {(number[] | undefined)[] | null} */
let _parsedBrowsersMemo = null;
// Whether the selection named a browser no compat dataset answers for, which
// is what a rewrite taking a declaration away has to decline on.
let _someBrowserUnanswered = false;
let _someBrowserUnansweredMemo = false;
// The prefixes those browsers read, by their engines; null for every prefix.
/** @type {Set<string> | null} */
let _unstatedPrefixes = null;
/** @type {Set<string> | null} */
let _unstatedPrefixesMemo = null;
// Each axis' table -> its per-construct decision for the selection in hand. Kept
// per table rather than under one tagged key, so a lookup allocates no string.
/** @type {Map<Map<string, [string, number][]>, Map<string, Set<string> | null>>} */
let _neededPrefixMemo = new Map();

// Where a browser's version sits in a `getSupportProfiles()` row, which is also the
// slot a prefix window names it by and the one the parsed selection holds it in.
// Built from the order the rows are stated in, so the three cannot drift apart.
/** @type {Map<string, number>} */
const SUPPORT_BROWSER_SLOT = new Map(
	SUPPORT_BROWSERS.map((browser, at) => [browser, at])
);

/**
 * Parse a browserslist selection into the versions selected for each browser
 * slot, memoized on the selection itself, and make it the one this parse
 * prefixes for. Null where it resolves to no browser at all, which is not a
 * target to prefix for.
 * @param {string[]} browsers the browserslist selection
 * @returns {void}
 */
const _useBrowsers = (browsers) => {
	// The same selection usually arrives as the same array — one resolution per
	// target serves the whole build — so the text it joins to is read only where
	// it does not, which is a worker handed each asset's own copy.
	if (browsers === _browsersArray) {
		_prefixBrowsers = _parsedBrowsersMemo;
		_someBrowserUnanswered = _someBrowserUnansweredMemo;
		return;
	}
	_browsersArray = browsers;
	const key = browsers.join(",");
	if (key !== _browsersKey) {
		_browsersKey = key;
		_neededPrefixMemo = new Map();
		// By the slot the tables name a browser with, so neither the profile rows
		// nor the prefix windows carry a name to look one up by.
		/** @type {(number[] | undefined)[]} */
		const parsed = Array.from({ length: SUPPORT_BROWSERS.length });
		let named = 0;
		let unanswered = false;
		/** @type {Set<string> | null} */
		let unstatedPrefixes = new Set();
		for (const entry of browsers) {
			const space = entry.indexOf(" ");
			const name = space === -1 ? entry : entry.slice(0, space);
			const version =
				space === -1 ? null : _encodeBrowserVersion(entry.slice(space + 1));
			// A browserslist name no compat dataset covers, or a version that did
			// not parse, is skipped — the same browsers lightningcss's target
			// mapping drops, so both minifiers prefix for the same selection.

			// WHY: noted rather than only skipped, because the two readers of this
			// want opposite answers for a browser nothing states. Reaching for a
			// spelling it may not read costs it nothing, so prefixing goes on
			// without it; dropping the declaration before one takes away the only
			// spelling it could have read, so `_valueReadByTarget` declines for the
			// whole selection instead.
			const slot =
				version === null ? undefined : SUPPORT_BROWSER_SLOT.get(name);
			if (slot === undefined) {
				const engine = UNSTATED_BROWSER_PREFIXES.get(name);
				// Node renders no CSS, so it is no target a stylesheet answers to.
				if (engine === null) continue;
				unanswered = true;
				if (engine === undefined) unstatedPrefixes = null;
				else if (unstatedPrefixes !== null) unstatedPrefixes.add(engine);
				continue;
			}
			// Every selected version is kept: a prefix window is an interval, so an
			// older selection outside it does not answer for a newer one inside.
			const versions = parsed[slot];
			if (versions === undefined) {
				parsed[slot] = [/** @type {number} */ (version)];
				named++;
			} else {
				versions.push(/** @type {number} */ (version));
			}
		}
		// Only browsers nothing states is still a target, which reads nothing known.
		_parsedBrowsersMemo = named === 0 && !unanswered ? null : parsed;
		_someBrowserUnansweredMemo = unanswered;
		_unstatedPrefixesMemo = unstatedPrefixes;
	}
	_prefixBrowsers = _parsedBrowsersMemo;
	_someBrowserUnanswered = _someBrowserUnansweredMemo;
	_unstatedPrefixes = _unstatedPrefixesMemo;
};

/**
 * Whether a target browser the tables do not state reads a vendor prefix, which
 * only its own engine's is.
 * @param {string} prefix the prefix, `-ms-` and the like
 * @returns {boolean} true when such a browser may read it
 */
const _unstatedReads = (prefix) =>
	_someBrowserUnanswered &&
	(_unstatedPrefixes === null || _unstatedPrefixes.has(prefix));

/**
 * A browserslist version to the same `major * 100000 + minor` integer the table
 * is keyed in. A range (`10.0-10.2`) takes its low end, Safari `TP` is newest,
 * and `all` (Opera Mini) is oldest (`0`).
 * @param {string} version the version part of a `"name version"` entry
 * @returns {number | null} the encoded version, or null when unreadable
 */
const _encodeBrowserVersion = (version) => {
	// Newer than any real version, but below `NEVER` — a still-prefixed feature
	// carries `to === NEVER`, and `TP < NEVER` must hold for TP to need it.
	if (version === "TP") return NEVER - 1;
	if (version === "all") return 0;
	const dash = version.indexOf("-");
	const low = dash === -1 ? version : version.slice(0, dash);
	const dot = low.indexOf(".");
	const major = Number.parseInt(dot === -1 ? low : low.slice(0, dot), 10);
	if (Number.isNaN(major)) return null;
	const minor = dot === -1 ? 0 : Number.parseInt(low.slice(dot + 1), 10) || 0;
	return major * 100000 + minor;
};

// `getValueSupportPacked()` unpacked, on the first lookup a build makes: only a
// build with a target and two declarations of one property asks.
/** @type {Map<string, number> | null} */
let _valueSupport = null;

/**
 * When every target browser first read one value, by the spelling
 * `getValueSupportPacked()` names it: `"<property> <keyword>"` for a keyword a
 * property's own grammar states, the bare name for a color function.
 * @param {string} key the spelling
 * @returns {number | undefined} its `getSupportProfiles()` row, or undefined
 */
const _valueSupportedFrom = (key) => {
	let table = _valueSupport;
	if (table === null) {
		table = new Map();
		for (const group of getValueSupportPacked().split("|")) {
			const colon = group.indexOf(":");
			const property = group.slice(0, colon);
			const prefix = property.length === 0 ? "" : `${property} `;
			const entries = group.slice(colon + 1).split(" ");
			for (let at = 0; at < entries.length; at += 2) {
				table.set(prefix + entries[at], Number(entries[at + 1]));
			}
		}
		_valueSupport = table;
	}
	return table.get(key);
};

/**
 * Whether every target browser reads a CSS ability, so a spelling that needs it
 * may be reached for. No selection names no browser to answer for, so the
 * ability is assumed — a build with no browserslist keeps every rewrite. A
 * browser the table does not name is one nothing states support for, which
 * answers no.
 * @param {string} feature a `SUPPORTED_FROM` key
 * @returns {boolean} true when the selection reads it
 */
const _targetSupports = (feature) =>
	_prefixBrowsers === null || _readsAll(SUPPORTED_FROM.get(feature));

/**
 * Whether every target browser is at or past the versions in one support
 * profile, named by the row it reads.
 * @param {number | undefined} profile a `getSupportProfiles()` index
 * @returns {boolean} true when the selection reads it
 */
const _readsAll = (profile) =>
	_prefixBrowsers === null ||
	(!_someBrowserUnanswered && _readsAllStated(profile));

/**
 * Whether every target browser the tables state is at or past one support
 * profile, which is what a lowering that only adds readers asks.
 * @param {number | undefined} profile a `getSupportProfiles()` index
 * @returns {boolean} true when every stated browser reads it
 */
const _readsAllStated = (profile) => {
	const parsed = _prefixBrowsers;
	if (parsed === null) return true;
	if (profile === undefined) return false;
	// One row of `SUPPORT_BROWSERS.length` versions, laid end to end with the rest.
	const row = profile * SUPPORT_BROWSERS.length;
	const profiles = getSupportProfiles();
	// A selection of only browsers nothing states vouches for nothing.
	let stated = false;
	for (let at = 0; at < parsed.length; at++) {
		const versions = parsed[at];
		if (versions === undefined) continue;
		stated = true;
		const from = profiles[row + at];
		for (let i = 0; i < versions.length; i++) {
			if (versions[i] < from) return false;
		}
	}
	return stated;
};

// The browsers standing for the three engines that ship today.
const _ENGINE_BROWSERS = ["chrome", "firefox", "safari"].map((name) =>
	SUPPORT_BROWSERS.indexOf(name)
);

/**
 * Whether each engine shipping today reads an ability in some release, which is
 * what a selector needs where no selection names the browsers to answer for.
 * @param {number} profile its `getSupportProfiles()` row
 * @returns {boolean} true when Chromium, Gecko and WebKit all read it
 */
const _readByEveryEngine = (profile) => {
	const row = profile * SUPPORT_BROWSERS.length;
	const profiles = getSupportProfiles();
	for (const at of _ENGINE_BROWSERS) {
		if (profiles[row + at] === NEVER) return false;
	}
	return true;
};

/**
 * Whether the targets read an ability: the selection's browsers, or with none
 * each engine shipping today, which is what `dropOverriddenDeclarations` assumes.
 * @param {number | undefined} profile its `getSupportProfiles()` row
 * @returns {boolean} true when the later value it names is read
 */
const _readsOverridingValue = (profile) =>
	_prefixBrowsers === null
		? profile !== undefined && _readByEveryEngine(profile)
		: _readsAll(profile);

/**
 * The vendor spellings at least one target browser still needs for a construct —
 * a target at version V needs spelling S when some `[browser, from, to]` of S has
 * `from <= V < to`. Null when not minifying for a target (leave prefixes alone),
 * the construct is never prefixed, or no target needs any of its spellings.
 * @param {Map<string, [string, number][]>} table its axis' prefix table
 * @param {string} name the construct name (unprefixed, lowercased)
 * @returns {Set<string> | null} the needed spellings, or null
 */
const _neededPrefixes = (table, name) => {
	const parsed = _prefixBrowsers;
	if (parsed === null) return null;
	let cache = _neededPrefixMemo.get(table);
	if (cache === undefined) {
		cache = new Map();
		_neededPrefixMemo.set(table, cache);
	}
	const cached = cache.get(name);
	if (cached !== undefined) return cached;
	const prefixes = table.get(name);
	/** @type {Set<string> | null} */
	let result = null;
	if (prefixes !== undefined) {
		const needed = new Set();
		const prefixWindows = getPrefixWindows();
		for (const [prefix, windows] of prefixes) {
			// The list's own `browser, from, to` triples, in the flat table the
			// starts index into.
			const end = PREFIX_WINDOW_STARTS[windows + 1];
			for (let at = PREFIX_WINDOW_STARTS[windows]; at < end; at += 3) {
				const versions = parsed[prefixWindows[at]];
				if (versions === undefined) continue;
				const from = prefixWindows[at + 1];
				const to = prefixWindows[at + 2];
				let hit = false;
				for (let i = 0; i < versions.length; i++) {
					if (versions[i] >= from && versions[i] < to) {
						hit = true;
						break;
					}
				}
				if (hit) {
					needed.add(prefix);
					break;
				}
			}
		}
		if (needed.size !== 0) result = needed;
	}
	cache.set(name, result);
	return result;
};

// A vendor prefix at the start of a name (`-webkit-`, `-moz-`, `-ms-`, `-o-`,
// `-khtml-`), captured so it can be split from the construct it sits on.
const VENDOR_PREFIX = /^(-[a-z]+-)(?=[a-z])/;

// Whether each prefix is one the selection still reads — kept until the
// selection changes rather than answered per rule.
/** @type {Map<string, boolean>} */
const _prefixLiveMemo = new Map();
/** @type {(number[] | undefined)[] | null} */
let _prefixLiveFor = null;

/**
 * Whether a construct's `[browser, from, to]` windows hold one the selection
 * sits inside.
 * @param {number} windows the windows' index into `PREFIX_WINDOW_STARTS`
 * @param {(number[] | undefined)[]} parsed the selected versions, per browser slot
 * @returns {boolean} true when a selected version falls in one of them
 */
const _windowsHitSelection = (windows, parsed) => {
	const prefixWindows = getPrefixWindows();
	const end = PREFIX_WINDOW_STARTS[windows + 1];
	for (let at = PREFIX_WINDOW_STARTS[windows]; at < end; at += 3) {
		const versions = parsed[prefixWindows[at]];
		if (versions === undefined) continue;
		const from = prefixWindows[at + 1];
		const to = prefixWindows[at + 2];
		for (let i = 0; i < versions.length; i++) {
			if (versions[i] >= from && versions[i] < to) return true;
		}
	}
	return false;
};

/**
 * Whether one prefix table names a spelling carrying `prefix` that the
 * selection still needs.
 * @param {Map<string, [string, number][]>} table a prefix table
 * @param {string} prefix the prefix, `-ms-` and the like
 * @param {(number[] | undefined)[]} parsed the selected versions, per browser slot
 * @returns {boolean} true when one of its spellings is still needed
 */
const _tableNeedsPrefix = (table, prefix, parsed) => {
	for (const spellings of table.values()) {
		for (const [spelling, windows] of spellings) {
			if (
				spelling.startsWith(prefix) &&
				_windowsHitSelection(windows, parsed)
			) {
				return true;
			}
		}
	}
	return false;
};

/**
 * Whether any target reads a vendor prefix at all. Asked of the tables as a
 * whole rather than of one name, so it answers for a proprietary pseudo none of
 * them names: a prefix nothing in the selection needs is one whose engine the
 * selection has left behind.
 * @param {string} prefix the prefix, `-ms-` and the like
 * @returns {boolean} true when a target still reads it
 */
const _prefixIsLive = (prefix) => {
	const parsed = _prefixBrowsers;
	// Told no target, every prefix is assumed read; so is one an engine the
	// tables do not state reads.
	if (parsed === null || _unstatedReads(prefix)) return true;
	if (_prefixLiveFor !== parsed) {
		_prefixLiveMemo.clear();
		_prefixLiveFor = parsed;
	}
	const cached = _prefixLiveMemo.get(prefix);
	if (cached !== undefined) return cached;
	// Every prefix the data names sits on some property, selector or at-rule, so
	// the value table — held one level deeper — would answer for none of its own.
	let live = false;
	for (const table of [
		getPrefixedProperties(),
		PREFIXED_SELECTORS,
		PREFIXED_AT_RULES
	]) {
		if (_tableNeedsPrefix(table, prefix, parsed)) {
			live = true;
			break;
		}
	}
	_prefixLiveMemo.set(prefix, live);
	return live;
};

/**
 * The index of the `)` closing the `(` at `at`, stepping over an escape, a
 * quoted run and a nested pair. The end of the text where it closes nowhere,
 * which a parsed selector never does — an unclosed function is closed for it.
 * @param {string} text the text to read
 * @param {number} at the index of the opening parenthesis
 * @returns {number} where the list it opens ends
 */
const _closingParenthesis = (text, at) => {
	let depth = 0;
	let i = at;
	for (; i < text.length; i++) {
		const code = text.charCodeAt(i);
		if (code === CC_REVERSE_SOLIDUS) {
			i++;
		} else if (code === CC_QUOTATION_MARK || code === CC_APOSTROPHE) {
			for (i++; i < text.length; i++) {
				const inner = text.charCodeAt(i);
				if (inner === CC_REVERSE_SOLIDUS) i++;
				else if (inner === code) break;
			}
		} else if (code === CC_LEFT_PARENTHESIS) {
			depth++;
		} else if (code === CC_RIGHT_PARENTHESIS && --depth === 0) {
			break;
		}
	}
	return i;
};

/**
 * Whether a selector can only be read through a vendor pseudo whose engine the
 * selection has left behind. An escape and a quoted attribute value are stepped
 * over: the `:` of `.sm\:flex` and of `[href="a:b"]` starts no pseudo.
 * @param {string} selector one printed selector
 * @returns {boolean} true when no target can read it
 */
const _needsGoneEnginePseudo = (selector) => {
	let found = false;
	for (let i = 0; i < selector.length; i++) {
		const code = selector.charCodeAt(i);
		if (code === CC_REVERSE_SOLIDUS) {
			i++;
			continue;
		}
		if (code === CC_QUOTATION_MARK || code === CC_APOSTROPHE) {
			for (i++; i < selector.length; i++) {
				const inner = selector.charCodeAt(i);
				if (inner === CC_REVERSE_SOLIDUS) i++;
				else if (inner === code) break;
			}
			continue;
		}
		if (code !== CC_COLON) continue;
		let end = i + 1;
		if (selector.charCodeAt(end) === CC_COLON) end++;
		const from = end;
		while (
			end < selector.length &&
			_isIdentCodePoint(selector.charCodeAt(end))
		) {
			end++;
		}
		const name = toLowerCaseIfNeeded(selector.slice(from, end));
		if (
			(name === "is" || name === "where") &&
			selector.charCodeAt(end) === CC_LEFT_PARENTHESIS
		) {
			// A forgiving list keeps every argument it can parse, so it matches
			// nothing only where all of them are dead — and the rest is still read.
			const close = _closingParenthesis(selector, end);
			if (_everyOneGone(selector.slice(end + 1, close))) found = true;
			i = close;
			continue;
		}
		const prefix = VENDOR_PREFIX.exec(name);
		if (prefix !== null && !_prefixIsLive(prefix[1])) found = true;
		i = end - 1;
	}
	return found;
};

/**
 * Whether every selector of a list is one no target can read. Asked of a rule's
 * own prelude and of a forgiving list's arguments alike, which is why it takes
 * the list rather than the rule.
 * @param {string} list a printed selector list
 * @returns {boolean} true when none of them can be read
 */
const _everyOneGone = (list) => {
	for (const one of _splitSelectorList(list)) {
		if (!_needsGoneEnginePseudo(one)) return false;
	}
	return true;
};

/**
 * Whether a whole selector list is one no target can read, which is a rule that
 * paints nothing anywhere it is served. Only a list every selector of which is
 * dead: an engine drops the rule over one selector it cannot parse, so a list
 * holding a live one already matches nothing extra, and taking the dead one out
 * would start it matching.
 * @param {string} list a printed selector list
 * @returns {boolean} true when the rule may go
 */
const _readsThroughGoneEngine = (list) => {
	// A vendor pseudo is a `:` a `-` follows, so a list without that pair holds
	// none — which is nearly every list, and the walk is skipped for them.
	if (!_S._transforms.removeDeadRules || !list.includes(":-")) return false;
	return _everyOneGone(list);
};

/**
 * Whether a present vendor-spelled property is dead weight beside its standard
 * twin. A 2009 flexbox property is not where its block lays out by that draft,
 * which reads it in place of the twin.
 * @param {string} base the standard property
 * @param {string} spelling the vendor-spelled one
 * @returns {boolean} true when it can be dropped
 */
const _prefixedPropertyRemovable = (base, spelling) =>
	spelling === _DECORATION_SPELLING
		? _textDecorationColorStyleAllowed && _textDecorationThicknessAllowed
		: !(_legacyBoxBlock && LEGACY_BOX_SPELLINGS.has(spelling)) &&
			_prefixRemovable(getPrefixedProperties(), base, spelling);

// WHY: Safari read `text-decoration`'s style, color and thickness through this
// spelling alone before 26.2. BCD files that prefix under the sub-feature, so the
// property table holds none, and the spelling goes with the feature's own row.
const _DECORATION_SPELLING = "-webkit-text-decoration";

/**
 * Whether a present vendor-spelled construct is dead weight: its spelling is one
 * the table knows for the base construct and no target browser needs it, so an
 * unprefixed sibling already covers every target.
 * @param {Map<string, [string, number][]>} table its axis' prefix table
 * @param {string} base the unprefixed construct name
 * @param {string} spelling the vendor spelling found in its place
 * @returns {boolean} true when the vendor-spelled construct can be dropped
 */
const _prefixRemovable = (table, base, spelling) => {
	// A browser nothing states may be the one still reading the vendor spelling.
	if (_someBrowserUnanswered) {
		const vendor = VENDOR_PREFIX.exec(spelling);
		if (vendor === null || _unstatedReads(vendor[1])) return false;
	}
	const spellings = table.get(base);
	if (spellings === undefined) return false;
	if (!spellings.some(([known]) => known === spelling)) return false;
	const needed = _neededPrefixes(table, base);
	return needed === null || !needed.has(spelling);
};

/**
 * What a block's rules have shown so far: the signatures met (`seen`), and the
 * prefixed ones an unprefixed twin would make dead — as an output piece the
 * writer can take back (`retractable`, for a rule written straight out), or as
 * the node itself (`pending`, for a rule whose parent still has to assemble it).
 * @typedef {object} PrefixScope
 * @property {Set<string>} seen the signatures met, and the `signature\0prefix`
 * markers of the prefixed spellings among them
 * @property {Map<string, number[]> | null} retractable each dead-if-twinned
 * rule's piece index, by the signature of the twin that would make it dead
 * @property {Map<string, Node[]> | null} pending each dead-if-twinned nested
 * rule, by that same signature
 * @property {Set<Node> | null} dead the nested rules a twin has since made dead,
 * read by their parent as it assembles its body
 * @property {{ missing: Set<string>, piece: number, node: Node | null }[] | null}
 * covering each prefixed list still short of a twin for some of its selectors,
 * with the signatures it is still waiting for
 */

/**
 * The rules already met in the block a rule sits in, made on first use.
 * @param {Node | null} parent the block's rule, null for the stylesheet itself
 * @returns {PrefixScope} the block's running sibling state
 */
const _prefixScope = (parent) => {
	const scopes = /** @type {Map<Node | null, PrefixScope>} */ (
		_seenPrefixRules
	);
	let scope = scopes.get(parent);
	if (scope === undefined) {
		scope = {
			seen: new Set(),
			retractable: null,
			pending: null,
			dead: null,
			covering: null
		};
		scopes.set(parent, scope);
	}
	return scope;
};

/**
 * Add one held twin to its signature's list, made on first use. A block may
 * write several prefixed spellings of the same rule, and one twin kills them all.
 * @template T
 * @param {Map<string, T[]>} held every held twin, by signature
 * @param {string} signature the rule's sibling signature
 * @param {T} value the piece index or node to hold
 * @returns {void}
 */
const _holdBySignature = (held, signature, value) => {
	const list = held.get(signature);
	if (list === undefined) held.set(signature, [value]);
	else list.push(value);
};

// A property's vendor spelling back to the property it stands for, built once
// from the forward table. Read rather than derived, since an engine as often
// renamed a property as prefixed it, and only a prefix strips back off.
/** @type {Map<string, string> | null} */
let _prefixedPropertyNames = null;
/**
 * @param {string} property a declaration's property name
 * @returns {string | undefined} the property it is a vendor spelling of
 */
const _prefixedPropertyName = (property) => {
	if (property === _DECORATION_SPELLING) return "text-decoration";
	if (_prefixedPropertyNames === null) {
		_prefixedPropertyNames = new Map();
		for (const [base, spellings] of getPrefixedProperties()) {
			for (const [spelling] of spellings) {
				_prefixedPropertyNames.set(spelling, base);
			}
		}
	}
	return _prefixedPropertyNames.get(property);
};

// A number with no sign, unit or exponent: what a legacy spelling reading one
// is written with, and what an offset can be added to exactly.
const _UNSIGNED_NUMBER_REGEXP = /^(?:\d+\.?\d*|\.\d+)$/;

/**
 * One prefixed copy of a declaration, for a spelling a target still needs. A
 * spelling whose older property read other keywords or a number carries them
 * with it, and writes nothing for a value that property cannot read.
 * @param {string} spelling the vendor spelling to write
 * @param {string} text the declaration's printed text
 * @param {number} colon where its `:` sits
 * @returns {string} the copy, empty where none can be written
 */
const _prefixedDeclaration = (spelling, text, colon) => {
	const keywords = getPrefixedSpellingKeywords().get(spelling);
	if (keywords === undefined) return spelling + text.slice(colon);
	const end = _printedValueEnd(text, colon);
	const value = toLowerCaseIfNeeded(text.slice(colon + 1, end));
	// A CSS-wide keyword is every property's, whatever its own grammar says.
	if (CSS_WIDE_KEYWORDS.has(value)) return spelling + text.slice(colon);
	let legacy = keywords.get(value);
	if (legacy === undefined) {
		const offset = PREFIXED_SPELLING_NUMBERS.get(spelling);
		if (offset === undefined) return "";
		const first = value.split(" ", 1)[0];
		if (!_UNSIGNED_NUMBER_REGEXP.test(first)) return "";
		legacy = offset === 0 ? first : `${Number(first) + offset}`;
	}
	// `!important` and the `;` carry over; the value between them is rewritten.
	return `${spelling}:${legacy}${text.slice(end)}`;
};

// WHY: the shape of every name some engine spells its own way — one bit per
// length, under the pair of letters it starts with. A declaration whose first
// two letters and length are not among those can be neither prefixed nor
// value-spelled, so its name is never read, which covers most of them,
// `content`, `opacity` and `margin-left` among the commonest. 676 slots, filled
// the first time a parse prefixes for a target and kept after.
/** @type {Int32Array | null} */
let _prefixableNameShapes = null;

/**
 * @returns {Int32Array} the lengths, by the two letters a name starts with
 */
const _prefixableNames = () => {
	if (_prefixableNameShapes === null) {
		const shapes = new Int32Array(26 * 26);
		for (const table of [getPrefixedProperties(), getPrefixedValues()]) {
			for (const name of table.keys()) {
				const slot = _nameShapeSlot(name.charCodeAt(0), name.charCodeAt(1));
				if (slot === -1) continue;
				shapes[slot] |= 1 << (name.length > 31 ? 31 : name.length);
			}
		}
		_prefixableNameShapes = shapes;
	}
	return _prefixableNameShapes;
};

/**
 * The shape table's slot for a name's first two characters, or `-1` where they
 * are not both ASCII letters. Names match ASCII case-insensitively, so `A`-`Z`
 * reads as its lowercase.
 * @param {number} first the first character's code
 * @param {number} second the second character's code
 * @returns {number} the slot, or -1
 */
const _nameShapeSlot = (first, second) => {
	const letter = (first | 0x20) - CC_LOWER_A;
	if (letter < 0 || letter > 25) return -1;
	const next = (second | 0x20) - CC_LOWER_A;
	return next < 0 || next > 25 ? -1 : letter * 26 + next;
};

/**
 * Whether a printed declaration's name is worth reading — one some engine
 * spells its own way, or a spelling itself.
 * @param {string} text the declaration's printed text
 * @param {number} colon where its `:` sits
 * @returns {boolean} true when the name may take part in prefixing
 */
const _mayPrefixDeclaration = (text, colon) => {
	// A spelling to drop leads with `-`; a custom property leads with `--` and is
	// neither prefixed nor spelled.
	if (text.charCodeAt(0) === CC_HYPHEN_MINUS) {
		return text.charCodeAt(1) !== CC_HYPHEN_MINUS;
	}
	const slot = _nameShapeSlot(text.charCodeAt(0), text.charCodeAt(1));
	if (slot === -1) return false;
	const lengths = _prefixableNames();
	return ((lengths[slot] >>> (colon < 0 || colon > 31 ? 31 : colon)) & 1) !== 0;
};

/**
 * The property a printed declaration sets, read from its text: a merged box
 * shorthand writes a different property than the node it is stored on.
 * @param {string} text the declaration's printed text
 * @param {number} colon where its `:` sits
 * @returns {string} the lowercased property name
 */
const _printedProperty = (text, colon) =>
	toLowerCaseIfNeeded(colon === -1 ? text : text.slice(0, colon));

// What a printed declaration carries after its value: the `;` closing it, and
// the `!important` before that where it has one (minifying leaves no space).
const _IMPORTANT = "!important";

/**
 * The value a printed declaration sets, read from its text — the whole value, so
 * only a declaration that is one keyword matches a keyword.
 * @param {string} text the declaration's printed text
 * @param {number} colon where its `:` sits
 * @returns {string} the lowercased value
 */
const _printedValue = (text, colon) =>
	toLowerCaseIfNeeded(text.slice(colon + 1, _printedValueEnd(text, colon)));

/**
 * A comma list's top-level items. What a later declaration has to write again for
 * an earlier one to be dead rather than the fallback an engine reads instead.
 * @param {string} value one printed declaration value
 * @returns {string[]} its items, in order
 */
const _valueItems = (value) => {
	/** @type {string[]} */
	const out = [];
	let depth = 0;
	let quote = 0;
	let start = 0;
	for (let i = 0; i <= value.length; i++) {
		const cc = i === value.length ? CC_COMMA : value.charCodeAt(i);
		if (quote !== 0) {
			if (cc === CC_REVERSE_SOLIDUS) i++;
			else if (cc === quote) quote = 0;
			continue;
		}
		if (cc === CC_QUOTATION_MARK || cc === CC_APOSTROPHE) {
			quote = cc;
		} else if (cc === CC_LEFT_PARENTHESIS) {
			depth++;
		} else if (cc === CC_RIGHT_PARENTHESIS) {
			depth--;
		} else if (cc === CC_COMMA && depth === 0) {
			out.push(value.slice(start, i).trim());
			start = i + 1;
		}
	}
	return out;
};

// A vendor prefix on an item's first token, which is the name slot a
// `<custom-ident>` list reads there.
const _VENDOR_ITEM_PREFIX_RE = /^-[a-z]+-/i;

// One value written as a single number, with the unit it carries: what a
// stylesheet says where a property takes one measure and nothing else.
const _LONE_NUMBER_RE = /^[+-]?(?:\d+\.?\d*|\.\d+)(%|[a-z]+)?$/i;

// ...and one written as a hex color, by how many digits it takes: three and six
// are CSS 1's, four and eight state an alpha, which is CSS Color 4's.
const _LONE_HEX_RE = /^#(?:[\da-f]{3}|[\da-f]{4}|[\da-f]{6}|[\da-f]{8})$/i;

/**
 * Whether a later declaration of the same property leaves an earlier one
 * unreadable rather than standing as its fallback — where the two are spelled
 * from one vocabulary, so no engine reads one and not the other.
 *
 * That is what parts an override from a fallback, and it is a question about the
 * spellings rather than about the target: `width:100px` before `width:50%` is a
 * pair an engine reading percentages resolves the same way it resolves any
 * other, while `color:#eee` before `color:lab(…)` is the older color standing
 * where the newer one is not read. `inherit` is the one keyword every engine has
 * taken since CSS 2, so nothing stands behind it.
 * @param {string} later the later declaration's printed value
 * @param {string} earlier the earlier one's
 * @returns {boolean} true when the earlier one can no longer be read
 */
const _sameVocabulary = (later, earlier) => {
	if (asciiCaseInsensitiveMatch(later, "inherit")) return true;
	const laterNumber = _LONE_NUMBER_RE.exec(later);
	if (laterNumber !== null) {
		const earlierNumber = _LONE_NUMBER_RE.exec(earlier);
		return (
			earlierNumber !== null &&
			// A unit an engine may not know is one the earlier states as well, so
			// an engine reading that one reads this.
			asciiCaseInsensitiveMatch(laterNumber[1] || "", earlierNumber[1] || "")
		);
	}
	// A pair of hexes, on one side of the alpha four digits and eight state and
	// the other two do not — and one with no alpha is read wherever a name is.
	if (_LONE_HEX_RE.test(later)) {
		const alpha = later.length === 5 || later.length === 9;
		// An alpha every target reads is read wherever the earlier color is.
		const read =
			!alpha ||
			(_prefixBrowsers !== null &&
				!_someBrowserUnanswered &&
				_S._hexAlphaAllowed);
		if (_LONE_HEX_RE.test(earlier)) {
			return read || alpha === (earlier.length === 5 || earlier.length === 9);
		}
		const earlierName = toLowerCaseIfNeeded(earlier);
		return (
			read &&
			getColorNameToRgb().has(earlierName) &&
			!LATER_COLOR_NAMES.has(earlierName)
		);
	}
	// ...or a color named where the earlier names or spells one. Only a name as old
	// as naming a color — a later one is read by engines the earlier is not.
	const named = toLowerCaseIfNeeded(later);
	return (
		getColorNameToRgb().has(named) &&
		!LATER_COLOR_NAMES.has(named) &&
		(_LONE_HEX_RE.test(earlier) ||
			getColorNameToRgb().has(toLowerCaseIfNeeded(earlier)))
	);
};

/**
 * Whether a later declaration of a `<custom-ident>` list leaves an earlier one
 * unreadable rather than standing as its fallback: every item the earlier writes
 * is written again, and every item the later adds is one of them under another
 * vendor spelling. A name is what that slot takes, so an engine knowing none of
 * the spellings still parses the value — there is nothing to fall back to.
 * `transition:box-shadow .25s` before `transition:box-shadow .25s,-webkit-box-shadow .25s`
 * is the shape, which is what a tool adding prefixes writes.
 * @param {string} later the later declaration's printed value
 * @param {string} earlier the earlier declaration's printed value
 * @returns {boolean} true when the earlier one can no longer be read
 */
const _coveredByLater = (later, earlier) => {
	const laterItems = _valueItems(later);
	const earlierItems = _valueItems(earlier);
	if (earlierItems.length === 0 || laterItems.length < earlierItems.length) {
		return false;
	}
	for (const item of earlierItems) {
		if (!laterItems.includes(item)) return false;
	}
	for (const item of laterItems) {
		if (earlierItems.includes(item)) continue;
		const bare = item.replace(_VENDOR_ITEM_PREFIX_RE, "");
		let variant = false;
		for (const other of earlierItems) {
			if (other.replace(_VENDOR_ITEM_PREFIX_RE, "") === bare) {
				variant = true;
				break;
			}
		}
		if (!variant) return false;
	}
	return true;
};

/**
 * Where a printed declaration's value ends: before the `!important` it may carry
 * and the `;` closing it.
 * @param {string} text the declaration's printed text
 * @param {number} colon where its `:` sits
 * @returns {number} the offset one past the value
 */
const _printedValueEnd = (text, colon) => {
	let end = text.length;
	if (text.charCodeAt(end - 1) === CC_SEMICOLON) end--;
	const bang = end - _IMPORTANT.length;
	if (bang > colon && text.startsWith(_IMPORTANT, bang)) end = bang;
	return end;
};

// A property's vendor value spellings back to the keyword each stands for, built
// on first use from the forward table.
/** @type {Map<Map<string, [string, number][]>, Map<string, string>>} */
const _prefixedValueKeywords = new Map();
/**
 * @param {Map<string, [string, number][]>} table one property's value table
 * @param {string} value a value that may be a vendor spelling
 * @returns {string | undefined} the keyword it spells, when it is one
 */
const _prefixedValueKeyword = (table, value) => {
	let reverse = _prefixedValueKeywords.get(table);
	if (reverse === undefined) {
		reverse = new Map();
		for (const [keyword, spellings] of table) {
			for (const [spelling] of spellings) reverse.set(spelling, keyword);
		}
		_prefixedValueKeywords.set(table, reverse);
	}
	return reverse.get(value);
};

// A plain number, optionally a percentage — the only argument form these color
// conversions are proven for.
const _COLOR_NUMBER_RE = /^([+-]?(?:\d+\.?\d*|\.\d+))(%?)$/;
// The hue's default unit; the other angle units would need their own conversion
// before the same boundary test could be applied.
const _COLOR_HUE_RE = /^([+-]?(?:\d+\.?\d*|\.\d+))(?:deg)?$/i;

// WHY: how far a channel must sit from a `.5` rounding boundary to be
// converted. Implementations only ever disagree at a tie: across 317520 `hsl()`
// and `hwb()` samples checked against headless Chromium every divergence was
// within 9.24e-14 of a boundary, and none of the 264084 converted outside it was
// wrong. The margin is orders above that and far below a visible difference.
const _ROUNDING_MARGIN = 1e-6;

// A conversion that runs the sRGB transfer needs a wider one: an engine's
// transfer is a fitted approximation of the spec's curve rather than the curve —
// Chromium's sits up to 1.01e-4 above it, in encoded units.
const _TRANSFER_ERROR = 1.65e-4;

// WHY: one that runs a matrix as well carries that approximation into every term
// of the product, so what it costs is relative to the color's own magnitude: up
// to 9.53e-4 of it, across every space and the Lab family.
//
// Both are `yarn measure:color-agreement`, which asks an engine for its own
// conversion rather than for a pixel, times the 1.6x that tool states, since
// only the engines it runs against have been measured. Each is the widest a run
// has reported rather than the last one's, so re-running it for a newer Chromium
// widens them and never narrows them.
const _CONVERSION_ERROR = 1.52e-3;

// How far two hues may sit from a half turn apart and still be read as one: a
// color reaches its own space through sRGB, and the trip back moves the hue by
// far less than this.
const _HUE_TIE = 1e-6;

// The sRGB transfer's own knee (CSS Color 4 §10.2), below which its slope is the
// constant one.
const _TRANSFER_KNEE = 0.0031308;

/**
 * How far a converted channel must sit from a `.5` boundary for the byte to be
 * the one an engine's own conversion lands on too. A difference in linear light
 * is worth whatever the transfer's slope makes of it, which is a sixteenth of a
 * byte at white and more than a whole byte near black.
 * @param {number} raw the channel, 0..255
 * @param {number} conversion what the value passed through, as `getColorSpaceModel()` states it
 * @param {number} scale the largest linear-light channel of the color it belongs to
 * @returns {number} the margin, in bytes
 */
const _conversionMargin = (raw, conversion, scale) => {
	// A space an engine reads through a different transfer altogether is not a
	// margin away from agreeing, so nothing is converted out of one.
	if (conversion === ENGINE_TRANSFER_DIFFERS) return Number.POSITIVE_INFINITY;
	if (conversion === ENCODED_ALREADY) return _ROUNDING_MARGIN;
	const transfer = 255 * _TRANSFER_ERROR;
	if (conversion === THROUGH_TRANSFER) return transfer;
	const linear = Math.abs(_gammaDecode(raw / 255));
	const slope =
		linear <= _TRANSFER_KNEE ? 12.92 : (1.055 / 2.4) * linear ** (1 / 2.4 - 1);
	return transfer + 255 * slope * _CONVERSION_ERROR * scale;
};

/**
 * Round a raw 0..255 channel, or return null when it sits close enough to a `.5`
 * boundary that another implementation's last bit could round it the other way.
 * @param {number} raw the channel, 0..255
 * @param {number} margin how far from a boundary the channel must sit, in bytes
 * @returns {number | null} the byte, or null to keep the function
 */
const _channelByte = (raw, margin) => {
	// WHY: past this the color is one sRGB cannot show, so clamping it into a hex
	// would pick a different color rather than respell the same one. Inside it,
	// clamping lands on the byte rounding would have picked anyway — which is what
	// lets a Lab white point a few 1e-6 past 255 still convert.
	if (raw < -0.5 || raw > 255.5) return null;
	// Rounding is monotone, so the whole interval is one byte exactly when its two
	// ends are — which past either end of the range they are however wide it is,
	// since both clamp to the same byte there.
	const low = _clamp255(raw - margin);
	return low === _clamp255(raw + margin) ? low : null;
};

/**
 * CSS Color 4 §7.1's `hsl()` -> sRGB, on 0..1 inputs, in 0..255 output.
 * @param {number} h hue in degrees, already wrapped to 0..360
 * @param {number} s saturation 0..1
 * @param {number} l lightness 0..1
 * @returns {number[]} the raw `[r, g, b]` channels, 0..255
 */
const _hslToRgb = (h, s, l) => {
	const a = s * Math.min(l, 1 - l);
	return [0, 8, 4].map((n) => {
		const k = (n + h / 30) % 12;
		return (l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1))) * 255;
	});
};

/**
 * CSS Color 4 §7.2's `hwb()` -> sRGB. Whiteness and blackness summing to 1 or
 * more is the achromatic case the spec spells out separately.
 * @param {number} h hue in degrees, already wrapped to 0..360
 * @param {number} w whiteness 0..1
 * @param {number} b blackness 0..1
 * @returns {number[]} the raw `[r, g, b]` channels, 0..255
 */
const _hwbToRgb = (h, w, b) => {
	if (w + b >= 1) {
		const gray = (w / (w + b)) * 255;
		return [gray, gray, gray];
	}
	return _hslToRgb(h, 1, 0.5).map(
		(channel) => (channel / 255) * (1 - w - b) * 255 + w * 255
	);
};

/**
 * Linear-light sRGB -> the gamma-encoded channel CSS serializes (CSS Color 4
 * §10.2). Out-of-gamut input is returned as-is so the caller's range check sees
 * it rather than a clipped value.
 * @param {number} c one linear component
 * @returns {number} the encoded component, nominally 0..1
 */
const _gammaEncode = (c) => {
	const sign = c < 0 ? -1 : 1;
	const abs = Math.abs(c);
	return abs > 0.0031308
		? sign * (1.055 * abs ** (1 / 2.4) - 0.055)
		: 12.92 * c;
};

// The D50 white point Lab is defined against (CSS Color 4 §12), as XYZ.
const _D50 = [0.3457 / 0.3585, 1, (1 - 0.3457 - 0.3585) / 0.3585];

// Bradford-adapted D50 to D65, then XYZ to linear-light sRGB — the two steps CSS
// Color 4's sample code uses rather than one composed matrix, whose composition
// loses enough precision to push `lab(100% 0 0)` outside the gamut check.
const _D50_TO_D65 = [
	[0.9554734527042182, -0.023098536874261423, 0.0632593086610217],
	[-0.028369706963208136, 1.0099954580058226, 0.021041398966943008],
	[0.012314001688319899, -0.020507696433477912, 1.3303659366080753]
];
const _XYZ_TO_LINEAR_SRGB = [
	[3.2409699419045226, -1.537383177570094, -0.4986107602930034],
	[-0.9692436362808796, 1.8759675015077202, 0.04155505740717559],
	[0.05563007969699366, -0.20397695888897652, 1.0569715142428786]
];

/**
 * @param {number[][]} matrix a 3x3 matrix
 * @param {number[]} vector the column it multiplies
 * @returns {number[]} the product
 */
const _multiply3 = (matrix, vector) =>
	matrix.map(
		(row) => row[0] * vector[0] + row[1] * vector[1] + row[2] * vector[2]
	);

/**
 * CIE Lab -> linear-light sRGB: Lab -> XYZ (D50) -> XYZ (D65) -> linear sRGB,
 * with the matrices and constants CSS Color 4 §12 gives.
 * @param {number} l lightness 0..100
 * @param {number} a the a axis
 * @param {number} b the b axis
 * @returns {number[]} the linear `[r, g, b]` components, nominally 0..1
 */
const _labToLinearSrgb = (l, a, b) => {
	const e = 216 / 24389;
	const k = 24389 / 27;
	const fy = (l + 16) / 116;
	const fx = a / 500 + fy;
	const fz = fy - b / 200;
	const xyz = [
		(fx ** 3 > e ? fx ** 3 : (116 * fx - 16) / k) * _D50[0],
		(l > k * e ? fy ** 3 : l / k) * _D50[1],
		(fz ** 3 > e ? fz ** 3 : (116 * fz - 16) / k) * _D50[2]
	];
	return _multiply3(_XYZ_TO_LINEAR_SRGB, _multiply3(_D50_TO_D65, xyz));
};

/**
 * Oklab -> linear-light sRGB (CSS Color 4 §9.2's matrices).
 * @param {number} l lightness 0..1
 * @param {number} a the a axis
 * @param {number} b the b axis
 * @returns {number[]} the linear `[r, g, b]` components, nominally 0..1
 */
const _oklabToLinearSrgb = (l, a, b) => {
	const lp = l + 0.3963377773761749 * a + 0.2158037573099136 * b;
	const mp = l - 0.1055613458156586 * a - 0.0638541728258133 * b;
	const sp = l - 0.0894841775298119 * a - 1.2914855480194092 * b;
	const lc = lp ** 3;
	const mc = mp ** 3;
	const sc = sp ** 3;
	return [
		4.076741661347994 * lc - 3.307711590408193 * mc + 0.230969928729428 * sc,
		-1.2684380040921763 * lc +
			2.6097574006633715 * mc -
			0.3413193963102197 * sc,
		-0.004196086541837188 * lc -
			0.7034186144594493 * mc +
			1.7076147009309444 * sc
	];
};

/**
 * The gamma-encoded channel back to linear light — `_gammaEncode` read the other
 * way, so a color already converted into sRGB can be carried on into a wider
 * gamut without being parsed a second time.
 * @param {number} c one encoded component, nominally 0..1
 * @returns {number} the linear component
 */
const _gammaDecode = (c) => {
	const sign = c < 0 ? -1 : 1;
	const abs = Math.abs(c);
	return abs <= 0.04045 ? c / 12.92 : sign * ((abs + 0.055) / 1.055) ** 2.4;
};

// One component of a `color()`: a number, a percentage of the space's range, or
// `none`, which computes as zero wherever it is read as a number.
const _COLOR_COMPONENT_RE = /^([+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)(%?)$/i;

/**
 * The sRGB a `color()` in a predefined space names, as raw `[r, g, b]` channels
 * 0..255 before rounding, and its alpha. Null when it is no such color — a
 * custom `--space`, a component holding a substitution, the wrong count.
 * @param {string} inner the already-joined argument text
 * @returns {{ raw: number[], alpha: number, conversion: number } | null} the channels, what reaching them passed through, or null
 */
const _predefinedColorChannels = (inner) => {
	// `color(<space> c1 c2 c3[ / <alpha>])`: the alpha stands after the grammar's
	// one slash and nowhere else, and a comma separates nothing here. Reading
	// either as whitespace paints a declaration the engine drops.
	if (inner.includes(",")) return null;
	const sides = inner.split("/");
	if (sides.length > 2) return null;
	const args = sides[0].split(/\s+/).filter((part) => part.length !== 0);
	if (args.length !== 4) return null;
	const space = PREDEFINED_COLOR_SPACES.get(asciiLowercaseName(args[0]));
	if (space === undefined) return null;
	let alpha = 1;
	if (sides.length === 2) {
		const written = sides[1].trim();
		const parsed = _COLOR_NUMBER_RE.exec(written);
		if (parsed === null || parsed[0] !== written) return null;
		alpha = Number(parsed[1]) / (parsed[2] === "%" ? 100 : 1);
		if (!(alpha >= 0 && alpha <= 1)) return null;
	}
	/** @type {number[]} */
	const stored = [];
	for (let i = 1; i <= 3; i++) {
		if (asciiCaseInsensitiveMatch(args[i], "none")) {
			stored.push(0);
			continue;
		}
		const parsed = _COLOR_COMPONENT_RE.exec(args[i]);
		if (parsed === null) return null;
		stored.push(Number(parsed[1]) / (parsed[2] === "%" ? 100 : 1));
	}
	const linear = stored.map((component) => space.transfer(component));
	const matrix = space.toSrgb;
	const raw = [0, 3, 6].map(
		(row) =>
			_gammaEncode(
				matrix[row] * linear[0] +
					matrix[row + 1] * linear[1] +
					matrix[row + 2] * linear[2]
			) * 255
	);
	return { raw, alpha, conversion: space.conversion };
};

/**
 * The `color(display-p3 …)` a set of sRGB channels names, for a color sRGB
 * cannot show and a target that reads the wider gamut. Null where P3 cannot
 * show it either, which is a color no fallback preserves.
 * @param {number[]} raw the sRGB channels, 0..255 before rounding
 * @param {number} alpha the alpha, 0..1
 * @returns {string | null} the call, or null
 */
const _wideGamutColor = (raw, alpha) => {
	const linear = raw.map((channel) => _gammaDecode(channel / 255));
	/** @type {string[]} */
	const parts = [];
	for (const row of [0, 3, 6]) {
		const component = _gammaEncode(
			LINEAR_SRGB_TO_P3[row] * linear[0] +
				LINEAR_SRGB_TO_P3[row + 1] * linear[1] +
				LINEAR_SRGB_TO_P3[row + 2] * linear[2]
		);
		// Past the gamut P3 shows either, so the hex before it is the whole answer.
		if (!(component >= -1e-6 && component <= 1 + 1e-6)) return null;
		parts.push(_roundColorComponent(component));
	}
	// Quantized before it is written: an alpha the engine stores as `255` is the
	// opaque color, which carries no alpha at all.
	const byte = _clamp255(alpha * 255);
	const opacity = byte === 255 ? "" : `/${_alphaByteText(byte)}`;
	return `color(display-p3 ${parts.join(" ")}${opacity})`;
};

// A wide-gamut component prints at the six significant digits a stylesheet can
// observe, with the leading zero and any trailing zeros dropped.
/**
 * @param {number} component one component, 0..1
 * @returns {string} its shortest spelling
 */
const _roundColorComponent = (component) => {
	// The caller admits a component a whole 1e-6 outside the gamut, which is the
	// only reason this clamps at all.
	const clamped = Math.min(1, Math.max(0, component));
	const text = clamped.toPrecision(6).replace(/0+$/, "").replace(/\.$/, "");
	return text.startsWith("0.") ? text.slice(1) : text;
};

/**
 * The `<percentage>` that stands for 100% of each Lab-family axis (CSS Color 4
 * §9, §12), as `[lightness, axis]`.
 * @type {Map<string, number[]>}
 */
const _LAB_SCALES = new Map([
	["lab", [100, 125]],
	["lch", [100, 150]],
	["oklab", [1, 0.4]],
	["oklch", [1, 0.4]]
]);

// One argument of a color function: a run between the whitespace, `,` and `/`
// its grammar separates on.
const _COLOR_ARGUMENT_RE = /[^\s,/]+/g;
// The two separator grammars a color function states: the modern `A B C` with
// an optional `/ D`, and the legacy `A, B, C` with an optional `, D`.
const _COLOR_MODERN_RE =
	/^\s*[^\s,/]+(?:\s+[^\s,/]+){2}(?:\s*\/\s*[^\s,/]+)?\s*$/;
const _COLOR_LEGACY_RE = /^\s*[^\s,/]+(?:\s*,\s*[^\s,/]+){2,3}\s*$/;

/**
 * Minify `hsl()` / `hwb()` / `lab()` / `lch()` / `oklab()` / `oklch()` to the
 * shortest hex.
 *
 * Two guards keep the rewrite from changing the color. A channel landing within
 * `_ROUNDING_MARGIN` of a `.5` boundary keeps the function, because that is the
 * only place implementations disagree — it is why esbuild and lightningcss emit
 * different bytes for `hwb(194 0% 0%)`. And a Lab-family color outside the sRGB
 * gamut keeps its function too: hex cannot express it, so converting would clip
 * it to a different color. Both are limitations, not correctness gaps, and
 * `convertApproximateColors` lifts them: the first to the byte other minifiers
 * write anyway, the second past where any of them goes.
 *
 * Both guards are about *replacing* the function. A fallback stands before the
 * function rather than in its place, so it is only ever read by an engine with
 * no better answer: `forFallback` clamps into the gamut and takes whichever byte
 * rounding picks, and writes an `rgba()` where the target has no hex alpha.
 * @param {string} fn the lowercased function name
 * @param {string} inner the already-joined argument text
 * @param {boolean} hexAlpha whether the target reads a 4-/8-digit hex (see `output.environment`)
 * @param {boolean=} forFallback whether this is the fallback for a function the target cannot read, rather than a replacement for one it can
 * @returns {string | null} the shortest color, or null to keep the function
 */
const _minifyPolarColorFunction = (
	fn,
	inner,
	hexAlpha,
	forFallback = false
) => {
	if (!forFallback && !_S._transforms.shortenColors) return null;
	if (fn !== "color-mix" && !_RELATIVE_CHANNELS.has(fn)) return null;
	const isHsl = fn === "hsl" || fn === "hsla";
	const labScale = _LAB_SCALES.get(fn);
	// A predefined space states its components directly, so it is read whole
	// rather than argument by argument — the rest of this then treats it as any
	// other conversion's output.
	if (fn === "color-mix") {
		return _foldColorMix(inner, hexAlpha, forFallback);
	}
	// A relative color states its origin first, whichever function names it.
	if (_RELATIVE_FROM_RE.test(inner)) {
		return _foldRelativeColor(fn, inner, hexAlpha, forFallback);
	}
	if (fn === "color") {
		const read = _predefinedColorChannels(inner);
		if (read === null) return null;
		return _colorFromChannels(
			read.raw,
			read.alpha,
			read.conversion,
			hexAlpha,
			forFallback
		);
	}
	if (!isHsl && fn !== "hwb" && labScale === undefined) return null;
	// The runs between the separators these calls allow, read out directly: the
	// replace/split/filter chain built a copy of the arguments and two arrays to
	// reach the same few strings.
	const args = inner.match(_COLOR_ARGUMENT_RE);
	if (args === null || (args.length !== 3 && args.length !== 4)) return null;
	// WHY: the read above states the runs between the separators without saying
	// which separator stood there, and CSS Color 4 gives `hsl()` alone a legacy
	// comma grammar — `hwb()` and the Lab family were specified later and take
	// the modern form only. A mixed spelling such as `hsl(120, 50% 50%)` matches
	// neither, so folding it would hand a color to a declaration engines drop.
	const legacy = _COLOR_LEGACY_RE.test(inner);
	if (legacy ? !isHsl : !_COLOR_MODERN_RE.test(inner)) return null;
	let alpha = 1;
	if (args.length === 4) {
		const parsed = _COLOR_NUMBER_RE.exec(args[3]);
		if (parsed === null) return null;
		alpha = Number(parsed[1]) / (parsed[2] === "%" ? 100 : 1);
		if (!(alpha >= 0 && alpha <= 1)) return null;
		// A partly transparent color needs the hex-alpha spelling, which is the
		// target's question, not this one's — a fallback answers it with `rgba()`.
		if (alpha < 1 && !hexAlpha && !forFallback) return null;
	}
	/** @type {number[]} */
	let raw;
	if (isHsl || fn === "hwb") {
		const hue = _COLOR_HUE_RE.exec(args[0]);
		const first = _COLOR_NUMBER_RE.exec(args[1]);
		const second = _COLOR_NUMBER_RE.exec(args[2]);
		if (hue === null || first === null || second === null) return null;
		// WHY: CSS Color 4 admits a bare `<number>` for these two channels in the
		// modern space-separated grammar alone — the legacy comma form takes
		// percentages only. Folding `hsl(120, 50, 50)` would hand invalid CSS a
		// color, so the grammar decides whether a missing `%` is readable.
		if (legacy && (first[2] !== "%" || second[2] !== "%")) return null;
		const x = Number(first[1]) / 100;
		const y = Number(second[1]) / 100;
		if (x < 0 || x > 1 || y < 0 || y > 1) return null;
		let h = Number(hue[1]) % 360;
		if (h < 0) h += 360;
		raw = isHsl ? _hslToRgb(h, x, y) : _hwbToRgb(h, x, y);
	} else {
		const scale = /** @type {number[]} */ (labScale);
		const isPolar = fn === "lch" || fn === "oklch";
		const lightness = _COLOR_NUMBER_RE.exec(args[0]);
		const second = _COLOR_NUMBER_RE.exec(args[1]);
		const third = isPolar
			? _COLOR_HUE_RE.exec(args[2])
			: _COLOR_NUMBER_RE.exec(args[2]);
		if (lightness === null || second === null || third === null) return null;
		const l =
			Number(lightness[1]) * (lightness[2] === "%" ? scale[0] / 100 : 1);
		let a;
		let b;
		if (isPolar) {
			const chroma =
				Number(second[1]) * (second[2] === "%" ? scale[1] / 100 : 1);
			const hue = (Number(third[1]) * Math.PI) / 180;
			a = chroma * Math.cos(hue);
			b = chroma * Math.sin(hue);
		} else {
			a = Number(second[1]) * (second[2] === "%" ? scale[1] / 100 : 1);
			b = Number(third[1]) * (third[2] === "%" ? scale[1] / 100 : 1);
		}
		const linear =
			fn === "lab" || fn === "lch"
				? _labToLinearSrgb(l, a, b)
				: _oklabToLinearSrgb(l, a, b);
		// WHY: a hex mixed in `oklch` reaches it through a conversion, and Chromium
		// reads a hue there as missing below a chroma of 0.02 — so `color-mix(in
		// oklch, currentColor 35%, #0000)` paints hue 0 where `oklch()` named one.
		if (fn === "oklch" && (a !== 0 || b !== 0)) {
			const space = getColorSpaceModel().get(fn);
			const clamped = linear.map((c) => Math.min(1, Math.max(0, c)));
			if (
				space !== undefined &&
				space.uncertainHue !== null &&
				space.uncertainHue(space.to(clamped))
			) {
				return null;
			}
		}
		// `_channelByte` decides the gamut from the encoded value, where the
		// question is whether clamping would move the byte.
		raw = linear.map((c) => _gammaEncode(c) * 255);
	}
	// `hsl()` and `hwb()` are the sRGB byte written another way round; the Lab
	// family is a matrix chain away from it.
	const conversion = labScale === undefined ? ENCODED_ALREADY : THROUGH_MATRIX;
	return _colorFromChannels(raw, alpha, conversion, hexAlpha, forFallback);
};

// The sRGB channels the last fallback conversion produced, before rounding, and
// whether clamping them into the gamut moved one — read by the wider-gamut
// fallback, which is only worth writing for a color sRGB cannot show.
/** @type {number[] | null} */
let _fallbackChannels = null;
let _fallbackAlpha = 1;
let _fallbackClipped = false;
// ...and what the conversion that produced them passed through, which a mix or a
// relative color reading this origin inherits.
let _fallbackConversion = ENCODED_ALREADY;

/**
 * The shortest text for raw sRGB channels and an alpha, or null to keep the
 * function they came from. Shared by every conversion above: what differs
 * between them is how the channels were reached, not how they are written.
 * @param {number[]} raw the channels, 0..255 before rounding
 * @param {number} alpha the alpha, 0..1
 * @param {number} conversion what reaching the channels passed through, as `getColorSpaceModel()` states it
 * @param {boolean} hexAlpha whether the target reads a 4-/8-digit hex
 * @param {boolean} forFallback whether this stands before the function rather than in its place
 * @returns {string | null} the color, or null
 */
const _colorFromChannels = (raw, alpha, conversion, hexAlpha, forFallback) => {
	/** @type {number[]} */
	const channels = [];
	let clipped = false;
	// A space read through a transfer of the engine's own is not a margin away
	// from agreeing, so the option that takes the nearest byte anyway does not
	// reach it: the hex would name a color the engine never paints.
	const approximate =
		_convertApproximateColors && conversion !== ENGINE_TRANSFER_DIFFERS;
	// The color's own magnitude, which the conversion's error is relative to.
	const scale = Math.max(
		...raw.map((value) => Math.abs(_gammaDecode(value / 255)))
	);
	for (const value of raw) {
		const byte =
			forFallback || approximate
				? _clamp255(value)
				: _channelByte(value, _conversionMargin(value, conversion, scale));
		if (byte === null) return null;
		if (Math.abs(byte - value) > 0.5) clipped = true;
		channels.push(byte);
	}
	if (forFallback) {
		_fallbackChannels = raw;
		_fallbackAlpha = alpha;
		_fallbackClipped = clipped;
		_fallbackConversion = conversion;
	}
	// The alpha is the author's own number rather than a conversion's output, so it
	// takes no boundary guard: the engine quantizes it to 8 bits as `rgba()`'s is,
	// and one quantizing to `255` is opaque, written with no alpha at all.
	const opacity = alpha < 1 ? _clamp255(alpha * 255) : 255;
	if (opacity !== 255) {
		return hexAlpha
			? _shortestAlphaColor(channels[0], channels[1], channels[2], opacity)
			: `rgba(${channels[0]},${channels[1]},${channels[2]},${_alphaByteText(
					opacity
				)})`;
	}
	return _shortestColor(channels[0], channels[1], channels[2]);
};

/**
 * Whether a color is a gray, which is the one color a polar space states no hue
 * for — an engine reads that component as missing rather than as a number. The
 * test is proportional: a gray reached through a matrix carries the product's
 * own last bits rather than three identical doubles.
 * @param {number[]} linear the color in linear-light sRGB
 * @returns {boolean} true when all three channels are the same
 */
const _isGray = (linear) => {
	const low = Math.min(linear[0], linear[1], linear[2]);
	const high = Math.max(linear[0], linear[1], linear[2]);
	return high - low <= Math.abs(high) * 1e-9 + 1e-12;
};

/**
 * Whether a color is gray only as far as its own conversion can tell: its
 * channels sit within the spread that trip could have invented. The hue such a
 * color carries is the residue's rather than the color's, so no two engines
 * name the same one.
 * @param {{ linear: number[], conversion: number }} color a resolved color
 * @returns {boolean} true where the hue rests on the conversion's last bits
 */
const _isResidualGray = (color) => {
	// WHY: a chroma of zero in Lab or Oklch is neutral in that space and reaches
	// sRGB a residue away from neutral — measured against Chromium 141, up to
	// 9.5e-5 of a channel, where webpack's own lands 1.97e-8 away. Neither is a
	// color anyone can see, but a hue interpolates at full weight however small
	// the chroma stating it, so the two part by 25 bytes on the mix.
	const raw = color.linear.map((channel) => _gammaEncode(channel) * 255);
	const scale = Math.max(
		Math.abs(color.linear[0]),
		Math.abs(color.linear[1]),
		Math.abs(color.linear[2])
	);
	const high = Math.max(raw[0], raw[1], raw[2]);
	const low = Math.min(raw[0], raw[1], raw[2]);
	return (
		high - low <=
		_conversionMargin(high, color.conversion, scale) +
			_conversionMargin(low, color.conversion, scale)
	);
};

/**
 * A color written in the space it was computed in — the answer for one no sRGB
 * byte holds, or one whose byte two engines could split over. Only ever the
 * spelling every color that went into it was already written in, so the numbers
 * here are the ones an engine reaches too: nothing was converted to get them.
 * @param {{ open: string, scale: number[], percent: boolean[], feature: string }} written how the space is written
 * @param {number} hue which component is an angle, `-1` for none
 * @param {number[]} components the color's components in that space
 * @param {number} alpha its alpha
 * @returns {string | null} the color, or null where the target does not read the spelling
 */
const _spaceColorText = (written, hue, components, alpha) => {
	if (written.feature !== "" && !_targetSupports(written.feature)) return null;
	const parts = components.map((value, at) => {
		// A component this small is zero at any precision a color is written to;
		// what is left of one is the polar round trip's own last bits.
		const held = Math.abs(value) < 1e-6 ? 0 : value;
		// A hue is an angle, so it says the same thing wrapped into one turn.
		const wrapped = at === hue ? ((held % 360) + 360) % 360 : held;
		const text = _roundSignificant(
			_normalizeNumber(String(wrapped * written.scale[at]))
		);
		return written.percent[at] ? `${text}%` : text;
	});
	const opacity =
		alpha < 1 ? `/${_roundSignificant(_normalizeNumber(String(alpha)))}` : "";
	return `${written.open}${parts.join(" ")}${opacity})`;
};

// The channels each color function names, in order, and the scale a relative
// color reads them at — `hsl()` states its saturation as a percentage, so the
// number bound to `s` counts to 100 where the model counts to 1.
/** @type {Map<string, { channels: string[], scale: number[] }>} */
const _RELATIVE_CHANNELS = new Map([
	["rgb", { channels: ["r", "g", "b"], scale: [255, 255, 255] }],
	["rgba", { channels: ["r", "g", "b"], scale: [255, 255, 255] }],
	["hsl", { channels: ["h", "s", "l"], scale: [1, 100, 100] }],
	["hsla", { channels: ["h", "s", "l"], scale: [1, 100, 100] }],
	["hwb", { channels: ["h", "w", "b"], scale: [1, 100, 100] }],
	["lab", { channels: ["l", "a", "b"], scale: [1, 1, 1] }],
	["lch", { channels: ["l", "c", "h"], scale: [1, 1, 1] }],
	["oklab", { channels: ["l", "a", "b"], scale: [1, 1, 1] }],
	["oklch", { channels: ["l", "c", "h"], scale: [1, 1, 1] }],
	["color", { channels: ["r", "g", "b"], scale: [1, 1, 1] }]
]);

// A color a mix or a relative reference resolves against holds no `none` (whose
// value is the other color's) and no reference of its own.
const _UNRESOLVED_COLOR_RE = /(^|[^-\w])(none|currentcolor|from)(?![-\w])/i;

/**
 * One color, whatever it is written as, in linear-light sRGB with its alpha.
 * Null for anything this cannot pin down — a substitution, `currentcolor`, a
 * `none` component, a relative reference of its own.
 * @param {string} text the color as printed
 * @returns {{ linear: number[], alpha: number, conversion: number, space: string } | null} the color, what reading it passed through, the space it was written in, or null
 */
const _resolveColor = (text) => {
	if (_UNRESOLVED_COLOR_RE.test(text) || containsSubstitutionFunction(text)) {
		return null;
	}
	/** @type {number[] | null} */
	let bytes = null;
	let alpha = 1;
	if (text.charCodeAt(0) === CC_NUMBER_SIGN) {
		bytes = _hexBytes(text.slice(1));
		if (bytes === null) return null;
		alpha = bytes.length === 4 ? bytes[3] / 255 : 1;
	} else if (_IDENT_RE.test(text)) {
		const lower = toLowerCaseIfNeeded(text);
		if (lower === "transparent") {
			return {
				linear: [0, 0, 0],
				alpha: 0,
				conversion: ENCODED_ALREADY,
				space: SRGB_SPACE
			};
		}
		const packed = getColorNameToRgb().get(lower);
		if (packed === undefined) return null;
		bytes = [(packed >> 16) & 255, (packed >> 8) & 255, packed & 255];
	} else {
		const open = text.indexOf("(");
		if (open === -1 || !text.endsWith(")")) return null;
		const fn = asciiLowercaseName(text.slice(0, open));
		const inner = text.slice(open + 1, -1);
		// Every function form reaches sRGB through the same conversions the
		// printer already makes, which leave the raw channels behind. Cleared
		// first, so a call that answers without recording any is not read as one.
		_fallbackChannels = null;
		// The space it was written in, which is `color()`'s first argument or the
		// function's own name — `rgb()` states the byte itself, as a hex does.
		const written =
			fn === "color"
				? asciiLowercaseName(_splitTopLevelSpaces(inner)[0] || "")
				: fn === "rgb" || fn === "rgba"
					? SRGB_SPACE
					: fn.replace(/a$/, "");
		if (
			_minifyColorFunction(fn, inner, true, false) === null &&
			_minifyPolarColorFunction(fn, inner, true, true) === null
		) {
			return null;
		}
		// Every call that answers records them, so a non-null answer is one.
		const raw = /** @type {number[]} */ (
			/** @type {number[] | null} */ (_fallbackChannels)
		);
		return {
			linear: raw.map((channel) => _gammaDecode(channel / 255)),
			alpha: _fallbackAlpha,
			conversion: _fallbackConversion,
			space: written
		};
	}
	return {
		linear: bytes.slice(0, 3).map((byte) => _gammaDecode(byte / 255)),
		alpha,
		conversion: ENCODED_ALREADY,
		space: SRGB_SPACE
	};
};

/**
 * A hex color's bytes, three or four of them.
 * @param {string} body the digits after the `#`
 * @returns {number[] | null} the bytes, or null when it is no hex color
 */
const _hexBytes = (body) => {
	const n = body.length;
	if (n !== 3 && n !== 4 && n !== 6 && n !== 8) return null;
	for (let i = 0; i < n; i++) {
		if (!isHexDigit(body.charCodeAt(i))) return null;
	}
	const wide = n >= 6;
	const count = wide ? n / 2 : n;
	/** @type {number[]} */
	const out = [];
	for (let i = 0; i < count; i++) {
		const digits = wide ? body.slice(i * 2, i * 2 + 2) : body[i] + body[i];
		out.push(Number.parseInt(digits, 16));
	}
	return out;
};

/**
 * The two hue angles a `<hue-interpolation-method>` interpolates between: the
 * method decides which way round the circle the second one is reached from the
 * first (CSS Color 4 §12.4).
 * @param {number} from the first hue, in degrees
 * @param {number} to the second
 * @returns {number[] | null} the pair to interpolate, or null for an unknown method
 */
const _hueEndpoints = (from, to) => {
	let difference = to - from;
	// WHY: both are wrapped to 0..360, so the shorter arc is the one under a half
	// turn, which is what every method is stated as a correction to. Two opposite
	// hues have no shorter arc and CSS Color 4 §12.4 breaks that tie by which is
	// greater, so the arc is kept as it stands: a half turn the conversion left a
	// hair over is not read as the arc the other way round.
	if (_hueMethod === "" || _hueMethod === "shorter") {
		if (difference > 180 + _HUE_TIE) difference -= 360;
		else if (difference < -180 - _HUE_TIE) difference += 360;
	} else if (_hueMethod === "longer") {
		if (difference > -180 && difference < 180) {
			difference += difference > 0 ? -360 : 360;
		}
	} else if (_hueMethod === "increasing") {
		if (difference < 0) difference += 360;
	} else if (_hueMethod === "decreasing") {
		if (difference > 0) difference -= 360;
	} else {
		return null;
	}
	return [from, from + difference];
};

// The hue interpolation method the mix being read names, or `""`.
let _hueMethod = "";

/**
 * Fold a `color-mix()` to the one color it names (CSS Color 5 §3): each color
 * read into the interpolation space, weighted by its own percentage with the
 * alphas premultiplied, and read back out. Null wherever a component cannot be
 * pinned down, which leaves the call written out.
 * @param {string} inner the already-joined argument text
 * @param {boolean} hexAlpha whether the target reads a 4-/8-digit hex
 * @param {boolean} forFallback whether this stands before the call rather than in its place
 * @returns {string | null} the color, or null
 */
const _foldColorMix = (inner, hexAlpha, forFallback) => {
	const args = _splitTopLevelArguments(inner);
	if (args.length !== 3) return null;
	const head = _splitTopLevelSpaces(args[0]);
	if (head.length < 2 || !asciiCaseInsensitiveMatch(head[0], "in")) return null;
	const space = getColorSpaceModel().get(asciiLowercaseName(head[1]));
	if (space === undefined) return null;
	// `in <space> <method> hue` — the two words after the space, or nothing. Held
	// locally until both colors are resolved: a nested mix folds through here too,
	// and would otherwise leave its own method behind for this one to read.
	let hueMethod = "";
	if (head.length === 4) {
		if (!asciiCaseInsensitiveMatch(head[3], "hue")) return null;
		hueMethod = asciiLowercaseName(head[2]);
	} else if (head.length !== 2) {
		return null;
	}
	/** @type {number[]} */
	const weights = [];
	/** @type {{ linear: number[], alpha: number, conversion: number, space: string }[]} */
	const colors = [];
	for (let i = 1; i <= 2; i++) {
		const parts = _splitTopLevelSpaces(args[i]);
		if (parts.length > 2) return null;
		let percent = -1;
		let text = parts[0];
		if (parts.length === 2) {
			// The percentage may be written either side of the color.
			const at = parts[1].endsWith("%") ? 1 : 0;
			const written = _COLOR_NUMBER_RE.exec(parts[at]);
			if (written === null || written[2] !== "%") return null;
			percent = Number(written[1]);
			// `<percentage [0,100]>` (CSS Color 5 §2.1): one outside the range is no
			// mix for an engine to compute, whatever the other says.
			if (!(percent >= 0 && percent <= 100)) return null;
			text = parts[at === 0 ? 1 : 0];
		}
		const color = _resolveColor(text);
		if (color === null) return null;
		weights.push(percent);
		colors.push(color);
	}
	// An omitted percentage is what the other leaves; both omitted is an even mix.
	if (weights[0] < 0 && weights[1] < 0) weights[0] = weights[1] = 50;
	else if (weights[0] < 0) weights[0] = 100 - weights[1];
	else if (weights[1] < 0) weights[1] = 100 - weights[0];
	const sum = weights[0] + weights[1];
	if (sum === 0) return null;
	// A pair summing under 100 leaves the result that much transparent; over 100
	// it is only the ratio that counts.
	const carried = sum < 100 ? sum / 100 : 1;
	const first = weights[0] / sum;
	const alpha =
		(colors[0].alpha * first + colors[1].alpha * (1 - first)) * carried;
	const components = [colors[0], colors[1]].map((color) =>
		space.to(color.linear)
	);
	_hueMethod = hueMethod;
	// A gray has no hue to interpolate, so an engine reads that component as
	// missing and takes the other color's instead of mixing the two (CSS Color 4
	// §12.2) — which is a different color, not a rounding away from one.
	if (space.hue !== -1) {
		const gray = colors.map((color) => _isGray(color.linear));
		for (let at = 0; at < 2; at++) {
			// ...unless it was written in this very space, where the hue it states
			// stands however gray the color is, and reaching it through sRGB is what
			// lost it.
			if (gray[at] && colors[at].space === asciiLowercaseName(head[1])) {
				return null;
			}
			// ...and a hue an engine may read as missing before the color is gray at
			// all is one no answer can be given for.
			if (
				!gray[at] &&
				space.uncertainHue !== null &&
				space.uncertainHue(components[at])
			) {
				return null;
			}
			// ...as is one a color states only through its conversion's residue,
			// which every engine leaves in a different place.
			if (!gray[at] && _isResidualGray(colors[at])) return null;
		}
		if (gray[0] !== gray[1]) {
			const stated = gray[0] ? 1 : 0;
			// WHY: a gray written in a polar space states a hue the engines split
			// over. Measured on `color-mix(in hsl, hwb(322.26 56.57% 49.83%) 7.3%,
			// oklch(.280497 .052616 353.043))`: Chrome 147 reads the hue as missing
			// and takes the other color's, painting 68,35,50, where Firefox 156
			// keeps the 322.26 it states and paints 68,35,56.
			const written = getColorSpaceModel().get(colors[1 - stated].space);
			if (written !== undefined && written.hue !== -1) return null;
			components[1 - stated][space.hue] = components[stated][space.hue];
		}
	}
	/** @type {number[]} */
	const mixed = [];
	for (let at = 0; at < 3; at++) {
		if (at === space.hue) {
			const ends = _hueEndpoints(components[0][at], components[1][at]);
			if (ends === null) return null;
			mixed.push(ends[0] * first + ends[1] * (1 - first));
			continue;
		}
		// Premultiplied, so a transparent color contributes none of its own hue.
		const one = components[0][at] * colors[0].alpha;
		const other = components[1][at] * colors[1].alpha;
		const carriedAlpha =
			colors[0].alpha * first + colors[1].alpha * (1 - first);
		const value = one * first + other * (1 - first);
		mixed.push(carriedAlpha === 0 ? 0 : value / carriedAlpha);
	}
	const raw = space.from(mixed).map((channel) => _gammaEncode(channel) * 255);
	// The mix is only as exact as the space it was made in and the colors that
	// went into it: an origin written in Lab carries that chain into an `in srgb`
	// mix as surely as the mix's own space would.
	const conversion = Math.max(
		space.conversion,
		colors[0].conversion,
		colors[1].conversion
	);
	const byte = _colorFromChannels(
		raw,
		alpha,
		conversion,
		hexAlpha,
		forFallback
	);
	if (byte !== null || forFallback) return byte;
	// No byte, the color being outside sRGB or its rounding one an engine could go
	// either way on. Where both colors were written in the space they were mixed
	// in, the mix converted nothing and can be written there.
	const name = asciiLowercaseName(head[1]);
	if (colors[0].space !== name || colors[1].space !== name) return null;
	return _spaceColorText(space.written, space.hue, mixed, alpha);
};

/**
 * The number a relative color's channel argument names, with the origin's own
 * channels bound to their keywords. A keyword stands for its channel, a plain
 * number for itself, and a `calc()` for whatever it reduces to once the
 * keywords in it are the numbers they name.
 * @param {string} text the argument as printed
 * @param {Map<string, number>} bound the origin's channels, by keyword
 * @param {boolean} angle whether the channel is an angle, where `deg` is its unit
 * @returns {number | null} the value, or null when it cannot be pinned down
 */
const _relativeChannel = (text, bound, angle) => {
	const direct = bound.get(toLowerCaseIfNeeded(text));
	if (direct !== undefined) return direct;
	const plain = _COLOR_NUMBER_RE.exec(text);
	if (plain !== null) {
		if (plain[2] === "%") return null;
		return Number(plain[1]);
	}
	if (angle) {
		const hue = _COLOR_HUE_RE.exec(text);
		if (hue !== null) return Number(hue[1]);
	}
	const open = text.indexOf("(");
	if (open === -1 || !text.endsWith(")")) return null;
	if (!MATH_FUNCTIONS.has(asciiLowercaseName(text.slice(0, open)))) return null;
	// The keywords are substituted as the numbers they name, so the expression
	// the engine would compute is the one the existing evaluator reduces.
	let refused = false;
	const written = text.replace(_CHANNEL_KEYWORD_RE, (whole, before, name) => {
		const value = bound.get(toLowerCaseIfNeeded(name));
		if (value === undefined) {
			refused = true;
			return whole;
		}
		return `${before}${value}`;
	});
	if (refused) return null;
	const tokens = _tokenizeCalc(written.slice(open + 1, -1));
	if (tokens === null) return null;
	const cursor = { at: 0 };
	const sum = _evaluateCalcSum(tokens, cursor);
	if (sum === null || cursor.at !== tokens.length || sum.size !== 1) {
		return null;
	}
	const only = [...sum];
	// A channel keyword substitutes as the number it names (CSS Color 5 §4.1), so
	// an expression over one reduces to no unit, which a hue slot reads as degrees.
	// An angle stands there too, and a sum of the two is refused above.
	const unit = only[0][0];
	if (angle ? unit !== "" && unit !== "deg" : unit !== "") return null;
	return only[0][1];
};

// One channel keyword inside a relative color's argument: a bare word no
// identifier character precedes, so a unit or a function name is never one.
const _CHANNEL_KEYWORD_RE = /(^|[^-\w.])([a-z]+)(?![-\w(])/gi;

/**
 * Fold a relative color — `rgb(from <color> r calc(g * 2) b)` — to the color it
 * names (CSS Color 5 §4): the origin read into the function's own channels, each
 * argument resolved against them, and the result read back out. Null wherever a
 * component cannot be pinned down.
 * @param {string} fn the lowercased function name
 * @param {string} inner the already-joined argument text
 * @param {boolean} hexAlpha whether the target reads a 4-/8-digit hex
 * @param {boolean} forFallback whether this stands before the call rather than in its place
 * @returns {string | null} the color, or null
 */
const _foldRelativeColor = (fn, inner, hexAlpha, forFallback) => {
	const shape = _RELATIVE_CHANNELS.get(fn);
	if (shape === undefined) return null;
	const parts = _splitTopLevelSpaces(inner.replace(/\//g, " / "));
	if (parts.length < 2 || !asciiCaseInsensitiveMatch(parts[0], "from")) {
		return null;
	}
	const origin = _resolveColor(parts[1]);
	if (origin === null) return null;
	// `color(from <color> <space> …)` names its space where the others name none.
	let at = 2;
	let channels;
	let write;
	let conversion = origin.conversion;
	// The space the function states its channels in, where it has one this can
	// write a color back into.
	let spaceName = "";
	/** @type {import("./data").ColorSpaceModel | undefined} */
	let space;
	if (fn === "color") {
		spaceName = asciiLowercaseName(parts[at++] || "");
		space = getColorSpaceModel().get(spaceName);
		if (space === undefined || space.hue !== -1) return null;
		channels = space.to(origin.linear);
		write = space.from;
		conversion = Math.max(conversion, space.conversion);
	} else if (fn === "rgb" || fn === "rgba") {
		spaceName = SRGB_SPACE;
		channels = origin.linear.map((c) => _gammaEncode(c) * 255);
		write = (/** @type {number[]} */ c) =>
			c.map((byte) => _gammaDecode(byte / 255));
	} else {
		// `hsla()` states the same channels in the same space `hsl()` does, and
		// the model holds it under that name — which is the name resolving the
		// origin already reads it as, so the two agree on what a color is in.
		spaceName = fn === "hsla" ? "hsl" : fn;
		space = /** @type {import("./data").ColorSpaceModel} */ (
			getColorSpaceModel().get(spaceName)
		);
		conversion = Math.max(conversion, space.conversion);
		channels = space.to(origin.linear).map((c, i) => c * shape.scale[i]);
		const read = space;
		write = (/** @type {number[]} */ c) =>
			read.from(c.map((value, i) => value / shape.scale[i]));
	}
	/** @type {Map<string, number>} */
	const bound = new Map();
	for (let i = 0; i < 3; i++) bound.set(shape.channels[i], channels[i]);
	bound.set("alpha", origin.alpha);
	const written = [];
	for (let i = 0; i < 3; i++) {
		const argument = parts[at + i];
		if (argument === undefined) return null;
		const hue = fn !== "color" && shape.channels[i] === "h";
		const value = _relativeChannel(argument, bound, hue);
		if (value === null) return null;
		written.push(value);
	}
	let alpha = origin.alpha;
	if (parts[at + 3] !== undefined) {
		if (parts[at + 3] !== "/" || parts[at + 4] === undefined) return null;
		const written = _relativeChannel(parts[at + 4], bound, false);
		if (written === null) {
			const percent = _COLOR_NUMBER_RE.exec(parts[at + 4]);
			if (percent === null || percent[2] !== "%") return null;
			alpha = Number(percent[1]) / 100;
		} else {
			alpha = written;
		}
		if (!(alpha >= 0 && alpha <= 1)) return null;
		if (parts.length > at + 5) return null;
	}
	const raw = write(written).map((channel) => _gammaEncode(channel) * 255);
	const byte = _colorFromChannels(
		raw,
		alpha,
		conversion,
		hexAlpha,
		forFallback
	);
	if (byte !== null || forFallback || space === undefined) return byte;
	// As a mix does: an origin written in the function's own space is read into
	// the channels it already states, so the result is written back there.
	if (origin.space !== spaceName) return null;
	return _spaceColorText(
		space.written,
		space.hue,
		written.map((value, i) => value / shape.scale[i]),
		alpha
	);
};

// The pair a lowered `light-dark()` reads, and the rule defaulting them.
// `initial` is the guaranteed-invalid value, so a `var()` naming a property set
// to it takes its fallback, while one set to nothing substitutes nothing.
const LIGHT_PROPERTY = "--webpack-light";
const DARK_PROPERTY = "--webpack-dark";
// A call, not a name that ends in one: the character before it has to close an
// identifier, and a function name is matched without regard to case.
const _LIGHT_DARK_CALL_RE = /(^|[^\w-])light-dark\(/i;

// No specificity at all, so every rule that sets `color-scheme` — `html` and
// `*` included — overrides it wherever it stands, which is what lets it be
// written last. The empty value carries no space: a second pass takes one off.
const LIGHT_DARK_DEFAULTS = `:where(:root){${LIGHT_PROPERTY}:initial;${DARK_PROPERTY}:}`;

// A relative color leads with `from`, which no other value of any of these
// functions does.
const _RELATIVE_FROM_RE = /^\s*from(?![-\w])/i;

// One color function a target may not read, printed with no call of its own
// inside it — a relative color or a `calc()` argument is not one to fold. The
// leading character keeps `--my-lab(1)` from reading as `lab(1)`.
const _FALLBACK_COLOR_FUNCTION_RE =
	/(^|[^-\w])(hwb|lab|lch|oklab|oklch|color-mix|color)\(([^()]*)\)/gi;

// The name of a call that folds on what it is written over rather than on
// channels of its own, wherever it stands and whatever it holds — the
// arguments are not read here. `color-mix` before `color`, which is its prefix.
const _FALLBACK_WHOLE_CALL_RE =
	/(^|[^-\w])(rgba?|hsla?|hwb|lab|lch|oklab|oklch|color-mix|color)\(/gi;

// The `from` a relative reference opens with, which is the whole of what makes
// one fold on its origin rather than on channels the author wrote out.
const _RELATIVE_ORIGIN_RE = /^\s*from\s/i;

// Whether folding the calls below clamped one into the sRGB gamut, read by the
// caller: a second result, and no object built per value to carry it.
let _wholeCallClipped = false;

/**
 * A color written where a call stood, with a space after it where what follows
 * would read on as part of it — `#7f007f` before `url(` is one hash token.
 * @param {string} written the color, as spliced in
 * @param {string} rest what follows the call it replaced
 * @returns {string} the color, parted from what follows where it has to be
 */
const _parted = (written, rest) =>
	rest.length !== 0 &&
	_wouldFuseTokens(written.charCodeAt(written.length - 1), rest, written)
		? `${written} `
		: written;

/**
 * A color folded into a fallback, after what stood before the call it replaces.
 * The space a call needed is none a hex needs — the printer writes `0 0#000` —
 * and the fallback has to print as that would.
 * @param {string} before what stood before the call: one character, or none
 * @param {string} color the folded color
 * @returns {string} the two joined
 */
const _spliceFallbackColor = (before, color) =>
	color.charCodeAt(0) === CC_NUMBER_SIGN &&
	isAsciiWhitespace(before.charCodeAt(0))
		? color
		: before + color;

/**
 * The same value with every `color-mix()` and relative reference written as the
 * color it names, read over the arguments the author wrote. Substituting each
 * of those for a byte first would round every one into eight bits, and a mix of
 * those bytes is not the color the mix names.
 * @param {string} value the declaration's value, as printed
 * @returns {string | null} the value, or null where none of them folded
 */
const _foldWholeColorCalls = (value) => {
	let out = "";
	let from = 0;
	_wholeCallClipped = false;
	_FALLBACK_WHOLE_CALL_RE.lastIndex = 0;
	for (
		let match = _FALLBACK_WHOLE_CALL_RE.exec(value);
		match !== null;
		match = _FALLBACK_WHOLE_CALL_RE.exec(value)
	) {
		const open = match.index + match[0].length - 1;
		const close = _closingParenthesis(value, open);
		const inner = value.slice(open + 1, close);
		const fn = asciiLowercaseName(match[2]);
		// Anything else stands as the author wrote it: whether the target reads
		// it is what the pass after this one answers, and this is not here to
		// reopen that.
		if (fn !== "color-mix" && !_RELATIVE_ORIGIN_RE.test(inner)) continue;
		const color = _minifyPolarColorFunction(
			fn,
			inner,
			_S._hexAlphaAllowed,
			true
		);
		if (color === null) continue;
		if (_fallbackClipped) _wholeCallClipped = true;
		out +=
			value.slice(from, match.index) +
			_parted(_spliceFallbackColor(match[1], color), value.slice(close + 1));
		from = close + 1;
		_FALLBACK_WHOLE_CALL_RE.lastIndex = from;
	}
	return from === 0 ? null : out + value.slice(from);
};

/**
 * Whether a color function is one some target does not read, so a declaration
 * naming it needs a fallback before it — and a fallback still naming one is no
 * fallback. Only the names `_FALLBACK_COLOR_FUNCTION_RE` and
 * `_UNREADABLE_COLOR_CALL_RE` match reach here.
 * @param {string} fn the lowercased function name
 * @returns {boolean} true when the target cannot read it
 */
const _unreadableColorFunction = (fn) => {
	if (fn === "hwb") return !_hwbColorsAllowed;
	if (fn === "lab" || fn === "lch") return !_labColorsAllowed;
	if (fn === "oklab" || fn === "oklch") return !_oklabColorsAllowed;
	if (fn === "color") return !_colorFunctionAllowed;
	if (fn === "color-mix") return !_colorMixAllowed;
	return !_lightDarkAllowed;
};

// The name of any color function a target may not read, wherever it stands —
// the arguments are not read here, so one holding a call of its own still
// matches. `color-mix` before `color`, which is its prefix.
const _UNREADABLE_COLOR_CALL_RE =
	/(^|[^-\w])(hwb|lab|lch|oklab|oklch|color-mix|color|light-dark)\(/gi;

/**
 * The declaration to write before one whose value names a color function the
 * target cannot read: the same declaration with every such color written in a
 * spelling it does read. Null where there is none to write.
 *
 * A fallback still naming a function the target cannot read is no fallback — the
 * engine drops it just as it drops the declaration it stands before — so a color
 * this cannot fold whole (a relative color, an argument holding a `calc()`, one
 * nested in a `color-mix()`) leaves the declaration alone rather than adding
 * bytes nothing reads.
 * @param {string} text one printed declaration
 * @param {number} colon where its `:` sits
 * @returns {string | null} the fallback declaration, or null
 */
const _colorFallbackDeclaration = (text, colon) => {
	// A custom property's value is handed back as written, and holds no
	// declaration of its own to stand before.
	if (text.charCodeAt(1) === CC_HYPHEN_MINUS) return null;
	const value = text.slice(colon + 1);
	// A value reached through a substitution is invalid at computed-value time
	// when what it substitutes is unreadable — which is `unset`, not the earlier
	// declaration — so a pair standing before one is bytes nothing ever reads.
	if (containsSubstitutionFunction(value)) return null;
	// The calls that fold on what they are written over go first, while their
	// arguments are still the author's: the pass after this one reaches only
	// what holds no call of its own, so it would read bytes this left behind.
	const outer = _foldWholeColorCalls(value);
	let folded = outer !== null;
	let clipped = folded && _wholeCallClipped;
	const written = (outer === null ? value : outer).replace(
		_FALLBACK_COLOR_FUNCTION_RE,
		(whole, before, name, inner, at, all) => {
			const fn = asciiLowercaseName(name);
			if (!_unreadableColorFunction(fn)) return whole;
			const color = _minifyPolarColorFunction(
				fn,
				inner,
				_S._hexAlphaAllowed,
				true
			);
			if (color === null) return whole;
			if (_fallbackClipped) clipped = true;
			folded = true;
			return _parted(
				_spliceFallbackColor(before, color),
				all.slice(at + whole.length)
			);
		}
	);
	if (!folded) return null;
	_UNREADABLE_COLOR_CALL_RE.lastIndex = 0;
	for (
		let match = _UNREADABLE_COLOR_CALL_RE.exec(written);
		match !== null;
		match = _UNREADABLE_COLOR_CALL_RE.exec(written)
	) {
		if (_unreadableColorFunction(asciiLowercaseName(match[2]))) return null;
	}
	const head = text.slice(0, colon + 1);
	const srgb = head + written;
	// A color sRGB cannot show was clamped to reach that hex, so where the target
	// reads `color()` the wider gamut goes between the two: the hex for an engine
	// reading neither, this for one without the Lab family, the author's for rest.
	if (!clipped || !_colorFunctionAllowed) return srgb;
	const wide = _wideGamutFallback(value);
	return wide === null ? srgb : `${srgb}${head}${wide}`;
};

/**
 * The same value with every color the target cannot read written as the
 * `color(display-p3 …)` naming it, or null where any of them is one P3 cannot
 * show either — a partial wide-gamut fallback would paint some channels right
 * and clamp the rest.
 * @param {string} value the declaration's value, as printed
 * @returns {string | null} the value, or null
 */
const _wideGamutFallback = (value) => {
	let withinGamut = true;
	const written = value.replace(
		_FALLBACK_COLOR_FUNCTION_RE,
		(whole, before, name, inner) => {
			const fn = asciiLowercaseName(name);
			if (!_unreadableColorFunction(fn)) return whole;
			const read = _minifyPolarColorFunction(
				fn,
				inner,
				_S._hexAlphaAllowed,
				true
			);
			/* istanbul ignore if -- @preserve: the sRGB pass above folded every one
			   of these and returned before this where any of them declined, so the
			   same call cannot answer differently here */
			if (read === null) return whole;
			const wide = _wideGamutColor(
				/** @type {number[]} */ (_fallbackChannels),
				_fallbackAlpha
			);
			if (wide === null) {
				withinGamut = false;
				return whole;
			}
			return before + wide;
		}
	);
	return withinGamut ? written : null;
};

/**
 * Lowercase the pseudo-class and pseudo-element names in a selector's printed
 * parts, in place: the name after a `:` matches ASCII case-insensitively, while
 * a type selector's does not (`linearGradient` is an SVG element) and neither
 * does an id, a class or an attribute's value. A functional pseudo prints as one
 * token and lowercases its own name, its argument being the author's.
 * @param {string[]} parts the prelude's printed parts
 * @returns {void}
 */
/**
 * Lowercase the names in a media condition's printed parts, in place: a feature
 * name, a media type, the keywords between them and a dimension's unit all match
 * ASCII case-insensitively. A custom media query's `--name` does not, and a
 * string or a nested function (a style query) carries text no such rule covers.
 * @param {string[]} parts the condition's printed parts
 * @returns {void}
 */
const _lowercaseConditionParts = (parts) => {
	for (let i = 0; i < parts.length; i++) {
		const part = parts[i];
		// Folded first, for the reason `_foldPseudoNames` folds first.
		const lowered = asciiLowercaseName(part);
		if (lowered === part) continue;
		const first = part.charCodeAt(0);
		if (
			first === CC_QUOTATION_MARK ||
			first === CC_APOSTROPHE ||
			(first === CC_HYPHEN_MINUS && part.charCodeAt(1) === CC_HYPHEN_MINUS) ||
			part.includes("(")
		) {
			continue;
		}
		parts[i] = lowered;
	}
};

// One plain pseudo-class inside a functional pseudo's argument: a `:` no second
// colon precedes and no `(` follows, so neither a pseudo-element nor a
// functional pseudo of its own is matched.
const _PSEUDO_CLASS_REFERENCE_RE = /(^|[^:\\]):([-\w]+)(?![-\w(])/g;

// What ends a compound selector: a combinator, or the comma between two.
const _COMPOUND_BOUNDARY_RE = /^[\s>+~,]/;

/**
 * Write each pseudo-class the caller named as the class it gave instead, in the
 * printed parts, in place. Nothing after a pseudo-element is replaced: a class
 * cannot stand there.
 * @param {string[]} parts the prelude's printed parts
 * @returns {void}
 */
const _replacePseudoClasses = (parts) => {
	const named = /** @type {Map<string, string>} */ (_pseudoClasses);
	let first = -1;
	let second = -1;
	// Selectors 4 §3.5: a pseudo-element takes only pseudo-classes after it, so a
	// class standing there matches nothing. The compound it opens is left alone.
	let afterPseudoElement = false;
	for (let i = 0; i < parts.length; i++) {
		if (parts[i].length === 0) continue;
		if (_COMPOUND_BOUNDARY_RE.test(parts[i])) afterPseudoElement = false;
		// `::name`, whether or not it takes arguments — `::part()` and `::slotted()`
		// are pseudo-elements as much as `::before` is.
		const doubled =
			second !== -1 &&
			parts[second] === ":" &&
			first !== -1 &&
			parts[first] === ":";
		if (parts[i].includes("(")) {
			// A functional pseudo prints with its arguments attached, and those hold
			// selectors of their own, `:not(:hover)` being the same question a level
			// down. Read as text, so a quoted attribute value is left alone.
			if (!parts[i].includes('"') && !parts[i].includes("'")) {
				parts[i] = parts[i].replace(
					_PSEUDO_CLASS_REFERENCE_RE,
					(whole, before, name) => {
						const written = named.get(toLowerCaseIfNeeded(name));
						return written === undefined
							? whole
							: `${before}.${escapeIdentifier(written)}`;
					}
				);
			}
			if (doubled) afterPseudoElement = true;
		} else if (second !== -1 && parts[second] === ":") {
			const name = toLowerCaseIfNeeded(parts[i]);
			// A `::name`, or one of the four a single colon also spells — which the
			// fold above may have just written this as.
			if (doubled || LEGACY_PSEUDO_ELEMENTS.has(name)) {
				afterPseudoElement = true;
			} else if (!afterPseudoElement) {
				const written = named.get(name);
				if (written !== undefined) {
					parts[second] = ".";
					parts[i] = escapeIdentifier(written);
				}
			}
		}
		first = second;
		second = i;
	}
};

/**
 * Drop the media type a query names for nothing. `all and (…)` matches exactly
 * what `(…)` alone does (Media Queries 4 §2.1), so the type is six bytes saying
 * nothing — while `all` on its own is the whole query and says everything. A
 * `not` or `only` before it is not the same query: `only` is there to hide the
 * whole thing from an engine reading CSS 2's grammar, which is what the keyword
 * is for.
 * @param {string[]} parts the prelude's printed parts
 * @returns {void}
 */
const _dropImpliedMediaType = (parts) => {
	// The parts after `@media`, and again after every comma, open a query.
	let opening = true;
	for (let i = 1; i < parts.length; i++) {
		const part = parts[i];
		if (part.length === 0 || part === " ") continue;
		if (part === ",") {
			opening = true;
			continue;
		}
		const opened = opening;
		opening = false;
		if (!opened || part !== "all") continue;
		// The `and` joining the type to a condition; with none, the type is the
		// query.
		let next = i + 1;
		while (
			next < parts.length &&
			(parts[next].length === 0 || parts[next] === " ")
		) {
			next++;
		}
		if (next === parts.length || parts[next] !== "and") continue;
		for (let at = i; at <= next; at++) parts[at] = "";
	}
};

/**
 * Drop an operand a condition already states. `(A) and (A)` is `(A)` and so is
 * `(A) or (A)`, whatever `A` is — and CSS Conditional 3 §3 takes no chain
 * mixing the two without parentheses, so every operand at one level answers to
 * the same operator.
 * @param {string[]} parts the prelude's printed parts
 * @returns {void}
 */
const _dropRepeatedConditions = (parts) => {
	/** @type {Set<string>} */
	const stated = new Set();
	let operator = "";
	for (let i = 1; i < parts.length; i++) {
		const part = parts[i];
		if (part.length === 0 || part === " ") continue;
		const lowered = asciiLowercaseName(part);
		if (lowered === "and" || lowered === "or") {
			// A chain of one operator only: anything else and the operands are no
			// longer a flat list of this one's.
			if (operator !== "" && operator !== lowered) return;
			operator = lowered;
			continue;
		}
		// Only a parenthesized operand is one this can compare whole; a bare
		// `not` or a declaration's own tokens are parts of one.
		if (part.charCodeAt(0) !== CC_LEFT_PARENTHESIS) return;
		if (!stated.has(part)) {
			stated.add(part);
			continue;
		}
		// ...and the operator joining it, which stands before it.
		for (let at = i; at >= 0; at--) {
			const seen = parts[at];
			parts[at] = "";
			if (at !== i && seen.length !== 0 && seen !== " ") break;
		}
	}
};

// One functional pseudo whose argument is a list the target may not take, as
// printed: the name and everything between its parentheses.
const _SELECTOR_LIST_PSEUDO_RE = /^(not|lang)\((.*)\)$/i;

/**
 * Write a selector list an older engine does not take inside the pseudo that
 * holds it as the `:is()` it means, in the printed parts, in place.
 *
 * `:not(a, b)` matches what neither matches, which is what `:not(:is(a, b))`
 * says — and `:is()` takes the specificity of its most specific argument, which
 * is the specificity `:not()`'s list has. `:lang(a, b)` matches either, which is
 * `:is(:lang(a), :lang(b))`. Both need `:is()` itself, so a target reading
 * neither keeps what the author wrote.
 * @param {string[]} parts the prelude's printed parts
 * @returns {void}
 */
const _lowerSelectorLists = (parts) => {
	for (let i = 0; i < parts.length; i++) {
		const found = _SELECTOR_LIST_PSEUDO_RE.exec(parts[i]);
		if (found === null) continue;
		const negation = asciiLowercaseName(found[1]) === "not";
		if (negation ? _notSelectorListAllowed : _langArgumentListAllowed) continue;
		const written = _splitSelectorList(found[2]);
		if (written.length < 2) continue;
		parts[i] = negation
			? `not(:is(${found[2]}))`
			: `is(${written.map((one) => `:lang(${one})`).join(",")})`;
	}
};

// One condition asking for a `@custom-media` name, as printed.
const _CUSTOM_MEDIA_REFERENCE_RE = /^\(\s*(--[^\s()]+)\s*\)$/;
// The same, anywhere in a printed `(…)` group.
const _NESTED_CUSTOM_MEDIA_REFERENCE_RE = /\(\s*(--[^\s()]+)\s*\)/g;
// A media type: an identifier, which no keyword between conditions is.
const _MEDIA_TYPE_RE = /^-?[a-zA-Z_\u0080-￿][\w\u0080-￿-]*$/;

/**
 * @param {string} token a printed query token
 * @returns {string} the keyword it spells, lowercased, without the kept comment
 */
const _queryKeyword = (token) =>
	asciiLowercaseName(token.endsWith("/**/") ? token.slice(0, -4) : token);

/**
 * @param {string} token a printed query token
 * @returns {boolean} true where it is `and`, `or`, `not` or `only`
 */
const _isQueryKeyword = (token) => {
	const keyword = _queryKeyword(token);
	return (
		keyword === "and" ||
		keyword === "or" ||
		keyword === "not" ||
		keyword === "only"
	);
};

/**
 * Split a printed media query into its top-level tokens: a `(…)` group is one,
 * and a keyword whose comment was kept is one apart from the `(` it precedes.
 * @param {string} query one printed media query
 * @returns {string[]} its tokens
 */
const _queryTokens = (query) => {
	/** @type {string[]} */
	const tokens = [];
	let depth = 0;
	let start = 0;
	for (let i = 0; i <= query.length; i++) {
		const code = i === query.length ? CC_SPACE : query.charCodeAt(i);
		if (code === CC_LEFT_PARENTHESIS) {
			if (depth === 0 && i > start && query.endsWith("/**/", i)) {
				tokens.push(query.slice(start, i));
				start = i;
			}
			depth++;
		} else if (code === CC_RIGHT_PARENTHESIS) {
			depth--;
		} else if (depth <= 0 && isAsciiWhitespace(code)) {
			if (i > start) tokens.push(query.slice(start, i));
			start = i + 1;
		}
	}
	return tokens;
};

/**
 * @param {string[]} tokens a query's tokens
 * @returns {string} them printed, spaced but where a kept comment parts them
 */
const _joinQueryTokens = (tokens) => {
	let out = tokens[0];
	for (let i = 1; i < tokens.length; i++) {
		out += tokens[i - 1].endsWith("/**/") ? tokens[i] : ` ${tokens[i]}`;
	}
	return out;
};

/**
 * One alternative of a `@custom-media` query, read as what it can be written as:
 * `true` / `false`, a bare condition, a media type with the conditions after it
 * (`negated` where a `not` turns the whole query), or none of these.
 * @typedef {object} CustomMediaAlternative
 * @property {"true" | "false" | "condition" | "typed" | "negated" | "other"} kind what it is
 * @property {string[]} prefix `only` where written before the media type
 * @property {string} type the media type
 * @property {string[]} tokens the condition's tokens
 * @property {string[]} written every token, as written
 */

/**
 * @param {string} text one alternative of a printed `@custom-media` query
 * @returns {CustomMediaAlternative} it, read
 */
const _customMediaAlternative = (text) => {
	const tokens = _queryTokens(text);
	/** @type {CustomMediaAlternative} */
	const read = {
		kind: "other",
		prefix: [],
		type: "",
		tokens: [],
		written: tokens
	};
	if (tokens.length === 0) return read;
	const first = _queryKeyword(tokens[0]);
	if (tokens.length === 1 && (first === "true" || first === "false")) {
		read.kind = first;
		return read;
	}
	if (
		tokens[0].charCodeAt(0) === CC_LEFT_PARENTHESIS ||
		(first === "not" &&
			tokens.length > 1 &&
			tokens[1].charCodeAt(0) === CC_LEFT_PARENTHESIS)
	) {
		read.kind = "condition";
		read.tokens = tokens;
		return read;
	}
	let at = 0;
	if (first === "only" || first === "not") {
		read.prefix = [tokens[0]];
		at = 1;
	}
	const type = tokens[at];
	if (
		type === undefined ||
		!_MEDIA_TYPE_RE.test(type) ||
		_isQueryKeyword(type) ||
		(at + 1 < tokens.length && _queryKeyword(tokens[at + 1]) !== "and")
	) {
		return read;
	}
	read.kind = first === "not" ? "negated" : "typed";
	read.type = type;
	read.tokens = tokens.slice(at + 2);
	return read;
};

/**
 * A condition's tokens as a `(…)` group, the one form every place in a
 * condition reads.
 * @param {string[]} tokens the condition's tokens
 * @returns {string} it as one group
 */
const _conditionGroup = (tokens) =>
	tokens.length === 1 ? tokens[0] : `(${_joinQueryTokens(tokens)})`;

/**
 * A condition's tokens as they may stand in a chain of `and`: as written where
 * each is joined by `and` alone, else as one group.
 * @param {string[]} tokens the condition's tokens
 * @returns {string[]} what to write in the chain
 */
const _andChainTokens = (tokens) => {
	for (const token of tokens) {
		const keyword = _queryKeyword(token);
		if (keyword === "or" || keyword === "not") {
			return [_conditionGroup(tokens)];
		}
	}
	return tokens;
};

/**
 * What a `@custom-media` name stands for as one condition, which only a query of
 * bare conditions has: alternatives are one `or`.
 * @param {CustomMediaAlternative[]} alternatives the query's alternatives
 * @returns {string | null} the condition as one group, or null
 */
const _customMediaCondition = (alternatives) => {
	for (const alternative of alternatives) {
		if (alternative.kind !== "condition") return null;
	}
	if (alternatives.length === 1) {
		return _conditionGroup(alternatives[0].tokens);
	}
	return `(${alternatives
		.map((alternative) => _conditionGroup(alternative.tokens))
		.join(" or ")})`;
};

/**
 * @param {string} name a `@custom-media` name
 * @returns {CustomMediaAlternative[] | undefined} the query it stands for, read
 */
const _customMediaQuery = (name) => {
	const query = _customMedia.get(name);
	if (query === undefined) return undefined;
	return _splitTopLevelArguments(query).map(_customMediaAlternative);
};

/**
 * A media query's tokens with one of them dropped, and the `and` joining it.
 * @param {string[]} tokens the query's tokens
 * @param {number} at the one to drop
 * @returns {string[] | null} what is left, or null where no `and` joins it
 */
const _withoutQueryToken = (tokens, at) => {
	const from = at > 0 ? at - 1 : at;
	const joint = at > 0 ? tokens[at - 1] : tokens[at + 1];
	if (joint === undefined || _queryKeyword(joint) !== "and") return null;
	return [...tokens.slice(0, from), ...tokens.slice(from + 2)];
};

/**
 * Write one media query's `@custom-media` names as the queries they stand for.
 * A name standing for alternatives beside anything else is written as one query
 * per alternative, where each can be; else the query is left as written.
 * @param {string[]} tokens the query's tokens
 * @param {number} from the first token not written out already, so what a name
 * stands for is never read for names again
 * @returns {string[][] | null} the queries it is written as, or null for as written
 */
const _writeCustomMediaQuery = (tokens, from) => {
	let changed = false;
	// A name inside a group can only be one condition there.
	for (let i = from; i < tokens.length; i++) {
		if (_CUSTOM_MEDIA_REFERENCE_RE.test(tokens[i])) continue;
		const written = tokens[i].replace(
			_NESTED_CUSTOM_MEDIA_REFERENCE_RE,
			(reference, name) => {
				const alternatives = _customMediaQuery(name);
				const condition =
					alternatives === undefined
						? null
						: _customMediaCondition(alternatives);
				return condition === null ? reference : condition;
			}
		);
		if (written !== tokens[i]) {
			tokens = [...tokens];
			tokens[i] = written;
			changed = true;
		}
	}
	/** @type {CustomMediaAlternative[] | undefined} */
	let alternatives;
	let at = from;
	for (; at < tokens.length; at++) {
		const found = _CUSTOM_MEDIA_REFERENCE_RE.exec(tokens[at]);
		if (found === null) continue;
		alternatives = _customMediaQuery(found[1]);
		if (alternatives !== undefined) break;
	}
	if (alternatives === undefined) return changed ? [tokens] : null;
	/** @type {string[][]} */
	const queries = [];
	if (tokens.length === 1) {
		for (const alternative of alternatives) {
			if (alternative.kind === "true") {
				queries.push(["all"]);
			} else if (alternative.kind !== "false") {
				queries.push(alternative.written);
			}
		}
		return queries;
	}
	// In a chain of `and` that no `not` turns, each alternative is a query of its
	// own, which every target reads; elsewhere they can only be one condition.
	if (
		_queryKeyword(tokens[0]) === "not" ||
		(at > 0 && _queryKeyword(tokens[at - 1]) === "not") ||
		tokens.some((token) => _queryKeyword(token) === "or")
	) {
		const condition = _customMediaCondition(alternatives);
		if (condition === null) return changed ? [tokens] : null;
		const written = [...tokens];
		written[at] = condition;
		return _writeCustomMediaQuery(written, at + 1) || [written];
	}
	const typed =
		_queryKeyword(tokens[0]) === "only" ||
		!(
			tokens[0].charCodeAt(0) === CC_LEFT_PARENTHESIS ||
			_CUSTOM_MEDIA_REFERENCE_RE.test(tokens[0])
		);
	for (const alternative of alternatives) {
		/** @type {string[]} */
		let written;
		let next = at;
		if (alternative.kind === "false") continue;
		if (alternative.kind === "condition") {
			const inserted = _andChainTokens(alternative.tokens);
			written = [...tokens.slice(0, at), ...inserted, ...tokens.slice(at + 1)];
			next = at + inserted.length;
		} else if (alternative.kind === "true" || alternative.kind === "typed") {
			const all =
				alternative.kind === "true" ||
				(alternative.prefix.length === 0 &&
					asciiLowercaseName(alternative.type) === "all");
			if (typed && !all) return changed ? [tokens] : null;
			const inserted = _andChainTokens(alternative.tokens);
			const rest =
				inserted.length === 0
					? _withoutQueryToken(tokens, at)
					: [...tokens.slice(0, at), ...inserted, ...tokens.slice(at + 1)];
			// Nothing left is a query that was never valid, as is no `and` beside it.
			if (rest === null || rest.length === 0) {
				return changed ? [tokens] : null;
			}
			next = inserted.length === 0 ? Math.max(at - 1, 0) : at + inserted.length;
			if (typed || all) {
				written = rest;
			} else {
				written = [...alternative.prefix, alternative.type, "and", ...rest];
				next += alternative.prefix.length + 2;
			}
		} else {
			return changed ? [tokens] : null;
		}
		const more = _writeCustomMediaQuery(written, next);
		if (more === null) queries.push(written);
		else for (const one of more) queries.push(one);
	}
	return queries;
};

/**
 * Write each condition naming a `@custom-media` as the query it stands for, in
 * the printed parts, in place; a query left with no alternative is `not all`.
 * @param {string[]} parts the prelude's printed parts
 * @returns {void}
 */
const _writeCustomMedia = (parts) => {
	/** @type {string[][]} */
	const queries = [[]];
	for (let i = 1; i < parts.length; i++) {
		if (parts[i] === ",") queries.push([]);
		else queries[queries.length - 1].push(parts[i]);
	}
	/** @type {string[][]} */
	const written = [];
	let changed = false;
	for (const query of queries) {
		const tokens = query.filter((part) => part.trim().length !== 0);
		const resolved =
			tokens.length === 0 ? null : _writeCustomMediaQuery(tokens, 0);
		if (resolved === null) {
			written.push(query);
			continue;
		}
		changed = true;
		let lead = 0;
		while (lead < query.length && query[lead].trim().length === 0) lead++;
		for (let i = 0; i < resolved.length; i++) {
			/** @type {string[]} */
			const one = i === 0 ? query.slice(0, lead) : [];
			const each = resolved[i];
			for (let j = 0; j < each.length; j++) {
				if (j > 0 && !each[j - 1].endsWith("/**/")) one.push(" ");
				one.push(each[j]);
			}
			written.push(one);
		}
	}
	if (!changed) return;
	const lead = queries[0].length > 0 && queries[0][0].trim().length === 0;
	parts.length = 1;
	if (written.length === 0) {
		if (lead) parts.push(" ");
		parts.push("not", " ", "all");
		return;
	}
	for (let i = 0; i < written.length; i++) {
		if (i > 0) parts.push(",");
		for (const part of written[i]) parts.push(part);
	}
};

// WHY: WebKit reads a `@supports` `and`, `or` or `not` as invalid unless
// whitespace follows it, where the spec only needs the `(` not to join it into
// a function. Where a comment alone parted the two, a space would make WebKit
// read the condition, so the comment is written back, in a media query too.
/**
 * @param {string[]} parts a condition's printed parts, in place
 * @returns {void}
 */
const _keepKeywordComments = (parts) => {
	for (let i = 0; i < parts.length; i++) {
		const part = parts[i];
		if (
			!asciiCaseInsensitiveMatch(part, "and") &&
			!asciiCaseInsensitiveMatch(part, "or") &&
			!asciiCaseInsensitiveMatch(part, "not")
		) {
			continue;
		}
		let next = i + 1;
		while (next < parts.length && parts[next].length === 0) next++;
		if (
			next < parts.length &&
			parts[next].charCodeAt(0) === CC_LEFT_PARENTHESIS
		) {
			parts[i] = `${part}/**/`;
		}
	}
};

/**
 * Minify a media or container query's printed parts, in place, after its first
 * part — the rule's name, which none of the passes read.
 * @param {string[]} parts the prelude's printed parts
 * @param {boolean} media whether it is a media query rather than a container one
 * @returns {void}
 */
const _minifyMediaQueryParts = (parts, media) => {
	// Only a media query's top level is media types and the keywords between
	// them; `@container`'s leads with a container name, which is the author's own
	// and case-sensitive.
	if (media) {
		// Before the passes below, which read the query this writes in.
		if (_S._transforms.customMedia && _customMedia.size !== 0) {
			_writeCustomMedia(parts);
		}
		if (_S._transforms.foldCase) _lowercaseConditionParts(parts);
		if (_S._transforms.shortenMediaQueries) _dropImpliedMediaType(parts);
	}
	if (_S._rangeSpellingAllowed) {
		_collapseRangeInterval(parts);
	}
	// A `@container` query is itself newer than the range spelling, so a target
	// that reads one reads the other and there is nothing to lower.
	else if (media && _S._transforms.lowerUnsupported) {
		_useMinMaxSpelling(parts);
	}
};

/**
 * Where an `@import`'s media query starts in its printed parts: past the URL,
 * then a `layer` and a `supports()` where written, each of which is one part.
 * @param {string[]} parts the prelude's printed parts, `@import` first
 * @returns {number} the index of the query's first part, or -1 when it has none
 */
const _importMediaStart = (parts) => {
	// 0: the URL is next, 1: a `layer`, 2: a `supports()`, 3: the query.
	let expecting = 0;
	for (let i = 1; i < parts.length; i++) {
		const part = parts[i];
		if (part.length === 0 || part === _SEP) continue;
		if (expecting === 0) {
			expecting = 1;
			continue;
		}
		const open = part.indexOf("(");
		const name = asciiLowercaseName(
			unescapeIdentifier(open === -1 ? part : part.slice(0, open))
		);
		if (expecting === 1 && name === "layer") {
			expecting = 2;
		} else if (expecting !== 3 && open !== -1 && name === "supports") {
			expecting = 3;
		} else {
			return i;
		}
	}
	return -1;
};

/**
 * Where a `@custom-media` query starts in its printed parts: past the name.
 * @param {string[]} parts the prelude's printed parts, `@custom-media` first
 * @returns {number} the index of the query's first part, or -1 when it has none
 */
const _customMediaStart = (parts) => {
	let named = false;
	for (let i = 1; i < parts.length; i++) {
		const part = parts[i];
		if (part.length === 0 || part === _SEP) continue;
		if (named) return i;
		named = true;
	}
	return -1;
};

// One selector naming a `@custom-selector`, as printed after its `:`.
const _CUSTOM_SELECTOR_NAME_RE = /^--[^\s(),]+$/;

/**
 * Write each selector naming a `@custom-selector` as the list it stands for, in
 * the printed parts, in place.
 *
 * The list is always written as `:is(…)`: it may name alternatives, and even a
 * lone type selector cannot be spliced into a compound another part opened.
 * Only a name already taken down, as with `@custom-media`.
 * @param {string[]} parts the prelude's printed parts
 * @returns {void}
 */
const _writeCustomSelector = (parts) => {
	// The two non-empty parts before the current one: the `:` a pseudo is written
	// after, and whether a second `:` made it a pseudo-element, which this never is.
	let first = -1;
	let second = -1;
	for (let i = 0; i < parts.length; i++) {
		if (parts[i].length === 0) continue;
		if (
			_CUSTOM_SELECTOR_NAME_RE.test(parts[i]) &&
			second !== -1 &&
			parts[second] === ":" &&
			!(first !== -1 && parts[first] === ":")
		) {
			const list = _customSelectors.get(parts[i]);
			if (list !== undefined) {
				parts[second] = "";
				parts[i] = `:is(${list})`;
			}
		}
		first = second;
		second = i;
	}
};

// `:dir(ltr)` / `:dir(rtl)`, as printed: the name and its one argument.
const _DIR_PSEUDO_RE = /^dir\(\s*([a-zA-Z-]+)\s*\)$/i;

/**
 * Write `:dir()` a target does not read as the `[dir]` attribute selector, in
 * the printed parts, in place.
 *
 * The two are not the same question: `:dir()` reads the directionality an
 * element resolves to, which it may inherit, while `[dir=rtl]` reads the
 * attribute itself — so an element inside a `dir="rtl"` ancestor matches the
 * first and not the second. That is why this is off unless asked for.
 * @param {string[]} parts the prelude's printed parts
 * @returns {void}
 */
const _lowerDirSelector = (parts) => {
	// The two non-empty parts before the current one: the `:` a pseudo is written
	// after, and whether a second `:` made it a pseudo-element, which this never is.
	let first = -1;
	let second = -1;
	for (let i = 0; i < parts.length; i++) {
		if (parts[i].length === 0) continue;
		const found = _DIR_PSEUDO_RE.exec(parts[i]);
		if (
			found !== null &&
			second !== -1 &&
			parts[second] === ":" &&
			!(first !== -1 && parts[first] === ":")
		) {
			parts[second] = "";
			parts[i] = `[dir=${asciiLowercaseName(found[1])}]`;
		}
		first = second;
		second = i;
	}
};

/**
 * Fold a selector's pseudo names to lowercase and drop the redundant colon of a
 * CSS2 pseudo-element, in one pass over the printed parts: both read the part
 * after a `:`, and the fold is what the legacy name is then matched against.
 * Empty parts are stepped over — a comment between the colons printed away, and
 * the tokenizer had already dropped it anyway.
 * @param {string[]} parts the prelude's printed parts
 * @returns {boolean} whether a pseudo it read may be an `:is()` for `_unwrapLoneIs`
 */
const _foldPseudoNames = (parts) => {
	const dropping = _S._transforms.shortenSelectors;
	let mayUnwrap = false;
	// The two non-empty parts before the current one.
	let first = -1;
	let second = -1;
	for (let i = 0; i < parts.length; i++) {
		let part = parts[i];
		if (part.length === 0) continue;
		// A pseudo's name matches ASCII case-insensitively; a type selector's does
		// not, `linearGradient` being an SVG element, and nor does an id, class or
		// attribute value. A functional pseudo folds its own name alone.
		if (second !== -1 && parts[second] === ":") {
			if (
				part.length > 4 &&
				part.charCodeAt(2) === CC_LEFT_PARENTHESIS &&
				(part.charCodeAt(0) | 0x20) === CC_LOWER_I
			) {
				mayUnwrap = true;
			}
			if (_S._transforms.foldCase && !part.includes("(")) {
				const lowered = asciiLowercaseName(part);
				if (lowered !== part) {
					part = lowered;
					parts[i] = lowered;
				}
			}
			// Folded first, so the legacy name below is matched against the folded
			// text rather than folded a second time.
			if (
				dropping &&
				first !== -1 &&
				parts[first] === ":" &&
				LEGACY_PSEUDO_ELEMENTS.has(part)
			) {
				parts[first] = "";
			}
		}
		first = second;
		second = i;
	}
	return mayUnwrap;
};

// A legacy pseudo-element written with one colon, which `:is()` cannot hold.
const _LEGACY_PSEUDO_ELEMENT_RE = new RegExp(
	`:(?:${[...LEGACY_PSEUDO_ELEMENTS].join("|")})(?![-\\w])`,
	"i"
);

/**
 * Whether a printed selector is one compound: no comma and no combinator
 * outside its brackets, parentheses, strings and escapes.
 * @param {string} selector a printed selector
 * @returns {boolean} true when it is a single compound selector
 */
const _isCompoundText = (selector) => {
	let depth = 0;
	let quote = 0;
	for (let i = 0; i < selector.length; i++) {
		const code = selector.charCodeAt(i);
		if (code === CC_REVERSE_SOLIDUS) {
			i++;
		} else if (quote !== 0) {
			if (code === quote) quote = 0;
		} else if (code === CC_QUOTATION_MARK || code === CC_APOSTROPHE) {
			quote = code;
		} else if (code === CC_LEFT_PARENTHESIS || code === CC_LEFT_SQUARE) {
			depth++;
		} else if (code === CC_RIGHT_PARENTHESIS || code === CC_RIGHT_SQUARE) {
			depth--;
		} else if (
			depth === 0 &&
			(code === CC_COMMA || _isCombinator(code) || isAsciiWhitespace(code))
		) {
			return false;
		}
	}
	return true;
};

/**
 * Unwrap each `:is()` holding one compound, which matches the same elements with
 * the same specificity. Only where the targets read every pseudo it names, since
 * `:is()` forgives an argument it cannot parse and a bare compound does not.
 * @param {string[]} parts a selector's printed parts, rewritten in place, called only where `_foldPseudoNames` read a pseudo that may be an `:is()`
 */
const _unwrapLoneIs = (parts) => {
	let before = -1;
	// Past a pseudo-element a compound takes only a few pseudo-classes, which an
	// `:is()` there may hold and a bare compound may not.
	let pastElement = false;
	for (let i = 0; i < parts.length; i++) {
		const part = parts[i];
		if (part.length === 0) continue;
		if (part === _SEP || part === "," || _isCombinator(part.charCodeAt(0))) {
			pastElement = false;
		}
		if (part === ":" && (before === -1 || parts[before] !== ":")) {
			let next = i + 1;
			while (next < parts.length && parts[next].length === 0) next++;
			const call = next < parts.length ? parts[next] : "";
			if (
				call === ":" ||
				LEGACY_PSEUDO_ELEMENTS.has(toLowerCaseIfNeeded(call))
			) {
				pastElement = true;
			}
			const isCall =
				!pastElement &&
				call.length > 4 &&
				(call.charCodeAt(0) | 0x20) === CC_LOWER_I &&
				(call.charCodeAt(1) | 0x20) === CC_LOWER_S &&
				call.charCodeAt(2) === CC_LEFT_PARENTHESIS &&
				call.charCodeAt(call.length - 1) === CC_RIGHT_PARENTHESIS;
			const inner = isCall ? call.slice(3, -1) : "";
			if (
				isCall &&
				_isCompoundText(inner) &&
				!inner.includes("::") &&
				!_LEGACY_PSEUDO_ELEMENT_RE.test(inner) &&
				// A type or universal only leads a compound, and its namespace must
				// not replace the default namespace constraining the outer compound.
				(!_startsTypeSelector(inner) ||
					(!inner.includes("|") &&
						(before === -1 ||
							parts[before] === _SEP ||
							parts[before] === "," ||
							_isCombinator(parts[before].charCodeAt(0))))) &&
				_pseudosReadable(inner)
			) {
				parts[i] = "";
				parts[next] = inner;
				i = next;
			}
		}
		before = i;
	}
};

/**
 * Whether a printed compound opens with a type, universal or namespaced selector.
 * @param {string} compound a printed compound selector
 * @returns {boolean} true when its first simple selector names an element
 */
const _startsTypeSelector = (compound) => {
	const code = compound.charCodeAt(0);
	return (
		isIdentCodePoint(code) ||
		code === CC_REVERSE_SOLIDUS ||
		code === CC_ASTERISK ||
		code === CC_VERTICAL_LINE
	);
};

/**
 * Where each `(` in a printed condition closes, skipping strings and escapes.
 * @param {string} text a printed condition
 * @returns {Int32Array | null} the closing index per opening one, or null where
 * the text is not balanced
 */
const _matchingParens = (text) => {
	const close = new Int32Array(text.length).fill(-1);
	/** @type {number[]} */
	const open = [];
	let quote = 0;
	for (let i = 0; i < text.length; i++) {
		const code = text.charCodeAt(i);
		if (code === CC_REVERSE_SOLIDUS) {
			i++;
		} else if (quote !== 0) {
			if (code === quote) quote = 0;
		} else if (code === CC_QUOTATION_MARK || code === CC_APOSTROPHE) {
			quote = code;
		} else if (code === CC_LEFT_PARENTHESIS) {
			open.push(i);
		} else if (code === CC_RIGHT_PARENTHESIS) {
			const at = open.pop();
			if (at === undefined) return null;
			close[at] = i;
		}
	}
	return open.length === 0 && quote === 0 ? close : null;
};

/**
 * The groups and words a printed condition is a chain of, at its top level.
 * @param {string} text a printed condition
 * @returns {string[] | null} its groups and the words between them, or null
 */
const _conditionItems = (text) => {
	const close = _matchingParens(text);
	if (close === null) return null;
	/** @type {string[]} */
	const items = [];
	let i = 0;
	while (i < text.length) {
		const code = text.charCodeAt(i);
		if (isAsciiWhitespace(code)) {
			i++;
		} else if (code === CC_LEFT_PARENTHESIS) {
			items.push(text.slice(i, close[i] + 1));
			i = close[i] + 1;
		} else {
			let end = i;
			while (end < text.length && _isIdentCodePoint(text.charCodeAt(end))) {
				end++;
			}
			if (end === i) return null;
			items.push(text.slice(i, end));
			i = end;
		}
	}
	return items;
};

/**
 * The tests of an `or` chain left once a vendor spelling's goes where the same
 * test of the standard spelling stands beside it and no target needs the vendor
 * one: a target reading it either reads the other too or needs it.
 * @param {string[]} items the chain's groups and words, at its top level
 * @returns {string[] | null} the kept groups, or null where nothing goes
 */
const _keptDisjuncts = (items) => {
	if (items.length < 3) return null;
	/** @type {string[]} */
	const groups = [];
	for (let k = 0; k < items.length; k++) {
		if (k % 2 === 1) {
			if (!asciiCaseInsensitiveMatch(items[k], "or")) return null;
		} else if (items[k].charCodeAt(0) === CC_LEFT_PARENTHESIS) {
			groups.push(items[k]);
		} else {
			return null;
		}
	}
	const lowered = groups.map((group) => toLowerCaseIfNeeded(group));
	const kept = groups.filter((group, k) => {
		const test = /^\(([-a-z]+):(.*)\)$/.exec(lowered[k]);
		if (test === null) return true;
		const [, property, value] = test;
		const vendor = /^(-[a-z]+-)([a-z-]+)$/.exec(property);
		if (
			vendor !== null &&
			lowered.includes(`(${vendor[2]}:${value})`) &&
			_prefixedPropertyRemovable(vendor[2], property)
		) {
			return false;
		}
		// A vendor spelling of a value, beside the keyword it spells.
		const valueTable = getPrefixedValues().get(property);
		const keyword =
			valueTable === undefined
				? undefined
				: _prefixedValueKeyword(valueTable, value);
		return (
			keyword === undefined ||
			!lowered.includes(`(${property}:${keyword})`) ||
			!_prefixRemovable(
				/** @type {Map<string, [string, number][]>} */ (valueTable),
				keyword,
				value
			)
		);
	});
	return kept.length === groups.length ? null : kept;
};

/**
 * A printed condition group with each `or` chain inside it rid of the vendor
 * tests `_keptDisjuncts` finds dead, however deep the chain stands.
 * @param {string} group a printed `(…)` group
 * @returns {string} the group, rewritten
 */
const _simplifySupportsGroup = (group) => {
	const inner = group.slice(1, -1);
	const items = _conditionItems(inner);
	// A test of a declaration, or of a function, holds no condition to simplify.
	if (items === null || items.length === 0) return group;
	if (
		items[0].charCodeAt(0) !== CC_LEFT_PARENTHESIS &&
		!asciiCaseInsensitiveMatch(items[0], "not")
	) {
		return group;
	}
	const rewritten = items.map((item) =>
		item.charCodeAt(0) === CC_LEFT_PARENTHESIS
			? _simplifySupportsGroup(item)
			: item
	);
	const kept = _keptDisjuncts(rewritten);
	return `(${kept === null ? rewritten.join(" ") : kept.join(" or ")})`;
};

/**
 * Drop each `@supports` test of a vendor spelling an `or` beside the standard
 * spelling's makes dead for every target, at any depth of the condition.
 * @param {string[]} parts the prelude's printed parts, rewritten in place
 * @returns {void}
 */
const _dropDeadPrefixedTests = (parts) => {
	/** @type {number[]} */
	const at = [];
	for (let i = 1; i < parts.length; i++) {
		const part = parts[i];
		if (part === _SEP || part.length === 0) continue;
		if (part.includes("/*")) return;
		if (part.charCodeAt(0) === CC_LEFT_PARENTHESIS) {
			parts[i] = _simplifySupportsGroup(part);
		}
		at.push(i);
	}
	const kept = _keptDisjuncts(at.map((i) => parts[i]));
	if (kept === null) return;
	parts.length = 1;
	parts.push(_SEP, kept.join(" or "));
};

/**
 * Drop the parentheses a `@supports` condition doubles: `((X))` is `(X)`, and a
 * prelude that is one group of groups is those groups. A test of a declaration
 * keeps its own, which the grammar asks for.
 * @param {string[]} parts the prelude's printed parts, rewritten in place
 * @returns {void}
 */
const _dropRedundantSupportsParens = (parts) => {
	let groups = 0;
	let only = -1;
	for (let i = 1; i < parts.length; i++) {
		const part = parts[i];
		if (part === _SEP || part.length === 0) continue;
		if (part.charCodeAt(0) !== CC_LEFT_PARENTHESIS || part.includes("/*")) {
			groups = -1;
			continue;
		}
		let text = part;
		// Only an opening pair is one to drop, which almost no condition holds.
		while (text.includes("((")) {
			const close = _matchingParens(text);
			if (close === null) break;
			let at = -1;
			for (let j = 0; j < text.length - 1; j++) {
				if (
					text.charCodeAt(j) === CC_LEFT_PARENTHESIS &&
					text.charCodeAt(j + 1) === CC_LEFT_PARENTHESIS &&
					close[j + 1] === close[j] - 1
				) {
					at = j;
					break;
				}
			}
			if (at === -1) break;
			text =
				text.slice(0, at) +
				text.slice(at + 1, close[at]) +
				text.slice(close[at] + 1);
		}
		parts[i] = text;
		if (groups !== -1) {
			groups++;
			only = i;
		}
	}
	if (groups !== 1) return;
	const text = parts[only];
	if (text.charCodeAt(1) !== CC_LEFT_PARENTHESIS) return;
	const close = _matchingParens(text);
	if (
		close !== null &&
		close[0] === text.length - 1 &&
		text.charCodeAt(1) === CC_LEFT_PARENTHESIS
	) {
		parts[only] = text.slice(1, -1);
	}
};

// `@keyframes` and every vendor spelling of it, whose prelude is a name and
// whose child preludes are keyframe selectors rather than selector lists.
const KEYFRAMES_AT_RULE_RE = /^(?:-[a-z]+-)?keyframes$/i;

// One printed `(<feature><comparison><value>)`, the shape the range spelling
// leaves behind. Media Queries 4 §2.4.3 writes an interval with both
// comparisons pointing the same way, so a pair is only ever joined as `<`.
const _RANGE_CONDITION_RE = /^\(([-\w]+)(>=|<=|>|<)([^()<>]+)\)$/;

// The two shapes the range spelling prints: `(<feature><comparison><value>)` and
// `(<value><comparison><feature><comparison><value>)`. A bound holds no
// comparison, so the greedy runs stop at the next one; nor opens a nested group.
const _RANGE_FEATURE_RE = /^\(([-\w]+)(>=|<=|=)([^<>=]+)\)$/;
const _RANGE_INTERVAL_RE =
	/^\(([^<>=(][^<>=]*)(<=|>=)([-\w]+)(<=|>=)([^<>=]+)\)$/;

/**
 * The `min-` / `max-` condition one non-strict comparison says: `width>=X` is
 * `(min-width:X)` and `width<=X` is `(max-width:X)`, both of which every engine
 * that reads a media query at all reads. `=` is the plain feature.
 *
 * A strict `>` / `<` has no such equivalent — its negation (`not (max-width:X)`)
 * is itself Media Queries 4, so an engine too old for the range spelling cannot
 * read that either — and is left as written.
 * @param {string} feature the feature name
 * @param {string} comparison one of `>=`, `<=`, `=`
 * @param {string} value the bound
 * @returns {string} the condition, parentheses included
 */
const _minMaxCondition = (feature, comparison, value) =>
	`(${
		comparison === "=" ? "" : comparison === ">=" ? "min-" : "max-"
	}${feature}:${value})`;

/**
 * Rewrite the range spelling back to the `min-` / `max-` prefixes every engine
 * reads, in the prelude's printed parts, in place — the inverse of
 * `_useRangeSpelling` / `_collapseRangeInterval`, run for a target that does not
 * read Media Queries 4 range syntax.
 *
 * An interval becomes an `and` of its two bounds, which is a conjunction — so it
 * is parenthesized where the prelude joins with `or`, or after a `not`, which
 * would otherwise negate the first bound alone. A prelude that holds an `or` is already Media
 * Queries 4, so the parentheses cost that query nothing.
 * @param {string[]} parts the prelude's printed parts
 * @returns {void}
 */
const _useMinMaxSpelling = (parts) => {
	/** @type {number[]} */
	const filled = [];
	let disjunction = false;
	for (let i = 0; i < parts.length; i++) {
		if (parts[i].length === 0 || parts[i] === _SEP) continue;
		if (_queryKeyword(parts[i]) === "or") disjunction = true;
		filled.push(i);
	}
	for (let index = 0; index < filled.length; index++) {
		const at = filled[index];
		const one = _RANGE_FEATURE_RE.exec(parts[at]);
		if (one !== null) {
			parts[at] = _minMaxCondition(one[1], one[2], one[3]);
			continue;
		}
		const interval = _RANGE_INTERVAL_RE.exec(parts[at]);
		if (interval === null) continue;
		// Media Queries 4 §2.4.3 writes both comparisons the same way round, so the
		// first bound is the lower one under `<=` and the upper one under `>=`.
		const rising = interval[2] === "<=";
		if (rising !== (interval[4] === "<=")) continue;
		const low = _minMaxCondition(
			interval[3],
			rising ? ">=" : "<=",
			interval[1]
		);
		const high = _minMaxCondition(
			interval[3],
			rising ? "<=" : ">=",
			interval[5]
		);
		const pair = `${low} and ${high}`;
		const negated =
			index > 0 && _queryKeyword(parts[filled[index - 1]]) === "not";
		parts[at] = disjunction || negated ? `(${pair})` : pair;
	}
};

/**
 * Collapse an `and` of two one-sided ranges on one feature into the interval
 * that says the same (`(width>=1px) and (width<=2px)` is `(1px<=width<=2px)`),
 * in the printed parts, in place. Only a bounded pair — two comparisons the
 * same way round are not an interval, and `or` is not a conjunction.
 * @param {string[]} parts the prelude's printed parts
 * @returns {void}
 */
const _collapseRangeInterval = (parts) => {
	if (!_S._transforms.shortenMediaQueries) return;
	/** @type {number[]} */
	const filled = [];
	for (let i = 0; i < parts.length; i++) {
		if (parts[i].length !== 0 && parts[i] !== _SEP) filled.push(i);
	}
	for (let i = 0; i + 2 < filled.length; i++) {
		if (!asciiCaseInsensitiveMatch(parts[filled[i + 1]], "and")) continue;
		const left = _RANGE_CONDITION_RE.exec(parts[filled[i]]);
		if (left === null) continue;
		const right = _RANGE_CONDITION_RE.exec(parts[filled[i + 2]]);
		if (right === null) continue;
		if (!asciiCaseInsensitiveMatch(left[1], right[1])) continue;
		// One has to bound it from below and the other from above.
		const lower = left[2].startsWith(">") ? left : right;
		const upper = left[2].startsWith(">") ? right : left;
		if (!lower[2].startsWith(">") || !upper[2].startsWith("<")) continue;
		const low = `${lower[3]}${lower[2] === ">=" ? "<=" : "<"}`;
		parts[filled[i]] = `(${low}${left[1]}${upper[2]}${upper[3]})`;
		parts[filled[i + 1]] = "";
		parts[filled[i + 2]] = "";
		i += 2;
	}
};

/**
 * Drop the universal selector a compound already implies (`*:before` is
 * `:before`), in the printed parts, in place. `*` carries no specificity and
 * matches every element, so a qualified compound means the same without it.
 * @param {string[]} parts the prelude's printed parts
 * @returns {void}
 */
/**
 * Whether the simple selector at `at` selects a featureless element, which
 * matches no type or universal selector — so the `*` before it is what keeps
 * the rule from matching, not a spelling of it.
 * @param {string[]} parts the printed selector, one piece per token
 * @param {number} at where the simple selector starts
 * @returns {boolean} true when the `*` before it has to stay
 */
const _selectsFeatureless = (parts, at) => {
	if (parts[at] !== ":") return false;
	let name = at + 1;
	while (name < parts.length && parts[name].length === 0) name++;
	if (name >= parts.length) return false;
	// A functional pseudo-class arrives with its arguments attached.
	const open = parts[name].indexOf("(");
	const named = open === -1 ? parts[name] : parts[name].slice(0, open);
	return FEATURELESS_PSEUDO_CLASSES.has(toLowerCaseIfNeeded(named));
};

/**
 * Drop the universal selector a compound already implies (`*:before` is
 * `:before`), in the printed parts, in place. `*` carries no specificity and
 * matches every element, so a qualified compound means the same without it —
 * except before a featureless pseudo-class, which no element with features
 * matches.
 * @param {string[]} parts the prelude's printed parts
 * @returns {void}
 */
const _dropImpliedUniversalSelector = (parts) => {
	// A native search first: almost no selector holds a `*` at all.
	if (!_S._transforms.shortenSelectors || !parts.includes("*")) return;
	let previous = -1;
	for (let i = 0; i < parts.length; i++) {
		const part = parts[i];
		if (part.length === 0) continue;
		if (
			previous !== -1 &&
			parts[previous] === "*" &&
			COMPOUND_CONTINUATIONS.has(part.charAt(0)) &&
			(previous === 0 || parts[previous - 1] !== "|") &&
			!_selectsFeatureless(parts, i)
		) {
			parts[previous] = "";
		}
		previous = i;
	}
};

/**
 * Split a selector list at the commas that separate its selectors. A comma
 * inside `:is(…)`, an attribute value or a string belongs to one selector.
 * @param {string} prelude the printed selector list
 * @returns {string[]} the selectors, in written order
 */
const _splitSelectorList = (prelude) => {
	/** @type {string[]} */
	const out = [];
	let depth = 0;
	let quote = 0;
	let start = 0;
	for (let i = 0; i < prelude.length; i++) {
		const code = prelude.charCodeAt(i);
		if (code === CC_REVERSE_SOLIDUS) {
			i++;
			continue;
		}
		if (quote !== 0) {
			if (code === quote) quote = 0;
			continue;
		}
		if (code === CC_QUOTATION_MARK || code === CC_APOSTROPHE) {
			quote = code;
		} else if (code === CC_LEFT_PARENTHESIS || code === CC_LEFT_SQUARE) {
			depth++;
		} else if (code === CC_RIGHT_PARENTHESIS || code === CC_RIGHT_SQUARE) {
			depth--;
		} else if (code === CC_COMMA && depth === 0) {
			out.push(prelude.slice(start, i));
			start = i + 1;
		}
	}
	out.push(prelude.slice(start));
	return out;
};

// A substitution expands to a whole token sequence, so two identical references
// are not one repeated value: with `--x:1px 2px`, `margin:var(--x) var(--x)` is
// four values, not two. `--name(` is a custom function (CSS Functions).
const _SUBSTITUTION_NAMES = [...SUBSTITUTION_FUNCTIONS];
// Every length a substitution function's name has, as a bit mask.
const _SUBSTITUTION_NAME_LENGTHS = _SUBSTITUTION_NAMES.reduce(
	(mask, fn) => mask | (1 << fn.length),
	0
);

/**
 * Whether the `(` at `at` closes a substitution: one of the substitution
 * functions standing as a whole name (ASCII case-insensitively), or a name
 * holding `--` — the same shapes the regex `(?:^|[^\w-])NAME\(|--[\w-]*\(` reads.
 * @param {string} text the text
 * @param {number} from where the name before the `(` may begin at the earliest
 * @param {number} at the `(`'s offset
 * @returns {boolean} true when a substitution stands there
 */
const _substitutionCallAt = (text, from, at) => {
	let start = at;
	let hyphens = 0;
	let custom = false;
	while (start > from) {
		const c = text.charCodeAt(start - 1);
		if (c === CC_HYPHEN_MINUS) {
			if (++hyphens === 2) custom = true;
		} else if (isLetter(c) || isDigit(c) || c === CC_LOW_LINE) {
			hyphens = 0;
		} else {
			break;
		}
		start--;
	}
	if (custom) return true;
	const length = at - start;
	if (length > 31 || (_SUBSTITUTION_NAME_LENGTHS & (1 << length)) === 0) {
		return false;
	}
	for (let i = 0; i < _SUBSTITUTION_NAMES.length; i++) {
		if (
			rangeAsciiCaseInsensitiveMatch(text, start, at, _SUBSTITUTION_NAMES[i])
		) {
			return true;
		}
	}
	return false;
};

/**
 * The substitution functions a printed value calls, outside its strings.
 * @param {string} value a printed declaration value
 * @returns {string[] | null} their lowercased names, or null where it calls none
 */
const _heldSubstitutions = (value) => {
	/** @type {string[] | null} */
	let out = null;
	let quote = 0;
	for (let i = 0; i < value.length; i++) {
		const cc = value.charCodeAt(i);
		if (cc === CC_REVERSE_SOLIDUS) {
			i++;
		} else if (quote !== 0) {
			if (cc === quote) quote = 0;
		} else if (cc === CC_QUOTATION_MARK || cc === CC_APOSTROPHE) {
			quote = cc;
		} else if (cc === CC_LEFT_PARENTHESIS && _substitutionCallAt(value, 0, i)) {
			let start = i;
			while (start > 0 && isIdentCodePoint(value.charCodeAt(start - 1))) {
				start--;
			}
			if (out === null) out = [];
			out.push(toLowerCaseIfNeeded(value.slice(start, i)));
		}
	}
	return out;
};

/**
 * Whether a value contains a substitution function such as `var()` or `env()`,
 * so its tokens are not what the engine will see there.
 * @param {string} text the text to look in
 * @returns {boolean} true when a substitution may stand in it
 */
const containsSubstitutionFunction = (text) => {
	// Every shape accepted ends in `(`, so only each `(` is looked behind.
	const first = text.indexOf("(");
	for (let at = first; at !== -1; at = text.indexOf("(", at + 1)) {
		if (_substitutionCallAt(text, 0, at)) return true;
	}
	return (
		first !== -1 &&
		text.includes("\\") &&
		_escapedSubstitutionIn(text, 0, text.length)
	);
};

/**
 * Whether a range calls a substitution by an escaped name, where the look behind
 * above stops at the `\`: each name is read forward, escapes resolved (§4.3.7).
 * @param {string} text the text
 * @param {number} from the range's start offset
 * @param {number} to the range's end offset (exclusive)
 * @returns {boolean} true when a substitution may stand in it
 */
const _escapedSubstitutionIn = (text, from, to) => {
	let start = -1;
	for (let i = from; i < to; i++) {
		const c = text.charCodeAt(i);
		if (
			c === CC_REVERSE_SOLIDUS &&
			i + 1 < to &&
			!_isNewline(text.charCodeAt(i + 1))
		) {
			if (start === -1) start = i;
			let next = i + 1;
			if (isHexDigit(text.charCodeAt(next))) {
				const limit = Math.min(to, next + 6);
				while (next < limit && isHexDigit(text.charCodeAt(next))) next++;
				if (next < to && isAsciiWhitespace(text.charCodeAt(next))) {
					next = consumeExtraNewline(text.charCodeAt(next), text, next + 1);
				}
			} else {
				next++;
			}
			i = next - 1;
		} else if (c >= 0x80 || _isIdentCodePoint(c)) {
			if (start === -1) start = i;
		} else {
			if (c === CC_LEFT_PARENTHESIS && start !== -1) {
				const name = asciiLowercaseName(
					unescapeIdentifier(text.slice(start, i))
				);
				if (SUBSTITUTION_FUNCTIONS.has(name) || name.includes("--")) {
					return true;
				}
			}
			start = -1;
		}
	}
	return false;
};

/**
 * @param {number} from the first offset to search
 * @returns {number} the next opening parenthesis, or -1
 */
const _nextSubstitutionOpening = (from) => {
	// A native search can pass the requested span. Reuse its empty interval
	// across later declarations, including an exhausted suffix of the input.
	if (
		_substitutionSearchFrom !== -1 &&
		from >= _substitutionSearchFrom &&
		(_substitutionSearchOpening === -1 || from <= _substitutionSearchOpening)
	) {
		return _substitutionSearchOpening;
	}
	_substitutionSearchFrom = from;
	return (_substitutionSearchOpening = _S._input.indexOf("(", from));
};

let _substitutionSearchFrom = -1;
let _substitutionSearchOpening = -1;

/**
 * {@link containsSubstitutionFunction} over a span of the input, which is cut out only once
 * the span turns out to hold a `(` at all.
 * @param {number} from the span's start offset
 * @param {number} to the span's end offset
 * @returns {boolean} true when a substitution may stand in it
 */
const _hasSubstitutionInSpan = (from, to) => {
	// The walk asks for a declaration's span and the printer asks again for the
	// same one, so one entry spares the second scan and the slice under it.
	if (from === _substitutionSpanFrom && to === _substitutionSpanTo) {
		return _substitutionSpanHas;
	}
	let has = false;
	let calls = false;
	// Each `(` is looked behind, as {@link containsSubstitutionFunction} does, and found the
	// same way: a native scan rather than a read per code point, since a value
	// holding no call at all is the common one and is most of the span.
	for (
		let at = _nextSubstitutionOpening(from);
		at !== -1 && at < to;
		at = _nextSubstitutionOpening(at + 1)
	) {
		calls = true;
		if (_substitutionCallAt(_S._input, from, at)) {
			has = true;
			break;
		}
	}
	if (!has && calls && _rangeHasEscape(from, to)) {
		has = _escapedSubstitutionIn(_S._input, from, to);
	}
	_substitutionSpanFrom = from;
	_substitutionSpanTo = to;
	_substitutionSpanHas = has;
	return has;
};

// The span `_hasSubstitutionInSpan` last answered for. Offsets index the current
// input, so a new stylesheet resets them.
let _substitutionSpanFrom = -1;
let _substitutionSpanTo = -1;
let _substitutionSpanHas = false;

// Every node type that is a whole component value on its own. What is left out
// is what only ever stands next to one: a delimiter, a colon, a comma, a CDO.
/** @type {Set<number>} */
const _STANDS_ALONE = new Set([
	NodeType.Ident,
	NodeType.Function,
	NodeType.AtKeyword,
	NodeType.Hash,
	NodeType.String,
	NodeType.Url,
	NodeType.Number,
	NodeType.Percentage,
	NodeType.Dimension,
	NodeType.SimpleBlock
]);

/**
 * @param {number} type a node type
 * @returns {boolean} whether it is a whole component value on its own
 */
const _standsAlone = (type) => _STANDS_ALONE.has(type);

/**
 * Split a declaration value into its top-level components. Whitespace parts
 * them, and so does a `/` delim — it separates `border-radius`'s two boxes and
 * needs no whitespace of its own. A comment is no tree node, so the gap it
 * leaves between two children parts them too: it ends both tokens, and a
 * rewritten value joins its components with a space that has to stand where
 * the comment did.
 * @param {CssPath} path the accessor positioned on the declaration
 * @param {Node} node the declaration whose value's children are read
 * @param {PrintContext} writer the print context (children's printed text)
 * @returns {string[]} the components, with a `/` delim as its own entry
 */
const _valueComponents = (path, node, writer) => {
	/** @type {string[]} */
	const components = [];
	let current = "";
	let previousEnd = -1;
	let previousType = -1;
	const count = path.childCount(node);
	for (let at = 0; at < count; at++) {
		const child = path.child(at, node);
		const type = path.type(child);
		const text = writer.get(child);
		const start = _startOf(child);
		// Two tokens written against each other are one component only where one
		// cannot stand alone: `1px#000` is a length and a color, not one of each.
		if (
			current.length !== 0 &&
			(start !== previousEnd ||
				(_standsAlone(previousType) && _standsAlone(type)))
		) {
			components.push(current);
			current = "";
		}
		previousEnd = _endOf(child);
		previousType = type;
		if (
			type === NodeType.Whitespace ||
			(type === NodeType.Delim && text === "/")
		) {
			if (current.length !== 0) components.push(current);
			current = "";
			if (type === NodeType.Delim) components.push("/");
			continue;
		}
		current += text;
	}
	if (current.length !== 0) components.push(current);
	return components;
};

/**
 * A custom property's value, token for token: every token is written back as
 * written, and a run of whitespace and dropped comments between two of them is
 * one boundary — the space they need, or nothing where a comma or a block edge
 * is already one, which leaves every `var()` the same token stream.
 * @param {CssPath} path the accessor positioned on the declaration
 * @param {Node} node the declaration, function or block whose tokens are printed
 * @param {PrintContext} writer the print context (holds the kept comments)
 * @param {number} from source offset the tokens start at
 * @param {number} to source offset they end at
 * @returns {string} the printed value
 */
const _customPropertyValue = (path, node, writer, from, to) => {
	let out = "";
	let at = from;
	// The boundary held back: whether a whitespace token stood in it, whether a
	// dropped comment did, and whether a comma closed it.
	let spaced = false;
	let dropped = false;
	let comma = false;
	// Whether the token last printed was a `+`/`-` operator (see below).
	let operator = false;
	// Whether it was a substitution, whose `)` is not where its value ends.
	let substituted = false;
	// Read where the child list lies, as `_appendChildTexts` does, rather than
	// materialized into an array per value only to be walked once.
	const i0 = _nodeIndex(node);
	const first = _listStarts[i0];
	const last = first + _listLens[i0];
	for (let k = first; k < last; k++) {
		const child = /** @type {ComponentValue} */ (_nodeRef(_flat[k]));
		const start = _startOf(child);
		// Every other token is a child of its own, so a gap is comments only.
		if (at !== start) {
			const kept = writer.takeInserts(at, start);
			if (kept === "") {
				dropped = true;
			} else {
				if (spaced && out !== "") out += " ";
				out += kept;
				spaced = false;
				dropped = false;
				comma = false;
				operator = false;
			}
		}
		at = _endOf(child);
		const type = path.type(child);
		if (type === NodeType.Whitespace) {
			spaced = true;
			continue;
		}
		if (type === NodeType.Comma) {
			out += ",";
			spaced = false;
			dropped = false;
			comma = true;
			operator = false;
			continue;
		}
		const text = _customPropertyToken(path, child, writer);
		// WHY: CSS Values 4 §10.1 requires whitespace on both sides of a `+` or `-`
		// in a math function, and this value is a token stream whose consuming
		// context is unknown here — `--tail:(1px) - (2px)` reaches a `calc()` by
		// substitution. So a space beside one of those is the operator's own rather
		// than the delimiter's, and outlives the rule below.
		const operates = type === NodeType.Delim && (text === "+" || text === "-");
		// A comma or a block delimiter is a boundary of its own, so the whitespace
		// beside one says nothing — except before `(`, which an ident in front of
		// makes a function token, and that is what `_wouldFuseTokens` answers.
		if (!comma && out !== "") {
			const last = out.charCodeAt(out.length - 1);
			const fuses = dropped && _wouldFuseTokens(last, text, out);
			// The value computes to what a `var()` stands for, which may end in a
			// token the next one fuses with, so the space after one stays.
			const parted =
				!operator &&
				!operates &&
				((!substituted &&
					CUSTOM_PROPERTY_CLOSERS.includes(out[out.length - 1])) ||
					CUSTOM_PROPERTY_OPENERS.includes(text[0]));
			if (fuses || (spaced && !parted)) out += " ";
		}
		out += text;
		spaced = false;
		dropped = false;
		comma = false;
		operator = operates;
		if (type === NodeType.Function) {
			const open = text.indexOf("(");
			substituted = open > 0 && _substitutionCallAt(text, 0, open);
		} else {
			substituted = false;
		}
	}
	// A trailing boundary separates the last token from a `)` or the value's end,
	// neither of which it can fuse with; only a kept comment in it still prints.
	if (at !== to) {
		const kept = writer.takeInserts(at, to);
		if (kept !== "") {
			if (spaced && out !== "") out += " ";
			out += kept;
			// A kept comment the input ran out in is closed as its end of input
			// closed it, or the `)` and `}` printed after it would be inside it.
			const opened = kept.lastIndexOf("/*");
			if (
				to === _S._input.length &&
				_blockContentsNodes === null &&
				!kept.includes("*/", opened + 2)
			) {
				out += "*/";
				_S._eofPrinted = "*/";
				_S._eofOpen = "";
			}
		}
	}
	return out;
};

// An input ending on an escape that escapes nothing: an odd run of backslashes.
const _LONE_BACKSLASH_AT_END_REGEXP = /(?:^|[^\\])(?:\\\\)*\\$/;

/** What makes a token worth reading through rather than writing back whole. */
const CUSTOM_PROPERTY_REWRITABLE_RE = /[\t\n\f\r ,]|\/\*/;

// Block delimiters a token cannot fuse across, so whitespace beside one goes.
// `(` is not among the openers: an ident in front of it makes a function token.
const CUSTOM_PROPERTY_CLOSERS = ")]}";
const CUSTOM_PROPERTY_OPENERS = "[{";

/**
 * One token of a custom property's value, as written — recursing into a
 * function or block so the boundaries nested in one print as they do at the top
 * level. A source holding neither whitespace, a comma nor a comment holds none
 * at any depth, so it is written back whole. The one token not written as it
 * stands is a url or a string the input ran out of inside, which the engine
 * closes there and so does this.
 * @param {CssPath} path the accessor positioned on the declaration
 * @param {ComponentValue} child the token to print
 * @param {PrintContext} writer the print context (holds the kept comments)
 * @returns {string} the printed token
 */
const _customPropertyToken = (path, child, writer) => {
	// Rewriting: the token already printed minified, its own nesting included.
	if (_S._rewriteCustomProperties) return writer.get(child);
	const source = _printedSource(child);
	const type = path.type(child);
	// WHY: Chromium keeps a value the input ran out inside as written, open, but
	// in a stylesheet the rule's `}` is printed after it, lands inside what is
	// open and loses the whole declaration — so there it is closed. A `style=""`
	// list prints nothing after it, and there it stays as the engine keeps it.
	const closes =
		_blockContentsNodes === null && _endOf(child) === _S._input.length;
	if (
		(type !== NodeType.Function && type !== NodeType.SimpleBlock) ||
		!CUSTOM_PROPERTY_REWRITABLE_RE.test(source)
	) {
		if (!closes) return source;
		_S._eofPrinted = _closeAtEof(source);
		// An escape the input ran out in is a token the engine rewrites, and writes
		// back closed; anything else it keeps as written, open.
		_S._eofOpen = _LONE_BACKSLASH_AT_END_REGEXP.test(_S._input)
			? _S._eofPrinted
			: source;
		return _S._eofPrinted;
	}
	const start = _startOf(child);
	const end = _endOf(child);
	let opened = _nameEndOf(child) + 1;
	let closer = ")";
	if (type === NodeType.SimpleBlock) {
		const block = _blockTokenOf(child);
		opened = start + 1;
		closer = block === "[" ? "]" : block === "{" ? "}" : ")";
	}
	// Closed at EOF, the source has no closer to strip, and one is written only
	// for the reason above. Read off the children, since a string running to EOF
	// can end on the closer's character (`f(")`) without closing anything.
	const i0 = _nodeIndex(child);
	const count = _listLens[i0];
	const lastEnd =
		count === 0
			? opened
			: _endOf(
					/** @type {ComponentValue} */ (
						_nodeRef(_flat[_listStarts[i0] + count - 1])
					)
				);
	// Only what follows the last child, less whitespace and closed comments: a
	// comment the input ran out in can end on the closer's character too.
	const closed =
		lastEnd < end &&
		_S._input
			.slice(lastEnd, end)
			.replace(/\/\*[\s\S]*?\*\//g, "")
			.trim() === closer;
	const inner = _customPropertyValue(
		path,
		child,
		writer,
		opened,
		closed ? end - 1 : end
	);
	const text = `${_S._input.slice(start, opened)}${inner}${
		closed || closes ? closer : ""
	}`;
	if (closes) {
		// What it prints left open: its own closer, and what its last token closed.
		const last = _S._eofPrinted;
		const innerOpen =
			last !== "" && inner.endsWith(last)
				? inner.slice(0, inner.length - last.length) + _S._eofOpen
				: inner;
		_S._eofPrinted = text;
		_S._eofOpen = `${_S._input.slice(start, opened)}${innerOpen}${
			closed ? closer : ""
		}`;
	}
	return text;
};

/**
 * Whether two box components fill their slot alike, read as they will print
 * rather than as they were written: a zero length loses its unit later, so
 * `0em` and `0rem` are one value.
 * @param {string} a one box component
 * @param {string} b another box component
 * @returns {boolean} true when the two print the same
 */
const _sameBoxValue = (a, b) =>
	a === b || _dropZeroLengthUnit(a) === _dropZeroLengthUnit(b);

/**
 * Drop the box values CSS's `{1,4}` notation already implies: an omitted value
 * is copied from the opposite side, so a 4th equal to the 2nd, a 3rd equal to
 * the 1st and a 2nd equal to the 1st are each redundant.
 * @param {string[]} values one box's components
 * @returns {string[] | null} the kept values, or `null` when the box cannot be collapsed
 */
const _collapseBox = (values) => {
	const n = values.length;
	if (n === 0 || n > 4) return null;
	for (const value of values) {
		if (containsSubstitutionFunction(value)) return null;
		if (n > 1 && CSS_WIDE_KEYWORDS.has(toLowerCaseIfNeeded(value))) {
			return null;
		}
	}
	let end = n;
	if (end === 4 && _sameBoxValue(values[3], values[1])) end = 3;
	if (end === 3 && _sameBoxValue(values[2], values[0])) end = 2;
	if (end === 2 && _sameBoxValue(values[1], values[0])) end = 1;
	return values.slice(0, end);
};

/**
 * Whether a shorthand's slots mix a bare non-zero number with a length or a
 * percentage. No box slot reads both, so one of them was never read at all.
 * @param {string[]} values one shorthand's slot values
 * @returns {boolean} true when the two kinds stand together
 */
const _mixesBareNumberWithLength = (values) => {
	let bareNumber = false;
	let measured = false;
	for (const value of values) {
		const kind = _componentKind(value);
		if (kind === _COMPONENT_NUMBER) bareNumber = true;
		else if (kind !== _COMPONENT_OTHER) measured = true;
	}
	return bareNumber && measured;
};

// What a shorthand component is, coarsely: a fold is safe only between two of a
// kind, a slot taking one taking the other. `0` is a length wherever a dimension
// is, and a bare non-zero number is neither — `padding:.25` is invalid.
const _COMPONENT_OTHER = 0;
const _COMPONENT_LENGTH = 1;
const _COMPONENT_PERCENTAGE = 2;
const _COMPONENT_NUMBER = 3;

/**
 * @param {string} component one printed component of a shorthand's value
 * @returns {number} one of the `_COMPONENT_*` kinds
 */
const _componentKind = (component) => {
	if (!NUMERIC_COMPONENT_RE.test(component)) return _COMPONENT_OTHER;
	const last = component.charCodeAt(component.length - 1);
	if (last === CC_PERCENTAGE) return _COMPONENT_PERCENTAGE;
	// A unit is what makes a non-zero number a length, and a digit is no unit —
	// `.25` is a bare number, which no box slot taking a length accepts.
	if (!isDigit(last)) return _COMPONENT_LENGTH;
	return ZERO_NUMBER_RE.test(component) ? _COMPONENT_LENGTH : _COMPONENT_NUMBER;
};

// A number with an optional sign and fraction, then a unit or `%` or nothing.
const NUMERIC_COMPONENT_RE = /^[-+]?(?:\d+(?:\.\d+)?|\.\d+)(?:%|[a-z]+)?$/i;
// The same, written zero — every spelling of it, so `0.0` is a length too.
const ZERO_NUMBER_RE = /^[-+]?0*(?:\.0*)?$/;

/**
 * One box or pair shorthand's value read back as the slots it fills — `1px 2px`
 * as all four sides, so a longhand can take one of them over.
 * @param {string} value the shorthand's printed value
 * @param {number} slots how many the shorthand holds (4 for a box, 2 for a pair)
 * @returns {string[] | null} the filled slots, or null when it fills none of them
 */
const _expandBox = (value, slots) => {
	const written = _splitPrintedComponents(value);
	if (written.length === 0 || written.length > slots) return null;
	const out = [...written];
	// The `{1,4}` rule: an omitted slot takes the one two back, and the second
	// takes the first.
	for (let i = written.length; i < slots; i++) out.push(out[i < 2 ? 0 : i - 2]);
	return out;
};

/**
 * Fold a longhand into the shorthand of its own family standing directly before
 * it, where the merged spelling is shorter than the two declarations. Adjacent
 * only: anything between them is read between them, so folding would move what
 * it wrote past it.
 * @param {CssPath} path the accessor
 * @param {Node[]} items the block's children
 * @param {string[]} texts their printed text, rewritten in place
 * @param {Set<number>} superseded the indices already dropped, added to here
 * @returns {void}
 */
const _foldFollowingLonghands = (path, items, texts, superseded) => {
	let shorthand = -1;
	/** @type {Set<string> | null} */
	let written = null;
	/**
	 * Whether a declaration is a legacy alias the prefix pass drops below, its
	 * property written: it prints nothing, so nothing stands where it stood.
	 * @param {number} at the declaration's index
	 * @returns {boolean} true when it goes
	 */
	const dropsWithPrefix = (at) => {
		if (!_S._prefixingOn || texts[at].charCodeAt(0) !== CC_HYPHEN_MINUS) {
			return false;
		}
		const property = _printedProperty(texts[at], texts[at].indexOf(":"));
		const base = _prefixedPropertyName(property);
		if (base === undefined) return false;
		if (written === null) {
			written = new Set();
			for (const text of texts) {
				if (text.length !== 0) {
					written.add(_printedProperty(text, text.indexOf(":")));
				}
			}
		}
		return written.has(base) && _prefixedPropertyRemovable(base, property);
	};
	// Each declaration's property, read once: the walk back from every longhand
	// asks again of the ones before it, and a fold keeps the property it rewrites.
	/** @type {(string | undefined)[]} */
	const properties = [];
	// The number `_declarationTable` gives each, or -1 for one it does not name.
	/** @type {number[]} */
	const ids = [];
	const table = _declarationTable();
	/**
	 * The property a declaration sets, read through `_canonicalProperty`.
	 * @param {number} at the declaration's index
	 * @returns {string} its property
	 */
	const propertyAt = (at) => {
		let property = properties[at];
		if (property === undefined) {
			const printed = _printedProperty(texts[at], texts[at].indexOf(":"));
			const id = table.ids.get(printed);
			if (id === undefined) {
				property = printed;
				ids[at] = -1;
			} else {
				ids[at] = table.canonical[id];
				property = table.names[ids[at]];
			}
			properties[at] = property;
		}
		return property;
	};
	/**
	 * Whether two declarations must keep their order, as `_orderMatters` reads it.
	 * @param {number} a a declaration's index, its property already read
	 * @param {number} b another's
	 * @returns {boolean} true when the two must keep their order
	 */
	const orderMattersAt = (a, b) => {
		const property = propertyAt(b);
		return ids[a] !== -1 && ids[b] !== -1
			? _orderMattersById(table, ids[a], ids[b])
			: _orderMatters(/** @type {string} */ (properties[a]), property);
	};
	for (let i = 0; i < items.length; i++) {
		// A dropped declaration prints nothing, so nothing stands where it stood.
		if (texts[i].length === 0 || superseded.has(i)) continue;
		if (path.type(items[i]) !== NodeType.Declaration) {
			shorthand = -1;
			continue;
		}
		if (dropsWithPrefix(i)) continue;
		if (
			shorthand !== -1 &&
			_foldIntoEarlier(
				path,
				items,
				texts,
				superseded,
				i,
				dropsWithPrefix,
				propertyAt,
				orderMattersAt
			)
		) {
			superseded.add(i);
			continue;
		}
		shorthand = i;
	}
};

// Every property some shorthand sets, so a declaration that is none returns
// before walking back for one.
/** @type {Set<string> | null} */
let _anyLonghand = null;

/**
 * Fold a longhand into the nearest earlier declaration of its family that takes
 * it, past declarations neither can interact with, so moving its write up past
 * them changes nothing they set.
 * @param {CssPath} path the accessor
 * @param {Node[]} items the block's children
 * @param {string[]} texts their printed text, rewritten in place
 * @param {Set<number>} superseded the indices already dropped
 * @param {number} from the longhand's index
 * @param {(at: number) => boolean} drops whether a declaration is dropped later
 * @param {(at: number) => string} propertyAt the property a declaration sets, read through `_canonicalProperty`
 * @param {(a: number, b: number) => boolean} orderMattersAt `_orderMatters` of two declarations' properties, the first already read
 * @returns {boolean} true when it was folded into one before it
 */
const _foldIntoEarlier = (
	path,
	items,
	texts,
	superseded,
	from,
	drops,
	propertyAt,
	orderMattersAt
) => {
	if (_anyLonghand === null) {
		_anyLonghand = new Set();
		for (const longhands of getShorthandLonghands().values()) {
			for (const longhand of longhands) _anyLonghand.add(longhand);
		}
	}
	const property = propertyAt(from);
	const bare =
		property.charCodeAt(0) === CC_HYPHEN_MINUS
			? property.replace(_VENDOR_ITEM_PREFIX_RE, "")
			: property;
	if (!_anyLonghand.has(bare)) {
		return false;
	}
	for (let at = from - 1; at >= 0; at--) {
		if (texts[at].length === 0 || superseded.has(at)) continue;
		if (path.type(items[at]) !== NodeType.Declaration) return false;
		if (drops(at)) continue;
		if (orderMattersAt(from, at)) {
			return _foldInto(path, items, texts, at, from);
		}
	}
	return false;
};

/**
 * @param {CssPath} path the accessor
 * @param {Node[]} items the block's children
 * @param {string[]} texts their printed text, rewritten in place
 * @param {number} at the shorthand's index
 * @param {number} from the longhand's index
 * @returns {boolean} true when the two were folded into one
 */
const _foldInto = (path, items, texts, at, from) => {
	if (_isImportant(items[at]) !== _isImportant(items[from])) return false;
	const shorthandText = texts[at];
	const longhandText = texts[from];
	const shorthandColon = shorthandText.indexOf(":");
	const longhandColon = longhandText.indexOf(":");
	if (shorthandColon <= 0 || longhandColon <= 0) return false;
	const property = _printedProperty(shorthandText, shorthandColon);
	const lines = _GRID_PLACEMENT_SLOTS.get(property);
	if (lines !== undefined) {
		const folded = _foldGridLine(
			lines,
			_printedValue(shorthandText, shorthandColon),
			_printedProperty(longhandText, longhandColon),
			_printedValue(longhandText, longhandColon)
		);
		if (folded === null) return false;
		const important = _isImportant(items[at]) ? _IMPORTANT : "";
		const text = `${property}:${folded}${important};`;
		if (text.length >= shorthandText.length + longhandText.length) return false;
		texts[at] = text;
		return true;
	}
	if (
		property === "font" &&
		_printedProperty(longhandText, longhandColon) === "line-height"
	) {
		// As written: a family name keeps its case, which engines report back.
		const folded = _foldFontLineHeight(
			shorthandText.slice(
				shorthandColon + 1,
				_printedValueEnd(shorthandText, shorthandColon)
			),
			_printedValue(longhandText, longhandColon)
		);
		if (folded === null) return false;
		const important = _isImportant(items[at]) ? _IMPORTANT : "";
		texts[at] = `font:${folded}${important};`;
		return true;
	}
	const sides = BOX_LONGHANDS.get(property) || PAIR_LONGHANDS.get(property);
	if (sides === undefined) {
		// As written: a value's names keep their case, so it is not lowercased.
		const folded = _foldIntoSlots(
			property,
			shorthandText.slice(
				shorthandColon + 1,
				_printedValueEnd(shorthandText, shorthandColon)
			),
			_printedProperty(longhandText, longhandColon),
			longhandText.slice(
				longhandColon + 1,
				_printedValueEnd(longhandText, longhandColon)
			)
		);
		if (folded === null) return false;
		// Never longer: each slot's value is written once, its name no more.
		const important = _isImportant(items[at]) ? _IMPORTANT : "";
		texts[at] = `${property}:${folded}${important};`;
		return true;
	}
	const slot = sides.indexOf(_printedProperty(longhandText, longhandColon));
	if (slot === -1) return false;
	const shorthandValue = _printedValue(shorthandText, shorthandColon);
	const longhandValue = _printedValue(longhandText, longhandColon);
	// A substitution may stand for any number of slots, and a comma list is no
	// box at all — neither reads back as the slots this fills.
	if (
		containsSubstitutionFunction(shorthandValue) ||
		containsSubstitutionFunction(longhandValue)
	) {
		return false;
	}
	if (_valueItems(shorthandValue).length !== 1) return false;
	if (_splitPrintedComponents(longhandValue).length !== 1) return false;
	// Validity is per property, and a component the property rejects invalidates
	// the whole shorthand, losing the slots that were fine. So the two fold only
	// within one narrow numeric class, which every box slot of the family takes.
	const kind = _componentKind(longhandValue);
	if (kind === _COMPONENT_OTHER) {
		if (!_keywordPairFolds(property, sides, shorthandValue, longhandValue)) {
			return false;
		}
	} else {
		for (const component of _splitPrintedComponents(shorthandValue)) {
			if (_componentKind(component) !== kind) return false;
		}
	}
	const filled = _expandBox(shorthandValue, sides.length);
	if (filled === null) return false;
	filled[slot] = longhandValue;
	const collapsed = _collapseBox(filled);
	if (collapsed === null) return false;
	if (
		collapsed.length !== 1 &&
		ONE_VALUE_PAIR_SHORTHANDS.has(property) &&
		!_overflowTwoValuesAllowed
	) {
		return false;
	}
	if (
		collapsed.length !== 1 &&
		PLACE_SHORTHANDS.has(property) &&
		!_placeShorthandAllowed
	) {
		return false;
	}
	const important = _isImportant(items[at]) ? _IMPORTANT : "";
	const folded = `${property}:${_joinSlots(collapsed)}${important};`;
	if (folded.length >= shorthandText.length + longhandText.length) return false;
	texts[at] = folded;
	return true;
};

/**
 * Whether an `overflow` and a longhand of it fold as keywords: each must be one
 * every target reads on both axes, since one it drops voids the whole shorthand.
 * @param {string} property the shorthand's printed property name
 * @param {string[]} sides the shorthand's longhands
 * @param {string} shorthandValue the shorthand's printed value
 * @param {string} longhandValue the longhand's printed value
 * @returns {boolean} true when the two fold
 */
const _keywordPairFolds = (property, sides, shorthandValue, longhandValue) => {
	// No selection names no browser to answer for, so no keyword is known read.
	if (_prefixBrowsers === null || !ONE_VALUE_PAIR_SHORTHANDS.has(property)) {
		return false;
	}
	for (const one of [
		..._splitPrintedComponents(shorthandValue),
		longhandValue
	]) {
		const keyword = toLowerCaseIfNeeded(one);
		for (const side of sides) {
			const since = _valueSupportedFrom(`${side} ${keyword}`);
			if (since === undefined || !_readsAll(since)) return false;
		}
	}
	return true;
};

const _FONT_SIZE_KEYWORDS = new Set([
	"xx-small",
	"x-small",
	"small",
	"medium",
	"large",
	"x-large",
	"xx-large",
	"xxx-large",
	"smaller",
	"larger",
	"math"
]);

/**
 * Whether a `font` component is a `<font-size>` written as a number: a
 * non-negative length or percentage, or a lone zero (a weight is a bare number).
 * @param {string} value one printed component, its line height cut off
 * @returns {boolean} true when it is one
 */
const _isFontSize = (value) => {
	const numeric = _NUMERIC_RE.exec(value);
	if (numeric === null || value.charCodeAt(0) === CC_HYPHEN_MINUS) return false;
	const unit = numeric[1];
	if (unit === "") return ZERO_NUMBER_RE.test(value);
	return unit === "%" || _LENGTH_UNIT_RE.test(unit);
};

/**
 * Whether a value is one the `font` shorthand's line height slot takes:
 * `normal`, or a non-negative number, percentage or length.
 * @param {string} value the `line-height` value as printed
 * @returns {boolean} true when the slot takes it
 */
const _isFontLineHeight = (value) => {
	if (value.toLowerCase() === "normal") return true;
	const numeric = _NUMERIC_RE.exec(value);
	if (numeric === null || value.charCodeAt(0) === CC_HYPHEN_MINUS) return false;
	const unit = numeric[1];
	return unit === "" || unit === "%" || _LENGTH_UNIT_RE.test(unit);
};

/**
 * A `font` value with a later `line-height` written into its `/` slot, which
 * reads the same: the shorthand resets the line height the longhand then sets.
 * @param {string} font the `font` value as printed
 * @param {string} lineHeight the `line-height` value as printed
 * @returns {string | null} the folded value, or null where it cannot be folded
 */
const _foldFontLineHeight = (font, lineHeight) => {
	if (
		containsSubstitutionFunction(font) ||
		containsSubstitutionFunction(lineHeight)
	) {
		return null;
	}
	if (!_isFontLineHeight(lineHeight)) return null;
	const components = _splitTopLevelSpaces(font);
	for (let i = 0; i < components.length - 1; i++) {
		const component = components[i];
		const slash = component.indexOf("/");
		const size = slash === -1 ? component : component.slice(0, slash);
		if (_isFontSize(size) || _FONT_SIZE_KEYWORDS.has(size.toLowerCase())) {
			// A family that is a CSS-wide keyword voids the `font` already, which
			// leaves the longhand the only line height that applies.
			for (const family of components
				.slice(i + 1)
				.join(" ")
				.split(",")) {
				const name = family.trim().toLowerCase();
				if (CSS_WIDE_KEYWORDS.has(name) || name === "default") return null;
			}
			// `normal` is the line height the shorthand already sets.
			components[i] =
				lineHeight.toLowerCase() === "normal" ? size : `${size}/${lineHeight}`;
			return _joinSlots(components);
		}
	}
	return null;
};

// A `<time>`, which a `transition` or an `animation` reads as its duration
// first and its delay second.
const _TIME_RE = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?(?:s|ms)$/i;
const _ZERO_TIME_RE = /^[+-]?(?:0+\.?0*|\.0+)(?:s|ms)$/i;
// An easing function a layer may hold, the keywords aside.
const _EASING_CALL_RE = /^(?:cubic-bezier|steps|linear)\(/i;

/**
 * A longhand's value written into the shorthand of its own family standing
 * before it, or null where the shorthand's slots cannot be read apart.
 * @param {string} property the shorthand's printed property name
 * @param {string} shorthandValue its printed value
 * @param {string} longhand the longhand's printed property name
 * @param {string} longhandValue its printed value
 * @returns {string | null} the shorthand's new value, or null
 */
const _foldIntoSlots = (property, shorthandValue, longhand, longhandValue) => {
	// A vendor spelling folds only into the shorthand of the same vendor.
	const prefix = /^-[a-z]+-/.exec(property);
	const base = prefix === null ? property : property.slice(prefix[0].length);
	if (prefix !== null && !longhand.startsWith(prefix[0])) return null;
	const slot = prefix === null ? longhand : longhand.slice(prefix[0].length);
	// A substitution may stand for any slot, and a comma list is more than one layer.
	if (
		containsSubstitutionFunction(shorthandValue) ||
		containsSubstitutionFunction(longhandValue)
	) {
		return null;
	}
	const layers = _valueItems(shorthandValue);
	const values = _valueItems(longhandValue);
	if (layers.length !== 1 || values.length !== 1) {
		// One value per layer folds into each. A shorter list the engine repeats
		// computes as written, so folding it would change what a script reads.
		if (
			(base !== "transition" && base !== "animation") ||
			values.length !== layers.length
		) {
			return null;
		}
		const out = [];
		for (let i = 0; i < layers.length; i++) {
			const layer = _foldIntoSlots(property, layers[i], longhand, values[i]);
			if (layer === null) return null;
			out.push(layer);
		}
		return out.join(",");
	}
	const components = _splitPrintedComponents(shorthandValue);
	const value = _splitPrintedComponents(longhandValue);
	if (value.length !== 1 || components.length === 0) return null;
	for (const one of [...components, value[0]]) {
		if (CSS_WIDE_KEYWORDS.has(toLowerCaseIfNeeded(one))) return null;
	}
	if (base === "flex") {
		// IE10's `-ms-flex` reads omitted factors the 2012 draft's way.
		if (prefix !== null && prefix[0] === "-ms-") return null;
		const factors = _flexFactors(components);
		const at = ["flex-grow", "flex-shrink", "flex-basis"].indexOf(slot);
		if (factors === null || at === -1) return null;
		if (_BARE_NUMBER_RE.test(value[0]) !== (at !== 2)) return null;
		factors[at] = value[0];
		return _joinSlots(_shortestAuthoredFlex(factors) || factors);
	}
	if (base === "transition") {
		return _foldTransitionSlot(components, slot, value[0]);
	}
	if (base === "animation") {
		return _foldAnimationSlot(components, slot, value[0]);
	}
	const longhands = FAMILY_LONGHANDS.get(base);
	// `text-wrap` was one keyword before it had longhands, and an engine reading
	// it so drops the whole of a value with two.
	if (
		longhands === undefined ||
		base === "text-wrap" ||
		!_shorthandSlotsReadable(base)
	) {
		return null;
	}
	const filled = _familySlots(longhands, components);
	const taken = _familySlots(longhands, value);
	if (filled === null || taken === null || !taken.has(slot)) return null;
	filled.set(slot, value[0]);
	const order = longhands.filter((name) => filled.has(name));
	return _familyValue(
		base,
		order,
		order.map((name) => /** @type {string} */ (filled.get(name)))
	);
};

/**
 * A `transition` longhand written into a one-layer `transition`, its slots
 * then written in the order the printer writes an authored one.
 * @param {string[]} components the layer's components
 * @param {string} slot the longhand's unprefixed name
 * @param {string} value its one component
 * @returns {string | null} the layer's new value, or null
 */
const _foldTransitionSlot = (components, slot, value) => {
	const names = [];
	const times = [];
	const easings = [];
	const behaviors = [];
	for (const one of components) {
		const lower = toLowerCaseIfNeeded(one);
		if (_TIME_RE.test(one)) {
			times.push(one);
		} else if (EASING_KEYWORDS.has(lower) || _EASING_CALL_RE.test(one)) {
			easings.push(one);
		} else if (TRANSITION_BEHAVIORS.has(lower)) {
			behaviors.push(one);
		} else {
			names.push(one);
		}
	}
	// An easing keyword is a property name too, so which slot it fills is the
	// engine's to say where no other name stands beside it.
	if (names.length > 1 || times.length > 2 || easings.length > 1) return null;
	if (behaviors.length > 1) return null;
	const lower = toLowerCaseIfNeeded(value);
	if (slot === "transition-duration" || slot === "transition-delay") {
		if (!_TIME_RE.test(value)) return null;
		if (slot === "transition-duration") {
			times[0] = value;
		} else {
			// A delay is the second time, so the first has to be written.
			if (times.length === 0) return null;
			times[1] = value;
		}
	} else if (slot === "transition-timing-function") {
		if (!EASING_KEYWORDS.has(lower) && !_EASING_CALL_RE.test(value)) {
			return null;
		}
		easings[0] = value;
	} else if (slot === "transition-behavior") {
		if (!TRANSITION_BEHAVIORS.has(lower)) return null;
		behaviors[0] = value;
	} else if (slot === "transition-property") {
		// A name that is a keyword of another slot reads as that slot.
		if (
			_TIME_RE.test(value) ||
			EASING_KEYWORDS.has(lower) ||
			TRANSITION_BEHAVIORS.has(lower) ||
			lower === "none"
		) {
			return null;
		}
		names[0] = value;
	} else {
		return null;
	}
	// What a slot holding its own initial says, it says unwritten too.
	if (times.length === 2 && _ZERO_TIME_RE.test(times[1])) times.pop();
	if (times.length === 1 && _ZERO_TIME_RE.test(times[0])) times.pop();
	if (easings.length === 1 && asciiCaseInsensitiveMatch(easings[0], "ease")) {
		easings.pop();
	}
	if (
		behaviors.length === 1 &&
		asciiCaseInsensitiveMatch(behaviors[0], "normal")
	) {
		behaviors.pop();
	}
	const rest = [...times, ...easings, ...behaviors];
	const named =
		names.length === 1 &&
		asciiCaseInsensitiveMatch(names[0], "all") &&
		rest.length > 0
			? []
			: names;
	const out = [...named, ...rest];
	return out.length === 0 ? "all" : _joinSlots(out);
};

/**
 * An `animation` longhand written into a one-layer `animation` whose every
 * component reads as one slot alone. The new component goes last, where an
 * empty slot takes it; one already filled is replaced where it stands.
 * @param {string[]} components the layer's components
 * @param {string} slot the longhand's unprefixed name
 * @param {string} value its one component
 * @returns {string | null} the layer's new value, or null
 */
const _foldAnimationSlot = (components, slot, value) => {
	const lower = toLowerCaseIfNeeded(value);
	/** @type {(one: string, lowered: string) => boolean} */
	let fills;
	if (slot === "animation-duration" || slot === "animation-delay") {
		if (!_TIME_RE.test(value)) return null;
		fills = (one) => _TIME_RE.test(one);
	} else if (slot === "animation-iteration-count") {
		if (lower !== "infinite" && !_BARE_NUMBER_RE.test(value)) return null;
		fills = (one, lowered) =>
			lowered === "infinite" || _BARE_NUMBER_RE.test(one);
	} else {
		const keywords = ANIMATION_KEYWORD_SLOTS.get(slot);
		if (keywords === undefined) return null;
		// The easing slot also takes a call, which no other slot does.
		const calls = slot === "animation-timing-function";
		if (!keywords.includes(lower) && !(calls && _EASING_CALL_RE.test(value))) {
			return null;
		}
		fills = (one, lowered) =>
			keywords.includes(lowered) || (calls && _EASING_CALL_RE.test(one));
	}
	// `none` and `normal` are each a name and a keyword slot's too, so a layer
	// holding one is read by its order, which a fold cannot keep.
	const out = [];
	const at = [];
	for (const one of components) {
		const lowered = toLowerCaseIfNeeded(one);
		if (lowered === "none" || lowered === "normal") return null;
		if (fills(one, lowered)) at.push(out.length);
		out.push(one);
	}
	if (lower === "none" || lower === "normal") return null;
	// A slot's own initial is written as no value at all, which is how the
	// printer spells the shorthand holding it — the fold has to agree.
	const initial =
		slot === "animation-delay" || slot === "animation-duration"
			? _ZERO_TIME_RE.test(value)
			: slot === "animation-iteration-count"
				? _ONE_COUNT_RE.test(value)
				: lower === "ease" || lower === "running";
	if (slot === "animation-delay") {
		// A delay is the second time; with no duration the first would be it.
		if (at.length === 0) return null;
		if (at.length === 2) {
			if (initial) out.splice(at[1], 1);
			else out[at[1]] = value;
		} else if (!initial) {
			out.push(value);
		}
	} else if (slot === "animation-duration") {
		// Dropped only with no delay after it, which would read as the duration.
		if (initial && at.length < 2) {
			if (at.length === 1) out.splice(at[0], 1);
		} else if (at.length === 0) {
			out.push(value);
		} else {
			out[at[0]] = value;
		}
	} else if (at.length > 1) {
		return null;
	} else if (initial) {
		if (at.length === 1) out.splice(at[0], 1);
	} else if (at.length === 0) {
		out.push(value);
	} else {
		out[at[0]] = value;
	}
	if (out.length === 0) return null;
	return _joinSlots(out);
};

// An iteration count of one, which an animation runs with anyway.
const _ONE_COUNT_RE = /^\+?1(?:\.0*)?$/;

// A number with no unit and no percent, which `flex` reads as a factor.
const _BARE_NUMBER_RE = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i;
// A basis `flex` reads alone as one: a dimension, or a sizing keyword.
const _FLEX_BASIS_RE =
	/^(?:[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?(?:%|[a-z]+)|content|min-content|max-content|fit-content)$/i;

/**
 * The shortest spelling of a three-factor `flex` value, or `null` when it is
 * already the shortest. Shared with the merge, which arrives at the same three
 * factors from the longhands rather than from an authored shorthand.
 * @param {string[]} components the value's components, in grammar order
 * @returns {string[] | null} the fragments to join, or `null` to keep them as they are
 */
const _shortestFlexValue = (components) => {
	if (components.length !== 3) return null;
	const keyword = FLEX_KEYWORDS.get(components.join(" ").toLowerCase());
	if (keyword !== undefined) return [keyword];
	if (_tridentTarget) return null;
	// CSS Flexbox 1 §7.1.1 reads an omitted basis as `0%`, so one spelt that way
	// says nothing — and an omitted shrink as `1`, so that says nothing either.
	if (components[2] === "0%") {
		return components[1] === "1"
			? [components[0]]
			: [components[0], components[1]];
	}
	// `<'flex-shrink'>` follows the grow factor and defaults to 1, so a `1` there
	// says nothing — over a basis not readable as a factor itself, §7.1.1 reading
	// a unitless zero after fewer than two factors as one, so `1 1 0` is not `1 0`.
	return components[1] === "1" && !_BARE_NUMBER_RE.test(components[2])
		? [components[0], components[2]]
		: null;
};

/**
 * An authored `flex` value of one to three components read into its three
 * factors in grammar order (CSS Flexbox 1 §7.1.1): a bare number is a factor
 * unless two precede it, an omitted shrink is `1` and basis `0%`, and a keyword
 * the factors it names.
 * @param {string[]} components the value's components, as written
 * @returns {string[] | null} grow, shrink and basis, or null where it is no such value
 */
const _flexFactors = (components) => {
	if (components.length === 1) {
		const lower = toLowerCaseIfNeeded(components[0]);
		for (const [factors, keyword] of FLEX_KEYWORDS) {
			if (keyword === lower) return factors.split(" ");
		}
	}
	const factors = [];
	let basis = null;
	for (const component of components) {
		if (_BARE_NUMBER_RE.test(component) && factors.length < 2) {
			// The two factors are adjacent: a basis between them parts no pair.
			if (factors.length === 1 && basis !== null && components[0] !== basis) {
				return null;
			}
			factors.push(component);
		} else {
			if (basis !== null) return null;
			basis = component;
		}
	}
	if (factors.length === 0) return null;
	return [
		factors[0],
		factors.length === 2 ? factors[1] : "1",
		basis === null ? "0%" : basis
	];
};

/**
 * The shortest spelling of an authored `flex` value in whatever order and count
 * its grammar allows, or `null` when what was written is already that short.
 * @param {string[]} components the value's components, as written
 * @returns {string[] | null} the fragments to join, or `null` to keep them as they are
 */
const _shortestAuthoredFlex = (components) => {
	const factors = _flexFactors(components);
	if (factors === null) return null;
	let shortest = _shortestFlexValue(factors) || factors;
	// §7.1.1 reads a lone basis as `1 1 <basis>`, where only a dimension or a
	// sizing keyword is one: a number or a math function could read as a factor.
	if (
		!_tridentTarget &&
		factors[0] === "1" &&
		factors[1] === "1" &&
		_FLEX_BASIS_RE.test(factors[2]) &&
		factors[2].length < shortest.join(" ").length
	) {
		shortest = [factors[2]];
	}
	return shortest.join(" ").length < components.join(" ").length
		? shortest
		: null;
};

/**
 * Split on the whitespace at the top of one value, a nested call's own spaces
 * staying inside it.
 * @param {string} text one comma-separated piece of a call's body
 * @returns {string[]} its components
 */
const _splitTopLevelSpaces = (text) => {
	/** @type {string[]} */
	const parts = [];
	let depth = 0;
	let start = 0;
	for (let i = 0; i < text.length; i++) {
		const code = text.charCodeAt(i);
		if (code === CC_LEFT_PARENTHESIS) {
			depth++;
		} else if (code === CC_RIGHT_PARENTHESIS) {
			depth--;
		} else if (depth === 0 && isAsciiWhitespace(code)) {
			if (i > start) parts.push(text.slice(start, i));
			start = i + 1;
		}
	}
	if (text.length > start) parts.push(text.slice(start));
	return parts;
};

/**
 * The components of a printed value, where the print may have left out the
 * separator between two of them: `underline solid #fff` prints as
 * `underline solid#fff`, an ident and a hash being two tokens whatever stands
 * between them. Whitespace parts two components, and so does any junction the
 * print could have left bare — at the top level a component is one token or one
 * call, so a junction inside one is inside its parentheses.
 * @param {string} text a printed value
 * @returns {string[]} its top-level components
 */
const _splitPrintedComponents = (text) => {
	/** @type {string[]} */
	const parts = [];
	let depth = 0;
	let start = 0;
	for (let i = 0; i < text.length; i++) {
		const code = text.charCodeAt(i);
		// A parenthesis belongs to the call it opens or closes, so neither is a
		// junction two components meet at.
		if (code === CC_LEFT_PARENTHESIS) {
			depth++;
			continue;
		}
		if (code === CC_RIGHT_PARENTHESIS) {
			depth--;
			continue;
		}
		if (depth !== 0) continue;
		if (isAsciiWhitespace(code)) {
			if (i > start) parts.push(text.slice(start, i));
			start = i + 1;
			continue;
		}
		if (i === start) continue;
		if (
			!_wouldFuseTokens(text.charCodeAt(i - 1), text.slice(i), text.slice(0, i))
		) {
			parts.push(text.slice(start, i));
			start = i;
		}
	}
	if (text.length > start) parts.push(text.slice(start));
	return parts;
};

/**
 * Whether every target starts `outline-color` at CSS UI 4's `auto`, which is
 * `currentcolor` beside any style but `auto`.
 * @returns {boolean} true when no target starts it elsewhere
 */
const _outlineColorStartsCurrent = () =>
	// WHY: An engine reading `outline:auto` paints by CSS UI 4's outline model;
	// Trident, EdgeHTML and Presto read no `auto` and started the color at `invert`.
	// No selection names no browser to answer for.
	_prefixBrowsers !== null &&
	!_someBrowserUnanswered &&
	_readsAll(_valueSupportedFrom("outline auto"));

/**
 * Whether every target reads a `<position>` property's lone edge keyword, which
 * an older engine took only as one of a pair. No selection reads everything.
 * @param {string} property the declaration's lowercased property name
 * @param {string} keyword the edge keyword
 * @returns {boolean} true when the keyword alone may stand for the position
 */
const _edgeKeywordRead = (property, keyword) =>
	_prefixBrowsers === null ||
	(!_someBrowserUnanswered &&
		_readsAll(
			_valueSupportedFrom(`${property.replace(VENDOR_PREFIX, "")} ${keyword}`)
		));

/**
 * Drop each component of a family shorthand that holds its own slot's initial —
 * a slot nothing writes takes that value anyway, so writing it says nothing.
 * Where every slot holds its own, one of them says all of them.
 *
 * `_familySlots` is what decides which slot a component fills, so a value it
 * cannot part unambiguously is one this leaves alone — and with it every value
 * naming a slot twice, which the engine drops whole.
 * @param {string} property the declaration's lowercased property name
 * @param {string[]} components the value's top-level components
 * @returns {string[] | null} the fragments to join, or `null` to keep the value
 */
const _dropFamilyInitials = (property, components) => {
	const longhands = FAMILY_LONGHANDS.get(property);
	if (longhands === undefined || components.length < 2) return null;
	for (const component of components) {
		if (component.includes(",") || component === "/") return null;
	}
	const filled = _familySlots(longhands, components);
	if (filled === null) return null;
	const style = property === "outline" && filled.get("outline-style");
	const currentColorIsInitial =
		typeof style === "string" &&
		!asciiCaseInsensitiveMatch(style, "auto") &&
		_outlineColorStartsCurrent();
	/** @type {string[]} */
	const kept = [];
	for (const longhand of longhands) {
		const written = filled.get(longhand);
		if (written === undefined) continue;
		const initial = /** @type {string} */ (FAMILY_SLOT_INITIALS.get(longhand));
		if (asciiCaseInsensitiveMatch(written, initial)) continue;
		if (
			currentColorIsInitial &&
			longhand === "outline-color" &&
			asciiCaseInsensitiveMatch(written, "currentcolor")
		) {
			continue;
		}
		kept.push(written);
	}
	if (kept.length === components.length) return null;
	if (kept.length !== 0) return kept;
	// Every slot held its own initial, and `_familySlots` already read each of
	// these as one slot's alone — so whichever is shortest says all of them.
	let shortest = components[0];
	for (const component of components) {
		if (component.length < shortest.length) shortest = component;
	}
	return [shortest];
};

// The length units, shared by the zero-unit drop and the value classifier. No
// dataset states the list, so it is the spec's, spelled out.
// cspell:ignore rlh cqmin cqmax vmin vmax dvmin dvmax lvmin lvmax svmin svmax whib
const _LENGTH_UNITS =
	"px|em|rem|ex|ch|cap|ic|lh|rlh|v[wh]|vmin|vmax|v[ib]|[sld]v[wh]|[sld]vmin|[sld]vmax|[sld]v[ib]|cq[whib]|cqmin|cqmax|cm|mm|in|pt|pc|q";

const _NUMERIC_RE = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?(%|[a-z]*)$/i;
// Only the four lengths `_minifyHash` accepts: a 5- or 7-digit hash is no
// color, and merging one would take the whole shorthand down with it.
const _HEX_COLOR_RE = /^#(?:[\da-f]{3,4}|[\da-f]{6}|[\da-f]{8})$/i;
const _IDENT_RE = /^-?[a-z_][-\w]*$/i;
const _URL_RE = /^(?:url|src)\(/i;
const _LENGTH_UNIT_RE = new RegExp(`^(?:${_LENGTH_UNITS})$`, "i");

/**
 * The value classes a printed component could be read as, for deciding which
 * slot of an order-free shorthand would claim it. Only the classes those slots
 * name, which the generator asserts is this list; anything else — a function
 * other than `url()`, an escape, a substitution — is `null`, which declines the
 * merge rather than guessing a slot.
 * @param {string} value one printed component
 * @param {string} lower the same, lowercased
 * @returns {Set<string> | null} the classes, or `null` when unclassifiable
 */
const _valueClasses = (value, lower) => {
	const numeric = _NUMERIC_RE.exec(value);
	if (numeric !== null) {
		const unit = toLowerCaseIfNeeded(numeric[1]);
		if (unit === "%") return new Set(["percentage"]);
		// CSS Values 4 §5: a zero length may drop its unit, so a bare `0` is a
		// `<length>`; any other bare number is a `<number>`, which no slot takes.
		if (unit === "") return Number(value) === 0 ? new Set(["length"]) : null;
		return _LENGTH_UNIT_RE.test(unit) ? new Set(["length"]) : null;
	}
	if (_HEX_COLOR_RE.test(value)) return new Set(["color"]);
	// Before the call below, which a `url()` would answer first and decline.
	if (_URL_RE.test(value)) return new Set(["url", "image"]);
	// A call that is a color rather than one that takes one: it fills a color
	// slot as surely as a hex does, and nothing else it could be.
	const open = value.indexOf("(");
	if (open > 0 && value.endsWith(")")) {
		return COLOR_FUNCTIONS.has(asciiLowercaseName(value.slice(0, open)))
			? new Set(["color"])
			: null;
	}
	if (value.charCodeAt(0) === 0x22 || value.charCodeAt(0) === 0x27) {
		return new Set(["string"]);
	}
	if (!_IDENT_RE.test(value)) return null;
	if (CSS_WIDE_KEYWORDS.has(lower)) return null;
	const classes = new Set(["custom-ident", "ident"]);
	if (COLOR_KEYWORDS.has(lower)) classes.add("color");
	return classes;
};

/**
 * Which of a family's longhands each component of its shorthand fills, by the
 * same question `_familyValue` asks the other way round: a component belongs to
 * the one slot whose keywords or value classes take it. Null where any of them
 * is ambiguous or belongs to none, which is a value this cannot part.
 * @param {string[]} longhands the family's longhands, in grammar order
 * @param {string[]} components the shorthand value's top-level components
 * @returns {Map<string, string> | null} the value per longhand, or null
 */
const _familySlots = (longhands, components) => {
	/** @type {Map<string, string>} */
	const filled = new Map();
	for (const component of components) {
		const lower = toLowerCaseIfNeeded(component);
		const classes = _valueClasses(component, lower);
		if (classes === null) return null;
		let owner = -1;
		for (let j = 0; j < longhands.length; j++) {
			const keywords = /** @type {string[]} */ (
				FAMILY_SLOT_KEYWORDS.get(longhands[j])
			);
			let takes = keywords.includes(lower);
			if (!takes) {
				const slot = /** @type {string[]} */ (
					FAMILY_SLOT_CLASSES.get(longhands[j])
				);
				for (const name of slot) {
					if (classes.has(name)) {
						takes = true;
						break;
					}
				}
			}
			if (!takes) continue;
			/* istanbul ignore if -- @preserve: no two slots of the one family this is
			   asked about (`text-decoration`) take the same value, so nothing reaches
			   this; it is the same refusal `_familyValue` makes the other way round */
			if (owner !== -1) return null;
			owner = j;
		}
		// A slot written twice is a value the engine drops whole.
		if (owner === -1 || filled.has(longhands[owner])) return null;
		filled.set(longhands[owner], component);
	}
	return filled;
};

/**
 * Whether the target reads every slot of a shorthand, so one holding its own
 * initial can come out. A `text-decoration` slot the target does not read is
 * one the engine drops the whole declaration for.
 * @param {string} property the shorthand's lowercased property name
 * @returns {boolean} true when an initial slot may be dropped
 */
const _shorthandSlotsReadable = (property) =>
	property !== "text-decoration" ||
	(_textDecorationColorStyleAllowed && _textDecorationThicknessAllowed);

/**
 * Join a merged shorthand's slots the way the printer joins any two fragments:
 * a separator only where leaving it out would fuse the tokens either side, so
 * the merge writes what the same value authored by hand would print as.
 * @param {string[]} slots the values the shorthand spells, in order
 * @returns {string} the value they spell
 */
const _joinSlots = (slots) => {
	let out = slots[0];
	for (let i = 1; i < slots.length; i++) {
		const slot = slots[i];
		if (_wouldFuseTokens(out.charCodeAt(out.length - 1), slot, out)) out += " ";
		out += slot;
	}
	return out;
};

/**
 * The shorthand value a family merge may emit: every longhand's value, in the
 * grammar's order. Each has to parse back into the longhand it was authored on,
 * so a value a second slot would also take declines the merge — `outline`'s
 * `auto` is both an `outline-style` and an `outline-color`.
 * @param {string} property the shorthand being written
 * @param {string[]} longhands the family's longhands, in grammar order
 * @param {string[]} values one component per longhand, in the same order
 * @returns {string | null} the shorthand value, or `null` when it is ambiguous
 */
const _familyValue = (property, longhands, values) => {
	for (let i = 0; i < values.length; i++) {
		// CSS Variables 1 §3.1: a substitution leaves the whole shorthand invalid
		// at computed-value time where its longhand alone would lose one slot.
		if (containsSubstitutionFunction(values[i])) return null;
		const lower = toLowerCaseIfNeeded(values[i]);
		const classes = _valueClasses(values[i], lower);
		if (classes === null) return null;
		let owner = -1;
		for (let j = 0; j < longhands.length; j++) {
			const keywords = /** @type {string[]} */ (
				FAMILY_SLOT_KEYWORDS.get(longhands[j])
			);
			let takes = keywords.includes(lower);
			if (!takes) {
				const slot = /** @type {string[]} */ (
					FAMILY_SLOT_CLASSES.get(longhands[j])
				);
				for (const name of slot) {
					if (classes.has(name)) {
						takes = true;
						break;
					}
				}
			}
			if (!takes) continue;
			if (owner !== -1) return null;
			owner = j;
		}
		if (owner !== i) return null;
	}
	// A slot holding its own initial says nothing, exactly as it says nothing
	// in the same value written by hand — where the target reads the slot.
	const dropped = _shorthandSlotsReadable(property)
		? _dropFamilyInitials(property, values)
		: null;
	return _joinSlots(dropped === null ? values : dropped);
};

/**
 * Whether every target browser reads a later declaration's whole value, which
 * leaves an earlier one of the same property carrying nothing rather than
 * standing as its fallback. Only a value whose every construct is named in
 * `getValueSupportPacked()` answers yes: a keyword the property's own grammar
 * states, or one color function over numbers and named colors.
 * @param {string} property the printed property name
 * @param {string} value the later declaration's printed value
 * @returns {boolean} true when no target is left reading the earlier one
 */
const _valueReadByTarget = (property, value) => {
	// No selection names no browser to answer for, so the earlier one stands
	// unless the option assumes each engine shipping today, read against below.
	if (_prefixBrowsers === null && !_dropOverriddenDeclarations) return false;
	// A selection naming a browser the tables do not cover answers for less than
	// the whole of it. Naming a browser states a target, which the option that
	// assumes one does not override, so this stands whatever it says.
	if (_someBrowserUnanswered) return false;
	// A declaration holding a substitution is valid when parsed, whatever it
	// holds, so the cascade never falls back past it where every target reads it.
	const substitutions = _heldSubstitutions(value);
	if (substitutions !== null) {
		for (const name of substitutions) {
			if (!_readsOverridingValue(_valueSupportedFrom(name))) return false;
		}
		return true;
	}
	const open = value.indexOf("(");
	if (open === -1) {
		if (property === "text-decoration") {
			const components = _splitPrintedComponents(value);
			if (components.length > 1) return _textDecorationRead(components);
		}
		return _readsOverridingValue(
			_valueSupportedFrom(`${property} ${toLowerCaseIfNeeded(value)}`)
		);
	}
	if (
		open === 0 ||
		value.includes("(", open + 1) ||
		value.charCodeAt(value.length - 1) !== CC_RIGHT_PARENTHESIS
	) {
		return false;
	}
	const fn = toLowerCaseIfNeeded(value.slice(0, open));
	if (!_readsOverridingValue(_valueSupportedFrom(fn))) return false;
	if (fn.endsWith("gradient")) {
		// A one-stop gradient is CSS Images 4's, which not every engine reads yet.
		return _gradientArgumentsRead(value.slice(open + 1, -1), 2);
	}
	const args = value.slice(open + 1, -1).match(_COLOR_ARGUMENT_RE);
	if (args === null) return false;
	for (const argument of args) {
		if (_COLOR_NUMBER_RE.test(argument) || _COLOR_HUE_RE.test(argument)) {
			continue;
		}
		// A hex of three or six digits is read wherever a color is; four and eight
		// state an alpha, which is the target's own question and not asked here.
		if (_LONE_HEX_RE.test(argument)) {
			if (argument.length === 4 || argument.length === 7) continue;
			return false;
		}
		// A named color reaches as far as naming one does, bar the two that
		// arrived after the list and carry a row of their own.
		const lowered = toLowerCaseIfNeeded(argument);
		if (getColorNameToRgb().has(lowered) && !LATER_COLOR_NAMES.has(lowered)) {
			continue;
		}
		return false;
	}
	return true;
};

// The words a gradient's line, shape and position are written with, which
// every engine reading the gradient reads.
const _GRADIENT_KEYWORDS = new Set([
	"at",
	"bottom",
	"center",
	"circle",
	"closest-corner",
	"closest-side",
	"currentcolor",
	"ellipse",
	"farthest-corner",
	"farthest-side",
	"from",
	"left",
	"right",
	"to",
	"top",
	"transparent"
]);

// A number, a length, a percentage or an angle, its unit captured.
const _GRADIENT_AMOUNT_RE = /^[+-]?(?:\d+\.?\d*|\.\d+)([a-z]+|%)?$/i;

/**
 * Whether a target reading the gradient function reads all of its arguments: a
 * line, shape or position of keywords and amounts, and stops of a color every
 * engine names. A call inside, or an interpolation space, is not asked here.
 * @param {string} inner the printed arguments
 * @param {number} stopsNeeded how many color stops the arguments must hold
 * @returns {boolean} true when the target reads every one
 */
const _gradientArgumentsRead = (inner, stopsNeeded) => {
	const args = _splitTopLevelArguments(inner);
	let stops = 0;
	for (let index = 0; index < args.length; index++) {
		// A line, shape or position opens the list alone; a stop is one color
		// and its amounts. Anything else, an empty argument too, is invalid.
		if (args[index].trim() === "") return false;
		let words = 0;
		let colors = 0;
		for (const component of _splitPrintedComponents(args[index].trim())) {
			const lowered = toLowerCaseIfNeeded(component);
			if (_GRADIENT_KEYWORDS.has(lowered)) {
				if (lowered === "transparent" || lowered === "currentcolor") {
					colors++;
				} else {
					words++;
				}
				if (words !== 0 && (index !== 0 || colors !== 0)) return false;
				if (colors > 1) return false;
				continue;
			}
			const amount = _GRADIENT_AMOUNT_RE.exec(lowered);
			if (amount !== null) {
				// A unit some target does not read drops the gradient there.
				const unit = amount[1];
				if (unit === undefined || unit === "%") continue;
				if (!_readsOverridingValue(_valueSupportedFrom(`unit ${unit}`))) {
					return false;
				}
				continue;
			}
			let color = false;
			if (_LONE_HEX_RE.test(component)) {
				if (
					component.length !== 4 &&
					component.length !== 7 &&
					!_S._hexAlphaAllowed
				) {
					return false;
				}
				color = true;
			} else if (
				getColorNameToRgb().has(lowered) &&
				!LATER_COLOR_NAMES.has(lowered)
			) {
				color = true;
			}
			if (!color || ++colors > 1 || words !== 0) return false;
		}
		if (colors !== 0) stops++;
	}
	return stops >= stopsNeeded;
};

/**
 * Whether every target reads a `text-decoration` naming more than its line: the
 * multi-value shorthand itself, and each slot's keyword, color or amount.
 * @param {string[]} components the value's top-level components
 * @returns {boolean} true when the target reads every one
 */
const _textDecorationRead = (components) => {
	if (!_textDecorationColorStyleAllowed) return false;
	const filled = _familySlots(
		/** @type {string[]} */ (FAMILY_LONGHANDS.get("text-decoration")),
		components
	);
	if (filled === null) return false;
	for (const [longhand, written] of filled) {
		if (
			longhand === "text-decoration-thickness" &&
			!_textDecorationThicknessAllowed
		) {
			return false;
		}
		const keywords = /** @type {string[]} */ (
			FAMILY_SLOT_KEYWORDS.get(longhand)
		);
		if (keywords.includes(toLowerCaseIfNeeded(written))) continue;
		// A color or an amount, read as a gradient's stop or length is.
		if (!_gradientArgumentsRead(written, 0)) return false;
	}
	return true;
};

/**
 * Whether a later declaration of one property leaves an earlier one carrying
 * nothing: the cascade reads the later, and it writes every name the earlier
 * does.
 * @param {CssPath} path the accessor positioned on the rule
 * @param {Node} later the later declaration
 * @param {string} laterValue its printed value
 * @param {Node} earlier the earlier declaration of the same property
 * @param {string} earlierValue its printed value
 * @param {string} property the printed property both write
 * @param {boolean} names whether the property takes a `<custom-ident>` list
 * @param {boolean} keyframe whether the block is a keyframe
 * @returns {boolean} true when the earlier one is read for nothing
 */
const _laterWriteWins = (
	path,
	later,
	laterValue,
	earlier,
	earlierValue,
	property,
	names,
	keyframe
) => {
	const important = _isImportant(later);
	// An `!important` one a keyframe ignores stands over nothing.
	if (keyframe && important) return false;
	// An `!important` earlier one still wins over a later plain declaration,
	// which leaves the later dead rather than it.
	if (!important && _isImportant(earlier)) return false;
	// A target that reads the later value everywhere is one nothing falls back
	// from, whatever the two values have in common.
	if (_valueReadByTarget(property, laterValue)) return true;
	return names
		? _coveredByLater(laterValue, earlierValue)
		: _sameVocabulary(laterValue, earlierValue);
};

/**
 * The same question asked of two declaration nodes before either is written,
 * so a merge can read a family as the output will carry it.
 * @param {CssPath} path the accessor positioned on the rule
 * @param {PrintContext} writer the print context (children's printed text)
 * @param {Declaration} earlier the earlier declaration
 * @param {Declaration} later the later declaration of the same property
 * @param {boolean} keyframe whether the block is a keyframe
 * @returns {boolean} true when the earlier one is read for nothing
 */
const _writeIsDead = (path, writer, earlier, later, keyframe) => {
	const laterText = writer.get(later);
	const earlierText = writer.get(earlier);
	const laterColon = laterText.indexOf(":");
	const earlierColon = earlierText.indexOf(":");
	// A custom property's value is a token stream nothing here reads, so what
	// an engine makes of it is not knowable from its spelling.
	if (
		laterColon <= 0 ||
		earlierColon <= 0 ||
		laterText.charCodeAt(1) === CC_HYPHEN_MINUS
	) {
		return false;
	}
	const property = _printedProperty(laterText, laterColon);
	return _laterWriteWins(
		path,
		later,
		_writtenValue(laterText, laterColon, property),
		earlier,
		_printedValue(earlierText, earlierColon),
		property,
		CUSTOM_IDENT_LIST_PROPERTIES.has(property),
		keyframe
	);
};

/**
 * A property's first word, what an unnamed property of its family starts with:
 * `margin` for `margin-top`.
 * @param {string} name a property
 * @returns {string} the word before its first `-`
 */
const _firstPropertyWord = (name) => {
	const dash = name.indexOf("-");
	return dash === -1 ? name : name.slice(0, dash);
};

/**
 * Merge a box family's four longhands into their shorthand, which sets exactly
 * those four (see `BOX_LONGHANDS`) — so the rule computes the same either way.
 *
 * Every guard here is about *not* reviving something the browser drops, or
 * moving a declaration past one that could overwrite it: the four must be
 * adjacent, each declared once, each a single component, and all agree on
 * `!important`. `_collapseBox` refuses a `var()` or a CSS-wide keyword, both of
 * which mean something else in a shorthand.
 * @param {CssPath} path the accessor positioned on the rule
 * @param {Declaration[]} decls the rule's declarations, in source order
 * @param {PrintContext} writer the print context (children's printed text)
 * @param {[string, string[]][]} table the shorthand-to-longhands entries to merge by
 * @param {number} mode how the table's values are written: `MERGE_BOX` for the
 * `{1,4}` notation, `MERGE_FAMILY` for order-free slots, `MERGE_SLASH` for slots
 * a `/` stands between
 * @param {Map<string, number>} at which declaration wrote each property, by name
 * @param {Set<string>} repeated the properties written more than once
 * @param {Uint32Array | null} rulesBefore how many child rules stand before each
 * declaration, or null where the block holds none
 * @returns {Map<Node, string> | null} replacement text per declaration, or null
 */
const _mergeBoxLonghands = (
	path,
	decls,
	writer,
	table,
	mode,
	at,
	repeated,
	rulesBefore
) => {
	if (!_S._transforms.mergeLonghands) return null;
	/** @type {Map<Node, string> | null} */
	let out = null;
	// What an earlier shorthand of this table wrote or blanked. Two can share a
	// longhand, and the second landing on the first's blanked tail would leave that
	// tail uncovered, dropping its declaration.
	/** @type {Set<Node> | null} */
	let claimed = null;
	// Only an entry whose first longhand the block writes can merge, and blocks
	// write few properties against a table of dozens of entries.
	const firsts = _mergeTableFirsts(table);
	/** @type {number[]} */
	const candidates = [];
	for (const property of at.keys()) {
		const entries = firsts.get(property);
		if (entries !== undefined) {
			for (const entry of entries) candidates.push(entry);
		}
	}
	if (candidates.length === 0) return null;
	// In table order, as the claims between entries are made.
	if (candidates.length > 1) candidates.sort((a, b) => a - b);
	for (let c = 0; c < candidates.length; c++) {
		const e = candidates[c];
		const shorthand = table[e][0];
		const longhands = table[e][1];
		// A plain loop: this runs for every table entry of every rule, where one
		// closure per entry is the allocation that dominates.
		let missing = false;
		for (let i = 0; i < longhands.length && !missing; i++) {
			missing = !at.has(longhands[i]) || repeated.has(longhands[i]);
		}
		if (missing) continue;
		// The shorthands newer than the longhands they merge.
		if (shorthand === "inset" && !_insetShorthandAllowed) continue;
		if (!_placeShorthandAllowed && PLACE_SHORTHANDS.has(shorthand)) continue;
		const indexes = longhands.map((l) => /** @type {number} */ (at.get(l)));
		if (claimed !== null) {
			let taken = false;
			for (let i = 0; i < indexes.length && !taken; i++) {
				taken = claimed.has(decls[indexes[i]]);
			}
			if (taken) continue;
		}
		const ordered = [...indexes].sort((a, b) => a - b);
		// A child rule between the first and the last is one the merge steps over.
		if (
			rulesBefore !== null &&
			rulesBefore[ordered[ordered.length - 1]] !== rulesBefore[ordered[0]]
		) {
			continue;
		}
		// WHY: the merge moves whatever stands between the slots below the shorthand,
		// so nothing there may be one whose order against a later slot decides what
		// is read — `_orderMatters`, which `_declarationOrder` moves by, so a block
		// merges the same before and after it is reordered. A pair family states no
		// prefix at all — its longhands are the two names — so a property no dataset
		// names is read by either one's first segment.
		const prefix =
			BOX_FAMILY_PREFIX.get(shorthand) || _firstPropertyWord(shorthand);
		const families = [prefix, ...longhands.map(_firstPropertyWord)];
		let blocked = false;
		for (
			let i = ordered[0] + 1;
			i < ordered[ordered.length - 1] && !blocked;
			i++
		) {
			if (indexes.includes(i)) continue;
			const name = asciiLowercaseName(path.name(decls[i]));
			// A legacy alias of a property the block still writes, in a spelling no
			// target reads, is dropped whether it stood here or not — so it is here.
			if (_S._prefixingOn && name.charCodeAt(0) === CC_HYPHEN_MINUS) {
				const base = _prefixedPropertyName(name);
				if (
					base !== undefined &&
					at.has(base) &&
					_prefixedPropertyRemovable(base, name)
				) {
					continue;
				}
			}
			blocked = _blocksMerge(
				name,
				longhands.filter((_, k) => indexes[k] > i),
				families
			);
		}
		if (blocked) continue;
		const important = _isImportant(decls[indexes[0]]);
		if (indexes.some((i) => _isImportant(decls[i]) !== important)) continue;
		const values = [];
		for (const i of indexes) {
			if (!_isSingleValue(path, decls[i], writer)) break;
			// What the declaration prints, not what its tokens spell: a zero length
			// loses its unit as a declaration is written, and a token knows no property.
			const text = writer.get(decls[i]);
			const colon = text.indexOf(":");
			values.push(
				colon === -1
					? _valueComponents(path, decls[i], writer)[0]
					: text.slice(colon + 1, _printedValueEnd(text, colon))
			);
		}
		if (values.length !== longhands.length) continue;
		// WHY: a bare number is no length, so one beside a length is a value the
		// property never read — the engine dropped that declaration and kept the
		// others. Merging them writes it into a shorthand the engine drops whole,
		// losing the slots that were fine. Not asked of an ordered grammar, whose
		// slots take different types by position.
		if (mode !== MERGE_ORDERED && _mixesBareNumberWithLength(values)) continue;
		/** @type {string | null} */
		let value;
		if (mode === MERGE_FAMILY) {
			value = _familyValue(shorthand, longhands, values);
			if (value === null) continue;
		} else if (mode === MERGE_ORDERED) {
			// The same two refusals every other mode makes: a `var()` may expand
			// across a slot boundary, and a CSS-wide keyword beside another value is
			// a shorthand the engine drops whole.
			let refused = false;
			for (const slot of values) {
				if (
					containsSubstitutionFunction(slot) ||
					CSS_WIDE_KEYWORDS.has(toLowerCaseIfNeeded(slot))
				) {
					refused = true;
					break;
				}
			}
			if (refused) continue;
			// Written by position, then shortened the way an authored one would be.
			const shortest = _shortestAuthoredFlex(values);
			value = _joinSlots(shortest === null ? values : shortest);
		} else if (mode === MERGE_SLASH) {
			// The same two refusals a box makes: a `var()` may expand across the `/`
			// into another slot, and a CSS-wide keyword beside another value is a
			// shorthand the engine drops whole.
			let refused = false;
			for (const slot of values) {
				if (
					containsSubstitutionFunction(slot) ||
					CSS_WIDE_KEYWORDS.has(toLowerCaseIfNeeded(slot))
				) {
					refused = true;
					break;
				}
			}
			if (refused) continue;
			// Every slot written: an omitted one means "the first slot where that is
			// a `<custom-ident>`, else `auto`", which is not "the same value".
			value = values.join("/");
		} else {
			// A pair collapses by the same rule as a box, and needs the same refusals:
			// a `var()` may expand to both values, and a CSS-wide keyword alongside
			// another value is a shorthand the engine drops whole.
			const box = _collapseBox(values);
			if (box === null) continue;
			// The two-value spelling is the newer one here, so it is written only
			// where the target reads it; the collapse to one value is as old as the
			// longhands and always is.
			if (
				box.length !== 1 &&
				ONE_VALUE_PAIR_SHORTHANDS.has(shorthand) &&
				!_overflowTwoValuesAllowed
			) {
				continue;
			}
			// A keyword only some of the longhands take makes the shorthand invalid,
			// where the declaration writing it stood on its own: `justify-items:left`
			// is read and `place-items:left` is dropped whole.
			const unshared = UNSHARED_LONGHAND_KEYWORDS.get(shorthand);
			if (unshared !== undefined) {
				// Every slot is one component here, so each is a keyword or nothing.
				let refused = false;
				for (const slot of box) {
					if (unshared.has(toLowerCaseIfNeeded(slot))) {
						refused = true;
						break;
					}
				}
				if (refused) continue;
			}
			value = _joinSlots(box);
		}
		if (out === null) out = new Map();
		out.set(
			decls[ordered[0]],
			`${shorthand}:${value}${important ? "!important" : ""};`
		);
		for (let i = 1; i < ordered.length; i++) out.set(decls[ordered[i]], "");
		// The shorthand writes each longhand still, but under its own name, so the
		// drop a legacy alias of one is owed is no longer one the prefix pass sees.
		if (_S._prefixingOn) {
			for (const longhand of longhands) {
				const spellings = getPrefixedProperties().get(longhand);
				if (spellings === undefined) continue;
				for (const [spelling] of spellings) {
					const j = at.get(spelling);
					if (
						j !== undefined &&
						_prefixedPropertyRemovable(longhand, spelling)
					) {
						out.set(decls[j], "");
					}
				}
			}
		}
		if (claimed === null) claimed = new Set();
		for (const i of ordered) claimed.add(decls[i]);
	}
	return out;
};

// A `<custom-ident>` as printed; one the printer keeps escaped fails it.
const _GRID_IDENT_RE = /^(?:--|-?[a-zA-Z_\u0080-\uFFFF])[-\w\u0080-\uFFFF]*$/;
const _GRID_INTEGER_RE = /^[-+]?\d+$/;

/**
 * Whether a word is a `<custom-ident>` a `<grid-line>` may name.
 * @param {string} word a printed word
 * @returns {boolean} true where it names a line
 */
const _isGridIdent = (word) => {
	if (!_GRID_IDENT_RE.test(word)) return false;
	const lowered = toLowerCaseIfNeeded(word);
	return (
		lowered !== "auto" &&
		lowered !== "span" &&
		lowered !== "default" &&
		!CSS_WIDE_KEYWORDS.has(lowered)
	);
};

/**
 * What an omitted `<grid-line>` after this one means (CSS Grid 2 §8.4): the
 * same line where this is a lone `<custom-ident>`, else `auto`.
 * @param {string} line a printed line
 * @returns {string | null} the line an omitted one stands for, or null for a
 * shape the fold leaves as written
 */
const _omittedGridLine = (line) => {
	const parts = line.split(" ");
	if (parts.length === 1) {
		if (toLowerCaseIfNeeded(line) === "auto") return "auto";
		if (_GRID_INTEGER_RE.test(line)) return Number(line) === 0 ? null : "auto";
		return _isGridIdent(line) ? line : null;
	}
	if (parts.length !== 2 || toLowerCaseIfNeeded(parts[0]) !== "span") {
		return null;
	}
	const count = parts[1];
	return (
		_GRID_INTEGER_RE.test(count) ? Number(count) > 0 : _isGridIdent(count)
	)
		? "auto"
		: null;
};

// The lines each grid placement shorthand sets, in the order its value lists
// them (CSS Grid 2 §8.4).
/** @type {Map<string, string[]>} */
const _GRID_PLACEMENT_SLOTS = new Map([
	[
		"grid-area",
		["grid-row-start", "grid-column-start", "grid-row-end", "grid-column-end"]
	],
	["grid-row", ["grid-row-start", "grid-row-end"]],
	["grid-column", ["grid-column-start", "grid-column-end"]]
]);

/**
 * Write a grid placement shorthand with one of its lines set again: every line
 * filled in as its omission means, the one replaced, and the trailing lines the
 * omission still means left out again.
 * @param {string[]} slots the longhands the shorthand sets, in its order
 * @param {string} value the shorthand's printed value
 * @param {string} longhand the longhand written after it
 * @param {string} longhandValue that longhand's printed value
 * @returns {string | null} the shorthand's new value, or null to leave both
 */
const _foldGridLine = (slots, value, longhand, longhandValue) => {
	const slot = slots.indexOf(longhand);
	if (
		slot === -1 ||
		containsSubstitutionFunction(value) ||
		containsSubstitutionFunction(longhandValue)
	) {
		return null;
	}
	if (_omittedGridLine(longhandValue) === null) return null;
	const lines = value.split("/").map((line) => line.trim());
	if (lines.length > slots.length) return null;
	for (const line of lines) if (_omittedGridLine(line) === null) return null;
	// A start line stands for the end line after it; in `grid-area` the column
	// start is read off the row start too.
	const half = slots.length / 2;
	while (lines.length < slots.length) {
		const from = lines.length < half ? 0 : lines.length - half;
		lines.push(/** @type {string} */ (_omittedGridLine(lines[from])));
	}
	lines[slot] = longhandValue;
	for (let i = slots.length - 1; i > 0; i--) {
		if (i !== lines.length - 1) break;
		const from = i < half ? 0 : i - half;
		if (lines[i] !== _omittedGridLine(lines[from])) break;
		lines.length = i;
	}
	return lines.join("/");
};

// The three longhands `grid-template` sets and nothing else, in the order a
// merge reads them.
const _GRID_TEMPLATE_LONGHANDS = [
	"grid-template-rows",
	"grid-template-columns",
	"grid-template-areas"
];

// What a `grid-template` written with areas cannot hold (CSS Grid 2 §7.4): a
// repeated or auto-filled list, line names this does not place, a subgrid or
// masonry axis.
const _GRID_TEMPLATE_REFUSED_RE = /\[|repeat\(|subgrid|masonry/i;

// One `<string>` of `grid-template-areas`, as printed.
const _GRID_AREA_STRING_RE = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g;

/**
 * Join a block's three `grid-template-*` longhands into the one `grid-template`
 * setting them: each row's size after its area string, then the columns. Only
 * a value every engine reads back into the same three longhands is written.
 * @param {CssPath} path the accessor positioned on the rule
 * @param {Declaration[]} decls the rule's declarations, in source order
 * @param {PrintContext} writer the print context (children's printed text)
 * @param {Map<string, number>} at which declaration wrote each property, by name
 * @param {Set<string>} repeated the properties written more than once
 * @param {Uint32Array | null} rulesBefore how many child rules stand before each
 * declaration, or null where the block holds none
 * @returns {Map<Node, string> | null} replacement text per declaration, or null
 */
const _mergeGridTemplate = (path, decls, writer, at, repeated, rulesBefore) => {
	/** @type {number[]} */
	const indices = [];
	for (const name of _GRID_TEMPLATE_LONGHANDS) {
		if (repeated.has(name)) return null;
		indices.push(/** @type {number} */ (at.get(name)));
	}
	const first = Math.min(...indices);
	const last = Math.max(...indices);
	if (rulesBefore !== null && rulesBefore[first] !== rulesBefore[last]) {
		return null;
	}
	for (let i = first + 1; i < last; i++) {
		if (indices.includes(i)) continue;
		const name = asciiLowercaseName(path.name(decls[i]));
		// A longhand written twice and not `repeated` is a dead write, dropped.
		if (_GRID_TEMPLATE_LONGHANDS.includes(name)) continue;
		if (
			_blocksMerge(
				name,
				_GRID_TEMPLATE_LONGHANDS.filter((_, k) => indices[k] > i),
				["grid"]
			)
		) {
			return null;
		}
	}
	const important = _isImportant(decls[indices[0]]);
	let before = 0;
	/** @type {string[]} */
	const values = [];
	for (const i of indices) {
		if (_isImportant(decls[i]) !== important) return null;
		const text = writer.get(decls[i]);
		before += text.length;
		let value = text.slice(text.indexOf(":") + 1);
		if (value.endsWith(";")) value = value.slice(0, -1);
		value = value.replace(_PRINTED_IMPORTANT_RE, "");
		if (
			containsSubstitutionFunction(value) ||
			CSS_WIDE_KEYWORDS.has(toLowerCaseIfNeeded(value))
		) {
			return null;
		}
		values.push(value);
	}
	const [rows, columns, areas] = values;
	let value;
	if (asciiCaseInsensitiveMatch(areas, "none")) {
		value =
			asciiCaseInsensitiveMatch(rows, "none") &&
			asciiCaseInsensitiveMatch(columns, "none")
				? "none"
				: `${rows}/${columns}`;
	} else {
		if (
			asciiCaseInsensitiveMatch(rows, "none") ||
			_GRID_TEMPLATE_REFUSED_RE.test(rows) ||
			_GRID_TEMPLATE_REFUSED_RE.test(columns)
		) {
			return null;
		}
		const strings = areas.match(_GRID_AREA_STRING_RE);
		if (strings === null || strings.join("") !== areas) return null;
		const sizes = _splitPrintedComponents(rows);
		// An area string with no size after it is an `auto` row, so the rows
		// written have to be one per string.
		if (sizes.length !== strings.length) return null;
		/** @type {string[]} */
		const slots = [];
		for (let i = 0; i < strings.length; i++) {
			slots.push(strings[i]);
			if (!asciiCaseInsensitiveMatch(sizes[i], "auto")) slots.push(sizes[i]);
		}
		value = _joinSlots(slots);
		if (!asciiCaseInsensitiveMatch(columns, "none")) value += `/${columns}`;
	}
	const text = `grid-template:${value}${important ? "!important" : ""};`;
	if (text.length >= before) return null;
	return new Map([
		[decls[first], text],
		...indices
			.filter((i) => i !== first)
			.map((i) => /** @type {[Node, string]} */ ([decls[i], ""]))
	]);
};

/**
 * Join a block's `grid-row` and `grid-column` into the one `grid-area` setting
 * the same four lines, written with every line its omission already means left
 * out. Nothing between the two may write a placement, and both share importance.
 * @param {CssPath} path the accessor positioned on the rule
 * @param {Declaration[]} decls the rule's declarations, in source order
 * @param {PrintContext} writer the print context (children's printed text)
 * @param {Map<string, number>} at which declaration wrote each property, by name
 * @param {Set<string>} repeated the properties written more than once
 * @param {Uint32Array | null} rulesBefore how many child rules stand before each
 * declaration, or null where the block holds none
 * @returns {Map<Node, string> | null} replacement text per declaration, or null
 */
const _mergeGridPlacement = (
	path,
	decls,
	writer,
	at,
	repeated,
	rulesBefore
) => {
	if (repeated.has("grid-row") || repeated.has("grid-column")) return null;
	const rowAt = /** @type {number} */ (at.get("grid-row"));
	const columnAt = /** @type {number} */ (at.get("grid-column"));
	const first = Math.min(rowAt, columnAt);
	const last = Math.max(rowAt, columnAt);
	if (rulesBefore !== null && rulesBefore[first] !== rulesBefore[last]) {
		return null;
	}
	// Moved past whatever stands between them, which then may set no line.
	for (let i = first + 1; i < last; i++) {
		const name = asciiLowercaseName(path.name(decls[i]));
		if (
			_blocksMerge(name, [rowAt > i ? "grid-row" : "grid-column"], ["grid"])
		) {
			return null;
		}
	}
	const important = _isImportant(decls[rowAt]);
	if (_isImportant(decls[columnAt]) !== important) return null;
	/**
	 * @param {string} text a placement declaration as printed
	 * @returns {string[] | null} its start and end line, or null to leave it
	 */
	const lines = (text) => {
		let value = text.slice(text.indexOf(":") + 1);
		if (value.endsWith(";")) value = value.slice(0, -1);
		value = value.replace(_PRINTED_IMPORTANT_RE, "");
		// A substitution may stand for the `/`.
		if (value.includes("(")) return null;
		const parts = value.split("/").map((part) => part.trim());
		if (parts.length > 2) return null;
		const omitted = _omittedGridLine(parts[0]);
		if (omitted === null) return null;
		if (parts.length === 1) return [parts[0], omitted];
		return _omittedGridLine(parts[1]) === null ? null : parts;
	};
	const rowText = writer.get(decls[rowAt]);
	const columnText = writer.get(decls[columnAt]);
	const row = lines(rowText);
	const column = lines(columnText);
	if (row === null || column === null) return null;
	const area = [row[0], column[0], row[1], column[1]];
	// Only a trailing line may go, and each says what an omitted one means.
	if (area[3] === _omittedGridLine(area[1])) {
		area.length = 3;
		if (area[2] === _omittedGridLine(area[0])) {
			area.length = 2;
			if (area[1] === _omittedGridLine(area[0])) area.length = 1;
		}
	}
	// Never longer: the names it drops outweigh a `/` and an `auto` it may add.
	const text = `grid-area:${area.join("/")}${important ? "!important" : ""};`;
	return new Map([
		[decls[first], text],
		[decls[last], ""]
	]);
};

// How a table's slots are written into the shorthand.
const MERGE_BOX = 0;
const MERGE_FAMILY = 1;
const MERGE_SLASH = 2;
const MERGE_ORDERED = 3;

/** @type {WeakMap<[string, string[]][], Map<string, number[]>>} */
const _mergeTableFirstsCache = new WeakMap();

/**
 * A merge table's entries by their first longhand, which a block has to write
 * for the entry to merge.
 * @param {[string, string[]][]} table a merge table
 * @returns {Map<string, number[]>} the indexes of the entries each first longhand leads
 */
const _mergeTableFirsts = (table) => {
	let firsts = _mergeTableFirstsCache.get(table);
	if (firsts === undefined) {
		firsts = new Map();
		for (let e = 0; e < table.length; e++) {
			const first = table[e][1][0];
			const entries = firsts.get(first);
			if (entries === undefined) firsts.set(first, [e]);
			else entries.push(e);
		}
		_mergeTableFirstsCache.set(table, firsts);
	}
	return firsts;
};

// Each table as its entries, read by index: a `for…of` over the map would
// allocate an entry pair per row of every table for every block merged.
/** @type {[[string, string[]][], number][]} */
const MERGE_TABLES = [
	[[...BOX_LONGHANDS], MERGE_BOX],
	[[...PAIR_LONGHANDS], MERGE_BOX],
	[[...FAMILY_LONGHANDS], MERGE_FAMILY],
	[[...ORDERED_LONGHANDS], MERGE_ORDERED],
	[[...SLASH_LONGHANDS], MERGE_SLASH]
];

/**
 * A printed rule the merge can take apart again. `prelude` is how much of `text`
 * comes before the `{`, so a parent parts it without re-scanning; `children` is
 * what a mergeable at-rule's block is made of, so two blocks join at the seam
 * their texts would otherwise hide.
 * @typedef {object} RuleEntry
 * @property {string} text the rule as printed
 * @property {number} prelude the prelude's length, `-1` for a rule that cannot join
 * @property {boolean} atRule whether it is an at-rule
 * @property {boolean} plain whether its block holds declarations and no rule,
 * so another's declarations may follow them without crossing one
 * @property {number} listable whether its prelude may also join another's
 * selector list, which a plain block under a plain selector may: `LIST_UNKNOWN`
 * until a join asks, then `LIST_YES` / `LIST_NO`
 * @property {number} listKind which shape answers that — a selector, a keyframe
 * selector or a nested selector
 * @property {RuleEntry[] | null} children a mergeable at-rule's block, else null
 * @property {string} head the block's text but for its last child, so a run of
 * joins appends what it adds rather than re-reading what it has
 * @property {boolean} unordered whether a join left a selector list under it
 * concatenated: its own, or one a child carries. The run that grew it settles
 * it where the run ends (`_settleJoinedSeams`), so a join stays what it costs
 * to append rather than re-reading the list it has
 * @property {string | null} list its prelude where a join wrote it, so the next
 * join reads or appends to it rather than cutting it back out of `text`, which
 * would flatten the whole run so far; null to read it off `text`
 * @property {string | null} block the text from its `{` on, held beside `list`
 * for the same reason
 * @property {PrintedDeclaration[] | null} declarations its block's declarations
 * where a join of two blocks under one prelude wrote them, so the next join
 * reads them rather than parsing the whole block again; null to parse `text`
 */

// Each printed rule the merge can join -> its entry, by node id. Emptied per
// top-level node by writing back only the ids set, which `_ruleEntryNodes` keeps.
/** @type {(RuleEntry | undefined)[]} */
let _ruleEntries = [];
/** @type {number[]} */
const _ruleEntryNodes = [];
let _ruleEntryCount = 0;

/**
 * @param {Node} node a printed rule
 * @param {RuleEntry} entry its entry
 * @returns {void}
 */
const _setRuleEntry = (node, entry) => {
	const i = _nodeIndex(node);
	if (_ruleEntries[i] === undefined) _ruleEntryNodes[_ruleEntryCount++] = i;
	_ruleEntries[i] = entry;
};

// A rule the nesting lowering wrote out as several -> one entry per rule, by
// node id, so the merge reads each as a sibling rather than one opaque text.
/** @type {(RuleEntry[] | undefined)[]} */
let _hoistedRuns = [];
/** @type {number[]} */
const _hoistedRunNodes = [];
let _hoistedRunCount = 0;

/**
 * @param {Node} node a rule whose text is several rules
 * @param {RuleEntry[]} runs an entry per rule, in the order the text holds them
 * @returns {void}
 */
const _setHoistedRuns = (node, runs) => {
	const i = _nodeIndex(node);
	if (_hoistedRuns[i] === undefined) _hoistedRunNodes[_hoistedRunCount++] = i;
	_hoistedRuns[i] = runs;
};

/**
 * The rules a node's text is, where it recorded them and the text is still theirs.
 * @param {Node} node a block item
 * @param {string} text the text the block carries for it
 * @returns {RuleEntry[] | null} an entry per rule, or null
 */
const _hoistedRunsOf = (node, text) => {
	const runs = _hoistedRuns[_nodeIndex(node)];
	if (runs === undefined) return null;
	let joined = "";
	for (let i = 0; i < runs.length; i++) joined += runs[i].text;
	return joined === text ? runs : null;
};

/**
 * Forget every entry set since the last call.
 * @returns {void}
 */
const _clearRuleEntries = () => {
	for (let i = 0; i < _ruleEntryCount; i++) {
		_ruleEntries[_ruleEntryNodes[i]] = undefined;
	}
	_ruleEntryCount = 0;
	for (let i = 0; i < _hoistedRunCount; i++) {
		_hoistedRuns[_hoistedRunNodes[i]] = undefined;
	}
	_hoistedRunCount = 0;
};

// Whether a rule's prelude may join another's selector list. Answered only when
// a join asks — two adjacent rules printing the same block, which is rare — so
// the shape test does not run per rule.
const LIST_UNKNOWN = 0;
const LIST_YES = 1;
const LIST_NO = 2;
// Which shape answers it, kept because the parent it is read from is gone by the
// time a join asks.
const LIST_KIND_SELECTOR = 0;
const LIST_KIND_KEYFRAME = 1;
const LIST_KIND_NESTED = 2;

// Anything else standing in a block: it parts a run rather than joining one.
/** @type {(text: string) => RuleEntry} */
const _opaqueEntry = (text) => ({
	text,
	prelude: -1,
	atRule: false,
	plain: false,
	listable: LIST_NO,
	listKind: LIST_KIND_SELECTOR,
	children: null,
	head: "",
	unordered: false,
	list: null,
	block: null,
	declarations: null
});

/**
 * Whether this rule's prelude may join another's selector list, running the
 * shape test the first time a join asks and remembering the answer.
 * @param {RuleEntry} entry a printed rule
 * @returns {boolean} true when its selectors may join a list
 */
const _entryListable = (entry) => {
	if (entry.listable === LIST_UNKNOWN) {
		entry.listable = _isJoinablePrelude(
			_entryPrelude(entry),
			entry.listKind === LIST_KIND_KEYFRAME,
			entry.listKind === LIST_KIND_NESTED
		)
			? LIST_YES
			: LIST_NO;
	}
	return entry.listable === LIST_YES;
};

/**
 * A node's entry, or an opaque one where the text it printed is not the text the
 * block ended up carrying.
 * @param {Node} node a block item
 * @param {string} text the text the block carries for it
 * @returns {RuleEntry} its entry
 */
const _ruleEntryOf = (node, text) => {
	const entry = _ruleEntries[_nodeIndex(node)];
	return entry !== undefined && entry.text === text
		? entry
		: _opaqueEntry(text);
};

// WHY: a selector every engine parses — compounds of a type, universal, class,
// id or attribute selector joined by a combinator, and nothing else. One
// selector an engine cannot parse invalidates the whole list, so joining a
// selector it keeps to one it drops loses the first: a pseudo it does not know,
// or a shape the parser passed through without validating. Widening this to the
// pseudos `mdn-data` names earns nothing a user downloads: over Bootstrap 5 it
// saves 195 raw bytes and costs one gzip byte, and which engine reads a pseudo
// is already `_pseudosReadable`'s answer rather than this shape's.
const _IDENT = String.raw`(?:[-\w\u00A0-\uFFFF]|\\[\s\S])+`;
const _STRING = String.raw`"(?:[^"\\]|\\[\s\S])*"|'(?:[^'\\]|\\[\s\S])*'`;
// The matchers Selectors 4 §6 defines, and only `i` after one: an engine that
// cannot read the `s` modifier drops the selector, and with it the list.
const _ATTRIBUTE = String.raw`\[(?:${_IDENT}\|)?${_IDENT}(?:[~|^$*]?=(?:${_IDENT}|${_STRING})(?: [iI])?)?\]`;
// CSS whitespace: U+00A0 is an identifier's character, not a separator.
const _SPACE = "[ \\t\\n\\f\\r]";
/**
 * A pattern matching the word in any ASCII case, where an `i` flag would make V8
 * case-fold the whole `\u00A0-\uFFFF` class too, costing milliseconds to compile.
 * @param {string} word a lowercase word
 * @returns {string} the pattern
 */
const _anyCase = (word) =>
	word.replace(/[a-z]/g, (letter) => `[${letter}${letter.toUpperCase()}]`);
const _LEGACY_ELEMENT_NAMES = [...LEGACY_PSEUDO_ELEMENTS]
	.map(_anyCase)
	.join("|");
// A pseudo-class with no selector inside it: argument-less, or the `An+B` of an
// `:nth-*()`, whose grammar is closed — the one argument a shape may carry.
const _PSEUDO_CLASS = String.raw`:(?!(?:${_LEGACY_ELEMENT_NAMES})(?![-\w]))(?:(?:${[
	...NTH_PSEUDO_FUNCTIONS
]
	.map(_anyCase)
	.join("|")})\(${_SPACE}*(?:${_anyCase("odd")}|${_anyCase(
	"even"
)}|[-+]?(?:\d*[nN](?:${_SPACE}*[-+]${_SPACE}*\d+)?|\d+))${_SPACE}*\)|[-\w]+(?![-\w(]))`;
// A pseudo-element ends the selector it is in: anything after it is no shape
// an engine parses.
const _PSEUDO_ELEMENT = String.raw`(?:::[-\w]+|:(?:${_LEGACY_ELEMENT_NAMES}))(?![-\w(])`;
const _COMPOUND = `(?:\\*|${_IDENT}|[.#]${_IDENT}|${_ATTRIBUTE})(?:[.#]${_IDENT}|${_ATTRIBUTE}|${_PSEUDO_CLASS})*`;
const _COMBINATOR = `(?:${_SPACE}*[>+~]${_SPACE}*|${_SPACE}+)`;
const _JOINABLE_SELECTOR_RE = new RegExp(
	`^(?:${_COMPOUND}${_COMBINATOR})*${_COMPOUND}(?:${_PSEUDO_ELEMENT})?$`
);

// The same, for a rule nested in another: `&` joins there because an engine that
// cannot read it cannot read the block holding it either, so both selectors are
// dropped together rather than one taking the other down.
const _NESTED_COMPOUND = `(?:&(?:${_COMPOUND})?|${_COMPOUND})`;
const _JOINABLE_NESTED_RE = new RegExp(
	`^(?:${_NESTED_COMPOUND}${_COMBINATOR})*${_NESTED_COMPOUND}(?:${_PSEUDO_ELEMENT})?$`
);

// A keyframe selector is a percentage or `to` — `from` is already printed `0%`.
// Its grammar is closed, so every one of them joins.
const _JOINABLE_KEYFRAME_RE = /^(?:\d+(?:\.\d+)?%|to)$/i;

// The full-progress keyframe selector, in every spelling `to` names.
const _KEYFRAME_FULL_RE = /^100(?:\.0+)?%$/;

/**
 * Whether a printed prelude is a list of selectors every engine parses, so
 * another may be joined onto it without risking the whole list.
 * @param {string} prelude the printed prelude
 * @param {boolean} keyframe whether the rule sits in a `@keyframes`
 * @param {boolean} nested whether the rule sits in another qualified rule
 * @returns {boolean} true when it may be joined
 */
const _isJoinablePrelude = (prelude, keyframe, nested) => {
	const shape = keyframe
		? _JOINABLE_KEYFRAME_RE
		: nested
			? _JOINABLE_NESTED_RE
			: _JOINABLE_SELECTOR_RE;
	// One selector is the common case and needs no list built to walk it.
	if (prelude.includes(",")) {
		for (const one of _splitSelectorList(prelude)) {
			if (!shape.test(one)) return false;
		}
	} else if (!shape.test(prelude)) {
		return false;
	}
	return keyframe || _pseudosReadable(prelude);
};

// Whether the selection reads each pseudo spelling met, which is the same answer
// for every prelude carrying it and for every stylesheet built for it — so it is
// kept until the selection itself changes rather than cleared per print.
/** @type {Map<string, boolean>} */
const _selectorReadMemo = new Map();
/** @type {(number[] | undefined)[] | null} */
let _selectorReadFor = null;

/**
 * Whether every target browser reads every pseudo in a prelude. One selector an
 * engine cannot parse invalidates the whole list it is joined into, so a pseudo
 * no target is known to read keeps the selector out of one. An escape and a
 * quoted attribute value are stepped over: the `:` of `.sm\:flex` and of
 * `[href="a:b"]` starts no pseudo.
 * @param {string} prelude a printed prelude the shape accepted
 * @returns {boolean} true when the target reads all of them
 */
const _pseudosReadable = (prelude) => {
	if (!prelude.includes(":")) return true;
	if (_selectorReadFor !== _prefixBrowsers) {
		_selectorReadMemo.clear();
		_selectorReadFor = _prefixBrowsers;
	}
	for (let i = 0; i < prelude.length; i++) {
		const code = prelude.charCodeAt(i);
		if (code === CC_REVERSE_SOLIDUS) {
			i++;
			continue;
		}
		if (code === CC_QUOTATION_MARK || code === CC_APOSTROPHE) {
			for (i++; i < prelude.length; i++) {
				const inner = prelude.charCodeAt(i);
				if (inner === CC_REVERSE_SOLIDUS) i++;
				else if (inner === code) break;
			}
			continue;
		}
		if (code !== CC_COLON) continue;
		let end = i + 1;
		if (prelude.charCodeAt(end) === CC_COLON) end++;
		const from = end;
		while (end < prelude.length && _isIdentCodePoint(prelude.charCodeAt(end))) {
			end++;
		}
		if (end === from) return false;
		// A pseudo taking an argument is a different entry from the bare one.
		const spelling = `${toLowerCaseIfNeeded(prelude.slice(i, end))}${
			prelude.charCodeAt(end) === CC_LEFT_PARENTHESIS ? "(" : ""
		}`;
		let reads = _selectorReadMemo.get(spelling);
		if (reads === undefined) {
			// A pseudo-element reads with one colon as well as two, and the table
			// carries whichever spelling the standard names.
			const since =
				getSelectorSupportedFrom().get(spelling) ||
				(spelling.charCodeAt(1) === CC_COLON
					? undefined
					: getSelectorSupportedFrom().get(`:${spelling}`));
			// A pseudo the table does not name is one no engine is known to parse —
			// a vendor spelling, a CSS-modules one, or simply newer than the data —
			// and no selection makes it safe, so this precedes the target check.
			reads =
				since !== undefined &&
				(_prefixBrowsers === null
					? _readByEveryEngine(since)
					: _readsAll(since));
			_selectorReadMemo.set(spelling, reads);
		}
		if (!reads) return false;
		i = end - 1;
	}
	return true;
};

/**
 * Whether two property sets can name the same declaration — `all` holds the
 * lot, and a shorthand holds every longhand it sets, which is not always one
 * its name prefixes (`inset` sets `top`, `font` sets `line-height`).
 * @param {Set<string>} a one block's properties
 * @param {Set<string>} b the other's
 * @returns {boolean} true when a declaration in one could be shadowed by the other
 */
const _propertiesOverlap = (a, b) => {
	if (a.has("all") || b.has("all")) return true;
	/** @type {Set<string>} */
	const names = new Set();
	for (const property of a) {
		_eachPropertyName(property, (name) => {
			names.add(name);
		});
	}
	for (const property of b) {
		if (_eachPropertyName(property, (name) => names.has(name))) return true;
	}
	return false;
};

/**
 * The properties a printed block declares at its top level. A nested rule is not
 * one, and neither is anything inside a string, a call or a block.
 * @param {string} body the block's text, its braces excluded
 * @returns {Set<string>} the property names, lowercased
 */
const _blockProperties = (body) => {
	const out = new Set();
	let depth = 0;
	let quote = 0;
	let start = 0;
	let colon = -1;
	let block = false;
	for (let i = 0; i < body.length; i++) {
		const cc = body.charCodeAt(i);
		if (quote !== 0) {
			if (cc === CC_REVERSE_SOLIDUS) {
				i++;
			} else if (cc === quote) {
				quote = 0;
			}
			continue;
		}
		if (cc === CC_QUOTATION_MARK || cc === CC_APOSTROPHE) {
			quote = cc;
		} else if (
			cc === CC_LEFT_PARENTHESIS ||
			cc === CC_LEFT_SQUARE ||
			cc === CC_LEFT_CURLY
		) {
			if (depth === 0 && cc === CC_LEFT_CURLY) block = true;
			depth++;
		} else if (
			cc === CC_RIGHT_PARENTHESIS ||
			cc === CC_RIGHT_SQUARE ||
			cc === CC_RIGHT_CURLY
		) {
			depth--;
		} else if (depth === 0) {
			if (cc === CC_COLON && colon === -1) {
				colon = i;
			} else if (cc === CC_SEMICOLON) {
				if (colon !== -1 && !block) {
					out.add(toLowerCaseIfNeeded(body.slice(start, colon)));
				}
				start = i + 1;
				colon = -1;
				block = false;
			}
		}
	}
	if (colon !== -1 && !block) {
		out.add(toLowerCaseIfNeeded(body.slice(start, colon)));
	}
	return out;
};

// The `!important` a printed declaration ends in, whatever it was written as.
const _PRINTED_IMPORTANT_RE = /!\s*important$/i;

/** @typedef {{ property: string, value: string, text: string }} PrintedDeclaration one declaration of a printed block: its lowercased property, its value and its whole text */

/**
 * The declarations one printed block holds, each as the whole of what it is
 * written as. Built on the same walk `_blockProperties` makes, since what a
 * declaration is here is the same question. Null where the block holds anything
 * else — a nested rule of its own, whose declarations are read at its place
 * rather than at the block's.
 * @param {string} body the block's printed body, between its braces
 * @returns {PrintedDeclaration[] | null} the declarations, in order, or null
 */
const _blockDeclarations = (body) => {
	/** @type {PrintedDeclaration[]} */
	const out = [];
	let depth = 0;
	let quote = 0;
	let start = 0;
	let colon = -1;
	let block = false;
	/**
	 * @param {number} end where the declaration ends
	 * @returns {boolean} true where it is one, or nothing at all
	 */
	const take = (end) => {
		// A printed block holds no empty declaration — the printer drops those —
		// so anything without a value is a block this cannot answer for.
		if (block || colon === -1) return false;
		out.push({
			property: toLowerCaseIfNeeded(body.slice(start, colon)),
			value: body.slice(colon + 1, end),
			text: body.slice(start, end)
		});
		return true;
	};
	for (let i = 0; i < body.length; i++) {
		const cc = body.charCodeAt(i);
		if (quote !== 0) {
			if (cc === CC_REVERSE_SOLIDUS) {
				i++;
			} else if (cc === quote) {
				quote = 0;
			}
			continue;
		}
		if (cc === CC_QUOTATION_MARK || cc === CC_APOSTROPHE) {
			quote = cc;
		} else if (
			cc === CC_LEFT_PARENTHESIS ||
			cc === CC_LEFT_SQUARE ||
			cc === CC_LEFT_CURLY
		) {
			if (depth === 0 && cc === CC_LEFT_CURLY) block = true;
			depth++;
		} else if (
			cc === CC_RIGHT_PARENTHESIS ||
			cc === CC_RIGHT_SQUARE ||
			cc === CC_RIGHT_CURLY
		) {
			depth--;
		} else if (depth === 0) {
			if (cc === CC_COLON && colon === -1) {
				colon = i;
			} else if (cc === CC_SEMICOLON) {
				if (!take(i)) return null;
				start = i + 1;
				colon = -1;
				block = false;
			}
		}
	}
	return take(body.length) ? out : null;
};

/**
 * Whether two ranges of two strings read the same, without cutting either out.
 * @param {string} a the first string
 * @param {number} aStart where its range starts
 * @param {number} aEnd where its range ends
 * @param {string} b the second string
 * @param {number} bStart where its range starts
 * @param {number} bEnd where its range ends
 * @returns {boolean} true when the two ranges are equal
 */
const _rangeEquals = (a, aStart, aEnd, b, bStart, bEnd) => {
	const length = aEnd - aStart;
	if (length !== bEnd - bStart) return false;
	for (let i = 0; i < length; i++) {
		if (a.charCodeAt(aStart + i) !== b.charCodeAt(bStart + i)) return false;
	}
	return true;
};

/**
 * A rule's prelude, read from where a join holds it.
 * @param {RuleEntry} entry the rule
 * @returns {string} its prelude, as printed
 */
const _entryPrelude = (entry) =>
	entry.list === null ? entry.text.slice(0, entry.prelude) : entry.list;

/**
 * Whether two rules' blocks say the same, each read from where it is held.
 * @param {RuleEntry} a one rule
 * @param {RuleEntry} b another
 * @returns {boolean} true when the blocks are equal
 */
const _blocksEqual = (a, b) => {
	const aText = a.block === null ? a.text : a.block;
	const aStart = a.block === null ? a.prelude : 0;
	const bText = b.block === null ? b.text : b.block;
	const bStart = b.block === null ? b.prelude : 0;
	return _rangeEquals(aText, aStart, aText.length, bText, bStart, bText.length);
};

// A layer with no name: the one at-rule prelude that is not the same rule twice.
const ANONYMOUS_LAYER_RE = /^@layer[ \t\n\r\f]*$/i;

/**
 * Whether a prelude opens a layer of its own rather than naming one, which no
 * join may treat as the same rule written twice. The plain spelling answers
 * from the regexp, and only a prelude carrying a backslash is read again.
 * @param {string} prelude the printed prelude, `@` included
 * @returns {boolean} true for an anonymous `@layer`
 */
const _isAnonymousLayerPrelude = (prelude) => {
	if (ANONYMOUS_LAYER_RE.test(prelude)) return true;
	if (!prelude.includes("\\")) return false;
	const match = AT_RULE_NAME_RE.exec(prelude);
	return (
		match !== null &&
		_atKeyword(match[1]) === "layer" &&
		_trimCssWhiteSpace(prelude.slice(match[0].length)).length === 0
	);
};

/**
 * Join two adjacent rules into one. A qualified rule keeps its block and gathers
 * the other's selectors; an at-rule keeps its prelude and gathers the other's
 * block — the block of a condition the sheet already opened once, whose own
 * rules then meet at the seam and are offered the same join.
 * @param {RuleEntry} before the earlier rule
 * @param {RuleEntry} entry the later rule
 * @param {boolean} owned whether `before` is this run's own accumulator, whose
 * children may be extended rather than copied
 * @returns {RuleEntry | null} the one rule, or `null` when they do not join
 */
/**
 * Take back each child an identical later one makes dead, reading only the ones
 * a join has just appended. A child whose place is what it says is never one.
 * @param {RuleEntry[]} joined the joined block's children, in order
 * @param {number} added how many of them the join appended
 * @returns {boolean} true when any earlier child was taken back
 */
const _dropRepeatedChildren = (joined, added) => {
	let dropped = false;
	let i = joined.length - added;
	while (i < joined.length) {
		const text = joined[i].text;
		if (_namesPositionalAtRule(text)) {
			i++;
			continue;
		}
		let j = i - 1;
		while (j >= 0 && joined[j].text !== text) j--;
		if (j < 0) {
			i++;
			continue;
		}
		joined.splice(j, 1);
		dropped = true;
		i--;
		// The two the drop leaves side by side meet here for the first time, and
		// the join that takes them leaves the pair before that one meeting too.
		let at = j;
		while (at > 0 && at < joined.length) {
			const merged = _joinRuleEntries(joined[at - 1], joined[at], false);
			if (merged === null) break;
			joined[at - 1] = merged;
			joined.splice(at, 1);
			if (at <= i) i--;
			at--;
		}
		i++;
	}
	return dropped;
};

/**
 * What a joined block says up to its last child, which is held apart so the `;`
 * a `}` makes redundant is dropped from it alone.
 * @param {RuleEntry[]} joined the joined block's children, in order
 * @returns {string} the text of every child but the last
 */
const _joinedHead = (joined) => {
	let head = "";
	for (let i = 0; i < joined.length - 1; i++) head += joined[i].text;
	return head;
};

/**
 * The text a joined block prints. Each block dropped the `;` its own `}` made
 * redundant; the one that is no longer last brings its own back, so the joined
 * body drops it again — only the last child can end in one.
 * @param {string} prelude the block's prelude, as printed
 * @param {string} head the text of every child but the last
 * @param {RuleEntry[]} joined the joined block's children, in order
 * @returns {string} the rule the block prints as
 */
const _joinedBlockText = (prelude, head, joined) => {
	const last = joined[joined.length - 1];
	let lastText = last.text;
	// Read off the block where a join holds one: the text before it is a list a
	// run of joins is still growing, which reading any of the text flattens.
	if (last.block !== null) return `${prelude}{${head}${lastText}}`;
	while (
		lastText.length !== 0 &&
		lastText.charCodeAt(lastText.length - 1) === CC_SEMICOLON
	) {
		lastText = lastText.slice(0, -1);
	}
	return `${prelude}{${head}${lastText}}`;
};

/**
 * Whether any of a block's children carries a list a join left concatenated.
 * @param {RuleEntry[]} joined the joined block's children
 * @returns {boolean} true when one of them is still to settle
 */
const _anyUnordered = (joined) => {
	for (let i = 0; i < joined.length; i++) {
		if (joined[i].unordered) return true;
	}
	return false;
};

/**
 * Join two adjacent rules into one. A qualified rule keeps its block and gathers
 * the other's selectors; an at-rule keeps its prelude and gathers the other's
 * block — the block of a condition the sheet already opened once, whose own
 * rules then meet at the seam and are offered the same join.
 * @param {RuleEntry} before the earlier rule
 * @param {RuleEntry} entry the later rule
 * @param {boolean} owned whether `before` is this run's own accumulator, whose
 * children may be extended rather than copied
 * @returns {RuleEntry | null} the one rule, or `null` when they do not join
 */
const _joinRuleEntries = (before, entry, owned) => {
	if (!_S._transforms.mergeRules) return null;
	if (before.prelude === -1 || entry.prelude === -1) return null;
	if (before.atRule !== entry.atRule) return null;
	// Compared where they stand: two rules meet far more often than they join, and
	// cutting each one's prelude and block out to answer that is two pieces of
	// string per meeting for an answer that is usually no.
	const preludesEqual = _rangeEquals(
		before.list === null ? before.text : before.list,
		0,
		before.prelude,
		entry.list === null ? entry.text : entry.list,
		0,
		entry.prelude
	);
	if (before.atRule) {
		if (!preludesEqual) return null;
		const beforePrelude = _entryPrelude(before);
		// CSS Cascade 5 §6.4.1: every `@layer {` opens a layer of its own, and a
		// later layer beats an earlier one whatever the selectors say — so joining
		// the two hands the block back to specificity.
		if (_isAnonymousLayerPrelude(beforePrelude)) return null;
		const beforeChildren = before.children;
		const children = entry.children;
		if (beforeChildren === null || children === null) return null;
		// An empty block can be the whole point of the rule — `@layer a{}` declares
		// where the layer sits in the cascade — so it is never folded away.
		if (beforeChildren.length === 0 || children.length === 0) return null;
		// A run of them extends one array rather than copying a growing one.
		const joined = owned ? beforeChildren : [...beforeChildren];
		let at = joined.length - 1;
		// Whether the seam reached back past the last child, so the head names text
		// that is gone.
		let refolded = false;
		// A list either side already carries concatenated is still one to settle,
		// so the block it lands in says so too.
		let unordered = before.unordered || entry.unordered;
		// The same block twice is one block: nothing stands between them, so the
		// later copy only restates what the earlier already says.
		let from = children.length;
		if (
			!_rangeEquals(
				before.text,
				before.prelude,
				before.text.length,
				entry.text,
				entry.prelude,
				entry.text.length
			)
		) {
			// Both sides are joined already, so the pair meeting at the seam is the
			// one new adjacency.
			const seam = _joinRuleEntries(joined[at], children[0], false);
			from = seam === null ? 0 : 1;
			// The seam concatenates like any other join, and is noted rather than
			// ordered: ordering each as it is made re-reads a list still growing.
			// `_settleJoinedSeams` orders it where the run ends.
			if (seam !== null) {
				joined[at] = seam;
				unordered = unordered || seam.unordered;
				// A seam that joined two blocks can now print what the child before it
				// does, which makes that pair one more adjacency to offer the join.
				while (at > 0) {
					const folded = _joinRuleEntries(joined[at - 1], joined[at], false);
					if (folded === null) break;
					joined.splice(at - 1, 2, folded);
					at--;
					unordered = unordered || folded.unordered;
					refolded = true;
				}
			}
		}
		let head = refolded ? _joinedHead(joined) : before.head;
		// Whatever the join leaves before the new last child joins the head, which
		// is why a run costs what it adds rather than what it has.
		if (from < children.length) {
			head += joined[at].text;
			for (let i = from; i < children.length - 1; i++) head += children[i].text;
		}
		for (let i = from; i < children.length; i++) joined.push(children[i]);
		// The same rule twice in a block is read once: the later writes the same
		// declarations to the same elements and wins the tie.
		if (from < children.length && _S._transforms.removeDeadRules) {
			const dropped = _dropRepeatedChildren(joined, children.length - from);
			if (dropped) {
				head = _joinedHead(joined);
				// The drop leaves two children side by side and joins them, which is a
				// seam of its own — read back off the children rather than through the
				// boolean the drop answers with.
				unordered = unordered || _anyUnordered(joined);
			}
		}
		return {
			text: _joinedBlockText(beforePrelude, head, joined),
			prelude: before.prelude,
			atRule: true,
			plain: false,
			listable: LIST_NO,
			listKind: LIST_KIND_SELECTOR,
			children: joined,
			head,
			unordered,
			list: beforePrelude,
			block: null,
			declarations: null
		};
	}
	const blocksEqual = _blocksEqual(before, entry);
	// The same selector twice is one rule: nothing stands between them, so its
	// declarations are read in the order they were written either way.
	if (preludesEqual) {
		// Two identical rules are one: only the last of a set of identical
		// declarations can be read, so the earlier block says nothing.
		if (blocksEqual) return before;
		// Declarations after a nested rule are the implicit `& {…}` the engine
		// builds for them, which is a rule the sheet did not have.
		if (!before.plain) return null;
		const beforePrelude = _entryPrelude(before);
		const rest = entry.text.slice(entry.prelude + 1, -1);
		// One block holds a property once, so two blocks join only where the later
		// declaration is one the earlier could not have been read behind.
		const earlier =
			before.declarations === null
				? _blockDeclarations(before.text.slice(before.prelude + 1, -1))
				: before.declarations;
		if (earlier === null) return null;
		// The later block's declarations, and only where it holds nothing else: a
		// nested rule leaves the properties readable but not their values.
		const later = _S._transforms.removeDeadRules
			? _blockDeclarations(rest)
			: null;
		const names =
			later === null
				? _blockProperties(rest)
				: new Set(later.map((one) => one.property));
		/** @type {Set<number>} */
		const dropped = new Set();
		for (const [at, one] of earlier.entries()) {
			for (const other of names) {
				if (one.property === other) {
					let value = null;
					// The last of that property, not the first: it is what the later
					// block leaves standing, and a `revert` there reads back to it.
					if (later !== null) {
						for (const held of later) {
							if (held.property === other) value = held.value;
						}
					}
					// An `!important` earlier declaration still wins, so it is not
					// behind the later one at all.
					if (
						value === null ||
						_PRINTED_IMPORTANT_RE.test(one.text) ||
						!_sameVocabulary(value.trim(), one.value.trim())
					) {
						return null;
					}
					dropped.add(at);
					continue;
				}
				if (
					one.property === "all" ||
					other === "all" ||
					one.property.startsWith(`${other}-`) ||
					other.startsWith(`${one.property}-`)
				) {
					return null;
				}
			}
		}
		const kept =
			dropped.size === 0
				? earlier
				: earlier.filter((_, at) => !dropped.has(at));
		const added =
			later !== null
				? later
				: rest.length === 0
					? []
					: _blockDeclarations(rest);
		/** @type {PrintedDeclaration[] | null} */
		let declarations = null;
		let joined;
		// Carried as a list only where reading the joined text back would split it
		// the same way, which a quote the split reads as left open would not.
		if (
			added === null ||
			!_lastDeclarationCloses(added) ||
			(before.declarations === null && !_lastDeclarationCloses(earlier))
		) {
			const keptText = kept.map((one) => one.text).join(";");
			joined = _orderJoinedBody(
				keptText.length === 0 || rest.length === 0
					? keptText + rest
					: _mergeJoinedBody(`${keptText};${rest}`),
				before.plain && entry.plain
			);
		} else {
			declarations = _orderJoinedDeclarations(
				kept.length === 0 || added.length === 0
					? [...kept, ...added]
					: _mergeJoinedBoxLonghands([...kept, ...added]),
				before.plain && entry.plain
			);
			// Each block already dropped the `;` its own `}` made redundant, so the
			// one that is no longer last needs it back.
			joined = declarations.map((one) => one.text).join(";");
		}
		return {
			text: `${beforePrelude}{${joined}}`,
			prelude: before.prelude,
			atRule: false,
			plain: entry.plain,
			// The same prelude answers the same way, so the join carries the state it
			// is in rather than resolving it — unless the block it took on is not one
			// a list may lend its selectors to.
			listable: entry.plain ? before.listable : LIST_NO,
			listKind: before.listKind,
			children: null,
			head: "",
			// The two preludes are equal, so the list this keeps is the one `before`
			// already carried — settled or not.
			unordered: before.unordered,
			list: beforePrelude,
			block: null,
			declarations
		};
	}
	if (!blocksEqual || !_entryListable(before) || !_entryListable(entry)) {
		return null;
	}
	// Concatenated, never canonicalized here: a run of joins would then re-read
	// the whole list once per rule. It is noted as a list still to settle, and
	// the run that grew it orders it once where it ends.
	const list = `${_entryPrelude(before)},${_entryPrelude(entry)}`;
	const block =
		before.block === null ? before.text.slice(before.prelude) : before.block;
	return {
		text: list + block,
		prelude: list.length,
		atRule: false,
		plain: true,
		listable: LIST_YES,
		listKind: before.listKind,
		children: null,
		head: "",
		unordered: true,
		list,
		block,
		declarations: null
	};
};

/**
 * `_mergeJoinedBoxLonghands` over a joined block's text.
 * @param {string} body the joined block's declarations
 * @returns {string} the body, each family it could merge merged
 */
const _mergeJoinedBody = (body) => {
	if (!_S._transforms.mergeLonghands) return body;
	const declarations = _blockDeclarations(body);
	if (declarations === null) return body;
	const merged = _mergeJoinedBoxLonghands(declarations);
	return merged === declarations
		? body
		: merged.map((one) => one.text).join(";");
};

/**
 * `_orderJoinedDeclarations` over a joined block's text.
 * @param {string} body the joined body, its braces excluded
 * @param {boolean} plain whether it holds declarations and nothing else
 * @returns {string} the body, reordered where that is allowed
 */
const _orderJoinedBody = (body, plain) => {
	if (!plain || !_S._transforms.reorderDeclarations) return body;
	const declarations = _blockDeclarations(body);
	if (declarations === null) return body;
	const ordered = _orderJoinedDeclarations(declarations, plain);
	return ordered === declarations
		? body
		: ordered.map((one) => one.text).join(";");
};

/**
 * Whether the last of a printed block's declarations leaves the split that read
 * them where it started: outside a string and every bracket. Only the last can
 * end elsewhere, since the split parts the others only there.
 * @param {PrintedDeclaration[]} declarations the declarations, in order
 * @returns {boolean} true when a text joined after them is read on its own
 */
const _lastDeclarationCloses = (declarations) => {
	if (declarations.length === 0) return true;
	const text = declarations[declarations.length - 1].text;
	let depth = 0;
	let quote = 0;
	for (let i = 0; i < text.length; i++) {
		const cc = text.charCodeAt(i);
		if (quote !== 0) {
			if (cc === CC_REVERSE_SOLIDUS) {
				i++;
			} else if (cc === quote) {
				quote = 0;
			}
		} else if (cc === CC_QUOTATION_MARK || cc === CC_APOSTROPHE) {
			quote = cc;
		} else if (
			cc === CC_LEFT_PARENTHESIS ||
			cc === CC_LEFT_SQUARE ||
			cc === CC_LEFT_CURLY
		) {
			depth++;
		} else if (
			cc === CC_RIGHT_PARENTHESIS ||
			cc === CC_RIGHT_SQUARE ||
			cc === CC_RIGHT_CURLY
		) {
			depth--;
		}
	}
	return quote === 0 && depth === 0;
};

/**
 * A joined style rule's declarations in the order `_declarationOrder` prints
 * one in, which each half was already in but the two concatenated need not be.
 * @param {PrintedDeclaration[]} declarations the joined block's declarations
 * @param {boolean} plain whether it holds declarations and nothing else
 * @returns {PrintedDeclaration[]} them, reordered where that is allowed
 */
const _orderJoinedDeclarations = (declarations, plain) => {
	if (!plain || !_S._transforms.reorderDeclarations) return declarations;
	if (declarations.length < 2) return declarations;
	const order = _declarationOrder(declarations.map((one) => one.text));
	return order === null ? declarations : order.map((at) => declarations[at]);
};

// The group rules whose declarations, nested in a style rule, are the implicit
// `& {}` rule CSS Nesting 1 §3.2 reads them as.
const _NESTED_GROUP_AT_RULES = new Set([
	"container",
	"layer",
	"media",
	"scope",
	"starting-style",
	"supports"
]);

// The opening of a `@scope` prelude as the join writes it.
const _SCOPE_OPENER_SPACED = "@scope (";

// A named layer block, up to the `{` its body opens with.
const NAMED_LAYER_BLOCK_RE = /^@layer [^{;]+\{/;

/**
 * Settle a parent's record of the layers already out against a block that
 * streamed past it. Only the layer that block wrote ends here — another is a
 * layer of its own, whose order against this one the cascade fixes.
 * @param {Map<string, SeenLayer>} layers what the parent has noted
 * @param {string | null} opener the block's `@layer <name>{`, null when it names none
 * @returns {void}
 */
const _closeStreamedLayer = (layers, opener) => {
	if (opener === null) {
		layers.clear();
		return;
	}
	// Read as opening a layer of its own: its body is not there to say otherwise.
	_noteLayerBlock(
		layers,
		_trimCssWhiteSpace(opener.slice(NAMED_LAYER_OPENER_HEAD, -1)),
		true
	);
	layers.delete(opener);
};

/**
 * Note where a streamed named layer block ends, against the blocks its parent
 * has noted. The `}` closing it is what a later block of that layer folds into.
 * @param {number} d the block's depth
 * @param {string} opener its `@layer <name>{`
 * @param {number} at the piece its `}` went out as
 * @returns {void}
 */
const _noteStreamedLayerClose = (d, opener, at) => {
	let layers = d === 0 ? _seenTopLevelLayers : _frameSeenLayers[d - 1];
	if (layers === null) {
		layers = new Map();
		if (d === 0) _seenTopLevelLayers = layers;
		else _frameSeenLayers[d - 1] = layers;
	}
	// Held as a piece a fold grows, so the rules a later block brings are keyed
	// alongside the ones this block wrote out as it streamed.
	layers.set(opener, {
		at,
		subtree: false,
		taken: new TakenPiece(at, "}", -1)
	});
};

/**
 * The `@layer <name> {` a printed sibling opens with, when the whole of it is
 * one named layer block. An anonymous `@layer {` is a layer of its own and is
 * not one of these (see `ANONYMOUS_LAYER_RE`).
 * @param {string} text a sibling's printed text
 * @returns {string | null} its opener, or null when it is not such a block
 */
const _namedLayerOpener = (text) => {
	if (text.length === 0 || text.charCodeAt(0) !== CC_AT_SIGN) return null;
	if (text.charCodeAt(text.length - 1) !== CC_RIGHT_CURLY) return null;
	const opener = NAMED_LAYER_BLOCK_RE.exec(text);
	return opener === null ? null : opener[0];
};

// `@layer ` — what an opener carries in front of the layer it names.
const NAMED_LAYER_OPENER_HEAD = "@layer ".length;

/** @typedef {{ at: number, subtree: boolean, taken?: TakenPiece, entry?: RuleEntry, statement?: boolean }} SeenLayer a named block already out, whether a later sibling wrote inside its layer's subtree, the piece its rules are keyed in, the entry a later block of its layer joins against, and whether it is still the `@layer <name>;` statement its first block takes the place of */

// A statement naming one layer: where it stands, that layer's block may.
const LAYER_STATEMENT_RE = /^@layer ([^\s,;{}]+);$/;

/**
 * Drop the blocks a node standing between them could reorder. Only a write into
 * the very same layer does: a layer's own declarations outrank its sublayers
 * whichever side they are written, and two layers that are not one are ordered
 * where each was first named, so neither moves when a block folds across it.
 * @param {Map<string, SeenLayer>} layers the blocks seen so far, by opener
 * @param {string} text the node's printed text
 * @returns {void}
 */
const _forgetLayersWritten = (layers, text) => {
	if (layers.size === 0 || !text.includes("@layer")) return;
	/** @type {Set<string>} */
	const written = new Set();
	// Read only what the parser would: `@layer` inside a string or a comment is
	// text. An anonymous `@layer{` names nothing, so it collides with nothing.
	for (let i = 0; i < text.length; i++) {
		const c = text.charCodeAt(i);
		if (c === CC_QUOTATION_MARK || c === CC_APOSTROPHE) {
			while (++i < text.length && text.charCodeAt(i) !== c) {
				if (text.charCodeAt(i) === CC_REVERSE_SOLIDUS) i++;
			}
			continue;
		}
		if (c === CC_SOLIDUS && text.charCodeAt(i + 1) === CC_ASTERISK) {
			const close = text.indexOf("*/", i + 2);
			i = close === -1 ? text.length : close + 1;
			continue;
		}
		if (c !== CC_AT_SIGN || !text.startsWith("@layer", i)) continue;
		let end = i + "@layer".length;
		if (end >= text.length || !isAsciiWhitespace(text.charCodeAt(end))) {
			continue;
		}
		while (
			end < text.length &&
			text.charCodeAt(end) !== CC_LEFT_CURLY &&
			text.charCodeAt(end) !== CC_SEMICOLON
		) {
			end++;
		}
		for (const name of text.slice(i + "@layer".length, end).split(",")) {
			const trimmed = _trimCssWhiteSpace(name);
			if (trimmed !== "") written.add(trimmed);
		}
		i = end;
	}
	for (const opener of layers.keys()) {
		if (
			written.has(_trimCssWhiteSpace(opener.slice(NAMED_LAYER_OPENER_HEAD, -1)))
		) {
			layers.delete(opener);
		}
	}
};

/**
 * Record what a named block about to go out does to the ones already out. A
 * block writes into its own layer, and into layers under it only when it opens
 * one — and a block for `a.b` writes where a block for `a` opening `b` inside
 * itself writes, so the two spellings are one layer and the order within it is
 * one the cascade reads. A block whose subtree a later sibling wrote into may
 * still be folded into, but only by one that stays in its own layer.
 * @param {Map<string, SeenLayer>} layers the blocks seen so far, by opener
 * @param {string} name the layer this block opens
 * @param {boolean} deep whether it opens a layer of its own
 * @returns {void}
 */
const _noteLayerBlock = (layers, name, deep) => {
	for (const [opener, seen] of layers) {
		const other = _trimCssWhiteSpace(opener.slice(NAMED_LAYER_OPENER_HEAD, -1));
		if (other === name) continue;
		if (name.startsWith(`${other}.`)) seen.subtree = true;
		else if (deep && other.startsWith(`${name}.`)) layers.delete(opener);
	}
};

/**
 * Whether a named layer block opens a layer of its own inside itself.
 * @param {string} text the block's printed text
 * @param {string} opener its `@layer <name> {`
 * @returns {boolean} whether it writes past its own layer
 */
const _opensNestedLayer = (text, opener) =>
	text.includes("@layer", opener.length);

// The at-rules whose place in the sheet is what they say: where a layer is first
// named fixes its order, and `@charset`, `@import` and `@namespace` are read
// only ahead of what they precede. A rule holding one is never dropped.
const POSITIONAL_AT_RULE_RE = /@(?:charset|import|layer|namespace)\b/i;
/** @type {Set<string>} */
const POSITIONAL_AT_RULES = new Set([
	"charset",
	"import",
	"layer",
	"namespace"
]);

/**
 * Whether a printed text names an at-rule whose place in the sheet is part of
 * what it means, so nothing moves across it. The plain spellings answer from a
 * regexp, and only a text carrying a backslash is read a second time.
 * @param {string} text printed CSS
 * @returns {boolean} true where one is named
 */
const _namesPositionalAtRule = (text) =>
	// An at-keyword is written with a literal `@`, so a text without one names
	// none, and a native scan answers that before the regexp runs.
	text.includes("@") &&
	(POSITIONAL_AT_RULE_RE.test(text) ||
		(text.includes("\\") && _namesAtRule(text, POSITIONAL_AT_RULES)));

/**
 * Whether a node is an `@layer` at-rule carrying a name — the only shape a
 * named layer block can have, answered from the node rather than from the text
 * it printed. Its name is read raw, unlike every other at-keyword here, because
 * what gathers these slices the name at a fixed `NAMED_LAYER_OPENER_HEAD` that
 * an escape makes wrong — so a layer named with one is gathered by nothing.
 * @param {Node} node a block item
 * @returns {boolean} true for `@layer <name>`
 */
const _isNamedLayerAtRule = (node) => {
	const i = _nodeIndex(node);
	return (
		_types[i] === NodeType.AtRule &&
		rangeAsciiCaseInsensitiveMatch(_S._input, _starts[i] + 1, _aux0[i], "layer")
	);
};

/**
 * Gather each named `@layer` block into the first sibling block of that name.
 * They are one layer however far apart they stand, and what separates them is in
 * another layer or in none — either way ordered against these by the cascade
 * rather than by where they sit, so moving the later body up is not a move the
 * cascade can see. An anonymous `@layer {` is a layer of its own and is left
 * alone (see `ANONYMOUS_LAYER_RE`).
 * @param {Node[]} items the siblings, in the order their texts stand in
 * @param {string[]} texts the siblings' printed texts, rewritten in place
 * @returns {void}
 */
const _mergeNamedLayerBlocks = (items, texts) => {
	if (!_S._transforms.mergeRules) return;
	// Which siblings could be one at all, read off the nodes: asking a text
	// makes the rope that node printed flatten, and this runs for every block.
	let named = false;
	for (let i = 0; i < items.length && !named; i++) {
		named = _isNamedLayerAtRule(items[i]);
	}
	if (!named) return;
	/** @type {Map<string, SeenLayer> | null} */
	let first = null;
	for (let i = 0; i < texts.length; i++) {
		const text = texts[i];
		const prelude = _isNamedLayerAtRule(items[i])
			? _namedLayerOpener(text)
			: null;
		if (prelude === null) {
			// Read the same way the streamed path reads it, so which blocks gather
			// does not turn on whether the block was small enough to assemble.
			if (first !== null) _forgetLayersWritten(first, text);
			continue;
		}
		if (first === null) first = new Map();
		const seen = first.get(prelude);
		const deep = _opensNestedLayer(text, prelude);
		_noteLayerBlock(
			first,
			_trimCssWhiteSpace(prelude.slice(NAMED_LAYER_OPENER_HEAD, -1)),
			deep
		);
		if (seen === undefined || (deep && seen.subtree)) {
			first.set(prelude, { at: i, subtree: false });
			continue;
		}
		// Both bodies keep their order, so the layer reads as it was written. Read
		// as rules where they can be, so the pair meeting at the seam joins too.
		const at = seen.at;
		const joined = _joinRuleEntries(
			_ruleEntryOf(items[at], texts[at]),
			_ruleEntryOf(items[i], text),
			false
		);
		// The block the gather grew is what this sibling carries from here on, so
		// the seam it made is settled rather than left for a second pass.
		const merged = joined === null ? null : _settleJoinedSeams(joined);
		texts[at] =
			merged === null
				? `${texts[at].slice(0, -1)}${text.slice(prelude.length)}`
				: merged.text;
		if (merged !== null) _setRuleEntry(items[at], merged);
		texts[i] = "";
	}
};

/**
 * Whether every rule between two siblings leaves the moved block's properties
 * alone, so moving it past them cannot change what any element reads. Anything
 * whose declarations this cannot read — a raw item, a rule with a nested one in
 * it, an at-rule a later one by its name replaces — stands in the way whatever
 * it says.
 * @param {Node[]} items the parent's children in source order
 * @param {string[]} texts their printed texts
 * @param {number} from the index the block would move to
 * @param {number} to the index it moves from
 * @param {Set<string>} declared the moved block's properties
 * @param {Set<string> | null} subjects the element types the moved selectors
 * match, or null where one may match any element
 * @returns {boolean} true when nothing between can shadow them
 */
const _nothingBetweenShadows = (items, texts, from, to, declared, subjects) => {
	for (let i = from + 1; i < to; i++) {
		if (texts[i].length === 0) continue;
		const between = _ruleEntryOf(items[i], texts[i]);
		if (between.prelude === -1) return false;
		// A condition only narrows which elements read the block it holds, so what
		// it can shadow is what its own rules declare.
		const written = between.atRule
			? _readableCondition(between)
				? _atRuleDeclaredProperties(between)
				: null
			: between.plain
				? _blockProperties(between.text.slice(between.prelude + 1, -1))
				: null;
		if (written === null) return false;
		if (
			_propertiesOverlap(declared, written) &&
			!_subjectsDisjoint(subjects, _entrySubjectTypes(between))
		) {
			return false;
		}
	}
	return true;
};

// The last compound of a selector the joinable shape accepted, once attribute
// selectors and pseudos are gone: what either holds may look like a combinator.
const _SUBJECT_STRIP_RE = new RegExp(
	`${_ATTRIBUTE}|${_PSEUDO_CLASS}|${_PSEUDO_ELEMENT}`,
	"g"
);
const _SUBJECT_TYPE_RE = /(?:^|[\s>+~])([-\w\u00A0-\uFFFF]+)[^\s>+~]*$/;

/**
 * The element types a selector list's subjects are, lowercased, or null where a
 * selector names none, escapes one or has a shape the joinable pattern rejects.
 * @param {string} prelude a printed selector list
 * @returns {Set<string> | null} the type names
 */
const _subjectTypes = (prelude) => {
	if (prelude.includes("\\")) return null;
	/** @type {Set<string>} */
	const out = new Set();
	for (const one of prelude.includes(",")
		? _splitSelectorList(prelude)
		: [prelude]) {
		if (!_JOINABLE_SELECTOR_RE.test(one)) return null;
		const match = _SUBJECT_TYPE_RE.exec(one.replace(_SUBJECT_STRIP_RE, ""));
		if (match === null) return null;
		out.add(match[1].toLowerCase());
	}
	return out;
};

/**
 * The element types a printed rule's subjects are, and for a condition those of
 * every rule it holds, or null where one may match any element.
 * @param {RuleEntry} entry a plain rule, or an at-rule `_atRuleDeclaredProperties` reads
 * @returns {Set<string> | null} the type names
 */
const _entrySubjectTypes = (entry) => {
	if (!entry.atRule) return _subjectTypes(_entryPrelude(entry));
	/** @type {Set<string>} */
	const out = new Set();
	for (const child of /** @type {RuleEntry[]} */ (entry.children)) {
		const types = _subjectTypes(_entryPrelude(child));
		if (types === null) return null;
		for (const type of types) out.add(type);
	}
	return out;
};

/**
 * Whether no element can match both sets' subjects: an element has one type, so
 * rules whose subjects name different ones never meet in its cascade.
 * @param {Set<string> | null} a one set of subject types
 * @param {Set<string> | null} b the other
 * @returns {boolean} true when the two share no element
 */
const _subjectsDisjoint = (a, b) => {
	if (a === null || b === null) return false;
	for (const type of a) if (b.has(type)) return false;
	return true;
};

/**
 * Whether this at-rule states a condition its own rules are read under, so what
 * it declares is what they declare. `@layer` is not one: where a layer is first
 * named fixes its order, and a block written into it from anywhere is read where
 * the layer sits rather than where it stands.
 * @param {RuleEntry} entry a printed at-rule
 * @returns {boolean} true where its block may be read for what it declares
 */
const _readableCondition = (entry) => {
	const name = _atRuleName(_entryPrelude(entry));
	return name !== "layer" && MERGEABLE_AT_RULES.has(name);
};

/**
 * The properties an at-rule's block declares, or null where it holds a child
 * this cannot read the declarations of — and so cannot say what moving the
 * block past a rule would shadow.
 * @param {RuleEntry} entry a printed at-rule
 * @returns {Set<string> | null} the property names, lowercased
 */
const _atRuleDeclaredProperties = (entry) => {
	const children = entry.children;
	if (children === null) return null;
	/** @type {Set<string>} */
	const out = new Set();
	for (const child of children) {
		if (child.prelude === -1 || child.atRule || !child.plain) return null;
		for (const property of _blockProperties(
			child.text.slice(child.prelude + 1, -1)
		)) {
			out.add(property);
		}
	}
	return out;
};

/**
 * Join an at-rule onto an earlier sibling stating the same condition, past the
 * rules between them. The later block's rules move to where the earlier one
 * ends, so only what stands between them can read differently for it.
 * @param {Node[]} items the parent's children in source order
 * @param {string[]} texts their printed texts, rewritten in place
 * @param {Map<string, number>} held the index each prelude was first seen at
 * @param {RuleEntry} entry the at-rule reached now
 * @param {number} at its index
 * @returns {void}
 */
const _mergeDistantAtRule = (items, texts, held, entry, at) => {
	const prelude = _entryPrelude(entry);
	// WHY: a layer's place in the cascade is where its name is first written, not
	// what its prelude states, so two blocks naming one are not the pair of equal
	// conditions this joins. `_mergeNamedLayerBlocks` gathers those under the rule
	// that order gives it, and an anonymous `@layer` opens a layer of its own.
	if (_atRuleName(prelude) === "layer") return;
	const first = held.get(prelude);
	if (first === undefined) {
		held.set(prelude, at);
		return;
	}
	const declared = _atRuleDeclaredProperties(entry);
	if (declared === null) return;
	if (
		!_nothingBetweenShadows(
			items,
			texts,
			first,
			at,
			declared,
			_entrySubjectTypes(entry)
		)
	) {
		return;
	}
	const joined = _joinRuleEntries(
		_ruleEntryOf(items[first], texts[first]),
		entry,
		false
	);
	if (joined === null) return;
	// The rule it moved into is what the block carries from here on, so the seam
	// the move made settles here rather than at the join.
	const merged = _settleJoinedSeams(joined);
	_setRuleEntry(items[first], merged);
	texts[first] = merged.text;
	texts[at] = "";
};

/**
 * Whether a later printed declaration of one property leaves an earlier one in
 * another block read for nothing — the same question `_laterWriteWins` answers
 * within one block, asked of printed text.
 * @param {string} property the lowercased property both write
 * @param {string} earlier the earlier declaration's printed value
 * @param {string} later the later one's
 * @returns {boolean} true when the earlier one is read for nothing
 */
const _printedWriteIsDead = (property, earlier, later) => {
	// A custom property's value is a token stream nothing here reads, and `all`
	// stands for every property, which no one value answers for.
	if (property === "all" || property.startsWith("--")) return false;
	const important = _PRINTED_IMPORTANT_RE.exec(later);
	// An `!important` earlier one still wins over a later plain declaration.
	if (important === null && _PRINTED_IMPORTANT_RE.test(earlier)) return false;
	const laterValue =
		important === null ? later : later.slice(0, important.index).trimEnd();
	const earlierValue = earlier.replace(_PRINTED_IMPORTANT_RE, "").trimEnd();
	// Past other rules a substitution is read only as the vocabulary it shares.
	if (
		_heldSubstitutions(laterValue) === null &&
		_valueReadByTarget(property, laterValue)
	) {
		return true;
	}
	return CUSTOM_IDENT_LIST_PROPERTIES.has(property)
		? _coveredByLater(laterValue, earlierValue)
		: _sameVocabulary(laterValue, earlierValue);
};

/**
 * Whether a later rule of the same prelude writes again every declaration an
 * earlier one holds, which leaves the earlier read for nothing.
 * @param {RuleEntry} entry the earlier rule
 * @param {Map<string, string>} later the later rule's last value per property
 * @returns {boolean} true when the earlier rule can go
 */
const _ruleWrittenAgain = (entry, later) => {
	// A plain block holds declarations and nothing else, so it always reads apart.
	const declarations =
		/** @type {{ property: string, value: string, text: string }[]} */ (
			_blockDeclarations(entry.text.slice(entry.prelude + 1, -1))
		);
	for (const one of declarations) {
		const value = later.get(one.property);
		if (
			value === undefined ||
			!_printedWriteIsDead(one.property, one.value, value)
		) {
			return false;
		}
	}
	return true;
};

/**
 * Drop a rule a later sibling of the same prelude writes wholly again, past
 * whatever stands between them: wherever both are read, the later wins the tie.
 * Only whole rules: one left shorter no longer equals a copy that drops it.
 * @param {Node[]} items the parent's children in source order
 * @param {string[]} texts their printed texts, rewritten in place
 * @returns {boolean} true when any rule was dropped
 */
const _dropRulesWrittenAgain = (items, texts) => {
	let dropped = false;
	/** @type {Map<string, number[]> | null} */
	let held = null;
	for (let i = 0; i < items.length; i++) {
		if (
			texts[i].length === 0 ||
			_types[_nodeIndex(items[i])] === NodeType.Declaration
		) {
			continue;
		}
		const entry = _ruleEntryOf(items[i], texts[i]);
		// Keyframes of one selector are not one cascade in every engine: an older
		// one reads only the last block written for it.
		if (
			entry.prelude === -1 ||
			entry.atRule ||
			!entry.plain ||
			entry.listKind === LIST_KIND_KEYFRAME
		) {
			continue;
		}
		const prelude = _entryPrelude(entry);
		if (held === null) held = new Map();
		const earlier = held.get(prelude);
		if (earlier === undefined) {
			held.set(prelude, [i]);
			continue;
		}
		const declarations =
			/** @type {{ property: string, value: string, text: string }[]} */ (
				_blockDeclarations(entry.text.slice(entry.prelude + 1, -1))
			);
		/** @type {Map<string, string>} */
		const later = new Map();
		for (const one of declarations) later.set(one.property, one.value);
		for (const at of earlier) {
			// One a copy between them already took is not there to drop again, and
			// an identical one is the joins' to place.
			if (texts[at].length === 0 || texts[at] === texts[i]) continue;
			if (_ruleWrittenAgain(_ruleEntryOf(items[at], texts[at]), later)) {
				texts[at] = "";
				dropped = true;
			}
		}
		earlier.push(i);
	}
	return dropped;
};

/**
 * Join sibling rules that print the same block with other rules between them.
 * Unlike `_mergeAdjacentRules` this moves a selector past what stands in its
 * way, so the cascade is only unchanged where nothing between declares a
 * property the shared block does.
 * @param {Node[]} items the parent's children in source order
 * @param {string[]} texts their printed texts, rewritten in place
 * @returns {void}
 */
const _mergeDistantRules = (items, texts) => {
	if (!_mergingDistantRules) return;
	/** @type {Map<string, number>} */
	const held = new Map();
	// The same, for at-rules: those join by stating one condition twice, so they
	// are held under their prelude where a plain rule is held under its block.
	/** @type {Map<string, number>} */
	const heldAtRules = new Map();
	// The rules selectors moved into, whose lists are canonicalized once at the
	// end: a rule can gather from several later ones.
	/** @type {Set<number>} */
	const joined = new Set();
	for (let i = 0; i < items.length; i++) {
		if (texts[i].length === 0) continue;
		const entry = _ruleEntryOf(items[i], texts[i]);
		if (entry.prelude === -1) continue;
		if (entry.atRule) {
			_mergeDistantAtRule(items, texts, heldAtRules, entry, i);
			continue;
		}
		if (!_entryListable(entry)) continue;
		const block = entry.text.slice(entry.prelude);
		const at = held.get(block);
		if (at === undefined) {
			held.set(block, i);
			continue;
		}
		// A block repeated at a distance already compresses well, so a merge earns
		// wire bytes only where the copy it drops is worth twice the selector.
		if (block.length <= 2 * entry.prelude) continue;
		const before = _ruleEntryOf(items[at], texts[at]);
		if (
			!_nothingBetweenShadows(
				items,
				texts,
				at,
				i,
				_blockProperties(block.slice(1, -1)),
				_entrySubjectTypes(entry)
			)
		) {
			continue;
		}
		const merged = _joinRuleEntries(before, entry, false);
		if (merged === null) continue;
		_setRuleEntry(items[at], merged);
		texts[at] = merged.text;
		texts[i] = "";
		joined.add(at);
	}
	for (const at of joined) {
		const canonical = _canonicalizeJoined(_ruleEntryOf(items[at], texts[at]));
		_setRuleEntry(items[at], canonical);
		texts[at] = canonical.text;
	}
};

/**
 * Join adjacent sibling rules that print the same block. Nothing stands between
 * them, so the cascade is unchanged; the later rule's text becomes empty and
 * its selectors move onto the earlier one.
 * @param {Node[]} items the parent's children in source order
 * @param {string[]} texts their printed texts, rewritten in place
 * @param {Set<Node> | null} pending the prefixed rules an unprefixed twin may
 * still make dead, which have to stay one rule to be dropped whole
 * @param {Set<number> | null} dead the children a later one already made dead,
 * which part nothing: what stands either side of one is adjacent once it goes
 * @returns {boolean} true when any two rules joined
 */
const _mergeAdjacentRules = (items, texts, pending, dead) => {
	if (!_S._transforms.mergeRules) return false;
	let joined = false;
	let previous = -1;
	/** @type {RuleEntry | null} */
	let held = null;
	// Whether `held` is a rule this run built, rather than one the printer stored.
	let owned = false;
	// The rules settled so far, nearest last. Only the one on top is next to what
	// settles now, so a fold that fails ends the walk rather than reaching past it.
	/** @type {{ at: number, entry: RuleEntry, owned: boolean }[]} */
	const settled = [];
	/**
	 * Write the run's accumulator back with its selector list canonical, which is
	 * what ends a run: the joins themselves only concatenate. The list it ends as
	 * may be what the rule before it was waiting for, so it is offered that fold.
	 * @returns {void}
	 */
	const finish = () => {
		if (held === null) return;
		let entry = held;
		if (owned) {
			const canonical = _canonicalizeJoined(entry);
			if (canonical !== entry) {
				entry = canonical;
				_setRuleEntry(items[previous], entry);
				texts[previous] = entry.text;
			}
		}
		let at = previous;
		let mine = owned;
		while (settled.length !== 0) {
			const before = settled[settled.length - 1];
			const merged = _joinRuleEntries(before.entry, entry, before.owned);
			if (merged === null) break;
			settled.pop();
			texts[at] = "";
			joined = true;
			at = before.at;
			// The fold makes a seam of its own, and nothing is appended to the rule
			// it leaves: this is where that run ends too.
			entry = _settleJoinedSeams(merged);
			mine = true;
			_setRuleEntry(items[at], entry);
			texts[at] = entry.text;
		}
		settled.push({ at, entry, owned: mine });
	};
	for (let i = 0; i < items.length; i++) {
		if (texts[i].length === 0 || (dead !== null && dead.has(i))) continue;
		if (
			_types[_nodeIndex(items[i])] === NodeType.Declaration ||
			(pending !== null && pending.has(items[i]))
		) {
			finish();
			// A declaration is read at its own place, so nothing folds across it.
			settled.length = 0;
			previous = i;
			held = null;
			owned = false;
			continue;
		}
		const entry = _ruleEntryOf(items[i], texts[i]);
		if (held !== null) {
			const merged = _joinRuleEntries(held, entry, owned);
			if (merged !== null) {
				held = merged;
				owned = true;
				_setRuleEntry(items[previous], merged);
				texts[previous] = merged.text;
				texts[i] = "";
				joined = true;
				continue;
			}
		}
		// Anything else between them — a declaration, a rule that cannot join —
		// parts the run: in a nested block a declaration is read at its own place.
		finish();
		previous = i;
		if (entry.prelude === -1) {
			// A rule the join cannot read stands between, so nothing folds across it.
			settled.length = 0;
			held = null;
		} else {
			held = entry;
		}
		owned = false;
	}
	finish();
	return joined;
};

// CSS Values 4 §5: "for zero lengths the unit identifier is optional". Only the
// length units — a zero time, angle, frequency, resolution or `<flex>` still
// needs its unit, and `0%` is a percentage, a different type.
const _ZERO_LENGTH_RE = new RegExp(`^0(?:${_LENGTH_UNITS})$`, "i");

/**
 * Drop a zero length's unit. Only a whole component: anything inside `calc()`
 * or a `var()` fallback stays, since there a bare `0` is a `<number>` and the
 * expression would stop parsing. `_dropZeroLengthUnitInCall` reaches into the
 * calls where it is a length instead.
 * @param {string} fragment one printed top-level component
 * @returns {string} the component, its zero unit dropped
 */
const _dropZeroLengthUnit = (fragment) =>
	_S._transforms.shortenNumbers &&
	fragment.charCodeAt(0) === CC_0 &&
	_ZERO_LENGTH_RE.test(fragment)
		? "0"
		: fragment;

/**
 * Whether a string token closed with its own quote rather than running to EOF.
 * §4.3.5 ends an unterminated string at EOF, so its text has no closing quote
 * and `slice(1, -1)` would drop a real character (`_minifyString` and
 * `_minifyUrlFunction` refuse to rewrite one for the same reason).
 * @param {string} text a string token's text
 * @returns {boolean} true when the string is closed
 */
const _isClosedString = (text) => {
	const quote = text.charCodeAt(0);
	const n = text.length;
	if (n < 2 || text.charCodeAt(n - 1) !== quote) return false;
	for (let i = 1; i < n - 1; i++) {
		if (text.charCodeAt(i) !== CC_REVERSE_SOLIDUS) continue;
		// The escape swallows what follows, the final quote included (`"a\"`).
		if (i + 1 === n - 1) return false;
		i++;
	}
	return true;
};

/**
 * Write a token's terminator back. A `\` left dangling at EOF has to be replaced
 * rather than kept: kept, it would escape the terminator and leave the token open
 * on exactly the input this repairs. What it stands for differs by token — nothing
 * in a string (§4.3.5), U+FFFD in a url (§4.3.6 reads it as an escape, §4.3.7 ends
 * one at EOF with the replacement character).
 * @param {string} raw the token's source text, missing its terminator
 * @param {string} terminator the character that closes it
 * @param {string} dangling what a `\` left at EOF contributed to the token's value
 * @returns {string} the terminated token
 */
const _terminate = (raw, terminator, dangling) => {
	let backslashes = 0;
	while (
		raw.length - 1 - backslashes > 0 &&
		raw.charCodeAt(raw.length - 1 - backslashes) === CC_REVERSE_SOLIDUS
	) {
		backslashes++;
	}
	const body = backslashes % 2 === 1 ? `${raw.slice(0, -1)}${dangling}` : raw;
	return `${body}${terminator}`;
};

/**
 * The `url()` counterpart of `_isClosedString`: §4.3.6 ends a url-token at EOF, so
 * it has no closing `)` and the printer's next byte would land inside it.
 * @param {string} raw the url-token's source text
 * @returns {boolean} true when the `)` is absent or itself escaped
 */
const _isUnterminatedUrl = (raw) => {
	const n = raw.length;
	if (n < 2 || raw.charCodeAt(n - 1) !== CC_RIGHT_PARENTHESIS) return true;
	let backslashes = 0;
	for (let i = n - 2; i > 0 && raw.charCodeAt(i) === CC_REVERSE_SOLIDUS; i--) {
		backslashes++;
	}
	return backslashes % 2 === 1;
};

/**
 * Text the input ran out inside, closed where the tokenizer and parser closed it
 * (§4.3, §5.4): its last token's terminator, then each block and function still
 * open, innermost first. Printed as written instead, the next byte the printer
 * writes — a `;` or the rule's `}` — would be read as part of what was left open.
 * @param {string} text source text that runs to the end of the input
 * @returns {string} the text with what it left open closed
 */
const _closeAtEof = (text) => {
	const stream = new TokenStream(text);
	/** @type {string[]} */
	const open = [];
	let lastType = TT_EOF;
	let lastStart = 0;
	let lastEnd = 0;
	for (;;) {
		const token = stream.consume();
		const type = token.type;
		if (type === TT_EOF) break;
		lastType = type;
		lastStart = token.start;
		lastEnd = token.end;
		if (type === TT_FUNCTION || type === TT_LEFT_PARENTHESIS) {
			open.push(")");
		} else if (type === TT_LEFT_SQUARE_BRACKET) {
			open.push("]");
		} else if (type === TT_LEFT_CURLY_BRACKET) {
			open.push("}");
		} else if (
			// §5.4.8 ends a block only on its own closer; any other is a token in it.
			open.length !== 0 &&
			((type === TT_RIGHT_PARENTHESIS && open[open.length - 1] === ")") ||
				(type === TT_RIGHT_SQUARE_BRACKET && open[open.length - 1] === "]") ||
				(type === TT_RIGHT_CURLY_BRACKET && open[open.length - 1] === "}"))
		) {
			open.pop();
		}
	}
	let out = text;
	// Comments are consumed between tokens, so one left open is what follows the
	// last; whitespace between them would be a token itself.
	let at = lastEnd;
	for (;;) {
		if (!out.startsWith("/*", at)) break;
		const end = out.indexOf("*/", at + 2);
		if (end === -1) {
			out += "*/";
			break;
		}
		at = end + 2;
	}
	if (out === text && lastEnd === text.length) {
		const last = text.slice(lastStart, lastEnd);
		if (lastType === TT_STRING && !_isClosedString(last)) {
			out = text.slice(0, lastStart) + _terminate(last, last[0], "");
		} else if (lastType === TT_URL && _isUnterminatedUrl(last)) {
			out = text.slice(0, lastStart) + _terminate(last, ")", "�");
		} else {
			// §4.3.7 reads a `\` at EOF as U+FFFD; kept, it would escape the closer.
			out = _terminate(text, "", "�");
		}
	}
	for (let i = open.length - 1; i >= 0; i--) out += open[i];
	return out;
};

/**
 * The canonical order of two selectors in a list: by code unit, save that an
 * attribute selector's `[` sorts after the letters, so a type name leads its
 * attribute variants (`button,[type=button]`) as authors write them.
 * @param {string} a one selector
 * @param {string} b another
 * @returns {number} negative, zero or positive, as `a` sorts before, with or after `b`
 */
const _compareSelectors = (a, b) => {
	const length = a.length < b.length ? a.length : b.length;
	for (let i = 0; i < length; i++) {
		const x = a.charCodeAt(i);
		const y = b.charCodeAt(i);
		if (x === y) continue;
		// `{` is the code unit after `z`; an escaped one still sorts apart.
		const order =
			(x === CC_LEFT_SQUARE ? CC_LEFT_CURLY : x) -
			(y === CC_LEFT_SQUARE ? CC_LEFT_CURLY : y);
		return order === 0 ? x - y : order;
	}
	return a.length - b.length;
};

/**
 * A selector list is a set: the same selectors in any order match the same
 * elements at the same specificity, so a repeat inside it says nothing and one
 * canonical order lets equal lists compress alike. Only ever the author's own
 * list — a join seam concatenates, so a run of them stays linear.
 * @param {string} prelude the printed selector list
 * @returns {string} it, or `prelude` when nothing there is shorter
 */
const _canonicalSelectorList = (prelude) => {
	if (!_S._transforms.shortenSelectors) return prelude;
	// A list needs a comma; the great majority of preludes are one selector, and
	// without one there is nothing to split, deduplicate or order.
	if (!prelude.includes(",")) return prelude;
	const list = _splitSelectorList(prelude);
	if (list.length < 2) return prelude;
	// Strictly increasing answers both questions the common case turns on —
	// every selector distinct, and already in order — without a set: only two
	// equal selectors compare as zero.
	let i = 1;
	while (i < list.length && _compareSelectors(list[i - 1], list[i]) < 0) i++;
	if (i === list.length) return prelude;
	list.sort(_compareSelectors);
	let kept = 1;
	for (let j = 1; j < list.length; j++) {
		if (list[j] !== list[kept - 1]) list[kept++] = list[j];
	}
	list.length = kept;
	return list.join(",");
};

/**
 * A joined at-rule whose block holds no list a join left concatenated. Two
 * blocks join at the seam their texts hide, and the pair meeting there joins
 * like any other adjacency — by concatenating, so that a run of joins costs
 * what it appends. The run ends here, which is where those seams are ordered:
 * once per block, whatever the run put through it, rather than once per join
 * over a list that was still growing.
 *
 * The block's text is written again only where a seam actually moved, and only
 * the children that carry one are read at all.
 * @param {RuleEntry} entry a rule a run of joins built
 * @returns {RuleEntry} it, every list under it canonical
 */
const _settleJoinedSeams = (entry) => {
	if (!entry.unordered || !entry.atRule) return entry;
	const children = entry.children;
	if (children === null) return entry;
	/** @type {RuleEntry[] | null} */
	let settled = null;
	for (let i = 0; i < children.length; i++) {
		const child = children[i];
		if (!child.unordered) continue;
		const canonical = _canonicalizeJoined(child);
		if (canonical === child) continue;
		if (settled === null) settled = [...children];
		settled[i] = canonical;
	}
	if (settled === null) {
		// Canonical is canonical wherever the block goes on to stand, so a later
		// settle of it reads nothing again.
		entry.unordered = false;
		return entry;
	}
	const head = _joinedHead(settled);
	return {
		...entry,
		text: _joinedBlockText(_entryPrelude(entry), head, settled),
		children: settled,
		head,
		unordered: false,
		list: null,
		block: null,
		declarations: null
	};
};

/**
 * A joined rule with its selector list canonical. The joins concatenate to keep
 * a run of them linear, so the list is ordered and deduplicated once here, where
 * the run that built it ends — its own list, and the ones its block's seams
 * left behind.
 * @param {RuleEntry} entry the rule a run of joins built
 * @returns {RuleEntry} it, its selector list canonical
 */
const _canonicalizeJoined = (entry) => {
	if (entry.atRule) return _settleJoinedSeams(entry);
	if (entry.prelude === -1) return entry;
	const prelude = _entryPrelude(entry);
	const canonical = _canonicalSelectorList(prelude);
	if (canonical === prelude) {
		entry.unordered = false;
		return entry;
	}
	return {
		...entry,
		text: canonical + entry.text.slice(entry.prelude),
		prelude: canonical.length,
		unordered: false,
		list: null,
		block: null,
		declarations: null
	};
};

/**
 * Whether a selector can only ever match through a name `unusedSymbols` says
 * nothing uses — a class or an id it names outright.
 *
 * Only at the top level of the selector: inside a functional pseudo the name is
 * not one the selector requires. `:not(.gone)` matches *more* elements once
 * `.gone` is nowhere, and `:is(.gone, .kept)` still matches through the other
 * half, so taking either out would change what the rule paints.
 * @param {string} selector one printed selector
 * @returns {boolean} true when the rule cannot match without an unused name
 */
const _namesUnusedSymbol = (selector) => {
	const unused = /** @type {Set<string>} */ (_unusedSymbols);
	let quote = 0;
	let depth = 0;
	for (let i = 0; i < selector.length; i++) {
		const code = selector.charCodeAt(i);
		if (code === CC_REVERSE_SOLIDUS) {
			i++;
			continue;
		}
		if (quote !== 0) {
			if (code === quote) quote = 0;
			continue;
		}
		if (code === CC_QUOTATION_MARK || code === CC_APOSTROPHE) {
			quote = code;
			continue;
		}
		if (code === CC_LEFT_PARENTHESIS || code === CC_LEFT_SQUARE) {
			depth++;
			continue;
		}
		if (code === CC_RIGHT_PARENTHESIS || code === CC_RIGHT_SQUARE) {
			depth--;
			continue;
		}
		if (depth !== 0) continue;
		if (code !== CC_FULL_STOP && code !== CC_NUMBER_SIGN) continue;
		let end = i + 1;
		while (end < selector.length) {
			const next = selector.charCodeAt(end);
			if (next === CC_REVERSE_SOLIDUS) {
				end += 2;
				continue;
			}
			if (!_isIdentCodePoint(next)) break;
			end++;
		}
		if (end === i + 1) continue;
		if (unused.has(unescapeIdentifier(selector.slice(i + 1, end)))) return true;
		i = end - 1;
	}
	return false;
};

/**
 * The selectors of a list that can still match, or null when none can — which
 * is a rule `unusedSymbols` takes out whole. A list is only rebuilt where it
 * actually lost one.
 * @param {string} list a printed selector list
 * @returns {string | null} the list to write, or null to drop the rule
 */
const _dropUnusedSelectors = (list) => {
	const selectors = _splitSelectorList(list);
	const kept = selectors.filter((one) => !_namesUnusedSymbol(one));
	if (kept.length === 0) return null;
	return kept.length === selectors.length ? list : kept.join(",");
};

/**
 * A rule's prelude: everything before its `{`, which depends on nothing but the
 * prelude itself. Split out of the printer so a rule whose block is streamed can
 * print its opener when the block opens, long before the block's children exist.
 * @param {CssPath} path the accessor positioned on the rule
 * @param {PrintContext} writer the print context
 * @param {boolean} minify whether printing minified
 * @param {string[]=} outParts filled with the prelude's tokens, for prefixing
 * @returns {string} the rule's prelude text
 */
const _rulePrelude = (path, writer, minify, outParts) => {
	const parts = outParts || [];
	// Fold the `@name` in first so the join keeps the separator before the
	// prelude's first token (e.g. `@media screen`, unmerged).
	if (path.type() === NodeType.AtRule) {
		const name = _rawName(path.node);
		// `@charset` is the one at-keyword read as bytes rather than matched
		// (CSS Syntax 3 §3.2), so lowercasing `@CHARSET` would turn a rule the
		// engine drops into one that sets the sheet's encoding.
		const lower =
			minify &&
			_S._transforms.foldCase &&
			!asciiCaseInsensitiveMatch(name, "charset");
		let printed = lower ? asciiLowercaseName(name) : name;
		// An escape spells the same name, except that an escaped `@charset` is no
		// encoding declaration, which only its literal bytes make.
		if (minify && _S._transforms.rewriteEscapes && name.includes("\\")) {
			const meaning = unescapeIdentifier(name);
			if (!asciiCaseInsensitiveMatch(meaning, "charset")) {
				const spelled = escapeName(
					lower ? asciiLowercaseName(meaning) : meaning
				);
				if (spelled.length <= printed.length) printed = spelled;
			}
		}
		parts.push(`@${printed}`);
	}
	_appendChildTexts(path.node, writer, parts);
	// `@media`/`@container` only: the two preludes whose `(…)` is a media
	// feature, so an `and` of two of them is an interval.
	if (minify && path.type() === NodeType.AtRule) {
		// One read of the name for both tests: each is a slice of the source.
		const name = _atKeywordOf(path.node);
		const media = name === "media";
		const container = name === "container";
		// Every prelude that is a chain of conditions, whichever rule states it.
		const supports = asciiCaseInsensitiveMatch(name, "supports");
		// First, so no pass below reads a keyword whose comment it would drop.
		if (media || container || supports) _keepKeywordComments(parts);
		if (
			_S._transforms.shortenMediaQueries &&
			(media || container || supports)
		) {
			_dropRepeatedConditions(parts);
		}
		if (supports && _S._transforms.shortenMediaQueries) {
			if (_prefixBrowsers !== null) _dropDeadPrefixedTests(parts);
			_dropRedundantSupportsParens(parts);
		}
		if (media || container) {
			_minifyMediaQueryParts(parts, media);
		} else {
			// An `@import`'s media query follows the URL, `layer` and `supports()`,
			// and a `@custom-media` one its name; each is read as a `@media` one.
			const start =
				name === "import"
					? _importMediaStart(parts)
					: name === "custom-media"
						? _customMediaStart(parts)
						: -1;
			if (start !== -1) {
				const query = parts.splice(start);
				query.unshift("");
				_keepKeywordComments(query);
				_minifyMediaQueryParts(query, true);
				for (let i = 1; i < query.length; i++) parts.push(query[i]);
			}
			if (
				name === "custom-selector" &&
				_S._transforms.customSelectors &&
				_customSelectors.size !== 0
			) {
				// The list, past the name the rule states.
				let named = 1;
				while (
					named < parts.length &&
					!_CUSTOM_SELECTOR_NAME_RE.test(parts[named])
				) {
					named++;
				}
				const list = parts.splice(named + 1);
				_writeCustomSelector(list);
				for (const part of list) parts.push(part);
			}
		}
	}
	if (minify && path.type() === NodeType.QualifiedRule) {
		// Before the fold, so what a name stands for is what every pass below reads.
		if (_S._transforms.customSelectors && _customSelectors.size !== 0) {
			_writeCustomSelector(parts);
		}
		if (_foldPseudoNames(parts) && _S._transforms.shortenSelectors) {
			_unwrapLoneIs(parts);
		}
		// After the fold, which is what makes the names above match, and before the
		// class replacement, so a pseudo written as a class is one either way.
		if (!_S._isSelectorAllowed) {
			// Nothing to write it as.
		} else if (
			(!_notSelectorListAllowed || !_langArgumentListAllowed) &&
			_S._transforms.lowerUnsupported
		) {
			_lowerSelectorLists(parts);
		}
		if (!_dirSelectorAllowed && _S._transforms.rewriteDirSelector) {
			_lowerDirSelector(parts);
		}
		if (_pseudoClasses !== null) _replacePseudoClasses(parts);
		_dropImpliedUniversalSelector(parts);
	}
	// Only a qualified rule's prelude is a selector — trim combinator spaces
	// there; an at-rule prelude (media / container query) keeps its spacing.
	let prelude = _join(
		parts,
		!minify,
		path.type() === NodeType.QualifiedRule ? _TRIM_COMBINATORS : _TRIM_NOTHING
	);
	// An at-keyword ends where `(` starts, and every engine reading `@scope`
	// parses its root list right after the name.
	if (minify && prelude.startsWith(_SCOPE_OPENER_SPACED)) {
		prelude = `@scope${prelude.slice(_SCOPE_OPENER_SPACED.length - 1)}`;
	}
	if (minify && path.type() === NodeType.QualifiedRule) {
		// A keyframe selector, not a selector list: CSS Animations 1 §4 gives
		// `from` and `to` as `0%` and `100%`, and each pair has a shorter half.
		const parent = path.parent;
		if (
			_S._transforms.shortenSelectors &&
			parent !== null &&
			path.type(parent) === NodeType.AtRule &&
			KEYFRAMES_AT_RULE_RE.test(_atKeywordOf(parent))
		) {
			return _splitSelectorList(prelude)
				.map((one) =>
					asciiCaseInsensitiveMatch(one, "from")
						? "0%"
						: _KEYFRAME_FULL_RE.test(one)
							? "to"
							: one
				)
				.join(",");
		}
		const list = _canonicalSelectorList(prelude);
		if (_readsThroughGoneEngine(list)) {
			/** @type {Set<Node>} */ (_unusedRules).add(_S._currentNode);
			return list;
		}
		if (_unusedSymbols === null) return list;
		const kept = _dropUnusedSelectors(list);
		// Marked rather than dropped here: the rule's own printer takes it out, the
		// same way it takes out one whose block ends up empty.
		if (kept !== null) return kept;
		/** @type {Set<Node>} */ (_unusedRules).add(_S._currentNode);
		return list;
	}
	if (
		_unusedSymbols !== null &&
		minify &&
		path.type() === NodeType.AtRule &&
		KEYFRAMES_AT_RULE_RE.test(_atKeywordOf(path.node)) &&
		// Past the `@name` the prelude leads with, which leaves the animation's own.
		_unusedSymbols.has(
			unescapeIdentifier(prelude.slice(_rawName(path.node).length + 1).trim())
		)
	) {
		/** @type {Set<Node>} */ (_unusedRules).add(_S._currentNode);
	}
	return prelude;
};

/**
 * Whether a rule is one `unusedSymbols` marked — its selectors all name an
 * unused symbol, or it is an `@keyframes` under an unused name. Reading the
 * mark clears it: a finished rule's node goes back to the pool, and a mark left
 * on it would land on whatever is built over it next.
 * @param {Node} rule the rule
 * @returns {boolean} true when it is to be dropped
 */
const _takeUnusedRule = (rule) =>
	_unusedRules !== null && _unusedRules.delete(rule);

/**
 * Whether text ends in an escape the input ran out of — a `\` no character
 * follows. The tokenizer already read it as one, so the raw text is not what the
 * token holds, and writing it out would escape whatever the printer puts next.
 * @param {string} text raw source text
 * @returns {boolean} true when the last `\` has nothing to escape
 */
const _endsInLoneEscape = (text) => {
	let at = text.length;
	while (at > 0 && text.charCodeAt(at - 1) === CC_REVERSE_SOLIDUS) at--;
	return (text.length - at) % 2 === 1;
};

/**
 * Text whose trailing escape the input ran out of, resolved to what it names:
 * nothing inside a string (§4.3.5) and the replacement character anywhere else
 * (§4.3.7). Text ending in anything else is handed back as written.
 * @param {string} text raw source text ending at end of input
 * @param {boolean} inString whether the text is a string token's
 * @returns {string} the text the token holds
 */
const _resolveTrailingEscape = (text, inString) => {
	if (!_endsInLoneEscape(text)) return text;
	const kept = text.slice(0, -1);
	return inString ? kept : `${kept}\uFFFD`;
};

/**
 * The declarations a `text-decoration` naming more than its line says, for a
 * target that reads the shorthand as CSS 2.1's line alone. The line stays in the
 * shorthand — which is what resets the rest of the family to its initial values,
 * as the shorthand does — and every other slot becomes its own longhand, each of
 * which every engine with the feature at all reads.
 * @param {string} value the shorthand's value, as printed
 * @param {string} important `!important` where the declaration carries it
 * @returns {string | null} the declarations, or null when there is nothing to lower
 */
const _expandTextDecoration = (value, important) => {
	const components = _splitPrintedComponents(value);
	// One component is the line alone, which is as old as the property.
	if (components.length < 2) return null;
	const longhands = /** @type {string[]} */ (
		FAMILY_LONGHANDS.get("text-decoration")
	);
	const filled = _familySlots(longhands, components);
	if (filled === null) return null;
	const line = filled.get("text-decoration-line");
	// Only where a slot the target cannot read is written: a thickness alone is
	// not the same question as a style or a color.
	let needed = false;
	for (const longhand of filled.keys()) {
		if (longhand === "text-decoration-line") continue;
		if (
			longhand === "text-decoration-thickness"
				? !_textDecorationThicknessAllowed
				: !_textDecorationColorStyleAllowed
		) {
			needed = true;
		}
	}
	if (!needed) return null;
	// The line's own initial, so the shorthand still resets the family.
	let out = `text-decoration:${
		line === undefined ? "none" : line
	}${important};`;
	for (const longhand of longhands) {
		if (longhand === "text-decoration-line") continue;
		const written = filled.get(longhand);
		if (written !== undefined) {
			out += _withColorFallback(`${longhand}:${written}${important};`);
		}
	}
	return out;
};

/**
 * One printed declaration with its color fallback written before it, where the
 * target reads no spelling of the color it names. A shorthand lowered into its
 * longhands is several declarations, and each needs its fallback against itself
 * — the shorthand standing between them resets what one written first had said.
 * @param {string} one a printed declaration, ending in `;`
 * @returns {string} the declaration, with any fallback before it
 */
const _withColorFallback = (one) => {
	if (!_colorFallbacksNeeded || !_S._transforms.colorFallbacks) return one;
	if (!one.includes("(")) return one;
	// Built from a longhand and its value, so the name before the `:` is there.
	const fallback = _colorFallbackDeclaration(one, one.indexOf(":"));
	return fallback === null ? one : fallback + one;
};

// The shorthands whose lowering keeps the shorthand itself, which is the one
// shape a later value can be read off. `inset`, `place-*` and the pair
// shorthands are written as longhands alone, so nothing later writes those.
const LOWERED_SHORTHANDS = new Set(["text-decoration"]);

/**
 * The longhand declarations a shorthand the target cannot read says, from the
 * shorthand's printed text — the inverse of the merges `_mergeBoxLonghands`
 * makes, and refused for the same reasons: a substitution may expand across a
 * slot boundary, and a keyword only some of the longhands take would leave the
 * others alive where the engine drops the shorthand whole.
 * @param {string} text one printed declaration
 * @returns {string | null} the longhands, or null when there is nothing to lower
 */
const _expandUnsupportedShorthand = (text) => {
	if (
		_placeShorthandAllowed &&
		_overflowTwoValuesAllowed &&
		_insetShorthandAllowed &&
		_textDecorationColorStyleAllowed &&
		_textDecorationThicknessAllowed
	) {
		return null;
	}
	const colon = text.indexOf(":");
	// A custom property's value is a token stream, not a shorthand's slots.
	if (colon <= 0 || text.charCodeAt(1) === CC_HYPHEN_MINUS) return null;
	const property = _printedProperty(text, colon);
	const place = !_placeShorthandAllowed && PLACE_SHORTHANDS.has(property);
	const pair =
		!_overflowTwoValuesAllowed && ONE_VALUE_PAIR_SHORTHANDS.has(property);
	const box = !_insetShorthandAllowed && property === "inset";
	const decoration =
		property === "text-decoration" &&
		(!_textDecorationColorStyleAllowed || !_textDecorationThicknessAllowed);
	if (!place && !pair && !box && !decoration) return null;
	const end = _printedValueEnd(text, colon);
	const value = text.slice(colon + 1, end);
	if (containsSubstitutionFunction(value)) return null;
	if (decoration) {
		return _expandTextDecoration(
			value,
			text.startsWith(_IMPORTANT, end) ? _IMPORTANT : ""
		);
	}
	/** @type {string[] | undefined} */
	let longhands;
	/** @type {string[] | null} */
	let values;
	if (box) {
		longhands = BOX_LONGHANDS.get(property);
		values = _expandBox(value, 4);
	} else {
		longhands = PAIR_LONGHANDS.get(property);
		const written = _splitPrintedComponents(value);
		// A one-value `overflow` is as old as the property, so only the two-value
		// spelling is one to lower; a `place-*` gives both longhands its one value.
		values =
			written.length === 2
				? written
				: place && written.length === 1
					? [written[0], written[0]]
					: null;
		const unshared = UNSHARED_LONGHAND_KEYWORDS.get(property);
		if (values !== null && unshared !== undefined) {
			for (const slot of values) {
				if (unshared.has(toLowerCaseIfNeeded(slot))) return null;
			}
		}
	}
	if (values === null || longhands === undefined) return null;
	const important = text.startsWith(_IMPORTANT, end) ? _IMPORTANT : "";
	let out = "";
	for (let i = 0; i < longhands.length; i++) {
		out += _withColorFallback(`${longhands[i]}:${values[i]}${important};`);
	}
	return out;
};

/**
 * The value a later declaration is read for, which is the one it is written as.
 * A shorthand the target cannot read is lowered into longhands, and the
 * shorthand it keeps carries the line alone — a value every target reads. So an
 * earlier declaration of that property is not the fallback its spelling makes
 * it look like: what is written over it is readable, and it is dead.
 * @param {string} text the declaration, as printed
 * @param {number} colon where its `:` stands
 * @param {string} property its lowercased property name
 * @returns {string} the value to read it by
 */
const _writtenValue = (text, colon, property) => {
	// Decided before the expansion is built: only these are ever written as
	// something else, and every other property pays one lookup.
	if (!LOWERED_SHORTHANDS.has(property) || !_S._transforms.lowerUnsupported) {
		return _printedValue(text, colon);
	}
	const expanded = _expandUnsupportedShorthand(text);
	if (expanded === null) return _printedValue(text, colon);
	// The shorthand stands in front of the longhands written beside it, which is
	// what `LOWERED_SHORTHANDS` names the shorthands this may be asked about by.
	const at = expanded.indexOf(":");
	const end = expanded.indexOf(";", at);
	return at === -1 || end === -1
		? _printedValue(text, colon)
		: expanded.slice(at + 1, end);
};

/**
 * Whether a declaration sets a custom property `unusedSymbols` says nothing
 * reads. Only a declaration: a rule's own name is decided by its prelude.
 * @param {CssPath} path the walk's accessors
 * @param {Node} item one child of the block
 * @param {string} text its printed text
 * @returns {boolean} true when the declaration goes
 */
const _isUnusedCustomProperty = (path, item, text) => {
	if (_unusedSymbols === null || path.type(item) !== NodeType.Declaration) {
		return false;
	}
	const colon = text.indexOf(":");
	if (colon <= 0 || text.charCodeAt(1) !== CC_HYPHEN_MINUS) return false;
	return _unusedSymbols.has(unescapeIdentifier(text.slice(0, colon)));
};

/** @type {Int32Array | null} */
let _mergeableNameShapes = null;
/**
 * @returns {Int32Array} the lengths, by the two letters a mergeable longhand starts with
 */
const _mergeableNames = () => {
	if (_mergeableNameShapes === null) {
		const shapes = new Int32Array(26 * 26);
		// The two placement shorthands `_mergeGridPlacement` joins and the three
		// longhands `_mergeGridTemplate` does, beside the table.
		for (const name of [
			...MERGE_LONGHANDS,
			"grid-row",
			"grid-column",
			..._GRID_TEMPLATE_LONGHANDS
		]) {
			const slot = _nameShapeSlot(name.charCodeAt(0), name.charCodeAt(1));
			if (slot === -1) continue;
			shapes[slot] |= 1 << (name.length > 31 ? 31 : name.length);
		}
		_mergeableNameShapes = shapes;
	}
	return _mergeableNameShapes;
};

/**
 * Whether a name of this shape may be a mergeable longhand.
 * @param {number} first the name's first code point
 * @param {number} second its second code point
 * @param {number} length its length
 * @returns {boolean} false only when the name is certainly not one
 */
const _mayBeMergeableShape = (first, second, length) => {
	const slot = _nameShapeSlot(first, second);
	if (slot === -1) return length > 31 || first > 127 || second > 127;
	return ((_mergeableNames()[slot] >>> (length > 31 ? 31 : length)) & 1) !== 0;
};

/**
 * Whether a declaration's name has the shape of a mergeable longhand.
 * @param {Node} node the declaration
 * @returns {boolean} false only when the name is certainly not one
 */
const _mayBeMergeableName = (node) => {
	const i = _nodeIndex(node);
	const start = _starts[i];
	const end = _aux0[i];
	if (!_rangeHasEscape(start, end)) {
		return _mayBeMergeableShape(
			_S._input.charCodeAt(start),
			_S._input.charCodeAt(start + 1),
			end - start
		);
	}
	// An escape spells the same name, which is the one the shape is read off.
	const name = unescapeIdentifier(_S._input.slice(start, end));
	return _mayBeMergeableShape(
		name.charCodeAt(0),
		name.charCodeAt(1),
		name.length
	);
};
// Up to this many items a block's dead writes are found by comparing pairs: a
// block holds two declarations on average, and hashing each text costs more.
const _PAIRWISE_ITEMS = 32;
/** @type {string[]} */
const _pairProperties = [];
const _pairGone = new Uint8Array(_PAIRWISE_ITEMS);

/**
 * Whether a later declaration of a custom property leaves an earlier one of the
 * same name dead. Any token stream is a valid value, so only importance decides
 * — and a keyframe ignores an `!important` one rather than letting it win.
 * @param {CssPath} path the walk's accessors
 * @param {Node} earlier the earlier declaration
 * @param {Node} later the later one
 * @param {boolean} keyframe whether the block is a keyframe
 * @returns {boolean} true when the earlier is read nowhere
 */
const _customWriteDead = (path, earlier, later, keyframe) => {
	// The lowering appends its own pair after the body this reads, so an authored
	// write of either name is left for that print to decide, not dropped here.
	if (_S._loweringLightDark) {
		const name = path.name(later);
		if (name === LIGHT_PROPERTY || name === DARK_PROPERTY) return false;
	}
	const laterImportant = _isImportant(later);
	if (keyframe) return !laterImportant && !_isImportant(earlier);
	return laterImportant || !_isImportant(earlier);
};

/**
 * The dead-write pass of `_composeBlockBody` for a short block, pair by pair
 * rather than through maps; it marks the same items superseded.
 * @param {CssPath} path the walk's accessors
 * @param {Node[]} items the block's declarations and rules, in source order
 * @param {string[]} texts each item's printed text
 * @param {Set<number>} superseded where a dead item's index is added
 * @param {boolean} keyframe whether the block is a keyframe
 * @returns {void}
 */
const _supersedeByPairs = (path, items, texts, superseded, keyframe) => {
	const properties = _pairProperties;
	const gone = _pairGone;
	// Where the run of declarations since the last nested rule starts: a rule
	// parts the ones either side of it into their own places in the cascade.
	let segment = 0;
	for (let i = 0; i < items.length; i++) {
		const text = texts[i];
		properties[i] = "";
		if (path.type(items[i]) !== NodeType.Declaration) {
			segment = i + 1;
			// An identical later sibling wins the tie wherever both are read.
			if (text.length !== 0 && !_namesPositionalAtRule(text)) {
				for (let k = 0; k < i; k++) {
					if (
						texts[k] === text &&
						path.type(items[k]) !== NodeType.Declaration
					) {
						superseded.add(k);
					}
				}
			}
			continue;
		}
		gone[i] = 0;
		for (let k = segment; k < i; k++) {
			if (texts[k] === text) superseded.add(k);
		}
		const colon = text.indexOf(":");
		if (colon <= 0) continue;
		// A custom property's value is a token stream nothing here reads, but its
		// name is case-sensitive and a later one of it still wins.
		if (text.charCodeAt(1) === CC_HYPHEN_MINUS) {
			const name = text.slice(0, colon);
			properties[i] = name;
			for (let k = i - 1; k >= segment; k--) {
				if (gone[k] !== 0 || properties[k] !== name) continue;
				if (_customWriteDead(path, items[k], items[i], keyframe)) {
					superseded.add(k);
					gone[k] = 1;
				}
			}
			continue;
		}
		const property = _printedProperty(text, colon);
		properties[i] = property;
		let value = "";
		let names = false;
		let read = false;
		for (let k = i - 1; k >= segment; k--) {
			if (gone[k] !== 0 || properties[k] !== property) continue;
			if (!read) {
				value = _printedValue(text, colon);
				names = CUSTOM_IDENT_LIST_PROPERTIES.has(property);
				read = true;
			}
			const beforeText = texts[k];
			const written = _printedValue(beforeText, beforeText.indexOf(":"));
			if (
				_laterWriteWins(
					path,
					items[i],
					value,
					items[k],
					written,
					property,
					names,
					keyframe
				)
			) {
				superseded.add(k);
				gone[k] = 1;
			}
		}
	}
	properties.length = 0;
};

/**
 * Whether a block's winning `display` is one the 2009 flexbox draft lays out:
 * the last `!important` one where there is any, else the last one written. Only
 * that draft's displays and the flex ones replacing them count, since any other
 * may be one an engine drops.
 * @param {CssPath} path the walk's accessors
 * @param {Declaration[]} decls the block's declarations, in source order
 * @param {PrintContext} writer the print context (children's printed text)
 * @returns {boolean} true when that display names the draft
 */
const _laysOutAsLegacyBox = (path, decls, writer) => {
	const twins = /** @type {Map<string, [string, number][]>} */ (
		getPrefixedValues().get("display")
	);
	let legacy = false;
	let winnerImportant = false;
	for (const decl of decls) {
		if (asciiLowercaseName(path.name(decl)) !== "display") continue;
		const important = _isImportant(decl);
		if (winnerImportant && !important) continue;
		const text = writer.get(decl);
		const value = toLowerCaseIfNeeded(
			text
				.slice(text.indexOf(":") + 1)
				.replace(/;$/, "")
				.replace(_PRINTED_IMPORTANT_RE, "")
				.trim()
		);
		const isLegacy = LEGACY_BOX_DISPLAYS.has(value);
		if (!isLegacy && !twins.has(value)) continue;
		legacy = isLegacy;
		winnerImportant = important;
	}
	return legacy;
};

/**
 * The property a printed declaration sets, its other spellings read as it.
 * @param {string} property a lowercased property name
 * @returns {string} the property every spelling of it shares
 */
const _canonicalProperty = (property) => {
	const alias = getPropertyAliases().get(property);
	return alias === undefined ? property : alias;
};

/**
 * Whether the order of two declarations can decide what a block sets: one
 * property under two spellings, a shorthand and what it sets, two shorthands
 * sharing a longhand, or a flow-relative side and a physical one.
 * @param {string} a a property, read through `_canonicalProperty`
 * @param {string} b another
 * @returns {boolean} true when the two must keep their order
 */
const _orderMatters = (a, b) => {
	if (a === b || a === "all" || b === "all") return true;
	if (a.startsWith("--") || b.startsWith("--")) return false;
	let row = _orderMattersMemo.get(a);
	let matters = row === undefined ? undefined : row.get(b);
	if (matters !== undefined) return matters;
	matters = _computeOrderMatters(a, b);
	// The names are the author's, so a sheet of made-up ones starts again.
	if (_orderMattersEntries === _ORDER_MATTERS_ENTRIES) {
		_orderMattersMemo.clear();
		_orderMattersEntries = 0;
		row = undefined;
	}
	if (row === undefined) {
		row = new Map();
		_orderMattersMemo.set(a, row);
	}
	row.set(b, matters);
	_orderMattersEntries++;
	return matters;
};

// `_orderMatters` by its first property, then its second: a sheet names few
// properties, and the walks back over a block ask of the same pairs again.
/** @type {Map<string, Map<string, boolean>>} */
const _orderMattersMemo = new Map();
let _orderMattersEntries = 0;
const _ORDER_MATTERS_ENTRIES = 1 << 15;

/**
 * `_orderMatters` without the memo.
 * @param {string} a a property, read through `_canonicalProperty`
 * @param {string} b another
 * @returns {boolean} true when the two must keep their order
 */
const _computeOrderMatters = (a, b) => {
	// A corner's radius is no side's: `border-bottom` sets no `-radius` it starts.
	if (
		a.charCodeAt(0) !== CC_HYPHEN_MINUS &&
		b.charCodeAt(0) !== CC_HYPHEN_MINUS &&
		a.endsWith("-radius") !== b.endsWith("-radius")
	) {
		return false;
	}
	if (_extendsProperty(a, b) || _extendsProperty(b, a)) return true;
	// A vendor property no standard one aliases says nothing of what it sets
	// beyond its words: `-webkit-border-before` is a border side.
	if (
		a.charCodeAt(0) === CC_HYPHEN_MINUS ||
		b.charCodeAt(0) === CC_HYPHEN_MINUS
	) {
		return _sideRoot(a) === _sideRoot(b);
	}
	const table = getShorthandLonghands();
	const longA = table.get(a);
	const longB = table.get(b);
	if (longA !== undefined) {
		if (longA.has(b)) return true;
		if (longB !== undefined) {
			for (const longhand of longB) if (longA.has(longhand)) return true;
		}
	}
	if (longB !== undefined && longB.has(a)) return true;
	if (_sideRoot(a) !== _sideRoot(b)) return false;
	return (
		(_LOGICAL_SIDE_RE.test(a) || _LOGICAL_SIDE_RE.test(b)) &&
		(_PHYSICAL_SIDE_RE.test(a) || _PHYSICAL_SIDE_RE.test(b))
	);
};

/**
 * Whether a property name is another's followed by more words: `margin-top` of
 * `margin`. Read in place, as a `${base}-` prefix would be built on every call.
 * @param {string} name a property
 * @param {string} base another
 * @returns {boolean} true when `name` starts with `base` and a `-`
 */
const _extendsProperty = (name, base) =>
	name.length > base.length &&
	name.charCodeAt(base.length) === CC_HYPHEN_MINUS &&
	name.startsWith(base);

// A box side, corner or axis a property names, physical or flow-relative.
const _PHYSICAL_SIDE_RE =
	/-(?:top|right|bottom|left|x|y)(?=-|$)|^(?:(?:min|max)-)?(?:top|right|bottom|left|width|height)$|-(?:width|height)$/;
const _LOGICAL_SIDE_RE = /(?:^|-)(?:inline|block|start|end)(?=-|$)/;

/**
 * Whether a declaration between a merge's slots keeps the later slots from
 * moving above it. Of a property no dataset names only the words are known:
 * a vendor one may set anything, another its own family.
 * @param {string} between the property standing between, lowercased
 * @param {string[]} later the slots written after it
 * @param {string[]} families the prefixes an unnamed property is read by
 * @returns {boolean} true when the merge must not step over it
 */
const _blocksMerge = (between, later, families) => {
	if (between.startsWith("--")) return false;
	if (getDeclarationRanks().has(between)) {
		const property = _canonicalProperty(between);
		for (const slot of later) {
			if (_orderMatters(property, _canonicalProperty(slot))) return true;
		}
		return false;
	}
	if (between.charCodeAt(0) === CC_HYPHEN_MINUS) return true;
	return families.some(
		(family) => between === family || between.startsWith(`${family}-`)
	);
};

// The properties `_sideRoot` reads as `inset`'s and as `size`'s sides.
const _INSET_SIDE_RE = /^(?:top|right|bottom|left)$/;
const _SIZE_PROPERTY_RE =
	/^(?:(?:min|max)-)?(?:width|height|inline-size|block-size)$/;

/**
 * The word a property's sides share: `margin` for `margin-left` and
 * `margin-inline-start`, `inset` for `top`, `size` for `width` and `inline-size`.
 * @param {string} property a lowercased property name
 * @returns {string} that word
 */
const _sideRoot = (property) => {
	const bare =
		property.charCodeAt(0) === CC_HYPHEN_MINUS
			? property.replace(_VENDOR_ITEM_PREFIX_RE, "")
			: property;
	if (_INSET_SIDE_RE.test(bare)) return "inset";
	if (_SIZE_PROPERTY_RE.test(bare)) return "size";
	const dash = bare.indexOf("-");
	return dash === -1 ? bare : bare.slice(0, dash);
};

/**
 * Whether a block writes declarations and nothing else: a nested rule that
 * prints nothing is no rule its order has to keep.
 * @param {CssPath} path the walk's accessors
 * @param {Node[]} items the block's children
 * @param {string[]} texts what each printed
 * @returns {boolean} true when every child written is a declaration
 */
const _writesOnlyDeclarations = (path, items, texts) => {
	for (let i = 0; i < items.length; i++) {
		if (texts[i].length !== 0 && path.type(items[i]) !== NodeType.Declaration) {
			return false;
		}
	}
	return true;
};

/**
 * The order to print a block's declarations in: each one no family ranks where
 * it stands, then the ranked ones family by family, so blocks setting the same
 * things read alike. Null keeps the block as written — a property no dataset
 * names, or two declarations whose order can decide what is read, would move.
 * @param {string[]} texts the declarations as printed
 * @returns {number[] | null} where each comes from, or null to keep them as written
 */
const _declarationOrder = (texts) => {
	const table = _declarationTable();
	/** @type {number[]} */
	const properties = [];
	/** @type {number[]} */
	const rankOf = [];
	let ranked = false;
	for (const text of texts) {
		const colon = text.indexOf(":");
		if (colon <= 0) return null;
		const property = toLowerCaseIfNeeded(text.slice(0, colon));
		if (property.startsWith("--")) {
			properties.push(-1);
			rankOf.push(-1);
			continue;
		}
		const id = table.ids.get(property);
		if (id === undefined) return null;
		const rank = table.ranks[id];
		if (rank !== -1) ranked = true;
		properties.push(table.canonical[id]);
		rankOf.push(rank);
	}
	if (!ranked) return null;
	// Already in rank order, which half of all blocks are: the stable sort would
	// hand every index back where it stands.
	let inOrder = true;
	for (let i = 1; i < rankOf.length && inOrder; i++) {
		if (rankOf[i] < rankOf[i - 1]) inOrder = false;
	}
	if (inOrder) return null;
	// A stable insertion sort by rank: blocks are short, and this allocates no
	// comparator and no copy as `Array#sort` does.
	/** @type {number[]} */
	const order = [];
	for (let i = 0; i < rankOf.length; i++) {
		let at = order.length;
		order.push(i);
		while (at > 0 && rankOf[order[at - 1]] > rankOf[i]) {
			order[at] = order[at - 1];
			at--;
		}
		order[at] = i;
	}
	let moved = false;
	for (let i = 0; i < order.length; i++) {
		if (order[i] !== i) moved = true;
		// Each declaration that now stands after one written later than it, the
		// ones still at their index included: `[a, b, c]` printed `[c, b, a]`.
		for (let j = i + 1; j < order.length; j++) {
			if (
				order[j] < order[i] &&
				_orderMattersById(table, properties[order[i]], properties[order[j]])
			) {
				return null;
			}
		}
	}
	return moved ? order : null;
};

/**
 * Every property `getDeclarationRanks` names by a small number: its rank, the
 * number of the property `_canonicalProperty` reads it as, and `_orderMatters`
 * of two such numbers, filled in as pairs are asked of.
 * @typedef {object} DeclarationTable
 * @property {Map<string, number>} ids each property's number
 * @property {Int32Array} ranks the rank of each number
 * @property {Int32Array} canonical the number of the property each is read as
 * @property {string[]} names the property each number stands for
 * @property {number} all the number of `all`
 * @property {Uint8Array} matters `_orderMatters` by `a * names.length + b`: 0 unknown, 1 no, 2 yes
 */

/** @type {DeclarationTable | undefined} */
let _declarationTableValue;

/**
 * @returns {DeclarationTable} the table, built on first use
 */
const _declarationTable = () => {
	if (_declarationTableValue !== undefined) return _declarationTableValue;
	const rankMap = getDeclarationRanks();
	/** @type {Map<string, number>} */
	const ids = new Map();
	/** @type {string[]} */
	const names = [];
	for (const name of rankMap.keys()) {
		ids.set(name, names.length);
		names.push(name);
	}
	const ranks = new Int32Array(names.length);
	const canonical = new Int32Array(names.length);
	for (let i = 0; i < names.length; i++) {
		ranks[i] = /** @type {number} */ (rankMap.get(names[i]));
		const id = ids.get(_canonicalProperty(names[i]));
		canonical[i] = id === undefined ? i : id;
	}
	_declarationTableValue = {
		ids,
		ranks,
		canonical,
		names,
		all: /** @type {number} */ (ids.get("all")),
		matters: new Uint8Array(names.length * names.length)
	};
	return _declarationTableValue;
};

/**
 * `_orderMatters` of two numbered properties, -1 for a custom one.
 * @param {DeclarationTable} table the numbering
 * @param {number} a a property's canonical number
 * @param {number} b another
 * @returns {boolean} true when the two must keep their order
 */
const _orderMattersById = (table, a, b) => {
	if (a === b || a === table.all || b === table.all) return true;
	// Two custom properties are both -1 yet never trade places: they share a rank.
	if (a === -1 || b === -1) return false;
	const at = a * table.names.length + b;
	const known = table.matters[at];
	if (known !== 0) return known === 2;
	const matters = _computeOrderMatters(table.names[a], table.names[b]);
	table.matters[at] = matters ? 2 : 1;
	return matters;
};

/**
 * Put one declaration list together: the list-wide transforms (merge a family's
 * longhands into their shorthand, drop what a later declaration supersedes, add
 * and drop vendor prefixes, join adjacent rules) and the body text they make.
 * A rule's block and a whole `block-contents` parse are the same production, so
 * both go through here — what differs is only what is written around the body.
 * @param {CssPath} path the walk's accessors
 * @param {Declaration[]} decls the list's declarations
 * @param {Rule[] | null} rules the rules interleaved with them, if any
 * @param {PrintContext} writer the print context holding each node's text
 * @param {boolean} minify whether to run the transforms
 * @param {string} nl what prefixes each item: nothing minifying, a line break beautifying (declarations end in `;`, rules don't)
 * @param {Node | null} owner the block this is the body of, or null at top level
 * @param {boolean=} unusedRule whether `unusedSymbols` marked this block's rule, so nothing in it is written
 * @returns {{ body: string, items: Node[], texts: string[], superseded: Set<number> | null, droppedPrefix: Set<number> | null, addedPrefix: Map<number, string> | null, deadPrefixed: Set<Node> | null, spans: RuleSpan[] | null }} the body, and what went into it
 */
const _composeBlockBody = (
	path,
	decls,
	rules,
	writer,
	minify,
	nl,
	owner,
	unusedRule = false
) => {
	// Merge the split declarations + child rules back into source order
	// (§5.4.5 loses their interleaving; reordering would change the cascade).
	/** @type {Node[]} */
	let items = decls;
	if (rules !== null && rules.length !== 0) {
		items =
			decls.length === 0
				? rules
				: [...decls, ...rules].sort((a, b) => _startOf(a) - _startOf(b));
	}
	// CSS Animations 1 §3: a keyframe ignores an `!important` declaration, so
	// one there is read for nothing and stands over nothing before it.
	const keyframe =
		owner !== null &&
		path.parent !== null &&
		path.type(path.parent) === NodeType.AtRule &&
		KEYFRAMES_AT_RULE_RE.test(_atKeywordOf(path.parent));
	_legacyBoxBlock =
		minify && _S._prefixingOn && _laysOutAsLegacyBox(path, decls, writer);
	// A family's longhands are their shorthand, so they print as it: four sides or
	// corners, the two a pair shorthand sets, or an order-free one's slots.
	// Earlier tables win, though no property is in two.
	const mergeable = minify;
	/** @type {Map<Node, string>[] | null} */
	let merges = null;
	// A write a later one of the same property leaves carrying nothing, found
	// before the merge so the family reads as the output will carry it.
	/** @type {Set<Node> | null} */
	let deadWrites = null;
	let shaped = 0;
	if (mergeable && decls.length >= 2) {
		for (let i = 0; i < decls.length && shaped < 2; i++) {
			if (_mayBeMergeableName(decls[i])) shaped++;
		}
	}
	if (shaped >= 2) {
		// One pass over the names for all three tables: which declaration wrote each
		// property, which was written twice, and how many a shorthand could gather.
		// One needs two, so a block holding fewer writes none — almost every block.
		/** @type {Map<string, number>} */
		const at = new Map();
		/** @type {Set<string>} */
		const repeated = new Set();
		let mergeableNames = 0;
		for (let i = 0; i < decls.length; i++) {
			const property = asciiLowercaseName(path.name(decls[i]));
			const before = at.get(property);
			if (before === undefined) {
				if (MERGE_LONGHANDS.has(property)) mergeableNames++;
			} else if (
				_S._transforms.removeDeadRules &&
				_writeIsDead(path, writer, decls[before], decls[i], keyframe)
			) {
				if (deadWrites === null) deadWrites = new Set();
				deadWrites.add(decls[before]);
			} else {
				repeated.add(property);
			}
			at.set(property, i);
		}
		const placement = at.has("grid-row") && at.has("grid-column");
		const template = _GRID_TEMPLATE_LONGHANDS.every((name) => at.has(name));
		if (mergeableNames >= 2 || placement || template) {
			// Child rules before each declaration: a family whose first and last
			// count the same has none between them for the merge to step over.
			/** @type {Uint32Array | null} */
			let rulesBefore = null;
			if (items !== decls) {
				rulesBefore = new Uint32Array(decls.length);
				let seen = 0;
				let next = 0;
				for (let i = 0; i < items.length; i++) {
					if (next < decls.length && items[i] === decls[next]) {
						rulesBefore[next++] = seen;
					} else {
						seen++;
					}
				}
			}
			for (let t = 0; t < MERGE_TABLES.length; t++) {
				const table = MERGE_TABLES[t][0];
				const mode = MERGE_TABLES[t][1];
				const merged = _mergeBoxLonghands(
					path,
					decls,
					writer,
					table,
					mode,
					at,
					repeated,
					rulesBefore
				);
				if (merged !== null) {
					if (merges === null) merges = [];
					merges.push(merged);
				}
			}
			const grid =
				placement && _S._transforms.mergeLonghands
					? _mergeGridPlacement(path, decls, writer, at, repeated, rulesBefore)
					: null;
			if (grid !== null) {
				if (merges === null) merges = [];
				merges.push(grid);
			}
			const gridTemplate =
				template && _S._transforms.mergeLonghands
					? _mergeGridTemplate(path, decls, writer, at, repeated, rulesBefore)
					: null;
			if (gridTemplate !== null) {
				if (merges === null) merges = [];
				merges.push(gridTemplate);
			}
		}
	}
	/** @type {string[]} */
	const texts = [];
	// A rule `unusedSymbols` marked writes nothing, which is what the empty-block
	// drop below — and `_streamClose`'s — then takes out; a custom property it
	// named goes on its own, wherever it stands.
	const dropAll = unusedRule;
	for (const item of items) {
		/** @type {string | undefined} */
		let replacement;
		if (merges !== null) {
			for (const merged of merges) {
				replacement = merged.get(item);
				if (replacement !== undefined) break;
			}
		}
		let text = replacement === undefined ? writer.get(item) : replacement;
		if (minify && (dropAll || _unusedSymbols !== null)) {
			text = dropAll || _isUnusedCustomProperty(path, item, text) ? "" : text;
		}
		texts.push(text);
	}
	// The block's children have all printed, so their sibling state is final:
	// which a twin has made dead, and which still wait for one. A rule that may
	// yet be taken back stays one rule, so it is kept out of a join.
	const childScope =
		_seenPrefixRules === null ? undefined : _seenPrefixRules.get(owner);
	// One signature holds every twin written under it, so the rules are the
	// lists flattened rather than the values themselves.
	let pendingRules = null;
	if (childScope !== undefined && childScope.pending !== null) {
		pendingRules = /** @type {Set<Node>} */ (new Set());
		for (const held of childScope.pending.values()) {
			for (const node of held) pendingRules.add(node);
		}
	}
	// A block of declarations alone, most of them, holds no rule to join or drop.
	if (minify && items !== decls) {
		// Before the joins, so a rule it takes out stands in none of their way;
		// and again after any join, which can write a rule again in its turn.
		if (_S._transforms.removeDeadRules) {
			_dropRulesWrittenAgain(items, texts);
			while (
				_mergeAdjacentRules(items, texts, pendingRules, null) &&
				_dropRulesWrittenAgain(items, texts)
			);
		}
		_mergeAdjacentRules(items, texts, pendingRules, null);
		_mergeNamedLayerBlocks(items, texts);
		_mergeDistantRules(items, texts);
	}
	// WHY: only the last of a set of declarations of one property can be read, so
	// the earlier carry nothing, whatever stands between them. Identical text
	// always resolves that way; a differently spelled value where the later writes
	// every name the earlier does, or where every target reads it
	// (`_valueReadByTarget`), since otherwise the earlier is its fallback —
	// dropping those is how esbuild and lightningcss lose a `color(display-p3 …)`
	// pair.
	// With no selection there are no engines to name, so the same question is
	// answered only where `dropOverriddenDeclarations` asks for it — csso and
	// cssnano answer it unconditionally, and give up the same fallback pair.
	/** @type {Set<number> | null} */
	let superseded = null;
	// CSS Animations 1 §3: a keyframe ignores an `!important` declaration, so one
	// there is read for nothing and stands over nothing. One item supersedes
	// nothing, and most rules hold one or none.
	const dropDead = minify && _S._transforms.removeDeadRules && items.length > 1;
	const foldLonghands =
		minify && _S._transforms.mergeLonghands && items.length > 1;
	if (dropDead || foldLonghands) {
		superseded = new Set();
		if (dropDead) {
			// A write the merge above already read as dead goes here: it read the
			// family as the output carries it, so the output must carry it that way.
			if (deadWrites !== null) {
				for (let i = 0; i < items.length; i++) {
					if (deadWrites.has(items[i])) superseded.add(i);
				}
			}
			if (items.length <= _PAIRWISE_ITEMS) {
				_supersedeByPairs(path, items, texts, superseded, keyframe);
			} else {
				/** @type {Map<string, number>} */
				const seen = new Map();
				/** @type {Map<string, number[]>} */
				const wrote = new Map();
				/** @type {Map<string, number> | null} */
				let seenRules = null;
				for (let i = 0; i < items.length; i++) {
					// A nested rule parts the declarations around it into their own
					// rules, so the one before it is read at its own place in the
					// cascade rather than resolving to the one after.
					if (path.type(items[i]) !== NodeType.Declaration) {
						seen.clear();
						wrote.clear();
						const rule = texts[i];
						// An identical later sibling wins the tie wherever both are read, so
						// this one is read for nothing whatever stands between them.
						if (rule.length !== 0 && !_namesPositionalAtRule(rule)) {
							if (seenRules === null) seenRules = new Map();
							const before = seenRules.get(rule);
							if (before !== undefined) superseded.add(before);
							seenRules.set(rule, i);
						}
						continue;
					}
					const text = texts[i];
					const at = seen.get(text);
					if (at !== undefined) superseded.add(at);
					seen.set(text, i);
					const colon = text.indexOf(":");
					if (colon <= 0) continue;
					// A custom property's value is a token stream nothing here reads, so
					// only its case-sensitive name and its importance decide.
					if (text.charCodeAt(1) === CC_HYPHEN_MINUS) {
						const name = text.slice(0, colon);
						const before = wrote.get(name);
						if (before === undefined) {
							wrote.set(name, [i]);
						} else {
							for (let j = before.length - 1; j >= 0; j--) {
								if (
									!_customWriteDead(path, items[before[j]], items[i], keyframe)
								) {
									continue;
								}
								superseded.add(before[j]);
								before.splice(j, 1);
							}
							before.push(i);
						}
						continue;
					}
					const property = _printedProperty(text, colon);
					const names = CUSTOM_IDENT_LIST_PROPERTIES.has(property);
					let earlier = wrote.get(property);
					if (earlier === undefined) {
						earlier = [];
						wrote.set(property, earlier);
					} else {
						const value = _printedValue(text, colon);
						for (let j = earlier.length - 1; j >= 0; j--) {
							const before = earlier[j];
							const beforeText = texts[before];
							const written = _printedValue(
								beforeText,
								beforeText.indexOf(":")
							);
							if (
								!_laterWriteWins(
									path,
									items[i],
									value,
									items[before],
									written,
									property,
									names,
									keyframe
								)
							) {
								continue;
							}
							superseded.add(before);
							earlier.splice(j, 1);
						}
					}
					earlier.push(i);
				}
			}
		}
		// After the supersedes: a declaration a later one already made dead is
		// no shorthand to fold into, and folding never makes one dead.
		if (foldLonghands) {
			_foldFollowingLonghands(path, items, texts, superseded);
		}
		// A dead child parts nothing, so the two that stood either side of one
		// are adjacent in what goes out and join there rather than a pass later.
		if (superseded.size !== 0) {
			_mergeAdjacentRules(items, texts, pendingRules, superseded);
		}
	}
	// Vendor prefixes: drop a prefixed declaration no target needs (its
	// unprefixed sibling already covers them), and before an unprefixed one
	// add the prefixes a target still needs. Off with no target list.
	/** @type {Set<number> | null} */
	let droppedPrefix = null;
	/** @type {Map<number, string> | null} */
	let addedPrefix = null;
	if (_S._prefixingOn) {
		// One pass reads each declaration's property, and only a block writing
		// a property some engine spells with a prefix — few do — is walked
		// again to decide what to add or drop.
		/** @type {string[]} */
		const properties = [];
		// The value only where its property has vendor spellings for one, which
		// is what keeps the read off every other declaration.
		/** @type {string[]} */
		const values = [];
		let prefixable = false;
		for (let i = 0; i < items.length; i++) {
			const text = texts[i];
			if (path.type(items[i]) !== NodeType.Declaration || text.length === 0) {
				properties.push("");
				values.push("");
				continue;
			}
			// The name is read only where its shape says it could matter, which
			// is what keeps this off the declarations — almost all of them — that
			// no engine ever spelled another way.
			const colon = text.indexOf(":");
			if (!_mayPrefixDeclaration(text, colon)) {
				properties.push("");
				values.push("");
				continue;
			}
			const property = _printedProperty(text, colon);
			properties.push(property);
			const valueTable = getPrefixedValues().get(property);
			values.push(valueTable === undefined ? "" : _printedValue(text, colon));
			if (
				valueTable !== undefined ||
				getPrefixedProperties().has(property) ||
				(property.charCodeAt(0) === CC_HYPHEN_MINUS &&
					VENDOR_PREFIX.test(property))
			) {
				prefixable = true;
			}
		}
		if (prefixable) {
			/** @type {Set<string>} */
			const present = new Set();
			// Where each value was last written, plain and important, so a vendor
			// spelling is dropped only for a twin that wins the cascade over it.
			/** @type {Map<string, number>} */
			const plainAt = new Map();
			/** @type {Map<string, number>} */
			const importantAt = new Map();
			for (let i = 0; i < properties.length; i++) {
				if (properties[i].length === 0) continue;
				present.add(properties[i]);
				(_isImportant(items[i]) ? importantAt : plainAt).set(properties[i], i);
				// A value is only ever asked for against its own property, so the
				// two share one set.
				if (values[i].length !== 0) {
					const key = `${properties[i]}\0${values[i]}`;
					present.add(key);
					(_isImportant(items[i]) ? importantAt : plainAt).set(key, i);
				}
			}
			for (let i = 0; i < properties.length; i++) {
				const property = properties[i];
				if (property.length === 0) continue;
				let add = "";
				const value = values[i];
				if (value.length !== 0) {
					const valueTable = /** @type {Map<string, [string, number][]>} */ (
						getPrefixedValues().get(property)
					);
					const keyword = _prefixedValueKeyword(valueTable, value);
					if (keyword !== undefined) {
						// A vendor-spelled value is dead where its keyword wins: an alias
						// means that keyword wherever it parses, so importance decides, but
						// a 2009 box lays out apart, so the keyword must come later too.
						const twin = `${property}\0${keyword}`;
						const legacyBox = LEGACY_BOX_DISPLAYS.has(
							toLowerCaseIfNeeded(value)
						);
						const importantTwin = importantAt.get(twin);
						const plainTwin = plainAt.get(twin);
						const twinWins = _isImportant(items[i])
							? importantTwin !== undefined && (importantTwin > i || !legacyBox)
							: importantTwin !== undefined ||
								(plainTwin !== undefined && (plainTwin > i || !legacyBox));
						if (twinWins && _prefixRemovable(valueTable, keyword, value)) {
							if (droppedPrefix === null) droppedPrefix = new Set();
							droppedPrefix.add(i);
							continue;
						}
					} else {
						const spellings = _neededPrefixes(valueTable, value);
						if (spellings !== null) {
							const colon = property.length;
							for (const spelling of spellings) {
								if (present.has(`${property}\0${spelling}`)) continue;
								add += `${
									nl +
									texts[i].slice(0, colon + 1) +
									spelling +
									texts[i].slice(colon + 1 + value.length)
								}`;
							}
						}
					}
				}
				const base =
					property.charCodeAt(0) === CC_HYPHEN_MINUS
						? _prefixedPropertyName(property)
						: undefined;
				if (base !== undefined) {
					// An engine still reading the vendor name as an alias cascades the
					// two as one property, so it is dead only behind a twin that wins.
					const importantBase = importantAt.get(base);
					const plainBase = plainAt.get(base);
					const important = _isImportant(items[i]);
					const baseWins = important
						? importantBase !== undefined && importantBase > i
						: importantBase !== undefined ||
							(plainBase !== undefined && plainBase > i);
					// ...or behind one of the same importance saying the same thing.
					const twinAt = important ? importantBase : plainBase;
					const sameTwin =
						twinAt !== undefined &&
						(important || importantBase === undefined) &&
						texts[twinAt].slice(
							base.length + 1,
							_printedValueEnd(texts[twinAt], base.length)
						) ===
							texts[i].slice(
								property.length + 1,
								_printedValueEnd(texts[i], property.length)
							);
					if (
						(baseWins || sameTwin) &&
						_prefixedPropertyRemovable(base, property)
					) {
						if (droppedPrefix === null) droppedPrefix = new Set();
						droppedPrefix.add(i);
						continue;
					}
				} else {
					const needed = _neededPrefixes(getPrefixedProperties(), property);
					if (needed !== null) {
						for (const spelling of needed) {
							if (present.has(spelling)) continue;
							const copy = _prefixedDeclaration(
								spelling,
								texts[i],
								property.length
							);
							if (copy.length !== 0) add += nl + copy;
						}
					}
				}
				if (add.length !== 0) {
					if (addedPrefix === null) addedPrefix = new Map();
					addedPrefix.set(i, add);
				}
			}
		}
	}
	// A color the target cannot read gets the declaration it names written again
	// before it, in a spelling it does. Only where the selection names a function
	// no target reads, which keeps this off every other block.
	/** @type {Map<number, string> | null} */
	let addedFallback = null;
	if (minify && _colorFallbacksNeeded && _S._transforms.colorFallbacks) {
		/** @type {Set<string>} */
		const alreadyWritten = new Set();
		for (let i = 0; i < items.length; i++) {
			const text = texts[i];
			if (path.type(items[i]) !== NodeType.Declaration || text.length === 0) {
				continue;
			}
			// One the body never writes is no fallback the author left.
			if (superseded !== null && superseded.has(i)) continue;
			if (droppedPrefix !== null && droppedPrefix.has(i)) continue;
			const colon = text.indexOf(":");
			const property = _printedProperty(text, colon);
			// An earlier declaration of the same property is already the fallback the
			// author wrote. A shorthand the target cannot read is lowered below into
			// longhands carrying their own, spelled as this one would not be.
			if (
				!alreadyWritten.has(property) &&
				text.includes("(") &&
				!(
					_S._transforms.lowerUnsupported &&
					_expandUnsupportedShorthand(text) !== null
				)
			) {
				const fallback = _colorFallbackDeclaration(text, colon);
				if (fallback !== null) {
					if (addedFallback === null) addedFallback = new Map();
					addedFallback.set(i, fallback);
				}
			}
			alreadyWritten.add(property);
		}
	}
	// Every child has printed by now, so the sibling set they shared goes.
	if (_seenPrefixRules !== null && owner !== null) {
		_seenPrefixRules.delete(owner);
	}
	// A nested prefixed rule an unprefixed twin has since made dead weight:
	// the twin could not take it back out of the output, since the text was
	// still on its way here, so it is left out as the body is put together.
	const deadPrefixed = childScope === undefined ? null : childScope.dead;
	let body = "";
	// Where each rule this block carries lands in it, so the copy making one dead
	// finds it without a second read of the sheet. A nested block's spans move in
	// with its text and its prelude joins their keys.
	/** @type {RuleSpan[] | null} */
	let spans = null;
	// One per child with a body, in the order they finished — which is what
	// `rules` holds, so there is nothing to count. Nothing reads them while
	// not minifying, when no block records any.
	const bodies = minify && rules !== null ? rules.length : 0;
	const childSpans =
		bodies === 0 ? _NO_BLOCK_SPANS : _blockSpans.splice(-bodies, bodies);
	let atChild = 0;
	// What each written declaration prints as, its fallback and vendor copies
	// with it, where the block's order is one `_declarationOrder` may choose.
	/** @type {string[] | null} */
	const pieces =
		minify &&
		_S._transforms.reorderDeclarations &&
		items.length > 1 &&
		owner !== null &&
		(path.type(owner) === NodeType.QualifiedRule ||
			_NESTED_GROUP_AT_RULES.has(_atKeywordOf(owner))) &&
		_writesOnlyDeclarations(path, items, texts)
			? []
			: null;
	/** @type {string[]} */
	const pieceTexts = [];
	// The body before its last piece, and that piece: the `;` a `}` makes
	// redundant is cut off the piece, as reading the body's end would flatten it.
	let prior = "";
	let last = "";
	for (let i = 0; i < texts.length; i++) {
		const isBlock =
			bodies !== 0 && path.type(items[i]) !== NodeType.Declaration;
		const mine = isBlock ? childSpans[atChild++] : undefined;
		if (droppedPrefix !== null && droppedPrefix.has(i)) continue;
		let text = texts[i];
		if (text.length === 0) continue;
		if (superseded !== null && superseded.has(i)) continue;
		if (deadPrefixed !== null && deadPrefixed.has(items[i])) continue;
		// Before the vendor spellings: this one is the oldest of the set, and the
		// only one an engine reading none of the others is left with.
		if (addedFallback !== null) {
			const fallback = addedFallback.get(i);
			if (fallback !== undefined) {
				body +=
					nl + (_oldTokenizer ? _spaceForOldTokenizer(fallback) : fallback);
			}
		}
		if (addedPrefix !== null) {
			const add = addedPrefix.get(i);
			if (add !== undefined) {
				body += _oldTokenizer ? _spaceForOldTokenizer(add) : add;
			}
		}
		// Last, over what every list-wide pass above left: those read a
		// declaration's property out of its text, and a shorthand lowered into its
		// longhands is no longer one declaration to read.
		if (minify && !isBlock) {
			const expanded = _S._transforms.lowerUnsupported
				? _expandUnsupportedShorthand(text)
				: null;
			if (expanded !== null) text = expanded;
			if (_oldTokenizer) text = _spaceForOldTokenizer(text);
		}
		if (pieces !== null) {
			pieces.push(body + text);
			pieceTexts.push(texts[i]);
			body = "";
			continue;
		}
		body += nl;
		const at = body.length;
		prior = body;
		last = text;
		body += text;
		if (minify && isBlock) {
			if (spans === null) spans = [];
			_collectRuleSpans(spans, items[i], text, at, path, mine);
		}
	}
	if (pieces !== null) {
		const order = _declarationOrder(pieceTexts);
		for (let i = 0; i < pieces.length; i++) {
			prior = body;
			last = pieces[order === null ? i : order[i]];
			body += last;
		}
	}
	// Drop the redundant `;` a following `}` — or, at top level, the end of the
	// input — makes so, when minifying.
	if (minify && last.charCodeAt(last.length - 1) === CC_SEMICOLON) {
		body = prior + last.slice(0, -1);
	}

	return {
		body,
		items,
		texts,
		superseded,
		droppedPrefix,
		addedPrefix,
		deadPrefixed,
		spans
	};
};

/**
 * Build a `SkipOptions.types` set (drop these component-value node types from
 * value / function-arg lists) from a list of `NodeType`s. Preludes are separate
 * (`SkipOptions.selectorPrelude` / `atRulePrelude`). The caller owns the safety
 * contract: only pass types nothing reads in the intended parse. Two
 * grammar-internal caveats beyond consumer needs: dropping both `Delim` and
 * `Ident` loses `!important` detection, and dropping `SimpleBlock` loses the
 * custom-property `{}`-value check (and its subtree). Precompute once per
 * configuration and reuse across parses.
 * @param {number[]} nodeTypes component-value node types to drop
 * @returns {Uint8Array} skip-types set indexed by `NodeType`
 */
const buildSkipSet = (nodeTypes) => {
	const set = new Uint8Array(32);
	for (let i = 0; i < nodeTypes.length; i++) set[nodeTypes[i]] = 1;
	return set;
};

/* eslint-disable jsdoc/require-template -- `A` below is the accessor const, not a type parameter */
/**
 * The CSS path: the AST accessor with the walk's
 * current position on it — the single argument every visitor receives.
 * @typedef {typeof A} CssPath
 */
/* eslint-enable jsdoc/require-template */

/** @typedef {1 | 2 | 3 | 4} CssListField a list `Field` */
/** @typedef {1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22} CssComponentValueType a component value's `NodeType` */
/**
 * CSS's parts by node type, which type each visitor's path; every CSS `Field`
 * is a list.
 * @typedef {{
 * types: CssNodeType,
 * field: CssListField,
 * flag: typeof Flag.important | typeof Flag.integer | typeof Flag.id,
 * part: 1 | 2 | 3,
 * text: never,
 * fields: { 1: never, 2: 2, 3: never, 4: never, 5: never, 6: never, 7: never, 8: never, 9: never, 10: never, 11: never, 12: never, 13: never, 14: never, 15: never, 16: never, 17: never, 18: never, 19: never, 20: never, 21: never, 22: 2, 23: 2, 24: 1 | 3 | 4, 25: 1 | 3 | 4, 26: 4, 27: never, 28: never },
 * flags: { 1: never, 2: never, 3: never, 4: typeof Flag.id, 5: never, 6: never, 7: never, 8: never, 9: never, 10: typeof Flag.integer, 11: typeof Flag.integer, 12: typeof Flag.integer, 13: never, 14: never, 15: never, 16: never, 17: never, 18: never, 19: never, 20: never, 21: never, 22: never, 23: typeof Flag.important, 24: never, 25: never, 26: never, 27: never, 28: never },
 * parts: { 1: never, 2: typeof Part.name, 3: never, 4: never, 5: never, 6: never, 7: typeof Part.value, 8: never, 9: never, 10: never, 11: never, 12: typeof Part.name, 13: never, 14: never, 15: never, 16: never, 17: never, 18: never, 19: never, 20: never, 21: never, 22: never, 23: typeof Part.name, 24: typeof Part.name | typeof Part.block, 25: typeof Part.block, 26: never, 27: never, 28: never },
 * texts: { 1: never, 2: never, 3: never, 4: never, 5: never, 6: never, 7: never, 8: never, 9: never, 10: never, 11: never, 12: never, 13: never, 14: never, 15: never, 16: never, 17: never, 18: never, 19: never, 20: never, 21: never, 22: never, 23: never, 24: never, 25: never, 26: never, 27: never, 28: never },
 * items: { [field: number]: number, 1: CssComponentValueType, 2: CssComponentValueType, 3: 23, 4: 24 | 25 },
 * }} CssParts
 */
/**
 * The path a visitor of node type `T` receives, with the run's state `S`.
 * @template [T=number]
 * @template [S=unknown]
 * @typedef {import("../util/SourceProcessor").TypedNodePath<CssPath, Node, CssParts, T, S>} CssNodePath
 */
/**
 * A visitor map whose each visitor's path is typed by its node type and run state.
 * @template [S=unknown]
 * @typedef {{
 * 1?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<1, S>>,
 * 2?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<2, S>>,
 * 3?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<3, S>>,
 * 4?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<4, S>>,
 * 5?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<5, S>>,
 * 6?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<6, S>>,
 * 7?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<7, S>>,
 * 8?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<8, S>>,
 * 9?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<9, S>>,
 * 10?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<10, S>>,
 * 11?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<11, S>>,
 * 12?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<12, S>>,
 * 13?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<13, S>>,
 * 14?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<14, S>>,
 * 15?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<15, S>>,
 * 16?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<16, S>>,
 * 17?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<17, S>>,
 * 18?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<18, S>>,
 * 19?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<19, S>>,
 * 20?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<20, S>>,
 * 21?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<21, S>>,
 * 22?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<22, S>>,
 * 23?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<23, S>>,
 * 24?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<24, S>>,
 * 25?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<25, S>>,
 * 26?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<26, S>>,
 * 27?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<27, S>>,
 * 28?: import("../util/SourceProcessor").VisitorBucket<CssNodePath<28, S>>,
 * }} CssVisitorMap
 */
/** @typedef {(typeof NodeType)[keyof typeof NodeType]} CssNodeType a `NodeType` member */

/**
 * Append the printed text of every child of `n` to `out`, in order — read where
 * the child list lies rather than materialized into a list of its own first.
 * @param {Node} n the node
 * @param {PrintContext} writer the print context (children's printed text)
 * @param {string[]} out the array to append to
 */
const _appendChildTexts = (n, writer, out) => {
	const i = _nodeIndex(n);
	const start = _listStarts[i];
	const len = _listLens[i];
	for (let k = 0; k < len; k++) {
		out.push(writer.get(_nodeRef(_flat[start + k])));
	}
};

// A fresh (safely retainable) array of a node's flat content span: a
// function's or block's value, a rule's prelude.
/** @type {(n: Node) => Node[]} */
const _materializeList = (n) => {
	const i = _nodeIndex(n);
	const start = _listStarts[i];
	const len = _listLens[i];
	/** @type {Node[]} */
	const out = [];
	for (let k = 0; k < len; k++) out.push(_nodeRef(_flat[start + k]));
	return out;
};

// Children-only skip: set by `A.skipChildren()` during an
// `enter` dispatch, consumed by the walk.
let _walkSkip = false;
// Set by `A.stop()` for the rest of the parse: no visitor fires after it, and a
// walk-only parse ends once the top-level node it was called in is done.
let _walkStopped = false;
// The visitor lists `A.stop()` emptied in place, put back when the parse ends.
/** @type {VisitorFn[][] | null} */
let _haltedVisitors = null;
// Thrown out of `consume` to end a stopped walk-only parse; `grammar` catches it.
const WALK_STOPPED = new Error("css syntax: walk stopped");
/** @type {CompiledVisitorMap} the map a stopped walk dispatches from */
const STOPPED_VISITORS = [];

/** @type {Node | null} */
let _currentParent = null;
// Index of the current node within its sibling list (a rule body's declarations
// and child rules are separate lists, so each indexes from 0 independently).
let _currentIndex = 0;
// How many nodes of each type enclose the current one (`A.within`): a hash inside
// a declaration is a color, elsewhere (a selector prelude) it is an id.
const _within = new Int32Array(32);

// Whether a declaration a later one overrides is dropped with no `browserslist`
// selection to say the later value is readable. Off by default: it gives up the
// fallback pair an engine that cannot read the later spelling depends on.
let _dropOverriddenDeclarations = false;
// Whether the block being composed lays out by the 2009 flexbox draft, which
// keeps that draft's properties however their replacements are spelled.
let _legacyBoxBlock = false;

// Inside a color the engine computes with rather than paints.

// Whether a color may be written as the nearest hex where that hex only
// approximates it. Off by default: webpack converts where it can prove an
// engine's own conversion lands on the same bytes, and declines the rest.
let _convertApproximateColors = false;
// Whether a rule may take the selectors of a later one printing the same block,
// past what stands between them. Off by default: it reorders the cascade.
let _mergingDistantRules = false;
// The rules a later one printing the same block may still take its selectors
// into, keyed by that block, and how far the walk had got when each was taken.
/** @type {Map<string, { rule: PriorTopLevel, seq: number }>} */
const _distantRules = new Map();
// The at-rules a later one stating the same condition may still fold its block
// into, keyed by that condition. Held apart from `_distantRules`: those join by
// printing one block under two selectors, these by stating one condition twice.
/** @type {Map<string, { at: number, entry: RuleEntry | undefined, seq: number }>} */
const _distantAtRules = new Map();
// Whether the at-rule just emitted folded into an earlier one instead, so the
// walk numbers it nowhere: what it declares moved with it.
let _distantAtRuleFolded = false;
// Where each property, and every name a shorthand of it would carry, was last
// declared by a rule that may match any element; `*` for any property at all.
/** @type {Map<string, number>} */
const _distantDeclaredAt = new Map();
// The same for rules whose subjects name element types: by name, and by `type
// name`, since only a rule matching one of a candidate's types can shadow it.
/** @type {Map<string, number>} */
const _distantTypedDeclaredAt = new Map();
/** @type {Map<string, number>} */
const _distantTypeDeclaredAt = new Map();
let _distantSeq = 0;

// Which comments this print keeps, with a pattern already compiled to the
// predicate it stands for — `true` every one, `false` none, `"some"` the ones
// that carry something (see `_isKeptComment`).
/** @type {boolean | "some" | ((comment: string, line: number, column: number) => boolean)} */
let _commentsKept = "some";

// Where a `block-contents` print holds its top-level nodes instead of emitting
// them one by one: that production is a declaration list, and the list-wide
// transforms need the whole list. Null for a stylesheet, which streams.
/** @type {(Rule | Declaration)[] | null} */
let _blockContentsNodes = null;

let _textDecorationColorStyleAllowed = true;
let _textDecorationThicknessAllowed = true;
// Whether a target may predate CSS Syntax 3 tokenizing, see `_spaceForOldTokenizer`.
let _oldTokenizer = false;
// Whether a target runs Trident, whose `flex` reads omitted parts its own way.
let _tridentTarget = false;
const _TRIDENT_BROWSERS = ["ie", "ie_mob"].map((name) =>
	SUPPORT_BROWSERS.indexOf(name)
);
let _colorFunctionAllowed = true;
let _colorMixAllowed = true;
let _hwbColorsAllowed = true;
let _labColorsAllowed = true;
let _lightDarkAllowed = true;
let _whereSelectorAllowed = true;

/** Whether every target reads `:dir()`. */
let _dirSelectorAllowed = true;

/** Whether every target reads nesting, so no rule is taken out of another. */
let _nestingAllowed = true;

/**
 * Rules taken out of the one they were written in, innermost first, each still
 * naming its parent with `&`. `owner` is the rule they are now written against,
 * which is the only one that drains them — siblings share it, and taking an
 * earlier sibling's would write it against the wrong parent.
 * `apart` marks one a prefix copy wrote, which is written as a rule of its own:
 * a selector an engine cannot read takes the whole list with it.
 * @type {{ prelude: string, rest: string, owner: Node | null, apart?: boolean }[]}
 */
const _pendingNested = [];

/**
 * Each `@custom-media` name -> the query it stands for, as printed.
 * @type {Map<string, string>}
 */
const _customMedia = new Map();
/**
 * The names read ahead of a walk, which it starts from instead of empty maps.
 * @type {{ media: Map<string, string>, selectors: Map<string, string> } | null}
 */
let _customAtRulesSeed = null;
/** Whether the walk is the one reading the names ahead. */
let _collectingCustomAtRules = false;
const _CUSTOM_AT_RULE_NAMES = new Set(["custom-media", "custom-selector"]);

/**
 * One `@custom-media` or `@custom-selector` rule a source writes: the name it
 * states and the names it is read through, keyed by the rule's own name so the
 * two never meet, and the rule as a statement.
 * @typedef {object} CustomAtRule
 * @property {string} name the name it states, or "" where it states none
 * @property {string[]} references the names it reads
 * @property {string} text the rule, ending in `;`
 */

/**
 * The `@custom-media` and `@custom-selector` rules a source writes, each as its
 * own statement, in order. One opening a block is no such rule, and is left out.
 * @param {string} input the source
 * @returns {CustomAtRule[]} the rules
 */
const _customAtRules = (input) => {
	const token = createToken();
	/** @type {CustomAtRule[]} */
	const out = [];
	let pos = 0;
	for (;;) {
		const found = readToken(input, pos, token);
		if (found === undefined) break;
		pos = found.end;
		if (found.type !== TT_AT_KEYWORD) continue;
		const kind = _atKeyword(input.slice(found.start + 1, found.end));
		if (!_CUSTOM_AT_RULE_NAMES.has(kind)) continue;
		const start = found.start;
		let end = input.length;
		let depth = 0;
		let name = "";
		/** @type {string[]} */
		const references = [];
		// A media name is read only as `(--name)`; `(--name: 1px)` is a feature.
		const media = kind === "custom-media";
		let previous = TT_EOF;
		let pending = "";
		for (;;) {
			const next = readToken(input, pos, token);
			if (next === undefined) break;
			const type = next.type;
			if (type === TT_WHITESPACE || type === TT_COMMENT) {
				pos = next.end;
				continue;
			}
			if (pending !== "" && type === TT_RIGHT_PARENTHESIS) {
				references.push(pending);
			}
			pending = "";
			if (type === TT_IDENTIFIER && input.startsWith("--", next.start)) {
				const named = `${kind} ${unescapeIdentifier(
					input.slice(next.start, next.end)
				)}`;
				if (name === "" && depth === 0) name = named;
				else if (!media) references.push(named);
				else if (previous === TT_LEFT_PARENTHESIS) pending = named;
			}
			previous = type;
			if (
				type === TT_LEFT_PARENTHESIS ||
				type === TT_LEFT_SQUARE_BRACKET ||
				type === TT_FUNCTION
			) {
				depth++;
			} else if (
				depth > 0 &&
				(type === TT_RIGHT_PARENTHESIS || type === TT_RIGHT_SQUARE_BRACKET)
			) {
				depth--;
			} else if (
				depth === 0 &&
				(type === TT_SEMICOLON ||
					type === TT_LEFT_CURLY_BRACKET ||
					type === TT_RIGHT_CURLY_BRACKET)
			) {
				end = type === TT_LEFT_CURLY_BRACKET ? -1 : next.start;
				break;
			}
			pos = next.end;
		}
		if (end !== -1) {
			out.push({ name, references, text: `${input.slice(start, end)};` });
		}
	}
	return out;
};

/**
 * The rules a name is stated by last, each after the ones its query reads, so a
 * single walk has every name it is read through down before it: the last rule
 * stating a name holds, and a cycle is left in the order it was written.
 * @param {CustomAtRule[]} rules the rules, in source order
 * @returns {string} the rules to walk, as one source
 */
const _orderCustomAtRules = (rules) => {
	/** @type {Map<string, CustomAtRule>} */
	const last = new Map();
	for (const rule of rules) if (rule.name !== "") last.set(rule.name, rule);
	/** @type {Set<CustomAtRule>} */
	const visited = new Set();
	let out = "";
	/**
	 * @param {CustomAtRule} rule a rule to write after what it reads
	 * @returns {void}
	 */
	const visit = (rule) => {
		if (visited.has(rule)) return;
		visited.add(rule);
		for (const name of rule.references) {
			const read = last.get(name);
			if (read !== undefined) visit(read);
		}
		out += rule.text;
	};
	for (const rule of rules) {
		if (rule.name === "" || last.get(rule.name) === rule) visit(rule);
	}
	return out;
};

// A `@custom-selector` name as a stored list writes it: after one `:`, not two.
const _NESTED_CUSTOM_SELECTOR_RE = /(?:^|[^:\\]):(--[^\s(),:.#[>+~*]+)/g;

/**
 * A printed prelude with each quoted string's contents taken out, so a name a
 * string spells is no name read.
 * @param {string} text the printed text
 * @returns {string} it without what its strings hold
 */
const _withoutStrings = (text) => {
	let out = "";
	for (let i = 0; i < text.length; i++) {
		const code = text.charCodeAt(i);
		if (code === CC_REVERSE_SOLIDUS) {
			out += text.slice(i, i + 2);
			i++;
		} else if (code === CC_QUOTATION_MARK || code === CC_APOSTROPHE) {
			let end = i + 1;
			while (end < text.length && text.charCodeAt(end) !== code) {
				if (text.charCodeAt(end) === CC_REVERSE_SOLIDUS) end++;
				end++;
			}
			out += `${text[i]}${text[i]}`;
			i = end;
		} else {
			out += text[i];
		}
	}
	return out;
};

/**
 * Drop each name still read through another name in `names`, which is on a
 * cycle and so stands for nothing.
 * @param {Map<string, string>} names each name to what it stands for, in place
 * @param {RegExp} reference a global pattern whose first group is a name read
 * @returns {Map<string, string>} the same map
 */
const _dropCyclicNames = (names, reference) => {
	/** @type {string[]} */
	const cyclic = [];
	for (const [name, stated] of names) {
		const written = _withoutStrings(stated);
		reference.lastIndex = 0;
		for (
			let found = reference.exec(written);
			found;
			found = reference.exec(written)
		) {
			if (names.has(found[1])) {
				cyclic.push(name);
				break;
			}
		}
	}
	reference.lastIndex = 0;
	for (const name of cyclic) names.delete(name);
	return names;
};

/**
 * Read every `@custom-media` and `@custom-selector` rule a source writes before
 * walking it, so a name is written wherever it is asked for: the last rule
 * stating it holds, and one read through another resolves in either order.
 * @param {string} input the source
 * @param {PrintContext} writer the walk's print context
 * @returns {void}
 */
const _readCustomAtRulesAhead = (input, writer) => {
	const rules = _customAtRules(input);
	if (rules.length === 0) return;
	const Context =
		/** @type {new (options: PrintContext["options"], printer: PrintContext["_printer"]) => PrintContext} */ (
			writer.constructor
		);
	_collectingCustomAtRules = true;
	try {
		grammar(
			_orderCustomAtRules(rules),
			[],
			new Context(writer.options, writer._printer),
			{}
		);
	} finally {
		_collectingCustomAtRules = false;
	}
	_customAtRulesSeed = {
		media: _dropCyclicNames(
			new Map(_customMedia),
			_NESTED_CUSTOM_MEDIA_REFERENCE_RE
		),
		selectors: _dropCyclicNames(
			new Map(_customSelectors),
			_NESTED_CUSTOM_SELECTOR_RE
		)
	};
};

/**
 * Each `@custom-selector` name taken down so far, to the selector list it
 * stands for, in the order the rules were written.
 * @type {Map<string, string>}
 */
const _customSelectors = new Map();

let _langArgumentListAllowed = true;
let _notSelectorListAllowed = true;

let _oklabColorsAllowed = true;
// Whether any color function this print may meet is one the target cannot read,
// so a block holding none is never scanned for a fallback.
let _colorFallbacksNeeded = false;
let _insetShorthandAllowed = true;

// CSS Box Alignment 3's `place-*`, newer than the `align-*` / `justify-*` pairs.
let _placeShorthandAllowed = true;

/** @type {Set<string>} */
const NAMESPACE_AT_RULE = new Set(["namespace"]);

// Set when the source names one at all, so a stylesheet without any pays a
// single scan and keeps nothing.
/**
 * Whether the source names `@namespace` under any spelling. A match inside a
 * comment or a string costs the bytes of an empty rule rather than changing
 * meaning, so the scan does not ask where the at-keyword stands.
 * @param {string} input source
 * @returns {boolean} true when the source names a `@namespace` at-rule
 */
const _namesNamespaceAtRule = (input) => _namesAtRule(input, NAMESPACE_AT_RULE);

// Whether the target reads `overflow`'s two-value form (`overflow:auto hidden`),
// which is newer than the longhands it merges.
let _overflowTwoValuesAllowed = true;
// WHY: one object rather than one binding each, so the printer module reads
// the same fields the walk writes — every slot is initialized here, so the
// shape never transitions (see AGENTS.md "Keep instance shapes stable").
const _S = {
	// The declaration whose value is being walked, for the printer's property-scoped
	// guards; only tracked while printing, and read lazily (naming it costs a slice).
	/** @type {Node | null} */
	_valueDeclaration: null,
	_inSubstitutedValue: false,
	// Which rewrites this print makes. One object rather than a flag apiece: it is
	// read straight off the options, and a fixed shape keeps each read monomorphic.
	/** @type {Required<CssTransformOptions>} */
	_transforms: _DEFAULT_TRANSFORMS,
	// Set by the walk while inside a `@supports` prelude: the declaration there is
	// the subject of a feature test, so a value rewrite would change the question.
	_inSupportsPrelude: false,
	// Set for a `block-contents` print, which is a `style=""` value: a string is
	// quoted so the list holds one quote kind, and the attribute escapes none.
	_inStyleAttribute: false,
	// Set by the walk inside a `@supports` `selector()`: the selector is what the
	// condition tests, so only its whitespace and escapes are respelled.
	_inSupportsSelector: false,
	// Set by the walk while inside a custom property's value. Its tokens are
	// squeezed, but a rewrite that restates a value in another notation is held
	// back: `getPropertyValue()` hands this text back, so a color stays as written.
	_inCustomProperty: false,
	// How many stepped-value functions the walk is inside. Everything below one
	// keeps the unit it was written with, folds included.
	_steppedFunctionDepth: 0,
	// How many math functions the walk is inside. Non-zero is where `*` and `/` are
	// operators that need no whitespace rather than value separators; above one is
	// where a folded term is an operand of an outer expression rather than a value.
	_mathFunctionDepth: 0,
	// The prefixed rule just printed, and the signature of the unprefixed twin that
	// would make it dead weight — read by whoever writes that rule out, so it can go
	// as a piece of its own. Null when the rule just printed is not one.
	/** @type {{ node: Node, signature: string, missing: Set<string> | null } | null} */
	_prefixDropCandidate: null,
	// The walk's current position (`A.node` / `A.parent` read these; module-level
	// so the accessor methods' defaults avoid self-referential `this` typing).
	/** @type {Node} */
	_currentNode: /** @type {Node} */ (/** @type {Node | number} */ (0)),
	// The declaration `_inKeywordOnlyValue` last answered for, and its answer: one
	// value's identifiers all ask it, and the read is a pointer compare after the
	// first. Cleared with the rest of the parse state, so no node outlives it.
	/** @type {Node | null} */
	_keywordOnlyFor: null,
	// Off, that verbatim rule stands; on, a custom property's tokens print like any
	// other value's and `getPropertyValue()` hands back the rewritten text.
	_rewriteCustomProperties: false,
	// A custom property's value the input ran out in, as printed and left open.
	/** @type {{ printed: string, source: string } | null} */
	_openCustomValue: null,
	// The token the input ran out in, as printed closed and as it prints open.
	_eofPrinted: "",
	_eofOpen: "",
	_input: "",
	// Set by the walk while inside a `@media` / `@container` prelude, where a `(…)`
	// is a media feature rather than a declaration or a selector list.
	_inMediaConditionPrelude: false,
	// What the target can read, from `output.environment` — resolved once per run
	// rather than per color. Unset means it can: only browserslist reports otherwise.
	_hexAlphaAllowed: true,
	// Whether the color being printed is read as numbers rather than shown — a
	// gradient stop, a `color-mix()` argument, a relative color's origin — where an
	// 8-bit hex moves the color the engine computes, not only how it is spelled.
	_inColorOperand: false,
	_rangeSpellingAllowed: true,
	// Set by the walk while inside an `@property` body, where `initial-value` is
	// typed by the sibling `syntax` descriptor rather than by any grammar webpack
	// reads — so no rewrite of it can be known to still match.
	_inPropertyRule: false,
	// Set by the walk while inside an `@function` body, where `result` carries the
	// token stream the call substitutes — as opaque as a custom property's value,
	// and empty on purpose when the function returns the guaranteed-invalid one.
	_inFunctionRule: false,
	// Set by the walk while inside an `@font-feature-values` body, where each
	// sub-rule's declaration names are `<custom-ident>` feature values — so they are
	// case-sensitive, and two spellings of one name are two distinct entries.
	_inFeatureValuesRule: false,
	// Whether vendor prefixes are written at all. The selection still answers which
	// spellings a target reads, so `vendorPrefixes: false` turns off only this.
	_prefixingOn: false,
	_isSelectorAllowed: true,
	// WHY: whether a top-level `@namespace` could still be read here. Only a rule
	// the engine keeps closes the run one may stand in (CSS Namespaces 3 §3.1), an
	// unknown at-rule being thrown away, so only a qualified rule closes it. Empty
	// or not, a rule dropped while this is open would carry a dead `@namespace`
	// after it back to the head and bring it to life.
	_namespacePrologueOpen: false,
	// Where a comment the source never closed opened, or -1. §4.3.2 runs one to
	// EOF, so at most one is open and only a span reaching the end sits inside it.
	_openCommentStart: -1,
	/** Whether every target reads `@custom-media`, so the rule naming one stays. */
	_customMediaAllowed: true,
	/** Whether every `@custom-media` name was read ahead, so none is taken down again. */
	_customAtRulesFixed: false,
	_doublePositionAllowed: true,
	_systemUiAllowed: true,
	/** @type {Map<string, [string, number]>} */
	_unitScale: ABSOLUTE_UNIT_SCALE,
	// WHY: whether a length may be rewritten into a shorter unit it is exactly equal
	// in. Off by default — the rewrite is sound, CSS Values 4 fixing the absolute
	// units against each other so `1pc` is `16px` on every medium, but it fires ~10
	// times in all of Bootstrap and costs bytes as often as it saves them once the
	// asset is compressed, and `cssnano` disables it in its default preset too. Time
	// is not gated: it is uncontested and every minifier does it.
	_convertLengthUnits: false,
	_keywordOnly: false,
	// Where an asynchronous caller's embedded sources are recorded instead of
	// rendered, set per print. Each entry carries the text to print once the answer
	// is in, so the `url()` around it is spelled from what it actually holds.
	/** @type {DeferredEmbeddedSource[] | undefined} */
	_deferEmbeddedSource: undefined,
	/** Whether a nested rule is written on its own instead of inside its parent. */
	_loweringNesting: false,
	// CSS Position 3 added `inset` long after the four longhands it merges, CSS 1.
	_displayTwoValuesAllowed: true,
	// Whether `light-dark()` is written as the custom-property pair below, and
	// whether this print has written one — the defaults rule goes out only then.
	_loweringLightDark: false,
	_lightDarkUsed: false,
	// The `@charset` piece a minified stylesheet starts with, taken back where
	// its output holds no non-ASCII character (-1 for none).
	_charsetPiece: -1,
	// Whether the output is read as UTF-8 without a `@charset` saying so.
	_assumeUtf8: false,
	// The caller's renderer for source this stylesheet embeds, set per print.
	/** @type {EmbeddedSourceRenderer | undefined} */
	_renderEmbeddedSource: undefined,
	_sourceNamesLightDark: false
};

// AST field-access seam: every field `CssParser` reads goes through an accessor
// here, `n` being an integer node id into the columns, so the storage stays
// behind it. `value` is the leaf-token string.
const A = {
	// === path position (rebound by the walk before every visitor call) ===
	/** @type {unknown} the run's state, set by `SourceProcessor` for the length of a run */
	state: undefined,
	/**
	 * @returns {Node} current node — only valid during a visitor callback
	 */
	get node() {
		return _S._currentNode;
	},
	/**
	 * @returns {Node | null} enclosing node (null = a top-level node)
	 */
	get parent() {
		return _currentParent;
	},
	/**
	 * @returns {number} index of the current node within its sibling list (0 for a top-level node) — only valid during a visitor callback
	 */
	get index() {
		return _currentIndex;
	},
	/** Stop the walk descending into the current node (enter only). */
	skipChildren() {
		_walkSkip = true;
	},
	/** Stop the walk: no visitor fires after the current one returns. */
	stop() {
		if (_walkStopped) return;
		_walkStopped = true;
		_haltedVisitors = haltVisitors(_visitors);
		_visitors = STOPPED_VISITORS;
		_commentBucket = undefined;
	},
	/**
	 * A top-level rule and a comment have none; a hash within a declaration is
	 * a color, elsewhere an id.
	 * @param {number} type a `NodeType`
	 * @returns {boolean} whether an ancestor of the current node has that type
	 */
	within(type) {
		return _currentParent !== null && _within[type] !== 0;
	},
	// === field reads — `n` defaults to the current node ===
	/**
	 * @param {Node=} n node
	 * @returns {number} node type
	 */
	type(n = _S._currentNode) {
		return _types[_nodeIndex(n)];
	},
	/**
	 * @param {Node=} n node
	 * @returns {[number, number]} start and end offsets in the input
	 */
	range(n = _S._currentNode) {
		const i = _nodeIndex(n);
		return [_starts[i], _ends[i]];
	},
	/**
	 * @param {Node=} n node
	 * @returns {number} where the node starts in the input
	 */
	start(n = _S._currentNode) {
		return _starts[_nodeIndex(n)];
	},
	/**
	 * @param {Node=} n node
	 * @returns {number} where the node ends in the input
	 */
	end(n = _S._currentNode) {
		return _ends[_nodeIndex(n)];
	},
	/**
	 * @param {Node=} n node
	 * @returns {[number, number, number, number]} start line and column, end line and column (lines from 1, columns from 0)
	 */
	loc(n = _S._currentNode) {
		const i = _nodeIndex(n);
		const lc = _locConverter;
		const s = lc.get(_starts[i]);
		const line = s.line;
		const column = s.column;
		const e = lc.get(_ends[i]);
		return [line, column, e.line, e.column];
	},
	/**
	 * A node's text exactly as the input wrote it (`name` and `value` decode).
	 * @param {Node | number=} n node, or the start offset when `end` is given
	 * @param {number=} end end offset, to read the input between two offsets
	 * @returns {string} the node's source, or the input between the offsets
	 */
	source(n, end) {
		if (end !== undefined) {
			return _originalInput.slice(/** @type {number} */ (n), end);
		}
		const i = _nodeIndex(
			n === undefined ? _S._currentNode : /** @type {Node} */ (n)
		);
		return _originalInput.slice(_starts[i], _ends[i]);
	},
	/**
	 * The token's value as CSS Syntax defines it: a number for a number,
	 * percentage or dimension, otherwise text with escapes resolved (a string
	 * without its quotes, a hash without its `#`, a `url()`'s contents, a
	 * comment's text); "" for a rule, declaration, function or block.
	 * @param {Node=} n node
	 * @returns {string | number} the decoded value
	 */
	value(n = _S._currentNode) {
		const i = _nodeIndex(n);
		const type = _types[i];
		if (_HAS_CHILD_LIST[type] === 1) {
			return type === NodeType.SimpleBlock ? _S._input[_starts[i]] : "";
		}
		if (
			type === NodeType.Number ||
			type === NodeType.Percentage ||
			type === NodeType.Dimension
		) {
			const v = _valueOf(i);
			return Number(v.slice(0, _consumeANumber(v, 0)));
		}
		if (type === NodeType.Comment) {
			const start = _starts[i] + 2;
			const end = _ends[i];
			const closed = end - start >= 2 && _S._input.endsWith("*/", end);
			return _S._input.slice(start, closed ? end - 2 : end);
		}
		return _decodedValue(n);
	},
	/**
	 * @param {Node=} n node
	 * @returns {string} an at-rule's, declaration's or function's name, escapes
	 * resolved (an at-rule's without the `@`); a dimension's unit; "" for other nodes
	 */
	name(n = _S._currentNode) {
		const i = _nodeIndex(n);
		if (_HAS_NAME[_types[i]] === 0) {
			return _types[i] === NodeType.Dimension ? _unitOf(i) : "";
		}
		return unescapeIdentifier(_rawName(n));
	},
	/**
	 * A rule's children are its prelude; its block is read with {@link field}.
	 * @param {Node=} n node
	 * @returns {number} number of children
	 */
	childCount(n = _S._currentNode) {
		const i = _nodeIndex(n);
		return _HAS_CHILD_LIST[_types[i]] === 0 ? 0 : _listLens[i];
	},
	/**
	 * @param {number} i child index
	 * @param {Node=} n node
	 * @returns {ComponentValue} the i-th child (0 = none)
	 */
	child(i, n = _S._currentNode) {
		const k = _nodeIndex(n);
		if (_HAS_CHILD_LIST[_types[k]] === 0 || i >= _listLens[k]) {
			return /** @type {ComponentValue} */ (_nodeRef(0));
		}
		return /** @type {ComponentValue} */ (_nodeRef(_flat[_listStarts[k] + i]));
	},
	/**
	 * Every child as a new array, for a reader that keeps or searches them.
	 * @param {Node=} n node
	 * @returns {ComponentValue[]} its children (empty for a leaf)
	 */
	children(n = _S._currentNode) {
		return _HAS_CHILD_LIST[_types[_nodeIndex(n)]] === 0
			? []
			: /** @type {ComponentValue[]} */ (_materializeList(n));
	},
	/**
	 * A block big enough to stream hands its children to the visitors as each
	 * one finishes rather than collecting them, so its lists read as empty here.
	 * @param {number} field a `Field`
	 * @param {Node=} n node
	 * @returns {number} number of items in that field (0 when the node has none)
	 */
	fieldCount(field, n = _S._currentNode) {
		const i = _nodeIndex(n);
		const type = _types[i];
		if (_LIST_FIELD[type] === field) return _listLens[i];
		if (
			(field !== Field.declarations && field !== Field.rules) ||
			(type !== NodeType.AtRule && type !== NodeType.QualifiedRule) ||
			_bodyIdx[i] === 0
		) {
			return 0;
		}
		return (field === Field.declarations ? _declBodies : _ruleBodies)[
			_bodyIdx[i] - 1
		].length;
	},
	/**
	 * @param {number} i item index
	 * @param {number} field a `Field`
	 * @param {Node=} n node
	 * @returns {Node} the i-th item of that field (0 = none)
	 */
	field(i, field, n = _S._currentNode) {
		const k = _nodeIndex(n);
		if (i < 0 || i >= A.fieldCount(field, n)) {
			return _nodeRef(0);
		}
		if (_LIST_FIELD[_types[k]] === field) {
			return _nodeRef(_flat[_listStarts[k] + i]);
		}
		return (field === Field.declarations ? _declBodies : _ruleBodies)[
			_bodyIdx[k] - 1
		][i];
	},
	/**
	 * @param {number} field a `Field`
	 * @param {string} name the decoded name to look for
	 * @param {Node=} n node
	 * @returns {Node} the first item of that field with this name (0 = none)
	 */
	fieldNamed(field, name, n = _S._currentNode) {
		for (let i = 0, count = A.fieldCount(field, n); i < count; i++) {
			const item = A.field(i, field, n);
			if (A.name(item) === name) return item;
		}
		return _nodeRef(0);
	},
	/**
	 * `important` is a container's bit, cleared when its id is reused; a token's
	 * type flags are read off its text.
	 * @param {number} flag a `Flag`
	 * @param {Node=} n node
	 * @returns {boolean} whether that flag is set
	 */
	flag(flag, n = _S._currentNode) {
		const i = _nodeIndex(n);
		const type = _types[i];
		if (flag === Flag.important) {
			return _HAS_CHILD_LIST[type] === 1 && (_flags[i] & flag) !== 0;
		}
		if (flag === Flag.integer) {
			return (
				(type === NodeType.Number ||
					type === NodeType.Percentage ||
					type === NodeType.Dimension) &&
				_isIntegerToken(i)
			);
		}
		return flag === Flag.id && type === NodeType.Hash && _isIdHash(i);
	},
	/**
	 * @param {number} part a `Part`
	 * @param {Node=} n node
	 * @returns {[number, number] | null} where that part is written (null without one)
	 */
	rangeOf(part, n = _S._currentNode) {
		const start = A.startOf(part, n);
		return start === -1 ? null : [start, A.endOf(part, n)];
	},
	/**
	 * @param {number} part a `Part`
	 * @param {Node=} n node
	 * @returns {number} where that part starts (-1 without one)
	 */
	startOf(part, n = _S._currentNode) {
		const i = _nodeIndex(n);
		const type = _types[i];
		if (part === Part.name) {
			if (_HAS_NAME[type] === 1) return _nameStartOf(n);
			return type === NodeType.Dimension
				? _starts[i] + _consumeANumber(_valueOf(i), 0)
				: -1;
		}
		if (part === Part.value) return type === NodeType.Url ? _aux0[i] : -1;
		return part === Part.block &&
			(type === NodeType.AtRule || type === NodeType.QualifiedRule)
			? _aux1[i]
			: -1;
	},
	/**
	 * @param {number} part a `Part`
	 * @param {Node=} n node
	 * @returns {number} where that part ends (-1 without one)
	 */
	endOf(part, n = _S._currentNode) {
		const i = _nodeIndex(n);
		const type = _types[i];
		if (part === Part.name) {
			if (_HAS_NAME[type] === 1) return _aux0[i];
			return type === NodeType.Dimension ? _ends[i] : -1;
		}
		if (part === Part.value) return type === NodeType.Url ? _aux1[i] : -1;
		return part === Part.block &&
			(type === NodeType.AtRule || type === NodeType.QualifiedRule) &&
			_aux1[i] !== -1
			? _ends[i]
			: -1;
	},
	/**
	 * CSS has no text beyond a node's name and value, read with `name` and `value`.
	 * @param {number} _part a `Part`
	 * @param {Node=} _n node
	 * @returns {null} none
	 */
	textOf(_part, _n) {
		return null;
	}
};

// Scalar position reads for the parser and printers, which must not allocate
// the arrays the path's `range`, `loc` and `rangeOf` return.
/** @type {(n: Node) => number} */
const _startOf = (n) => _starts[_nodeIndex(n)];
/** @type {(n: Node) => number} */
const _endOf = (n) => _ends[_nodeIndex(n)];
/** @type {(n: Node) => number} where a name starts, an at-rule's after the `@` */
const _nameStartOf = (n) => {
	const i = _nodeIndex(n);
	return _types[i] === NodeType.AtRule ? _starts[i] + 1 : _starts[i];
};
/** @type {(n: Node) => number} */
const _nameEndOf = (n) => _aux0[_nodeIndex(n)];

/**
 * A token's text value with escapes resolved, as `A.value` reads every token
 * but a number, percentage, dimension or comment.
 * @param {Node} n node
 * @returns {string} the decoded text
 */
const _decodedValue = (n) => {
	const i = _nodeIndex(n);
	const v = _valueOf(i);
	if (_types[i] !== NodeType.String) {
		const ranOut = _ends[i] === _S._input.length;
		return unescapeIdentifier(ranOut ? _resolveTrailingEscape(v, false) : v);
	}
	return _unescapeStringValue(_stringContent(v));
};

/**
 * A node's text as the printer reads it: as written, but for an escape the
 * input ran out of, which is the character it names (§4.3.5, §4.3.7).
 * @param {Node} n node
 * @returns {string} its text
 */
const _printedSource = (n) => {
	const i = _nodeIndex(n);
	const end = _ends[i];
	const text = _S._input.slice(_starts[i], end);
	if (end !== _S._input.length) return text;
	return _resolveTrailingEscape(text, _types[i] === NodeType.String);
};

/**
 * A token's value as written (`A.value` resolves its escapes): what the
 * minifier reads to keep the author's spelling.
 * @param {Node} n node
 * @returns {string} the raw value
 */
const _rawValue = (n) => _valueOf(_nodeIndex(n));

/**
 * A rule's, declaration's or function's name as written, but for an escape the
 * input ran out of, which is the replacement character it names (§4.3.7).
 * @param {Node} n node
 * @returns {string} the raw name
 */
const _rawName = (n) => {
	const i = _nodeIndex(n);
	const end = _aux0[i];
	const text = _S._input.slice(
		_types[i] === NodeType.AtRule ? _starts[i] + 1 : _starts[i],
		end
	);
	if (end !== _S._input.length) return text;
	return _resolveTrailingEscape(text, false);
};

/**
 * @param {Node} n at-rule
 * @returns {string} its name, escapes resolved and ASCII lowercased
 */
const _atKeywordOf = (n) => _atKeyword(_rawName(n));

// The AST node shapes are types only: nodes are integer ids into the store,
// surfaced by the `A` accessor and the `parseA*` readers, which are exported at
// runtime with the `TokenStream` and the string utilities.
/**
 * Note where one child of a block lands in it. A qualified rule is one rule
 * whatever it nests, so it is a span of its own; an at-rule is a condition its
 * body is read under, so its own spans move in, its prelude joining their keys.
 * @param {RuleSpan[]} spans the block's spans, added to
 * @param {Node} item the child
 * @param {string} text the child's printed text
 * @param {number} at where the text lands in the block's body
 * @param {CssPath} path the accessor
 * @param {BlockSpans=} inner what the child recorded, when it has a body
 * @returns {void}
 */
const _collectRuleSpans = (spans, item, text, at, path, inner) => {
	// A qualified rule is one rule, and each rule it nests is another read under
	// it — the parent first, so a cut that takes the whole rule is seen before
	// anything inside it.
	if (path.type(item) === NodeType.QualifiedRule) {
		spans.push({
			scope: _rootScope(),
			key: text,
			at,
			len: text.length,
			taken: null
		});
	} else if (
		inner !== undefined &&
		inner.prelude.charCodeAt(0) === CC_AT_SIGN &&
		DROPPABLE_WHEN_EMPTY_AT_RULES.has(_atRuleName(inner.prelude))
	) {
		// Not a rule of its own — a condition its body is read under — so it is
		// noted only to be cut once the cuts inside it have left it empty.
		spans.push({
			scope: _rootScope(),
			key: UNKEYED,
			at,
			len: text.length,
			taken: null
		});
	}
	if (inner === undefined) return;
	const base = at + inner.bodyAt;
	for (const span of inner.spans) {
		spans.push({
			scope: _enclosingRuleScope(span.scope, inner.keyPrelude),
			key: span.key,
			at: base + span.at,
			len: span.len,
			taken: null
		});
	}
};

module.exports.A = A;
module.exports.DARK_PROPERTY = DARK_PROPERTY;
module.exports.KEYFRAMES_AT_RULE_RE = KEYFRAMES_AT_RULE_RE;
module.exports.LIGHT_PROPERTY = LIGHT_PROPERTY;
module.exports.Field = Field;
module.exports.Flag = Flag;
module.exports.Part = Part;
module.exports._CC_0 = CC_0;
module.exports._CC_APOSTROPHE = CC_APOSTROPHE;
module.exports._CC_COMMA = CC_COMMA;
module.exports._CC_FULL_STOP = CC_FULL_STOP;
module.exports._CC_GREATER_THAN_SIGN = CC_GREATER_THAN_SIGN;
module.exports._CC_HYPHEN_MINUS = CC_HYPHEN_MINUS;
module.exports._CC_LEFT_PARENTHESIS = CC_LEFT_PARENTHESIS;
module.exports._CC_LEFT_SQUARE = CC_LEFT_SQUARE;
module.exports._CC_LOWER_D = CC_LOWER_D;
module.exports._CC_LOW_LINE = CC_LOW_LINE;
module.exports._CC_NUMBER_SIGN = CC_NUMBER_SIGN;
module.exports._CC_PLUS_SIGN = CC_PLUS_SIGN;
module.exports._CC_QUOTATION_MARK = CC_QUOTATION_MARK;
module.exports._CC_REVERSE_SOLIDUS = CC_REVERSE_SOLIDUS;
module.exports._CC_RIGHT_PARENTHESIS = CC_RIGHT_PARENTHESIS;
module.exports._CC_RIGHT_SQUARE = CC_RIGHT_SQUARE;
module.exports._CC_SPACE = CC_SPACE;
module.exports._CC_TILDE = CC_TILDE;
module.exports._CC_UPPER_A = CC_UPPER_A;
module.exports._CC_UPPER_Z = CC_UPPER_Z;
module.exports._LIST_KIND_KEYFRAME = LIST_KIND_KEYFRAME;
module.exports._LIST_KIND_NESTED = LIST_KIND_NESTED;
module.exports._LIST_KIND_SELECTOR = LIST_KIND_SELECTOR;
module.exports._LIST_NO = LIST_NO;
module.exports._LIST_UNKNOWN = LIST_UNKNOWN;
module.exports._blockTokenOf = _blockTokenOf;
module.exports.NodeType = NodeType;
module.exports.TokenType = TokenType;
module.exports.TokenStream = TokenStream;
module.exports.VENDOR_PREFIX = VENDOR_PREFIX;
module.exports._IMPORTANT = _IMPORTANT;
module.exports._LONE_NUMBER_RE = _LONE_NUMBER_RE;
module.exports._NO_RULE_SPANS = _NO_RULE_SPANS;
module.exports._NUMERIC_RE = _NUMERIC_RE;
module.exports._atKeywordOf = _atKeywordOf;
module.exports._printedSource = _printedSource;
module.exports._rawName = _rawName;
module.exports._setVerbatimLeafPrinter = setVerbatimLeafPrinter;
module.exports._trimCssWhiteSpace = _trimCssWhiteSpace;
module.exports._declarationsOf = _declarationsOf;
module.exports._rulesOf = _rulesOf;
module.exports._isImportant = _isImportant;
module.exports._rawValue = _rawValue;
module.exports._S = _S;
module.exports._SEP = _SEP;
module.exports._keepKeywordComments = _keepKeywordComments;
module.exports._TRIM_COMBINATORS = _TRIM_COMBINATORS;
/**
 * The body a reused block composes to, for the rule being printed.
 * @param {Node} node the rule
 * @returns {string | undefined} the body, or undefined to compose it
 */
module.exports._memoizedBlockBody = (node) =>
	_blockMemoNode === node ? _blockMemoBody : undefined;
/**
 * Done with the rule the walk looked a block up for: keep the body it composed
 * where the walk asked for one, and forget the rule — node ids are reused.
 * @param {Node} node the rule
 * @param {string | null} body its composed body, null to keep none
 * @returns {void}
 */
module.exports._memoizeBlockBody = (node, body) => {
	if (
		_blockMemoNode === node &&
		body !== null &&
		_blockMemoKey !== "" &&
		_blockMemo !== null
	) {
		_blockMemoKeyTable.set(_blockMemoKey, body);
	}
	_blockMemoNode = null;
	_blockMemoBody = undefined;
	_blockMemoKey = "";
};
module.exports._TRIM_CONDITIONS = _TRIM_CONDITIONS;
module.exports._TRIM_MATH = _TRIM_MATH;
module.exports._TRIM_NOTHING = _TRIM_NOTHING;
module.exports._TRIM_SEPARATORS = _TRIM_SEPARATORS;
module.exports._UNREADABLE_COLOR_CALL_RE = _UNREADABLE_COLOR_CALL_RE;
module.exports._ZERO_LENGTH_RE = _ZERO_LENGTH_RE;
module.exports._alphaByteText = _alphaByteText;
module.exports._anyUnordered = _anyUnordered;
module.exports._appendChildTexts = _appendChildTexts;
module.exports._atKeyword = _atKeyword;
module.exports._blockSpans = _blockSpans;
module.exports._closeAtEof = _closeAtEof;
module.exports._collapseBox = _collapseBox;
module.exports._composeBlockBody = _composeBlockBody;
module.exports._customMedia = _customMedia;
module.exports._customPropertyValue = _customPropertyValue;
module.exports._customSelectors = _customSelectors;
module.exports._dropFamilyInitials = _dropFamilyInitials;
module.exports._dropImpliedUniversalSelector = _dropImpliedUniversalSelector;
module.exports._dropZeroLengthUnit = _dropZeroLengthUnit;
module.exports._evaluateCalcSum = _evaluateCalcSum;
module.exports._foldPseudoNames = _foldPseudoNames;
module.exports._unwrapLoneIs = _unwrapLoneIs;
module.exports.containsSubstitutionFunction = containsSubstitutionFunction;
module.exports._hasSubstitutionInSpan = _hasSubstitutionInSpan;
module.exports._holdBySignature = _holdBySignature;
module.exports._noteRetractedPiece = _noteRetractedPiece;
module.exports._nearByteHalf = _nearByteHalf;
module.exports._isClosedString = _isClosedString;
module.exports.isDigit = isDigit;
module.exports.isHexDigit = isHexDigit;
module.exports._isIdentCodePoint = _isIdentCodePoint;
module.exports.isIdentCodePoint = isIdentCodePoint;
module.exports.isIdentStartCodePoint = isIdentStartCodePoint;
module.exports.isLetter = isLetter;
module.exports._isUnterminatedUrl = _isUnterminatedUrl;
module.exports._join = _join;
module.exports._lowercaseConditionParts = _lowercaseConditionParts;
module.exports._minifyColorFunction = _minifyColorFunction;
module.exports._minifyPolarColorFunction = _minifyPolarColorFunction;
module.exports._neededPrefixes = _neededPrefixes;
module.exports._normalizeExponent = _normalizeExponent;
module.exports._normalizeNumber = _normalizeNumber;
module.exports._opaqueEntry = _opaqueEntry;
module.exports._openerKey = _openerKey;
module.exports._pendingNested = _pendingNested;
module.exports._prefixRemovable = _prefixRemovable;
module.exports._prefixScope = _prefixScope;
module.exports._roundSignificant = _roundSignificant;
module.exports._edgeKeywordRead = _edgeKeywordRead;
module.exports._canonicalSelectorList = _canonicalSelectorList;
module.exports._ruleEntryOf = _ruleEntryOf;
module.exports._rulePrelude = _rulePrelude;
module.exports._setRuleEntry = _setRuleEntry;
module.exports._setHoistedRuns = _setHoistedRuns;
module.exports._shortestColor = _shortestColor;
module.exports._shortestFlexValue = _shortestFlexValue;
module.exports._shortestAuthoredFlex = _shortestAuthoredFlex;
module.exports._joinSlots = _joinSlots;
module.exports._splitPrintedComponents = _splitPrintedComponents;
module.exports._shorthandSlotsReadable = _shorthandSlotsReadable;
module.exports._splitSelectorList = _splitSelectorList;
module.exports._splitTopLevelArguments = _splitTopLevelArguments;
module.exports._splitTopLevelSpaces = _splitTopLevelSpaces;
module.exports._stream = _stream;
module.exports._takeUnusedRule = _takeUnusedRule;
module.exports._terminate = _terminate;
module.exports._tokenizeCalc = _tokenizeCalc;
module.exports._unreadableColorFunction = _unreadableColorFunction;
module.exports._valueClasses = _valueClasses;
module.exports._valueComponents = _valueComponents;
module.exports.asciiLowercaseName = asciiLowercaseName;
module.exports.buildSkipSet = buildSkipSet;
module.exports.consumeExtraNewline = consumeExtraNewline;
module.exports.asciiCaseInsensitiveMatch = asciiCaseInsensitiveMatch;
module.exports.escapeIdentifier = escapeIdentifier;
module.exports.escapeName = escapeName;
module.exports.grammar = grammar;
module.exports.isDashedIdentifier = isDashedIdentifier;
module.exports.isSimpleIdentifier = isSimpleIdentifier;
module.exports.isAsciiWhitespace = isAsciiWhitespace;
module.exports.normalizeUrl = normalizeUrl;
module.exports.parseABlocksContents = parseABlocksContents;
module.exports.parseACommaSeparatedListOfComponentValues =
	parseACommaSeparatedListOfComponentValues;
module.exports.parseAComponentValue = parseAComponentValue;
module.exports.parseADeclaration = parseADeclaration;
module.exports.parseAListOfComponentValues = parseAListOfComponentValues;
module.exports.parseARule = parseARule;
module.exports.parseAStylesheet = parseAStylesheet;
module.exports.parseAStylesheetsContents = parseAStylesheetsContents;
module.exports.pickTransforms = pickTransforms;
module.exports.rangeEquals = rangeEquals;
module.exports.rangeAsciiCaseInsensitiveMatch = rangeAsciiCaseInsensitiveMatch;
module.exports.readToken = readToken;
module.exports.skipEscape = skipEscape;
module.exports.toLowerCaseIfNeeded = toLowerCaseIfNeeded;
module.exports.unescapeIdentifier = unescapeIdentifier;