/* ==========================================================================
 * 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.
 * ------------------------------------------------------------------------ */

: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));

	/*
	 * 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
 * ------------------------------------------------------------------------ */

.upline-menu {
	position: relative;
}

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

.upline-menu__bar {
	display: flex;
	flex-wrap: wrap;
	border-top: 1px solid var(--upline-rule);
	border-bottom: 1px solid var(--upline-rule);
}

/*
 * 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;
	min-height: var(--upline-tap);
	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;
}

.upline-menu__toggle {
	display: none;
	align-items: center;
	gap: 0.6em;
	min-height: var(--upline-tap);
	padding: 0.6em 1em;
	border: 1px solid var(--upline-rule);
	border-radius: var(--upline-radius);
	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;
}

.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;

		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;
		border: 0;
	}

	.upline-menu__switch:checked ~ .upline-menu__drawer,
	.upline-menu__drawer[data-upline-open] {
		grid-template-rows: 1fr;
		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.
	 * ------------------------------------------------------------------ */

	.upline-menu.upline-menu--panes .upline-menu__drawer {
		--upline-pane-rule: 1px solid var(--upline-rule);
	}

	/*
	 * 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 is set by the script from the menu's own bottom edge,
	 * so the header row stays and the drawer owns everything below it. A theme
	 * that wants it aligned to a different edge sets the token.
	 */
	/*
	 * 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;
		bottom: 0;
		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".
		 */
		min-height: 100%;
		height: 100%;
	}

	.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: 30%;
	}

	.upline-menu.upline-menu--panes .upline-menu__bar > .upline-menu__item > .upline-menu__link {
		display: block;
		border-bottom: 0;
		font-weight: 600;
		/* 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: uppercase;
		letter-spacing: 0.06em;
		font-size: 0.74rem;
	}

	.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: 30%;
		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.
		 */
		grid-template-columns: fit-content(58%) 1px minmax(6rem, 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-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.
		 */
		 * Size, weight and case only. font-family is deliberately absent: a
		 * typeface is the skin's identity and has to arrive from the theme for
		 * free, so the guard defends the drill-down's structure and not its
		 * voice. letter-spacing is left unguarded for the same reason -- it
		 * reads as tracking, which is type design, not layout.
		 */
		font-size: 0.74rem !important;
		font-weight: 700 !important;
		text-transform: uppercase !important;
		letter-spacing: 0.04em;
	}

	/*
	 * 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;
		display: block;
		margin: 0;
		padding: 0.62em 1.1em;
		text-transform: uppercase;
		letter-spacing: 0.04em;
		font-size: 0.74rem;
		font-weight: 700;
	}

	.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 carry its rule. */
	/* A headless column's items ARE pane-two entries, so they match them. */
	.upline-menu.upline-menu--panes .upline-mega__col--headless > .upline-mega__list > li > a {
		display: block;
		padding: 0.62em 1.1em;
		text-transform: uppercase;
		letter-spacing: 0.04em;
		font-size: 0.74rem;
		font-weight: 700;
	}

	.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;
	}

	.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);
	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;
}
}
