/* ==========================================================================
 * Everything below is inside @layer upline.
 *
 * This is the ownership line made structural rather than habitual. By the
 * cascade specification, a normal declaration in ANY layer loses to an
 * unlayered one, whatever the selectors say -- so a theme's plain
 * `.upline-menu__link { color: red }` beats anything in here without needing
 * to out-specify it, and a theme author never has to count our classes or
 * reach for !important to restyle a surface.
 *
 * The reverse holds for important declarations: layered !important beats
 * unlayered !important. So the few guards that genuinely must not lose --
 * the breakpoint that keeps navigation off a click, the visually-hidden
 * utility, reduced motion -- get *stronger* here, not weaker. That asymmetry
 * is the reason this is the right mechanism and not merely a tidy one: it
 * makes feel a theme's to take and mechanics ours to keep, by construction
 * rather than by everybody remembering.
 *
 * Browser floor: @layer is Safari 15.4+, Chrome/Edge 99+, Firefox 97+ (all
 * March 2022). Below that the whole block is dropped as an unknown at-rule
 * and the surface renders unstyled -- which is why the floor is stated in the
 * release notes rather than left to be discovered.
 * ========================================================================== */

@layer upline {

/* ------------------------------------------------------------------------
 * The motion gate: one place, so reduced motion cannot drift.
 *
 * These three are internal plumbing, not tokens -- they are not in
 * docs/theme-tokens.md and a theme has no business setting them. They exist
 * because the reduced-motion guard used to be a second copy of the component
 * selector list, and the two copies had already drifted: the base motion
 * declaration covered ten selectors, the guard covered three, so seven
 * surfaces kept moving for someone who had asked their operating system for
 * stillness. That is the doc's strongest promise broken by a list nobody
 * remembered to grow.
 *
 * A gate declared once at :root and consumed by every component declaration
 * cannot drift, because there is no second list to forget. Adding a surface
 * gets reduced motion for free.
 *
 * :root works here precisely because nothing re-declares these -- the same
 * inheritance rule that makes a shop's `:root { --upline-* }` override fail is
 * what makes this reach every surface.
 * ------------------------------------------------------------------------ */

/* ---------------------------------------------------------------------------
 * Two geometry facts that belong to the page rather than to a component.
 *
 * Everything else in this file declares its tokens on a list of component
 * roots, and that is right for them: a shop overrides through theme.json, and
 * the component-root declaration is what reaches every surface.
 *
 * These two are read from outside every component. A `push` shell lays the
 * page out against the drawer's width and a sticky header lays out against the
 * bar's height, and both of those selectors live on `<body>` -- outside
 * `.upline-menu`, so a value declared there is invisible to them. 1.49.5 and
 * 1.51.1 both claimed these were on :root and both put them in the component
 * list, which is why the push shell went on hard-coding 360: it could not read
 * the number it was told to read. Measured on range, not here -- they compute
 * on `.upline-menu` and empty on `:root`.
 *
 * Nothing re-declares these, so a shop's own `:root` override reaches them
 * too, and the theme.json route still works through the var chain.
 * ------------------------------------------------------------------------ */

:root {
	--upline-menu-bar-h: var(--upline-tap, var(--wp--custom--upline--tap, 44px));
	--upline-drawer-w: var(--wp--custom--upline--drawer-w, 360px);

	/*
	 * What the drawer is actually that wide, right now.
	 *
	 * `--upline-drawer-w` is the shop's setting; this is the consequence, and
	 * they differ while a third step is open. A `push` shell offsets the page
	 * by the drawer's width, so it has to read the live one -- offsetting by
	 * the token would leave the page short by the difference exactly when the
	 * drawer is widest.
	 *
	 * Read this, set that. It is an output and carries no theme.json route on
	 * purpose: a shop setting it would be overriding a measurement.
	 */
	--upline-drawer-live-w: var(--upline-drawer-w);

	/*
	 * Where pane two starts, and therefore how wide pane one is.
	 *
	 * It was `width: 30%` on the item and `left: 30%` on the pane -- two
	 * copies of one number, in two blocks, duplicated again for the drawer
	 * layout. At a 360px drawer that is 108px, and "Snowboarding" measures
	 * 109.9px: the label ran under the pane. A proportion of the drawer has
	 * nothing to do with how long a shop's category names are.
	 *
	 * One property now, read by both sides, so the column and the pane's start
	 * cannot disagree. A shop with long names widens it; a shop with short ones
	 * narrows it. Labels that still exceed it truncate rather than collide --
	 * the stated rule, below.
	 */
	--upline-pane-one-w: var(--wp--custom--upline--pane-one-w, 44%);
}

/*
 * Typography levers, two registers.
 *
 * Asked for by Ridgeline, through the route this contract invites rather than
 * around it: 20 of the 28 type rules in the menu sit on internal classes the
 * doc says move without notice, so a theme aiming at them is aiming at
 * something it was told not to rely on. Ben's rule is that headers and menus
 * carry the theme's styling and the plugin supplies function and basic layout,
 * and a register a theme cannot reach is the plugin holding identity.
 *
 * The LABEL register is the menu's structural voice -- the drill-down headings
 * and the all-link. The LEAF register is the entries themselves. Defaults are
 * exactly what the plugin drew before, so a theme that sets nothing sees no
 * change; leaf inherits, because it always did.
 *
 * The BAR register is separate from the label one rather than folded into it:
 * the bar draws at 600 and 0.06em where the heading draws at 700 and 0.04em,
 * so one register with one set of defaults would have restyled the bar for
 * every theme that set nothing. Asked and answered -- the theme chose the
 * second pair over the change.
 */
:root {
	--upline-menu-label-size: var(--wp--custom--upline--menu-label-size, 0.74rem);
	--upline-menu-label-weight: var(--wp--custom--upline--menu-label-weight, 700);
	--upline-menu-label-case: var(--wp--custom--upline--menu-label-case, uppercase);
	--upline-menu-label-tracking: var(--wp--custom--upline--menu-label-tracking, 0.04em);

	/*
	 * No fallbacks on the leaf register, on purpose, and it is the only set in
	 * this file written that way.
	 *
	 * A leaf declares none of these today: it inherits. `inherit` as a
	 * fallback would say the same thing and is a CSS-wide keyword, which this
	 * contract refuses -- and a concrete default would be worse, because
	 * `text-transform: none` and `letter-spacing: normal` do not mean "as
	 * before", they mean "stop inheriting", which would silently cut a theme
	 * off from its own ancestor rule.
	 *
	 * With no fallback and nothing set, the token is invalid at computed-value
	 * time and each property falls back to its inherited value -- exactly what
	 * a leaf does now. Set one, and it takes effect.
	 */
	/*
	 * The bar register, separate from the label register on purpose.
	 *
	 * Pane one draws at 600 and 0.06em where the drill-down heading draws at
	 * 700 and 0.04em. One register with one set of defaults cannot preserve
	 * both, and the rule the theme asked for is that a theme setting nothing
	 * sees no change -- so the bar keeps its own four, at its own values,
	 * rather than being folded in and quietly restyled.
	 */
	/*
	 * The slide's own motion, as a lever.
	 *
	 * Ridgeline reveals its panels over 800ms on a different curve and this
	 * slide runs 280ms on another, so one gesture speaks in two voices. That
	 * was reported as an observation and not a defect, which is the right
	 * reading -- neither duration is wrong, they are simply not each other's.
	 *
	 * A duration is timing, and Ben's rule puts the menu's feel with the
	 * theme. Reduced motion is unaffected either way: the block below removes
	 * the transition outright rather than shortening it, so no value a theme
	 * sets here survives `prefers-reduced-motion: reduce`.
	 */
	--upline-menu-slide-duration: var(--wp--custom--upline--menu-slide-duration, 280ms);
	--upline-menu-slide-easing: var(--wp--custom--upline--menu-slide-easing, cubic-bezier(0.22, 1, 0.36, 1));

	--upline-menu-bar-size: var(--wp--custom--upline--menu-bar-size, 0.74rem);
	--upline-menu-bar-weight: var(--wp--custom--upline--menu-bar-weight, 600);
	--upline-menu-bar-case: var(--wp--custom--upline--menu-bar-case, uppercase);
	--upline-menu-bar-tracking: var(--wp--custom--upline--menu-bar-tracking, 0.06em);

	--upline-menu-leaf-size: var(--wp--custom--upline--menu-leaf-size);
	--upline-menu-leaf-weight: var(--wp--custom--upline--menu-leaf-weight);
	--upline-menu-leaf-case: var(--wp--custom--upline--menu-leaf-case);
	--upline-menu-leaf-tracking: var(--wp--custom--upline--menu-leaf-tracking);
}

:root {
	--upline-motion-permitted: var(--wp--custom--upline--motion-duration, 0s);
	--upline-menu-transform-permitted: var(--wp--custom--upline--menu-hidden-transform, translateY(-8px));
	--upline-drawer-transform-permitted: var(--wp--custom--upline--drawer-hidden-transform, translateY(-4px));
}

@media (prefers-reduced-motion: reduce) {
	/*
	 * !important audit: stays. A shop that has asked for less movement gets it
	 * whatever theme it wears, and a theme cannot opt out. Three declarations
	 * in one place, replacing six spread across two files and two selector
	 * lists.
	 */
	:root {
		--upline-motion-permitted: 0s !important;
		--upline-menu-transform-permitted: none !important;
		--upline-drawer-transform-permitted: none !important;
	}
}
/**
 * Upline components: menu, tiles, chips.
 *
 * Implements the tile-and-mega-menu mock. One stylesheet because the three
 * share a token block, and splitting it would mean duplicating that block or
 * loading two files to draw one panel.
 *
 * The discipline is unchanged from the rail: no font, no text colour, no page
 * background. Everything is inherited or derived from currentColor, with one
 * documented exception — an overlay panel must be opaque, so it takes the
 * theme's own base/contrast pair and falls back to Canvas/CanvasText. See
 * DECISIONS.md.
 *
 * Every color-mix() is preceded by an rgba() declaration as its fallback.
 */

/* ---------------------------------------------------------------------------
 * Shared tokens
 *
 * Three layers, in order of precedence:
 *
 *   1. The theme's own Upline tokens, declared in theme.json under
 *      settings.custom.upline.* and surfacing as --wp--custom--upline--*.
 *      An "Upline approved" theme sets these once and every component follows.
 *   2. The theme's general tokens where one fits — its colour palette, its
 *      border radius.
 *   3. Our own default, derived from currentColor wherever it can be.
 *
 * A shop or child theme can still override any --upline-* directly. Nothing here
 * needs a selector fight; see docs/theme-tokens.md for the full list.
 * ------------------------------------------------------------------------ */

/*
 * Every storefront surface, not only the three that happened to exist when
 * this block was written.
 *
 * The tokens were scoped to .upline-menu/.upline-tiles/.upline-chips, so
 * surfaces added later -- availability, brand pages, search, the New badge --
 * sat outside the scope and silently ignored every theme.json token. Their
 * inline var() fallbacks meant nothing looked broken, which is why it went
 * unnoticed: a theme setting `accent` got an accent everywhere it looked and
 * not on the surfaces it did not think to check.
 *
 * A new surface must join this list. That is a real maintenance cost and the
 * reason it is stated here rather than left to be rediscovered.
 */
.upline-menu,
.upline-tiles,
.upline-chips,
.upline-availability,
.upline-brands,
.upline-brand,
.upline-brand-intro,
.upline-search,
.upline-search-pages,
.upline-new-badge,
.upline-picker,
.upline-search--collapsible {
	--upline-gap: var(--wp--custom--upline--gap, 1rem);

	/* Half-gap, for the tighter rows inside a panel. */
	--upline-gap-sm: var(--wp--custom--upline--gap-sm, .5rem);

	/*
	 * Must carry a unit at every level. The chip radius is
	 * calc(var(--upline-radius) + 999px), and calc() rejects a unitless zero
	 * added to a length — the whole declaration is then invalid and chips come
	 * out square.
	 */
	--upline-radius: var(--wp--custom--upline--radius, var(--wp--custom--border--radius, 0px));

	--upline-tile-ratio-brand: var(--wp--custom--upline--tile-ratio-brand, 1 / 1);
	--upline-tile-ratio-category: var(--wp--custom--upline--tile-ratio-category, 4 / 3);
	--upline-tile-ratio-featured: var(--wp--custom--upline--tile-ratio-featured, 3 / 2);

	--upline-rule: rgba(128, 128, 128, 0.26);
	--upline-rule: var(--wp--custom--upline--rule, color-mix(in srgb, currentColor 18%, transparent));

	--upline-muted: rgba(128, 128, 128, 0.85);
	--upline-muted: var(--wp--custom--upline--muted, color-mix(in srgb, currentColor 58%, transparent));

	--upline-accent: var(--wp--custom--upline--accent, var(--wp--preset--color--primary, currentColor));

	/* The overlay exception, and the only place a background is asserted. */
	--upline-panel-bg: var(--wp--custom--upline--panel-bg, var(--wp--preset--color--base, Canvas));
	--upline-panel-fg: var(--wp--custom--upline--panel-fg, var(--wp--preset--color--contrast, CanvasText));
	--upline-panel-shadow: var(--wp--custom--upline--panel-shadow, 0 14px 34px -18px rgba(0, 0, 0, 0.35));

	--upline-tile-bg: rgba(128, 128, 128, 0.08);
	--upline-tile-bg: var(--wp--custom--upline--tile-bg, color-mix(in srgb, currentColor 6%, transparent));

	/* Never smaller than a fingertip. */
	--upline-tap: var(--wp--custom--upline--tap, 44px);


	/*
	 * The badge sits on top of a product image, so it is the second place a
	 * background is asserted. Canvas follows the reader's own light or dark
	 * preference where the theme has published no base colour.
	 */
	--upline-badge-bg: var(--wp--custom--upline--badge-bg, var(--wp--preset--color--base, Canvas));
	--upline-badge-fg: var(--wp--custom--upline--badge-fg, var(--wp--preset--color--contrast, CanvasText));

	/*
	 * The selected pill's fill. Defaults to what 1.18.2 shipped, which is no
	 * fill at all -- selection was an accent outline and nothing more -- so a
	 * theme that sets neither is unchanged.
	 *
	 * Deliberately NOT panelBg/panelFg. Coupling pill selection to the mega
	 * panel's surface would leave a theme unable to make them differ, and they
	 * are different things: one is a sheet of paper, the other is a pressed
	 * button.
	 */
	/*
	 * Real values, not empty fallbacks. An empty `var(--token,)` does not drop
	 * the declaration that uses it -- invalid at computed-value time means the
	 * property takes its INITIAL value, not that the cascade moves on to the
	 * next candidate. For `background` that initial value is `transparent`,
	 * which is exactly the paint-over-the-theme behaviour it was meant to
	 * avoid. Measured, after being built on the opposite belief.
	 */
	--upline-selected-bg: var(--wp--custom--upline--selected-bg, transparent);
	/*
	 * `currentColor`, not `inherit`. A custom property whose declared value is
	 * the CSS-wide keyword `inherit` does not store the token "inherit" -- it
	 * makes the property inherit, nothing up the tree declares it, and the
	 * property computes to the guaranteed-invalid value. It then reads as unset
	 * and `color: var(--upline-selected-fg)` is discarded at computed-value
	 * time. It happened to look right, because the discarded declaration left
	 * the inherited colour in place, which is what `inherit` would have done.
	 *
	 * Third time this trap has been paid for here and the first time in a
	 * fallback: the value in a var() fallback is a token, and CSS-wide keywords
	 * are not tokens.
	 */
	--upline-selected-fg: var(--wp--custom--upline--selected-fg, currentColor);

	/*
	 * Motion.
	 *
	 * The duration default is 0s, and that is deliberate rather than lazy:
	 * before these tokens existed the menu toggled `display`, which cannot
	 * animate, so every shop's menu appeared instantly. A non-zero default
	 * would change the feel of every site that has not asked for it. Zero
	 * means a theme opts in and nobody else notices.
	 */
	--upline-motion-duration: var(--upline-motion-permitted, 0s);
	--upline-motion-easing: var(--wp--custom--upline--motion-easing, ease-out);

	/*
	 * Where a closed surface sits before it arrives. Panels drop, drawers
	 * slide -- the defaults are small because at 0s they are invisible and at
	 * a theme's duration they should read as arrival, not as travel.
	 */
	--upline-menu-hidden-transform: var(--upline-menu-transform-permitted, translateY(-8px));
	--upline-drawer-hidden-transform: var(--upline-drawer-transform-permitted, translateY(-4px));

	color: inherit;
	font: inherit;
}

.upline-menu *,
.upline-tiles *,
.upline-chips *,
.upline-menu *::before,
.upline-tiles *::before,
.upline-chips *::before {
	box-sizing: border-box;
}

/* Counts are one class everywhere, so the on/off setting is one toggle. */
.upline-menu__count {
	margin-left: 0.4em;
	font-size: 0.85em;
	font-weight: 400;
	font-variant-numeric: tabular-nums;
	color: var(--upline-muted);
}

/* ---------------------------------------------------------------------------
 * Menu bar
 * ------------------------------------------------------------------------ */

/*
 * The glyph is markup every layout carries and only one layout shows.
 *
 * It was emitted unconditionally in 1.49.3 -- correct, because a rail is a
 * width and the markup should not have to know which header a shop chose --
 * and styled only under the sidebar shell, which left it *displayed and
 * unsized* everywhere else. On bike co.'s live mega header that rendered a
 * 150px term thumbnail inside every top-level item.
 *
 * Unconditional markup needs an unconditional default. The sidebar turns it
 * on; nothing else does.
 */
.upline-menu__glyph {
	display: none;
}

.upline-menu {
	position: relative;

}

.upline-menu ul {
	margin: 0;
	padding: 0;
	list-style: none;
}

/*
 * No rules above or below. Ben, on seeing the same hairline on two different
 * skins: get rid of it.
 *
 * It was one declaration pair on this one element -- border-top and
 * border-bottom, both `var(--upline-rule)` -- which is why it crossed skins:
 * the shared layer was this file, not a coincidence between two themes.
 *
 * Removed rather than defaulted off, and the distinction matters. --upline-rule
 * has two dozen other consumers -- the mega panel's edge, tile frames, the
 * rail's separators -- and every one of them still wants a hairline, so
 * defaulting the token to nothing would strip lines from surfaces nobody asked
 * about. This is a decision about the menu bar, not about the token.
 *
 * That does leave it without a lever: .upline-menu__bar is internal, so a skin
 * cannot put the rules back with a supported selector. That is the standing
 * answer for internals -- ask for the lever rather than reach past the
 * contract -- and it is written into the tokens doc beside this, where a theme
 * author will meet the question.
 */
.upline-menu__bar {
	display: flex;
	flex-wrap: wrap;
}

/*
 * Themes underline links in content areas — Storefront's rule is
 * `.entry-content a:not(.button)`, which outranks a single class and would
 * underline every menu item and tile. Navigation is a different convention from
 * body copy. Specific enough to win on its own; this stylesheet spends
 * !important only where a rule genuinely cannot be allowed to lose.
 */
/*
 * Storefront's rule is `.entry-content a:not(.button)` — two classes and an
 * element. Matching that specificity only ties, and a tie is decided by load
 * order, which a plugin does not control. These go one class further so they
 * win outright, which is still cheaper than !important.
 */
.upline-menu .upline-menu__item > .upline-menu__link,
.upline-menu .upline-mega .upline-mega__list a,
.upline-menu .upline-mega .upline-mega__heading a,
.upline-menu .upline-mega .upline-mega__all,
.upline-tiles .upline-tiles__inner .upline-tile,
.upline-tiles .upline-tile.upline-tile,
.upline-chips .upline-chip.upline-chip {
	text-decoration: none;
	color: inherit;
}

.upline-menu__bar > .upline-menu__item > .upline-menu__link {
	display: flex;
	align-items: center;
	/* The published height is what the bar renders from, not a restatement. */
	min-height: var(--upline-menu-bar-h);
	padding: 0 1.1em;
	font-weight: 600;
	letter-spacing: 0.02em;
}

.upline-menu__bar > .upline-menu__item > .upline-menu__link[aria-expanded="true"] {
	box-shadow: inset 0 -3px 0 var(--upline-accent);
}

.upline-menu :focus-visible {
	outline: 2px solid currentColor;
	outline-offset: -2px;
}

.upline-menu__item.is-empty > .upline-menu__link {
	opacity: 0.5;
}

/* ---------------------------------------------------------------------------
 * Mobile toggle
 *
 * Outside core/navigation this menu inherits nothing from the theme's header,
 * so it brings its own. The switch itself is never visible and never submitted
 * — it has no name.
 * ------------------------------------------------------------------------ */

.upline-menu__switch {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
}

/* --------------------------------------------------------------------------
   The three labels carry no margin -- P9
   --------------------------------------------------------------------------

   WooCommerce's `woocommerce-blocktheme.css` ships
   `.woocommerce-page label{margin-bottom:.7em}`, which lands on every one of
   these: the toggle, the scrim and the close control are all `<label>`s,
   because each has to work with no script. The class is on the body for shop,
   product, cart and checkout and absent from the home page, so the menu's slot
   changed height by route -- measured on range's bottom-bar top bar at 55.2 on
   /shop/ against 52.0 on /, and on bikeco's header at 129.4 against 118.9 --
   and the toggle sat 11px off-centre in its own slot on every shop page.

   !important audit: stays, and it is the layer asymmetry again rather than
   anyone doing anything wrong. That stylesheet contains no `@layer` at all, so
   its declaration is unlayered and beats a layered one at any specificity.
   Measured rather than reasoned: with the rule present the label computed
   12.6px; a layered `margin: 0` left it at 12.6px; the same declaration with
   `!important` gave 0px. The plain form was the fix proposed and it would have
   shipped and changed nothing.

   Scoped to the three labels this plugin owns. A blanket `label { margin: 0 }`
   would be reaching into a shop's forms to fix our own slot.
   -------------------------------------------------------------------------- */
label.upline-menu__toggle,
label.upline-menu__scrim,
label.upline-menu__close {
	margin: 0 !important;
}

/*
 * No box. Ben's rule: styling comes from the theme, and a bare default border
 * is a look decision a skin has to undo rather than one it gets to make -- a
 * new skin that never mentions the toggle shipped a box it never asked for.
 *
 * What stays is what the control needs to be a control: the tap size, the
 * icon's box, the pointer. A skin that wants a border adds one, and nothing
 * here has to be reset first.
 */
.upline-menu__toggle {
	display: none;
	align-items: center;
	gap: 0.6em;
	min-height: var(--upline-tap);
	padding: 0.6em 1em;
	font-weight: 600;
	cursor: pointer;
}

.upline-menu__switch:focus-visible + .upline-menu__toggle {
	outline: 2px solid currentColor;
	outline-offset: 2px;
}

/* Three lines, drawn rather than shipped as an icon font or an SVG file. */
.upline-menu__toggle-icon,
.upline-menu__toggle-icon::before,
.upline-menu__toggle-icon::after {
	display: block;
	width: 1.1em;
	height: 2px;
	background: currentColor;
}

.upline-menu__toggle-icon {
	position: relative;
}

.upline-menu__toggle-icon::before,
.upline-menu__toggle-icon::after {
	content: "";
	position: absolute;
	left: 0;
}

.upline-menu__toggle-icon::before { top: -6px; }
.upline-menu__toggle-icon::after  { top: 6px; }

/* ---------------------------------------------------------------------------
 * Mega panel
 *
 * Spans the whole bar, not its own item: a panel anchored to an item halfway
 * along throws itself off the side of the screen.
 * ------------------------------------------------------------------------ */

/*
 * The panel is always laid out and never `display: none`.
 *
 * `display` cannot be transitioned, so toggling it left a theme no way to make
 * the menu move -- the one thing a theme most wants to own. Hidden is now four
 * animatable properties instead.
 *
 * `visibility: hidden` is what carries the accessibility, and it is not a
 * lesser substitute for `display: none` here: a visibility-hidden subtree is
 * out of the tab order and out of the accessibility tree exactly as a
 * display-none one is. That matters because it holds with JavaScript off,
 * which no scripted `inert` can promise. The script adds `inert` as well, for
 * the frames during a slow fade-out when visibility is still `visible`.
 *
 * The panel is absolutely positioned, so taking it out of `display` costs no
 * layout -- see the drawer below, where it does and has to be handled.
 */
.upline-mega {
	display: grid;

	opacity: 0;
	visibility: hidden;
	pointer-events: none;
	transform: var(--upline-menu-hidden-transform, translateY(-8px));

	/*
	 * Visibility flips after the fade rather than with it, so the panel is
	 * still painted while it leaves. On the way in it flips immediately, or
	 * there would be nothing to fade.
	 */
	transition:
		opacity var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
		transform var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
		visibility 0s linear var(--upline-motion-duration, 0s);

	position: absolute;
	left: 0;
	right: 0;
	top: 100%;
	z-index: 20;
	gap: var(--upline-gap);
	padding: calc(var(--upline-gap) * 1.25) calc(var(--upline-gap) * 1.5);
	border: 1px solid var(--upline-rule);
	border-top: 0;
	background: var(--upline-panel-bg);
	color: var(--upline-panel-fg);

	/*
	 * Bottom corners only. The panel hangs off the bar with `border-top: 0`,
	 * so rounding all four would round two corners that are not there.
	 *
	 * The doc's table has always said `radius` does "corners on tiles and
	 * panels" and the full-width panel was the one surface that never read it
	 * -- the variants did, which is why the token test stayed green while a
	 * theme setting a radius watched this panel stay square.
	 */
	border-radius: 0 0 var(--upline-radius) var(--upline-radius);

	/*
	 * A shadow a theme can decline. Ridgeline is outlined rather than
	 * shadowed by its own stated rule, and could not obey it while this was a
	 * literal. `none` is a legal value; the default is the literal it replaced,
	 * so a shop that sets nothing is unchanged.
	 */
	box-shadow: var(--upline-panel-shadow);
}

/*
 * Hover and keyboard focus open the panel with no script at all; the script
 * adds touch, Escape and the aria state on top.
 */
/*
 * Structural ancestors go inside :where() so they add no specificity: what a
 * theme has to out-rank to restyle an open panel is one class, not three.
 */
:where(.upline-menu__item--parent, .upline-menu__item--plain):is(:hover, :focus-within, .is-open) > .upline-mega,
.upline-mega[data-upline-open] {
	opacity: 1;
	visibility: visible;
	pointer-events: auto;
	transform: none;
	transition:
		opacity var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
		transform var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
		visibility 0s linear 0s;
}

/*
 * Column count follows the number of subcategory groups. Overflow wraps within
 * a column before a fourth is added, so the panel never turns into a wall.
 */
.upline-mega--cols-1 { grid-template-columns: 1fr; }
.upline-mega--cols-2 { grid-template-columns: repeat(2, 1fr); }
.upline-mega--cols-3 { grid-template-columns: repeat(3, 1fr); }

.upline-mega--brands.upline-mega--cols-1 { grid-template-columns: 1fr 1.05fr; }
.upline-mega--brands.upline-mega--cols-2 { grid-template-columns: 1fr 1fr 1.05fr; }
.upline-mega--brands.upline-mega--cols-3 { grid-template-columns: 1fr 1fr 1fr 1.05fr; }

.upline-mega--featured.upline-mega--cols-3 { grid-template-columns: 1fr 1fr 1fr 1.6fr; }
.upline-mega--brands.upline-mega--featured.upline-mega--cols-3 { grid-template-columns: 1fr 1fr 1fr 1.05fr 1.6fr; }
.upline-mega--brands.upline-mega--featured.upline-mega--cols-2 { grid-template-columns: 1fr 1fr 1.05fr 1.6fr; }
.upline-mega--brands.upline-mega--featured.upline-mega--cols-1 { grid-template-columns: 1fr 1.05fr 1.6fr; }

.upline-mega__col {
	min-width: 0;
}

.upline-mega__heading {
	margin: 0 0 0.55em;
	padding-bottom: 0.45em;
	border-bottom: 1px solid var(--upline-rule);
	font-size: 0.8em;
}

.upline-mega__col > .upline-mega__heading:not(:first-child) {
	margin-top: 1.1em;
}

.upline-mega__list li a {
	display: block;
	padding: 0.32em 0;
	line-height: 1.35;
	/* The leaf register. Defaults inherit, which is what this drew before. */
	font-size: var(--upline-menu-leaf-size);
	font-weight: var(--upline-menu-leaf-weight);
	text-transform: var(--upline-menu-leaf-case);
	letter-spacing: var(--upline-menu-leaf-tracking);
}

.upline-mega__list li a:hover,
.upline-mega__list li a:focus-visible {
	color: var(--upline-accent);
	outline: none;
}

.upline-mega__list .upline-menu__count {
	font-size: 0.8em;
}

/* A third level reads as an indented sub-list, not another dropdown. */
.upline-mega__list--types {
	margin: 0.2em 0 0.6em;
	padding-left: 0.9em;
	border-left: 1px solid var(--upline-rule);
}

/*
 * Qualified with the base class the renderer always emits alongside it
 * (class-menu-render.php:515), so the modifier out-ranks .upline-mega__list
 * by specificity rather than by being 23 lines further down.
 */
.upline-mega__list.upline-mega__list--types li a {
	padding: 0.2em 0;
	font-size: 0.92em;
	color: var(--upline-muted);
}

/* Spans every column, so it reads as the panel's footer at any column count. */
.upline-mega__footer {
	grid-column: 1 / -1;
	margin-top: 0.4em;
	padding-top: 0.7em;
	border-top: 1px solid var(--upline-rule);
}

.upline-mega__all {
	display: inline-block;
	margin-top: 0.5em;
	font-size: 0.85em;
	font-weight: 600;
	color: var(--upline-accent);
}

.upline-mega__featured {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: calc(var(--upline-gap) * 0.75);
	align-content: start;
}

/* One featured tile spans; two or three share a row. Capped at three. */
.upline-mega__featured .upline-tile--featured:first-child {
	grid-column: 1 / -1;
}

.upline-mega .upline-tile--featured .upline-tile__title {
	font-size: 1em;
}

.upline-mega .upline-tile__cta {
	display: none;
}

/* ---------------------------------------------------------------------------
 * A panel that did not earn a mega
 *
 * Same tokens, same hover and focus behaviour, none of the mega's composition:
 * no columns, no brand column, no footer. It keeps a background because it
 * still overlays page content and would be unreadable without one.
 * ------------------------------------------------------------------------ */

.upline-menu__item--plain {
	position: relative;
}

.upline-mega--plain {
	/* Layout, not state: the variant is laid out this way open or shut, and
	   the shared open rule alone decides which it is. */
	display: block;
	left: 0;
	right: auto;
	width: auto;
	min-width: 14rem;
	padding: 0.5em 0;
	border-top: 1px solid var(--upline-rule);
	border-radius: var(--upline-radius);
}

.upline-mega--plain .upline-mega__list li a {
	padding: 0.45em 1em;
}

.upline-mega--plain .upline-mega__list li a:hover {
	background: var(--upline-tile-bg);
}

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

.upline-menu--dropdown .upline-menu__item--parent {
	position: relative;
}

.upline-menu--dropdown .upline-mega {
	display: block;
	left: auto;
	right: auto;
	min-width: 220px;
	padding: 0.5em 0;
	border-top: 1px solid var(--upline-rule);
	border-radius: var(--upline-radius);
}



.upline-menu--dropdown .upline-mega__list li a {
	padding: 0.45em 1em;
}

.upline-menu--dropdown .upline-mega__list li a:hover {
	background: var(--upline-tile-bg);
}

/* ---------------------------------------------------------------------------
 * List layout — everything visible, nothing overlaid, no script involved.
 * Also what every layout degrades to if this stylesheet fails to load.
 * ------------------------------------------------------------------------ */

.upline-menu--list .upline-menu__bar {
	display: block;
	border: 0;
}

.upline-menu--list .upline-mega {
	display: block;
	position: static;
	padding: 0 0 0.5em 1em;
	border: 0;
	background: none;
	color: inherit;
	box-shadow: none;
}

.upline-menu--list .upline-mega__featured {
	display: none;
}

/* ---------------------------------------------------------------------------
 * Tiles — one component, three sizes
 * ------------------------------------------------------------------------ */

.upline-tiles {
	display: grid;
	gap: var(--upline-gap);
}

.upline-tiles--brand    { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); }
.upline-tiles--category { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }
.upline-tiles--featured { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }

