/**
 * LightMega — Frontend styles (desktop)
 *
 * G7: desktop layout — full-width panel with a category sidebar (left) and a
 * product grid (right), replicating the client-approved Oudimmo mockup v4.
 * Loaded ONLY when a mega menu item is actually rendered (conditional enqueue,
 * G2); never globally. Mobile (accordion / slide-in) lands in G8; the desktop
 * panel is simply hidden below the breakpoint here.
 *
 * Design tokens are scoped to .lightmega-panel (not :root) so they never leak
 * into the theme and stay easy to override (basis for Pro theming).
 *
 * @package LightMega
 */

/* ─────────────────────────────────────────────────────────────
   Design tokens (scoped) + local reset

   Two families live here.

   FIXED tokens are internal: the plugin's palette and fonts. Not configurable
   in v1.

   CONFIGURABLE tokens map 1:1 onto LightMega_Settings parameters. The value
   declared here IS the product default, which is what makes the customization
   layer cost nothing: the panel's inline style attribute only ever carries the
   values that DIFFER from these, so a merchant who customizes nothing gets no
   attribute at all. Never move them to :root — scoping them to the element is
   what keeps them travelling with the panel when the JS portals it into <body>,
   and what stops them leaking into the theme.
   ───────────────────────────────────────────────────────────── */
/* `.lightmega-preview` (G11) is the editor's card preview. It is on this list so
   the preview draws its badges with THESE rules and THESE tokens rather than a
   copy of them: the badge palette used to live twice, once here and once in the
   editor's stylesheet, and the two had drifted apart on four colours of five.
   Since Badge-Color the palette is values in LightMega_Menu_Meta and the editor's
   presets and swatches sit in this scope too, for the neutral below. The wrapper
   matches nothing on the front end, and none of the panel's shell rules below
   select it — only the tokens travel. */
.lightmega-panel,
.lightmega-preview {
	/* ── Fixed ── */
	--lm-accent: #c8961e;
	/* Declared, not yet consumed — NOT a leftover to clean up. Kept in the accent
	   family on purpose: a --lm-gold-* sitting next to --lm-accent reads as a
	   second colour when there is only one.

	   --lm-accent-hover (#a87a10) was declared alongside it in G10a and REMOVED in
	   G10c-2, which is the block that was supposed to consume it. Two findings, both
	   checked rather than assumed. (a) The footer button is the only opaque accent
	   surface with a hover state, and its colour is CONFIGURABLE
	   (footer_button_color): a fixed darker gold would stay gold on a blue button,
	   so the hover is derived with brightness() and follows whatever the merchant
	   set. (b) Nothing else in the panel wanted it — all four hardcoded accent
	   literals (the sidebar tint 8%, the card shadow 18%, the arrow-disc shadow 35%,
	   the mobile row tint 8%) are ALPHA variants of the same accent, and what they
	   will need when accent_color becomes configurable is --lm-accent-rgb, a token
	   of a different shape. See the TODOs on those rules. */
	--lm-accent-light: #f0c84a;
	--lm-dark: #1a1a1a;
	--lm-white: #ffffff;
	--lm-off: #f7f5f0;
	--lm-border: #e0dbd0;
	--lm-muted: #777;
	--lm-text: #2c2c2c;
	/* Reserved column for the sidebar count. Internal, not a setting: it is used
	   TWICE — as the count's floor and as the description's right inset — and a
	   single declaration is what stops the two from drifting apart. */
	--lm-sidebar-count-width: 26px;
	/* The active-row indicator's width. Same reason as the token above, and the
	   same shape: it is used TWICE — it draws the border, and the mobile inset
	   SUBTRACTS it, because a border contributes to where the text starts just as
	   padding does. Measured on a device: the row's total inset is
	   border 3 + padding, so the two numbers have to move together or the
	   accordion drifts out of line with the theme's menu by exactly the
	   difference. */
	--lm-cat-indicator-width: 3px;
	/* The category column's label (Sidebar-Heading). Its own token, and the reason
	   is a MEASUREMENT: the label sits on the sidebar's beige (#f2efe9), where the
	   category heading's --lm-muted (#777) gives 3.90:1 — under the 4.5:1 a 10px
	   text needs. #6b6b6b gives 4.64:1 there, and it is the neutral the badges
	   moved to in Badge-Color. Not a retouch of --lm-muted, which is shared and
	   passes where its consumers sit, on white; not --lm-sidebar-desc-color either,
	   which is a Pro parameter and would drag the label along with the
	   description. G10d's reasoning, one element over. The test recomputes the
	   ratio against the sidebar's own background, so a change to either fails
	   there rather than on a screen. */
	--lm-sidebar-heading-color: #6b6b6b;

	/* ── Configurable (LightMega_Settings) ── */
	--lm-panel-bg: #ffffff;
	--lm-panel-height: 75vh;
	--lm-panel-max-width: none;
	--lm-panel-radius: 8px;
	/* The header's own two colours. They take over --lm-accent's job INSIDE the bar
	   and nowhere else: its other consumers (sidebar hover, both scrollbars, the
	   card hover border, the arrow disc) stay on the shared token, so recolouring
	   the header does not repaint the panel. The gold badge used to be the sixth;
	   since Badge-Color it carries the gold as a value of its own palette, because
	   a colour the merchant picks BY NAME must not move if the accent ever does. */
	--lm-header-bg: #1a1a1a;
	--lm-header-accent: #c8961e;
	--lm-sidebar-width: 290px;
	--lm-sidebar-font-size: 13px;
	--lm-sidebar-desc-font-size: 11px;
	/* Its own colour, NOT --lm-muted: a sidebar description is navigation, a card
	   subtitle is product copy. Sharing a token would mean one cannot be darkened
	   without moving the other two that use it (count, card subtitle). */
	--lm-sidebar-desc-color: #595959;
	--lm-sidebar-radius: 8px;
	--lm-card-min-width: 190px;
	--lm-card-radius: 8px;
	--lm-card-image-ratio: 4 / 3;
	/* Footer (G10c-2). --lm-footer-button-radius is fed by an ENUM: the merchant
	   picks a shape (square|rounded|pill), the schema's css_map turns it into the
	   radius below. The stored slug is never the CSS value — see
	   LightMega_Settings::css_value(). */
	--lm-footer-bg: #faf8f5;
	--lm-footer-separator-color: #e8e8e8;
	--lm-footer-button-radius: 6px;
	--lm-footer-button-bg: #c8961e;
	--lm-footer-button-color: #ffffff;
	/* Badges (G11). ONE property per parameter, which is what lets the generic
	   gate, report() and the import handle them with nothing of their own.
	   --lm-badges-place is <align> <justify> and is read TWICE, by place-content
	   and place-items; --lm-badges-display IS the layout (grid = stacked, flex =
	   in a row — see the Badges block for why not one of the two for both). The
	   -mobile set is a second set, not the first re-valued in the media query:
	   the inline style attribute freezes a token for every breakpoint (G10a). */
	--lm-badges-place: start end;
	--lm-badges-display: grid;
	--lm-badges-place-mobile: start end;
	--lm-badges-display-mobile: grid;
	--lm-badges-visibility-mobile: visible;
	/* A badge's colour (Badge-Color). NOT settings: each badge carries its own in
	   its style attribute, from LightMega_Menu_Meta::BADGE_PALETTE, as a DELTA
	   against these two. So these ARE the palette's neutral — `grey` has no values
	   in PHP, and a grey badge, a white label and a Pro colour behind a closed gate
	   all emit nothing and land here. One copy of each value.
	   Contrast 5.33:1 (#777, the old neutral, gave 4.48:1 — under the 4.5:1 an 8px
	   label needs). tests/badge-color.test.php recomputes it from this block. */
	--lm-badge-bg: #6b6b6b;
	--lm-badge-fg: #fff;

	/* Fonts are referenced, never bundled (WP.org privacy + performance): if
	   the theme already serves Montserrat/Lato they are used, else a clean
	   system fallback keeps the panel light. */
	--lm-font-head: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
	--lm-font-body: 'Lato', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}

.lightmega-panel,
.lightmega-panel * {
	box-sizing: border-box;
}

/* ─────────────────────────────────────────────────────────────
   Panel shell + open state
   ───────────────────────────────────────────────────────────── */
/* The panel is portaled to <body> by JS at init, so position:fixed is relative
   to the viewport (immune to ancestor transform/filter). It therefore no longer
   positions against .lightmega-item, and opening is driven by a class the JS
   toggles on the panel itself — not by :hover on the menu item. */
.lightmega-panel {
	display: none;
	position: fixed;
	left: 0;
	right: 0;
	top: 0; /* refined to the menu item's bottom edge by JS */
	height: var(--lm-panel-height);
	z-index: 9999;
	flex-direction: column;
	overflow: hidden;
	background: var(--lm-panel-bg);
	color: var(--lm-text);
	font-family: var(--lm-font-body);
	text-align: left;
	/* Depth BELOW the panel and nothing above it. The three numbers are one unit,
	   and `0 8px 48px` — the value through G10c-2-fix — is the one that breaks:
	   a 48px blur on a shadow offset only 8px down reaches ~16px ABOVE the
	   panel's top edge (48/2 - 8), and at z-index 9999 it paints that band onto
	   the site header. The whole header read as veiled whenever a panel was open,
	   and it looked like an overlay bug — the overlay is innocent, it starts at
	   the panel's top edge and in mobile mode it does not exist at all.

	   The arithmetic, because it is not guessable: spread resizes the shadow's
	   box BEFORE the blur is applied (negative contracts it on every side), and
	   the blur then extends the shadow about HALF its radius beyond that box. So
	   the topmost painted pixel sits at `offset - spread - blur/2` below the
	   panel's top edge: 24 - (-24) - 24 = +24px, i.e. 24px INSIDE an opaque
	   panel, hidden by it.

	   The constraint to keep when touching ANY of the three:
	       offset >= blur / 2 + spread
	   Lower the offset, widen the blur, or drop the negative spread — each on its
	   own puts the shadow back on the header. Verified on the client's site.
	   Locked by tests/settings-css.test.php. */
	box-shadow: 0 24px 48px -24px rgba(0, 0, 0, 0.25);
	/* Only the bottom corners, always: a panel that drops from the site header is
	   flush with it, so rounding its top edge would cut a notch out of the bar.
	   One setting, applied to the two corners it can sensibly affect. */
	border-radius: 0 0 var(--lm-panel-radius) var(--lm-panel-radius);
}