.upline-tile {
	position: relative;
	display: block;
	min-width: 0;
}

.upline-tile:focus-visible {
	outline: 2px solid currentColor;
	outline-offset: 3px;
}

/*
 * The ratio lives on the media box rather than the image, so the grid never
 * shifts while images load.
 */
.upline-tile__media {
	display: block;
	position: relative;
	width: 100%;
	overflow: hidden;
	border: 1px solid var(--upline-rule);
	border-radius: var(--upline-radius);
	background-color: var(--upline-tile-bg);
}

.upline-tile__media img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	/*
	 * A brand logo is not a photograph. `cover` crops to fill, which is right
	 * for a category or featured tile -- those are pictures, and losing an edge
	 * costs nothing. A logo is usually a wide wordmark, and cropping it cuts
	 * the ends off the brand's name. `contain` fits the whole mark inside the
	 * square and lets the tile's own tint show around it.
	 */
}

.upline-tile--brand .upline-tile__media img {
	object-fit: contain;
	padding: 12%;

	/*
	 * Multiply, so a logo's white background reads as the tile's ground.
	 *
	 * Brand logos arrive as whatever the brand publishes, and a good share of
	 * them are white-background JPEGs and PNGs rather than transparent files.
	 * On bike co. that painted a white rectangle inside every grey tile, which
	 * is the shape of the artwork rather than the shape of the mark. Multiply
	 * takes white to the backdrop and leaves the ink, so a white-ground file
	 * and a transparent one land the same way on any light tile.
	 *
	 * The trade, stated rather than solved: multiply eats a WHITE logo on a
	 * DARK tile ground. So a skin that ships a dark tileBg gets white marks
	 * disappearing, and this rule is the thing to reach for first if that is
	 * ever reported. The durable fix is transparent source files, which Base
	 * can normalise as it collects brand assets; this is the bridge until then
	 * and should be reconsidered the moment a dark-tile skin exists.
	 */
	mix-blend-mode: multiply;
}

.upline-tile--brand .upline-tile__media {
	aspect-ratio: var(--upline-tile-ratio-brand);
	display: grid;
	place-items: center;
}

.upline-tile--category .upline-tile__media { aspect-ratio: var(--upline-tile-ratio-category); }
.upline-tile--featured .upline-tile__media { aspect-ratio: var(--upline-tile-ratio-featured); }

/* Brand tiles show a logo or the name, never both. */
.upline-tile__logo {
	padding: 0.5em;
	font-size: 0.95em;
	font-weight: 800;
	letter-spacing: 0.04em;
	text-align: center;
	opacity: 0.85;
}

.upline-tile__body {
	display: block;
	padding: 0.55em 0 0;
}

.upline-tile__title {
	display: block;
	margin: 0;
	font-size: 1em;
	line-height: 1.25;
}

.upline-tile--featured .upline-tile__title {
	font-size: 1.15em;
}

.upline-tile__meta {
	display: block;
	margin-top: 0.15em;
	font-size: 0.85em;
	color: var(--upline-muted);
}

.upline-tile__cta {
	display: inline-block;
	margin-top: 0.45em;
	font-size: 0.85em;
	font-weight: 600;
	color: var(--upline-accent);
}

.upline-tile:hover .upline-tile__media {
	border-color: rgba(128, 128, 128, 0.6);
	border-color: color-mix(in srgb, currentColor 45%, transparent);
}

.upline-tile:hover .upline-tile__title {
	color: var(--upline-accent);
}

/* ---------------------------------------------------------------------------
 * Chips — quick filters. Anchors, not buttons: a chip must work with JS off.
 * ------------------------------------------------------------------------ */

.upline-chips {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5em;
	margin: 0;
	padding: 0;
	list-style: none;
}

.upline-chip {
	display: inline-flex;
	align-items: center;
	gap: 0.45em;
	min-height: 36px;
	padding: 0 0.9em;
	/* Square themes get pills; rounded themes get rounder pills. */
	border-radius: calc(var(--upline-radius) + 999px);
	border: 1px solid rgba(128, 128, 128, 0.45);
	border: 1px solid color-mix(in srgb, currentColor 35%, transparent);
	font-size: 0.9em;
	line-height: 1;
}

.upline-chip:hover {
	border-color: currentColor;
}

.upline-chip:focus-visible {
	outline: 2px solid currentColor;
	outline-offset: 2px;
}

.upline-chip[aria-pressed="true"] {
	background: var(--upline-panel-fg);
	color: var(--upline-panel-bg);
	border-color: var(--upline-panel-fg);
}

.upline-chip .upline-menu__count {
	margin: 0;
	color: inherit;
	opacity: 0.7;
}

.upline-chip__x {
	font-size: 1.1em;
	line-height: 1;
	opacity: 0.8;
}

/* ---------------------------------------------------------------------------
 * Stacked breakpoint
 *
 * No hover to rely on and no room to overlay, so every layout becomes the list
 * layout: no panel background, no featured tiles.
 * ------------------------------------------------------------------------ */

@media (max-width: 760px) {
	.upline-menu__toggle {
		display: inline-flex;
	}

	/*
	 * Closed until asked for. The checkbox is the state; no script involved.
	 *
	 * Unlike the panel, the drawer is in normal flow, so dropping `display:
	 * none` for `visibility: hidden` alone would reserve its full height and
	 * leave a tall blank gap under the header of every mobile page. It has to
	 * collapse as well as fade, so `grid-template-rows: 0fr -> 1fr` does the
	 * height: it animates, it needs no magic pixel number, and it collapses to
	 * genuinely nothing.
	 *
	 * The inner element is the grid item that gets clipped; `min-height: 0` is
	 * required or the row refuses to go below its content height.
	 */
	/*
	 * Open, the hamburger is a close control -- the mockup draws an X where the
	 * three lines were. The same element, because it is the same control: a
	 * second button would mean two things claiming to close one drawer.
	 */
	.upline-menu__switch:checked ~ .upline-menu__toggle .upline-menu__toggle-icon {
		background: transparent;
	}

	/* The outer lines sit at top: -6px and +6px, so they have to come back to
	   centre before rotating or the X is drawn as a wide V. */
	.upline-menu__switch:checked ~ .upline-menu__toggle .upline-menu__toggle-icon::before {
		top: 0;
		transform: rotate(45deg);
	}

	.upline-menu__switch:checked ~ .upline-menu__toggle .upline-menu__toggle-icon::after {
		top: 0;
		transform: rotate(-45deg);
	}

	.upline-menu__drawer {
		display: grid;
		grid-template-rows: 0fr;
		/*
		 * The block axis is not the only axis.
		 *
		 * `grid-template-rows: 0fr` collapses the height and says nothing
		 * about the width, so a shut drawer still reserved its full inline
		 * size in flow: measured 330px of a 390px row on the fixture, and
		 * 133.4px of a 358px brand row on bikeco -- a width set by the longest
		 * category name, so it varies per shop. The case that makes it matter:
		 * one long category name pushes a shop's own logo onto a second row
		 * while the menu is shut.
		 *
		 * Same shape as the desk defect 1.54.3 fixed by making the panel fixed
		 * in both states, and it was still here below the breakpoint. Zeroed
		 * rather than taken out of flow: `position` cannot be transitioned, so
		 * flipping it would snap the layout at the start of the closing
		 * animation instead of after it, which is the mistake the visibility
		 * timing on the desk panel exists to avoid.
		 */
		inline-size: 0;

		opacity: 0;
		visibility: hidden;
		pointer-events: none;
		transform: var(--upline-drawer-hidden-transform, translateY(-4px));

		transition:
			grid-template-rows var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
			opacity var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
			transform var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
			visibility 0s linear var(--upline-motion-duration, 0s);
	}

	/* The single grid item. min-height:0 or the row refuses to go below its
	   content height and the collapse silently does nothing. */
	.upline-menu__drawer > .upline-menu__bar {
		min-height: 0;
		overflow: hidden;

		/*
		 * Belt kept, assumption pinned: the base rule stopped drawing borders
		 * when Ben asked for the lines to go, so this zeroes nothing today. It
		 * stays because it also catches a border arriving from anywhere else,
		 * and the assertion beside it is what would notice if the base rule
		 * ever draws one again.
		 */
		border: 0;
	}

	.upline-menu__switch:checked ~ .upline-menu__drawer,
	.upline-menu__drawer[data-upline-open] {
		grid-template-rows: 1fr;
		inline-size: auto;
		opacity: 1;
		visibility: visible;
		pointer-events: auto;
		transform: none;
		transition:
			grid-template-rows var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
			opacity var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
			transform var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
			visibility 0s linear 0s;
	}

	/* Once open the drawer must not clip its own submenus. */
	.upline-menu__switch:checked ~ .upline-menu__drawer > .upline-menu__bar {
		overflow: visible;
	}

	.upline-menu__bar > .upline-menu__item > .upline-menu__link {
		border-bottom: 1px solid var(--upline-rule);
	}

	/*
	 * The drawer had no mobile layout at all: the bar stayed flex-direction
	 * row, so top-level items sat side by side and wrapped, and each item grew
	 * to 600px tall because its panel rendered static in flow while still
	 * computing the desktop three-column grid inside a 144px box. Measured at
	 * 390px: a 1178px-tall bar nobody could use.
	 *
	 * This is the layout with no JavaScript -- a plain vertical stack, which is
	 * usable on its own. The three-pane drill-down is layered on top of it by
	 * the script; see .upline-menu.upline-menu--panes below.
	 */
	.upline-menu__bar {
		flex-direction: column;
	}

	.upline-menu__bar > .upline-menu__item {
		width: 100%;
	}

	.upline-mega,
	.upline-menu--dropdown .upline-mega {
		display: block;
		position: static;
		padding: 0 0 0.5em 1em;
		border: 0;
		background: transparent;
		color: inherit;
		box-shadow: none;

		/* The desktop grid has no business in a phone-width box. */
		grid-template-columns: none;
	}

	.upline-mega__featured {
		display: none;
	}

	.upline-mega__footer {
		margin-top: 0.2em;
		padding-top: 0.4em;
	}

	.upline-mega__col {
		margin-bottom: 0.75em;
	}

	/* ---- the three-pane drill-down ------------------------------------
	 *
	 * Taxonomy, then subcategories, then leaves, left to right, with the
	 * active entry in each pane underlined in accent. Added by the script:
	 * without it the stack above is what a shopper gets, which is the whole
	 * reason the stack is built first rather than as an afterthought.
	 * ------------------------------------------------------------------ */

	/*
	 * The drawer paints an opaque surface.
	 *
	 * The mobile block resets the panel's background to `transparent` -- right
	 * for a stacked list sitting in the page, wrong the moment the drawer is a
	 * surface of its own: page content showed straight through it and the menu
	 * could not be read over a product grid.
	 *
	 * panelBg is the token for exactly this, and its default chain ends at
	 * `Canvas`, which is opaque. That matters more than the colour -- a drawer
	 * that goes transparent when a theme supplies nothing fails toward
	 * unreadable, and the failure direction is the thing being chosen here.
	 */
	/*
	 * The open drawer fills the screen. Ben: "and it should fill the screen."
	 *
	 * `fixed` rather than a width calculation, because the drawer sits inside
	 * whatever boxes the theme's header is built from and every one of them can
	 * inset it. bike co.'s was 343px wide inside a 375px viewport -- the
	 * theme-side `calc(100vw - 32px)` was compensating for a margin the plugin
	 * had no business inheriting. Taking the drawer out of flow means the
	 * plugin owns the box outright and that compensation can be deleted rather
	 * than propped up.
	 *
	 * --upline-drawer-top defaults to 0 and nothing in this plugin sets it.
	 * It once was, from the bar's measured bottom edge -- 7001eef removed that
	 * setter and its scroll follower deliberately, and the paragraph describing
	 * it survived here directly above the paragraph that supersedes it, so this
	 * file stated an abandoned design as fact for four months. A theme that
	 * wants the drawer aligned to a different edge sets the token itself.
	 */
	/*
	 * The open drawer covers everything, header included. Ben, owning the
	 * change: "this menu should take up the whole screen, cover the header,
	 * everything."
	 *
	 * top: 0 rather than a measured edge, which is why this no longer needs the
	 * script and is no longer scoped to --panes. The previous version hung the
	 * drawer off the menu's own bottom, measured in JS and written to a token;
	 * with the answer being zero there is nothing to measure, nothing to keep
	 * in step on scroll, and no script-set value to overwrite whatever a theme
	 * put in the token. The token stays as the theme's lever and defaults to
	 * the ruling.
	 *
	 * Scoped to :checked only, so the no-JS drawer is full screen too -- and
	 * the control that closes it is a <label> inside it, which needs no script
	 * either. A full-screen surface whose only exit depends on JavaScript is
	 * the lockout this file has already built once.
	 */
	.upline-menu__switch:checked ~ .upline-menu__drawer {
		/*
		 * Column, not the collapse grid.
		 *
		 * The collapse is `grid-template-rows: 0fr -> 1fr` on a drawer with one
		 * child. Adding the close control made two, so the label took the 1fr
		 * row and the entire menu was laid out beneath it -- pane one starting
		 * 580px down an 812px screen. The collapse has no work to do once the
		 * drawer is a fixed full-screen box, so the open state stops being a
		 * grid and becomes what it actually is: a control, then the menu.
		 */
		display: flex;
		flex-direction: column;
		position: fixed;
		top: var(--upline-drawer-top, 0px);
		right: 0;
		/*
		 * Term 7's whole purpose is the 2f phone frame: a drawer above a
		 * persistent bottom bar. It was consumed at >=761 only, so on the one
		 * width the token exists for, the drawer covered the bar it was meant
		 * to sit on top of. 0 by default, so nothing moves for anyone else.
		 */
		bottom: var(--upline-drawer-bottom, 0px);
		left: 0;
		z-index: 100;
		overflow-y: auto;
		background: var(--upline-panel-bg);
		color: var(--upline-panel-fg);
	}

	/*
	 * The drawer's own way out, top-left, per state one of the drawing.
	 *
	 * Same surface tokens as everything else in here: the drawer and the
	 * desktop mega panel are one colour identity, so a theme cannot hand a shop
	 * two different menus by styling one of them.
	 */
	.upline-menu__close {
		display: none;
		/*
		 * 0 0 auto, because its only content is an absolutely positioned icon:
		 * as a shrinkable flex item its content height is zero, so it collapsed
		 * to 44x0 -- the X still painted, and the tap target it painted over did
		 * not exist. A close control that looks present and cannot be pressed is
		 * the lockout wearing its third disguise in this file.
		 */
		flex: 0 0 auto;
		position: relative;
		z-index: 1;
		width: 44px;
		height: 44px;
		margin: 0.6em 0 0.4em 0.55em;
		cursor: pointer;
		color: inherit;
	}

	.upline-menu__switch:checked ~ .upline-menu__drawer .upline-menu__close {
		display: block;
	}

	/*
	 * The same bar the toggle icon is built from.
	 *
	 * Both icons are two-pixel rules of `currentColor`; the hamburger's are
	 * 1.1em and the X's were 19px, so on a theme with a larger type scale the
	 * control that opens the drawer grew and the one that closes it did not.
	 * A theme styling one and finding the other unmoved is the kind of
	 * inconsistency that reads as a bug in the plugin rather than a difference
	 * of units, which matters now that both are designated stylable surfaces.
	 *
	 * Same metric, same primitive, one lever.
	 */
	/*
	 * A box, not a bar -- so a theme can paint it with one mask.
	 *
	 * 1.26.1 put this icon on the toggle's 1.1em metric, which fixed the
	 * length but not the construction: the span WAS one arm of the X, 16.5x2
	 * and rotated 45deg, with the second arm on ::before. Measured in the
	 * served drawer it came out 13.1x13.1 against the toggle's and magnifier's
	 * 20x20. A mask on a rotated 16.5x2 strip cannot show a glyph, so theming
	 * this one icon meant five overrides that reconstruct it -- box, un-rotate,
	 * re-centre, repaint through a mask, suppress the stray arm -- where the
	 * other two take two lines.
	 *
	 * The magnifier is the pattern: a 1.1em square whose marks are drawn by its
	 * pseudo-elements, so `background: currentColor` plus a mask replaces the
	 * whole glyph and the drawn marks are clipped away with it. The span itself
	 * now carries no rotation and no paint -- it is just the box.
	 */
	.upline-menu__close-icon {
		display: block;
		position: absolute;
		top: 50%;
		left: 50%;
		width: 1.1em;
		height: 1.1em;
		transform: translate(-50%, -50%);
	}

	.upline-menu__close-icon::before,
	.upline-menu__close-icon::after {
		content: "";
		position: absolute;
		top: 50%;
		left: 0;
		width: 100%;
		height: 2px;
		background: currentColor;
	}

	.upline-menu__close-icon::before {
		transform: translateY(-50%) rotate(45deg);
	}

	.upline-menu__close-icon::after {
		transform: translateY(-50%) rotate(-45deg);
	}

	/*
	 * The checkbox goes where the X is.
	 *
	 * A <label> cannot take focus, so the X on its own is a mouse-only control.
	 * The checkbox is the real control and is already the first focusable thing
	 * in the nav -- it is simply parked under a drawer that now covers the whole
	 * screen. Moving it onto the X keeps one control doing one job: Tab reaches
	 * it, Space closes, the ring is drawn where the shopper is looking, and none
	 * of it needs the script.
	 *
	 * Still invisible, still 44px, still the same checkbox: this is a change of
	 * address, not a second control to keep in step with the first.
	 */
	.upline-menu__switch:checked {
		position: fixed;
		top: 0.6em;
		left: 0.55em;
		z-index: 101;
		width: 44px;
		height: 44px;
		clip: auto;
		clip-path: inset(50%);
	}

	.upline-menu__switch:checked:focus-visible ~ .upline-menu__drawer .upline-menu__close {
		outline: 2px solid var(--upline-accent);
		outline-offset: 2px;
	}

	/*
	 * The surface is right with or without the script; only the full-screen box
	 * depends on it. Scoped to --panes because --upline-drawer-top is measured
	 * by the script: with no script the token is unset, `top` falls back to 0,
	 * and the drawer covers its own toggle -- a menu that opens and cannot be
	 * closed. The no-JS path keeps the in-flow stacked list it always had.
	 */
	.upline-menu__switch:checked ~ .upline-menu__drawer {
		background: var(--upline-panel-bg);
		color: var(--upline-panel-fg);
	}

	.upline-menu.upline-menu--panes .upline-menu__bar {
		/*
		 * Pane one flows normally; the panel is lifted out and placed beside
		 * it. Three attempts put all three panes in one grid with every
		 * wrapper set to `display: contents`, and each failed the same way:
		 * one grid has ONE vertical flow, so pane one's five 44px tap targets
		 * and pane two's list had to share rows. First that opened a 100px
		 * hole in a list meant to read as continuous; then making the panel
		 * span every row pushed pane one below it entirely.
		 *
		 * Two flows need two boxes. This is the cheap version of that.
		 */
		display: block;
		position: relative;
		padding-right: 0;
	}

	/*
	 * The height belongs to the OPEN drawer only.
	 *
	 * As a plain `min-height` on the bar it beat the collapse's `min-height: 0`
	 * -- same specificity, later in the file -- so the grid row could never
	 * reach 0fr and the shut drawer occupied 60vh of every mobile page. It was
	 * invisible and out of the tab order, which is exactly why it survived:
	 * nothing looked wrong, there was simply a screen of nothing under the
	 * header.
	 *
	 * Measured on a live store at 390px: 506px tall while shut.
	 */
	.upline-menu__switch:checked ~ .upline-menu__drawer .upline-menu__bar {
		flex: 1 1 auto;
		/*
		 * 100%, not 60vh: the drawer is now a fixed box that already ends at
		 * the viewport bottom, so the bar fills its parent rather than naming a
		 * viewport fraction of its own. The 60vh version is what occupied the
		 * page while SHUT in 1.23.0 -- a height that belongs to the open state
		 * is safest expressed as "all of the thing that is only there when
		 * open".
		 */
		/*
		 * Was `min-height: 100%; height: 100%`, which made the bar as tall as
		 * the whole panel. That was right while the bar was the only thing in
		 * it, and wrong the moment the drawer grew a footer: the bar took the
		 * full height and pushed the footer past the bottom edge -- measured on
		 * bikeco at y933.7 in an 844 viewport, reached only by scrolling the
		 * whole drawer.
		 *
		 * The drawer is a flex column, so the bar claims what is left after
		 * the heading and the footer and scrolls inside itself. `min-height: 0`
		 * is what lets it: a flex item will not shrink below its content
		 * without it, which is the usual reason a "scrolling" child scrolls
		 * its parent instead.
		 *
		 * The panes still get their height from the bar -- they are positioned
		 * against it -- and now that height stops above the footer, which is
		 * where a pane should stop anyway.
		 */
		flex: 1 1 auto;
		min-height: 0;
		overflow-y: auto;
	}

	.upline-menu.upline-menu--panes .upline-menu__bar > .upline-menu__item {
		/*
		 * Static, so the panel positions against the BAR.
		 *
		 * The dropdown layout makes each parent item `position: relative` so a
		 * desktop dropdown hangs off its own row, and that rule carries no
		 * breakpoint. In panes mode it made the item the panel's containing
		 * block, so `left: 30%` resolved against a 117px list item instead of
		 * the 390px bar and the panel landed on top of pane one. Measured at
		 * 390px: left computed to 35px.
		 *
		 * The mega layout never hit this because it does not position its
		 * items -- which is why a bug in the shared drill-down only showed on
		 * one of the two layouts that use it.
		 */
		position: static;
		display: block;
		width: var(--upline-pane-one-w);
	}

	/*
	 * The stated rule: a level-one label wider than its column truncates
	 * rather than running under pane two.
	 *
	 * Authored here on purpose. The drawer layout's copy of this block is
	 * generated from it by tools/publish-drawer-css.py, and my first attempt
	 * added this to the generated half -- which the drift gate refused, which
	 * is exactly what it is for. The duplication I took for a defect is a
	 * mirror with a guard on it.
	 *
	 * A column sized to its longest label would be better and is not reachable
	 * from here: the pane is absolutely positioned inside its own list item, so
	 * sizing the column to the content means restructuring the bar into a
	 * shared grid, which needs `display: contents` on the list items and takes
	 * the list's own semantics with it. That is an accessibility trade on a
	 * navigation list and it wants a rendered look before it is made.
	 */
	.upline-menu.upline-menu--panes .upline-menu__bar > .upline-menu__item > .upline-menu__link {
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}

	.upline-menu.upline-menu--panes .upline-menu__bar > .upline-menu__item > .upline-menu__link {
		display: block;
		border-bottom: 0;
		font-weight: var(--upline-menu-bar-weight);
		/* Pane one is now against the viewport edge, so it needs the inset the
		 * theme's container used to give it. */
		padding: 0.62em 1.1em;
		text-transform: var(--upline-menu-bar-case);
		letter-spacing: var(--upline-menu-bar-tracking);
		font-size: var(--upline-menu-bar-size);
	}

	.upline-menu.upline-menu--panes .upline-menu__item.is-open > .upline-menu__link {
		text-decoration: underline;
		text-decoration-color: var(--upline-accent);
		text-decoration-thickness: 2px;
		text-underline-offset: 5px;
	}

	/* Panes two and three, beside pane one, with their own vertical flow. */
	.upline-menu.upline-menu--panes .upline-mega {
		position: absolute;
		left: var(--upline-pane-one-w);
		right: 0;
		top: 0;
		bottom: 0;
		display: grid;
		/*
		 * 58%, not 45%. The panel starts at 30% of the drawer, so 45% of what
		 * is left capped pane two at 118px and "SHOP BY BRAND" wrapped -- with
		 * the active entry's accent underline drawn through both lines, which
		 * reads as a defect rather than as emphasis. fit-content still shrinks
		 * to the content, so a pane of short words does not take the room.
		 */
		/*
		 * Every track able to reach zero, so the row cannot sum past the pane.
		 *
		 * It was `fit-content(58%) 1px minmax(6rem, 1fr)` and both ends had a
		 * floor: `fit-content()` never shrinks below min-content, so one long
		 * word takes more than its 58%, and `6rem` is a hard 96px. Measured on
		 * range at 1440: 143.82 + 1 + 96 = 240.8 inside a 201.6 pane, with the
		 * third step clipped by the pane's own `overflow: auto` -- "Wax"
		 * surviving at 25.9px and "Freestyle" cut.
		 *
		 * `minmax(0, …)` removes the automatic minimum, which is the only thing
		 * that lets a grid track go narrower than its content rather than
		 * overflow its container.
		 */
		/*
		 * `minmax(0, 58%)`, not `minmax(0, fit-content(58%))`.
		 *
		 * `fit-content()` is not a <track-breadth> and may not appear inside
		 * `minmax()`. 1.53.0 shipped it, the whole declaration was invalid, the
		 * browser dropped it, and the pane fell back to `.upline-mega--cols-N`
		 * -- two tracks where the placement expects three. `grid-column: 3` on
		 * an open level-3 list then created an implicit track after the
		 * explicit two, which is why the third step rendered at the drawer's
		 * right edge and scrolled the pane sideways when focused: Ben's "step
		 * two disappears and no 3 appears".
		 *
		 * The percentage does the capping and the 0 minimum does the shrinking,
		 * which is what `fit-content()` was there for; what it loses is sizing
		 * to content below the cap, and a `1fr` third track takes that room
		 * back.
		 *
		 * Not caught here, and could not have been: this suite matches text and
		 * the text was exactly what was intended. Only a browser knows the
		 * declaration is invalid.
		 */
		grid-template-columns: minmax(0, 58%) 1px minmax(0, 1fr);
		align-content: start;
		grid-auto-rows: min-content;
		column-gap: 0;
		/*
		 * row-gap too, not just column-gap. The desktop panel's `gap` survived
		 * the override and left 16px between every pane-two entry, which with a
		 * rule under each entry reads as the rule floating loose of the thing it
		 * belongs to. Entries meet; the rule is the join.
		 */
		row-gap: 0;
		border-left: 1px solid var(--upline-rule);
		padding: 0;
		overflow-y: auto;
	}

	.upline-menu.upline-menu--panes .upline-menu__item:not(.is-open) .upline-mega {
		display: none;
	}

	.upline-menu.upline-menu--panes .upline-mega::before {
		content: "";
		grid-column: 2;
		grid-row: 1 / -1;
		background: var(--upline-rule);
	}

	/*
	 * --upline-pane-rule was declared here and consumed by nothing. It served
	 * the drawer's horizontal entry rules, which came out in 1.27.2 when the
	 * spec drawings turned out to have no horizontal rules at all; the token
	 * outlived them and sat declared and unread across three releases with the
	 * suite green. Removed in 1.29.0, along with the blind spot: the contract
	 * is now asserted in the consumed direction too, not only published-and-read.
	 */
	.upline-menu.upline-menu--panes .upline-mega__col {
		display: contents;
	}

	/*
	 * One rule per pane-two entry, the same rule on every one of them.
	 *
	 * Ben: "there un expected lines kinda all over the place." They were the
	 * desktop panel's own borders showing through -- a border under each
	 * heading sized to the heading's text column, a border over the footer, and
	 * a 1.1em top margin on any heading that happened to be second in its
	 * column. Three decorations designed for a wide multi-column panel, all
	 * landing in a narrow drill-down at whatever width the column gave them.
	 *
	 * In panes mode the entry styling replaces them wholesale rather than
	 * patching each one, so nothing from the desktop panel leaks through.
	 */
	/*
	 * No horizontal rules. The spec drawings have none at all: the only lines
	 * in the drawer are the thin continuous verticals between panes.
	 *
	 * 1.24.0 gave every pane-two entry a uniform border-bottom, on a reading of
	 * "there un expected lines kinda all over the place" that took the remedy to
	 * be tidier lines. The drawings say the remedy was fewer. Entry rhythm is
	 * spacing now, which is what the drawing actually shows.
	 *
	 * Pane two is caps bold against pane one's caps -- the middle of the three
	 * weights the drawings step through.
	 */
	.upline-menu.upline-menu--panes .upline-mega__heading {
		grid-column: 1;
		margin: 0;
		padding: 0.62em 1.1em;
		border-bottom: 0;
		cursor: pointer;

		/*
		 * !important audit: stays, and it is the layer asymmetry rather than a
		 * theme doing anything wrong.
		 *
		 * A pane-two entry is an <h4>. Everything this plugin ships sits in
		 * @layer upline, and a layered normal declaration loses to an unlayered
		 * one at any specificity -- so every theme that styles h4, which is
		 * every theme, sets the size and weight of these entries. Measured on
		 * Twenty Twenty-Five: 16.1px at weight 400 where this asks for 11.8px
		 * at 700. text-transform survived only because the theme happens not to
		 * set it.
		 *
		 * At drawer width that is not a matter of taste. Pane two is about
		 * 150px wide; a 16px heading wraps nearly every multi-word entry and
		 * the pane's rhythm goes with it. The three weights the spec drawings
		 * step through -- pane one caps, pane two caps bold, pane three title
		 * case lighter -- are how a shopper reads which pane they are in.
		 *
		 * Scoped to the drawer only. The desktop panel keeps taking the theme's
		 * heading type, where there is room for it.
		 *
		 * ---- withdrawn in 1.54.0 ----
		 *
		 * Two things were wrong here, and the second is why this is quoted
		 * rather than deleted.
		 *
		 * The ruling: an important declaration in a layer beats an unlayered
		 * one, and unlayered counts as the last layer, so against these a theme
		 * had no route at all -- not normal, not important, not a deeper
		 * selector. The comment called the guard "structure, not identity", and
		 * 0.74rem / 700 / uppercase / 0.04em is identity. It is a token now.
		 *
		 * The defect: font-size was never applying anyway. A second comment had
		 * lost its opening delimiter, so six lines of prose sat loose inside
		 * the declaration block, and CSS error recovery runs to the next
		 * semicolon -- which was the end of the font-size declaration. It was
		 * dropped with the prose. font-weight and text-transform survived
		 * because they sat after the resync point. Measured on range: the
		 * heading computed 18px, not the 11.84px this asked for, through every
		 * release that carried the comment explaining why it must not lose.
		 *
		 * Nothing textual could have caught it. The suite matched the
		 * declaration, and the declaration was there.
		 */
		font-size: var(--upline-menu-label-size);
		font-weight: var(--upline-menu-label-weight);
		text-transform: var(--upline-menu-label-case);
		letter-spacing: var(--upline-menu-label-tracking);
	}

	/*
	 * Explicitly beaten rather than left to source order: the desktop rule is
	 * `.upline-mega__col > .upline-mega__heading:not(:first-child)` at (0,3,0),
	 * and the panes rule above is (0,3,0) too, so which margin applied was
	 * decided by which sat later in this file. TESTS.md 7n.
	 */
	.upline-menu.upline-menu--panes .upline-mega__col > .upline-mega__heading:not(:first-child) {
		margin-top: 0;
	}

	.upline-menu.upline-menu--panes [data-upline-entry].is-open {
		text-decoration: underline;
		text-decoration-color: var(--upline-accent);
		text-decoration-thickness: 2px;
		text-underline-offset: 5px;
	}

	/* ---- a panel with no columns -------------------------------------
	 *
	 * The mega layout's fallback for a category that does not earn a composed
	 * panel, and every non-mega layout: both are a nested <ul> from
	 * plain_list() with no .upline-mega__col anywhere. The rules below assumed
	 * columns, so every list in such a panel -- the top-level one and each
	 * nested one -- matched the pane-three rule and stacked in its single cell,
	 * drawn over each other beside pane one. Measured on bike co.'s GEAR at
	 * 390px: two lists, both at y=187, in a panel 44px tall because everything
	 * in it was out of flow.
	 *
	 * `display: contents` on the list and its items, so the anchor and its
	 * sub-list become grid items in their own right -- the same mechanism
	 * .upline-mega__col already uses, one level further in.
	 * ------------------------------------------------------------------ */
	.upline-menu.upline-menu--panes .upline-mega > .upline-mega__list,
	.upline-menu.upline-menu--panes .upline-mega > .upline-mega__list > li {
		display: contents;
	}

	.upline-menu.upline-menu--panes .upline-mega > .upline-mega__list > li > a {
		grid-column: 1;
		margin: 0;
	}

	.upline-menu.upline-menu--panes .upline-mega > .upline-mega__list > li > .upline-mega__list {
		grid-column: 3;
		grid-row: 1;
		position: absolute;
		top: 0;
		bottom: 0;
		overflow-y: auto;
		margin: 0;
		padding: 0.4em 1em;
		font-size: 0.82rem;
		overflow-wrap: break-word;
	}

	.upline-menu.upline-menu--panes .upline-mega > .upline-mega__list > li > .upline-mega__list:not(.is-open) {
		display: none;
	}

	.upline-menu.upline-menu--panes .upline-mega__col--headless > .upline-mega__list {
		grid-column: 1;
		/* Pane two is in flow; only the leaves are lifted out of it. */
		position: static;
		/*
		 * Explicitly auto: the pane-three rule below pins every list to row 1
		 * so the leaves start at the top, and a headless list inherited that
		 * and drew both subcategory groups on top of each other.
		 */
		grid-row: auto;
		font-size: 0.8rem;
		margin: 0;
		padding: 0;
	}

	/*
	 * A headless column's items ARE pane-two entries, so they match them --
	 * and since 1.54.4 they match them by being in the same rule rather than
	 * by carrying a copy of its declarations. Two rules that agree today are
	 * not one rule that means it, and the release gate refused the tie.
	 */

	/*
	 * One owner for the label register.
	 *
	 * These two selectors are the same weight and both used to set the same
	 * four type properties, so which one won was decided by which sat lower in
	 * the file. The release gate refused it, correctly: a headless column's
	 * items ARE pane-two entries, and two rules agreeing today is not the same
	 * as one rule meaning it.
	 */
	.upline-menu.upline-menu--panes .upline-mega > .upline-mega__list > li > a,
	.upline-menu.upline-menu--panes .upline-mega__col--headless > .upline-mega__list > li > a {
		font-size: var(--upline-menu-label-size);
		font-weight: var(--upline-menu-label-weight);
		text-transform: var(--upline-menu-label-case);
		letter-spacing: var(--upline-menu-label-tracking);
	}

	/* ----------------------------------------------------------------
	 * Every entry is a link the whole way across its row.
	 *
	 * The drill-down's headings were padded on the `<h4>` and the anchor
	 * inside stayed inline: a 107x16 link in a 35.7px row, so most of the row
	 * was the heading's padding. A click there is not a miss -- it reaches the
	 * heading, does nothing, and (until this release) collapsed the open
	 * category on the way, which is what Ben was hitting. The same shape held
	 * for the leaves.
	 *
	 * So the padding moves to the anchor and the anchor fills the row, at the
	 * 44px tap floor. The rule a shopper can rely on afterwards: a click in a
	 * row is on that row's link, or it is on nothing.
	 * ---------------------------------------------------------------- */
	.upline-menu.upline-menu--panes .upline-mega__heading,
	.upline-menu.upline-menu--panes .upline-mega > .upline-mega__list > li,
	.upline-menu.upline-menu--panes .upline-mega__list li {
		padding: 0;
	}

	.upline-menu.upline-menu--panes .upline-mega__heading > a,
	.upline-menu.upline-menu--panes .upline-mega__list li a,
	.upline-menu.upline-menu--panes .upline-mega > .upline-mega__list > li > a,
	.upline-menu.upline-menu--panes .upline-mega__col--headless > .upline-mega__list > li > a {
		display: flex;
		align-items: center;
		box-sizing: border-box;
		width: 100%;
		min-height: 44px;
		padding: 0.5em 1.1em;
	}

	.upline-menu.upline-menu--panes .upline-mega__list {
		grid-column: 3;
		grid-row: 1;
		/*
		 * Absolutely positioned INTO its grid area, which is the point: an
		 * abspos grid item is placed in its area but contributes nothing to
		 * track sizing. In flow, the leaves' height set the first row's height
		 * and pushed the second group of subcategories 79px down the pane --
		 * the last survivor of "a pane must not resize its neighbour", one
		 * level down from where that law was learned.
		 */
		position: absolute;
		top: 0;
		bottom: 0;
		overflow-y: auto;
		font-size: 0.8rem;
		overflow-wrap: break-word;
		margin: 0;
		padding: 0.4em 1em;
	}

	/*
	 * Hidden unless THIS list is the open one. Keyed to the list rather than to
	 * its column, because a column can hold several heading+list pairs and
	 * keying to the column revealed all of them at once, stacked in the single
	 * pane-three cell. Ben: "loading each layer/subcategory on top its self."
	 *
	 * Headless columns are excluded: their list is pane-two content, always
	 * visible, and never a pane-three target.
	 */
	.upline-menu.upline-menu--panes .upline-mega__col:not(.upline-mega__col--headless) > .upline-mega__list:not(.is-open) {
		display: none;
	}

	.upline-menu.upline-menu--panes .upline-mega__list li a {
		display: block;
		padding: 0.42em 0;
		text-transform: none;
		letter-spacing: 0;
		font-size: 0.82rem;
		font-weight: 400;
	}

	/*
	 * "Shop all X" is the way out of the drill-down, so it sits at the foot of
	 * the pane the shopper is reading rather than adrift under pane one, where
	 * it landed when it inherited the bar's first column.
	 *
	 * It is the last pane-two entry, so it wears the entry's rule -- under it,
	 * like the others. The border-TOP it used to carry was one of the lines
	 * Ben was seeing: the only edge in the pane that sat above its label.
	 */
	.upline-menu.upline-menu--panes .upline-mega__footer {
		grid-column: 1;
		margin: 0;
		padding: 0.62em 1.1em;
		border-top: 0;
		border-bottom: 0;
	}

	.upline-menu.upline-menu--panes .upline-mega__footer .upline-mega__all {
		text-transform: uppercase;
		letter-spacing: 0.04em;
		font-size: 0.74rem;
		font-weight: 700;
	}
}