/* Boxed layouts: the shell stays full-bleed (it never breaks a theme), while the
   CONTENT can be constrained and centred. Applied to the panel's children rather
   than a new wrapper — no extra DOM.

   `width: 100%` is load-bearing, do not drop it: an auto margin on a flex item's
   cross axis SUPPRESSES align-items:stretch, so `margin-inline: auto` alone would
   shrink the children to their content width. With an explicit width there is no
   free space to absorb while max-width is `none` (the default), so the margins
   resolve to zero and layout is unchanged; set a max-width and the same margins
   centre the content.

   The FOOTER joins the rule in G10c-2: left out, it would have stayed full-width
   while the other two were centred. Declared consequence, and the same one the
   header already has today — the constrained box carries the BACKGROUND too, so
   with panel_max_width set the footer's fill is a centred band rather than a
   full-bleed strip. Consistency with the header is the argument. */
.lightmega-panel__header,
.lightmega-panel__body,
.lightmega-panel__footer {
	max-width: var(--lm-panel-max-width);
	margin-inline: auto;
	width: 100%;
}

/* Open state, toggled by JS on hover / focus / tap (see lightmega-frontend.js).
   CSS cannot drive this: the panel is detached from its menu item (portaled to
   <body>), so a descendant selector on the item would never match it. */
.lightmega-panel.is-open {
	display: flex;
}

/* Shared dark overlay (created and toggled by JS, appended to <body> so it is
   never trapped in the theme header's stacking context). */
.lightmega-overlay {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	top: 0; /* refined to the panel's top edge by JS, so the header stays clear */
	z-index: 9990;
	background: rgba(0, 0, 0, 0.5);
	opacity: 0;
	visibility: hidden;
	transition: opacity 0.2s ease;
}

.lightmega-overlay.is-visible {
	opacity: 1;
	visibility: visible;
}

/* ─────────────────────────────────────────────────────────────
   Header (dark bar: heading · count · close)
   ───────────────────────────────────────────────────────────── */
.lightmega-panel__header {
	flex-shrink: 0;
	display: flex;
	align-items: center;
	gap: 16px;
	height: 58px;
	padding: 0 28px;
	background: var(--lm-header-bg);
	border-bottom: 3px solid var(--lm-header-accent);
}

.lightmega-panel__titles {
	margin-right: auto;
	min-width: 0;
}

.lightmega-panel__heading {
	margin: 0;
	font-family: var(--lm-font-head);
	font-size: 15px;
	font-weight: 700;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: #fff;
}

/* The count pill follows the HEADER accent, not the global one: it and the bottom
   line are the only two things inside the bar, and a line that recolours while the
   pill beside it stays gold reads as a bug rather than a choice. */
.lightmega-panel__count {
	flex-shrink: 0;
	background: var(--lm-header-accent);
	color: #fff;
	font-family: var(--lm-font-head);
	font-size: 10px;
	font-weight: 700;
	letter-spacing: 0.05em;
	padding: 3px 12px;
	border-radius: 20px;
	white-space: nowrap;
}

.lightmega-panel__close {
	/* Pin to a perfect circle, defended against theme <button> styles (Astra Pro
	   adds padding / min-width / line-height that otherwise deform this into an
	   oval). Fixed square box + zeroed padding + aspect-ratio belt-and-braces. */
	flex: 0 0 auto;
	box-sizing: border-box;
	width: 34px;
	height: 34px;
	min-width: 34px;
	min-height: 34px;
	padding: 0;
	aspect-ratio: 1 / 1;
	display: flex;
	align-items: center;
	justify-content: center;
	border-radius: 50%;
	border: 1.5px solid rgba(255, 255, 255, 0.25);
	background: rgba(255, 255, 255, 0.12);
	color: #fff;
	font-size: 20px;
	line-height: 1;
	cursor: pointer;
	transition: background 0.15s;
}

.lightmega-panel__close:hover,
.lightmega-panel__close:focus-visible {
	background: rgba(255, 255, 255, 0.28);
	outline: none;
}

/* ─────────────────────────────────────────────────────────────
   Body: sidebar + grid
   ───────────────────────────────────────────────────────────── */
.lightmega-panel__body {
	flex: 1;
	display: flex;
	overflow: hidden;
}

/* ─── Left: category sidebar ─── */
.lightmega-panel__sidebar {
	width: var(--lm-sidebar-width);
	flex-shrink: 0;
	overflow-y: auto;
	padding: 8px 0;
	background: #f2efe9;
	border-right: 1px solid var(--lm-border);
	scrollbar-width: thin;
	scrollbar-color: var(--lm-accent) #e0dbd0;
	/* Mirror of the panel radius, and the mirror is deliberate: the sidebar meets
	   the panel's bottom edge, so only its TOP corners can be rounded. */
	border-radius: var(--lm-sidebar-radius) var(--lm-sidebar-radius) 0 0;
}

.lightmega-panel__sidebar::-webkit-scrollbar {
	width: 4px;
}

.lightmega-panel__sidebar::-webkit-scrollbar-thumb {
	background: var(--lm-accent);
	border-radius: 3px;
}

/* ─── The category column's label (Sidebar-Heading) ───
   Text only — no accent square, unlike the category heading: that is how the
   client's reference draws it. Its TYPOGRAPHY is shared with
   `.lightmega-section__label` in one grouped rule further down, so the two column
   labels cannot drift; what is here is only what differs — box, colour, wrapping.

   WHAT IT DOES TO LAYOUT, argued from the algorithm:

   • A BLOCK IN THE SIDEBAR'S OWN FLOW. The sidebar is a plain block container
     (no flex), so the label is its first in-flow child and pushes the entries
     down by its own height — 14px padding + 14px line (10px × 1.4) + 6px padding
     = 34px. No entry is resized. It sits INSIDE the scroll container, so it
     scrolls away with the entries: fixed, never sticky, like the category
     heading. No `position` is declared because `static` is the value wanted.
   • THE TWO LABELS SHARE A TOP EDGE. The sidebar's 8px padding plus this 14px is
     22px, which is the grid's own top padding — so with the same font, size and
     line height the label beside the category heading starts on the same line.
     14px is also the reference's own value.
   • THE LEFT INSET IS THE ENTRIES' TEXT, not their box: `.lightmega-cat` is a
     border of --lm-cat-indicator-width plus 18px of padding, so the label's text
     starts where the category names start. The right inset is the entries' 18px.
   • VALUES IN THE ABSENCE OF A DECLARATION: a <p>'s UA margin is `1em 0`, which
     would open 10px above and below — declared away. And the sidebar declares
     `overflow-y: auto`, which makes its `overflow-x` compute to `auto` as well, so
     a single word wider than the column would not overflow visibly: it would
     give the sidebar a HORIZONTAL SCROLLBAR. `overflow-wrap: break-word` breaks
     such a word instead; ordinary text wraps at its spaces as it always would. */
.lightmega-panel__sidebar-heading {
	margin: 0;
	padding: 14px 18px 6px calc(var(--lm-cat-indicator-width) + 18px);
	color: var(--lm-sidebar-heading-color);
	overflow-wrap: break-word;
}

.lightmega-cat {
	display: flex;
	/* The description (G10b) is a third child that takes a full row of its own, so
	   the entry has to be able to wrap.

	   WHAT WRAP ACTUALLY CHANGES (it is not a no-op, and calling it one is what
	   let a regression through): when the line overflows, a nowrap container
	   SHRINKS its flexible items, while a wrap container BREAKS THE LINE first —
	   line breaking runs before shrinking. With the label sized from its content,
	   a long category name therefore stopped shrinking and pushed the count onto a
	   line of its own, left-aligned by justify-content. That happened with or
	   without a description. See .lightmega-cat__label for the fix.

	   align-items applies PER FLEX LINE, so on line 1 (label + count) it decides
	   where the count sits against the label. flex-start anchors it to the label's
	   FIRST line, which is what a two-line category name needs — and what the
	   client's "category title aligned to the top" actually meant. Uniform across
	   the sidebar, never conditional on a description, or entries would disagree. */
	flex-wrap: wrap;
	align-items: flex-start;
	justify-content: space-between;
	gap: 2px 10px;
	width: 100%;
	padding: 11px 18px;
	border: 0;
	/* Width through the token, because the mobile inset subtracts it — see
	   --lm-cat-indicator-width. Only the COLOUR ever changes with state. */
	border-left: var(--lm-cat-indicator-width) solid transparent;
	background: none;
	font-family: var(--lm-font-head);
	font-size: var(--lm-sidebar-font-size);
	font-weight: 600;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: #444;
	text-align: left;
	text-decoration: none; /* the entry is an <a> (B1) — never underline it */
	cursor: pointer;
	transition: background 0.15s, color 0.15s, border-color 0.15s;
}