/*
 * Above the breakpoint the menu is always open and the toggle never exists.
 * Stated as a rule that cannot lose, for the same reason the rail states its
 * own: a theme carrying something like `.woocommerce label { display:
 * inline-block !important }` would otherwise put a hamburger on a full-width
 * screen and the navigation behind a click.
 *
 * !important audit: the toggle rule stays; the two that pinned the bar to
 * flex and block are gone.
 *
 * The test is whether a rule exists for correctness or to out-shout a theme.
 * Hiding the hamburger above the breakpoint is correctness: a named
 * third-party selector (`.woocommerce label { display: inline-block
 * !important }`) matches our label and would put the navigation behind a click
 * on a desktop screen, and nothing but !important survives an incoming one.
 * Pinning our own bar to `display: flex` was the other kind -- it named no
 * threat and only stopped a theme choosing differently. Inside @layer upline a
 * theme can now simply choose, which is the point.
 */
@media (min-width: 761px) {
	/* The drawer wrapper is a mobile device only; above the breakpoint it is
	   not part of the layout at all. */
	.upline-menu__drawer {
		display: contents;
	}

	/*
	 * Every layout but the drawer hides its toggle above the boundary.
	 *
	 * This was unqualified, so it hid the drawer's toggle too -- and the
	 * drawer is a drawer at every width, opened by that control and nothing
	 * else. A shopper above 761px could not open it, which is why header
	 * option 2a could not ship on any released plugin. Two theme benches were
	 * waiting on this.
	 *
	 * Written as `:not()` on the root rather than as a louder rule in the
	 * generated block below, because the two then describe different menus
	 * and never contest each other: the hide cannot match a drawer, and the
	 * drawer's show cannot match anything else. A fight settled by weight is
	 * a fight that can be lost again.
	 *
	 * `:not()` carries its argument's weight -- (0,2,0) here, not (0,1,0) --
	 * which the Slope bench established and which matters because the
	 * generated rule opposite is also (0,2,0). They do not tie because they
	 * cannot both match, not because one outranks the other.
	 */
	.upline-menu:not(.upline-menu--drawer) .upline-menu__toggle {
		display: none !important;
	}

	.upline-menu--mega .upline-menu__bar,
	.upline-menu--dropdown .upline-menu__bar {
		display: flex;
	}

	.upline-menu--list .upline-menu__bar {
		display: block;
	}
}

/*
 * Provide the utility ourselves; not every theme ships it.
 *
 * !important audit: this one stays. A visually-hidden label that a theme's
 * `position: relative` wins against stops being visually hidden, and dumps
 * screen-reader text into the middle of the page. It is defending a rule
 * against being broken, not winning a stylistic argument, and there is no
 * specificity that survives an incoming `!important` from a theme.
 */
.upline-menu .screen-reader-text,
.upline-tiles .screen-reader-text,
.upline-chips .screen-reader-text {
	position: absolute !important;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

/* ---------------------------------------------------------------------------
 * Search
 *
 * The panel is the one place besides the mega menu that needs an opaque
 * background, and for the same reason: it overlays the page. It uses the same
 * documented exception — the theme's own base and contrast, falling back to
 * the system Canvas pair — and asserts no colour of its own beyond that.
 * ------------------------------------------------------------------------ */

.upline-search {
	position: relative;
}

.upline-search__field {
	display: flex;
	gap: var(--upline-gap-sm, .5rem);
	align-items: center;
}

.upline-search__input {
	flex: 1 1 auto;
	min-width: 0;
	font: inherit;
	color: inherit;
}

.upline-search__panel {
	position: absolute;
	left: 0;
	right: 0;
	top: 100%;
	z-index: 30;
	max-height: min(70vh, 32rem);
	overflow-y: auto;
	padding: var(--upline-gap-sm, .5rem);
	border: 1px solid var(--upline-rule);
	border-radius: var(--upline-radius);
	background: var(--wp--preset--color--base, Canvas);
	color: var(--wp--preset--color--contrast, CanvasText);
}

.upline-search__group {
	margin: .5em .25em .25em;
	font-size: .78em;
	letter-spacing: .04em;
	text-transform: uppercase;
	opacity: .7;
}

.upline-search__group + .upline-search__group,
.upline-search__result--page:first-of-type {
	border-top: 1px solid var(--upline-rule);
	padding-top: .5em;
}

.upline-search__result,
.upline-search__all {
	display: flex;
	gap: var(--upline-gap-sm, .5rem);
	align-items: center;
	padding: .45em .5em;
	border-radius: var(--upline-radius);
	text-decoration: none;
	color: inherit;
}

.upline-search__result:hover,
.upline-search__result:focus,
.upline-search__result.is-active,
.upline-search__all:hover,
.upline-search__all.is-active {
	background: color-mix(in srgb, currentColor 8%, transparent);
}

.upline-search__thumb {
	flex: 0 0 auto;
	width: 40px;
	height: 40px;
	object-fit: cover;
	border-radius: var(--upline-radius);
	background: color-mix(in srgb, currentColor 8%, transparent);
}

.upline-search__text {
	display: flex;
	flex-direction: column;
	min-width: 0;
}

.upline-search__title {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.upline-search__meta {
	font-size: .85em;
	opacity: .8;
}

.upline-search__out {
	opacity: .75;
}

.upline-search__all {
	justify-content: center;
	margin-top: .25em;
	border-top: 1px solid var(--upline-rule);
	padding-top: .6em;
	font-size: .9em;
}

.upline-search__empty {
	margin: 0;
	padding: .6em .5em;
	opacity: .8;
}

.upline-search-pages {
	margin-top: calc(var(--upline-gap) * 1.5);
	border-top: 1px solid var(--upline-rule);
	padding-top: var(--upline-gap);
}

.upline-search-pages__title {
	margin: 0 0 .4em;
	font-size: 1em;
}

.upline-search-pages__list {
	margin: 0;
	padding-left: 1.1em;
}

/* ---------------------------------------------------------------------------
 * In-store availability
 *
 * Arrives after the page does, so it must not move anything that was already
 * readable: no reserved height, no skeleton, no placeholder text. It is hidden
 * until it has something true to say.
 * ------------------------------------------------------------------------ */

.upline-availability {
	margin: 0 0 var(--upline-gap, 1rem);
	font-size: .95em;
}

.upline-availability__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: .2em;
}

/*
 * The "New" badge. Positioned like WooCommerce's own sale flash and coloured
 * from currentColor, so it reads as part of the theme rather than as something
 * a plugin bolted on -- a shop that has restyled .onsale gets a badge that
 * still belongs beside it.
 */
/*
 * The brand landing treatment. No colours and no type of its own -- a logo is
 * the brand's, and everything else is the theme's.
 */
.upline-brand-intro {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 1.5em;
	margin: 0 0 1.5em;
}

.upline-brand__logo {
	max-width: 220px;
	height: auto;
}

.upline-brand__wordmark {
	font-size: 1.6em;
	letter-spacing: .01em;
}

.upline-brand__blurb {
	flex: 1 1 26ch;
	max-width: 62ch;
}

.upline-brand__blurb p:last-child { margin-bottom: 0; }

.upline-brand__empty {
	margin: 0 0 1.5em;
	opacity: .8;
}

.upline-brands__jump {
	display: flex;
	flex-wrap: wrap;
	gap: .6em;
	margin: 0 0 1.5em;
}

.upline-brands__group { margin: 0 0 1.5em; }

.upline-brands__letter {
	margin: 0 0 .4em;
	font-size: 1.1em;
}

.upline-brands__list {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(14em, 1fr));
	gap: .3em 1.5em;
	margin: 0;
	padding: 0;
	list-style: none;
}

.upline-brands__count { opacity: .6; }

.upline-new-badge {
	position: absolute;
	top: .5em;
	left: .5em;
	z-index: 2;
	padding: .25em .6em;
	border: 1px solid currentColor;
	border-radius: 2em;
	background: var(--upline-badge-bg, Canvas);
	/*
	 * Paired with the ground, for the reason panelBg/panelFg are paired: a
	 * surface asserting a background must assert its ink or it inherits the
	 * page's and reads at whatever contrast that happens to give. Measured in
	 * the wild before this existed -- Bones paired badgeBg: inverse with
	 * currentColor at contrast 1.00, and Ridgeline's brand badge read 4.02
	 * while the right value sat in the skin with nowhere to go.
	 */
	color: var(--upline-badge-fg, CanvasText);
	border-color: currentColor;
	font-size: .75em;
	line-height: 1.4;
	letter-spacing: .04em;
	text-transform: uppercase;
}

.upline-availability__lead {
	margin: 0 0 .25em;
}

.upline-availability__line {
	display: flex;
	align-items: baseline;
	gap: .5em;
}

.upline-availability__dot {
	flex: none;
	width: .45em;
	height: .45em;
	border-radius: 50%;
	background: currentColor;
	opacity: .55;
	transform: translateY(-0.1em);
}

.upline-availability__note {
	margin: .25em 0 0;
	opacity: .75;
	font-size: .92em;
}


/* ------------------------------------------------------------------------
 * Reduced motion is the plugin's job, not the theme's
 *
 * A shop that has asked its operating system for less movement must get it
 * whatever theme it is wearing, and accessibility that depends on every theme
 * author remembering is accessibility that fails. So this is stated as a rule
 * that cannot lose.
 *
 * It neutralises the token rather than each animated property: every
 * transition in this file reads --upline-motion-duration, so zeroing it once
 * stops all of them, including any added later. That is one !important
 * instead of a list that would need maintaining, and it still leaves a theme
 * free to choose the motion that shoppers who have not asked for less will
 * see. `!important` on a custom property is the only way to make it something
 * a theme cannot outrank, which is the point.
 * ------------------------------------------------------------------------ */


/* ---- the variant picker ------------------------------------------------ */

.upline-picker { margin: 0 0 var(--upline-gap); }

.upline-picker__axis { margin: 0 0 var(--upline-gap-sm); }

.upline-picker__label { font-weight: 600; margin-right: .4em; }

.upline-picker__chosen { color: var(--upline-muted); }

.upline-picker__options {
	display: flex;
	flex-wrap: wrap;
	gap: var(--upline-gap-sm);
	margin-top: .5em;
}

.upline-picker__option {
	display: inline-flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: .15em;
	/*
	 * !important audit: stays. A tap target smaller than a fingertip is an
	 * accessibility regression, and a theme's generic `button { min-height:
	 * auto }` would otherwise win -- not because the theme disagrees, but
	 * because it never knew this control existed. Everything else about how
	 * these look is deliberately the theme's; this floor is not.
	 */
	min-width: var(--upline-tap) !important;
	min-height: var(--upline-tap) !important;
	padding: .35em .85em;
	border: 1px solid var(--upline-rule);
	border-radius: var(--upline-radius);
	background: transparent;
	color: inherit;
	font: inherit;
	line-height: 1.2;
	cursor: pointer;
}

.upline-picker__option:hover { border-color: var(--upline-accent); }

.upline-picker__option:focus-visible {
	outline: 2px solid var(--upline-accent);
	outline-offset: 2px;
}

/*
 * !important audit: the outline stays, the fill does not.
 *
 * Which option a shopper has selected is not a matter of taste -- a picker
 * where selection does not paint is a picker that has lost the shopper's place
 * -- so the ring that says "this one" is a mechanic and must not lose. Measured
 * on Ridgeline 2.4.2: unlayered theme button rules beat our layered
 * border-color and box-shadow outright, and selection rendered byte-identical
 * to its neighbours. That is the layer inversion pointing the wrong way, same
 * shape as the breakpoint defences.
 *
 * The fill deliberately carries no !important. A theme sets selectedBg/-Fg to
 * say what selection looks like, and forcing a transparent default over a
 * theme's own button background would make the selected option look *less*
 * styled than the rest. The plugin guarantees selection is visible; the theme
 * decides what visible looks like.
 */
.upline-picker__option[aria-pressed="true"] {
	border-color: var(--upline-accent) !important;
	box-shadow: inset 0 0 0 2px var(--upline-accent) !important;
	/*
	 * The fill carries no !important, and that is a deliberate limit on what
	 * the plugin promises. The ring above guarantees selection is *visible*;
	 * what selection looks like beyond that is the theme's, and a theme's own
	 * unlayered button rules will beat these two.
	 *
	 * The consequence a theme needs to know: set both tokens or neither. If a
	 * theme's button rule sets `background` but not `color`, our colour lands
	 * and our background does not, which is how white-on-white happens. Scope
	 * your button styling with `form.cart.is-enhanced` and it cannot arise.
	 */
	background: var(--upline-selected-bg);
	color: var(--upline-selected-fg);
}

/*
 * Grey means unbuyable, and only that. A size Upline will order in is a size
 * the shopper can have, so it stays at full strength and says so underneath --
 * greying it would be telling them no when the answer is yes.
 *
 * These two declarations are the rail's derived-disabled treatment, character
 * for character (facets.css, `.upline-facet__option.is-disabled`). A shopper
 * can meet "unavailable" twice on one page -- once in the rail, once here --
 * and it must look like the same fact both times. The fixture asserts the two
 * stay identical, because a shared look maintained by memory is a look that
 * drifts.
 */
.upline-picker__option[data-state="sold-out"],
.upline-picker__option[data-state="never"] {
	cursor: not-allowed;
	opacity: 0.45;
}

.upline-picker__option[data-state="order"] .upline-picker__option-note {
	font-size: .72em;
	color: var(--upline-muted);
}

.upline-picker__option-note:empty { display: none; }

.upline-picker__explain {
	margin: .4em 0 0;
	color: var(--upline-muted);
	min-height: 1.2em;
}

/*
 * WooCommerce's own selects stay in the DOM and keep doing the work -- they are
 * what the cart reads. Hidden only once the picker is live, and never for a
 * shopper without JavaScript.
 *
 * !important audit: stays. Whether two variation pickers render on one page is
 * not a matter of taste, so this is a mechanic and belongs with the breakpoint
 * defences rather than in the layer's normal traffic. The measured threat is
 * Ridgeline's unlayered `.rl-product__buy form.cart { display: flex }` -- the
 * layer inversion pointing the wrong way for once, which is exactly the
 * incoming-display-declaration shape this policy exists for.
 *
 * Note what it is NOT doing: if this stylesheet fails to load, the rule is gone
 * and the native form is visible again. That is the right failure -- an
 * unstyled picker beside a working form beats a hidden form beside a broken
 * picker.
 */
form.cart.is-enhanced .variations { display: none !important; }

/* ---- search, icon mode -------------------------------------------------- */

.upline-search--collapsible { display: flex; align-items: center; gap: var(--upline-gap-sm); }

.upline-search__toggle {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	/*
	 * !important audit: stays, same reason as the picker's options. A tap
	 * target smaller than a fingertip is an accessibility regression, and a
	 * theme's generic `button { min-height: auto }` would win -- not because
	 * the theme disagrees, but because it never knew this control existed.
	 */
	min-width: var(--upline-tap) !important;
	min-height: var(--upline-tap) !important;
	padding: 0;
	border: 0;
	background: transparent;
	color: inherit;
	cursor: pointer;
}

.upline-search__toggle:focus-visible { outline: 2px solid var(--upline-accent); outline-offset: 2px; }

.upline-search__toggle-icon {
	width: 1.1em;
	height: 1.1em;
	border: 2px solid currentColor;
	border-radius: 50%;
	position: relative;
	display: block;
}

.upline-search__toggle-icon::after {
	content: "";
	position: absolute;
	top: 100%;
	left: 100%;
	width: 0.5em;
	height: 2px;
	background: currentColor;
	transform: rotate(45deg);
	transform-origin: 0 0;
}

/*
 * Collapsed by width rather than `display`, so the transition is animatable and
 * rides the same motion gate as everything else -- including reduced motion,
 * which is handled once at :root and reaches this without a second list.
 */
/*
 * The expanded field is an OVERLAY, not a wider form.
 *
 * Ben: "tap the magnifier, the field expands over the header row, nothing in
 * flow moves." Measured on bike co. at 390px before this: expanding grew the
 * form 52 -> 309px, which grew the theme's action group 300 -> 358px inside a
 * row set to `flex-wrap: wrap`, so the row re-wrapped and the header went
 * 147 -> 201px. The plugin pushed; the theme's wrapping row turned the push
 * into a height change.
 *
 * Taking the field out of flow ends that by construction rather than by
 * tuning: no ancestor's width changes, so no ancestor can re-wrap, and it
 * holds on any header rather than on the one row it was measured against. The
 * alternative was capping the field at the 120px this particular row could
 * absorb, which buys a steady header with a search box too small to search in.
 *
 * It anchors to the form's right edge and grows leftward across the row. The
 * toggle stays above it (z-index below) because the magnifier is also the
 * control that closes it, and an overlay that buries its own dismiss is the
 * drawer's lockout in miniature.
 */
.upline-search--collapsible {
	position: relative;
}

.upline-search--collapsible .upline-search__toggle {
	position: relative;
	z-index: 2;
}

/*
 * While the overlay is under it, the magnifier takes the overlay's foreground.
 *
 * The toggle is coloured for wherever the header puts it -- on bike co. that
 * is near-black for a white header. Expanded, an opaque panel slides beneath
 * it, and measured there the icon was rgb(15,15,15) on rgb(11,11,11): present,
 * hit-testable, and invisible. The control that dismisses the overlay cannot
 * be the one thing the overlay hides.
 *
 * Same panel pair the surface itself uses, so a theme that recolours one
 * recolours both and cannot produce this mismatch.
 */
.upline-search--collapsible.is-expanded .upline-search__toggle {
	color: var(--upline-panel-fg);
}

.upline-search--collapsible .upline-search__field {
	position: absolute;
	top: 0;
	bottom: 0;
	right: 0;
	z-index: 1;
	width: min(26rem, 60vw);

	/*
	 * border-box, because the padding below is inside the width. Without it
	 * the field measured 278px against a 234px width and its left edge landed
	 * at -31px, off the side of a 390px screen -- the overlay solved the bump
	 * and introduced a horizontal overflow in the same move.
	 */
	box-sizing: border-box;

	/* Content stops short of the magnifier riding on top of it. */
	padding-right: var(--upline-tap, 44px);

	background: var(--upline-panel-bg);
	color: var(--upline-panel-fg);

	overflow: hidden;
	max-width: 0;
	opacity: 0;
	visibility: hidden;
	transition:
		max-width var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
		opacity var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
		visibility 0s linear var(--upline-motion-duration, 0s);
}

.upline-search--collapsible.is-expanded .upline-search__field {
	max-width: min(26rem, 60vw);
	opacity: 1;
	visibility: visible;
	transition:
		max-width var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
		opacity var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
		visibility 0s linear 0s;
}

/* --------------------------------------------------------------------------
   The search boundary
   --------------------------------------------------------------------------

   A collapsible search is a toggle below the boundary and, for the options
   with room for one, a field at or above it. Until 1.56.0 the plugin's search
   carried no width rule at all: `collapsible` was all-or-nothing at every
   width, which is why the boundary could not be set from outside and why
   `push` could not gain a collapsing search.

   Keyed on `body.upline-header--<option>`, which the plugin now emits for
   every option. It did not before: `shell` is geometry and three of the six
   options need none, so half of them were anonymous in the document and no
   rule could ask which header this was.

   `toggle` is the default state of a collapsible search, so the three options
   that stay a toggle at every width -- sidebar, bottom-bar and, since M-2,
   two-row -- need no rule here at all. What is written is the opt-in, which is
   also why this list is short enough to read against the registry.

   two-row was in both lists below until M-2. Ben read the built header and
   ruled the desk field out of it: the frame already spends its upper row on
   the brand and its utilities, and the field is the one element that does not
   sit in the drawing. Removing it from the selectors is the whole change --
   with no rule here, `collapsible` means what it meant before 1.56.0 gave the
   wide headers an opt-in, which is a toggle at every width.

   The boundary is published as `header.search.boundary`; this media query and
   that value have to move together, and the suite holds them to it.
   -------------------------------------------------------------------------- */
@media (min-width: 761px) {

	/*
	 * !important audit: stays, and the reason is what the declaration is FOR.
	 *
	 * Whether a toggle exists at a given width is not a look -- it is the
	 * search boundary, which this plugin publishes as
	 * `header.search.boundary` and a theme reads and builds against. A rule
	 * the plugin publishes and any unlayered declaration can silently win is
	 * not a contract.
	 *
	 * Measured on range rather than argued: with the plugin alone the toggle
	 * computed `none` at desk; adding one unlayered `.upline-search__toggle {
	 * display: inline-flex }` -- the shape Ridgeline had, by accident -- made
	 * it `flex` and the boundary was gone; the same declaration with
	 * `!important` held it at `none`. Everything here is in `@layer upline`,
	 * and an unlayered declaration beats a layered one at any specificity, so
	 * the plain form was never going to hold this.
	 *
	 * The line it draws: function the plugin publishes ships held, look stays
	 * the theme's. The toggle's size, colour, icon and box are all still
	 * theirs -- only its existence at this width is ours, and a skin that
	 * wants a desk toggle chooses an option whose search is `toggle`.
	 */
	body.upline-header--utility-line .upline-search--collapsible .upline-search__toggle,
	body.upline-header--drawer .upline-search--collapsible .upline-search__toggle,
	body.upline-header--push .upline-search--collapsible .upline-search__toggle {
		display: none !important;
	}

	body.upline-header--utility-line .upline-search--collapsible .upline-search__field,
	body.upline-header--drawer .upline-search--collapsible .upline-search__field,
	body.upline-header--push .upline-search--collapsible .upline-search__field {
		position: static;
		max-width: min(26rem, 60vw);
		width: min(26rem, 60vw);
		padding-right: 0;
		opacity: 1;
		visibility: visible;
		overflow: visible;

		/*
		 * Background and colour come off together.
		 *
		 * The collapsed state sets `background: var(--upline-panel-bg)` and
		 * `color: var(--upline-panel-fg)` as a pair, because collapsed it IS a
		 * panel sitting over the header. Opened into the header at desk it is
		 * not one, and 1.56.0 removed only the background -- so the field kept
		 * the PANEL's foreground while sitting on the header's background.
		 *
		 * On a skin whose panel is dark that is light text on a light header.
		 * Measured on bikeco at 1440: the field's colour `rgb(255,255,255)`
		 * from `--upline-panel-fg`, the input inheriting it, both backgrounds
		 * transparent, the nearest painted ancestor the header group at white
		 * -- contrast 1.0, an empty gap where the search sits and typed text
		 * that cannot be read.
		 *
		 * Not reproducible on range, whose own theme sets the colour and pins
		 * it dark either way, which is why the plugin's suite never saw it and
		 * why this was found by a render on a skin that differs.
		 *
		 * It is unavoidable rather than unlucky: since 1.56.0 the field is the
		 * only desk search control on every `field` option, so any skin whose
		 * panel ink differs from its header ink hits it.
		 */
		color: inherit;
		background: none;
		transition: none;
	}
}

/* ==========================================================================
   The drawer layout -- GENERATED, do not edit inside the markers
   ==========================================================================

   Every rule below is the mobile block above with its selectors scoped to
   `.upline-menu--drawer`. The drawer layout is that block at every width: it
   needs the toggle, the collapsing drawer and the close control, and it needs
   the narrow-width stacking too, because with no script there are no panes and
   an opened drawer would otherwise hold absolutely-positioned mega panels.

   The rules cannot simply be keyed on a class instead of a width. The drawer
   mechanism is checkbox-and-label with no script by design, so a JS-applied
   class would make the mobile drawer stop working with JavaScript off -- and
   CSS cannot say "this media query OR this class" for one shared block.

   So they exist twice and are maintained once:

     regenerate    python3 tools/publish-drawer-css.py
     check         python3 tools/drawer-drift.py   (runs in tools/release.sh)

   Editing between the markers is lost on the next regeneration, and the
   release gate goes red before that happens. Change the mobile block above;
   this follows.
   ========================================================================== */