/* A row without a destination URL renders as an inert <span> (B1): it still
   previews its section on hover/focus, but it is not clickable, so it must not
   masquerade as a link. */
span.lightmega-cat {
	cursor: default;
}

.lightmega-cat:hover,
.lightmega-cat:focus-visible,
.lightmega-cat.is-active {
	/* TODO (accent_color, G11): this is --lm-accent at 8% written by hand. CSS
	   cannot derive an alpha from a hex custom property, so the day the accent
	   becomes configurable this tint will NOT follow it. Fix by storing the
	   accent as RGB components and composing rgb(var(--lm-accent-rgb) / 8%). */
	background: rgba(200, 150, 30, 0.08);
	color: var(--lm-accent);
	border-left-color: var(--lm-accent);
	outline: none;
}

/* `flex: 1 1 0` is what keeps the count on the label's line under flex-wrap.
   With a content-based basis the label claims its preferred width, the line
   overflows, and a WRAPPING container resolves that by breaking rather than
   shrinking — so the count dropped below. A zero basis means the label defends no
   preferred width: the line always fits, and the label then grows into whatever
   space the count leaves. Text wrapping inside the label is unchanged, because the
   width it ends up with is the same one shrinking used to produce.
   `min-width: 0` removes the automatic min-content floor, so a single very long
   word cannot re-create the overflow that starts the whole chain. */
.lightmega-cat__label {
	flex: 1 1 0;
	min-width: 0;
	line-height: 1.3;
}

/* Row description (G10b). `flex: 0 0 100%` is what pushes it onto its own line
   under name + count — no wrapper element needed.
   The four resets are not cosmetic: .lightmega-cat styles a short uppercase nav
   label (uppercase, letter-spaced, semibold, display font), and a sentence
   inheriting that is unreadable. The explicit colour also stops the entry's hover
   rule from turning the description gold along with the name.
   The clamp is the real length guarantee — MAX_DESCRIPTION caps what is STORED,
   but how much fits depends on --lm-sidebar-width, which is configurable. */
.lightmega-cat__desc {
	flex: 0 0 100%;
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
	font-family: var(--lm-font-body);
	font-size: var(--lm-sidebar-desc-font-size);
	font-weight: 400;
	line-height: 1.4;
	letter-spacing: normal;
	text-transform: none;
	color: var(--lm-sidebar-desc-color);
	/* Stop short of the count's column instead of running under it, so the sidebar
	   reads as two columns. Paired with the count's min-width below: both come from
	   the same token, so the reserved width and the inset cannot disagree. */
	padding-right: calc(var(--lm-sidebar-count-width) + 10px);
}

.lightmega-cat__count {
	flex-shrink: 0;
	/* A floor, not a fixed width: it makes the reserved column exact rather than
	   estimated, and lines up one- and two-digit counts down the sidebar. Counts of
	   three digits or more simply grow past it. */
	min-width: var(--lm-sidebar-count-width);
	text-align: center;
	background: var(--lm-border);
	color: var(--lm-muted);
	font-size: 9px;
	font-weight: 700;
	padding: 1px 6px;
	border-radius: 8px;
	transition: background 0.15s, color 0.15s;
}

.lightmega-cat:hover .lightmega-cat__count,
.lightmega-cat:focus-visible .lightmega-cat__count,
.lightmega-cat.is-active .lightmega-cat__count {
	background: var(--lm-accent);
	color: #fff;
}

/* The two accordion disclosures (G8) are injected by the JS and belong to mobile
   mode only. The row buttons are built lazily, on the first mobile open, so on a
   desktop render this matches nothing; the ITEM button is built at init — it has
   to be visible before the visitor touches anything — so above the breakpoint
   this is what actually hides it, not a belt. */
.lightmega-cat__toggle,
.lightmega-item__toggle {
	display: none;
}

/* The CLONED control (G8-fix2) is the theme's own markup copied from a sibling
   menu item, so the theme styles it: its icon, its size, its position, and which
   of its controls shows at which breakpoint all come for free, and none of it
   requires us to know a single theme class.

   WE WRITE NO RULE ABOUT ITS APPEARANCE — a copy that we then restyle is not a
   copy. Exactly one rule exists, in the mobile block, and it declares a STATE
   rather than a look: the flip on `aria-expanded`, which is ours to report
   because nothing else in the menu can report it. `tests/mobile-css.test.php`
   fails if any other property reaches the clone from this file.

   The wording used to be "no rule at all", and G8-fix7 revised it deliberately.
   G8-fix3 had removed the rotation on a finding that was correct and has since
   expired: five rules in the theme rotate `.ast-submenu-expanded >
   .ast-menu-toggle`, and on a device none of Astra's arrows moved — four target
   `::before`, which the stylesheet gives no `content` anywhere (leftovers from
   the icon-font era; the markup now ships an inline `<svg class="ast-arrow-svg">`),
   and the fifth is scoped to `.ast-mobile-popup-content`, which a DROPDOWN
   mobile menu never creates.

   The client's menu is now a FLYOUT, which creates that container, and the fifth
   rule fires. So the method survives — A RULE PRESENT IN THE STYLESHEET IS NOT AN
   OBSERVED BEHAVIOUR — with the corollary that cost this a second device round:
   AN OBSERVED BEHAVIOUR IS OBSERVED IN A CONFIGURATION, and the merchant can
   change the configuration. Neither half is safe alone. */

/* ─── Right: product grid ─── */
.lightmega-panel__grid {
	flex: 1;
	overflow-y: auto;
	padding: 22px 26px 36px;
	scrollbar-width: thin;
	scrollbar-color: var(--lm-accent) #ddd;
}

.lightmega-panel__grid::-webkit-scrollbar {
	width: 4px;
}

.lightmega-panel__grid::-webkit-scrollbar-thumb {
	background: var(--lm-accent);
	border-radius: 3px;
}

.lightmega-section {
	display: none;
}

.lightmega-section.is-active {
	display: block;
}

/* The TYPOGRAPHY of the two column labels, declared once (Sidebar-Heading): the
   category column's label and the category heading below. One style in two
   places — a size or a spacing changed here moves both, which is the point. Box,
   colour and wrapping stay on each label's own rule: the two sit on different
   backgrounds (white, the sidebar's beige), so they cannot share a grey without
   one of them failing contrast.

   10px, NOT THE REFERENCE'S 9px, and it was measured before it was chosen. The
   reference sets its label in Roboto at 9px/.14em; in --lm-font-head at .12em,
   9px has the same cap height (6.41px against 6.49) and 10px is 0.6px taller. So
   9px would copy the reference — and split out of this rule the one declaration
   it exists to share, on two labels that start on the same line. */
.lightmega-panel__sidebar-heading,
.lightmega-section__label {
	font-family: var(--lm-font-head);
	font-size: 10px;
	font-weight: 700;
	letter-spacing: 0.12em;
	line-height: 1.4;
	text-transform: uppercase;
}

/* ─── The category heading above each grid (G10c-2-fix) ───
   The row's own title, plus the count the sidebar entry already shows.

   WHAT IT DOES TO LAYOUT, argued from the algorithm rather than from the diff:

   • IT IS INSIDE THE SCROLLING AREA, not above it. The scroll container is
     `.lightmega-panel__grid` (`flex: 1` + `overflow-y: auto`); this sits within
     `.lightmega-section`, which is inside it. So the heading SCROLLS AWAY with
     the cards. That is the intended behaviour — fixed, never sticky — and it is
     also why no `position` is declared here: the initial value, `static`, is the
     one that is wanted, and the CSS contract fails if it ever becomes `sticky`.
   • USEFUL HEIGHT FOR THE CARDS. The panel's height is definite, so the grid
     container's is too; this heading adds roughly 10px of text + 7px padding +
     2px rule + 16px margin ≈ 35px of block-level content ABOVE the grid inside
     the same scroll box. The cards are not resized — `.lightmega-grid` sizes its
     columns from `--lm-card-min-width` and its rows from content, neither of
     which the section's height feeds into. What changes is how much of the grid
     is visible before scrolling: about 35px less, scrolled that much further.
     One heading per section, and only the active section is displayed, so the
     cost is paid once and does not accumulate over the rows.
   • VALUES IN THE ABSENCE OF A DECLARATION, since this is a <p> and the UA has
     opinions: `margin: 1em 0` (10px top AND bottom at this font size) would push
     the first card row down and open a gap the design does not have — so the
     margin is declared outright. `display` would be `block`, which is not what a
     square + two runs of text want, hence flex.
   • FLEX, AND WHAT THE OVERFLOW DOES. nowrap (the initial value, kept): a long
     category name SHRINKS rather than pushing the count onto its own line. That
     is the G10b-fix lesson applied before it can bite — the name carries
     `min-width: 0` so it can shrink below min-content and wrap internally, and
     the count carries `flex-shrink: 0` so it is never the thing that gives way. */
.lightmega-section__label {
	display: flex;
	align-items: baseline;
	gap: 8px;
	margin: 0 0 16px;
	padding-bottom: 7px;
	border-bottom: 2px solid var(--lm-border);
	color: var(--lm-muted);
}

/* The accent square. A pseudo-element, not the <span> the client's reference
   uses: it is purely decorative, so it has no business in the accessibility tree
   — and unlike the mobile disclosures (which needed a name and a state, and had
   to be real buttons) a marker has neither. Zero DOM. */
.lightmega-section__label::before {
	content: "";
	flex: 0 0 auto;
	width: 6px;
	height: 6px;
	background: var(--lm-accent);
}

.lightmega-section__name {
	min-width: 0;
}

/* Same style as the name, minus the case: the count reads as prose ("12
   products"), and uppercasing it makes it shout the way a heading does. */