/* upline:generated drawer-layout BEGIN */
	.upline-menu--drawer .upline-menu__toggle {
		display: inline-flex;
	}

	/*
	 * Closed until asked for. The checkbox is the state; no script involved.
	 *
	 * Unlike the panel, the drawer is in normal flow, so dropping `display:
	 * none` for `visibility: hidden` alone would reserve its full height and
	 * leave a tall blank gap under the header of every mobile page. It has to
	 * collapse as well as fade, so `grid-template-rows: 0fr -> 1fr` does the
	 * height: it animates, it needs no magic pixel number, and it collapses to
	 * genuinely nothing.
	 *
	 * The inner element is the grid item that gets clipped; `min-height: 0` is
	 * required or the row refuses to go below its content height.
	 */
	/*
	 * Open, the hamburger is a close control -- the mockup draws an X where the
	 * three lines were. The same element, because it is the same control: a
	 * second button would mean two things claiming to close one drawer.
	 */
	.upline-menu--drawer .upline-menu__switch:checked ~ .upline-menu__toggle .upline-menu__toggle-icon {
		background: transparent;
	}

	/* The outer lines sit at top: -6px and +6px, so they have to come back to
	   centre before rotating or the X is drawn as a wide V. */
	.upline-menu--drawer .upline-menu__switch:checked ~ .upline-menu__toggle .upline-menu__toggle-icon::before {
		top: 0;
		transform: rotate(45deg);
	}

	.upline-menu--drawer .upline-menu__switch:checked ~ .upline-menu__toggle .upline-menu__toggle-icon::after {
		top: 0;
		transform: rotate(-45deg);
	}

	.upline-menu--drawer .upline-menu__drawer {
		display: grid;
		grid-template-rows: 0fr;
		/*
		 * The block axis is not the only axis.
		 *
		 * `grid-template-rows: 0fr` collapses the height and says nothing
		 * about the width, so a shut drawer still reserved its full inline
		 * size in flow: measured 330px of a 390px row on the fixture, and
		 * 133.4px of a 358px brand row on bikeco -- a width set by the longest
		 * category name, so it varies per shop. The case that makes it matter:
		 * one long category name pushes a shop's own logo onto a second row
		 * while the menu is shut.
		 *
		 * Same shape as the desk defect 1.54.3 fixed by making the panel fixed
		 * in both states, and it was still here below the breakpoint. Zeroed
		 * rather than taken out of flow: `position` cannot be transitioned, so
		 * flipping it would snap the layout at the start of the closing
		 * animation instead of after it, which is the mistake the visibility
		 * timing on the desk panel exists to avoid.
		 */
		inline-size: 0;

		opacity: 0;
		visibility: hidden;
		pointer-events: none;
		transform: var(--upline-drawer-hidden-transform, translateY(-4px));

		transition:
			grid-template-rows var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
			opacity var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
			transform var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
			visibility 0s linear var(--upline-motion-duration, 0s);
	}

	/* The single grid item. min-height:0 or the row refuses to go below its
	   content height and the collapse silently does nothing. */
	.upline-menu--drawer .upline-menu__drawer > .upline-menu__bar {
		min-height: 0;
		overflow: hidden;

		/*
		 * Belt kept, assumption pinned: the base rule stopped drawing borders
		 * when Ben asked for the lines to go, so this zeroes nothing today. It
		 * stays because it also catches a border arriving from anywhere else,
		 * and the assertion beside it is what would notice if the base rule
		 * ever draws one again.
		 */
		border: 0;
	}

	.upline-menu--drawer .upline-menu__switch:checked ~ .upline-menu__drawer,
	.upline-menu--drawer .upline-menu__drawer[data-upline-open] {
		grid-template-rows: 1fr;
		inline-size: auto;
		opacity: 1;
		visibility: visible;
		pointer-events: auto;
		transform: none;
		transition:
			grid-template-rows var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
			opacity var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
			transform var(--upline-motion-duration, 0s) var(--upline-motion-easing, ease-out),
			visibility 0s linear 0s;
	}

	/* Once open the drawer must not clip its own submenus. */
	.upline-menu--drawer .upline-menu__switch:checked ~ .upline-menu__drawer > .upline-menu__bar {
		overflow: visible;
	}

	.upline-menu--drawer .upline-menu__bar > .upline-menu__item > .upline-menu__link {
		border-bottom: 1px solid var(--upline-rule);
	}

	/*
	 * The drawer had no mobile layout at all: the bar stayed flex-direction
	 * row, so top-level items sat side by side and wrapped, and each item grew
	 * to 600px tall because its panel rendered static in flow while still
	 * computing the desktop three-column grid inside a 144px box. Measured at
	 * 390px: a 1178px-tall bar nobody could use.
	 *
	 * This is the layout with no JavaScript -- a plain vertical stack, which is
	 * usable on its own. The three-pane drill-down is layered on top of it by
	 * the script; see .upline-menu.upline-menu--panes below.
	 */
	.upline-menu--drawer .upline-menu__bar {
		flex-direction: column;
	}

	.upline-menu--drawer .upline-menu__bar > .upline-menu__item {
		width: 100%;
	}

	.upline-menu--drawer .upline-mega {
		display: block;
		position: static;
		padding: 0 0 0.5em 1em;
		border: 0;
		background: transparent;
		color: inherit;
		box-shadow: none;

		/* The desktop grid has no business in a phone-width box. */
		grid-template-columns: none;
	}

	.upline-menu--drawer .upline-mega__featured {
		display: none;
	}

	.upline-menu--drawer .upline-mega__footer {
		margin-top: 0.2em;
		padding-top: 0.4em;
	}

	.upline-menu--drawer .upline-mega__col {
		margin-bottom: 0.75em;
	}

	/* ---- the three-pane drill-down ------------------------------------
	 *
	 * Taxonomy, then subcategories, then leaves, left to right, with the
	 * active entry in each pane underlined in accent. Added by the script:
	 * without it the stack above is what a shopper gets, which is the whole
	 * reason the stack is built first rather than as an afterthought.
	 * ------------------------------------------------------------------ */
	/*
	 * The drawer paints an opaque surface.
	 *
	 * The mobile block resets the panel's background to `transparent` -- right
	 * for a stacked list sitting in the page, wrong the moment the drawer is a
	 * surface of its own: page content showed straight through it and the menu
	 * could not be read over a product grid.
	 *
	 * panelBg is the token for exactly this, and its default chain ends at
	 * `Canvas`, which is opaque. That matters more than the colour -- a drawer
	 * that goes transparent when a theme supplies nothing fails toward
	 * unreadable, and the failure direction is the thing being chosen here.
	 */
	/*
	 * The open drawer fills the screen. Ben: "and it should fill the screen."
	 *
	 * `fixed` rather than a width calculation, because the drawer sits inside
	 * whatever boxes the theme's header is built from and every one of them can
	 * inset it. bike co.'s was 343px wide inside a 375px viewport -- the
	 * theme-side `calc(100vw - 32px)` was compensating for a margin the plugin
	 * had no business inheriting. Taking the drawer out of flow means the
	 * plugin owns the box outright and that compensation can be deleted rather
	 * than propped up.
	 *
	 * --upline-drawer-top defaults to 0 and nothing in this plugin sets it.
	 * It once was, from the bar's measured bottom edge -- 7001eef removed that
	 * setter and its scroll follower deliberately, and the paragraph describing
	 * it survived here directly above the paragraph that supersedes it, so this
	 * file stated an abandoned design as fact for four months. A theme that
	 * wants the drawer aligned to a different edge sets the token itself.
	 */
	/*
	 * The open drawer covers everything, header included. Ben, owning the
	 * change: "this menu should take up the whole screen, cover the header,
	 * everything."
	 *
	 * top: 0 rather than a measured edge, which is why this no longer needs the
	 * script and is no longer scoped to --panes. The previous version hung the
	 * drawer off the menu's own bottom, measured in JS and written to a token;
	 * with the answer being zero there is nothing to measure, nothing to keep
	 * in step on scroll, and no script-set value to overwrite whatever a theme
	 * put in the token. The token stays as the theme's lever and defaults to
	 * the ruling.
	 *
	 * Scoped to :checked only, so the no-JS drawer is full screen too -- and
	 * the control that closes it is a <label> inside it, which needs no script
	 * either. A full-screen surface whose only exit depends on JavaScript is
	 * the lockout this file has already built once.
	 */
	.upline-menu--drawer .upline-menu__switch:checked ~ .upline-menu__drawer {
		/*
		 * Column, not the collapse grid.
		 *
		 * The collapse is `grid-template-rows: 0fr -> 1fr` on a drawer with one
		 * child. Adding the close control made two, so the label took the 1fr
		 * row and the entire menu was laid out beneath it -- pane one starting
		 * 580px down an 812px screen. The collapse has no work to do once the
		 * drawer is a fixed full-screen box, so the open state stops being a
		 * grid and becomes what it actually is: a control, then the menu.
		 */
		display: flex;
		flex-direction: column;
		position: fixed;
		top: var(--upline-drawer-top, 0px);
		right: 0;
		/*
		 * Term 7's whole purpose is the 2f phone frame: a drawer above a
		 * persistent bottom bar. It was consumed at >=761 only, so on the one
		 * width the token exists for, the drawer covered the bar it was meant
		 * to sit on top of. 0 by default, so nothing moves for anyone else.
		 */
		bottom: var(--upline-drawer-bottom, 0px);
		left: 0;
		z-index: 100;
		overflow-y: auto;
		background: var(--upline-panel-bg);
		color: var(--upline-panel-fg);
	}

	/*
	 * The drawer's own way out, top-left, per state one of the drawing.
	 *
	 * Same surface tokens as everything else in here: the drawer and the
	 * desktop mega panel are one colour identity, so a theme cannot hand a shop
	 * two different menus by styling one of them.
	 */
	.upline-menu--drawer .upline-menu__close {
		display: none;
		/*
		 * 0 0 auto, because its only content is an absolutely positioned icon:
		 * as a shrinkable flex item its content height is zero, so it collapsed
		 * to 44x0 -- the X still painted, and the tap target it painted over did
		 * not exist. A close control that looks present and cannot be pressed is
		 * the lockout wearing its third disguise in this file.
		 */
		flex: 0 0 auto;
		position: relative;
		z-index: 1;
		width: 44px;
		height: 44px;
		margin: 0.6em 0 0.4em 0.55em;
		cursor: pointer;
		color: inherit;
	}

	.upline-menu--drawer .upline-menu__switch:checked ~ .upline-menu__drawer .upline-menu__close {
		display: block;
	}

	/*
	 * The same bar the toggle icon is built from.
	 *
	 * Both icons are two-pixel rules of `currentColor`; the hamburger's are
	 * 1.1em and the X's were 19px, so on a theme with a larger type scale the
	 * control that opens the drawer grew and the one that closes it did not.
	 * A theme styling one and finding the other unmoved is the kind of
	 * inconsistency that reads as a bug in the plugin rather than a difference
	 * of units, which matters now that both are designated stylable surfaces.
	 *
	 * Same metric, same primitive, one lever.
	 */
	/*
	 * A box, not a bar -- so a theme can paint it with one mask.
	 *
	 * 1.26.1 put this icon on the toggle's 1.1em metric, which fixed the
	 * length but not the construction: the span WAS one arm of the X, 16.5x2
	 * and rotated 45deg, with the second arm on ::before. Measured in the
	 * served drawer it came out 13.1x13.1 against the toggle's and magnifier's
	 * 20x20. A mask on a rotated 16.5x2 strip cannot show a glyph, so theming
	 * this one icon meant five overrides that reconstruct it -- box, un-rotate,
	 * re-centre, repaint through a mask, suppress the stray arm -- where the
	 * other two take two lines.
	 *
	 * The magnifier is the pattern: a 1.1em square whose marks are drawn by its
	 * pseudo-elements, so `background: currentColor` plus a mask replaces the
	 * whole glyph and the drawn marks are clipped away with it. The span itself
	 * now carries no rotation and no paint -- it is just the box.
	 */
	.upline-menu--drawer .upline-menu__close-icon {
		display: block;
		position: absolute;
		top: 50%;
		left: 50%;
		width: 1.1em;
		height: 1.1em;
		transform: translate(-50%, -50%);
	}

	.upline-menu--drawer .upline-menu__close-icon::before,
	.upline-menu--drawer .upline-menu__close-icon::after {
		content: "";
		position: absolute;
		top: 50%;
		left: 0;
		width: 100%;
		height: 2px;
		background: currentColor;
	}

	.upline-menu--drawer .upline-menu__close-icon::before {
		transform: translateY(-50%) rotate(45deg);
	}

	.upline-menu--drawer .upline-menu__close-icon::after {
		transform: translateY(-50%) rotate(-45deg);
	}

	/*
	 * The checkbox goes where the X is.
	 *
	 * A <label> cannot take focus, so the X on its own is a mouse-only control.
	 * The checkbox is the real control and is already the first focusable thing
	 * in the nav -- it is simply parked under a drawer that now covers the whole
	 * screen. Moving it onto the X keeps one control doing one job: Tab reaches
	 * it, Space closes, the ring is drawn where the shopper is looking, and none
	 * of it needs the script.
	 *
	 * Still invisible, still 44px, still the same checkbox: this is a change of
	 * address, not a second control to keep in step with the first.
	 */
	.upline-menu--drawer .upline-menu__switch:checked {
		position: fixed;
		top: 0.6em;
		left: 0.55em;
		z-index: 101;
		width: 44px;
		height: 44px;
		clip: auto;
		clip-path: inset(50%);
	}

	.upline-menu--drawer .upline-menu__switch:checked:focus-visible ~ .upline-menu__drawer .upline-menu__close {
		outline: 2px solid var(--upline-accent);
		outline-offset: 2px;
	}

	/*
	 * The surface is right with or without the script; only the full-screen box
	 * depends on it. Scoped to --panes because --upline-drawer-top is measured
	 * by the script: with no script the token is unset, `top` falls back to 0,
	 * and the drawer covers its own toggle -- a menu that opens and cannot be
	 * closed. The no-JS path keeps the in-flow stacked list it always had.
	 */
	.upline-menu--drawer .upline-menu__switch:checked ~ .upline-menu__drawer {
		background: var(--upline-panel-bg);
		color: var(--upline-panel-fg);
	}

	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-menu__bar {
		/*
		 * Pane one flows normally; the panel is lifted out and placed beside
		 * it. Three attempts put all three panes in one grid with every
		 * wrapper set to `display: contents`, and each failed the same way:
		 * one grid has ONE vertical flow, so pane one's five 44px tap targets
		 * and pane two's list had to share rows. First that opened a 100px
		 * hole in a list meant to read as continuous; then making the panel
		 * span every row pushed pane one below it entirely.
		 *
		 * Two flows need two boxes. This is the cheap version of that.
		 */
		display: block;
		position: relative;
		padding-right: 0;
	}

	/*
	 * The height belongs to the OPEN drawer only.
	 *
	 * As a plain `min-height` on the bar it beat the collapse's `min-height: 0`
	 * -- same specificity, later in the file -- so the grid row could never
	 * reach 0fr and the shut drawer occupied 60vh of every mobile page. It was
	 * invisible and out of the tab order, which is exactly why it survived:
	 * nothing looked wrong, there was simply a screen of nothing under the
	 * header.
	 *
	 * Measured on a live store at 390px: 506px tall while shut.
	 */
	.upline-menu--drawer .upline-menu__switch:checked ~ .upline-menu__drawer .upline-menu__bar {
		flex: 1 1 auto;
		/*
		 * 100%, not 60vh: the drawer is now a fixed box that already ends at
		 * the viewport bottom, so the bar fills its parent rather than naming a
		 * viewport fraction of its own. The 60vh version is what occupied the
		 * page while SHUT in 1.23.0 -- a height that belongs to the open state
		 * is safest expressed as "all of the thing that is only there when
		 * open".
		 */
		/*
		 * Was `min-height: 100%; height: 100%`, which made the bar as tall as
		 * the whole panel. That was right while the bar was the only thing in
		 * it, and wrong the moment the drawer grew a footer: the bar took the
		 * full height and pushed the footer past the bottom edge -- measured on
		 * bikeco at y933.7 in an 844 viewport, reached only by scrolling the
		 * whole drawer.
		 *
		 * The drawer is a flex column, so the bar claims what is left after
		 * the heading and the footer and scrolls inside itself. `min-height: 0`
		 * is what lets it: a flex item will not shrink below its content
		 * without it, which is the usual reason a "scrolling" child scrolls
		 * its parent instead.
		 *
		 * The panes still get their height from the bar -- they are positioned
		 * against it -- and now that height stops above the footer, which is
		 * where a pane should stop anyway.
		 */
		flex: 1 1 auto;
		min-height: 0;
		overflow-y: auto;
	}

	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-menu__bar > .upline-menu__item {
		/*
		 * Static, so the panel positions against the BAR.
		 *
		 * The dropdown layout makes each parent item `position: relative` so a
		 * desktop dropdown hangs off its own row, and that rule carries no
		 * breakpoint. In panes mode it made the item the panel's containing
		 * block, so `left: 30%` resolved against a 117px list item instead of
		 * the 390px bar and the panel landed on top of pane one. Measured at
		 * 390px: left computed to 35px.
		 *
		 * The mega layout never hit this because it does not position its
		 * items -- which is why a bug in the shared drill-down only showed on
		 * one of the two layouts that use it.
		 */
		position: static;
		display: block;
		width: var(--upline-pane-one-w);
	}

	/*
	 * The stated rule: a level-one label wider than its column truncates
	 * rather than running under pane two.
	 *
	 * Authored here on purpose. The drawer layout's copy of this block is
	 * generated from it by tools/publish-drawer-css.py, and my first attempt
	 * added this to the generated half -- which the drift gate refused, which
	 * is exactly what it is for. The duplication I took for a defect is a
	 * mirror with a guard on it.
	 *
	 * A column sized to its longest label would be better and is not reachable
	 * from here: the pane is absolutely positioned inside its own list item, so
	 * sizing the column to the content means restructuring the bar into a
	 * shared grid, which needs `display: contents` on the list items and takes
	 * the list's own semantics with it. That is an accessibility trade on a
	 * navigation list and it wants a rendered look before it is made.
	 */
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-menu__bar > .upline-menu__item > .upline-menu__link {
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}

	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-menu__bar > .upline-menu__item > .upline-menu__link {
		display: block;
		border-bottom: 0;
		font-weight: var(--upline-menu-bar-weight);
		/* Pane one is now against the viewport edge, so it needs the inset the
		 * theme's container used to give it. */
		padding: 0.62em 1.1em;
		text-transform: var(--upline-menu-bar-case);
		letter-spacing: var(--upline-menu-bar-tracking);
		font-size: var(--upline-menu-bar-size);
	}

	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-menu__item.is-open > .upline-menu__link {
		text-decoration: underline;
		text-decoration-color: var(--upline-accent);
		text-decoration-thickness: 2px;
		text-underline-offset: 5px;
	}

	/* Panes two and three, beside pane one, with their own vertical flow. */
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega {
		position: absolute;
		left: var(--upline-pane-one-w);
		right: 0;
		top: 0;
		bottom: 0;
		display: grid;
		/*
		 * 58%, not 45%. The panel starts at 30% of the drawer, so 45% of what
		 * is left capped pane two at 118px and "SHOP BY BRAND" wrapped -- with
		 * the active entry's accent underline drawn through both lines, which
		 * reads as a defect rather than as emphasis. fit-content still shrinks
		 * to the content, so a pane of short words does not take the room.
		 */
		/*
		 * Every track able to reach zero, so the row cannot sum past the pane.
		 *
		 * It was `fit-content(58%) 1px minmax(6rem, 1fr)` and both ends had a
		 * floor: `fit-content()` never shrinks below min-content, so one long
		 * word takes more than its 58%, and `6rem` is a hard 96px. Measured on
		 * range at 1440: 143.82 + 1 + 96 = 240.8 inside a 201.6 pane, with the
		 * third step clipped by the pane's own `overflow: auto` -- "Wax"
		 * surviving at 25.9px and "Freestyle" cut.
		 *
		 * `minmax(0, …)` removes the automatic minimum, which is the only thing
		 * that lets a grid track go narrower than its content rather than
		 * overflow its container.
		 */
		/*
		 * `minmax(0, 58%)`, not `minmax(0, fit-content(58%))`.
		 *
		 * `fit-content()` is not a <track-breadth> and may not appear inside
		 * `minmax()`. 1.53.0 shipped it, the whole declaration was invalid, the
		 * browser dropped it, and the pane fell back to `.upline-mega--cols-N`
		 * -- two tracks where the placement expects three. `grid-column: 3` on
		 * an open level-3 list then created an implicit track after the
		 * explicit two, which is why the third step rendered at the drawer's
		 * right edge and scrolled the pane sideways when focused: Ben's "step
		 * two disappears and no 3 appears".
		 *
		 * The percentage does the capping and the 0 minimum does the shrinking,
		 * which is what `fit-content()` was there for; what it loses is sizing
		 * to content below the cap, and a `1fr` third track takes that room
		 * back.
		 *
		 * Not caught here, and could not have been: this suite matches text and
		 * the text was exactly what was intended. Only a browser knows the
		 * declaration is invalid.
		 */
		grid-template-columns: minmax(0, 58%) 1px minmax(0, 1fr);
		align-content: start;
		grid-auto-rows: min-content;
		column-gap: 0;
		/*
		 * row-gap too, not just column-gap. The desktop panel's `gap` survived
		 * the override and left 16px between every pane-two entry, which with a
		 * rule under each entry reads as the rule floating loose of the thing it
		 * belongs to. Entries meet; the rule is the join.
		 */
		row-gap: 0;
		border-left: 1px solid var(--upline-rule);
		padding: 0;
		overflow-y: auto;
	}

	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-menu__item:not(.is-open) .upline-mega {
		display: none;
	}

	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega::before {
		content: "";
		grid-column: 2;
		grid-row: 1 / -1;
		background: var(--upline-rule);
	}

	/*
	 * --upline-pane-rule was declared here and consumed by nothing. It served
	 * the drawer's horizontal entry rules, which came out in 1.27.2 when the
	 * spec drawings turned out to have no horizontal rules at all; the token
	 * outlived them and sat declared and unread across three releases with the
	 * suite green. Removed in 1.29.0, along with the blind spot: the contract
	 * is now asserted in the consumed direction too, not only published-and-read.
	 */
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega__col {
		display: contents;
	}

	/*
	 * One rule per pane-two entry, the same rule on every one of them.
	 *
	 * Ben: "there un expected lines kinda all over the place." They were the
	 * desktop panel's own borders showing through -- a border under each
	 * heading sized to the heading's text column, a border over the footer, and
	 * a 1.1em top margin on any heading that happened to be second in its
	 * column. Three decorations designed for a wide multi-column panel, all
	 * landing in a narrow drill-down at whatever width the column gave them.
	 *
	 * In panes mode the entry styling replaces them wholesale rather than
	 * patching each one, so nothing from the desktop panel leaks through.
	 */
	/*
	 * No horizontal rules. The spec drawings have none at all: the only lines
	 * in the drawer are the thin continuous verticals between panes.
	 *
	 * 1.24.0 gave every pane-two entry a uniform border-bottom, on a reading of
	 * "there un expected lines kinda all over the place" that took the remedy to
	 * be tidier lines. The drawings say the remedy was fewer. Entry rhythm is
	 * spacing now, which is what the drawing actually shows.
	 *
	 * Pane two is caps bold against pane one's caps -- the middle of the three
	 * weights the drawings step through.
	 */
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega__heading {
		grid-column: 1;
		margin: 0;
		padding: 0.62em 1.1em;
		border-bottom: 0;
		cursor: pointer;

		/*
		 * !important audit: stays, and it is the layer asymmetry rather than a
		 * theme doing anything wrong.
		 *
		 * A pane-two entry is an <h4>. Everything this plugin ships sits in
		 * @layer upline, and a layered normal declaration loses to an unlayered
		 * one at any specificity -- so every theme that styles h4, which is
		 * every theme, sets the size and weight of these entries. Measured on
		 * Twenty Twenty-Five: 16.1px at weight 400 where this asks for 11.8px
		 * at 700. text-transform survived only because the theme happens not to
		 * set it.
		 *
		 * At drawer width that is not a matter of taste. Pane two is about
		 * 150px wide; a 16px heading wraps nearly every multi-word entry and
		 * the pane's rhythm goes with it. The three weights the spec drawings
		 * step through -- pane one caps, pane two caps bold, pane three title
		 * case lighter -- are how a shopper reads which pane they are in.
		 *
		 * Scoped to the drawer only. The desktop panel keeps taking the theme's
		 * heading type, where there is room for it.
		 *
		 * ---- withdrawn in 1.54.0 ----
		 *
		 * Two things were wrong here, and the second is why this is quoted
		 * rather than deleted.
		 *
		 * The ruling: an important declaration in a layer beats an unlayered
		 * one, and unlayered counts as the last layer, so against these a theme
		 * had no route at all -- not normal, not important, not a deeper
		 * selector. The comment called the guard "structure, not identity", and
		 * 0.74rem / 700 / uppercase / 0.04em is identity. It is a token now.
		 *
		 * The defect: font-size was never applying anyway. A second comment had
		 * lost its opening delimiter, so six lines of prose sat loose inside
		 * the declaration block, and CSS error recovery runs to the next
		 * semicolon -- which was the end of the font-size declaration. It was
		 * dropped with the prose. font-weight and text-transform survived
		 * because they sat after the resync point. Measured on range: the
		 * heading computed 18px, not the 11.84px this asked for, through every
		 * release that carried the comment explaining why it must not lose.
		 *
		 * Nothing textual could have caught it. The suite matched the
		 * declaration, and the declaration was there.
		 */
		font-size: var(--upline-menu-label-size);
		font-weight: var(--upline-menu-label-weight);
		text-transform: var(--upline-menu-label-case);
		letter-spacing: var(--upline-menu-label-tracking);
	}

	/*
	 * Explicitly beaten rather than left to source order: the desktop rule is
	 * `.upline-mega__col > .upline-mega__heading:not(:first-child)` at (0,3,0),
	 * and the panes rule above is (0,3,0) too, so which margin applied was
	 * decided by which sat later in this file. TESTS.md 7n.
	 */
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega__col > .upline-mega__heading:not(:first-child) {
		margin-top: 0;
	}

	.upline-menu.upline-menu--panes.upline-menu--drawer [data-upline-entry].is-open {
		text-decoration: underline;
		text-decoration-color: var(--upline-accent);
		text-decoration-thickness: 2px;
		text-underline-offset: 5px;
	}

	/* ---- a panel with no columns -------------------------------------
	 *
	 * The mega layout's fallback for a category that does not earn a composed
	 * panel, and every non-mega layout: both are a nested <ul> from
	 * plain_list() with no .upline-mega__col anywhere. The rules below assumed
	 * columns, so every list in such a panel -- the top-level one and each
	 * nested one -- matched the pane-three rule and stacked in its single cell,
	 * drawn over each other beside pane one. Measured on bike co.'s GEAR at
	 * 390px: two lists, both at y=187, in a panel 44px tall because everything
	 * in it was out of flow.
	 *
	 * `display: contents` on the list and its items, so the anchor and its
	 * sub-list become grid items in their own right -- the same mechanism
	 * .upline-mega__col already uses, one level further in.
	 * ------------------------------------------------------------------ */
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega > .upline-mega__list,
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega > .upline-mega__list > li {
		display: contents;
	}

	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega > .upline-mega__list > li > a {
		grid-column: 1;
		margin: 0;
	}

	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega > .upline-mega__list > li > .upline-mega__list {
		grid-column: 3;
		grid-row: 1;
		position: absolute;
		top: 0;
		bottom: 0;
		overflow-y: auto;
		margin: 0;
		padding: 0.4em 1em;
		font-size: 0.82rem;
		overflow-wrap: break-word;
	}

	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega > .upline-mega__list > li > .upline-mega__list:not(.is-open) {
		display: none;
	}

	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega__col--headless > .upline-mega__list {
		grid-column: 1;
		/* Pane two is in flow; only the leaves are lifted out of it. */
		position: static;
		/*
		 * Explicitly auto: the pane-three rule below pins every list to row 1
		 * so the leaves start at the top, and a headless list inherited that
		 * and drew both subcategory groups on top of each other.
		 */
		grid-row: auto;
		font-size: 0.8rem;
		margin: 0;
		padding: 0;
	}

	/*
	 * A headless column's items ARE pane-two entries, so they match them --
	 * and since 1.54.4 they match them by being in the same rule rather than
	 * by carrying a copy of its declarations. Two rules that agree today are
	 * not one rule that means it, and the release gate refused the tie.
	 */
	/*
	 * One owner for the label register.
	 *
	 * These two selectors are the same weight and both used to set the same
	 * four type properties, so which one won was decided by which sat lower in
	 * the file. The release gate refused it, correctly: a headless column's
	 * items ARE pane-two entries, and two rules agreeing today is not the same
	 * as one rule meaning it.
	 */
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega > .upline-mega__list > li > a,
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega__col--headless > .upline-mega__list > li > a {
		font-size: var(--upline-menu-label-size);
		font-weight: var(--upline-menu-label-weight);
		text-transform: var(--upline-menu-label-case);
		letter-spacing: var(--upline-menu-label-tracking);
	}

	/* ----------------------------------------------------------------
	 * Every entry is a link the whole way across its row.
	 *
	 * The drill-down's headings were padded on the `<h4>` and the anchor
	 * inside stayed inline: a 107x16 link in a 35.7px row, so most of the row
	 * was the heading's padding. A click there is not a miss -- it reaches the
	 * heading, does nothing, and (until this release) collapsed the open
	 * category on the way, which is what Ben was hitting. The same shape held
	 * for the leaves.
	 *
	 * So the padding moves to the anchor and the anchor fills the row, at the
	 * 44px tap floor. The rule a shopper can rely on afterwards: a click in a
	 * row is on that row's link, or it is on nothing.
	 * ---------------------------------------------------------------- */
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega__heading,
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega > .upline-mega__list > li,
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega__list li {
		padding: 0;
	}

	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega__heading > a,
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega__list li a,
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega > .upline-mega__list > li > a,
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega__col--headless > .upline-mega__list > li > a {
		display: flex;
		align-items: center;
		box-sizing: border-box;
		width: 100%;
		min-height: 44px;
		padding: 0.5em 1.1em;
	}

	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega__list {
		grid-column: 3;
		grid-row: 1;
		/*
		 * Absolutely positioned INTO its grid area, which is the point: an
		 * abspos grid item is placed in its area but contributes nothing to
		 * track sizing. In flow, the leaves' height set the first row's height
		 * and pushed the second group of subcategories 79px down the pane --
		 * the last survivor of "a pane must not resize its neighbour", one
		 * level down from where that law was learned.
		 */
		position: absolute;
		top: 0;
		bottom: 0;
		overflow-y: auto;
		font-size: 0.8rem;
		overflow-wrap: break-word;
		margin: 0;
		padding: 0.4em 1em;
	}

	/*
	 * Hidden unless THIS list is the open one. Keyed to the list rather than to
	 * its column, because a column can hold several heading+list pairs and
	 * keying to the column revealed all of them at once, stacked in the single
	 * pane-three cell. Ben: "loading each layer/subcategory on top its self."
	 *
	 * Headless columns are excluded: their list is pane-two content, always
	 * visible, and never a pane-three target.
	 */
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega__col:not(.upline-mega__col--headless) > .upline-mega__list:not(.is-open) {
		display: none;
	}

	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega__list li a {
		display: block;
		padding: 0.42em 0;
		text-transform: none;
		letter-spacing: 0;
		font-size: 0.82rem;
		font-weight: 400;
	}

	/*
	 * "Shop all X" is the way out of the drill-down, so it sits at the foot of
	 * the pane the shopper is reading rather than adrift under pane one, where
	 * it landed when it inherited the bar's first column.
	 *
	 * It is the last pane-two entry, so it wears the entry's rule -- under it,
	 * like the others. The border-TOP it used to carry was one of the lines
	 * Ben was seeing: the only edge in the pane that sat above its label.
	 */
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega__footer {
		grid-column: 1;
		margin: 0;
		padding: 0.62em 1.1em;
		border-top: 0;
		border-bottom: 0;
	}

	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega__footer .upline-mega__all {
		text-transform: uppercase;
		letter-spacing: 0.04em;
		font-size: 0.74rem;
		font-weight: 700;
	}