.lightmega-section__count {
	flex-shrink: 0;
	text-transform: none;
}

.lightmega-grid {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(var(--lm-card-min-width), 1fr));
	gap: 12px;
}

/* ─────────────────────────────────────────────────────────────
   Product card
   ───────────────────────────────────────────────────────────── */
.lightmega-card {
	position: relative;
	margin: 0;
	padding: 0;
	list-style: none;
	/* Height floor for the lone-card case: a row with a single short card has no
	   taller neighbour for the grid's align-items:stretch to match, so without a
	   floor it renders shorter than sibling rows.
	   G10d NOTE: this no longer binds anywhere in the supported range. It was sized
	   against a 100px fixed media box; now the media is ratio-sized, so at the
	   narrowest allowed card (140px) it is already ~105px tall and the natural card
	   height clears 200px on its own. Kept because removing it would be a behaviour
	   change with nothing to gain — but do not read it as the current floor. */
	min-height: 200px;
}

.lightmega-card__link {
	display: block;
	position: relative;
	height: 100%;
	background: var(--lm-white);
	border: 1.5px solid var(--lm-border);
	border-radius: var(--lm-card-radius);
	overflow: hidden;
	color: inherit;
	text-decoration: none;
	transition: transform 0.22s, box-shadow 0.22s, border-color 0.22s, background 0.22s;
}

.lightmega-card__link:hover,
.lightmega-card__link:focus-visible {
	transform: translateY(-4px);
	/* TODO (accent_color, G11): hardcoded --lm-accent at 18% — see the note on
	   .lightmega-cat:hover; it will not follow a configurable accent. */
	box-shadow: 0 12px 32px rgba(200, 150, 30, 0.18);
	border-color: var(--lm-accent);
	background: #fffdf5;
	outline: none;
}

/* Media: positioning context for the image, which fills it (card-image-fill), and
   for the badges, in whichever corner they sit.
   The height comes from the RATIO, not from a fixed value: it is computed from the
   box's width during layout, before the image loads, so the space is reserved just
   as the old fixed height reserved it — the zero-CLS promise is unchanged, and the
   ratio also gives Lighthouse the explicit sizing signal it looks for. What DOES
   change is that the media now grows with the card instead of staying 100px while
   the card widens. */
.lightmega-card__media {
	position: relative;
	display: block;
	width: 100%;
	aspect-ratio: var(--lm-card-image-ratio);
	overflow: hidden;
	background: #e4e0d8;
}

/* The image FILLS the media box by construction: out of flow, pinned to the box's
   padding box. Two exposures closed at once (card-image-fill, 2026-09-29):

   - THE CASCADE, observed on the client's live shop pages. There `<body>` carries
     `woocommerce`, and WooCommerce ships `.woocommerce img { height: auto;
     max-width: 100% }` at (0,1,1), which beat the single class (0,1,0) whatever
     the order. The image took its own proportions: every landscape photo left a
     band of the media's beige under it, every portrait one was cropped from the
     top instead of the centre. The home page, with no such body class, was fine.
     The panel lives in <body> on sites we do not control, so the class is
     DOUBLED: (0,2,0) beats a body class plus an element, and the doubling
     armours EVERY declaration of this rule — object-fit included, which a
     theme's `contain` would turn into the same band.
   - THE DERIVATION. In flow, `height: 100%` resolved against a height the box
     does not have of its own: the media's height is DERIVED from its width
     through the ratio. Definite by the spec (css-sizing-4 §4.2), but it is a
     second-order derivation, which WebKit resolves from whatever width the box
     holds when the image asks (read in its source, not observed). The
     percentages of an absolutely positioned box resolve against the containing
     block's padding box with no such condition (CSS 2 §10.5), and it is laid
     out after that box's height is final. So the size depends neither on WHEN it
     is computed nor on anything a lazy loader rewrites — src, srcset, sizes,
     the placeholder, the `contain: size` a `sizes="auto"` brings.

   `width` and `height` stay: a replaced element does not stretch between its
   insets, it falls back to its own ratio when they are `auto` (CSS 2 §10.6.5) —
   which is exactly why a theme's `height: auto` must never win here.
   `z-index: 0` because the image is now POSITIONED: a z-index a theme puts on
   img would start to apply, and could lift it over the badges (z-index 2).
   The media box does not move: its height was always the ratio, and its
   `overflow: hidden` removes the content-based minimum, so the image never
   sized it. Its `position: relative` is this rule's containing block.
   tests/card-image.test.php resolves the real cascade, WooCommerce's rules in. */
.lightmega-card__image.lightmega-card__image {
	position: absolute;
	inset: 0;
	z-index: 0;
	display: block;
	width: 100%;
	height: 100%;
	max-width: 100%;
	object-fit: cover;
	transition: transform 0.35s;
}

.lightmega-card__link:hover .lightmega-card__image,
.lightmega-card__link:focus-visible .lightmega-card__image {
	transform: scale(1.07);
}

/* Hover "go" affordance — purely DECORATIVE. Anchored to the card link and placed
   BOTTOM-RIGHT (below the description), clear of the image, whose four corners
   belong to the badges (G11: up to 3 in Pro, in any of them). A filled gold disc
   with a white arrow gives clear contrast on the white/cream card, where the old
   white disc used to blend in.
   Pure ::after — zero extra DOM — and pointer-events:none so it never becomes a
   second click target: the whole card is already the product link. */
.lightmega-card__link::after {
	content: "\2192"; /* → */
	position: absolute;
	right: 9px;
	bottom: 9px;
	width: 22px;
	height: 22px;
	display: flex;
	align-items: center;
	justify-content: center;
	border-radius: 50%;
	background: var(--lm-accent);
	color: var(--lm-white);
	font-family: var(--lm-font-head);
	font-size: 11px;
	font-weight: 700;
	line-height: 1;
	/* TODO (accent_color, G11): hardcoded --lm-accent at 35% — see the note on
	   .lightmega-cat:hover; it will not follow a configurable accent. */
	box-shadow: 0 2px 6px rgba(200, 150, 30, 0.35);
	opacity: 0;
	transform: translateY(3px);
	pointer-events: none;
	transition: opacity 0.18s, transform 0.18s;
}

.lightmega-card__link:hover::after,
.lightmega-card__link:focus-visible::after {
	opacity: 1;
	transform: translateY(0);
}

/* The extra bottom padding reserves a clear strip for the hover "go" arrow
   (::after, bottom-right). The arrow's box reaches ~31px up from the card's
   bottom edge; a 32px bottom padding keeps the last line of text — title OR
   description, whichever ends lowest — just above it, so the arrow never
   overlaps the words (reported on the live site). B1/B2. */
.lightmega-card__body {
	display: block;
	padding: 9px 11px 32px;
}

.lightmega-card__title {
	display: block;
	font-family: var(--lm-font-head);
	font-size: 13px;
	font-weight: 700;
	line-height: 1.3;
	color: var(--lm-dark);
	margin-bottom: 4px;
	transition: color 0.18s;
}

.lightmega-card__link:hover .lightmega-card__title,
.lightmega-card__link:focus-visible .lightmega-card__title {
	color: var(--lm-accent);
}

/* Descriptions only: clamp to 3 lines so the text block height is uniform across
   cards (the subtitle is already truncated server-side in G5c; this evens out the
   visual height). Titles are deliberately NOT clamped — Oudimmo product names
   carry the model (AKUPAN, ADAMANT FireSafe, EXIST ART) and must read in full. */
.lightmega-card__subtitle {
	display: -webkit-box;
	-webkit-line-clamp: 3;
	-webkit-box-orient: vertical;
	overflow: hidden;
	font-size: 11.5px;
	font-weight: 300;
	line-height: 1.5;
	color: var(--lm-muted);
}

/* ─────────────────────────────────────────────────────────────
   Badges (G11: where they sit and how they line up are settings)
   ───────────────────────────────────────────────────────────── */
/* WHAT THIS CHANGED IN LAYOUT BEHAVIOUR, argued and then measured (CLAUDE.md).
   Until G11 this box was shrink-to-fit, pinned with top/right and capped with
   max-width: calc(100% - 14px). Now it COVERS the media box less the same 7px on
   every side, and the badges are PLACED inside it by alignment:

   - place-content moves the whole group into its corner and place-items aligns
     each badge to the same side. Both read --lm-badges-place, which is why one
     setting needs only one property.
   - `display` IS the layout. Stacked is a one-column GRID: each badge keeps its
     own width and lines up on the chosen side. In a row is a FLEX line: when the
     labels do not fit, flex shrinks them and each truncates with its ellipsis.
     A grid row does not shrink them — measured, the labels OVERLAPPED by 5px —
     which is why the row is not a grid as well.
   - A row in the top-right corner is PIXEL-IDENTICAL to the pre-G11 rule. The box
     is now exactly as wide as the old max-width cap, so a row that overflows
     shrinks against the same width and a row that fits is packed to the same
     edge. Measured on 8 cases at two card widths, in Chromium only: WebKit and a
     device are the reviewer's.
   - The width cap therefore lives in the box's own definite width, and the badge
     carries max-width: 100% for the stack, where a grid item would otherwise be
     as wide as its unbroken label.
   - pointer-events: none, because the box now lies over the whole image. Badges
     are not interactive; the card link under them is.

   The hover arrow is not in this box: it hangs off the card LINK, below the body
   text, so a badge in a bottom corner cannot meet it. */
.lightmega-badges {
	position: absolute;
	inset: 7px;
	z-index: 2;
	display: var(--lm-badges-display);
	gap: 4px;
	place-content: var(--lm-badges-place);
	place-items: var(--lm-badges-place);
	pointer-events: none;
}