/* upline:generated drawer-layout END */

/* ----------------------------------------------------------------------
 * Everything below was outside @layer upline until 1.53.1.
 *
 * It was appended to the end of the file across several releases -- the desk
 * drawer and its scrim, the sidebar, the contact list reset, the widened
 * three-step state -- and the layer had already closed. Unlayered rules beat
 * layered ones, so these overrode a theme that did exactly what the tokens doc
 * tells it to do, and no selector a skin could write would have won. The
 * promise "you never need a deep selector" was false for every surface added
 * since 1.49.2.
 *
 * Found by asking which element carries --upline-drawer-live-w and reading the
 * context rather than assuming it.
 * ------------------------------------------------------------------- */

/* ----------------------------------------------------------------------
 * The contact block's link list.
 *
 * It is a <ul> and nothing reset it, so it arrived with the user agent's own
 * disc bullets, 40px of inline padding and 16px of block margin -- 91x44 of
 * content in a 44px header row. Every other list this plugin renders is reset;
 * this one was written after those rules and matched none of their selectors.
 * ------------------------------------------------------------------- */

/*
 * The other-locations line is a desk affordance.
 *
 * It is the fact a locator link would carry, and below the breakpoint the
 * header has no room to carry it beside an address and a phone -- which is
 * where it ran into the address and read as part of it. Hidden rather than
 * shrunk: a line that only half fits is worse than one that waits.
 */
@media (max-width: 760px) {
	.upline-contact__branches {
		display: none;
	}
}

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

.upline-contact__links > li {
	margin: 0;
}

/* ----------------------------------------------------------------------
 * The drawer on a desk, and the scrim that closes it.
 *
 * P13, open since 1.31.0. The drawer layout was full-viewport below 760 and an
 * in-flow collapse above it, and 2a's frame draws neither: a panel down the
 * left with the page dimmed behind it. So the desk case had no geometry at all
 * rather than the wrong geometry, which is why nothing looked broken.
 *
 * One geometry serves three options. `drawer`, `push` and `bottom-bar` all use
 * the drawer layout; what differs is the scrim and the bottom inset, and both
 * are keyed off the body class the plugin already emits for the option. No new
 * attribute, no second source of truth about which header a shop chose.
 *
 * Fixed from inside the header part, which is the same mechanism the mobile
 * drawer has used since 1.29: a theme whose header is a containing block for
 * fixed descendants already breaks that, so the constraint is not new.
 * ------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   A nested list reads as a level
   --------------------------------------------------------------------------

   The dropdown's panel nests correctly and drew flat: every depth matched
   `.upline-mega__list li a`, so a grandchild was typographically identical to
   its parent and three levels came out at one rank. Measured on range:
   Snowboarding's panel listed Maintenance, Wax, Snowboard Bindings, Step In,
   Snowboards and Freestyle side by side.

   A parent takes the label register -- the same four tokens the drill-down
   headings use -- and its children stay in the leaf register, indented and
   tied to it by a rule. No new tokens: the registers already say which voice
   a structural label speaks in, and this is one.

   Not in panes mode. There a nested list is pane three and is positioned into
   its own column; indenting it there would be drawing the hierarchy twice.
   -------------------------------------------------------------------------- */
.upline-menu:not(.upline-menu--panes) .upline-mega__group > a {
	font-size: var(--upline-menu-label-size);
	font-weight: var(--upline-menu-label-weight);
	text-transform: var(--upline-menu-label-case);
	letter-spacing: var(--upline-menu-label-tracking);
}

.upline-menu:not(.upline-menu--panes) .upline-mega__group > .upline-mega__list {
	margin-block: 0.2em 0.6em;
	margin-inline-start: 0.35em;
	padding-inline-start: 0.75em;
	border-inline-start: 1px solid var(--upline-rule);
}

/* --------------------------------------------------------------------------
   The drawer's heading and its footer
   --------------------------------------------------------------------------

   Both are the drawer's own furniture: they sit in its flow, above and below
   the bar, and neither is inside the element that translates when the pane
   pair slides.

   The footer takes `margin-block-start: auto` so it falls to the foot of the
   panel on a short category list and is simply pushed down by a long one --
   rather than being pinned, which would have it overlap the categories a
   shopper is still scrolling.
   -------------------------------------------------------------------------- */
.upline-menu__heading {
	margin: 0;
	padding: 0.62em 1.1em;
	font-size: var(--upline-menu-label-size);
	font-weight: var(--upline-menu-label-weight);
	text-transform: var(--upline-menu-label-case);
	letter-spacing: var(--upline-menu-label-tracking);
}

/*
 * Only inside a panel. The heading is emitted next to the close control, which
 * exists only on a layout that collapses -- but the element is in the markup
 * at every width, and on a desk mega menu there is no panel for it to head.
 */
.upline-menu:not(.upline-menu--drawer) .upline-menu__heading {
	display: none;
}

.upline-menu__footer {
	margin-block-start: auto;
	padding: 1.1em;
	border-block-start: 1px solid var(--upline-rule);
	display: flex;
	flex-direction: column;
	gap: var(--upline-gap-sm);
}

.upline-menu__footer .upline-contact {
	margin: 0;
}

/* --------------------------------------------------------------------------
   The phone drawer slides too -- 2a, below the breakpoint
   --------------------------------------------------------------------------

   Measured on bikeco at 390: 30ms after the switch, the panel had already gone
   `display: grid -> flex` and `position: static -> fixed`, 0x0 -> 390x844, with
   the transform unchanged. Neither `display` nor `position` can be
   transitioned, so the declared transform and opacity transition never ran --
   the panel appeared, it did not arrive. Same shape as the desk before 1.54.3.

   Scoped to the drawer layout on purpose. `.upline-menu__drawer` is the shared
   collapsing panel for EVERY layout below 761 -- mega, dropdown and list all
   emit it -- so making it fixed unscoped would turn every phone menu into a
   full-screen panel, which is not what was ordered and not what those layouts
   are.

   Written after the generated region rather than inside the mobile block it
   mirrors, so the generator keeps one source and does not emit a second copy
   of rules that are already scoped to the drawer.
   -------------------------------------------------------------------------- */