.lightmega-badge {
	font-family: var(--lm-font-head);
	font-size: 8px;
	font-weight: 700;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	padding: 2px 6px;
	border-radius: 3px;
	background: var(--lm-badge-bg);
	color: var(--lm-badge-fg);
	/* nowrap stays — a badge reads as one token, never two lines. The ellipsis is
	   the structural guarantee behind MAX_BADGE_TEXT: 20 characters fit a 190px
	   card and overflow a 140px one, and card_min_width is configurable, so the
	   character count alone can promise nothing.
	   All four declarations are required together: text-overflow needs overflow
	   hidden; a flex item (the row) will not shrink below its content width
	   without min-width: 0; and a grid item (the stack) is not capped by its track
	   without max-width: 100%. Leave one out and the ellipsis never appears in
	   that layout. */
	white-space: nowrap;
	min-width: 0;
	max-width: 100%;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* NO PALETTE RULES HERE (Badge-Color). There used to be one class per badge
   type — `--new`, `--sale`, `--eco`, `--premium`, `--default` — each painting a
   colour, which made a MEANING the only way to choose one. The palette is now a
   list of VALUES in LightMega_Menu_Meta::BADGE_PALETTE, written on each badge as
   `--lm-badge-bg` / `--lm-badge-fg` and consumed by `.lightmega-badge` above, so
   a colour the merchant picks later needs no rule either. No alias for the old
   classes: nothing in production carries them. */

/* ─────────────────────────────────────────────────────────────
   Footer (G10c-2): up to three links, plus one call to action

   WHAT THIS CHANGES IN LAYOUT BEHAVIOUR, argued from the flex algorithm and not
   from the diff (CLAUDE.md), because this is a NEW third item in the panel's
   column and the previous three defects in this project were all in that slot.

   The panel is `display:flex; flex-direction:column` with a definite height and
   `overflow:hidden`. Its items are the header (`flex-shrink:0`, 58px), the body
   (`flex:1`, i.e. `1 1 0%`) and now this.

   • VALUE IN THE ABSENCE OF A DECLARATION: `0 1 auto` — basis from the content,
     and SHRINK 1. That is not neutral, and it is the exact trap of G8-fix4 with
     the roles swapped.
   • `flex-shrink: 0` IS THEREFORE OBLIGATORY, not decoration. Negative free space
     is distributed by SCALED shrink factor (flex-shrink × flex base size): the
     body's base size is 0, so its scaled factor is 0 and it absorbs none of it,
     and the header already declares shrink 0. Every pixel of overflow would land
     on this element alone — squashed, and then clipped invisibly by the panel's
     own `overflow: hidden`.
   • WITH A FOOTER, the body (grow 1 on a 0 basis) takes what is left, so the
     grid's visible area shrinks by EXACTLY the footer's height and the grid —
     which already has `overflow-y: auto` — scrolls that much more. No content is
     lost.
   • WITHOUT ONE, no markup is emitted, so there is no box and no flex item: the
     body's space is what it is on main, to the pixel.
   • ON A LOW PANEL the body reaches zero FIRST (its basis is 0), so the grid area
     disappears before this element gives up anything; past that the footer
     overflows the panel and its bottom edge is clipped. That ordering is the
     point of shrink 0 — the fixed-height thing keeps its height.
   FOUR EQUAL COLUMNS (G10c-2-fix), and the reason it is a GRID and not the flex
   row that shipped first. The client's own CSS uses `justify-content:
   space-between`, which distributes free space BETWEEN items and only looks
   equidistant while the labels happen to be of similar length — give one link a
   long name and the three columns stop lining up. Four explicit tracks are
   equidistant by construction, whatever the text does.

   • `repeat(4, minmax(0, 1fr))`, not `repeat(4, 1fr)`. `1fr` is
     `minmax(auto, 1fr)`, whose minimum is the track content's min-content size,
     so one long unbreakable word makes its track wider than the other three —
     which is the very failure this change exists to prevent. A `0` minimum makes
     the four tracks exactly equal and lets the text wrap inside its own column.
   • HOW OVERFLOW RESOLVES CHANGES with it, and this is the part that is not
     visible in the diff. The flex row WRAPPED: on a narrow panel the button
     dropped onto a line of its own. A grid with four explicit tracks does not
     wrap — there is no line to break — so the same narrow panel now compresses
     the four tracks and the labels wrap INSIDE them. That is the deliberate
     trade for equal columns. Declared consequence: with `panel_max_width` at its
     320px minimum the tracks are ~48px and the labels wrap hard. Mobile does not
     arise — there is no footer down there at all.
   • THE BUTTON IS PINNED to the fourth track (`grid-column: 4`) rather than left
     to auto-placement, so it stays in the last position with one or two links
     instead of sliding left into track 2 or 3. Auto-placement fills tracks 1..n
     with the links and skips the cell the button already occupies.
   • The list keeps its `<ul>` — a nested grid rather than `display: contents`,
     which would have cost the list semantics. It spans tracks 1-3 and subdivides
     into three equal ones; because both grids take their gap from the SAME
     token, its three tracks land exactly on the parent's. Two literals would be
     two things that drift.
   ───────────────────────────────────────────────────────────── */
.lightmega-panel__footer {
	/* Used by both grids. One declaration is what keeps the nested tracks aligned
	   with the outer ones — see above. */
	--lm-footer-gap: 24px;
	flex-shrink: 0;
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	align-items: center;
	gap: var(--lm-footer-gap);
	padding: 14px 28px;
	background: var(--lm-footer-bg);
	border-top: 1px solid var(--lm-footer-separator-color);
}

.lightmega-footer__items {
	grid-column: 1 / 4;
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	align-items: center;
	gap: var(--lm-footer-gap);
	list-style: none;
	margin: 0;
	padding: 0;
}

.lightmega-footer__item {
	min-width: 0;
	margin: 0;
	padding: 0;
	list-style: none;
}

.lightmega-footer__link {
	display: block;
	font-family: var(--lm-font-head);
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0.04em;
	line-height: 1.35;
	color: var(--lm-text);
	text-decoration: none;
	transition: color 0.15s;
}

/* An item with no URL renders as an inert <span> (same rule as a sidebar entry
   without one), so it must not masquerade as a link. */
span.lightmega-footer__link {
	cursor: default;
}

/* No `outline: none` here, unlike the sidebar entry: a colour change alone is not
   a focus indicator, and these links have no border or background shift to carry
   the state. The user agent's ring stays. */
a.lightmega-footer__link:hover,
a.lightmega-footer__link:focus-visible {
	color: var(--lm-accent);
}

/* The two lines carry the SAME treatment — same size, same weight, same colour.
   In the client's reference they are one label broken across two lines, not a
   title and a subtitle, and the data keeps them apart only so they can be
   translated separately and, one day, styled apart without a migration. */
.lightmega-footer__text,
.lightmega-footer__subtext {
	display: block;
}

/* Pinned to the last track, never auto-placed: with one or two links the button
   must stay in the fourth column rather than slide left into the next free one.
   `justify-self: end` keeps it flush right INSIDE that track — the track is a
   quarter of the row, the button is only as wide as its label. */
.lightmega-footer__button {
	grid-column: 4;
	justify-self: end;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 9px 20px;
	border-radius: var(--lm-footer-button-radius);
	font-family: var(--lm-font-head);
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 0.04em;
	line-height: 1.2;
	transition: filter 0.15s;
}

/* THE SAME ARMOUR THE TWO CHEVRONS CARRY, and for the third instance of the same
   failure (G8-fix6). The button's label went unreadable on hover — dark on the
   gold, only its edges left — and the cause is not the brightness filter, which
   was measured and exonerated: it darkens the WHOLE element, text included, so
   #ffffff becomes #ebebeb against a background that becomes #b88a1b, and the
   contrast moves 2.68:1 -> 2.62:1. That cannot make a label vanish.
   What can is the CASCADE. `color` was declared on the base rule at (0,1,0), and
   any theme rule of the shape `a:hover { color: … }` is (0,1,1) — one class more
   than nothing beats one class, but ours is one class and theirs is one class
   plus one type, so THEIRS WINS. At rest we win, on hover we lose: exactly the
   symptom. The plugin never learns which rule it is, and does not have to.
   Doubling the class is (0,2,0), with a state (0,3,0): it wins regardless of
   source order and regardless of the theme, which is the documented remedy here.
   The property list is the shared one that tests/mobile-css.test.php enforces on
   every control of ours sitting on a theme's surface, PLUS text-decoration: this
   is the only one of the three that is an <a>, and `a:hover { text-decoration:
   underline }` is the same defect wearing a different property. The chevrons are
   <button>s, where it cannot arise, so it stays out of the shared list.
   Declared HERE and not on the base rule so there is one declaration of each
   value, not two. */
.lightmega-footer__button.lightmega-footer__button,
.lightmega-footer__button.lightmega-footer__button:hover,
.lightmega-footer__button.lightmega-footer__button:focus,
.lightmega-footer__button.lightmega-footer__button:focus-visible,
.lightmega-footer__button.lightmega-footer__button:active {
	background: var(--lm-footer-button-bg);
	color: var(--lm-footer-button-color);
	border: 0;
	box-shadow: none;
	opacity: 1;
	text-decoration: none;
	appearance: none;
	-webkit-appearance: none;
}

/* DERIVED, not a second colour token: footer_button_color is configurable, so a
   fixed darker shade would stay gold on a blue button. brightness() follows
   whatever the merchant set — and it is why --lm-accent-hover, declared in G10a
   for exactly this hover, was removed here instead of being consumed. */
a.lightmega-footer__button:hover,
a.lightmega-footer__button:focus-visible {
	filter: brightness(0.92);
}

a.lightmega-footer__button:focus-visible {
	outline: 2px solid var(--lm-accent);
	outline-offset: 2px;
}

/* ─────────────────────────────────────────────────────────────
   Reduced motion: honor the user preference (a11y + perf)
   ───────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
	.lightmega-panel *,
	.lightmega-overlay {
		transition: none !important;
	}

	.lightmega-card__link:hover,
	.lightmega-card__link:focus-visible {
		transform: none;
	}

	.lightmega-card__link:hover .lightmega-card__image,
	.lightmega-card__link:focus-visible .lightmega-card__image {
		transform: none;
	}
}

/* ─────────────────────────────────────────────────────────────
   Mobile: an accordion IN LINE, inside the theme's own menu (G8)

   Not a drawer. The theme's mobile menu already is one — with its own X and its
   own per-item disclosure — and a second drawer on top of it gives the visitor
   two X buttons that do different things.

   WHAT CHANGES IN LAYOUT BEHAVIOUR, argued from the algorithm rather than from
   the diff (CLAUDE.md, learned from the G10b count regression):

   • position: fixed → static. The panel stops taking the viewport as its
     containing block and re-enters the flow of the <li>: it now CONTRIBUTES to
     the height of the menu item instead of being lifted out of the box tree.
   • height: var(--lm-panel-height) → auto. The height becomes content-based —
     the rows plus whichever section is expanded. This is why there is no inner
     scrolling left to configure: there is no longer a definite height for the
     content to exceed.
   • overflow on the body and the grid → visible. The grid's own overflow-y:auto
     disappears, so the scrolling container becomes whatever the THEME scrolls
     (its drawer, or the page). We do not take the scroll.
   • display: contents on the sidebar and the grid. Their boxes are not
     generated, so their children become items of .lightmega-panel__body
     directly — which is what allows an entry and its section, born in two
     different containers, to sit next to each other. It also drops the
     sidebar's width, background, right border, radius and the grid's padding
     with no rule to undo them.
     COST, declared: <nav aria-label="Categories"> stops generating a box, and
     some browsers then drop the navigation landmark from the accessibility
     tree. Inside the theme's own <nav> that is one nested landmark fewer;
     accepted, not overlooked.
   • The sequence entry → toggle → section is restored by `order`, written
     inline by the JS. See buildMobile() for why it is not CSS.

   Four DIMENSIONAL tokens are neutralised, and always on the CONSUMER, never on
   the token: the panel's style attribute is (1,0,0,0) and freezes a custom
   property's value for every media query (known debt, G10a). So `height: auto`,
   not `--lm-panel-height`. Everything else a merchant configures — colours,
   typography, radii, and --lm-card-image-ratio, which is a ratio and holds at
   any scale — still applies down here.
   ───────────────────────────────────────────────────────────── */
@media (max-width: 900px) {
	/* Internal tokens, not settings.

	   --lm-mobile-toggle is the minimum comfortable tap target, used as the
	   toggle column's width and as the button's floor in both axes: one
	   declaration so the column and the target cannot disagree.

	   --lm-row-open-bg is the open row's tint, and it is a token for the same
	   reason: the row and its chevron have to be the SAME colour or the row reads
	   as two pieces, and a literal repeated in two rules is a literal that drifts.
	   Declared on BOTH selectors because the menu item is an ancestor of the panel
	   in mobile mode but not on desktop, where the panel lives in <body> — a
	   custom property does not travel upwards.
	   TODO (accent_color, G11): --lm-accent at 8%, written by hand. CSS cannot
	   derive an alpha from a hex custom property, so this will not follow a
	   configurable accent — same note as .lightmega-cat:hover. */
	.lightmega-panel,
	.lightmega-item {
		--lm-mobile-toggle: 44px;
		--lm-row-open-bg: rgba(200, 150, 30, 0.08);
		/* THE COLUMN OUR CONTENT STARTS ON, and it is a token because it is used
		   TWICE — the category row's text and the card block underneath it, which
		   must start on the same line or the accordion reads as two ragged
		   columns. Same reason as --lm-row-open-bg: two literals of one intent are
		   two things that drift, and these two were exactly that until G8-fix5.

		   IT CANNOT BE DERIVED FROM THE THEME, and the reason is structural rather
		   than a limitation we have not worked around yet. On mobile the panel is
		   appended INTO the menu item (`item.el.appendChild( item.panel )`), so it
		   is a SIBLING of the theme's link and a child of the same <li>:

		     <li>                       content box starts at L
		       <a>   padding-left: T    theme's text starts at L + T
		       <div class=panel>        our content box also starts at L
		         .lightmega-cat         our text starts at L + this token

		   T lives on the link. CSS `inherit` reads the PARENT, and there is no way
		   to read a sibling's used padding — so the alignment can only be
		   replicated, never derived. Reading it at runtime was ruled out: this is a
		   refinement, not worth a measurement in JS.

		   IT IS THE TOTAL, NOT THE PADDING. What lines up with the theme's menu is
		   the distance from the panel's edge to the TEXT, and a border contributes
		   to that exactly as padding does. The row carries a 3px indicator border,
		   so its padding is the token MINUS that border — the subtraction is the
		   whole reason this token is a total and not a padding. Both halves read
		   --lm-cat-indicator-width, so they cannot diverge. `.lightmega-section`
		   has no border at all (verified: none on desktop, none here, and the grid
		   inside it has zero horizontal padding), so there the token applies whole.

		   20px IS MEASURED, NOT CHOSEN: it is the horizontal padding of the mobile
		   menu links on the client's site, read off `a.menu-link` in DevTools
		   (border 0, padding-left 20). ON ANOTHER THEME THE RIGHT VALUE IS
		   DIFFERENT, and on this one it changes if the merchant moves Astra's own
		   menu-item padding in the Customizer. A site corrects it in one
		   declaration, doubling the class because our stylesheet prints after the
		   Customizer's (the G10a cascade debt):

		     .lightmega-panel.lightmega-panel { --lm-mobile-inset: 24px; }

		   Nothing consumes it above the breakpoint, so that override needs no media
		   query of its own. It is the natural first Free mobile parameter — fitting
		   the panel to its site is the whole Free promise — and is queued with the
		   card sizes and the column count. */
		--lm-mobile-inset: 20px;
	}

	.lightmega-panel {
		position: static;
		height: auto;
		z-index: auto;
		/* A 48px drop shadow says "floating above the page". In the flow of a
		   menu it is just dirt. */
		box-shadow: none;
		/* Squared off, and it is the MODE that decides, not the width — the same
		   reasoning that removed the header and the footer here. On desktop the
		   panel is a dropdown whose bottom edge is the end of the panel, so
		   rounding it is deliberate design. In the accordion that edge is the
		   BOUNDARY WITH THE THEME'S NEXT MENU ITEM, and a notch there breaks a
		   list of otherwise square rows.

		   It is `border-radius`, on the CONSUMER, and never an override of
		   `--lm-panel-radius`: the inline style attribute is (1,0,0,0) and
		   freezes a token's value for every media query (the G10a debt), so
		   re-valuing the token here would do nothing the moment a merchant sets
		   that parameter. Same rule as the four dimensional tokens above.

		   NOTE what is being squared. The panel declares
		   `border-radius: 0 0 var(--lm-panel-radius) ...` plus `overflow: hidden`,
		   so it CLIPS its own last in-flow child — the visible symptom was the
		   last category row coming back with a rounded bottom corner. The
		   sidebar's radius is not involved and could not be: it is declared on
		   the TOP corners only, and `.lightmega-panel__sidebar` is
		   `display: contents` down here, so it generates no box to paint. */
		border-radius: 0;
		/* ── WHY THIS VALUE, AND WHY NOT THE OTHER TWO ──────────────────────
		   The panel is a flex ITEM: the client's <li> is
		   `display:flex; flex-direction:column; justify-content:center`, so the
		   main axis here is VERTICAL and every flex property below sizes height.
		   Measured in a reproduction of the real page, not read off the source.

		   `flex: 0 0 100%` (G8, removed in G8-fix3) — WRONG. A percentage basis
		   in a column is a percentage of the HEIGHT. It was written as insurance
		   for a flex ROW and applied to a column.

		   NO DECLARATION (G8-fix3) — ALSO WRONG, and this is the value that was
		   missing from the reasoning all three times: the initial value is
		   `0 1 auto`, so the panel is SHRINKABLE. Worse, because it declares
		   `overflow: hidden`, its automatic minimum size resolves to 0 instead of
		   min-content — an item with clipped overflow has no min-content floor —
		   so there is nothing to stop it being compressed to almost nothing. The
		   theme's own link cannot shrink past its min-content, so the panel is
		   the item that gives way, and `overflow: hidden` then hides the loss:

		     content 216px  → used 118px  (98px clipped)
		     content 1229px → used 625px  (604px clipped)
		     content 2985px → used 1503px (1482px clipped)

		   The space the panel gave up went to the link, which grew from its
		   natural 45px to 143 / 649 / 1526 — and since its text sits at the top
		   of that inflated box, the rest of it reads as a vertical VOID between
		   the menu row and the first category. That was the regression.

		   `flex: 0 0 auto` — RIGHT. Basis `auto` takes the hypothetical size from
		   the content; `flex-shrink: 0` removes the panel from the distribution
		   of negative space altogether. Used height then equals content height
		   whatever the container decides — measured exactly equal at all three
		   sizes above — and the link returns to its natural 45px.

		   Closed: `display: none`, so no box and no flex item; this is inert.
		   Open: the <li> grows to link + panel and the next item is pushed down
		   in normal flow. Content taller than the viewport: the <li> keeps
		   growing (2985px measured) and the THEME's drawer scrolls, which is the
		   G8 decision — the panel has no inner scrolling left to do. */
		flex: 0 0 auto;
	}

	/* No header, whatever panel_header_show says. The title is already the menu
	   item the visitor tapped and the X is already the theme's, so both would be
	   duplicates — and duplicating an X is how someone ends up closing the whole
	   drawer when they meant to close a section. The close button lives inside
	   this bar, so it goes with it and needs no rule of its own.

	   Note what this is NOT: the markup is still emitted. The server cannot know
	   the viewport and must not try — one cached page is served to both — so
	   "not rendered" here means "generates no box", which is a CSS fact. */
	.lightmega-panel__header {
		display: none;
	}

	/* No footer either (G10c-2), and the reason is the mode rather than the width:
	   in accordion mode the panel is IN THE FLOW of the theme's own mobile menu, so
	   it has no "bottom" to anchor anything to — a footer would land in the middle
	   of the menu, between our last category and the theme's next item. Decision
	   taken and communicated to the client; the CTA reaches the mobile menu from
	   the theme's side instead.

	   Same note as the header: the markup is still EMITTED. The server cannot know
	   the viewport and must not try, since one cached page is served to both, so
	   "not rendered" here means "generates no box" — a CSS fact, asserted in
	   tests/mobile-css.test.php, with the markup half asserted in tests/footer.test.php. */
	.lightmega-panel__footer {
		display: none;
	}

	.lightmega-overlay {
		display: none !important;
	}

	/* Two columns: the entry, and the disclosure. A section spans both and is
	   auto-placed on the next row, because by then the placement cursor has run
	   past the end of the entry's row. */
	.lightmega-panel__body {
		display: grid;
		grid-template-columns: 1fr var(--lm-mobile-toggle);
		align-content: start;
		max-width: none;
		margin-inline: 0;
		overflow: visible;
	}

	.lightmega-panel__sidebar,
	.lightmega-panel__grid {
		display: contents;
	}

	/* ── The accordion row ── */
	.lightmega-cat {
		/* A grid item's automatic minimum is its content, so a long category
		   name would push the 1fr column past the panel. */
		min-width: 0;
		/* MINUS THE BORDER, and that subtraction is the fix: the row's inset is
		   border 3 + padding, so a token spent entirely on padding lands 3px wide.
		   Measured on the device — theme link 0 + 20 = 20, our row 3 + 16 = 19 —
		   which is how the border turned out to be already paying for most of the
		   difference.

		   IN THE ABSENCE OF THIS DECLARATION the element does NOT fall back to 0:
		   the desktop rule still applies and gives `11px 18px`, because a media
		   query overrides rather than replaces. So deleting the line moves the row
		   to an 18px padding — plus the border, a 21px inset — which is a different
		   number and not a neutral one. The right edge stays 6px: the toggle sits in
		   the next grid column, so it has nothing to do with this alignment. */
		padding: 12px 6px 12px calc(var(--lm-mobile-inset) - var(--lm-cat-indicator-width));
		border-bottom: 1px solid var(--lm-border);
	}

	/* One line per row, like the theme's own menu items. Almost every theme lays a
	   menu item out on a single line, and a two-line description under the title
	   breaks that alignment twice over: visually, and against the arrow and the
	   count, which would then sit beside a block of variable height.

	   `display: none` only — the markup is still EMITTED, for the same reason the
	   panel header is: the server cannot know the viewport and must not try, since
	   one cached page is served to both. Asserted on the CSS here and on the
	   rendered HTML in tests/row-description.test.php.

	   Showing it on mobile is a candidate setting after the holidays — some
	   merchants will want it, and by then the row can afford the height. */
	.lightmega-cat__desc {
		display: none;
	}

	/* No category heading either (G10c-2-fix), and the reason is the accordion:
	   the category's name is the row the visitor has just tapped, two centimetres
	   above the grid. Repeating it is pure redundancy, and it spends vertical
	   space where vertical space is worth most. Same reasoning that took the
	   description out of mobile.

	   `display: none` only — the markup is still EMITTED, as it is for the panel
	   header, the footer and the description: the server cannot know the viewport
	   and one cached page is served to both. Asserted on the CSS here and on the
	   rendered HTML in tests/section-label.test.php. */
	.lightmega-section__label {
		display: none;
	}

	/* No category column label either (Sidebar-Heading), and the reason is the
	   same menu seen from above: the theme's item the visitor has just tapped
	   ("Shop Prodotti") already heads the list that opened under it, and no
	   submenu of the theme carries a label — ours would be the only one, so it
	   would not set the categories apart, it would make them look out of place.

	   `display: none` only, and it is also what keeps the grid intact: the
	   sidebar is `display: contents` here, so a label with a box would become an
	   item of the two-column body grid and push the first entry into the 44px
	   toggle column. The markup is still EMITTED — one cached page is served to
	   both — and the <nav>'s aria-labelledby still resolves, since a name is
	   computed from a referenced element even when it is not rendered.

	   Showing it is a candidate Free switch after the holidays, in the same family
	   as the row description on mobile. */
	.lightmega-panel__sidebar-heading {
		display: none;
	}

	/* is-active is the DESKTOP preview state, and the server ships it on row 0 so
	   the grid is never blank. Down here it must say nothing: the accordion opens
	   with every row closed, and a gold row above a collapsed section reads as a
	   bug. is-expanded, declared after it, is the state that counts. */
	.lightmega-cat.is-active {
		background: none;
		color: #444;
		border-left-color: transparent;
	}

	.lightmega-cat.is-active .lightmega-cat__count {
		background: var(--lm-border);
		color: var(--lm-muted);
	}

	/* ── The hover, restated, and WHY it has to be ── (G8-fix6)
	   Reported from the device: the FIRST row did not respond to touch while every
	   other row did. The first row is the only one the server marks `is-active` —
	   it ships that class so the desktop grid is never blank — and the two rules
	   above neutralise it here.

	   THE MECHANISM IS NOT "the hover was neutralised along with it": the hover
	   lives in the DESKTOP rule, which nothing in this block touches. It is
	   SPECIFICITY PLUS SOURCE ORDER. `.lightmega-cat:hover` is (0,2,0) — one class
	   and one pseudo-class — and `.lightmega-cat.is-active` is (0,2,0) as well.
	   Equal specificity is settled by whichever comes last, and the neutraliser is
	   later in the file, so on the one row carrying `is-active` it won in EVERY
	   state, hover included. Rows two onward carry no `is-active`, nothing
	   competed, and the desktop rule applied — which is exactly the split that was
	   observed. The count pill had the same defect one level down, at (0,3,0)
	   against (0,3,0).

	   Restating the hover after the neutraliser, at the same specificity, is all it
	   takes to win. It sits BEFORE `is-expanded` on purpose: that rule is later
	   still, so an open row keeps the open look even while it is being touched.
	   Open and hovered stay distinguishable, and not by this colour — the open row
	   tints its CHEVRON column too, through the toggle's own
	   `[aria-expanded="true"]` rule, while a hovered row tints only the entry. The
	   rotation is the state indicator, as decided in G8. */
	.lightmega-cat:hover,
	.lightmega-cat:focus-visible {
		background: var(--lm-row-open-bg);
		color: var(--lm-accent);
		border-left-color: var(--lm-accent);
	}

	.lightmega-cat:hover .lightmega-cat__count,
	.lightmega-cat:focus-visible .lightmega-cat__count {
		background: var(--lm-accent);
		color: #fff;
	}

	.lightmega-cat.is-expanded {
		background: var(--lm-row-open-bg);
		color: var(--lm-accent);
		border-left-color: var(--lm-accent);
	}

	.lightmega-cat.is-expanded .lightmega-cat__count {
		background: var(--lm-accent);
		color: #fff;
	}

	/* ── The disclosure (injected by the JS, mobile only) ──
	   A real button, not a pseudo-element on the entry: the entry is a link to a
	   category archive and has to keep navigating, which means the arrow needs a
	   hit area, a name and a state of its own. */
	.lightmega-cat__toggle {
		display: flex;
		align-items: center;
		justify-content: center;
		box-sizing: border-box;
		min-width: var(--lm-mobile-toggle);
		min-height: var(--lm-mobile-toggle);
		width: 100%;
		padding: 0;
		cursor: pointer;
	}

	/* EVERY visual property of this button is declared HERE, on a doubled class and
	   for every interactive state, and that is a rule rather than a habit: twice now
	   a theme rule has beaten ours on a property we had not thought to claim. First
	   `background` — the chevron column came up gold, from the theme's own <button>
	   styling with a tap-sticky hover. Then `color`: the client's theme ships
	   `button:hover, button:focus { color: #fff }` at (0,1,1), which beat our
	   (0,1,0) and turned the glyph white on a white panel — it vanished after a tap.

	   Doubling the class is (0,2,0), and with a state (0,3,0): it wins regardless of
	   source order, which is the documented remedy in this project. The list is the
	   defence, so it is deliberately complete rather than minimal, and
	   tests/mobile-css.test.php fails if a property leaves it.

	   `border` is reset and then re-declared in the same block, so the divider that
	   continues the row's own bottom line survives the reset that protects it. */
	.lightmega-cat__toggle.lightmega-cat__toggle,
	.lightmega-cat__toggle.lightmega-cat__toggle:hover,
	.lightmega-cat__toggle.lightmega-cat__toggle:focus,
	.lightmega-cat__toggle.lightmega-cat__toggle:focus-visible,
	.lightmega-cat__toggle.lightmega-cat__toggle:active {
		/* Opaque, so it covers whatever was underneath rather than letting it show. */
		background: var(--lm-panel-bg);
		color: var(--lm-muted);
		border: 0;
		border-bottom: 1px solid var(--lm-border);
		box-shadow: none;
		opacity: 1;
		appearance: none;
		-webkit-appearance: none;
	}

	/* EXPANDED is the one state that takes a colour, and it is OURS: the open row
	   tints across its full width, chevron included, so which category is open
	   reads at a glance. Same token as the entry — see --lm-row-open-bg. Declared
	   after the reset above and at (0,3,0), so it also wins over the sticky hover
	   that caused the original report. */
	.lightmega-cat__toggle.lightmega-cat__toggle[aria-expanded="true"] {
		background: var(--lm-row-open-bg);
	}

	/* The glyph stays --lm-muted in every state, and that is measured rather than
	   chosen: on the open row's tint (~#fdf7e9) it gives 4.19:1, while tinting it
	   with --lm-accent to match the entry's text would give 2.51:1 — under the
	   3:1 that WCAG 1.4.11 asks of a non-text graphic. On the resting white it is
	   4.48:1. The rotation is the state indicator; the colour only has to stay
	   legible. */
	.lightmega-cat__toggle::after,
	.lightmega-item__toggle::after {
		content: "\25BE"; /* ▾ */
		font-size: 12px;
		line-height: 1;
		transition: transform 0.2s;
	}

	.lightmega-cat__toggle[aria-expanded="true"]::after,
	.lightmega-item__toggle[aria-expanded="true"]::after {
		transform: rotate(180deg);
	}

	.lightmega-cat__toggle:focus-visible,
	.lightmega-item__toggle:focus-visible {
		outline: 2px solid var(--lm-accent);
		outline-offset: -2px;
	}

	/* ── The menu item's own disclosure ──
	   Built only when NO item in the menu carries a control we could copy — which
	   makes this the default LightMega look, seen by every merchant whose menu has
	   no item with children, and not an emergency branch.

	   Accepted limit, stated rather than guessed at: a neutral arrow, not a copy of
	   the theme's chevron. Where a chevron exists to copy we copy it (see
	   cloneThemeToggles); where none exists there is nothing to approximate, and
	   approximating one badly is worse than clearly not being it.

	   OUT OF FLOW, and that is the lesson from the device pass. This was a grid on
	   the <li>, which lost: the client's theme declares
	   `.main-header-menu .menu-item { display: flex }` at (0,2,0) against our
	   (0,1,0), the full-width link took the whole line, and the chevron wrapped
	   below it. Absolute has no `display` to lose and no line to be pushed off.
	   The containing block is the only imposition, and it is carried by a class we
	   add only when we actually inject — doubled, because `position` is as
	   contestable as anything else here.

	   Pinned top and sized to the tap target rather than stretched top-to-bottom:
	   the panel is a child of the same <li>, so a stretched button would run down
	   the whole open accordion, cover a strip of the cards and swallow their taps.
	   Consequence, accepted: on a row taller than the target the arrow sits at the
	   top of it rather than centred. */
	.lightmega-item--lm-toggle.lightmega-item--lm-toggle {
		position: relative;
	}

	.lightmega-item__toggle {
		position: absolute;
		top: 0;
		right: 0;
		display: flex;
		align-items: center;
		justify-content: center;
		box-sizing: border-box; /* outside .lightmega-panel: no local reset reaches here */
		width: var(--lm-mobile-toggle);
		height: var(--lm-mobile-toggle);
		padding: 0;
		font-size: 12px;
		line-height: 1;
		cursor: pointer;
		z-index: 1;
	}

	/* The same complete defence as the row chevron above, and for the same two
	   theme rules — but resolving to `none` rather than to a colour: this button
	   sits on the THEME's menu surface, and we do not know whether it is light or
	   dark. `color: inherit` for the identical reason; it is the menu's own text
	   colour, which is the only value that cannot be wrong. */
	.lightmega-item__toggle.lightmega-item__toggle,
	.lightmega-item__toggle.lightmega-item__toggle:hover,
	.lightmega-item__toggle.lightmega-item__toggle:focus,
	.lightmega-item__toggle.lightmega-item__toggle:focus-visible,
	.lightmega-item__toggle.lightmega-item__toggle:active {
		background: none;
		color: inherit;
		border: 0;
		box-shadow: none;
		opacity: 1;
		appearance: none;
		-webkit-appearance: none;
	}

	/* ── The CLONED control: the one rule it takes from us, and it is a STATE ──
	   In FLYOUT the theme flips its own arrows, from a rule that a DROPDOWN menu
	   never creates the container for (see the clone's note above the breakpoint):

	       .ast-mobile-popup-content .ast-submenu-expanded > .ast-menu-toggle
	           { transform: rotateX(180deg) }

	   It cannot reach our clone. The `>` wants `.ast-submenu-expanded` on the
	   clone's PARENT — our <li> — and nothing puts it there: not us (it is a theme
	   class, and adding it would arm an `if` in Astra's handler that navigates the
	   visitor away with `window.location`), and not Astra, whose handler never runs
	   on our item because the capture listener on the <li> stops the event first.
	   So the two cannot compose, which was the only reason this rule was dropped,
	   and our marker is the only thing left that can report our state.

	   scaleY(-1), NEVER rotateX — AND THIS IS THE PART NOT TO "ALIGN TO THE THEME"
	   LATER. rotateX is a 3D transform: it turns the element to show its BACK FACE,
	   and WebKit on iOS does not render it. On a Mac and in the simulator the arrow
	   flips; on a real iPhone it VANISHES — which is why every attempt to fix that
	   disappearance with `color` failed, and it is the theme's own defect, not a
	   colour of ours. Resembling the model covers its LOOK, never its bugs. And on
	   an iPhone every browser is WebKit, so this is the whole device, not Safari.
	   scaleY(-1) is the same flip with the same pixels and no 3D context at all.
	   rotate(180deg) was tried and rejected on its own account: with asymmetric
	   horizontal padding it walks the glyph sideways.

	   backface-visibility is belt and braces against an ANCESTOR of the theme's
	   establishing a 3D context around us; a 2D transform creates no back face of
	   its own. The class is doubled to (0,3,0) — equal to the theme's rule, and our
	   stylesheet prints last (the G10a debt, working in our favour for once), so
	   even if that class ever did land on our <li>, rotateX could not take our
	   arrow back to invisible on iOS.

	   NO `transition`: the theme's flip is instantaneous, and animating ours is
	   precisely how our arrow would start behaving differently from the ones
	   beside it. */
	.lightmega-cloned-toggle.lightmega-cloned-toggle[aria-expanded="true"] {
		transform: scaleY(-1);
		-webkit-backface-visibility: visible;
		backface-visibility: visible;
	}

	/* ── The expanded section ── */
	.lightmega-section {
		grid-column: 1 / -1;
		/* The other consumer, and the reason this is a token: the cards have to
		   start on the same column as the category name above them.

		   THE TOKEN APPLIES WHOLE HERE, and that is checked rather than assumed:
		   this element declares no border on desktop or in this block, and the
		   grid inside it has zero horizontal padding, so nothing but this padding
		   stands between the panel's edge and the first card.

		   The value in the absence of a declaration IS 0 — unlike .lightmega-cat,
		   this element has no desktop padding to fall back to, so the two answer
		   that question differently and neither answer is "nothing". */
		padding: 0 var(--lm-mobile-inset);
	}

	.lightmega-section.is-active {
		display: none;
	}

	.lightmega-section.is-expanded {
		display: block;
	}

	/* ~96px columns give three cards on a 375px phone once the theme's drawer
	   padding and ours are taken out, which puts the image at roughly the 80px
	   of the approved mockup — via --lm-card-image-ratio, so a merchant who
	   changed the ratio still gets their ratio.
	   FIXED here, deliberately: card_min_width fits a desktop grid to a
	   catalogue, and the same number cannot also fit a phone. Candidates to
	   become their own mobile parameters after the holidays — these five values
	   and the two font sizes below.
	   The COLUMN COUNT is auto-fill on purpose and is first in that queue: it
	   follows the width the theme's menu actually gives us, which no fixed count
	   can know in advance. The client decides the final number after seeing it on
	   a device, and that decision wants a parameter, not another literal here. */
	.lightmega-grid {
		grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
		gap: 8px;
		padding: 4px 0 14px;
	}

	.lightmega-card {
		min-height: 0;
	}

	/* The 32px bottom padding reserved a strip for the hover arrow. There is no
	   hover here and the arrow is hidden, so the strip is dead space on a card a
	   third of the size. */
	.lightmega-card__body {
		padding: 6px 7px 8px;
	}

	.lightmega-card__link::after {
		display: none;
	}

	/* Badges (G11): the mobile twins of the desktop pair, and whether the badges
	   exist here at all. Declared on the consumer, like every override in this
	   block. `visibility` and not `display` for the third, because `display` is
	   already the layout's property — and a hidden box stays out of the
	   accessibility tree just the same. The markup is still emitted: the server
	   cannot know the viewport, and one cached page is served to both. */
	.lightmega-badges {
		display: var(--lm-badges-display-mobile);
		place-content: var(--lm-badges-place-mobile);
		place-items: var(--lm-badges-place-mobile);
		visibility: var(--lm-badges-visibility-mobile);
	}

	.lightmega-card__title {
		font-size: 10.5px;
		margin-bottom: 2px;
	}

	.lightmega-card__subtitle {
		font-size: 9.5px;
		line-height: 1.35;
		-webkit-line-clamp: 2;
	}
}