@media (max-width: 760px) {

	.upline-menu--drawer .upline-menu__drawer {
		position: fixed;
		top: 0;
		bottom: var(--upline-drawer-bottom, 0px);
		inset-inline-start: 0;
		z-index: 101;

		/*
		 * `inline-size`, not `width`: the shared mobile rule zeroes the
		 * inline size when shut and this has to beat that property by name.
		 */
		inline-size: 100vw;

		display: flex;
		flex-direction: column;
		grid-template-rows: none;
		overflow-y: auto;
		overflow-x: clip;

		/*
		 * One state, two positions. Opacity is pinned so the shared rule's
		 * fade cannot run alongside the slide and read as a second gesture.
		 */
		opacity: 1;
		transform: translateX(-100%);
		visibility: hidden;

		transition:
			transform var(--upline-menu-slide-duration) var(--upline-menu-slide-easing),
			visibility 0s linear var(--upline-menu-slide-duration);
	}

	.upline-menu--drawer .upline-menu__switch:checked ~ .upline-menu__drawer,
	.upline-menu--drawer .upline-menu__drawer[data-upline-open] {
		transform: translateX(0);
		visibility: visible;
		opacity: 1;

		transition:
			transform var(--upline-menu-slide-duration) var(--upline-menu-slide-easing),
			visibility 0s linear 0s;
	}

	/*
	 * Push, on a phone: narrower than the viewport, so the page it pushed is
	 * still visibly there.
	 *
	 * The drawings give it ~85%. Every other option covers the screen at this
	 * width and should: a drawer over a dimmed page is a modal surface and the
	 * scrim says so. Push has no scrim -- a pushed page is still being read --
	 * so a panel at the full width would have shoved the page out of sight
	 * while telling the shopper it was only moved aside. The visible edge IS
	 * the affordance.
	 *
	 * Written as a `max-inline-size` beside the `inline-size` above rather
	 * than replacing it, so the shared rule stays the one place the width is
	 * decided and this only caps it.
	 */
	/* ----------------------------------------------------------------
	 * The phone pane is the drawer, not 44% of it.
	 *
	 * `--upline-pane-one-w` is the DESK pair's proportion -- it is the width
	 * of the left pane when two are exposed side by side, and it is also the
	 * distance the pair slides. Below the breakpoint there is no pair: the
	 * drill-down replaces one pane with the next. Applying the token here put
	 * level-one rows at 171.6px inside a 390px drawer with their dividers
	 * ending at x171, which is a desk measurement deciding a phone layout.
	 *
	 * Measured on bikeco at 390 and reproduced here. The token is untouched;
	 * what changes is that the phone stops reading it.
	 * ---------------------------------------------------------------- */
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-menu__bar > .upline-menu__item {
		width: 100%;
	}

	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega {
		inset-inline: 0;
		width: 100%;
		grid-template-columns: minmax(0, 1fr);

		/*
		 * Opaque, because it covers rather than sits beside.
		 *
		 * The panel is transparent by default, which is right when it has its
		 * own column to live in. At this width it is laid over the category
		 * list, and a transparent cover is not a cover: 1.55.5 shipped both
		 * legible at the same position.
		 */
		background: var(--upline-panel-bg);
		color: var(--upline-panel-fg);
	}

	/* ----------------------------------------------------------------
	 * Opening a parent replaces the list, it does not cover it.
	 *
	 * 1.55.5 gave the pane the drawer's full width -- correct, the phone has
	 * room for one pane -- and left the category list exactly where it was, so
	 * the two were drawn over each other and both were readable. Before that
	 * the pane sat at 44% beside the list: cramped, and not this.
	 *
	 * The LINKS slide, not the bar. The pane is a descendant of the open list
	 * item, so translating the bar would carry the pane out with it -- which
	 * is why the desk's slide works there and could not simply be reused here.
	 * Moving each row's link leaves the pane where it is and takes the list
	 * off the edge, which is the thing that has to happen.
	 *
	 * Keyed on the step the script publishes rather than `:has()`, which would
	 * raise the browser floor for a state that cannot exist without the script
	 * anyway.
	 * ---------------------------------------------------------------- */
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-menu__bar > .upline-menu__item > .upline-menu__link {
		transition: transform var(--upline-menu-slide-duration) var(--upline-menu-slide-easing);
	}

	.upline-menu.upline-menu--panes.upline-menu--drawer[data-upline-step] .upline-menu__bar > .upline-menu__item > .upline-menu__link {
		transform: translateX(-100%);
	}

	/*
	 * And the way back, which at this width is the only one: the category list
	 * is off the edge, so without this a shopper is in a category with no exit
	 * but the X.
	 */
	.upline-menu.upline-menu--panes.upline-menu--drawer[data-upline-step] .upline-menu__back {
		display: flex;
		align-items: center;
		gap: 0.4em;
		width: 100%;
		box-sizing: border-box;
		min-height: var(--upline-tap, 44px);
		padding: 0.5em 1.1em;
		border: 0;
		background: none;
		color: inherit;
		font: inherit;
		font-size: var(--upline-menu-label-size);
		font-weight: var(--upline-menu-label-weight);
		text-transform: var(--upline-menu-label-case);
		letter-spacing: var(--upline-menu-label-tracking);
		text-align: start;
		cursor: pointer;
	}

	@media (prefers-reduced-motion: reduce) {
		.upline-menu.upline-menu--panes.upline-menu--drawer .upline-menu__bar > .upline-menu__item > .upline-menu__link {
			transition: none;
		}
	}

	/* ----------------------------------------------------------------
	 * The footer sits at the drawer's foot, and only the list scrolls.
	 *
	 * The drawer was already a flex column, but nothing claimed the free
	 * space, so the footer simply followed the list: measured at y933.7 in an
	 * 844 viewport with a five-item list, below the fold with the whole drawer
	 * scrolling to reach it. The drawing pins it.
	 *
	 * So the bar takes the free space and scrolls inside itself, and the
	 * footer is last and does not shrink. `min-height: 0` because a flex item
	 * will not shrink below its content without it, which is the usual reason
	 * a "scrolling" flex child scrolls its parent instead.
	 * ---------------------------------------------------------------- */
	.upline-menu--drawer .upline-menu__drawer {
		/* Only the list scrolls; the footer is furniture, not content. */
		overflow-y: hidden;
	}

	.upline-menu--drawer .upline-menu__drawer > .upline-menu__footer {
		flex: 0 0 auto;
		margin-block-start: 0;
	}

	body.upline-shell--push .upline-menu--drawer .upline-menu__drawer {
		max-inline-size: 85vw;
	}


	@media (prefers-reduced-motion: reduce) {
		.upline-menu--drawer .upline-menu__drawer {
			transition: none;
		}
	}
}

@media (min-width: 761px) {

	/*
	 * The panel. Full height, down the inline-start edge, above the scrim.
	 *
	 * `--upline-drawer-bottom` is new in this release and 0 by default, so this
	 * is a full-height panel everywhere except a shop whose theme sets it --
	 * which is what the bottom-bar option's pattern does, to keep the panel off
	 * its own fixed bar.
	 */
	/*
	 * The drawer does not widen. Retired in 1.54.0 on Ben's ruling, from a
	 * rendered read: "it opens all three layers, opens full width of the menu,
	 * not the screen, and then splits that menu as it opens sections -- it's
	 * pretty disorienting."
	 *
	 * Three panes was the defect, and widening was the accommodation for it.
	 * Two panes are exposed at every step and the third arrives by sliding the
	 * pair, so there is nothing left for a width to make room for.
	 *
	 * `--upline-drawer-live-w` stays published and stays equal to the token:
	 * the theme reads it, and a constant is a better contract than a value
	 * that changed under a state nobody outside this file could see.
	 */

	/*
	 * The closed desk drawer is out of flow, and that is structural now rather
	 * than incidental.
	 *
	 * It used to inherit the mobile collapse grid: `display: contents` at this
	 * width is (0,1,0) and loses to the unconditioned
	 * `.upline-menu--drawer .upline-menu__drawer` at (0,2,0), so the closed
	 * desk state WAS the phone's `grid-template-rows: 0fr`. That collapsed to
	 * nothing only because the bar was its only child. 1.54.0 put the back
	 * control beside the bar, a theme rule on that button gave it height, and
	 * the explicit 0fr row went to the back while the bar fell into an
	 * implicit `auto` row: rows `44px 220px`, a 264px drawer inside a closed
	 * header, on every desk drawer shop. Measured on bikeco at 397px of header
	 * and reproduced here exactly.
	 *
	 * So the panel is fixed and translated away whether it is open or shut,
	 * and opening moves it rather than growing it. A closed panel that is out
	 * of flow cannot be given height by anything inside it -- which is the
	 * property that was missing, not the row count.
	 */
	.upline-menu--drawer .upline-menu__drawer {
		position: fixed;
		top: 0;
		bottom: var(--upline-drawer-bottom, 0px);
		inset-inline-start: 0;
		width: min(var(--upline-drawer-live-w), 100vw);
		z-index: 101;

		display: flex;
		flex-direction: column;

		transform: translateX(-100%);
		visibility: hidden;
	}

	.upline-menu--drawer .upline-menu__switch:checked ~ .upline-menu__drawer,
	.upline-menu--drawer .upline-menu__drawer[data-upline-open] {
		transform: translateX(0);
		visibility: visible;
		position: fixed;
		top: 0;
		bottom: var(--upline-drawer-bottom, 0px);
		inset-inline-start: 0;
		/* One default, on :root. A second one here could disagree with it. */
		width: min(var(--upline-drawer-live-w), 100vw);
		z-index: 101;

		display: flex;
		flex-direction: column;
		overflow-y: auto;

		background: var(--upline-panel-bg);
		color: var(--upline-panel-fg);
	}

	/* ----------------------------------------------------------------
	 * Two panes, never three, and the third arrives by sliding.
	 *
	 * Geometry, because it is easier to check than to infer. `A` is
	 * `--upline-pane-one-w`; `B` is what is left of the drawer.
	 *
	 *   |<--- A --->|<------ B ------>|<--- A --->|
	 *   |  level 1  |     level 2     |  level 3  |
	 *      step 2 window: [   A + B   ]
	 *      step 3 window:       [   B + A   ]
	 *
	 * The mega is widened to the whole drawer and carries both remaining
	 * levels, so level three is simply off the right edge until the pair
	 * moves. One `translateX` on the bar moves both, because the mega is a
	 * descendant of the open list item.
	 *
	 * The transform is on the bar at EVERY step, `translateX(0)` included.
	 * A transformed element is the containing block for its absolutely
	 * positioned descendants, so applying it only at step three would move
	 * what the mega's `left` and `width` resolve against at the moment the
	 * motion starts -- a re-layout in the middle of a slide, which is the
	 * one thing the ruling asks this not to do.
	 * ---------------------------------------------------------------- */
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-menu__bar {
		transform: translateX(0);
		transition: transform var(--upline-menu-slide-duration) var(--upline-menu-slide-easing);
	}

	.upline-menu.upline-menu--panes.upline-menu--drawer[data-upline-step="3"] .upline-menu__bar {
		transform: translateX(calc(-1 * var(--upline-pane-one-w)));
	}

	/*
	 * Written at the generated copy's own specificity, not at the shape that
	 * reads most naturally.
	 *
	 * The drawer-then-panes pair without the base class is three classes
	 * and the generated drawer region carries
	 * `.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega`,
	 * which is four. The `right`/`width` half of this rule applied (nothing
	 * there to contest it) and the tracks silently did not: measured 208.219px
	 * where the model wants 201, which is 58% of 359 -- the old track, to the
	 * hundredth. A rule that half-applies looks like a maths error in the half
	 * that did.
	 */
	.upline-menu.upline-menu--panes.upline-menu--drawer .upline-mega {
		right: auto;
		width: 100%;
		grid-template-columns:
			minmax(0, calc(100% - var(--upline-pane-one-w)))
			1px
			minmax(0, 1fr);
	}

	/*
	 * Level three sits off the right edge until the pair moves, so the drawer
	 * clips instead of scrolling to it. `clip` rather than `hidden`: the
	 * drawer scrolls vertically, and `hidden` on one axis makes the box a
	 * scroll container on both.
	 */
	.upline-menu--drawer .upline-menu__switch:checked ~ .upline-menu__drawer,
	.upline-menu--drawer .upline-menu__drawer[data-upline-open] {
		overflow-x: clip;
	}

	/*
	 * The drawer's own open and close take the same easing, so the panel and
	 * the panes inside it read as one movement rather than two.
	 */
	.upline-menu--drawer .upline-menu__drawer {
		/*
		 * `visibility` is switched, not eased, and its timing is the point.
		 *
		 * The closed panel is `visibility: hidden` so that nothing inside a
		 * shut drawer is focusable or clickable. Flipped at once it would also
		 * delete the closing slide -- the panel would vanish instead of
		 * leaving. So closing holds visibility until the transform has run,
		 * and opening restores it immediately, which is the asymmetry the two
		 * declarations below encode.
		 */
		transition:
			transform var(--upline-menu-slide-duration) var(--upline-menu-slide-easing),
			visibility 0s linear var(--upline-menu-slide-duration);
	}

	.upline-menu--drawer .upline-menu__switch:checked ~ .upline-menu__drawer,
	.upline-menu--drawer .upline-menu__drawer[data-upline-open] {
		transition:
			transform var(--upline-menu-slide-duration) var(--upline-menu-slide-easing),
			visibility 0s linear 0s;
	}

	/*
	 * The back control. Present only when step one is off screen, which on
	 * desk is step three alone -- at step two the category list IS the left
	 * pane, so a control offering to return to it would be pointing at
	 * something already on the screen.
	 */
	.upline-menu--drawer .upline-menu__back {
		display: none;
	}

	.upline-menu.upline-menu--panes.upline-menu--drawer[data-upline-step="3"] .upline-menu__back {
		display: flex;
		align-items: center;
		gap: 0.4em;
		width: var(--upline-pane-one-w);
		box-sizing: border-box;
		padding: 0.62em 1.1em;
		border: 0;
		background: none;
		color: inherit;
		font: inherit;
		font-size: 0.74rem;
		font-weight: 600;
		text-transform: uppercase;
		letter-spacing: 0.06em;
		text-align: start;
		cursor: pointer;
	}

	/*
	 * The back chevron, drawn the way the other five are: the span is a 1.1em
	 * box with no paint of its own, and the two marks are pseudo-elements, so
	 * `background: currentColor` plus a mask on the span replaces the whole
	 * glyph and clips these away with it. That is what makes `iconBack` a slot
	 * rather than a special case.
	 */
	.upline-menu__back-icon {
		flex: none;
		position: relative;
		/* One system with the other designated icons. */
		width: 1.1em;
		height: 1.1em;
	}

	.upline-menu__back-icon::before,
	.upline-menu__back-icon::after {
		content: "";
		position: absolute;
		top: 50%;
		left: 26%;
		width: 52%;
		height: 2px;
		background: currentColor;
		transform-origin: left center;
	}

	.upline-menu__back-icon::before {
		transform: translateY(-50%) rotate(-45deg);
	}

	.upline-menu__back-icon::after {
		transform: translateY(-50%) rotate(45deg);
	}

	@media (prefers-reduced-motion: reduce) {
		/*
		 * Same end states, no motion between them. Written as `none` rather
		 * than a 1ms duration: a transition that still fires still emits
		 * `transitionend` and still shows a frame of travel.
		 */
		.upline-menu.upline-menu--panes.upline-menu--drawer .upline-menu__bar,
		.upline-menu--drawer .upline-menu__drawer {
			transition: none;
		}
	}

	/*
	 * The scrim sits under the panel and over the page. Present for `drawer`
	 * and `bottom-bar`; `push` suppresses it below, because a pushed page is
	 * still being read and dimming it would say otherwise.
	 */
	.upline-menu--drawer .upline-menu__scrim {
		display: none;
	}

	.upline-menu--drawer .upline-menu__switch:checked ~ .upline-menu__scrim {
		display: block;
		position: fixed;
		inset: 0;
		z-index: 100;
		background: var(--upline-scrim, rgba(0, 0, 0, 0.45));
		cursor: pointer;
	}

	body.upline-shell--push .upline-menu--drawer .upline-menu__switch:checked ~ .upline-menu__scrim {
		display: none;
	}
}

/*
 * Below the breakpoint the drawer is the full-screen box it already was, and
 * the scrim has nothing to dim -- there is no page visible behind it.
 */
@media (max-width: 760px) {

	.upline-menu__scrim {
		display: none;
	}
}

/* ----------------------------------------------------------------------
 * The sidebar option: a vertical menu that renders correctly at both of the
 * shell column's widths, knowing nothing about scroll.
 *
 * The theme's shell column is what narrows -- on its own scroll primitive,
 * with no script and nothing set across the seam. This half never learns that
 * it happened. It measures itself instead: a container query on the bar, so
 * the narrow state is a fact about the box the menu is actually in rather than
 * a class or a variable somebody else has to remember to set. Nothing here can
 * be silently unset, because there is nothing for anyone to set.
 *
 * Keyed on `body.upline-shell--sidebar`, which the plugin already emits for the
 * option. No second attribute: the shell class is the single source of truth
 * for which header a shop chose, and this is the same fact.
 *
 * The two widths and the threshold are published in contract/layouts.json
 * under header.options.sidebar.geometry. 120px below is `narrow_below` there,
 * and the two must move together.
 * ------------------------------------------------------------------- */

@media (min-width: 761px) {

	body.upline-shell--sidebar .upline-menu__bar {
		flex-direction: column;
		align-items: stretch;
		flex-wrap: nowrap;

		/* What makes the narrow state measurable rather than announced. */
		container-type: inline-size;
		container-name: upline-menu-bar;
	}

	body.upline-shell--sidebar .upline-menu__bar > .upline-menu__item {
		position: relative;
		width: 100%;
	}

	body.upline-shell--sidebar .upline-menu__bar > .upline-menu__item > .upline-menu__link {
		justify-content: flex-start;
		gap: 0.6em;
	}

	/*
	 * The chevron the frames draw on every parent. A pseudo-element rather than
	 * markup: it is decoration, it says nothing a screen reader needs, and the
	 * aria-expanded on the link already carries the state.
	 */
	body.upline-shell--sidebar .upline-menu__item--parent > .upline-menu__link::after {
		content: "\203A";
		margin-inline-start: auto;
		opacity: 0.6;
	}

	/*
	 * The panel opens BESIDE the item, not under it. This is the whole of what
	 * makes a sidebar a sidebar rather than a narrow dropdown.
	 */
	body.upline-shell--sidebar .upline-menu__item--parent > .upline-mega {
		top: 0;
		inset-inline-start: 100%;
		inset-inline-end: auto;
		min-width: 16rem;
	}

	/* The glyph is the rail's whole content, so it has a box at every width. */
	body.upline-shell--sidebar .upline-menu__glyph {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		flex: 0 0 auto;
		inline-size: 1.5em;
		block-size: 1.5em;
		overflow: hidden;
		border-radius: 0.2em;
		font-weight: 700;
		text-transform: uppercase;
		line-height: 1;
	}

	body.upline-shell--sidebar .upline-menu__glyph img {
		inline-size: 100%;
		block-size: 100%;
		object-fit: cover;
	}

	/*
	 * The narrow state. Measured, not told: when the bar's own inline size is
	 * under the published threshold the words go and the glyph stays, and the
	 * panel still opens beside on hover, focus or tap because it was never
	 * keyed on anything but the item.
	 */
	@container upline-menu-bar (max-width: 120px) {

		.upline-menu__label,
		.upline-menu__count {
			position: absolute;
			width: 1px;
			height: 1px;
			margin: -1px;
			padding: 0;
			overflow: hidden;
			clip-path: inset(50%);
			white-space: nowrap;
			border: 0;
		}

		/*
		 * The collapsed rail's hover label.
		 *
		 * The plugin hides the word, on a measurement only the plugin can
		 * make, so the plugin owes a way to read it -- a glyph with no name is
		 * not a navigation. The accessible name never went anywhere: the rule
		 * above clips, it does not remove. This is for a sighted pointer and
		 * nothing else.
		 *
		 * The same element, unhidden in place, rather than a tooltip beside
		 * it: one string, one accessible name, nothing to keep in step.
		 *
		 * Leaves only -- Ben's ruling from the drawing. A parent already opens
		 * its panel beside the rail on the same gesture and the panel is the
		 * parent's label; showing both would be two answers to one hover.
		 *
		 * Hover-capable pointers only. The rail is a >=761 arrangement and
		 * there is no tap-to-peek: on a touch screen the first tap would have
		 * to mean "show me the name" and the second "go there", which is the
		 * drill-down's bargain borrowed somewhere it was not agreed.
		 */
		@media (hover: hover) and (pointer: fine) {
			.upline-menu__bar > .upline-menu__item:not(.upline-menu__item--parent) > .upline-menu__link:hover > .upline-menu__label,
			.upline-menu__bar > .upline-menu__item:not(.upline-menu__item--parent) > .upline-menu__link:focus-visible > .upline-menu__label {
				position: absolute;
				inset-inline-start: 100%;
				inset-block-start: 50%;
				transform: translateY(-50%);
				z-index: 102;

				/* Undone in the same properties that hid it. */
				width: auto;
				height: auto;
				margin: 0;
				padding: 0.4em 0.7em;
				overflow: visible;
				clip-path: none;
				border: 0;

				white-space: nowrap;
				background: var(--upline-panel-bg);
				color: var(--upline-panel-fg);
				font-size: var(--upline-menu-bar-size);
				font-weight: var(--upline-menu-bar-weight);
				text-transform: var(--upline-menu-bar-case);
				letter-spacing: var(--upline-menu-bar-tracking);
				pointer-events: none;
			}
		}

		.upline-menu__item--parent > .upline-menu__link::after {
			content: none;
		}

		.upline-menu__bar > .upline-menu__item > .upline-menu__link {
			justify-content: center;
			padding-inline: 0;
		}
	}
}

}
