/* Sandbox page chrome — force the whole document to plain white so we're
   building the product layout on a clean surface, regardless of what Astra
   or the active brand would otherwise paint. */
body.ap-sandbox-page,
body.ap-sandbox-page #page,
body.ap-sandbox-page #content,
body.ap-sandbox-page .site-content,
body.ap-sandbox-page .ap-sandbox { background: #fff; }

/* Hide Astra's site header on this template — we paint our own bespoke
   header inside `<main>` (see `.ap-pp__header` below). Scoped to the
   body class so other pages are untouched. */
body.ap-sandbox-page .site-header,
body.ap-sandbox-page header[id="masthead"] {
	display: none !important;
}

/* Same treatment for the site footer — we paint our own bespoke footer
   inside `<main>` (see `.ap-pp__footer` below). */
body.ap-sandbox-page .site-footer,
body.ap-sandbox-page footer[id="colophon"] {
	display: none !important;
}

/* Sticky footer — make `<main>` a full-viewport-tall flex column so the
   bespoke footer can be pushed to the bottom on short pages (e.g. the
   checkout confirmation). `margin-top: auto` on the footer absorbs the
   leftover space; it's a no-op once the content already fills the viewport,
   and works regardless of each page's content-wrapper class. */
body.ap-sandbox-page .ap-sandbox {
	display: flex;
	flex-direction: column;
	min-height: 100vh;
	/* Astra renders `.ast-container` as a flex ROW on WC archives, so
	   `<main>` (a flex item there) shrink-to-fits its content instead
	   of filling the viewport — the live PLP rendered 1008px wide with
	   dead space right (0017 PLP cutover, 2026-07-21). Inert on sandbox
	   pages, whose wrappers are plain blocks. */
	width: 100%;
}
/* The Customizer "Additional CSS" (admin content, post 3733) carries
   `body.woocommerce.archive #primary { width: 70% }` at (1,2,0) — the
   OLD sidebar-based archive's split, which capped the modern PLP's
   `<main>` too. Out-specify it at (1,3,0) rather than editing DB
   content that wouldn't travel to staging/prod with git; purging the
   legacy archive rules from the Customizer CSS is relaunch hygiene. */
body.woocommerce.archive.ap-sandbox-page #primary {
	width: 100%;
}
body.ap-sandbox-page .ap-sandbox > .ap-pp__footer {
	margin-top: auto;
}

/* Below-the-fold footer — the footer must never show inside the initial
   viewport; it only appears once the visitor scrolls. Every sandbox page
   is header + one content wrapper + footer inside the `.ap-sandbox` flex
   column, so pinning the content wrapper to (viewport − header) makes
   header + content fill exactly 100vh and pushes the footer's top edge
   to the fold on short pages (taller pages are unaffected — this is a
   min). `border-box` so the wrappers' own paddings count toward the
   fill rather than overshooting it. */
body.ap-sandbox-page .ap-sandbox > :is(.ap-pp, .ap-co, .ap-tpl, .ap-info, .ap-auth, .ap-profile, .ap-order, .ap-lp, .ap-contacts) {
	box-sizing: border-box;
	min-height: calc(100vh - 72px); /* 72px = desktop header height */
}
@media (max-width: 768px) {
	body.ap-sandbox-page .ap-sandbox > :is(.ap-pp, .ap-co, .ap-tpl, .ap-info, .ap-auth, .ap-profile, .ap-order, .ap-lp, .ap-contacts) {
		min-height: calc(100vh - 60px); /* 60px = mobile header height */
	}
}


/* ─────────────────────────────────────────────────────────────────────
   `.ap-info` component family — static info/text pages (delivery &
   returns, privacy policy, terms & conditions). Originally lived inline
   in `delivery-returns-sandbox.php`; lifted here when the family grew
   to three pages so every info page shares one stylesheet. The pages
   themselves are pure markup: title + `.ap-info__section` blocks of
   headings, paragraphs and lists.
   ───────────────────────────────────────────────────────────────────── */

/* Info-page body wrapper — horizontal page insets pin the content to
   the outer edge of grid column 4 / column 9 (`--grid-col4-inset`;
   ≈ 536px at the 1920 frame), so the text spans the middle six
   columns. 64px gap to the header's bottom edge, 88px gap to the
   footer's top edge, per the family baseline. */
.ap-info {
	padding: 64px var(--grid-col4-inset, 536px) 88px;
}

/* Page title — `.type-heading-lg` (Special Gothic Expanded One 44/1.1,
   0.025em ≈ 1.1px tracking, uppercase) carries the type per the
   Heading/LG/Regular spec; this only owns the colour and centring.
   The 64px gap to the header's bottom edge is the wrapper's top
   padding — the sticky header is in-flow, so padding measures from
   its real bottom edge. */
.ap-info__title {
	color: var(--text-primary, #191919);
	text-align: center;
}

/* Section — one titled block of copy. 48px below the title / the
   previous section. */
.ap-info__section {
	margin-top: var(--space-48, 48px);
}
/* Deep-link targets (sandbox sections + the slugified h2/h3 ids the
   legal-info root template stamps onto page copy) land clear of the
   72px sticky header, with a 24px breather. */
.ap-info__section[id],
.ap-info__content :is(h2, h3)[id] {
	scroll-margin-top: calc(72px + var(--space-24, 24px));
}

/* Section title — `.type-heading-md` (Special Gothic Expanded One
   32/1.1, 0.025em ≈ 0.8px tracking, uppercase) per the
   Heading/MD/Regular spec; this only owns the colour. */
.ap-info__section-title {
	color: var(--text-primary, #191919);
}

/* Sub-heading — numbered lead-ins split out of their body paragraphs
   into their own element (e.g. "1. Pristatymas į namus" on the
   delivery page). `.type-body-md-medium` (Outfit 16/24/500) per the
   Body/MD/Medium spec; the 16px top margin is the specced gap below
   the section title. */
.ap-info__subtitle {
	margin-top: var(--space-16, 16px);
	color: var(--text-primary, #191919);
}

/* Body copy — `.type-body-md-regular` (Outfit 16/24/400), secondary
   colour per spec, 16px rhythm between paragraphs. No inline
   bold/italic emphasis in body copy for now (per design). */
.ap-info__text {
	margin-top: var(--space-16, 16px);
	color: var(--text-secondary, #5C5C5C);
}
/* A body paragraph directly under its numbered sub-heading hugs it —
   the pair reads as one block, so no 16px rhythm gap between them. */
.ap-info__subtitle + .ap-info__text {
	margin-top: 0;
}

/* Inline link — the neutraliser strips anchor colour + underline at
   rest, so opt back in per spec (secondary colour, from-font underline
   position, everything else at its auto default); every interactive
   state is pinned per the brand-wash contract so multi-brand-uni's
   accent never leaks in. */
.ap-info__link {
	color: var(--text-secondary, #5C5C5C);
	text-decoration: underline;
	text-underline-position: from-font;
}
.ap-sandbox .ap-info__link:hover,
.ap-sandbox .ap-info__link:focus,
.ap-sandbox .ap-info__link:focus-visible,
.ap-sandbox .ap-info__link:active {
	color: var(--text-secondary, #5C5C5C);
	text-decoration: underline;
	background-color: transparent;
	outline: none;
	box-shadow: none;
}

/* Content lists — the neutraliser strips `list-style` + indent from
   bare ul/ol (they double as structural containers elsewhere); these
   are genuine text lists, so opt markers + indent back in. 16px below
   the preceding paragraph, 8px between items. */
.ap-info__list {
	margin-top: var(--space-16, 16px);
	padding-left: var(--space-24, 24px);
	list-style: disc;
	color: var(--text-secondary, #5C5C5C);
}
.ap-info__list--ordered {
	list-style: decimal;
}
.ap-info__list li + li {
	margin-top: var(--space-8, 8px);
}

/* ── Live info pages: raw post_content mapping ──────────────────────
   The three sandbox info pages hardcode `.ap-info__section` markup with
   `.type-*` utility classes. The LIVE pages (page-templates/legal-info.php)
   render the WP page's own post_content instead ("layout in code, content
   in DB"), which emits bare WP-block elements — h2–h6, p, ul/ol, a,
   strong — carrying NO `.type-*` class. Without help the global
   `h1–h6 { font-family: Racing Sans One }` rule (0,0,1) would paint them
   42px display and the `.ap-sandbox` neutraliser would strip list markers
   and <p> rhythm (it only opts those back in on the `.ap-info__*` classes).
   These `.ap-info__content …` rules (0,1,1) re-apply the `.ap-info`
   brandbook scale to whatever markup the page author wrote. The section
   heading level varies per page (privacy=h2, delivery=h3, terms=h4), so
   h2–h4 ALL map to the section-title scale; the mobile token remap on
   `.ap-info` cascades in automatically. Found with real data 2026-07-30
   (Gvidas sign-off pending). Scoped to `.ap-info__content` so the sandbox
   `.ap-info__section` pages stay untouched. */
/* Title → body gap: the sandbox's first `.ap-info__section` sits 48px
   below the title; the live content wrapper reproduces that on itself so
   its own first child can stay flush (no doubled rhythm). */
.ap-info__content {
	margin-top: var(--space-48, 48px);
}
.ap-info__content > :first-child {
	margin-top: 0;
}
.ap-info__content :is(h2, h3, h4) {
	margin-top: var(--space-48, 48px);
	font-family: var(--font-family-display);
	font-weight: var(--font-weight-regular);
	font-size: var(--font-size-32);
	line-height: 1.1;
	letter-spacing: 0.025em;
	text-transform: uppercase;
	color: var(--text-primary, #191919);
}
.ap-info__content :is(h5, h6) {
	margin-top: var(--space-24, 24px);
	font-family: var(--font-family-ui);
	font-weight: var(--font-weight-medium);
	font-size: var(--font-size-16);
	line-height: 1.5;
	color: var(--text-primary, #191919);
}
.ap-info__content p {
	margin-top: var(--space-16, 16px);
	font-family: var(--font-family-ui);
	font-weight: var(--font-weight-regular);
	font-size: var(--font-size-16);
	line-height: 1.5;
	color: var(--text-secondary, #5C5C5C);
}
.ap-info__content :is(ul, ol) {
	margin-top: var(--space-16, 16px);
	padding-left: var(--space-24, 24px);
	font-family: var(--font-family-ui);
	font-size: var(--font-size-16);
	line-height: 1.5;
	list-style: disc;
	color: var(--text-secondary, #5C5C5C);
}
.ap-info__content ol {
	list-style: decimal;
}
.ap-info__content li + li {
	margin-top: var(--space-8, 8px);
}
/* Section separators — the terms page's post_content carries
   `<hr class="wp-block-separator">` between sections; the sandbox spec
   has NO separators (sections separate by the 48px rhythm alone), but
   wp-includes/blocks/separator CSS paints them as visible centred
   rules. Hide any hr in live content. Found with real data 2026-08-19. */
.ap-info__content hr {
	display: none;
}
/* Inline emphasis — the sandbox spec has NO bold in body copy ("no
   inline bold/italic emphasis in body copy for now (per design)"), but
   the live pages' post_content carries <strong> around names/addresses.
   Flatten it to plain body text so live matches the sandbox. Found with
   real data 2026-08-19 (supersedes the 2026-07-30 medium/primary map). */
.ap-info__content strong {
	font-weight: inherit;
	color: inherit;
}
.ap-info__content a {
	color: var(--text-secondary, #5C5C5C);
	text-decoration: underline;
	text-underline-position: from-font;
}
.ap-sandbox .ap-info__content a:hover,
.ap-sandbox .ap-info__content a:focus,
.ap-sandbox .ap-info__content a:focus-visible,
.ap-sandbox .ap-info__content a:active {
	color: var(--text-secondary, #5C5C5C);
	text-decoration: underline;
	background-color: transparent;
	outline: none;
	box-shadow: none;
}
/* The Complianz cookie statement (slapuku-politika-es) embeds real form
   controls — per-service consent checkboxes — that the neutraliser's
   input flattening (appearance/border/background all stripped) renders
   invisible. Opt them back into native chrome; text inputs stay flat. */
.ap-info__content input[type="checkbox"],
.ap-info__content input[type="radio"] {
	-webkit-appearance: auto;
	-moz-appearance: auto;
	appearance: auto;
}

/* Mobile (≤768px) — 24px insets per the sibling sandboxes' mobile
   baseline; the token remap scales the brandbook type once at the
   wrapper (44→34 for the `.type-heading-lg` title, 32→27 for the
   `.type-heading-md` section titles — everything typographic on an
   info page lives inside `.ap-info`, so the remap scopes here rather
   than on `.ap-sandbox`) and every `.type-*` class keeps carrying
   the typography. */
@media (max-width: 768px) {
	.ap-info {
		--font-size-44: 34px;
		--font-size-32: 27px;
		padding: 24px 24px 64px;
	}
}


/* ─────────────────────────────────────────────────────────────────────
   Astra neutraliser. Astra's customizer-generated CSS, the child theme's
   `.entry-content` rules, and multi-brand-uni's brand wash all paint
   resting styles on bare `h1`-`h6`, `input`, `textarea`, `select`,
   `button`, `a`, `p`, `ul`, and `ol`. Every sandbox component was
   fighting these in its own rule (`margin: 0`, `text-transform: none`,
   `appearance: none`, `border: 0`, etc.) — this block fights them once,
   centrally, so new components start on a blank canvas.

   Specificity contract: every selector below is wrapped in
   `:where(.ap-sandbox)` so the parent contributes ZERO specificity.
   `:where(.ap-sandbox) h1` is (0,0,1) — strictly weaker than any class
   selector. That means the brandbook utility classes
   (`.type-heading-md`, `.type-body-sm-regular`, …) and every per-
   component rule (`.ap-pp__cart-added__title`, `.ap-co__item__qty-input`,
   `.ap-pp__cart-added__btn--primary`, …) win cleanly without `!important`
   and without ratcheting up specificity in every component rule.

   The universal accent-wash kill further down handles hover/focus/
   active leaks — this block only addresses RESTING values. Together
   the two blocks form the contract: sandbox components inherit a
   blank-canvas baseline at rest AND a defanged brand-wash at every
   interactive state.
   ───────────────────────────────────────────────────────────────────── */

/* Headings — Astra customizer paints uppercase + display tracking +
   Astra font + heading margins on h1-h6 globally. Strip everything;
   the `.type-heading-*` utility classes (or per-component rules) opt
   back in to whatever typography they want. */
:where(.ap-sandbox) :is(h1, h2, h3, h4, h5, h6) {
	margin: 0;
	padding: 0;
	color: inherit;
	font: inherit;
	text-transform: none;
	letter-spacing: normal;
}

/* Paragraphs — `.entry-content p` paints a ~1.75em bottom margin;
   components own their own rhythm via `margin-top` on each row. */
:where(.ap-sandbox) p {
	margin: 0;
}

/* Form controls — Astra paints border + box-shadow + 8px border-radius
   + 2px border-width via the `form input/textarea/select` rule and the
   WebKit `<input type="search">` chrome adds spin/clear/decoration
   pseudos on top. Strip all of it; components opt back in to whatever
   chrome they want via their own class rules. */
:where(.ap-sandbox) :is(input, textarea, select) {
	-webkit-appearance: none;
	-moz-appearance: none;
	appearance: none;
	margin: 0;
	padding: 0;
	border: 0;
	border-radius: 0;
	background: transparent;
	color: inherit;
	font: inherit;
	box-shadow: none;
}
:where(.ap-sandbox) input::-webkit-inner-spin-button,
:where(.ap-sandbox) input::-webkit-outer-spin-button,
:where(.ap-sandbox) input[type="search"]::-webkit-search-cancel-button,
:where(.ap-sandbox) input[type="search"]::-webkit-search-decoration,
:where(.ap-sandbox) input[type="search"]::-webkit-search-results-button,
:where(.ap-sandbox) input[type="search"]::-webkit-search-results-decoration {
	-webkit-appearance: none;
	margin: 0;
}

/* Buttons — Astra paints global border-radius + uppercase + tracking
   + padding via the customizer's "Buttons" panel. Strip everything;
   `.ap-pp__*` / `.ap-co__*` button classes own their resting visuals. */
:where(.ap-sandbox) button {
	margin: 0;
	padding: 0;
	border: 0;
	border-radius: 0;
	background: transparent;
	color: inherit;
	font: inherit;
	text-transform: none;
	letter-spacing: normal;
	line-height: inherit;
}

/* Anchors — Astra paints underline + brand colour at rest. Strip both;
   each anchor that wants a visible state opts in via its own class. */
:where(.ap-sandbox) a {
	color: inherit;
	text-decoration: none;
	background-color: transparent;
}

/* Lists — strip Astra's default `list-style` and indent so flex/grid
   layouts can use `<ul>`/`<ol>` as structural containers. */
:where(.ap-sandbox) :is(ul, ol) {
	margin: 0;
	padding: 0;
	list-style: none;
}


/* ─────────────────────────────────────────────────────────────────────
   Shared input primitive — `.ap-pp__personalize__field` (wrapper) +
   `.ap-pp__personalize__field--number` (narrow modifier) +
   `.ap-pp__personalize__field-input` (input chrome). Numeric/text
   field with a white fill, 1px gray-200 border, 15px radius, sized
   for a 2-digit value in the `--number` variant.

   Originally lived in product-page-sandbox.php; lifted here so the
   checkout sandbox (and any future sandbox) can reuse the full
   wrapper-around-input pattern verbatim. The `-head` / `-label` /
   `-counter` rules stay local to the product page since they're
   specific to its label+counter chip row.

   Hover/focus/active re-assert the resting bg + border at (0,3,1) to
   beat the universal accent-wash kill (`.ap-sandbox input:hover` at
   (0,2,1)) which would otherwise wipe the fill on first interaction.
   ───────────────────────────────────────────────────────────────────── */

/* Wrapper — column-flex with a 4px gap so a label/counter row (when
   present) sits 4px above the input. `flex: 1; min-width: 0` lets the
   wrapper grow inside row-flex parents; the `--number` modifier opts
   out and pins to 96px wide. */
.ap-pp__personalize__field {
	display: flex;
	flex-direction: column;
	gap: 4px;
	flex: 1;
	min-width: 0;
}
.ap-pp__personalize__field--number {
	flex: 0 0 96px;
}

/* Scoped under `.ap-sandbox` (0,2,0) so the resting chrome beats Astra's
   `.entry-content input[type="text"]` (0,1,1), which would otherwise force
   its own border + radius + padding onto the input. */
.ap-sandbox .ap-pp__personalize__field-input {
	display: flex;
	height: 60px;
	padding: 10px var(--space-12, 12px);
	align-items: center;
	gap: var(--space-8, 8px);
	align-self: stretch;
	box-sizing: border-box;
	border: 1px solid var(--gray-200, #E4E4E4);
	border-radius: var(--radius-15, 15px);
	background: var(--basic-white, #FFF);
}
.ap-sandbox .ap-pp__personalize__field-input::placeholder {
	color: var(--gray-400, #A3A3A3);
	opacity: 1;
}
.ap-sandbox .ap-pp__personalize__field-input:hover,
.ap-sandbox .ap-pp__personalize__field-input:focus,
.ap-sandbox .ap-pp__personalize__field-input:focus-visible,
.ap-sandbox .ap-pp__personalize__field-input:active {
	background: var(--basic-white, #FFF);
	color: var(--text-primary, #191919);
	border-color: var(--gray-200, #E4E4E4);
	outline: none;
	box-shadow: none;
}

/* Error state — `.is-invalid` on the field wrapper turns the input border
   to the error token. Four classes (0,4,0) outrank the interactive-state
   lock above (0,3,1), so the red border holds at rest and through
   hover/focus without re-listing the states. Each page owns its error
   message element; this rule only owns the border. */
.ap-sandbox .ap-pp__personalize__field.is-invalid .ap-pp__personalize__field-input {
	border-color: var(--input-border-error, #FF0014);
}


/* ─────────────────────────────────────────────────────────────────────
   Sandbox header — bespoke. 72px tall desktop / 60px tall mobile;
   full-width with 48px (desktop) / 8px (mobile) horizontal inset.
   Painted in `--navigation-background` (dark) with white menu text via
   the navigation tokens. Lives inside `<main class="ap-sandbox">`, so
   the page's brand-wash defang applies to its anchors automatically;
   we still pin all five hover/focus/active props at the resting value
   so a brand-coloured anchor wash can't leak through.
   ───────────────────────────────────────────────────────────────────── */
.ap-pp__header {
	/* `sticky` keeps the dark header docked to the viewport top while the
	   page scrolls behind it (both breakpoints). It also serves as the
	   positioning context for the centred logo — `sticky`, like `relative`,
	   establishes a containing block for absolutely-positioned children. */
	position: sticky;
	top: 0;
	height: 72px;
	box-sizing: border-box;
	/* Asymmetric per the header frame (385-103): nav text starts 48px
	   in; the actions row ends at 32px — its outer icon shell carries
	   14px internal padding, so the bag glyph's visual inset (46px)
	   mirrors the nav's 48px. */
	padding: 0 32px 0 48px;
	background: var(--navigation-background, #0A0A0A);
	color: var(--navigation-menu-item-enabled, #FFF);
	display: flex;
	align-items: center;
	justify-content: space-between;
	/* Sits one tier above the burger panel (z-index: 1100) so the header
	   always paints on top of the panel anywhere they meet — the panel
	   reads as a sheet dropping *under* the header, never overlapping
	   into it. */
	z-index: 1101;
}

/* Header logo lockup — true viewport-centre, independent of nav/actions
   widths. Absolute + 50%/translate(-50%) keeps the group pinned even if
   either side grows (more category links, a wider actions row, etc.).
   On neutral pages it holds a single child — the AGO mark; on themed
   pages it expands into a multi-part lockup (AGO + separator + tenant
   logo) without changing this rule (the outer auto-sizes via inline-flex
   and the 12px gap is inert when there's only one child). */
.ap-pp__header__logo {
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	display: inline-flex;
	align-items: center;
	gap: 12px;
}

/* AGO mark — original 81×36 single-image slot, now nested inside the
   logo lockup container. Bg-image is applied inline via the partial.
   `flex` + centring kept so the slot can host SVG/IMG content in the
   future without rewriting the rule; today the PNG renders via
   background-image + `contain` so it never stretches. */
.ap-pp__header__logo__ago {
	display: flex;
	width: 81px;
	height: 36px;
	aspect-ratio: 9 / 4;
	justify-content: center;
	align-items: center;
	background-color: transparent;
	background-position: 50% 50%;
	background-size: contain;
	background-repeat: no-repeat;
}

/* Themed lockup separator — 17×17 inline icon between the AGO mark
   and the tenant logo. Wrapping <span> sized so the SVG fills it
   1:1 without distortion. Class only renders on themed pages
   (currently the Vilniaus Rytas variant), so no defensive scoping
   is needed. */
.ap-pp__header__logo__separator {
	display: inline-flex;
	width: 17px;
	height: 17px;
}
.ap-pp__header__logo__separator img {
	display: block;
	width: 100%;
	height: 100%;
}

/* Themed tenant logo — 48×48 slot, bg-image inline via the partial,
   `contain` so the source never stretches. Same neutral chrome as
   the AGO mark above, just sized for the themed variant. */
.ap-pp__header__logo__tenant {
	width: 48px;
	height: 48px;
	background-color: transparent;
	background-position: 50% 50%;
	background-size: contain;
	background-repeat: no-repeat;
}
/* Wordmark tenant slot — for wide marks (UTMA lockup, frame 5:612:
   138×38). `contain` keeps the source's ratio inside the box; the
   partial adds the modifier when the template sets
   `$ap_chrome_tenant_logo_wordmark`. Mobile scales it ÷1.5 below,
   like the crest slot. */
.ap-pp__header__logo__tenant--wordmark {
	width: 138px;
	height: 38px;
}

/* Left-side category nav. 40px gap between items per spec. Anchors
   pinned to the navigation token at every state so brand-wash and
   :visited can't repaint them.

   Nav + anchors `align-self: stretch` so each anchor fills the full
   72px header height; that lets the active-state `::after` underline
   sit at `bottom: 0` and land flush with the header's bottom edge
   without measuring header-height in JS. Text inside the anchor
   stays vertically centred via the inner flex (`align-items: center`). */
.ap-pp__header__nav {
	display: flex;
	gap: 40px;
	align-items: stretch;
	align-self: stretch;
}
.ap-pp__header__nav-item,
.ap-pp__header__nav-item:visited {
	position: relative;
	display: inline-flex;
	align-items: center;
	color: var(--navigation-menu-item-enabled, #FFF);
	text-decoration: none;
}
.ap-sandbox .ap-pp__header__nav-item:hover,
.ap-sandbox .ap-pp__header__nav-item:focus,
.ap-sandbox .ap-pp__header__nav-item:focus-visible,
.ap-sandbox .ap-pp__header__nav-item:active {
	color: var(--navigation-menu-item-enabled, #FFF);
	background-color: transparent;
	border-color: transparent;
	outline: none;
	box-shadow: none;
}
/* Active-state underline — 4px white bar with rounded top corners,
   glued to the header's bottom edge (anchor stretches full height,
   so `bottom: 0` resolves there). Toggled by `.is-active` from JS;
   opacity-transitioned ~200ms ease-out per the project's animation
   style. Burger variant opts out — `.ap-pp__header__nav-item--burger`
   lives in the mobile drawer, not on a dark header. */
.ap-pp__header__nav-item:not(.ap-pp__header__nav-item--burger)::after {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 4px;
	background: var(--gray-0, #FFF);
	border-radius: var(--radius-8, 8px) var(--radius-8, 8px) 0 0;
	opacity: 0;
	transition: opacity 200ms ease-out;
	pointer-events: none;
}
.ap-pp__header__nav-item.is-active::after {
	opacity: 1;
}

/* Mega-menu panel — white sheet that drops below the header when a
   top-level nav anchor is activated. Absolute-positioned inside the
   sticky header so the panel sticks to the header on scroll without
   a JS reposition loop (sticky parents establish a containing block
   for absolutely-positioned descendants). 24px gap from the header's
   bottom edge, 24px insets from both viewport sides; inner padding
   48px vertical / 64px horizontal per spec. Empty by default — the
   shape only at this stage; content arrives in a follow-up.

   Hidden at rest via `opacity: 0 + visibility: hidden`; opens via
   `.is-open` (toggled from JS in lockstep with the underline). Small
   `translateY(-8px) → 0` slide on opacity-fade for an Apple-HIG feel
   (~240ms ease-out). `visibility` is transitioned with a 0s delay on
   open and a delayed-tail on close so the panel doesn't intercept
   pointer events after fading out. Mobile hides this — the desktop
   nav is `display: none` there, so the panel has no trigger anyway. */
.ap-pp__header__mega {
	position: absolute;
	top: calc(100% + 24px);
	left: 24px;
	right: 24px;
	/* 64px top + left per spec so the categories column lands flush in
	   that corner of the white sheet; bottom keeps the original 48px,
	   right keeps the original 64px (results column stays on its anchor). */
	padding: 64px 64px 48px 64px;
	background: var(--gray-0, #FFF);
	border-radius: var(--radius-15, 15px);
	/* Inner flow: row. Categories sit at flex-start (left edge of the
	   padding box); results are pushed to the trailing edge via
	   `margin-left: auto` on `.ap-pp__header__mega__results`. */
	display: flex;
	justify-content: flex-start;
	align-items: flex-start;
	opacity: 0;
	visibility: hidden;
	transform: translateY(-8px);
	transition: opacity 240ms ease-out,
				transform 240ms ease-out,
				visibility 0s linear 240ms;
}
.ap-pp__header__mega.is-open {
	opacity: 1;
	visibility: visible;
	transform: translateY(0);
	transition: opacity 240ms ease-out,
				transform 240ms ease-out,
				visibility 0s linear 0s;
}

/* Mega-panel backdrop — fixed full-viewport dim layer behind the panel
   and above the page. Sibling of `.ap-pp__header` (not a child) so it
   sits outside the header's z:1101 stacking context; placed at z:1100
   so the header (1101) paints on top of it — backdrop dims the page
   AND the 24px gap between header and panel, but the header itself
   stays crisp. The mega panel paints inside the header's stacking
   context, so it lands above the backdrop too. Click on the backdrop
   bubbles to document → outside-click handler closes the panel. */
.ap-pp__header__mega-backdrop {
	position: fixed;
	inset: 0;
	z-index: 1100;
	background: var(--backdrop-background-light, rgba(10, 10, 10, 0.30));
	opacity: 0;
	visibility: hidden;
	transition: opacity 240ms ease-out,
				visibility 0s linear 240ms;
}
.ap-pp__header__mega-backdrop.is-open {
	opacity: 1;
	visibility: visible;
	transition: opacity 240ms ease-out,
				visibility 0s linear 0s;
}

/* Mega-panel product results — row of fixed-size product cards on the
   right side of the panel. 8px gap between cards in the row; each card
   is a 322×427 image followed by a 16px gap → title → 8px gap → price.
   Title/price are inset 8px from the image's left edge per spec (same
   `padding-left: 8px` shape as `.ap-pp__collection__item__*`). Card is
   an anchor; brand-wash + Astra anchor globals are pinned at every
   state so neither can repaint title or price colour on hover/focus. */
.ap-pp__header__mega__results {
	display: flex;
	gap: 8px;
	/* Pushed to the trailing edge of the padding box; categories column
	   sits at flex-start, results column hugs the right. */
	margin-left: auto;
	/* Per spec: results column starts 48px from the panel top while the
	   categories column keeps the panel's 64px top padding. Pull up 16px. */
	margin-top: -16px;
}

/* Mega-panel categories column — left-side stack of category links,
   same row options as the mobile burger category view but without the
   chevron. Reuses `.ap-pp__header__nav-item--burger` on each anchor for
   the light-context dark-text + brand-wash lock; this wrapper only owns
   the column flow + the 16px inter-row gap per spec. Typography comes
   from `type-heading-sm-regular` (Outfit 20/1.1, weight 400) on each
   anchor — matches the Figma values pasted in the brief. */
.ap-pp__header__mega__categories {
	display: flex;
	flex-direction: column;
	gap: 16px;
}

/* Sibling-dim hover/focus pattern — every row list of
   `.ap-pp__header__nav-item--burger` defaults to `--text-primary` for
   every row (via the base rule on `.ap-pp__header__nav-item--burger`
   at the next block). When the user hovers / focuses / activates one
   row, that row stays primary (brand-wash lock below) while the others
   dim to `--text-tertiary`. The chevron strokes `currentColor`, so it
   tracks the row's text colour at every state.

   Applies to the three places that stack these rows vertically: the
   mobile root burger view (Mens/Womens/Kids), the mobile burger
   category view (sub-category list), and the desktop mega-panel left
   categories column. `:has()` + `:not()` together: the parent matches
   when ANY row is interacted with, and the negation excludes the row
   currently being interacted with, so only the siblings dim. */
:is(.ap-pp__burger-view--root, .ap-pp__burger-view--category, .ap-pp__header__mega__categories):has(.ap-pp__header__nav-item--burger:is(:hover, :focus-visible, :active)) .ap-pp__header__nav-item--burger:not(:is(:hover, :focus-visible, :active)) {
	color: var(--text-tertiary, #787878);
}
.ap-pp__header__mega__product,
.ap-pp__header__mega__product:visited {
	display: flex;
	flex-direction: column;
	/* Basis 322px but shrinkable — three rigid tiles (982px) overflow
	   the panel on narrower desktop viewports; the media below keeps
	   its 322:427 ratio while the card scales. */
	flex: 0 1 322px;
	min-width: 0;
	color: var(--text-primary, #191919);
	text-decoration: none;
}
.ap-pp__header__mega__product__media {
	width: 100%;
	aspect-ratio: 322 / 427;
	background-color: #D9D9D9;
	background-position: 50% 50%;
	background-size: cover;
	background-repeat: no-repeat;
}
.ap-pp__header__mega__product__title {
	align-self: stretch;
	margin: 16px 0 0;
	padding-left: 8px;
	color: var(--text-primary, #191919);
	text-transform: none;
}
.ap-pp__header__mega__product__price {
	align-self: stretch;
	margin: 8px 0 0;
	padding-left: 8px;
	color: var(--text-primary, #191919);
}
.ap-sandbox .ap-pp__header__mega__product:hover,
.ap-sandbox .ap-pp__header__mega__product:focus,
.ap-sandbox .ap-pp__header__mega__product:focus-visible,
.ap-sandbox .ap-pp__header__mega__product:active {
	color: var(--text-primary, #191919);
	background-color: transparent;
	border-color: transparent;
	outline: none;
	box-shadow: none;
}

/* Burger variant — same nav semantics (Mens / Womens / Kids), rendered
   as full-width rows with a trailing chevron. Lives in the mobile burger
   panel as direct children (no wrapping `<nav>`); the desktop horizontal
   nav rule above is untouched. Per-row layout is `space-between` so the
   chevron always hugs the right edge regardless of text length. Column
   flow + inter-row spacing are owned by `.ap-pp__burger-panel`. */
.ap-pp__header__nav-item--burger,
.ap-pp__header__nav-item--burger:visited {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
	color: var(--text-primary, #191919);
}
.ap-pp__header__nav-item--burger img,
.ap-pp__header__nav-item--burger .ap-pp__header__nav-item__chevron {
	display: block;
	width: 24px;
	height: 24px;
	flex: 0 0 24px;
}
/* Kolekcijos mega — brand rows (logo + name), left-aligned. Overrides the
   burger row's space-between + 24px img (that's the chevron variant); the
   logo is the tenant's, resolved from brand data. Same specificity as the
   img rule above, so source order wins. */
.ap-pp__header__mega__brand,
.ap-pp__header__mega__brand:visited {
	justify-content: flex-start;
	gap: 12px;
}
.ap-pp__header__mega__brand img {
	width: 48px;
	height: 48px;
	flex: 0 0 48px;
	object-fit: contain;
}
/* Brand-wash + Astra hover lock — pinned to resting at every state so
   neither multi-brand-uni nor Astra's anchor globals can repaint the
   row. Five properties per the brand-wash memory. */
.ap-sandbox .ap-pp__header__nav-item--burger:hover,
.ap-sandbox .ap-pp__header__nav-item--burger:focus,
.ap-sandbox .ap-pp__header__nav-item--burger:focus-visible,
.ap-sandbox .ap-pp__header__nav-item--burger:active {
	color: var(--text-primary, #191919);
	background-color: transparent;
	border-color: transparent;
	outline: none;
	box-shadow: none;
}

/* Right-side action icons — row of 48×48 shells, edge-to-edge with
   their neighbours (gap: 0). DOM order is search → profile → bag so
   the bag sits on the right edge as specified. The SVGs ship with a
   hard-coded white stroke, so the icon glyphs render white on the
   dark header without any colour override; the shell itself is
   transparent. */
.ap-pp__header__actions {
	display: flex;
	/* 8px between the 48px icon shells (search/heart/user/bag) per the
	   header frame — same rhythm the mobile block always had. */
	gap: var(--space-8, 8px);
	align-items: center;
}
.ap-pp__header__icon {
	display: inline-flex;
	width: 48px;
	height: 48px;
	padding: 0;
	margin: 0;
	border: 0;
	border-radius: 0; /* Astra applies a global radius on <button>; pin to 0 for clean shells */
	background: transparent;
	align-items: center;
	justify-content: center;
	cursor: pointer;
}
.ap-pp__header__icon img {
	display: block;
	width: 20px;
	height: 20px;
}
/* Brand-wash + Astra hover lock — pinned to resting (transparent shell)
   at every state. Five properties per the brand-wash memory. */
.ap-sandbox .ap-pp__header__icon:hover,
.ap-sandbox .ap-pp__header__icon:focus,
.ap-sandbox .ap-pp__header__icon:focus-visible,
.ap-sandbox .ap-pp__header__icon:active {
	background-color: transparent;
	color: inherit;
	border-color: transparent;
	outline: none;
	box-shadow: none;
}

/* Count badge on the heart + bag shells — `.ap-badge--count` (Badge
   component, main.min.css) owns the 16px inverse pill; this rule only
   places it. Tangent to the glyph, not over it: the 20px glyph sits
   14px inside the 48px shell, and a 16px circle at the shell's own
   top-right corner touches the glyph's corner at 45° without crossing
   the 1.5px stroke — the system never overlays its outline glyphs.
   Wider counts ("12", "99+") grow leftwards from that right edge.
   Zero → `hidden`; the component's inline-flex must yield to it. */
.ap-pp__header__icon {
	position: relative;
}
.ap-pp__header__badge {
	position: absolute;
	top: 0;
	right: 0;
	pointer-events: none;
}
.ap-pp__header__badge[hidden] {
	display: none;
}

/* Burger group — wraps the burger button and the mobile-only search
   shell so the two ride together with an 8px gap on mobile. Hidden on
   desktop entirely so the header's `space-between` keeps placing the
   categories nav on the left edge and actions on the right. */
.ap-pp__header__burger-group { display: none; }

/* Mobile-only search shell — same 48x48 button chrome as the other
   header icons; only revealed in the mobile breakpoint below where it
   sits 8px to the right of the burger. The desktop search lives in
   `.ap-pp__header__actions` and stays as-is. */
.ap-pp__header__icon--mobile-search { display: none; }

/* Burger — desktop hides it; the mobile rule below reveals it and the
   categories nav hides on mobile in the same place.

   Two icons live inside the button (menu glyph + X glyph), stacked on top
   of each other via absolute positioning. The swap is a cross-fade + 90deg
   rotate driven by `aria-expanded`, easing on iOS's standard out-curve
   (`cubic-bezier(0.32, 0.72, 0, 1)`, ~250ms) so it reads as a single morph
   rather than a hard hide/show. JS only flips the attribute. */
.ap-pp__header__icon--burger { display: none; position: relative; }
.ap-pp__header__icon--burger .ap-pp__header__icon__open,
.ap-pp__header__icon--burger .ap-pp__header__icon__close {
	position: absolute;
	top: 50%;
	left: 50%;
	transition: opacity 250ms cubic-bezier(0.32, 0.72, 0, 1),
				transform 250ms cubic-bezier(0.32, 0.72, 0, 1);
}
.ap-pp__header__icon--burger .ap-pp__header__icon__open  { opacity: 1; transform: translate(-50%, -50%) rotate(0);      }
.ap-pp__header__icon--burger .ap-pp__header__icon__close { opacity: 0; transform: translate(-50%, -50%) rotate(-90deg); }
.ap-pp__header__icon--burger[aria-expanded="true"] .ap-pp__header__icon__open  { opacity: 0; transform: translate(-50%, -50%) rotate(90deg); }
.ap-pp__header__icon--burger[aria-expanded="true"] .ap-pp__header__icon__close { opacity: 1; transform: translate(-50%, -50%) rotate(0);     }

/* Mobile burger drawer — drops down below the header on burger open.
   The drawer is a fixed wrapper holding two stacked children:

     1. `.ap-pp__burger-panel` — full-viewport-width white sheet (min 390px
        tall, bottom-only 15px radius, top edges flush against the header).
     2. `.ap-pp__burger-close` — pill close button, centred 24px below the
        sheet (`gap: 24px` on the column flow).

   Wrapping both in one fixed container means the JS only positions the
   wrapper at the header's bottom — the panel and the pill move together
   without two separate measurements.

   `top` is set inline by JS at open time using the header's actual
   `getBoundingClientRect().bottom`. The CSS fallback (60px = mobile
   header height) only holds when scroll=0 AND nothing above the header
   takes space — the WP admin bar / Astra wrappers can push the real
   header down, which would otherwise leave the wrapper overlapping the
   header. Same pattern as `positionSearchPanel()`.

   Hidden at rest via `visibility: hidden` + `opacity: 0` (not `display:
   none`) so the open/close transition can play; `inert` (set by the JS)
   keeps it out of the AT tree regardless. Mobile-only — the burger itself
   is hidden on desktop, so this drawer never appears outside the mobile
   breakpoint.

   Reveal animation is staged across the two children, not on the wrapper:
   the white panel slides down from behind the header (`translateY(-100%)
   → 0`, 360ms on `.ap-pp__cart-modal__drawer`'s Material curve), and the
   close pill fades in *after* the panel lands. The pill animates with
   opacity only, holding its layout slot (24px below the panel's layout
   box) the whole time — flex flow positions it from the panel's *layout*
   box, which doesn't move when the panel transforms, so it never floats
   in empty space waiting for the panel to arrive.

   Direction-aware sequencing via `transition-delay`:
     OPEN  →  panel slides 0–360ms,  pill fades 360–560ms (200ms, delay 360ms).
     CLOSE →  pill fades  0–200ms,   panel slides 200–560ms (delay 200ms).
   So the pill never sits alone in space in either direction.

   The wrapper itself only gates pointer-events (`none` at rest, `auto`
   when open). `top` is set inline by JS using the header's actual
   `getBoundingClientRect().bottom` — same pattern as `positionSearchPanel`. */
.ap-pp__burger-drawer {
	display: flex;
	position: fixed;
	top: 60px;
	left: 0;
	right: 0;
	z-index: 1100;
	flex-direction: column;
	align-items: center; /* horizontally centres the close pill */
	gap: 24px;
	pointer-events: none;
	/* Clip the panel's off-screen position so it can't peek into the
	   viewport once the page scrolls past the header (header is
	   `position: relative`, so it scrolls away and stops masking the
	   panel at `translateY(-100%)`). Same defence as the cart-modal
	   wrapper, which also uses `overflow: hidden` to clip its off-screen
	   drawer. The wrapper's own box is anchored at the header's bottom,
	   so anything translated above that line gets clipped at the
	   wrapper's top edge regardless of scroll position. */
	overflow: hidden;
}
.ap-pp__burger-drawer.is-open {
	pointer-events: auto;
}
body.ap-pp__burger-open { overflow: hidden; }

/* Light dimming layer that fades in behind the drawer when the burger is
   open. Covers the full viewport — the header sits at z 1101 above this,
   so the header band stays un-dimmed. Click-on-backdrop closes the drawer
   via the existing document-level outside-click listener (the backdrop is
   outside both the burger button and the drawer wrapper). Timing matches
   the drawer's slide (360ms, Material ease) so the dim and the sheet
   land in lockstep. */
.ap-pp__burger-backdrop {
	visibility: hidden;
	opacity: 0;
	position: fixed;
	inset: 0;
	z-index: 1099;
	background: var(--backdrop-background-light, rgba(10, 10, 10, 0.30));
	/* Symmetric fade both directions — paired with the panel's slide so
	   the dim doesn't vanish while the panel is still rolling up. The
	   visibility step is delayed 360ms on close so the element stays
	   visible until the fade-out completes; `.is-open` zeroes the delay
	   so visibility flips to `visible` immediately when opening. */
	transition: opacity 360ms cubic-bezier(0.4, 0, 0.2, 1),
				visibility 0s linear 360ms;
}
.ap-pp__burger-backdrop.is-open {
	visibility: visible;
	opacity: 1;
	transition-delay: 0s;
}

.ap-pp__burger-panel {
	align-self: stretch; /* sheet spans full viewport width inside the flex column */
	box-sizing: border-box;
	min-height: 390px;
	/* Panel padding lives on each `.ap-pp__burger-view` so the panel itself
	   can `overflow: hidden` flush at its border — otherwise the off-screen
	   view's leading edge bleeds into the panel's right-padding zone (24px
	   sliver visible during the slide). */
	padding: 0;
	background: var(--gray-0, #FFF);
	border-radius: 0 0 var(--radius-15, 15px) var(--radius-15, 15px);
	/* Clip the carousel track horizontally (off-screen view stays hidden)
	   and the panel's content vertically to its rounded bottom corners. */
	overflow: hidden;
	/* The panel hosts a `.ap-pp__burger-track` carousel holding both views
	   side-by-side; the track translates horizontally and resizes its
	   height in lockstep to swap views. `data-view` on the panel picks
	   which view is active. */
	/* Cancel the page-wide mobile remap (`.ap-sandbox { --font-size-20:
	   16px }`) for this subtree: the burger nav rows are designed at 20px
	   on mobile, not the product-page 16px scale. Per the token-remap
	   pattern: override the token at the wrapper, not on each element. */
	--font-size-20: 20px;
	/* Mobile remap for the category title — brandbook spec is 27px on
	   mobile (vs. desktop 32px) for `.type-heading-md`. Token-remap once
	   here so the title keeps its utility class. */
	--font-size-32: 27px;
	/* Slide motion both directions: rests above the header (`translateY
	   (-100%)` of its own height); header at z 1101 masks the off-screen
	   sheet during the slide. Transition lives on the base rule so the
	   panel animates symmetrically on open and on close. */
	transform: translateY(-100%);
	transition: transform 360ms cubic-bezier(0.4, 0, 0.2, 1);
	will-change: transform;
}
.ap-pp__burger-drawer.is-open .ap-pp__burger-panel {
	transform: translateY(0);
}

/* Carousel track — holds both views side-by-side and slides horizontally
   to swap them (iOS UINavigationController push/pop). `width: 100%` with
   each view at `flex: 0 0 100%` means the second view overflows the track
   to the right; `translateX(-100%)` on the track shifts the first view
   off-screen left and brings the second into view. The panel's
   `overflow: hidden` does the clipping.

   Height also transitions because the views are different heights (root
   ≈280px, category ≈485px); animating both transform and height with the
   same curve/duration makes the panel grow/shrink in lockstep with the
   slide rather than snapping after. Apple HIG curve `cubic-bezier(0.32,
   0.72, 0, 1)` at 360ms matches the drawer's open/close duration and the
   burger-icon swap's spring curve. JS sets the height to the active
   view's `offsetHeight` on each show. */
.ap-pp__burger-track {
	display: flex;
	flex-direction: row;
	align-items: flex-start; /* each view keeps its natural height */
	width: 100%;
	transform: translateX(0);
	transition: transform 360ms cubic-bezier(0.32, 0.72, 0, 1),
	            height    360ms cubic-bezier(0.32, 0.72, 0, 1);
	will-change: transform, height;
}
.ap-pp__burger-panel[data-view="category"] .ap-pp__burger-track {
	transform: translateX(-100%);
}
/* Suppress track transitions while the drawer is closed — `openBurger
   Panel()` calls `showRootView()` BEFORE adding `.is-open`, so the reset
   to root snaps instantly (no invisible slide playing under a hidden
   panel). Mirrors the `.is-open`-gated transition on the close pill. */
.ap-pp__burger-drawer:not(.is-open) .ap-pp__burger-track { transition: none; }

/* Views — flex column with the 24px row gap; padding lives here (moved
   off the panel) so the panel can clip cleanly at its border. `flex: 0 0
   100%` puts each view at exactly the panel's content width; two views
   side-by-side overflow the track horizontally and are clipped by the
   panel. */
.ap-pp__burger-view {
	flex: 0 0 100%;
	box-sizing: border-box;
	display: flex;
	flex-direction: column;
	gap: 24px;
	padding: 24px 24px 48px;
}
/* Category view: 33px from header bottom (= panel top) to title row. */
.ap-pp__burger-view--category {
	padding-top: 33px;
}

/* Root view: 25px from header bottom (= panel top) to the first nav row.
   Search lives in the header on mobile now, so the panel opens straight
   to the category list. */
.ap-pp__burger-view--root {
	padding-top: 25px;
}

/* Category heading row — back chevron on the left, title centred. The
   back button is absolutely positioned so the title stays centred on
   the row's full width regardless of the button's hit-area expansion.
   `margin-bottom: 33px` + view gap (24px) = the spec'd 57px between
   the title's bottom edge and the first sub-category row. */
.ap-pp__burger-category__heading {
	position: relative;
	display: flex;
	justify-content: center;
	align-items: center;
	min-height: 48px;
	margin-bottom: 33px;
}
.ap-pp__burger-category__title {
	color: var(--text-primary, #191919);
	text-align: center;
}
/* 24×24 chevron with a 12px tap-area expansion → 48×48 hit target.
   The chevron path lives at x=10–15 inside its 24px viewBox, so the SVG
   element has ~10px of internal whitespace on its left. The negative
   `left` compensates twice: −12px for the button's left padding, and
   another −10px for that SVG whitespace, so the *visible* chevron glyph
   lands flush at the heading row's left edge (not 10px inset from it).
   Brand-wash lock pinned to resting at every state — chevron uses
   `currentColor` so colour tracks `color` automatically. */
.ap-pp__burger-category__back {
	position: absolute;
	left: -22px;
	top: 50%;
	transform: translateY(-50%);
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 12px;
	border: 0;
	background: transparent;
	color: var(--text-primary, #191919);
	cursor: pointer;
}
.ap-pp__burger-category__back-icon {
	display: block;
	width: 24px;
	height: 24px;
}
.ap-sandbox .ap-pp__burger-category__back:hover,
.ap-sandbox .ap-pp__burger-category__back:focus,
.ap-sandbox .ap-pp__burger-category__back:focus-visible,
.ap-sandbox .ap-pp__burger-category__back:active {
	background-color: transparent;
	color: var(--text-primary, #191919);
	border-color: transparent;
	outline: none;
	box-shadow: none;
}

/* Close pill — standard brandbook icon-button shell: 14px padding +
   20px icon = 48×48, fully rounded, gray-100 background. Five-property
   brand-wash lock at every interactive state so multi-brand-uni can't
   repaint the pill (same shape as the footer social pill). */
.ap-pp__burger-close {
	display: inline-flex;
	padding: 14px;
	align-items: center;
	gap: 10px;
	border: 0;
	border-radius: 999px;
	background: var(--icon-button-secondary-background-enabled, #F0F0F0);
	cursor: pointer;
	/* Pill holds its layout slot the whole time (24px below the panel's
	   *layout* box; the panel's transform doesn't shift its sibling-flow
	   anchor). On OPEN the pill waits 360ms for the panel to land, then
	   fades in over 200ms — the open-only transition is declared on
	   `.is-open` below. No transition at rest means CLOSE snaps to
	   opacity 0 instantly while the panel slides up underneath it. */
	opacity: 0;
}
.ap-pp__burger-drawer.is-open .ap-pp__burger-close {
	opacity: 1;
	transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 360ms;
}
.ap-pp__burger-close img { display: block; width: 20px; height: 20px; }
.ap-sandbox .ap-pp__burger-close:hover,
.ap-sandbox .ap-pp__burger-close:focus,
.ap-sandbox .ap-pp__burger-close:focus-visible,
.ap-sandbox .ap-pp__burger-close:active {
	background-color: var(--icon-button-secondary-background-enabled, #F0F0F0);
	color: inherit;
	border-color: transparent;
	outline: none;
	box-shadow: none;
}

/* Reduced-motion: kill all burger transitions. Placed at the end of the
   burger block so source-order resolves the ties at matching specificity
   (panel/backdrop base rules vs `.is-open` for the pill). */
@media (prefers-reduced-motion: reduce) {
	.ap-pp__burger-panel,
	.ap-pp__burger-backdrop,
	.ap-pp__burger-track,
	.ap-pp__burger-drawer.is-open .ap-pp__burger-close { transition: none; }
}


/* ─────────────────────────────────────────────────────────────────────
   Sandbox footer — bespoke. 1071px tall desktop / 861px tall mobile;
   full-width, painted in `--footer-background` (basic-black). Internal
   layout is being assembled bottom-up; this rule owns the shell and
   the 96px horizontal insets. Children stack as a flex column so
   row-level `margin-top: auto` can anchor a row to the bottom edge;
   `position: relative` is the anchor for absolutely-positioned bottom
   rows (e.g. the socials row pinned 32px above the bottom edge).

   `--brand-h` is the unified visual height for the brand-row: both the
   brandmark image's pixel height AND the wordmark's rendered cap-height
   resolve to this value, so the two appear at the same visual size and
   can sit on a shared vertical center axis (see `.ap-pp__footer__brand-
   row` below). The clamp gives a viewport-fluid initial value (45→145
   between the mobile breakpoint and design viewport); JS at the bottom
   of the template overrides `--brand-h` here if the natural content
   width would otherwise overflow the row, so the height match is
   preserved at every viewport. The kicker also derives its position
   from `--brand-h`, which is why it lives on the footer (the shared
   ancestor) rather than on the row. */
.ap-pp__footer {
	--brand-h: clamp(45px, calc((100vw - 240px) * 145 / 1488), 145px);
	position: relative;
	box-sizing: border-box;
	/* Total height anchored to "115px headroom above the jersey's top
	   edge." Jersey bottom = 225 + 1.15·brand-h, jersey height = 535,
	   so jersey top = 760 + 1.15·brand-h; +115 headroom → footer height
	   = 875 + 1.15·brand-h. The footer grows/shrinks with `--brand-h`
	   so the headroom stays exactly 115px at every viewport. */
	height: calc(875px + var(--brand-h) * 1.15);
	padding: 0 96px;
	background: var(--footer-background, #000);
	color: var(--text-white, #FFF);
	display: flex;
	flex-direction: column;
	/* Clip the decorative background shape so it can't bleed outside
	   the footer at narrow viewports (the SVG is 1445px wide at native
	   size and centered on the footer's x-axis). All current content
	   children fit within the footer's box, so this doesn't affect
	   them. */
	overflow: hidden;
}

/* Hairline divider painted across the footer's content column (96px insets
   come from the parent's padding). 112px below it is the gap to the bottom
   of the footer; `margin-top: auto` pins it to the bottom of the flex flow
   until rows are layered above. Opacity 0.3 per spec — the underlying token
   is the standard light divider colour. */
.ap-pp__footer__divider {
	border: 0;
	height: 1px;
	margin: auto 0 112px;
	background: var(--divider-background-light, #E4E4E4);
	opacity: 0.3;
}

/* Compact footer — used on the checkout-flow pages (delivery → confirmation).
   The divider and the whole decorative block above it aren't rendered (see
   footer.php); only the legal + socials rows remain. Those are absolutely
   positioned against the footer (bottom 44px / 32px), so the footer is given
   the same 112px band height it occupies below the divider in the full footer
   — leaving ~32px of headroom above the 48px social pills. */
.ap-pp__footer--compact {
	height: 112px;
}
/* Mobile: the legal row collapses to `display: contents` and the socials +
   copyright go static (in-flow), so the band can't be a fixed height — it
   sizes to the stacked menu/socials/copyright column. 40px top padding gives
   the headroom the absent divider used to carry. Scoped under `.ap-sandbox`
   to outweigh the later mobile `.ap-pp__footer { padding: 0 24px }` reset. */
@media (max-width: 768px) {
	.ap-sandbox .ap-pp__footer--compact {
		height: auto;
		padding-top: 40px;
	}
}

/* Socials row — pinned to the bottom-right corner of the footer:
   32px above the bottom edge, flush with the 96px right inset.
   Sits below the divider inside the divider's 112px bottom margin. */
.ap-pp__footer__socials {
	position: absolute;
	right: 96px;
	bottom: 32px;
	display: flex;
	gap: 8px;
}

/* Legal row — mirror of the socials row on the left side of the same
   bottom band. Copyright + menu links sit in a single flex row with a
   uniform 40px gap between every child. Typography is `type-body-md-light`
   applied per item; only the colours differ (tertiary for copyright,
   white for menu links). */
.ap-pp__footer__legal {
	position: absolute;
	left: 96px;
	bottom: 44px;
	display: flex;
	align-items: center;
	gap: 40px;
}
.ap-pp__footer__copyright { color: var(--text-tertiary, #787878); }

.ap-pp__footer__menu {
	display: flex;
	align-items: center;
	gap: 40px;
}
.ap-pp__footer__menu-item,
.ap-pp__footer__menu-item:visited {
	color: var(--text-white, #FFF);
	text-decoration: none;
}
/* Five-property brand-wash lock so the active-brand colour can't repaint
   the link on hover/focus/active (same shape as `.ap-pp__header__nav-item`). */
.ap-sandbox .ap-pp__footer__menu-item:hover,
.ap-sandbox .ap-pp__footer__menu-item:focus,
.ap-sandbox .ap-pp__footer__menu-item:focus-visible,
.ap-sandbox .ap-pp__footer__menu-item:active {
	color: var(--text-white, #FFF);
	background-color: transparent;
	border-color: transparent;
	outline: none;
	box-shadow: none;
}

/* Single social pill — 48×48 (14px padding + 20×20 icon slot).
   `align-items: center` + `gap: 10px` match the spec; with one child
   per button the gap is currently moot, kept for parity with the
   design token. Pinned to gray-950 at all five interactive states so
   multi-brand-uni's brand-wash can't repaint the pill. */
.ap-pp__footer__social {
	display: flex;
	padding: 14px;
	align-items: center;
	gap: 10px;
	border-radius: 999px;
	background: var(--gray-950, #191919);
	border: 0;
	text-decoration: none;
	cursor: pointer;
}
.ap-sandbox .ap-pp__footer__social:hover,
.ap-sandbox .ap-pp__footer__social:focus,
.ap-sandbox .ap-pp__footer__social:focus-visible,
.ap-sandbox .ap-pp__footer__social:active {
	background-color: var(--gray-950, #191919);
	color: inherit;
	border-color: transparent;
	outline: none;
	box-shadow: none;
}
/* 20×20 icon slot — the Facebook glyph is intrinsically 11×20, so we
   centre each image inside a fixed slot so both pills end up the same
   size regardless of glyph aspect ratio. */
.ap-pp__footer__social__icon {
	display: inline-flex;
	width: 20px;
	height: 20px;
	align-items: center;
	justify-content: center;
}
.ap-pp__footer__social__icon img { display: block; }

/* Wordmark band — single flex row spanning the 96px insets. Brandmark
   anchored at the row's left edge (96px inset), wordmark packed
   immediately to its right with exactly 48px between them and no more
   — `justify-content: flex-start` instead of `space-between`, because
   the latter would balloon the inter-item gap on any viewport where
   the content is narrower than the row (the leftover space gets
   distributed as extra gap). The wordmark is therefore NOT pinned to
   the right inset; it floats wherever `brandmark-width + 48px` puts
   it. Both sized off the unified `--brand-h` token so they share a
   visual height; `align-items: center` puts them on a common vertical
   center axis so they read as one inline lockup. `line-height: 1` on
   the wordmark removes the default half-leading so the wordmark's box
   height ≈ its em-size and the center axis matches the visible glyph
   centerline.

   The 1.3 multiplier on the wordmark's font-size compensates for the
   cap-height being ~75–78% of em-size in Special Gothic Expanded One
   — at `font-size: brand-h * 1.3` the rendered caps land at ≈ brand-h,
   matching the brandmark image's pixel height. The exact constant may
   need a small tweak per the actual font metrics; keep it here as the
   single tuning knob.

   The row's bottom-anchor stays at 144px (112px divider→footer-bottom +
   32px row→divider). Total width is JS-fitted in *both* directions:
   the script at the bottom of the template measures brandmark width +
   48px gap + wordmark width at the current `--brand-h` and scales the
   token so the content exactly fills the row — shrinks when natural
   content would overflow, grows when there'd otherwise be leftover
   space on the right. Both children scale in lockstep so the height
   match and center axis are preserved. */
.ap-pp__footer__brand-row {
	position: absolute;
	left: 96px;
	right: 96px;
	bottom: 144px;
	display: flex;
	align-items: center;
	justify-content: flex-start;
	gap: 48px;
	min-width: 0;
}
.ap-pp__footer__brandmark {
	flex: 0 0 auto;
}
.ap-pp__footer__brandmark img {
	display: block;
	height: var(--brand-h);
	width: auto;
	/* Ink-flush left edge: the PNG carries 9px of transparent padding per
	   side (500×222 asset, ink bbox 9→491), so the A's ink would start
	   9/222·brand-h inside column 1. Shift the rendered image left by that
	   much; layout (and the JS fitter's offsetWidth) are unaffected. */
	position: relative;
	left: calc(var(--brand-h) * -0.0405);
}

.ap-pp__footer__wordmark {
	flex: 0 1 auto;
	min-width: 0;
	margin: 0;
	color: var(--text-white, #FFF);
	text-align: right;
	font-family: var(--font-family-display, 'Special Gothic Expanded One', sans-serif);
	font-size: calc(var(--brand-h) * 1.3);
	font-weight: 400;
	line-height: 1;
	text-transform: uppercase;
	white-space: nowrap;
	/* Ink-flush right edge: the S's right sidebearing in Special Gothic
	   Expanded One is 0.0259em (canvas TextMetrics: advance 960.45px vs
	   ink 956.87px at 138.6px), so the painted glyph would stop that far
	   short of column 12's edge. Shift the rendered text right by the
	   sidebearing; layout (and the JS fitter's scrollWidth) are
	   unaffected. */
	position: relative;
	right: -0.0259em;
}

/* "Powered by" kicker — sits 24px above the brandmark's top edge on
   the left column. With the row center-aligned and the wordmark box
   (font-size 1.3·brand-h, line-height 1) being the taller child, the
   row's effective height is 1.3·brand-h. The row's bottom is anchored
   144px above the footer bottom, so its vertical center sits at
   `144 + 0.65·brand-h`, the brandmark's top at `144 + 1.15·brand-h`,
   and the kicker at `168 + 1.15·brand-h`. Typography is
   `type-label-lg-regular` (Outfit 16/24, +0.06em tracking, uppercase). */
.ap-pp__footer__kicker {
	position: absolute;
	left: 96px;
	bottom: calc(168px + var(--brand-h) * 1.15);
	margin: 0;
	color: var(--text-tertiary, #787878);
}

/* Left-column CTA stack — wraps the note, title, and CTA button so
   they flow as a flex column. This lets the title's box grow when it
   wraps to two lines (see `max-width` below) without the note above it
   getting overlapped: items below the title still hug its bottom and
   items above it ride up automatically.

   Anchor: the wrapper's bottom edge sits where the CTA's bottom edge
   needs to be, i.e. 241px above the kicker's top edge. Kicker top is
   at `192 + 1.15·brand-h` above the footer bottom, so the wrapper's
   bottom anchor is `433 + 1.15·brand-h`. Inter-item gaps are owned by
   `margin-bottom` on the note (16px → title) and the title (64px →
   CTA); the CTA itself has no bottom margin. */
.ap-pp__footer__cta-stack {
	position: absolute;
	left: 96px;
	bottom: calc(433px + var(--brand-h) * 1.15);
	display: flex;
	flex-direction: column;
	align-items: flex-start;
}

/* "Neradai ko ieškojai?" note — `.type-heading-sm-light` (Outfit
   20/1.1, weight 300, no tracking). Tertiary gray on the black
   footer. */
.ap-pp__footer__note {
	margin: 0 0 16px 0;
	color: var(--text-tertiary, #787878);
}

/* "Susikurk savo" footer title — `.type-heading-xl` (Special Gothic
   Expanded One 58/1.1, 0.025em tracking, UPPERCASE). White on the
   black footer. `max-width` caps the title so it naturally wraps at
   its word boundary ("Susikurk" / "savo") before reaching the
   centered jersey's left edge. Math: jersey is centered (left edge =
   `50vw - 194.5px`), title sits at `left: 96px` (96px inside the
   footer's left edge), 24px breathing buffer between the two →
   max-width = 50vw - 194.5 - 24 - 96 = 50vw - 314.5px. The jersey
   stays fixed; the title yields. */
/* Selector chained to `.ap-pp__footer` (parent class) for (0,2,0)
   specificity. Type properties redeclared inline because
   `.type-heading-xl` (the design-system token class on this <h2>)
   sits at (0,1,0) and gets beaten on WC product pages by
   `.woocommerce h2 { font-size: 1.625rem }` at (0,1,1) — collapses
   the title from 58px to 26px. Sandbox doesn't see this fight (no
   `.woocommerce` body class), but the same selector ships into the
   modern PDP per the canonical-CSS rule, where it has to win. */
.ap-pp__footer .ap-pp__footer__title {
	margin: 0 0 64px 0;
	max-width: calc(50vw - 314.5px);
	color: var(--text-white, #FFF);
	font-family: var(--font-family-display);
	font-weight: var(--font-weight-regular);
	font-size: var(--font-size-58);
	line-height: 1.1;
	letter-spacing: 0.025em;
	text-transform: uppercase;
}

/* "Pradėti" CTA — tertiary (white) button, Outfit 16/1.5 medium (no
   uppercase, no tracking), matching the existing `.ap-pp__add-to-cart`
   precedent. Five-property brand-wash lock on hover/focus/active so
   multi-brand-uni's brand colour can't repaint the button. */
.ap-pp__footer__cta {
	display: inline-flex;
	box-sizing: border-box;
	height: 60px;
	padding: 18px 96px;
	justify-content: center;
	align-items: center;
	gap: var(--space-8, 8px);
	border: 0;
	border-radius: var(--radius-15, 15px);
	background: var(--button-tertiary-background-enabled, #FFF);
	color: var(--text-primary, #191919);
	text-decoration: none;
	cursor: pointer;
	font-family: var(--font-family-ui, 'Outfit', sans-serif);
	font-size: var(--font-size-16, 16px);
	font-weight: var(--font-weight-medium, 500);
	line-height: 1.5;
	letter-spacing: 0;
	text-transform: none;
}
.ap-sandbox .ap-pp__footer__cta:hover,
.ap-sandbox .ap-pp__footer__cta:focus,
.ap-sandbox .ap-pp__footer__cta:focus-visible,
.ap-sandbox .ap-pp__footer__cta:active {
	background-color: var(--button-tertiary-background-hover, #F5F5F5);
	color: var(--text-primary, #191919);
	border-color: transparent;
	outline: none;
	box-shadow: none;
}

/* Jersey + decorative bg-shape pairing. The wrapper is the positioning
   anchor — its own absolute position (desktop) or flex-flow position
   (mobile) defines where the jersey sits; the bg-shape is then
   centered *on the wrapper* via `top: 50%; left: 50%; translate(-50%,
   -50%)`, which is identical to "centered on the jersey" because the
   wrapper is sized exactly to the jersey. This guarantees the
   bg-shape and jersey share both center axes at every viewport, with
   no hand-computed offset math.

   Stacking order inside the wrapper is DOM order: bg-shape (first
   child) paints under the jersey (second child). The wrapper itself
   is the *first* child of `<footer>`, so the whole pair paints under
   every other footer child (cta-stack, kicker, brand-row, divider,
   legal, socials). */

/* Jersey-wrap (desktop) — sits 33px above the kicker, horizontally
   centered on the footer. Same anchor the jersey used to own:
   kicker's top = `192 + 1.15·brand-h` above the footer bottom;
   jersey bottom = 33px above that = `225 + 1.15·brand-h`. Width and
   height are the jersey's fixed pixel dimensions (does not scale
   with the brand-row token). */
.ap-pp__footer__jersey-wrap {
	position: absolute;
	left: 50%;
	bottom: calc(225px + var(--brand-h) * 1.15);
	transform: translateX(-50%);
	width: 389px;
	height: 535px;
}

.ap-pp__footer__jersey {
	/* `position: relative` is required for the jersey to paint above
	   the bg-shape sibling. CSS painting-order puts positioned
	   descendants with `z-index: auto` (the bg-shape) at a *higher*
	   layer than in-flow non-positioned descendants — so a static
	   jersey would render *under* the absolute bg-shape regardless
	   of DOM order. With the jersey also positioned at `z-index:
	   auto`, both land in the same layer and the DOM order
	   (bg-shape first, jersey second) takes over: bg-shape under,
	   jersey over. */
	position: relative;
	display: block;
	width: 100%;
	height: 100%;
	aspect-ratio: 381 / 524;
}

/* Selector chained to `.ap-pp__footer` for (0,2,0). Original
   `.ap-pp__footer__bg-shape { ... }` at (0,1,0) was beaten on WC
   product pages by `.woocommerce img { max-width: 100%; height: auto }`
   at (0,1,1), which squashed the 1445×480 backdrop into the 389px
   jersey wrap. Sandbox doesn't see the WC rule (no `.woocommerce`
   body), but the bumped selector matches both contexts harmlessly. */
.ap-pp__footer .ap-pp__footer__bg-shape {
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	/* Astra's global `img { max-width: 100%; height: auto }` (parent
	   theme `style.css:1397`) would otherwise cap this decorative
	   image to the wrapper's width and squash it into the jersey's
	   bounding box. The shape must render at its full native pixel
	   size and spill past the wrapper — so we explicitly disable
	   both caps. */
	max-width: none;
	max-height: none;
	width: 1445px;
	height: 480px;
	pointer-events: none;
	user-select: none;
}


/* Full-bleed page body — Astra's .ast-container caps width and pads sides by
   default; #primary (our `.ap-sandbox`) also gets 1.5em vertical padding
   and a 4em bottom margin; Astra's `#page` and `.site-content` add their
   own top padding to offset Astra's (now-hidden) header. Zero all of it so
   the breadcrumb's 64px top margin is the only vertical space between the
   header and the first row — and so the sticky header sits flush against
   the viewport top (no leftover header-offset gap above it on mobile). */
body.ap-sandbox-page,
body.ap-sandbox-page #page,
body.ap-sandbox-page .site-content,
body.ap-sandbox-page #content,
body.ap-sandbox-page #content .ast-container,
body.ap-sandbox-page #content .ast-inner,
body.ap-sandbox-page .ap-sandbox { max-width: none !important; padding: 0 !important; margin: 0 !important; }

/* WP's `_admin_bar_bump_cb` outputs an inline style block in `wp_head`
   that forces `html { margin-top: 32/46px !important }` to make space
   for the admin bar. That hook is added during `init` priority 0, well
   before our template loads — so the `show_admin_bar( false )` call at
   the top of this file stops the bar from rendering, but can't unwind
   the already-registered html-margin shim. Override the html margin
   here. `:has()` is required to scope to the sandbox (the
   `page-template-*` class is on body, not html); supported in all
   modern browsers. */
html:has(body.ap-sandbox-page) { margin-top: 0 !important; }

/* ─────────────────────────────────────────────────────────────────────
   Cart modal — opens on `.ap-pp__add-to-cart` click. A full-viewport
   dim scrim with a white drawer (left edge on grid column 8) sliding in
   over the right side.
   Desktop-only for now; the mobile pattern is hard-hidden below 768px.

   Architecture: the modal itself is just a positioning context
   (`position: fixed; inset: 0`). The backdrop and drawer are both
   absolutely positioned inside it — backdrop covers the WHOLE
   viewport (`inset: 0`), drawer overlays the right side on top of it.
   This is deliberate: with a flex side-by-side layout the dim scrim
   only covered the left half, leaving the slot the drawer slides into
   visually undimmed during the slide — a half-dark/half-bright frame
   that read as broken. Full-viewport backdrop fixes it.

   Stack order is set by DOM order: backdrop first, drawer second, so
   the drawer paints on top without z-index. The drawer also has its
   own `background`, so backdrop tinting never bleeds into the white
   surface.

   Behaviour: backdrop pops in/out with the `.is-open` flip, with NO
   transition — per spec, it appears the instant the trigger fires and
   disappears the instant dismiss fires. Drawer slides 360ms via
   `transform: translateX`. The container stays laid out and visible
   at all times — the drawer is hidden visually by sitting at
   `translateX(100%)` (clipped by `overflow: hidden`), the backdrop by
   `opacity: 0`. Interaction is gated by `pointer-events`, a11y by
   `inert` on the markup (toggled by JS in sync with `.is-open`).

   Why not `visibility: hidden`: it suppresses paint of all descendants.
   When visibility flips to visible at slide-in time, the children
   (images, text) are being painted for the first frame — they land a
   beat AFTER the drawer's solid-colour background, producing the
   "white surface arrives, content fills in second" two-step. Keeping
   the subtree always painted (off-screen + transparent) means the GPU
   layers are warm and slide-in just translates already-painted pixels.
   The curve is the Material "standard" easing — gentle and
   Apple-flavoured.

   Compositing: the drawer carries `will-change: transform`, and both
   inner rails carry `transform: translateZ(0)`. That gives the drawer
   its own GPU layer with the rails as compositor-child layers
   established at page load — so the slide-in just translates an
   already-warm layer tree instead of creating layers on the fly.
   ───────────────────────────────────────────────────────────────────── */
.ap-pp__cart-modal {
	position: fixed;
	inset: 0;
	/* Above the header (z-index: 1101) so the drawer + backdrop cover the
	   header band when open. The header itself sits above the burger panel
	   (1100); modals are the topmost overlay tier. */
	z-index: 1102;
	overflow: hidden; /* clip the off-screen drawer so it doesn't widen the page */
	pointer-events: none;
}
.ap-pp__cart-modal.is-open {
	pointer-events: auto;
}
body.ap-pp__cart-modal-open { overflow: hidden; }

/* Backdrop — full viewport, sits behind the drawer. Pops in/out
   instantly with `.is-open`; no transition by spec. */
.ap-pp__cart-modal__backdrop {
	position: absolute;
	inset: 0;
	/* Light dim scrim — matches every other backdrop in the sandbox
	   (mega-menu, burger, search modal, combo modal, tenant filter
	   modal). The dark 80% `--backdrop-background` token is unused
	   in the sandbox; if it gets reintroduced anywhere see
	   `feedback_backdrops_use_light_token`. */
	background: var(--backdrop-background-light, rgba(10, 10, 10, 0.30));
	cursor: pointer;
	opacity: 0;
}
.ap-pp__cart-modal.is-open .ap-pp__cart-modal__backdrop {
	opacity: 1;
}

/* Drawer — right-anchored column reaching left to the left edge of
   grid column 8 (the 5th column from the right), the shared left edge
   for every side drawer on the site. The `100%` basis is the fixed inset-0 wrapper, which spans the
   viewport, so the token's own `100%` resolves against the same box.
   Slides in over the backdrop; solid background so the dim scrim never
   tints the white surface as the drawer crosses it during the slide. */
.ap-pp__cart-modal__drawer {
	position: absolute;
	top: 0;
	right: 0;
	bottom: 0;
	width: calc(100% - var(--grid-col8-inset, 1123px));
	display: flex;
	min-width: 0;
	background: var(--drawer-background, var(--basic-white, #FFF));
	transform: translateX(100%);
	transition: transform 360ms cubic-bezier(0.4, 0, 0.2, 1);
	will-change: transform;
}
.ap-pp__cart-modal.is-open .ap-pp__cart-modal__drawer {
	transform: translateX(0);
}

@media (prefers-reduced-motion: reduce) {
	.ap-pp__cart-modal__drawer { transition: none; }
}


/* Drawer left rail — fixed 400px column of vertically stacked product
   photos. Scrolls independently when the stack overflows the drawer
   height. `translateZ(0)` is a deliberate GPU-layer hint: without it
   the scroll-promoted layer can land a frame late behind the parent
   drawer's transform during slide-in, producing the "background first,
   content second" two-step. Pre-promoting the layer at page load keeps
   the topology stable. */
.ap-pp__cart-modal__photos {
	/* Basis 400px but shrinkable: on in-between viewports the drawer
	   (100% − col8-inset) gets narrower than the rail's basis and a
	   rigid column would push the summary out of the drawer. Photos
	   are width: 100% so they scale with the rail. */
	flex: 0 1 400px;
	min-width: 0;
	height: 100%;
	overflow-y: auto;
	display: flex;
	flex-direction: column;
	gap: 2px;
	transform: translateZ(0);
	/* Hide the scrollbar but keep wheel/drag scrolling working. */
	scrollbar-width: none;          /* Firefox */
	-ms-overflow-style: none;       /* old Edge / IE */
}
.ap-pp__cart-modal__photos::-webkit-scrollbar {
	display: none;                  /* Chromium / Safari */
}
.ap-pp__cart-modal__photos img {
	display: block;
	width: 100%;
	height: auto;
}

/* Drawer right rail — summary panel. 48px side padding per spec; same
   inset on top so the title's "48px from the top of the summary" comes
   straight from the padding. `translateZ(0)` mirrors the photos rail —
   see the note above. */
.ap-pp__cart-modal__summary {
	flex: 1 1 auto;
	padding: 48px;
	box-sizing: border-box;
	overflow-y: auto;
	transform: translateZ(0);
}
.ap-pp__cart-modal__summary .ap-pp__cart-modal__title {
	color: var(--text-primary, #191919);
	margin: 0; /* defeat Astra's `.entry-content h2` margins so the 48px
	             padding-top above is the actual gap to the title */
}

/* "Visa informacija apie prekę" — centered link below the buy row. Color
   is pinned to primary at every state per spec (including :visited, which
   the brand-wash defang doesn't touch). Trailing 24×24 icon shell sits
   after a 4px gap; the SVG is centered inside the shell. */
.ap-pp__cart-modal__info-link,
.ap-pp__cart-modal__info-link:visited {
	display: flex;
	justify-content: center;
	align-items: center;
	gap: 4px;
	margin-top: 48px;
	color: var(--text-primary, #191919);
	text-decoration: none;
}
.ap-pp__cart-modal__info-link__icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 24px;
	height: 24px;
	flex: 0 0 24px;
}
.ap-pp__cart-modal__info-link__icon img {
	display: block;
	width: 100%;
	height: 100%;
}
.ap-sandbox .ap-pp__cart-modal__info-link:hover,
.ap-sandbox .ap-pp__cart-modal__info-link:focus,
.ap-sandbox .ap-pp__cart-modal__info-link:focus-visible,
.ap-sandbox .ap-pp__cart-modal__info-link:active {
	color: var(--text-primary, #191919);
	background-color: transparent;
	border-color: transparent;
	outline: none;
	box-shadow: none;
}

/* ─────────────────────────────────────────────────────────────────────
   Cart-added popover — opens from `[data-cart-added-open]` (currently
   the header cart icon). Unlike the cart/search modals this is a
   non-blocking toast: no backdrop, no scroll lock, no viewport-clip
   wrapper. A 400px white panel anchored 24px from the viewport's top
   and right edges; flex-column with a 24px gap so future rows stack
   below the title row without extra wiring. Apple-HIG entry: fade +
   8px translate-down, 240ms ease-out.
   ───────────────────────────────────────────────────────────────────── */
.ap-pp__cart-added {
	position: fixed;
	top: 24px;
	right: 24px;
	z-index: 1102; /* same overlay tier as the cart/search modals */
	display: flex;
	width: 400px;
	max-width: calc(100vw - 48px); /* keep the 24px right inset even when 400px would overflow narrow viewports */
	padding: var(--space-16, 16px);
	box-sizing: border-box;
	flex-direction: column;
	align-items: flex-start;
	gap: 24px;
	border-radius: var(--radius-15, 15px);
	background: var(--gray-0, #FFF);
	box-shadow: 0 2px 10px 0 rgba(10, 10, 10, 0.10);
	opacity: 0;
	transform: translateY(-8px);
	transition: opacity 240ms cubic-bezier(0, 0, 0.2, 1),
	            transform 240ms cubic-bezier(0, 0, 0.2, 1);
	pointer-events: none;
}
.ap-pp__cart-added.is-open {
	opacity: 1;
	transform: translateY(0);
	pointer-events: auto;
}
@media (prefers-reduced-motion: reduce) {
	.ap-pp__cart-added,
	.ap-pp__cart-added__backdrop { transition: none; }
}

/* Grabber — bottom-sheet affordance, mobile only (see the max-width:768px
   block). Hidden on the desktop top-right toast. */
.ap-pp__cart-added__grabber {
	display: none;
}

/* Backdrop — mobile-only scrim behind the bottom sheet (styled + shown in
   the max-width:768px block). Hidden on the desktop toast, which is
   non-blocking by design. */
.ap-pp__cart-added__backdrop {
	display: none;
}

/* Title row — title pinned left, close button pinned right. `align-self:
   stretch` defeats the parent's `align-items: flex-start` so the row
   itself spans the panel's full inner width; `space-between` then does
   the placement without extra wrappers or margin tricks. Close button
   top-aligns (not centred to the title) so it keeps a fixed top inset
   regardless of how many lines the title wraps to. */
.ap-pp__cart-added__header {
	align-self: stretch;
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: var(--space-16, 16px);
}
.ap-pp__cart-added__title {
	margin: 0; /* defeat Astra's `.entry-content h2` margins so padding owns the spacing */
	color: var(--text-primary, #191919);
	text-transform: none; /* Astra's customizer styles uppercase `h2` globally; pin sentence-case here so the utility class's typography lands as designed */
}
/* Astra's customizer h2 rule lands at (0,1,1) — it beats both the
   `.type-heading-sm-medium` utility AND the bare component rule above
   (both (0,1,0)), inflating the title to 26px/two lines. Pin the spec's
   20px at (0,2,0). (The neutraliser can't help: `:where()` is (0,0,1)
   by design.) */
.ap-sandbox .ap-pp__cart-added__title {
	margin: 0; /* the bare rule's margin: 0 loses to the same (0,1,1) Astra rule — a residual 14px bottom margin rode the title 3px above the close pill's centre */
	font-size: var(--font-size-20, 20px);
	line-height: 1.1;
}

/* Close button — 16px glyph in an 8px-padded pill (32×32 total).
   Five-property brand-wash lock at every interactive state, mirroring
   `.ap-pp__search-modal__close` and the other sandbox icon buttons —
   without this the multi-brand-uni overrides leak through Astra's
   button globals (see `feedback_lock_button_states_against_brand_wash`). */
.ap-pp__cart-added__close {
	display: inline-flex;
	flex-shrink: 0; /* min-content trap: a long title otherwise squashes the 32px pill into an oval */
	padding: var(--space-8, 8px);
	align-items: center;
	gap: 10px;
	border: 0;
	border-radius: var(--radius-999, 999px);
	background: var(--icon-button-secondary-background-enabled, #F0F0F0);
	cursor: pointer;
	text-transform: none;
	letter-spacing: 0;
}
.ap-pp__cart-added__close img {
	display: block;
	width: 16px;
	height: 16px;
}
.ap-sandbox .ap-pp__cart-added__close:hover,
.ap-sandbox .ap-pp__cart-added__close:focus,
.ap-sandbox .ap-pp__cart-added__close:focus-visible,
.ap-sandbox .ap-pp__cart-added__close:active {
	background-color: var(--icon-button-secondary-background-hover, var(--icon-button-secondary-background-enabled, #F0F0F0));
	color: inherit;
	border-color: transparent;
	outline: none;
	box-shadow: none;
}

/* Product row — second child below the title row, picks up the panel's
   `gap: 24px` automatically. 104px 3:4 media tile on the left, info
   column on the right with a 12px gap. `align-self: stretch` lets the
   info column fill the remaining inner width. */
.ap-pp__cart-added__product {
	align-self: stretch;
	display: flex;
	gap: var(--space-12, 12px);
	align-items: flex-start;
}
.ap-pp__cart-added__product__media {
	flex: 0 0 104px;
	width: 104px;
	aspect-ratio: 3 / 4;
	border-radius: var(--radius-10, 10px);
	background-color: #D9D9D9; /* fallback fill if the image is missing */
	background-position: center;
	background-size: cover;
	background-repeat: no-repeat;
}
.ap-pp__cart-added__product__info {
	flex: 1 1 auto;
	min-width: 0; /* allow long product titles to wrap inside the flex item — see `feedback_flex_min_width_auto_trap` */
	padding-top: var(--space-8, 8px); /* offset the info column 8px below the media's top edge */
	display: flex;
	flex-direction: column;
}
/* Per-row spacing from the spec: title → 8px → first attr → 2px → second
   attr → 2px → price. Each row owns its own top gap so reorders stay
   self-describing without touching the parent. */
.ap-pp__cart-added__product__title {
	margin: 0; /* defeat Astra's `.entry-content h3` margins */
	color: var(--text-primary, #191919);
	text-transform: none; /* same defence as the popover title — Astra uppercases `h3` globally */
}
.ap-pp__cart-added__product__attr {
	margin: 2px 0 0;
	color: var(--text-secondary, #5C5C5C);
}
.ap-pp__cart-added__product__title + .ap-pp__cart-added__product__attr {
	margin-top: var(--space-8, 8px);
}
.ap-pp__cart-added__product__price {
	margin: 2px 0 0;
	color: var(--text-primary, #191919);
}

/* Actions block — third child of the panel, picks up the outer `gap: 24px`
   automatically so it sits 24px below the product row. Internal `gap: 8px`
   stacks the two CTAs vertically with the spec's tight rhythm. */
.ap-pp__cart-added__actions {
	align-self: stretch;
	display: flex;
	flex-direction: column;
	gap: var(--space-8, 8px);
}

/* CTA buttons — anchor-as-button. Sentence-case + flat letter-spacing
   ship in the base rule so Astra's button globals can't push them back
   to uppercase (see `feedback_sandbox_button_baseline_resets`). */
.ap-pp__cart-added__btn {
	display: flex;
	height: 48px;
	padding: 12px var(--space-16, 16px);
	box-sizing: border-box;
	justify-content: center;
	align-items: center;
	gap: var(--space-8, 8px);
	align-self: stretch;
	border-radius: 12px;
	font-family: var(--font-family-ui, 'Outfit', sans-serif);
	font-size: var(--font-size-16, 16px);
	font-weight: 500;
	line-height: 1.5;
	letter-spacing: 0;
	text-transform: none;
	text-decoration: none;
	cursor: pointer;
}

/* Secondary — outline-only, transparent fill, primary text. */
.ap-pp__cart-added__btn--secondary {
	border: 2px solid var(--button-secondary-outline-enabled, #F0F0F0);
	background: transparent;
	color: var(--text-primary, #191919);
}
/* Five-property brand-wash lock at every interactive state. Outline shifts
   to the hover token, but bg stays transparent so the button keeps the
   ghost-button look (see `feedback_lock_button_states_against_brand_wash`). */
.ap-sandbox .ap-pp__cart-added__btn--secondary:hover,
.ap-sandbox .ap-pp__cart-added__btn--secondary:focus,
.ap-sandbox .ap-pp__cart-added__btn--secondary:focus-visible,
.ap-sandbox .ap-pp__cart-added__btn--secondary:active,
.ap-sandbox .ap-pp__cart-added__btn--secondary:visited {
	background-color: transparent;
	color: var(--text-primary, #191919);
	border-color: var(--button-secondary-outline-hover, var(--button-secondary-outline-enabled, #F0F0F0));
	outline: none;
	box-shadow: none;
	text-decoration: none;
}

/* Primary — dark fill, white text, no border. */
.ap-pp__cart-added__btn--primary {
	border: 0;
	background: var(--button-primary-background-enabled, #191919);
	color: var(--text-white, #FFF);
}
.ap-sandbox .ap-pp__cart-added__btn--primary:hover,
.ap-sandbox .ap-pp__cart-added__btn--primary:focus,
.ap-sandbox .ap-pp__cart-added__btn--primary:focus-visible,
.ap-sandbox .ap-pp__cart-added__btn--primary:active,
.ap-sandbox .ap-pp__cart-added__btn--primary:visited {
	background-color: var(--button-primary-background-hover, var(--button-primary-background-enabled, #191919));
	color: var(--text-white, #FFF);
	border-color: transparent;
	outline: none;
	box-shadow: none;
	text-decoration: none;
}

/* ─────────────────────────────────────────────────────────────────────
   Search modal — opens from `.ap-pp__header__icon--search` in the page
   header. Same overlay architecture as the cart/combo modals (fixed
   viewport-clip wrapper + dim backdrop + content panel + `.is-open`
   toggle), but the panel is a centred sheet rather than a side drawer:
   vertically centred in the viewport, with its side edges pinned to
   the outer edge of grid column 2 / column 11 (`--grid-col2-inset`)
   and its content box on the outer edge of column 3 / column 10 (side
   padding of one `--grid-col-step`), painted on gray-0 with a 15px
   corner radius.

   Centering + overflow contract (shared with the sizes-table modal):
   the wrapper is a flex scroll container and the panel centres via
   `margin: auto`. When the panel outgrows the viewport the auto
   margins collapse to 0 — the panel tops out at the wrapper's 32px
   vertical padding and the wrapper scrolls, so the top edge is never
   clipped. The backdrop is `position: fixed` so it can't scroll away
   with the panel.
   ───────────────────────────────────────────────────────────────────── */
.ap-pp__search-modal {
	position: fixed;
	inset: 0;
	z-index: 1102; /* same overlay tier as `.ap-pp__cart-modal` — above header */
	overflow: hidden;
	pointer-events: none;
	display: flex;
	/* Minimum panel ↔ viewport-edge gap once the panel overflows. */
	padding: 32px 0;
}
.ap-pp__search-modal.is-open {
	pointer-events: auto;
	overflow-y: auto;
}
body.ap-pp__search-modal-open { overflow: hidden; }

.ap-pp__search-modal__backdrop {
	/* Fixed (not absolute) so the scrim stays viewport-glued when the
	   wrapper scrolls a taller-than-viewport panel. */
	position: fixed;
	inset: 0;
	/* Light dim scrim — see `feedback_backdrops_use_light_token`. */
	background: var(--backdrop-background-light, rgba(10, 10, 10, 0.30));
	cursor: pointer;
	opacity: 0;
}
.ap-pp__search-modal.is-open .ap-pp__search-modal__backdrop {
	opacity: 1;
}

.ap-pp__search-modal__panel {
	/* Relative: containing block for the absolutely-anchored close pill.
	   The panel is a normal-flow flex item — `margin: auto` centres it
	   vertically while it fits and lets the wrapper scroll it (top edge
	   first) once it doesn't. Width mirrors the old left/right insets
	   (outer edge of grid column 2 / 11). */
	position: relative;
	margin: auto;
	width: calc(100% - 2 * var(--grid-col2-inset, 192px));
	/* Panel auto-sizes to its content; height = top/bottom padding + the
	   inner column's natural height. Horizontal padding is one grid step
	   (`--grid-col-step`'s % resolves against the viewport — the panel's
	   containing block), so with the panel's edges pinned to column
	   2/11's outer edges, the content box lands exactly on the outer
	   edge of column 3 / column 10. All inner content rows (field,
	   suggestions title + list, results title + grid) live inside
	   `.ap-pp__search-modal__column`, which spans the content box
	   full-width — the results UL clips against its edges. Top padding
	   is 48px (spec gap between modal top edge and the search field).
	   Bottom padding is 65px to leave the spec gap between the last
	   result's price and the panel bottom. The close button
	   (`position: absolute`) anchors to the panel's right border at
	   `right: 32px`, so it lives inside the side-padding gutter and
	   doesn't collide with the column. The gutter is floored at
	   --modal-close-clearance: --grid-col-step is %-based and below
	   ~1120px shrinks under the pill's 80px footprint, which used to
	   drop the pill onto the search field. */
	padding: 48px max(var(--grid-col-step, 64px), var(--modal-close-clearance, 96px)) 65px;
	box-sizing: border-box;
	background: var(--gray-0, #FFF);
	border-radius: var(--radius-15, 15px);
	opacity: 0;
	transform: translateY(-8px);
	transition: opacity 160ms ease, transform 160ms ease, padding-bottom 280ms ease-out;
}
.ap-pp__search-modal.is-open .ap-pp__search-modal__panel {
	opacity: 1;
	transform: translateY(0);
}
/* In search state the panel grows to make room for the see-all button +
   its 48px gaps; bottom padding drops from 65px (rest spec) to 48px (the
   spec gap below the button). Animated for parity with the grid track
   and text-results opacity transitions. */
.ap-pp__search-modal.is-searching .ap-pp__search-modal__panel {
	padding-bottom: 48px;
}

@media (prefers-reduced-motion: reduce) {
	.ap-pp__search-modal__panel { transition: none; }
}

/* The inner `.ap-pp__search-modal__column` carries no padding of its
   own: the panel's grid-step side padding already places the content
   box on column 3/10's outer edges, and the column (a normal-flow
   child) spans it full-width. The results UL clips overflowing tiles
   against that same edge via `overflow: hidden`. */

/* Bottom-sheet grabber — mobile-only affordance. Hidden on desktop where
   the modal renders as a centered panel. The mobile rule below flips it
   visible, centres it, and pins the 12px gap to the next content row.
   Mirrors `.ap-tpl__filter-modal__grabber`. */
.ap-pp__search-modal__grabber {
	display: none;
}

/* Header row — wraps the title + close. On desktop the title is hidden
   and the close stays absolutely positioned (existing rule), so the
   wrapper has no visual effect. The mobile rule below turns this into
   a flex row with the title on the left and the close pill on the right,
   mirroring `.ap-tpl__filter-modal__header`. */
.ap-pp__search-modal__title {
	display: none;
}

/* Close button — pill-shaped icon button anchored to the inner top-right
   of the panel (top: 54px, right: 32px). Five-property brand-wash lock at
   every interactive state, mirroring the rest of the sandbox. */
.ap-pp__search-modal__close {
	position: absolute;
	top: 54px;
	right: 32px;
	display: inline-flex;
	padding: 14px;
	align-items: center;
	gap: 10px;
	border: 0;
	border-radius: 999px;
	background: var(--icon-button-secondary-background-enabled, #F0F0F0);
	cursor: pointer;
}
.ap-pp__search-modal__close img {
	display: block;
	width: 20px;
	height: 20px;
}
.ap-sandbox .ap-pp__search-modal__close:hover,
.ap-sandbox .ap-pp__search-modal__close:focus,
.ap-sandbox .ap-pp__search-modal__close:focus-visible,
.ap-sandbox .ap-pp__search-modal__close:active {
	background-color: var(--icon-button-secondary-background-hover, var(--icon-button-secondary-background-enabled, #F0F0F0));
	color: inherit;
	border-color: transparent;
	outline: none;
	box-shadow: none;
}

/* Search field — pill row sitting at the panel top with the 24px search
   glyph on the left and the actual <input> filling the remaining width.
   Border + background are pinned via the new input semantic tokens so
   any future input on the site stays consistent without re-deriving the
   primitives. The 4px gap between icon and placeholder text comes from
   the spec; if a future input needs the standard 8px gap, prefer a new
   modifier over editing this base. */
.ap-pp__search-modal__field {
	display: flex;
	height: 60px;
	padding: 10px var(--space-12, 12px);
	box-sizing: border-box;
	align-items: center;
	gap: 4px;
	border-radius: var(--radius-15, 15px);
	border: 2px solid var(--input-border-focus, #191919);
	background: var(--input-background-enabled, #FFF);
}
/* Compact size variant — mobile-leaning instance (used in the burger
   panel). 48px shell, same padding/icon/typography as the 60px desktop
   default; only the outer height changes so all interior tokens stay
   consistent across both variants. */
.ap-pp__search-modal__field--compact { height: 48px; }
.ap-pp__search-modal__field-icon {
	display: inline-flex;
	flex: 0 0 24px;
	width: 24px;
	height: 24px;
}
.ap-pp__search-modal__field-icon img {
	display: block;
	width: 100%;
	height: 100%;
}
/* Reset Astra's global input chrome (border, box-shadow, rounded corners,
   inner padding) plus the browser's native `input[type="search"]`
   appearance, which otherwise paints a secondary rounded border inside
   our pill. Scoped under `.ap-pp__search-modal__field` for a (0,2,0)
   selector that beats Astra's `.entry-content input` (0,1,1). */
.ap-pp__search-modal__field .ap-pp__search-modal__field-input {
	flex: 1 1 0;
	min-width: 0;
	width: 100%;
	height: 100%;
	margin: 0;
	padding: 0;
	border: 0;
	border-radius: 0;
	background: transparent;
	box-shadow: none;
	outline: none;
	-webkit-appearance: none;
	appearance: none;
	color: var(--input-text-filled, #191919);
	font-family: var(--font-family-ui, 'Outfit', sans-serif);
	font-size: var(--font-size-16, 16px);
	font-weight: var(--font-weight-regular, 400);
	line-height: 1.5;
	letter-spacing: 0;
}
/* Kill the WebKit search-cancel button (the little × that appears on the
   right when the field has a value); we don't want a second close affordance
   competing with our explicit close button. */
.ap-pp__search-modal__field-input::-webkit-search-cancel-button,
.ap-pp__search-modal__field-input::-webkit-search-decoration {
	-webkit-appearance: none;
	appearance: none;
}
.ap-pp__search-modal__field-input::placeholder {
	color: var(--input-text-placeholder, #A3A3A3);
	opacity: 1; /* Firefox lowers placeholder opacity by default */
}
/* Five-property brand-wash lock — scoped under `.ap-pp__search-modal__field`
   so the (0,3,1) selector beats Astra's `.entry-content input:focus`
   (0,1,2). Resting border is already 0; we additionally zero box-shadow
   so Astra's focus-ring inset shadow can't draw an inner pill. */
.ap-sandbox .ap-pp__search-modal__field .ap-pp__search-modal__field-input:hover,
.ap-sandbox .ap-pp__search-modal__field .ap-pp__search-modal__field-input:focus,
.ap-sandbox .ap-pp__search-modal__field .ap-pp__search-modal__field-input:focus-visible,
.ap-sandbox .ap-pp__search-modal__field .ap-pp__search-modal__field-input:active {
	background-color: transparent;
	color: var(--input-text-filled, #191919);
	border: 0;
	outline: none;
	box-shadow: none;
}
/* Clear-input affordance — 24×24 glyph sitting at the field's right
   edge, surfaced only while the input has a value (sibling
   `:placeholder-shown` rule below). The field's own 12px right padding
   gives the spec'd 12px gap to the outer edge; `align-items: center` on
   the parent gives 18px top/bottom on the 60px shell (12px on the 48px
   compact variant). Chrome pinned at base + every interactive state so
   Astra's button globals + brand-wash can't bleed through. */
.ap-pp__search-modal__field-clear {
	flex: 0 0 24px;
	width: 24px;
	height: 24px;
	padding: 0;
	margin: 0;
	border: 0;
	border-radius: 0;
	background: transparent;
	box-shadow: none;
	outline: none;
	cursor: pointer;
	display: inline-flex;
	align-items: center;
	justify-content: center;
}
.ap-pp__search-modal__field-clear img {
	display: block;
	width: 24px;
	height: 24px;
}
.ap-pp__search-modal__field-input:placeholder-shown ~ .ap-pp__search-modal__field-clear {
	display: none;
}
.ap-sandbox .ap-pp__search-modal__field .ap-pp__search-modal__field-clear:hover,
.ap-sandbox .ap-pp__search-modal__field .ap-pp__search-modal__field-clear:focus,
.ap-sandbox .ap-pp__search-modal__field .ap-pp__search-modal__field-clear:focus-visible,
.ap-sandbox .ap-pp__search-modal__field .ap-pp__search-modal__field-clear:active {
	background-color: transparent;
	color: inherit;
	border: 0;
	outline: none;
	box-shadow: none;
}

/* Section titles — "Populiarios paieškos" (above suggestions list),
   "Populiarios prekės" / "{n} prekių" (above results grid), and
   "Rezultatai" (above the text-result list in search state). Identical
   body-md-regular typography (Outfit 16/1.5, tertiary text); each sits
   32px below the preceding row (or 32px from the top of its grid cell).
   Typography pinned at panel-scope to win over Astra's
   `.entry-content h3` defaults (same defence as `.ap-pp__cart-modal__title`
   above). */
.ap-pp__search-modal__panel .ap-pp__search-modal__suggestions-title,
.ap-pp__search-modal__panel .ap-pp__search-modal__results-title,
.ap-pp__search-modal__panel .ap-pp__search-modal__text-results-title {
	margin: 32px 0 0;
	color: var(--text-tertiary, #787878);
	font-family: var(--font-family-ui, 'Outfit', sans-serif);
	font-size: var(--font-size-16, 16px);
	font-weight: var(--font-weight-regular, 400);
	line-height: 1.5;
	letter-spacing: 0;
	text-transform: none;
}
/* Count span inside `.__results-title` (e.g. "19 prekių"): the number
   gets primary colour; the surrounding word inherits tertiary from the
   parent title. JS swaps the title's innerHTML between
   "Populiarios prekės" (rest state) and a span + word pair (search state). */
.ap-pp__search-modal__results-title__count {
	color: var(--text-primary, #191919);
}

/* Suggestions block collapse — once the user has typed a query
   (`.is-searching`, when the text-results column is shown), the
   "Populiarios paieškos" block folds away so the field sits directly
   above the results. Same `grid-template-rows: 1fr → 0fr` natural-
   height collapse the mobile pass uses (the `__inner` clips during
   the motion), but at the desktop search-state timing (280ms
   ease-out, matching the results-grid column flip and the panel's
   padding transition). The mobile block below overrides both rules
   with its own 360ms Material curve — unchanged. The collapsed
   title's 32px top margin clips with the inner; the results-grid
   titles carry their own 32px margin, so the field → "Rezultatai"
   gap keeps the same rhythm. */
.ap-pp__search-modal__suggestions {
	display: grid;
	grid-template-rows: 1fr;
	transition: grid-template-rows 280ms ease-out, opacity 280ms ease-out;
}
.ap-pp__search-modal__suggestions__inner {
	overflow: hidden;
	min-height: 0;
}
.ap-pp__search-modal.is-searching .ap-pp__search-modal__suggestions {
	grid-template-rows: 0fr;
	opacity: 0;
	pointer-events: none;
}

/* Suggestions list — pill row of popular searches, 8px below the title.
   Chips wrap to a second (or further) row when they exceed the column
   width; the `gap` shorthand applies the same 8px to both the column and
   row gaps. */
.ap-pp__search-modal__suggestions-list {
	list-style: none;
	margin: 8px 0 0;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
}
.ap-pp__search-modal__suggestion {
	display: flex;
	height: 48px;
	padding: 0 var(--space-16, 16px);
	justify-content: center;
	align-items: center;
	gap: 10px;
	border: 0;
	border-radius: var(--radius-10, 10px);
	background: var(--gray-50, #F7F7F7);
	color: var(--text-primary, #191919);
	font-family: var(--font-family-ui, 'Outfit', sans-serif);
	font-size: var(--font-size-14, 14px);
	font-weight: var(--font-weight-medium, 500);
	line-height: 1.5;
	letter-spacing: 0;
	text-transform: none;
	cursor: pointer;
}
/* Brand-wash lock — pin all five at the resting value (gray-50 fill,
   primary text, no ring) so multi-brand-uni's brand-wash can't repaint
   the pill on hover/focus/active. */
.ap-sandbox .ap-pp__search-modal__suggestion:hover,
.ap-sandbox .ap-pp__search-modal__suggestion:focus,
.ap-sandbox .ap-pp__search-modal__suggestion:focus-visible,
.ap-sandbox .ap-pp__search-modal__suggestion:active {
	background-color: var(--gray-50, #F7F7F7);
	color: var(--text-primary, #191919);
	border-color: transparent;
	outline: none;
	box-shadow: none;
}

/* Results grid — wraps the text-result list (left column) and the
   product-tiles stack (right column). The grid is always live; only the
   left track size animates between 0 and one tile width. In the rest
   state the left track is 0 and `__text-results` is faded out + clipped
   via `overflow: hidden`, so the stack visually takes the full width.
   In `.is-searching` the left track expands to `var(--tile-w)` (one
   tile width) with a 4px column-gap matching the tile grid below, and
   `__text-results` fades in. Because both tiles and the text-results
   slot reference the same `--tile-w`, the product tiles keep a
   constant size across states — when search starts the row simply
   shifts right by one slot rather than resizing.
   `--tile-w` is set by JS (`updateSearchTileWidth` in chrome-scripts):
   `(results-grid.offsetWidth - 12) / 4`, matching "4 equal tiles + 3
   gaps of 4px fill the grid". 322px is a fallback for first paint
   before JS runs. Transitions: 280ms ease-out per Apple HIG, honouring
   `prefers-reduced-motion`. The right track uses `minmax(0, 1fr)`
   instead of plain `1fr` so it ignores the UL's intrinsic min-content
   (sum of all non-shrinking 322px tiles) — see
   `feedback_flex_min_width_auto_trap`. */
.ap-pp__search-modal__results-grid {
	--tile-w: 322px;
	display: grid;
	grid-template-columns: 0 minmax(0, 1fr);
	column-gap: 0;
	transition: grid-template-columns 280ms ease-out, column-gap 280ms ease-out;
}
.ap-pp__search-modal__text-results {
	min-width: 0;
	overflow: hidden;
	opacity: 0;
	transition: opacity 240ms ease-out;
}
/* Inner content laid out at the final tile width regardless of the
   animating grid cell. Without this, each intermediate cell width
   re-flows the text rows (icon + word breaking awkwardly at narrow
   widths), producing a visible "squish-then-expand" during the
   transition. Pinning the width here makes the cell's `overflow: hidden`
   reveal the already-final layout left-to-right as the track grows. */
.ap-pp__search-modal__text-results-title,
.ap-pp__search-modal__text-results-list {
	width: var(--tile-w);
}
.ap-pp__search-modal.is-searching .ap-pp__search-modal__results-grid {
	grid-template-columns: var(--tile-w) minmax(0, 1fr);
	column-gap: 4px;
}
.ap-pp__search-modal.is-searching .ap-pp__search-modal__text-results {
	opacity: 1;
}
/* Tile-count cap — markup renders the max (4 tiles, per the rest spec);
   in search state we drop one to leave 3 product tiles alongside the
   text-result panel. The BE will eventually populate the markup with
   real result data, but the visible-count caps stay enforced here. */
.ap-pp__search-modal.is-searching .ap-pp__search-modal__result:nth-child(n+4) {
	display: none;
}
@media (prefers-reduced-motion: reduce) {
	.ap-pp__search-modal__results-grid,
	.ap-pp__search-modal__text-results {
		transition: none;
	}
}

/* Text-result list — 16px below the "Rezultatai" title (per spec). Row
   density comes from the natural body-md-regular line-height; no extra
   row gap. Each row is icon + 4px gap + text, all in primary colour. */
.ap-pp__search-modal__text-results-list {
	list-style: none;
	margin: 16px 0 0;
	padding: 0;
}
.ap-pp__search-modal__text-result {
	display: flex;
	align-items: center;
	gap: 4px;
	color: var(--text-primary, #191919);
	font-family: var(--font-family-ui, 'Outfit', sans-serif);
	font-size: var(--font-size-16, 16px);
	font-weight: var(--font-weight-regular, 400);
	line-height: 1.5;
	letter-spacing: 0;
}
/* Icon — 16×16 search glyph. Astra's global `img { max-width: 100% }`
   doesn't bite here (icon is intrinsic 16×16) but we pin both max
   constraints for consistency with the other sandbox icons (see
   `feedback_astra_img_max_width_override`). */
.ap-pp__search-modal__text-result__icon {
	display: block;
	flex: 0 0 16px;
	width: 16px;
	height: 16px;
	max-width: none;
	max-height: none;
}

/* ─── No-results state (Figma 1728:4722) ──────────────────────────────
   `.is-no-results` joins `.is-searching` on the modal when the active
   query returns 0 items (chrome-scripts owns the toggle; search-live.js
   re-syncs it when a live tally lands). Left column: the text-result
   list swaps for the single "✕ Įrašų nerasta" row. Right stack: the
   count title stays ("0 prekių" via the existing count pipeline), the
   tiles + see-all hide, and the no-results block appears — message
   echoing the query, divider, popular-searches pills (reusing the
   suggestion classes; the rest-state suggestions block is already
   collapsed by `.is-searching`). Vertical pitch per the frame: title →
   16px → message → 24px → divider → 24px → pills title → 8px → pills. */
.ap-pp__search-modal__text-results-empty {
	display: none;
	margin-top: 16px; /* same title gap the list carries */
}
.ap-pp__search-modal.is-no-results .ap-pp__search-modal__text-results-empty {
	display: flex;
}
.ap-pp__search-modal.is-no-results .ap-pp__search-modal__text-results-list {
	display: none;
}
.ap-pp__search-modal__no-results {
	display: none;
}
.ap-pp__search-modal.is-no-results .ap-pp__search-modal__no-results {
	display: block;
}
.ap-pp__search-modal.is-no-results .ap-pp__search-modal__results,
.ap-pp__search-modal.is-no-results .ap-pp__search-modal__see-all-wrap {
	display: none;
}
/* Message — body-md, tertiary, with the echoed query in primary. Panel-
   scoped typography pin mirrors the section-title defence above. */
.ap-pp__search-modal__panel .ap-pp__search-modal__no-results__message {
	margin: 16px 0 0;
	color: var(--text-tertiary, #787878);
	font-family: var(--font-family-ui, 'Outfit', sans-serif);
	font-size: var(--font-size-16, 16px);
	font-weight: var(--font-weight-regular, 400);
	line-height: 1.5;
	letter-spacing: 0;
}
.ap-pp__search-modal__no-results__query {
	color: var(--text-primary, #191919);
}
.ap-pp__search-modal__panel .ap-pp__search-modal__no-results__divider {
	margin: 24px 0 0;
	border: 0;
	height: 1px;
	background: var(--divider-background-light, #E4E4E4);
}
/* The shared suggestions-title rule carries a 32px top margin (its gap
   in the rest-state column); inside the no-results block the frame puts
   it 24px below the divider. */
.ap-pp__search-modal__panel .ap-pp__search-modal__no-results .ap-pp__search-modal__suggestions-title {
	margin-top: 24px;
}

/* Results row — fixed 322×428 product tiles, 4px gap, single line for
   now (no wrap). 16px gap from the "Populiarios prekės" title above (the
   title itself carries the 32px gap from the suggestions row). The UL
   sits inside `.ap-pp__search-modal__results-stack` (right column of
   `__results-grid`); tiles past the right edge are clipped via
   `overflow: hidden` until pagination/scroll is wired up. */
.ap-pp__search-modal__results {
	list-style: none;
	margin: 16px 0 0;
	padding: 0;
	display: flex;
	flex-wrap: nowrap;
	gap: 4px;
	overflow: hidden;
}
/* Result tile — column flex matching `.ap-pp__header__mega__product`:
   media on top, title 16px below, price 8px below the title. Tile width
   is pinned to the shared `--tile-w` token (set by JS, computed so 4
   tiles + 3 gaps exactly fill the grid container). With both 4-in-rest
   and 3-in-search states using the same value, tiles keep a constant
   width across the search transition — the row appears to shift right
   by one slot rather than resizing. The media's `aspect-ratio: 3 / 4`
   makes the height track the width. */
.ap-pp__search-modal__result {
	display: flex;
	flex-direction: column;
	flex: 0 0 var(--tile-w);
	color: var(--text-primary, #191919);
}
/* Media — 3:4 photo. Astra's global `img { max-width: 100% }` would
   silently cap the photo if it ever rendered narrower than the natural
   width; neutralise both max constraints (see
   `feedback_astra_img_max_width_override` memory). `object-fit: cover`
   crops to the 3:4 box regardless of the source image's ratio. */
.ap-pp__search-modal__result__media {
	display: block;
	width: 100%;
	aspect-ratio: 3 / 4;
	max-width: none;
	max-height: none;
	object-fit: cover;
}
/* Title + price — mirror the mega-product spacing (16px above title,
   8px above price, 8px left padding). Title is panel-scoped so its
   margin/text-transform beat Astra's `.entry-content h3` defaults. */
.ap-pp__search-modal__panel .ap-pp__search-modal__result__title {
	align-self: stretch;
	margin: 16px 0 0;
	padding-left: 8px;
	color: var(--text-primary, #191919);
	text-transform: none;
}
.ap-pp__search-modal__result__price {
	align-self: stretch;
	margin: 8px 0 0;
	padding-left: 8px;
	color: var(--text-primary, #191919);
}

/* "Rodyti visas prekes" CTA — secondary-style pill shown only in search
   state. Lives inside `.ap-pp__search-modal__results-stack` (after the
   results UL) so the wrapper's `justify-content: center` centres the
   button within the right column of `__results-grid` (i.e. aligned with
   the `__results` tile strip), not the whole panel. Wrapper handles
   vertical reveal (`max-height` + `margin-top` + `opacity` transitions);
   the button itself is `inline-flex` per the spec, sized to its content.
   Spacing per spec: 48px above (margin-top in search state), 48px below
   (handled by the panel's `padding-bottom: 48px` in search state). Same
   animation timing as the results-grid track and text-results opacity. */
.ap-pp__search-modal__see-all-wrap {
	display: flex;
	justify-content: center;
	overflow: hidden;
	max-height: 0;
	margin-top: 0;
	opacity: 0;
	/* Size animates with the grid; opacity is intentionally NOT listed at
	   rest so close snaps the button to invisible (burger-close pattern —
	   no transition at rest = instant disappear under the collapsing
	   layout). The open-only opacity transition lives on `.is-searching`. */
	transition: max-height 280ms ease-out, margin-top 280ms ease-out;
}
.ap-pp__search-modal.is-searching .ap-pp__search-modal__see-all-wrap {
	max-height: 48px;
	margin-top: 48px;
	opacity: 1;
	/* Wait 280ms for the grid + wrap-size reveal to land, then fade the
	   button in over 200ms — mirrors the burger-close opening cadence
	   (panel lands, then pill fades). */
	transition: max-height 280ms ease-out, margin-top 280ms ease-out, opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 280ms;
}
.ap-pp__search-modal__see-all {
	display: inline-flex;
	height: 48px;
	padding: 12px 16px;
	justify-content: center;
	align-items: center;
	gap: var(--space-8, 8px);
	border: 0;
	border-radius: 12px;
	background: var(--button-secondary-background-enabled, #F0F0F0);
	color: var(--text-primary, #191919);
	font-family: var(--font-family-ui, 'Outfit', sans-serif);
	font-size: var(--font-size-16, 16px);
	font-weight: var(--font-weight-medium, 500);
	line-height: 1.5;
	letter-spacing: 0;
	text-transform: none;
	cursor: pointer;
}
/* Brand-wash lock — pin all five at the resting value so multi-brand-uni
   can't repaint the secondary button on hover/focus/active. */
.ap-sandbox .ap-pp__search-modal__see-all:hover,
.ap-sandbox .ap-pp__search-modal__see-all:focus,
.ap-sandbox .ap-pp__search-modal__see-all:focus-visible,
.ap-sandbox .ap-pp__search-modal__see-all:active {
	background-color: var(--button-secondary-background-enabled, #F0F0F0);
	color: var(--text-primary, #191919);
	border-color: transparent;
	outline: none;
	box-shadow: none;
}
@media (prefers-reduced-motion: reduce) {
	.ap-pp__search-modal__see-all-wrap { transition: none; }
}

/* ─────────────────────────────────────────────────────────────────────
   Accordion primitive — `.ap-pp__section-head` is a button that toggles
   an adjacent panel via `aria-expanded`. Used by the product page's
   details accordion (Aprašymas / Sudėtis / Pristatymas) AND by the
   tenant list's filter modal (Rūšiuoti pagal, …). Each page declares
   its own "what hides when collapsed" rule against its specific sibling
   class so the chevron primitive stays generic. Chevron rotates 180°
   on collapse via a 0.18s ease transition. Brand-wash lock pinned at
   resting per the universal lock pattern.
   ───────────────────────────────────────────────────────────────────── */
.ap-pp__section-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	width: 100%;
	gap: 12px;
	margin: 0;
	padding: 0;
	background: transparent;
	border: 0;
	color: inherit;
	cursor: pointer;
	text-align: left;
}
.ap-pp__section-head[aria-expanded="true"] {
	margin-bottom: 16px;
}
.ap-pp__section-head[aria-expanded="false"] .ap-pp__section-head__icon img {
	transform: rotate(180deg);
}
.ap-sandbox .ap-pp__section-head:hover,
.ap-sandbox .ap-pp__section-head:focus,
.ap-sandbox .ap-pp__section-head:focus-visible,
.ap-sandbox .ap-pp__section-head:active {
	background-color: transparent;
	color: inherit;
	border-color: transparent;
	outline: none;
	box-shadow: none;
}
.ap-pp__section-title {
	color: var(--text-primary, #191919);
	margin: 0;
}
.ap-pp__section-head__icon {
	display: inline-flex;
	width: 24px;
	height: 24px;
	flex-shrink: 0;
}
.ap-pp__section-head__icon img {
	display: block;
	width: 100%;
	height: 100%;
	transition: transform 0.18s ease;
}

/* Hairline divider — 1px line, 32px top inset by default. Used by the
   product page summary and the tenant list's filter modal; pages own
   the horizontal inset via their parent's padding. Promoted here so the
   `<hr>` doesn't fall back to browser defaults (which add a visible
   margin-bottom) on pages that don't redeclare the rule. */
.ap-pp__divider {
	border: 0;
	height: 1px;
	margin: 32px 0 0;
	background: var(--divider-background-light, #E4E4E4);
}

/* ─────────────────────────────────────────────────────────────────────
   Size-option chip — `.ap-pp__size-option`. Used by the product page's
   size selector (row of 5 chips, flex: 1 stretches each) AND by the
   tenant list's filter modal (grid of 6 chips per row — grid cells
   stretch their items by default, so the `flex: 1` is inert there).
   The chip owns its own shape + selected/disabled visuals + brand-wash
   locks; the page that hosts it only owns the container's layout
   (flex row vs grid).
   ───────────────────────────────────────────────────────────────────── */
.ap-pp__size-option {
	display: flex;
	padding: 16px 15px;
	justify-content: center;
	align-items: center;
	gap: 10px;
	flex: 1;
	min-width: 0;
	box-sizing: border-box;
	border-radius: var(--radius-10, 10px);
	/* Reserve a 2px border slot so the 1↔2 toggle on selection doesn't shift
	   layout. Default state paints a 1px stroke via inset box-shadow, leaving
	   the actual border transparent. */
	border: 2px solid transparent;
	box-shadow: inset 0 0 0 1px var(--size-selector-outline-enabled, #E4E4E4);
	background: transparent;
	color: var(--text-primary, #191919);
	cursor: pointer;
}
.ap-pp__size-option[aria-pressed="true"] {
	border-color: var(--size-selector-outline-selected, #191919);
	box-shadow: none;
	font-weight: var(--font-weight-bold, 700);
}
/* On hover / focus / click, pin EVERY visible ring on the tile to its
   resting value so neither Astra's button globals nor the browser focus
   ring can paint a competing colour. Three properties are involved:
     • `border-color`  – Astra's `button:hover/focus` paints this
     • `outline`       – browsers paint a focus ring here (NOT the same
                         as `border`; it lives outside the border box)
     • `box-shadow`    – the tile's visible 1px stroke is a box-shadow,
                         not a border; lock it so it can't be overridden
   Two rules: the first locks non-selected tiles to their resting look
   (transparent border, 1px gray inset shadow); the second locks the
   selected tile to its dark border and absent shadow. */
.ap-pp__size-option:not([aria-pressed="true"]):hover,
.ap-pp__size-option:not([aria-pressed="true"]):focus,
.ap-pp__size-option:not([aria-pressed="true"]):focus-visible,
.ap-pp__size-option:not([aria-pressed="true"]):active {
	background-color: transparent;
	border-color: transparent;
	color: var(--text-primary, #191919);
	outline: none;
	box-shadow: inset 0 0 0 1px var(--size-selector-outline-enabled, #E4E4E4);
}
.ap-pp__size-option[aria-pressed="true"]:hover,
.ap-pp__size-option[aria-pressed="true"]:focus,
.ap-pp__size-option[aria-pressed="true"]:focus-visible,
.ap-pp__size-option[aria-pressed="true"]:active {
	background-color: transparent;
	border-color: var(--size-selector-outline-selected, #191919);
	color: var(--text-primary, #191919);
	outline: none;
	box-shadow: none;
}

/* Size option disabled — text drops to muted, the 1px enabled-grey stroke
   stays (from the default state's inset shadow), and a 1px diagonal runs
   exactly from the tile's bottom-left corner to its top-right corner.
   The strike is painted by a linear-gradient band parallel to the BL→TR
   diagonal — `to bottom right` makes the gradient axis perpendicular to
   that diagonal, so the band lands flush regardless of tile dimensions. */
.ap-pp__size-option:disabled {
	color: var(--text-muted, #D4D4D4);
	cursor: not-allowed;
	background-image: linear-gradient(
		to bottom right,
		transparent calc(50% - 0.5px),
		var(--size-selector-outline-enabled, #E4E4E4) calc(50% - 0.5px),
		var(--size-selector-outline-enabled, #E4E4E4) calc(50% + 0.5px),
		transparent calc(50% + 0.5px)
	);
	/* Clip the painted strikethrough to the tile's rounded silhouette so the
	   line ends at the curved edge instead of running into the cut corners. */
	clip-path: inset(0 round var(--radius-10, 10px));
}
/* Disabled tiles must look identical on hover/focus/active: the
   non-pressed hover rule above (same specificity 0,3,0) flips `color`
   back to text-primary, which would un-mute the disabled label. Scoping
   under `.ap-sandbox` bumps this to 0,4,0 so it wins, and every visible
   property is pinned to the disabled resting state. */
.ap-sandbox .ap-pp__size-option:disabled:hover,
.ap-sandbox .ap-pp__size-option:disabled:focus,
.ap-sandbox .ap-pp__size-option:disabled:focus-visible,
.ap-sandbox .ap-pp__size-option:disabled:active {
	background-color: transparent;
	color: var(--text-muted, #D4D4D4);
	border-color: transparent;
	outline: none;
	box-shadow: inset 0 0 0 1px var(--size-selector-outline-enabled, #E4E4E4);
}

/* SM variant — Figma SizeSelector `Size=SM` (nodes 571:441…450): the
   32px-high chip for drawer item rows (cart drawer, combo drawer). Same
   state model as the MD base above — selected 2px ring + bold, disabled
   strike + muted, brand-wash locks — only the box scale changes: 32px
   min-height (Figma pins height at 32; min- not fixed because real
   catalog size terms like "104-110" wrap to two lines in narrow drawer
   cells and must grow the chip like the MD base does, instead of
   overflowing a hard 32px box), radius-8. Vertical padding is 7px, not
   Figma's 8: the base chip reserves a 2px border slot where Figma draws
   a 1px inside stroke, so 7px + 2px keeps the label's 9px inset from
   the outer edge AND lands single-line chips exactly on the 32px spec
   (2·2 border + 2·7 padding + 13.8px label line-box = 31.8 → min-height
   rounds it to 32). Typography rides on `.type-label-sm-regular` in the
   markup (Figma Label/SM/Regular; the base [aria-pressed="true"] rule
   bolds it into Label/SM/Bold when selected). Layout (flex: 1 / grid
   stretch) is inherited — the host row still owns chip widths. */
.ap-pp__size-option--sm {
	min-height: 32px;
	padding: 7px 12px;
	border-radius: var(--radius-8, 8px);
}
/* Disabled strike clips against the smaller corner radius. */
.ap-pp__size-option--sm:disabled {
	clip-path: inset(0 round var(--radius-8, 8px));
}

/* ─────────────────────────────────────────────────────────────────────
   Universal accent-wash kill. Astra + multi-brand-uni paint the active
   brand colour on `button:hover/focus/active`, `a:hover/focus/active`,
   and `input:focus` defaults — on this brand the colour is a vivid
   pink/red, which leaks into every interactive element that doesn't
   explicitly lock all five ring properties (see
   `feedback_lock_button_states_against_brand_wash` memory). This rule
   defangs every <button>, <a>, <input>, <select>, and <textarea> under
   `.ap-sandbox` by resetting the five candidate properties to neutral
   resting values.

   Specificity is low on purpose: `.ap-sandbox button:hover` = (0,2,1).
   Per-element rules below (e.g. `.ap-pp__add-to-cart:hover` with its
   dark fill + white text, or `.ap-pp__personalize__field-input:focus`
   with its visible gray border + white fill) all have higher specificity
   and keep ownership of element-specific resting values. This rule only
   "fills in" the props those rules don't already set — which catches
   the brand-wash leak. If a new interactive element is added later,
   it's covered for free.
   ───────────────────────────────────────────────────────────────────── */
.ap-sandbox button:hover,
.ap-sandbox button:focus,
.ap-sandbox button:focus-visible,
.ap-sandbox button:active,
.ap-sandbox a:hover,
.ap-sandbox a:focus,
.ap-sandbox a:focus-visible,
.ap-sandbox a:active,
.ap-sandbox input:hover,
.ap-sandbox input:focus,
.ap-sandbox input:focus-visible,
.ap-sandbox input:active,
.ap-sandbox select:hover,
.ap-sandbox select:focus,
.ap-sandbox select:focus-visible,
.ap-sandbox select:active,
.ap-sandbox textarea:hover,
.ap-sandbox textarea:focus,
.ap-sandbox textarea:focus-visible,
.ap-sandbox textarea:active {
	background-color: transparent;
	color: inherit;
	border-color: transparent;
	outline: none;
	box-shadow: none;
}


/* ─────────────────────────────────────────────────────────────────────
   Sizes-table modal — opened from any `.ap-pp__sizes-table` anchor on
   the product-page sandbox. Shell mirrors `.ap-pp__search-modal__panel`
   so the two modal types feel like the same family: gray-0 panel on a
   light-token backdrop, radius-15 corners, close pill anchored to the
   panel's inner top-right. Body content (the actual size chart) lands
   in a later pass; only the shell + close are wired here.
   ───────────────────────────────────────────────────────────────────── */
.ap-pp__sizes-table-modal {
	position: fixed;
	inset: 0;
	z-index: 1102; /* same overlay tier as `.ap-pp__search-modal` */
	overflow: hidden;
	pointer-events: none;
	display: flex;
	/* Minimum panel ↔ viewport-edge gap once the panel overflows —
	   same centering + overflow contract as `.ap-pp__search-modal`. */
	padding: 32px 0;
}
.ap-pp__sizes-table-modal.is-open {
	pointer-events: auto;
	overflow-y: auto;
}
body.ap-pp__sizes-table-modal-open { overflow: hidden; }

.ap-pp__sizes-table-modal__backdrop {
	/* Fixed (not absolute) so the scrim stays viewport-glued when the
	   wrapper scrolls a taller-than-viewport panel. */
	position: fixed;
	inset: 0;
	/* Light dim scrim — see `feedback_backdrops_use_light_token`. */
	background: var(--backdrop-background-light, rgba(10, 10, 10, 0.30));
	cursor: pointer;
	opacity: 0;
	transition: opacity 160ms ease;
}
.ap-pp__sizes-table-modal.is-open .ap-pp__sizes-table-modal__backdrop {
	opacity: 1;
}

/* Centered panel — same gray-0 shell + radius as the search modal.
   Height is content-driven: the two columns (form + visual) live in a
   flex row so the panel grows to the taller column instead of
   clipping/scrolling the visual; on short viewports the WRAPPER
   scrolls the whole panel (top edge first — see the search modal's
   centering + overflow contract). Panel padding is 16px so the gray
   `__visual` shape can sit 16px from the panel edges; the form column
   carries its own 32px inset to keep the form content at the 48px
   gutter (16 panel + 32 form) the close pill needs. */
.ap-pp__sizes-table-modal__panel {
	/* Relative: containing block for the absolutely-anchored close pill.
	   `margin: auto` centres the flex item vertically while it fits and
	   collapses once it overflows. Width mirrors the search modal (outer
	   edge of grid column 2 / 11) so both modals share width on any
	   viewport. Form column on the left stays fixed at 564px; the
	   `__visual` absorbs the extra width. */
	position: relative;
	margin: auto;
	width: calc(100% - 2 * var(--grid-col2-inset, 192px));
	box-sizing: border-box;
	padding: 16px;
	background: var(--gray-0, #FFF);
	border-radius: var(--radius-15, 15px);
	opacity: 0;
	transform: translateY(-8px);
	transition: opacity 160ms ease, transform 160ms ease;
}

/* Desktop two-column row — form (fixed) + visual (fills remainder).
   `align-items: stretch` lets the gray visual shape match the row's
   height (the taller of the two columns) so it always frames its full
   content. 16px gap between the columns. */
.ap-pp__sizes-table-modal__columns {
	display: flex;
	align-items: stretch;
	gap: 16px;
}
/* Left form column — sized so the visual's left edge lands on grid
   column 6: the panel starts at column 2's outer edge, so the form
   spans 4 grid steps minus the 16px panel padding + 16px column gap.
   vw-based --grid-col-w (not the %-based inset tokens) because %
   here would resolve against the columns row, not the viewport. */
.ap-pp__sizes-table-modal__form {
	/* Shrinkable: on in-between viewports (769–~930px) the panel's
	   content box is narrower than the basis and a rigid column would
	   poke out of the panel. */
	flex: 0 1 calc(4 * (var(--grid-col-w, 114.66px) + var(--grid-gutter, 32px)) - 32px);
	min-width: 0;
	box-sizing: border-box;
	padding: 32px 32px 0;
}
.ap-pp__sizes-table-modal.is-open .ap-pp__sizes-table-modal__panel {
	opacity: 1;
	transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
	.ap-pp__sizes-table-modal__panel { transition: none; }
	.ap-pp__sizes-table-modal__backdrop { transition: none; }
}

/* Grabber hidden on desktop (mobile bottom-sheet only). */
.ap-pp__sizes-table-modal__grabber { display: none; }

/* Modal title — sits at the top-left of the panel content (panel padding
   anchors it 48px from the panel's top + left edges). The close pill at
   `top:32px; right:32px` overlaps the gutter on the right; the title
   never collides because the heading wraps before reaching the pill. */
.ap-pp__sizes-table-modal__title {
	margin: 0 0 32px;
	color: var(--text-primary, #191919);
}

/* Close pill — same primitive as `.ap-pp__search-modal__close`: 14px
   pad, pill radius, secondary icon-button token, all interactive states
   pinned to defeat brand-wash. Anchored to the panel's inner top-right
   so its position survives content reflow. `z-index: 1` keeps it above
   the `__visual` shape (which sits at top:16, right:16) so the close
   stays clickable on the right column. */
.ap-pp__sizes-table-modal__close {
	position: absolute;
	top: 32px;
	right: 32px;
	z-index: 1;
	display: inline-flex;
	padding: 14px;
	align-items: center;
	gap: 10px;
	border: 0;
	border-radius: 999px;
	background: var(--icon-button-secondary-background-enabled, #F0F0F0);
	cursor: pointer;
}
.ap-pp__sizes-table-modal__close img {
	display: block;
	width: 20px;
	height: 20px;
}
.ap-sandbox .ap-pp__sizes-table-modal__close:hover,
.ap-sandbox .ap-pp__sizes-table-modal__close:focus,
.ap-sandbox .ap-pp__sizes-table-modal__close:focus-visible,
.ap-sandbox .ap-pp__sizes-table-modal__close:active {
	background-color: var(--icon-button-secondary-background-hover, var(--icon-button-secondary-background-enabled, #F0F0F0));
	color: inherit;
	border-color: transparent;
	outline: none;
	box-shadow: none;
}

/* Row that lays the two measurement fields (Ūgis, Svoris) side by side.
   Each `.ap-pp__personalize__field` child already ships with
   `flex: 1; min-width: 0`, so they split the row evenly minus the 24px
   spec gap. Width is capped to match the fit-options row width below:
   3 cards × (140 image + 16 padding) + 2 × 16 gap = 500px. Keep these
   two values in sync — if the jersey image size changes, update both. */
.ap-pp__sizes-table-modal__field-row {
	display: flex;
	gap: 24px;
	max-width: 500px;
}

/* Input shell — flex row that hosts the bare <input> plus a trailing
   `cm` suffix. Owns the visual chrome (60px height, 1px gray-200
   border, radius-15, white background, 10px×12px inner padding) that
   `.ap-pp__personalize__field-input` paints directly on its <input> —
   the difference here is the chrome lives on the wrapper so the suffix
   can sit inside the same shell. `gap: 8px` is the spec gap from input
   to suffix. */
.ap-pp__sizes-table-modal__input-shell {
	display: flex;
	align-items: center;
	gap: 8px;
	height: 60px;
	padding: 10px var(--space-12, 12px);
	box-sizing: border-box;
	border: 1px solid var(--gray-200, #E4E4E4);
	border-radius: var(--radius-15, 15px);
	background: var(--basic-white, #FFF);
}

/* Bare input — strips Astra's input chrome (border + box-shadow +
   radius at 0,1,1) and the sandbox's universal accent-wash kill so
   the shell above carries the visual border instead. See
   `feedback_astra_input_chrome_override`. */
.ap-sandbox .ap-pp__sizes-table-modal__input-field {
	flex: 1;
	min-width: 0;
	height: auto;
	padding: 0;
	border: 0;
	border-radius: 0;
	background: transparent;
	color: var(--text-primary, #191919);
	box-shadow: none;
	-webkit-appearance: none;
	appearance: none;
}
.ap-sandbox .ap-pp__sizes-table-modal__input-field::placeholder {
	color: var(--gray-400, #A3A3A3);
	opacity: 1;
}
.ap-sandbox .ap-pp__sizes-table-modal__input-field:hover,
.ap-sandbox .ap-pp__sizes-table-modal__input-field:focus,
.ap-sandbox .ap-pp__sizes-table-modal__input-field:focus-visible,
.ap-sandbox .ap-pp__sizes-table-modal__input-field:active {
	background: transparent;
	color: var(--text-primary, #191919);
	border-color: transparent;
	outline: none;
	box-shadow: none;
}

/* "cm" suffix — tertiary-token grey, Outfit 16/400. Line-height pinned
   to `normal` (spec) rather than the .type-body-md-regular 1.5 so the
   suffix doesn't add vertical metrics to the shell. */
.ap-pp__sizes-table-modal__input-suffix {
	flex: 0 0 auto;
	color: var(--text-tertiary, #787878);
	line-height: normal;
}

/* Sex radiogroup — `Vyras` / `Moteris` pair. 24px gap above the field
   row, 16px gap between the two radios. Visuals are the same custom
   radio primitive used by `.ap-tpl__filter-modal__sort-option`: 20×20
   SVG marker via `::before` keyed off `aria-pressed`, 8px gap between
   marker and label, brand-wash locked at all interactive states. */
.ap-pp__sizes-table-modal__sex-options {
	display: flex;
	gap: 16px;
	margin-top: 24px;
}
.ap-pp__sizes-table-modal__sex-option {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 0;
	border: 0;
	background: transparent;
	color: var(--text-primary, #191919);
	cursor: pointer;
	text-align: left;
	letter-spacing: 0;
	text-transform: none;
}
.ap-pp__sizes-table-modal__sex-option::before {
	content: '';
	flex-shrink: 0;
	width: 20px;
	height: 20px;
	background-image: url('../img/sandbox/radio-unchecked.svg');
	background-repeat: no-repeat;
	background-position: center;
}
.ap-pp__sizes-table-modal__sex-option[aria-pressed="true"]::before {
	background-image: url('../img/sandbox/radio-checked.svg');
}
.ap-sandbox .ap-pp__sizes-table-modal__sex-option:hover,
.ap-sandbox .ap-pp__sizes-table-modal__sex-option:focus,
.ap-sandbox .ap-pp__sizes-table-modal__sex-option:focus-visible,
.ap-sandbox .ap-pp__sizes-table-modal__sex-option:active {
	background-color: transparent;
	color: var(--text-primary, #191919);
	border-color: transparent;
	outline: none;
	box-shadow: none;
}

/* Section title for the merch-fit chooser — same typographic treatment
   as `.ap-pp__personalize__field-label` (Outfit 14/500/150%, primary
   text colour) but semantically a group heading, not a form label, so
   it gets its own class. Owns the 24px gap from the sex-options above. */
.ap-pp__sizes-table-modal__group-title {
	margin-top: 24px;
	color: var(--text-primary, #191919);
}

/* Merch-fit chooser — three 3:4 cards in a row, 16px gap between them.
   4px top gap mirrors the title-to-control rhythm used by the input
   fields above (label → 4px → input). Cards are radiogroup buttons so
   the existing `[role="radiogroup"]` handler in product-page-sandbox.php
   flips `aria-pressed` between them for free. Selected-state visuals
   land in a later pass once the card content is specced. */
.ap-pp__sizes-table-modal__fit-options {
	display: flex;
	gap: 16px;
	margin-top: 4px;
}
.ap-pp__sizes-table-modal__fit-option {
	position: relative;
	display: flex;
	flex-direction: column;
	align-items: center;
	/* Shrinkable so the three cards compress (jersey image scales via
	   its max-width: 100%) instead of overflowing a squeezed form. */
	flex: 0 1 auto;
	min-width: 0;
	padding: var(--space-8, 8px);
	box-sizing: border-box;
	border: 1px solid var(--gray-200, #E4E4E4);
	border-radius: var(--radius-15, 15px);
	background: var(--gray-0, #FFF);
	cursor: pointer;
	text-transform: none;
	letter-spacing: 0;
}
/* Radio marker — 20×20 SVG glyph in the card's top-right corner. The
   `top:8px; right:8px` insets place it flush with the content area
   while preserving the 8px padding gutter on the outside. State swap
   keys off `aria-pressed`, same SVG primitives as the sex radiogroup. */
.ap-pp__sizes-table-modal__fit-option::after {
	content: '';
	position: absolute;
	top: var(--space-8, 8px);
	right: var(--space-8, 8px);
	width: 20px;
	height: 20px;
	background-image: url('../img/sandbox/radio-unchecked.svg');
	background-repeat: no-repeat;
	background-position: center;
}
.ap-pp__sizes-table-modal__fit-option[aria-pressed="true"]::after {
	background-image: url('../img/sandbox/radio-checked.svg');
}
/* Jersey illustration — `margin-top: auto` pushes the image-label pair
   down so the image sits directly above the label at the bottom of the
   card. Width caps at 100% (Astra's global rule already does this; kept
   explicit) and height auto preserves the jersey's intrinsic ratio. */
.ap-pp__sizes-table-modal__fit-option__image {
	display: block;
	margin-top: auto;
	max-width: 100%;
	height: auto;
}

/* Card label — sits immediately below the image. No `margin-top: auto`
   here (the image owns the pushdown) so the pair stays adjacent.
   `align-self: stretch` + `text-align: center` horizontally centres the
   text and survives multi-line wraps. */
.ap-pp__sizes-table-modal__fit-option__name {
	align-self: stretch;
	text-align: center;
	color: var(--text-primary, #191919);
}

/* Right-column visual area — gray-50 panel that sits 16px from the
   panel's top, right, and bottom edges, with a 48px gap to the right
   edge of the form column on the left. Left inset is computed from the
   form column's anchor: panel-padding-left (48) + form width (500) +
   gap (48) = 596px from the panel's inner-left edge. Absolute pos
   means the panel doesn't need to switch to a two-column layout —
   the existing form content stays exactly where it is. Hidden on mobile
   where the modal is a bottom-sheet drawer. */
.ap-pp__sizes-table-modal__visual {
	/* Zero basis, not auto: an auto basis resolves to the previews'
	   max-content (two intrinsic ~1500px images), overflowing the row
	   and crushing the form column's 564px basis. With 0 the visual
	   only ever takes the remainder next to the full-width form. */
	flex: 1 1 0;
	min-width: 0;
	padding: 32px 48px;
	box-sizing: border-box;
	background: var(--gray-50, #F7F7F7);
	border-radius: var(--radius-15, 15px);
}

/* Note row at the top-left of the visual area — 20×20 info glyph + 8px
   gap + supporting copy. Single line per spec when it fits; on narrower
   viewports the copy wraps instead of running out of the panel. The
   right padding keeps the first line clear of the absolutely-anchored
   close pill (48px pill + breathing room). */
.ap-pp__sizes-table-modal__visual__note {
	display: flex;
	align-items: flex-start;
	gap: 8px;
	padding-right: 56px;
}
.ap-pp__sizes-table-modal__visual__note__icon {
	display: inline-flex;
	flex: 0 0 20px;
	width: 20px;
	height: 20px;
}
.ap-pp__sizes-table-modal__visual__note__icon img {
	display: block;
	width: 100%;
	height: 100%;
}
/* Default placeholder copy — supporting tone (--text-secondary) so it
   reads as guidance until the suggester picks a size. */
.ap-pp__sizes-table-modal__visual__note__text--default {
	color: var(--text-secondary, #5C5C5C);
}
/* Recommended-size readout — primary text colour so the prefix + the
   bolded size share weight; revealed by JS only after both inputs
   produce a valid pick. */
.ap-pp__sizes-table-modal__visual__note__text--recommended {
	color: var(--text-primary, #191919);
}

/* Chart title — 46px gap above (to note row), 8px gap below (to the
   chart grid). Same typographic treatment as the form-side group title
   (Outfit 14/500/150%, --text-primary). */
.ap-pp__sizes-table-modal__chart-title {
	margin: 46px 0 8px;
	color: var(--text-primary, #191919);
}

/* Size chart — 8-column grid (1 label + 7 size cols). Each row is a
   wrapper with `display: contents` so its cells participate directly
   in the grid layout while the wrapper itself still owns the row's
   visual identity (`--row--head`, `--row--shaded`). The default cell
   paints a 60px-tall white row with a 1px gray-200 hairline at the
   bottom; row modifiers repaint the background. */
/* Scroll shell — on viewports where the visual column is narrower than
   the grid's min-content width the table side-scrolls inside the panel
   instead of overflowing it. */
.ap-pp__sizes-table-modal__chart-scroll {
	overflow-x: auto;
}
.ap-pp__sizes-table-modal__chart {
	display: grid;
	/* Tracks floor at their content width so values never wrap — once
	   the 8 columns don't fit, the wrapper above scrolls instead. */
	grid-template-columns: repeat(8, minmax(max-content, 1fr));
}
.ap-pp__sizes-table-modal__chart__row {
	display: contents;
}
.ap-pp__sizes-table-modal__chart__cell {
	display: flex;
	height: 60px;
	padding: 10px var(--space-16, 16px);
	justify-content: center;
	align-items: center;
	gap: 10px;
	box-sizing: border-box;
	border-bottom: 1px solid var(--gray-200, #E4E4E4);
	background: var(--basic-white, #FFF);
	color: var(--text-primary, #191919);
}
/* First column of any row is the size-label cell — left-aligned, body
   rows use --text-secondary. Head + shaded row modifiers below override
   the colour where the spec calls for muted / inherits secondary. */
.ap-pp__sizes-table-modal__chart__cell--label {
	justify-content: flex-start;
	color: var(--text-secondary, #5C5C5C);
}

/* Head row — dark band with white text. Rounded outer corners on the
   first + last cell so the band sits inside a 15px-radius shell. */
.ap-pp__sizes-table-modal__chart__row--head .ap-pp__sizes-table-modal__chart__cell {
	background: var(--gray-800, #333);
	color: var(--text-white, #FFF);
}
.ap-pp__sizes-table-modal__chart__row--head .ap-pp__sizes-table-modal__chart__cell--label {
	color: var(--text-muted, #D4D4D4);
}
.ap-pp__sizes-table-modal__chart__row--head .ap-pp__sizes-table-modal__chart__cell:first-child {
	border-top-left-radius: var(--radius-15, 15px);
}
.ap-pp__sizes-table-modal__chart__row--head .ap-pp__sizes-table-modal__chart__cell:last-child {
	border-top-right-radius: var(--radius-15, 15px);
}

/* Body-row column alternation — within each body row, odd-indexed
   cells (label, 2nd value, 4th value, 6th value) keep the default
   white fill; even-indexed cells (1st value, 3rd, 5th, 7th) drop to
   gray-50. The pattern repeats every row so the chart reads as
   vertical column stripes rather than horizontal row stripes. Head
   row excluded — its dark band stays uniform across all 8 cells. */
.ap-pp__sizes-table-modal__chart__row:not(.ap-pp__sizes-table-modal__chart__row--head) .ap-pp__sizes-table-modal__chart__cell:nth-child(even) {
	background: var(--gray-50, #F7F7F7);
}

/* Last body row — mirror the head row's outer-corner rounding so the
   chart sits inside a closed 15px-radius shell. The 1px gray-200
   bottom border curves along the rounded corners, forming the table's
   bottom edge. */
.ap-pp__sizes-table-modal__chart__row:last-child .ap-pp__sizes-table-modal__chart__cell:first-child {
	border-bottom-left-radius: var(--radius-15, 15px);
}
.ap-pp__sizes-table-modal__chart__row:last-child .ap-pp__sizes-table-modal__chart__cell:last-child {
	border-bottom-right-radius: var(--radius-15, 15px);
}

/* Recommended-size highlight — applied to every cell sharing the
   target column's `data-size` (head + Krūtinė + Liemuo) once the
   suggester JS picks a size. Head cell flips to the red-500 brand
   accent; body cells drop to red-50. Rules come AFTER the column
   alternation above so source-order wins at equal specificity for
   even-indexed (gray-50) cells. */
.ap-pp__sizes-table-modal__chart__row--head .ap-pp__sizes-table-modal__chart__cell.is-highlighted {
	background: var(--red-500, #FF0014);
}
.ap-pp__sizes-table-modal__chart__row:not(.ap-pp__sizes-table-modal__chart__row--head) .ap-pp__sizes-table-modal__chart__cell.is-highlighted {
	background: var(--red-50, #FFE5E7);
}

/* AI-generated product previews — two equal 3:4 cards below the chart
   with a 24px gap above (to the chart) and 24px between them. The cards
   share the visual's content width (each flexes to fill its half) so
   they always fit inside the gray shape regardless of panel width.
   Renders the product as it would look for the user's provided info. */
.ap-pp__sizes-table-modal__previews {
	display: flex;
	gap: 24px;
	margin-top: 24px;
}
.ap-pp__sizes-table-modal__preview {
	display: block;
	flex: 1 1 0;
	min-width: 0;
	aspect-ratio: 3 / 4;
	object-fit: cover;
	border-radius: var(--radius-15, 15px);
}

.ap-sandbox .ap-pp__sizes-table-modal__fit-option:hover,
.ap-sandbox .ap-pp__sizes-table-modal__fit-option:focus,
.ap-sandbox .ap-pp__sizes-table-modal__fit-option:focus-visible,
.ap-sandbox .ap-pp__sizes-table-modal__fit-option:active {
	background-color: var(--gray-0, #FFF);
	color: var(--text-primary, #191919);
	border-color: var(--gray-200, #E4E4E4);
	outline: none;
	box-shadow: none;
}

/* Selected state — keeps the border at 1px (so the card's intrinsic
   width stays the same as in the unselected state and the row doesn't
   shift on click) and paints the second pixel of the spec'd 2px ring
   via an inset box-shadow. The border colour swaps to primary text so
   border + shadow blend into a single continuous 2px ring. Brand-wash
   lock re-asserts both properties across all interactive states. */
.ap-pp__sizes-table-modal__fit-option[aria-pressed="true"] {
	border-color: var(--text-primary, #191919);
	box-shadow: inset 0 0 0 1px var(--text-primary, #191919);
}
.ap-sandbox .ap-pp__sizes-table-modal__fit-option[aria-pressed="true"]:hover,
.ap-sandbox .ap-pp__sizes-table-modal__fit-option[aria-pressed="true"]:focus,
.ap-sandbox .ap-pp__sizes-table-modal__fit-option[aria-pressed="true"]:focus-visible,
.ap-sandbox .ap-pp__sizes-table-modal__fit-option[aria-pressed="true"]:active {
	border-color: var(--text-primary, #191919);
	box-shadow: inset 0 0 0 1px var(--text-primary, #191919);
}


/* ────────────────────────────────────────────────────────────────────
   Mobile (< 768px) — chrome subset. Header collapses to 60px tall, the
   burger replaces the categories nav, and the footer rebuilds into a
   centered single-column flex stack. Cart + search modals are hidden on
   mobile until their mobile patterns are designed. Body-specific mobile
   rules (gallery, summary, sizes, lightbox, combo modal) live in the
   per-page body stylesheet.
   ──────────────────────────────────────────────────────────────────── */
@media (max-width: 768px) {
	/* 8px bar padding per the mobile header frames (416-303 / 789-1207)
	   — the 48px icon shells' own 14px glyph inset puts the outermost
	   glyphs 22px from the viewport edge. */
	.ap-pp__header {
		height: 60px;
		padding: 0 8px;
	}

	/* Logo ↔ actions collision guard. The centred lockup (AGO-only, AGO ×
	   tenant crest, or AGO × tenant wordmark — three different widths)
	   closes in on the actions row as the viewport narrows. chrome-scripts
	   measures the gap between the lockup's right edge and where the
	   heart (the leftmost action) would sit and sets `--crowded` on the
	   header once it drops under `--header-safe-zone`; the heart is shed
	   there (favourites stay reachable via the account pages). Replaces
	   the old fixed 450px breakpoint, which was derived from the crest
	   lockup alone and already let the wider wordmark lockup touch. */
	.ap-pp__header {
		--header-safe-zone: var(--space-24, 24px);
	}
	.ap-pp__header--crowded .ap-pp__header__icon--fav {
		display: none;
	}

	/* Mobile logo — 54×24 per design, centred on both axes. We keep the
	   base rule's `top: 50% + translate(-50%, -50%)` centring rather than
	   pin a `top: 18px` constant: for the AGO-only lockup (24px tall) that
	   constant happened to equal centring in the 60px header, but the
	   themed Rytas lockup is 48px tall (tenant logo) and the constant left
	   it ~12px low and overflowing the header. Vertical centring stays
	   correct for any lockup height. The 54×24 size lives on the inner
	   AGO slot so this rule still works on themed pages (multiple
	   children). */
	.ap-pp__header__logo {
		top: 50%;
		left: 50%;
		transform: translate(-50%, -50%);
	}
	.ap-pp__header__logo__ago {
		width: 54px;
		height: 24px;
	}
	/* Themed lockup scales with the AGO mark's mobile ratio (÷1.5, same
	   as 81×36 → 54×24) so the crest stops filling the 60px bar:
	   tenant 48 → 32, separator 17 → 12, lockup gap 12 → 8. */
	.ap-pp__header__logo {
		gap: 8px;
	}
	.ap-pp__header__logo__separator {
		width: 12px;
		height: 12px;
	}
	.ap-pp__header__logo__tenant {
		width: 32px;
		height: 32px;
	}
	.ap-pp__header__logo__tenant--wordmark {
		width: 92px;
		height: 25px;
	}

	/* Burger swaps with the categories nav on mobile — burger reveals,
	   nav hides. The burger now lives inside `.ap-pp__header__burger-group`
	   together with a mobile-only search shell (8px gap), so the flex
	   `space-between` puts that group on the left and the actions row
	   on the right. */
	.ap-pp__header__burger-group { display: inline-flex; align-items: center; gap: 8px; }
	.ap-pp__header__icon--burger { display: inline-flex; }
	.ap-pp__header__icon--mobile-search { display: inline-flex; }
	.ap-pp__header__nav { display: none; }
	.ap-pp__header__mega,
	.ap-pp__header__mega-backdrop { display: none; }

	/* Mobile actions — search is removed, remaining icons get 8px gap. */
	.ap-pp__header__icon--search { display: none; }
	.ap-pp__header__actions { gap: 8px; }

	/* Mobile footer — 861px tall per spec; side padding is the mobile
	   grid margin (24px) so all content — including the 1px divider —
	   sits flush on the 2-column grid's outer edges. The bottom region
	   flips from the desktop's two-column absolute layout into a
	   single centered flex column.
	   `.ap-pp__footer__legal` collapses via `display: contents` so
	   copyright + menu promote to direct flex children of the footer;
	   we reorder six siblings (jersey-wrap, cta-stack, divider, menu,
	   socials, copyright) with the `order` property and own the
	   vertical rhythm via per-item `margin-bottom`. The footer height
	   on mobile is `auto`: the jersey-wrap's `margin-top: 64px` defines
	   the top edge, copyright's `margin-bottom: 16px` defines the
	   bottom edge, and everything in between is content + per-item
	   margins. The flex column, top-down — is:
	     jersey-wrap (64px above, 25px below; holds the jersey and the
	                  bg-shape painted behind it, both centered on the
	                  wrapper's center axis)
	     cta-stack   (64px below)
	     divider     (40px below; `width: 100vw`, side-to-side)
	     menu        (40px below)
	     socials     (16px below)
	     copyright   (16px below; bottom edge of the footer).
	   Inside the cta-stack on mobile (top-down):
	     note  "Neradai ko ieškojai?"  (8px below)
	     title "Susikurk savo"         (24px below; forced to 2 lines
	                                    via `max-width: min-content`,
	                                    "savo" centered under "Susikurk")
	     CTA   "Pradėti"               (spans cta-stack's full inline
	                                    size via `align-self: stretch`).
	   `.type-heading-xl` auto-scales to 34px and `.type-heading-sm-light`
	   to 16px via the page-level token remap (`--font-size-58` and
	   `--font-size-20` overrides on `.ap-sandbox` further down in this
	   media query). */
	/* Footer height = auto on mobile: the explicit `margin-top: 64px`
	   on the jersey now defines the footer's top edge (the footer is
	   sized exactly to its content + that 64px top gap), so the spec
	   "64px above the jersey, footer ends" is satisfied without us
	   computing the total bottom-band height by hand. */
	.ap-pp__footer { height: auto; padding: 0 var(--grid-margin, 24px); align-items: center; }

	.ap-pp__footer__jersey-wrap {
		/* Reset desktop absolute anchor; the wrapper becomes a flex
		   item of the footer. `position: relative` keeps it a
		   positioning context so the bg-shape (absolute-positioned
		   inside) still centers on the wrapper. `margin-top: 64px`
		   defines the gap to the footer's top edge — see footer rule
		   comment above. */
		position: relative;
		left: auto;
		bottom: auto;
		transform: none;
		width: 141.871px;
		height: 195.117px;
		aspect-ratio: 141.87 / 195.12;
		margin-top: 64px;
		margin-bottom: 25px;
	}

	/* Chained to `.ap-pp__footer` to (0,2,0) — the base rule carries the
	   same boost (to beat `.woocommerce img`), and at (0,1,0) this swap
	   lost: mobile kept the desktop 1445×480 box sprawling over the
	   cta-stack texts. Same trap as `__title` above. */
	.ap-pp__footer .ap-pp__footer__bg-shape {
		/* Mobile SVG via the `<picture>` source swap. Centered on the
		   wrapper (= jersey) by the base rule's `top/left/transform`
		   — we only swap the dimensions here. */
		width: 527px;
		height: 176px;
	}

	.ap-pp__footer__cta-stack {
		/* Relative (not static): the jersey-wrap's positioned children
		   (jersey, oversized bg-shape) paint above in-flow statics, so
		   an unpositioned stack's text disappears behind the overhang.
		   Positioning the stack joins it to the same layer, where DOM
		   order (jersey-wrap first) puts it on top. CRITICAL: the
		   desktop base sets `left: 96px; bottom: calc(433px + …)` —
		   inert under the old `static`, but LIVE offsets under
		   `relative`, shoving the stack out of the footer's
		   overflow:hidden box. Both must be re-set to auto. */
		position: relative;
		left: auto;
		bottom: auto;
		align-self: stretch;
		align-items: center;
		margin-bottom: 64px;
	}
	.ap-pp__footer__note {
		margin: 0 0 8px 0;
		text-align: center;
	}
	/* Chained to `.ap-pp__footer` to (0,2,0) — the desktop rule carries
	   the same boost (to beat `.woocommerce h2`), and at (0,1,0) this
	   override lost margin/max-width to it: the title kept the desktop
	   64px gap and its `calc(50vw - 314.5px)` max-width went negative →
	   used 0px, shoving the text right of center. */
	.ap-pp__footer .ap-pp__footer__title {
		margin: 0 0 24px 0;
		/* `min-content` clamps the title's box to its widest word
		   ("Susikurk"), which forces "Susikurk savo" to break at the
		   space into two lines regardless of how wide the viewport is.
		   `text-align: center` then centers the shorter "savo" line
		   under "Susikurk". */
		max-width: min-content;
		text-align: center;
	}
	.ap-pp__footer__cta {
		/* Span the full mobile content width (cta-stack is `align-self:
		   stretch`, so this stretches the CTA to the cta-stack's full
		   inline size). The desktop 18×96 padding is left intact — at
		   full width the horizontal padding becomes a minimum left/right
		   buffer rather than the size driver, and `justify-content:
		   center` keeps "Pradėti" centered in the resulting pill. */
		align-self: stretch;
	}

	.ap-pp__footer__divider {
		margin-top: 0;
		margin-bottom: 40px;
		/* Spans the footer's content box — flush with the grid margins
		   like every other row (was a 100vw edge-to-edge breakout). */
		width: 100%;
	}
	.ap-pp__footer__legal { display: contents; }
	.ap-pp__footer__menu {
		flex-direction: column;
		gap: 16px;
		order: 2;
		margin-bottom: 40px;
	}
	.ap-pp__footer__socials {
		position: static;
		order: 3;
		margin-bottom: 16px;
	}
	.ap-pp__footer__copyright {
		order: 4;
		margin-bottom: 16px;
	}

	/* Brand-row + kicker — desktop-only for now; mobile spec pending.
	   The jersey, CTA stack, and bg-shape all appear on mobile but at
	   very different sizes/positions — see their per-element overrides
	   below. The bg-shape additionally swaps SVG sources via the
	   `<picture>` element in markup (mobile uses a 390×196 shape,
	   desktop a 1445×480 one). */
	.ap-pp__footer__brand-row,
	.ap-pp__footer__kicker { display: none; }

	/* Cart modal — desktop-only for now (mobile pattern TBD); JS still
	   toggles `.is-open` but the display: flex above is suppressed. The
	   combo modal hide lives with the product-page body CSS since combo
	   is product-page only. */
	.ap-pp__cart-modal,
	.ap-pp__cart-modal.is-open { display: none; }

	/* Cart-added — mobile pass: the desktop top-right toast becomes a
	   bottom sheet anchored to the viewport bottom (full width, top
	   corners rounded, slides up from below), mirroring the search
	   modal's `__panel`. It stays a non-blocking toast — no backdrop,
	   no scroll lock — so we only restyle the panel itself: reset the
	   24px top/right insets and 400px cap, anchor bottom/left/right,
	   round the top corners, and swap the desktop fade+8px-drop for a
	   translateY(100%) slide. Opacity pinned to 1 so that fade doesn't
	   fight the slide. The component's own 24px flex gap owns the
	   grabber→header→product→actions rhythm. */
	.ap-pp__cart-added {
		top: auto;
		right: 0;
		bottom: 0;
		left: 0;
		width: 100%;
		max-width: none;
		/* 8px top so the grabber sits 8px from the edge; 24px sides for
		   the standard mobile gutter; 24px bottom so the actions row has
		   a 24px gap to the sheet's bottom edge. */
		padding: 8px 24px 24px;
		border-radius: var(--radius-15, 15px) var(--radius-15, 15px) 0 0;
		opacity: 1;
		transform: translateY(100%);
		transition: transform 360ms cubic-bezier(0.4, 0, 0.2, 1);
		will-change: transform;
	}
	.ap-pp__cart-added.is-open {
		transform: translateY(0);
	}

	/* Scrim — full-viewport backdrop behind the sheet, one tier below it
	   (1101 vs the sheet's 1102). Light token per the sandbox backdrop
	   convention. Tap-to-dismiss is wired via `data-cart-added-close` in
	   chrome-scripts.php; the body class (set on open) fades it in and
	   arms its pointer-events. Fade timing matches the sheet slide and
	   the search modal's mobile backdrop. */
	/* Bottom-sheet mode scrolls INSIDE the sheet (`max-height: 90vh` +
	   its own overflow), so the desktop wrapper-scroll contract is
	   pinned off here — otherwise the sheet's translateY(100%) rest
	   position would register as scrollable overflow below the
	   viewport during the slide animation. The wrapper's desktop
	   padding is inert for the absolutely-positioned sheets but reset
	   anyway for hygiene. */
	.ap-pp__search-modal,
	.ap-pp__sizes-table-modal {
		padding: 0;
	}
	.ap-pp__search-modal.is-open,
	.ap-pp__sizes-table-modal.is-open {
		overflow: hidden;
	}
	.ap-pp__cart-added__backdrop {
		display: block;
		position: fixed;
		inset: 0;
		z-index: 1101;
		background: var(--backdrop-background-light, rgba(10, 10, 10, 0.30));
		opacity: 0;
		pointer-events: none;
		cursor: pointer;
		transition: opacity 360ms cubic-bezier(0.4, 0, 0.2, 1);
	}
	/* Each popover arms ONLY its own scrim (the fav-added variant ships a
	   `--fav`-flagged backdrop + its own body class), so a scrim tap always
	   dismisses the sheet that is actually open — never its sibling. */
	body.ap-pp__cart-added-open .ap-pp__cart-added__backdrop:not(.ap-pp__cart-added__backdrop--fav),
	body.ap-pp__fav-added-open .ap-pp__cart-added__backdrop--fav {
		opacity: 1;
		pointer-events: auto;
	}
	/* Lock page scroll while the sheet is open — mobile only, so the
	   desktop toast stays non-blocking. */
	body.ap-pp__cart-added-open,
	body.ap-pp__fav-added-open { overflow: hidden; }

	/* Grabber — 40×4 pill centred at the top of the sheet. 8px above
	   comes from the sheet's own top padding; the 24px below is the
	   panel's flex gap to the header. */
	.ap-pp__cart-added__grabber {
		display: block;
		width: 40px;
		height: 4px;
		margin: 0 auto;
		border-radius: var(--radius-999, 999px);
		background: var(--gray-100, #F0F0F0);
	}

	/* Backdrop fade — matches the filter modal's mobile timing so the
	   scrim eases in/out alongside the sheet slide. The desktop backdrop
	   has no transition (intentional snap); we add it only here. */
	.ap-pp__search-modal__backdrop {
		transition: opacity 360ms cubic-bezier(0.4, 0, 0.2, 1);
	}

	/* Search modal — mobile pass: bottom sheet anchored to the viewport
	   bottom (full width, top corners rounded, slides up from below).
	   Same architecture as `.ap-tpl__filter-modal__drawer` so the two
	   feel consistent. The grabber sits 8px from the drawer top edge
	   (drawer's own top padding); the header row (title + close pill)
	   follows; then the column with the search content. JS skips its
	   `positionSearchPanel()` inline-top assignment on mobile (see
	   chrome-scripts.php) so this CSS bottom-anchor isn't overridden.
	   Side padding matches the page inset (24px); top padding is 8px
	   so the grabber sits 8px from the edge; bottom padding is 24px
	   for content → viewport-bottom gap. */
	.ap-pp__search-modal__panel {
		/* Re-pin: the desktop base is now a flow flex item (`margin:
		   auto`); the bottom sheet needs the absolute anchor back. */
		position: absolute;
		margin: 0;
		top: auto;
		right: 0;
		bottom: 0;
		left: 0;
		width: 100%;
		max-height: 90vh;
		/* No top padding — the sticky grabber bar owns the 8px inset so
		   the pinned strip masks content flush from the sheet's top edge.
		   24px sides for the standard mobile gutter; 48px bottom so the
		   last result row has a 48px gap to the modal's bottom edge. */
		padding: 0 24px 48px;
		border-radius: var(--radius-15, 15px) var(--radius-15, 15px) 0 0;
		/* Opacity pinned to 1 so the desktop fade-in (which is tuned for
		   a centered panel) doesn't fight the bottom-sheet slide. */
		opacity: 1;
		transform: translateY(100%);
		transition: transform 360ms cubic-bezier(0.4, 0, 0.2, 1);
		will-change: transform;
		overflow-y: auto;
	}
	.ap-pp__search-modal.is-open .ap-pp__search-modal__panel {
		transform: translateY(0);
	}

	/* Bottom-sheet grabber — pinned. The element becomes a full-width
	   sticky bar carrying the sheet background (8px top inset + 12px gap
	   to the header as padding, so the mask has no see-through margins);
	   the 40×4 pill itself is drawn by the ::after. Sticks at the very
	   top of the scrollport while the content scrolls behind it. */
	.ap-pp__search-modal__grabber {
		display: block;
		position: sticky;
		top: 0;
		z-index: 5;
		width: 100%;
		margin: 0;
		padding: 8px 0 12px;
		background: var(--gray-0, #FFF);
	}
	.ap-pp__search-modal__grabber::after {
		content: '';
		display: block;
		width: 40px;
		height: 4px;
		margin: 0 auto;
		border-radius: var(--radius-999, 999px);
		background: var(--gray-100, #F0F0F0);
	}

	/* Header row — title left, close pill right; mirrors the filter
	   modal's `__header`. The 16px gap separates the two when the title
	   ellipsises near long widths. Pinned right below the grabber bar
	   (top = its 8+4+12 stuck height) with the sheet background, so the
	   title + close stay visible while results scroll underneath. */
	.ap-pp__search-modal__header {
		position: sticky;
		top: 24px;
		z-index: 5;
		background: var(--gray-0, #FFF);
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 16px;
		margin-bottom: 26px;
	}
	.ap-pp__search-modal__title {
		display: block;
		margin: 0;
		color: var(--text-primary, #191919);
	}

	/* Close pill — overrides the desktop absolute positioning so the
	   button sits inside the flex header row. 8px padding tightens it
	   to match the filter modal's mobile close (vs 14px on desktop).
	   Background uses the same tertiary token; brand-wash lock already
	   applies via the base rule. */
	.ap-pp__search-modal__close {
		position: static;
		display: inline-flex;
		padding: 8px;
		background: var(--icon-button-tertiary-background-enabled, rgba(255, 255, 255, 0.50));
		/* Pin to header's top edge so the close stays at the same Y
		   regardless of title wrap depth — matches the sizes-table modal. */
		align-self: flex-start;
	}
	.ap-sandbox .ap-pp__search-modal__close:hover,
	.ap-sandbox .ap-pp__search-modal__close:focus,
	.ap-sandbox .ap-pp__search-modal__close:focus-visible,
	.ap-sandbox .ap-pp__search-modal__close:active {
		background-color: var(--icon-button-tertiary-background-enabled, rgba(255, 255, 255, 0.50));
	}

	/* Inner-stack title gaps drop from desktop's 32px to 24px on mobile:
	     - "Populiarios paieškos" (above the chip row)
	     - "Populiariausios prekės" (above the product results)
	   Same selector shape as the desktop rule (scoped under
	   `.ap-pp__search-modal__panel`) so specificity matches and source-
	   order resolves the cascade. The text-results-title (search-state
	   left column heading) stays at desktop's 32px until a mobile spec
	   for the searching state lands. */
	.ap-pp__search-modal__panel .ap-pp__search-modal__suggestions-title,
	.ap-pp__search-modal__panel .ap-pp__search-modal__results-title {
		margin-top: 24px;
	}

	/* Results grid — flips from desktop's single-row 4-tile flex to a
	   2-column grid on mobile: 4px column gap (horizontal between tiles
	   in a row) + 32px row gap (vertical between rows). `overflow:
	   visible` undoes the desktop clip that hid wrapped tiles. The
	   tiles' desktop `flex: 0 0 var(--tile-w)` (JS-computed for the
	   4-col layout) is benign here since they're now grid items, but we
	   pin width to 100% so the grid track defines the size. */
	.ap-pp__search-modal__results {
		display: grid;
		/* Two tracks on the mobile grid columns: the panel's 24px side
		   padding doubles as the grid margin and the column gap is the
		   grid gutter, so each tile sits exactly on one grid column
		   (was an edge-to-edge breakout with a 4px gap). */
		grid-template-columns: repeat(2, minmax(0, 1fr));
		column-gap: var(--grid-gutter, 16px);
		row-gap: 32px;
		overflow: visible;
	}
	.ap-pp__search-modal__result {
		width: 100%;
	}

	/* Tile-count cap is desktop-only — desktop hides tile 4 in search
	   state to leave room for the text-results column on the left. On
	   mobile the layout stacks vertically, so the tiles never lose
	   horizontal room; restore all 4. Matches the base rule's
	   `display: flex` (column flex) so column-flex layout still applies. */
	.ap-pp__search-modal.is-searching .ap-pp__search-modal__result:nth-child(n+4) {
		display: flex;
	}

	/* Searching state on mobile — the rest-state suggestions block
	   collapses out of flow; the text-results block (currently a hidden
	   left column on desktop) takes its place above the product results.
	   Visually: field → text-results-title → text-results-list → results-
	   title ("19 prekių") → product tiles. The existing JS already swaps
	   the results-title text content via `.is-searching`.

	   Both swaps use the `grid-template-rows: 1fr ↔ 0fr` collapse
	   pattern, which animates the *natural* content height (unlike
	   max-height, which transitions linearly across an arbitrary cap and
	   leaves dead zones at one end of the curve). The `__suggestions`
	   and `__text-results` wrappers become single-row grid containers;
	   their `__inner` children have `overflow: hidden` so content clips
	   cleanly during the collapse. Both directions complete visible
	   motion across the full 360ms — no dead pause, no "input field
	   settles last" effect, no asymmetric show-fast / hide-slow on
	   clear. Same Material curve the bottom-sheet slide uses so all
	   motions stay coherent. */
	.ap-pp__search-modal__suggestions,
	.ap-pp__search-modal__text-results {
		display: grid;
		grid-template-rows: 1fr;
		transition: grid-template-rows 360ms cubic-bezier(0.4, 0, 0.2, 1),
		            opacity 360ms cubic-bezier(0.4, 0, 0.2, 1);
	}
	.ap-pp__search-modal__suggestions__inner,
	.ap-pp__search-modal__text-results__inner {
		overflow: hidden;
		min-height: 0;
	}

	/* Rest state — suggestions expanded, text-results collapsed. */
	.ap-pp__search-modal__text-results {
		grid-template-rows: 0fr;
		opacity: 0;
		pointer-events: none;
		/* Desktop pins these to `var(--tile-w)` so the text-results
		   column doesn't reflow during the column-track animation; on
		   mobile there's no column animation, so let them go full
		   width. */
		width: auto;
	}
	.ap-pp__search-modal__text-results-title,
	.ap-pp__search-modal__text-results-list {
		width: auto;
	}

	/* Search state — suggestions collapsed, text-results expanded. */
	.ap-pp__search-modal.is-searching .ap-pp__search-modal__suggestions {
		grid-template-rows: 0fr;
		opacity: 0;
		pointer-events: none;
	}
	.ap-pp__search-modal.is-searching .ap-pp__search-modal__text-results {
		grid-template-rows: 1fr;
		opacity: 1;
		pointer-events: auto;
	}

	/* Results-grid drops the desktop two-column flip and stacks its two
	   children vertically (text-results first, product results below).
	   Same selector specificity as the desktop rule; source order wins. */
	.ap-pp__search-modal__results-grid {
		display: flex;
		flex-direction: column;
	}

	/* In search state, the text-results-title sits 24px below the field
	   (matching the rest-state field → suggestions-title gap). Desktop's
	   text-results-title has `margin-top: 0` (it's the first child of the
	   left column); on mobile it becomes the first content under the
	   field, so it owns the 24px gap directly. */
	.ap-pp__search-modal.is-searching .ap-pp__search-modal__panel .ap-pp__search-modal__text-results-title {
		margin-top: 24px;
	}

	/* Text-results-list — 16px below its title (carried from desktop);
	   8px row-gap between entries (denser than desktop's pure line-
	   height adjacency). */
	.ap-pp__search-modal__text-results-list {
		display: flex;
		flex-direction: column;
		gap: 8px;
	}

	/* Sizes-table modal — bottom-sheet drawer mirroring the mobile pass
	   for `.ap-pp__search-modal`: full width, top corners rounded,
	   slides up from the viewport bottom. Grabber visible, title in the
	   flex header, close pill switches to the tertiary token. */
	.ap-pp__sizes-table-modal__backdrop {
		transition: opacity 360ms cubic-bezier(0.4, 0, 0.2, 1);
	}

	.ap-pp__sizes-table-modal__panel {
		/* Re-pin: the desktop base is now a flow flex item (`margin:
		   auto`); the bottom sheet needs the absolute anchor back. */
		position: absolute;
		margin: 0;
		top: auto;
		right: 0;
		bottom: 0;
		left: 0;
		width: 100%;
		max-width: none;
		max-height: 90vh;
		overflow-y: auto;
		/* No top padding — the sticky grabber bar owns the 8px inset
		   (same pinned-header contract as the search modal). */
		padding: 0 24px 48px;
		border-radius: var(--radius-15, 15px) var(--radius-15, 15px) 0 0;
		opacity: 1;
		transform: translateY(100%);
		transition: transform 360ms cubic-bezier(0.4, 0, 0.2, 1);
		will-change: transform;
	}
	.ap-pp__sizes-table-modal.is-open .ap-pp__sizes-table-modal__panel {
		transform: translateY(0);
	}

	/* Pinned grabber bar — full-width sticky strip with the sheet
	   background (8px inset + 12px header gap as padding); the 40×4
	   pill is the ::after. Content scrolls behind it. */
	.ap-pp__sizes-table-modal__grabber {
		display: block;
		position: sticky;
		top: 0;
		z-index: 5;
		width: 100%;
		margin: 0;
		padding: 8px 0 12px;
		background: var(--gray-0, #FFF);
	}
	.ap-pp__sizes-table-modal__grabber::after {
		content: '';
		display: block;
		width: 40px;
		height: 4px;
		margin: 0 auto;
		border-radius: var(--radius-999, 999px);
		background: var(--gray-100, #F0F0F0);
	}

	/* Pinned header (title + close) right below the grabber bar. Sticky
	   works against the PANEL scrollport only because `__columns` and
	   `__form` become `display: contents` below — as real boxes they'd
	   be the header's containing block, and the header would unstick as
	   soon as the form column scrolled past. */
	.ap-pp__sizes-table-modal__header {
		position: sticky;
		top: 24px;
		z-index: 5;
		background: var(--gray-0, #FFF);
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 16px;
		margin-bottom: 26px;
	}
	.ap-pp__sizes-table-modal__title {
		display: block;
		margin: 0;
		color: var(--text-primary, #191919);
	}

	.ap-pp__sizes-table-modal__close {
		position: static;
		display: inline-flex;
		padding: 8px;
		background: var(--icon-button-tertiary-background-enabled, rgba(255, 255, 255, 0.50));
		/* Pin the close pill to the header's top edge so its vertical
		   position stays constant regardless of how many lines the title
		   wraps to. With `align-items: center` on the header, a 2-line
		   title would re-centre the close downward; `align-self` here
		   locks it at the 1-line-equivalent position. */
		align-self: flex-start;
	}
	.ap-sandbox .ap-pp__sizes-table-modal__close:hover,
	.ap-sandbox .ap-pp__sizes-table-modal__close:focus,
	.ap-sandbox .ap-pp__sizes-table-modal__close:focus-visible,
	.ap-sandbox .ap-pp__sizes-table-modal__close:active {
		background-color: var(--icon-button-tertiary-background-enabled, rgba(255, 255, 255, 0.50));
	}

	/* Two-column row collapses to a single stack on mobile — the form
	   and visual sit on top of each other. `display: contents` (not
	   block) dissolves both wrappers so the header's containing block
	   is the scrolling panel and its sticky pin holds for the whole
	   sheet, not just while the form section is in view. Neither
	   wrapper carries mobile paint/box styles, so dissolving them is
	   free; the form's desktop inset disappears with its box. */
	.ap-pp__sizes-table-modal__columns {
		display: contents;
	}
	.ap-pp__sizes-table-modal__form {
		display: contents;
	}

	/* The desktop right-column visual flows into the column on mobile:
	   sits below the fit chooser with a 24px gap. Its side edges stay
	   on the mobile grid margins (the panel's 24px side padding); only
	   the bottom still cancels the panel's 48px bottom padding so the
	   gray-50 shape closes the sheet, with rounded top corners.
	   Internal padding 32px top, 24px sides, 24px bottom. */
	.ap-pp__sizes-table-modal__visual {
		position: static;
		top: auto;
		right: auto;
		bottom: auto;
		left: auto;
		margin: 24px 0 -48px;
		padding: 32px 24px 24px;
		border-radius: var(--radius-15, 15px) var(--radius-15, 15px) 0 0;
	}

	/* Note row — the close pill is static in the header on mobile, so
	   the base rule's reserved space isn't needed. */
	.ap-pp__sizes-table-modal__visual__note {
		padding-right: 0;
	}

	/* Chart wrapper — the ONE element inside the visual allowed to
	   scroll sideways. Fixed-width columns force the chart's intrinsic
	   width past the visual's content area; the wrapper catches the
	   overflow and produces a horizontal scrollbar so the surrounding
	   note + title stay inside the panel and only the table slides.
	   The scroll viewport stays within the visual's content box — no
	   edge bleed — so the table clips at the same inset every other
	   row uses. The chart is sized to its content (`width:
	   max-content`) — a block-level grid otherwise takes the wrapper's
	   width and the 90px columns overflow that box. */
	.ap-pp__sizes-table-modal__chart-scroll {
		overflow-x: auto;
		/* Keep the swipe-scroll but hide the scrollbar chrome. */
		scrollbar-width: none;
		-ms-overflow-style: none;
	}
	.ap-pp__sizes-table-modal__chart-scroll::-webkit-scrollbar {
		display: none;
	}
	.ap-pp__sizes-table-modal__chart {
		grid-template-columns: repeat(8, 90px);
		width: max-content;
	}

	/* Chart title — tighten the gap above (note → title) from the
	   desktop spec of 46px down to 24px on mobile so the vertical
	   rhythm matches the bottom-sheet's compressed scale. Bottom gap
	   to the chart stays at 8px. */
	.ap-pp__sizes-table-modal__chart-title {
		margin-top: 24px;
	}

	/* AI previews stack one-per-row on mobile, spanning the visual's
	   content width (24px inset from the panel edges). 24px gap above (to
	   the chart) and 24px between the two cards carry over from the base
	   rule. */
	.ap-pp__sizes-table-modal__previews {
		flex-direction: column;
	}
	.ap-pp__sizes-table-modal__preview {
		flex: 0 0 auto;
		width: 100%;
	}

	/* Tighten theŪgis ↔ Svoris gap from 24px (desktop spec) down to
	   the mobile spec of 8px so both fields read as a paired duo
	   rather than as two isolated columns at viewport widths. */
	.ap-pp__sizes-table-modal__field-row {
		gap: 8px;
	}

	/* Sex chooser stacks vertically on mobile (one radio per row) with
	   a 12px gap between Vyras and Moteris. */
	.ap-pp__sizes-table-modal__sex-options {
		flex-direction: column;
		gap: 12px;
	}

	/* Fit chooser — stop hugging the 140px image and switch back to
	   equal-share columns so the three cards fit the viewport with no
	   side-scroll. `flex: 1 0 0` distributes the row width evenly; the
	   image's own `max-width: 100%` shrinks it proportionally inside
	   the now-narrower card, preserving the jersey's square aspect.
	   16px row gap stays from the desktop rule. */
	.ap-pp__sizes-table-modal__fit-option {
		flex: 1 0 0;
	}
}

/* Reduced motion — kill the rest↔search collapse transitions (desktop
   280ms suggestions fold + mobile 360ms swap). Placed at the top level
   (matching the file's other reduced-motion blocks) and AFTER the
   mobile breakpoint so it wins by source order at matching
   specificity. `__text-results` already has a separate top-level
   reduced-motion override earlier in the file for its desktop opacity
   fade. */
@media (prefers-reduced-motion: reduce) {
	.ap-pp__search-modal__suggestions,
	.ap-pp__search-modal__text-results { transition: none; }
}

/* ─────────────────────────────────────────────────────────────────────
   Media gallery nav — hover prev/next arrows over product-card images
   (mega-menu tiles, search-modal result tiles, collection/PLP cards),
   letting the visitor flip through a product's photos without opening
   the product. Injected by chrome-scripts.php onto any media element
   carrying `data-ap-gallery` (JSON array of image URLs, ≥2 entries;
   first = the image already rendered). Button per Figma 853:2492:
   60px pill (20px padding + 20px chevron), `--gray-0-a40` translucent
   fill, 12px from the image edge; hover paint per IconButton
   secondary-filled-hover (395:296). Arrows fade in on media hover
   (hover-capable devices only); an edge arrow gets `hidden` by the
   JS when no more images exist in that direction, and media with <2
   images never get arrows at all.
   ───────────────────────────────────────────────────────────────────── */
.ap-pp__media-nav {
	position: relative;
}
/* <img> media can't hold children — chrome-scripts wraps them in a
   span host; make it a block that mirrors the img's flow footprint.
   `height: 100%` so the host fills a tile whose height comes from an
   aspect-ratio (the card media divs); where the parent's height is
   indefinite (search modal) the percentage resolves to auto and the
   img's own aspect-ratio still governs. */
span.ap-pp__media-nav {
	display: block;
	width: 100%;
	height: 100%;
}

/* ─────────────────────────────────────────────────────────────────────
   Card media image — the <img> that astp_card_img_html() renders inside
   a card's media tile (inc/card-images.php, 2026-09-15).

   Live-only primitive, like `span.ap-pp__media-nav` above: sandbox card
   mocks still paint their grey/demo tile as a CSS background, and that
   path is untouched — this rule only applies once a real <img> is
   present. The tile keeps owning the aspect ratio and the #D9D9D9
   placeholder tint (visible while the image loads); the image just
   fills it.
   ───────────────────────────────────────────────────────────────────── */
.ap-card-media__img {
	display: block;
	width: 100%;
	height: 100%;
	/* WooCommerce/Astra pin img{max-width:100%} and, on some archive
	   contexts, max-height — both would letterbox the cover fill. */
	max-width: none;
	max-height: none;
	object-fit: cover;
	object-position: 50% 50%;
	border-radius: inherit;
}
.ap-pp__media-nav__btn {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 20px;
	border: 0;
	border-radius: var(--radius-999, 999px);
	background: var(--gray-0-a40, rgba(255, 255, 255, 0.4));
	cursor: pointer;
	z-index: 1;
	/* Hidden at rest — revealed by media hover / keyboard focus below.
	   pointer-events gate so the invisible pill never intercepts a
	   click meant for the card link. */
	opacity: 0;
	pointer-events: none;
	transition: opacity 160ms ease;
}
.ap-pp__media-nav__btn--prev { left: 12px; }
.ap-pp__media-nav__btn--next { right: 12px; }
/* JS sets `hidden` on the edge arrow; the display:flex above would
   otherwise beat the UA's [hidden] rule. */
.ap-pp__media-nav__btn[hidden] { display: none; }
.ap-pp__media-nav__btn img {
	display: block;
	width: 20px;
	height: 20px;
}
/* Prev mirrors the chevron-right asset 180° — same trick as
   `.ap-pp__collection__nav-button--prev`. */
.ap-pp__media-nav__btn--prev img { transform: rotate(180deg); }
@media (hover: hover) {
	.ap-pp__media-nav:hover .ap-pp__media-nav__btn {
		opacity: 1;
		pointer-events: auto;
	}
}
.ap-pp__media-nav__btn:focus-visible {
	opacity: 1;
	pointer-events: auto;
}
/* Hover paint (IconButton secondary filled hover) — doubles as the
   brand-wash lock: scoped under `.ap-sandbox` (0,3,0) to beat the
   universal button kill (0,2,1), all five wash properties pinned. */
.ap-sandbox .ap-pp__media-nav__btn:hover,
.ap-sandbox .ap-pp__media-nav__btn:focus,
.ap-sandbox .ap-pp__media-nav__btn:focus-visible,
.ap-sandbox .ap-pp__media-nav__btn:active {
	background-color: var(--icon-button-secondary-background-hover, #E4E4E4);
	color: inherit;
	border-color: transparent;
	outline: none;
	box-shadow: none;
}
/* Crossfade veil — chrome-scripts drops a snapshot of the outgoing
   image on top of the host and fades it out, so an arrow click reads
   as a dissolve instead of a hard swap. Sits under the arrow pills
   (z-index 1) and must never eat their clicks mid-fade. */
.ap-pp__media-nav__fade {
	position: absolute;
	inset: 0;
	background-size: cover;
	background-position: center;
	background-repeat: no-repeat;
	border-radius: inherit;
	pointer-events: none;
	opacity: 1;
	transition: opacity 320ms cubic-bezier(0.25, 0.1, 0.25, 1);
}
.ap-pp__media-nav__fade--out { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
	.ap-pp__media-nav__btn { transition: none; }
	.ap-pp__media-nav__fade { transition: none; }
}

/* ─────────────────────────────────────────────────────────────────────
   12-column grid system. `.ap-grid` is the layout container (use it
   on a page-section wrapper, then declare `grid-column: span N` on
   children). `.ap-grid-overlay` is a dev-only visual aid — a fixed,
   pointer-events:none overlay that paints the 12 columns Figma-style
   so we can verify alignment. Toggle with `?grid=1` on any sandbox URL.
   Tokens (--grid-columns / --grid-gutter / --grid-margin) are declared
   below — ago-tokens.css is a verbatim design-team export since
   2026-08-16 and the layout grid is a separate Figma spec (Desktop ·
   12 col / 96 margin / 32 gutter; Mobile · 2 col / 24 margin /
   16 gutter). Values are literal px on purpose: the spacing-scale
   tokens (--space-16/32) shrink at the Laptop/Mobile modes, but the
   layout grid must not follow them.
   ───────────────────────────────────────────────────────────────────── */
:root {
	--grid-columns: 12;
	--grid-gutter: 32px;
	--grid-margin: 96px;
}

/* 768px matches the sandbox pages' shared mobile breakpoint. */
@media (max-width: 768px) {
	:root {
		--grid-columns: 2;
		--grid-gutter: 16px;
		--grid-margin: 24px;
	}
}

.ap-grid {
	display: grid;
	grid-template-columns: repeat(var(--grid-columns), 1fr);
	column-gap: var(--grid-gutter);
	padding-inline: var(--grid-margin);
	box-sizing: border-box;
}

/* Grid-derived horizontal insets. Both are declared with `100%` so they
   must be used in horizontal properties of elements whose containing
   block spans the viewport width: the fixed inset-0 modal wrappers and
   their abspos panels qualify, and so do in-flow full-bleed page
   wrappers (direct children of the full-bleed `<main>`, e.g. the
   checkout `.ap-co`) — a classic scrollbar shrinks the in-flow basis
   and the fixed overlay's basis identically, so the two stay aligned.
   `--grid-col-step` is one column plus one gutter — each step inward
   from a column's outer edge to the next column's outer edge.
   `--grid-col2-inset` is the distance from the viewport edge to the
   OUTER edge of column 2 (mirrored: column 11) — the search +
   sizes-table modal panels pin their side edges here; the search
   panel's own side padding adds one more step so its content box lands
   on the outer edge of column 3 / 10. */
:root {
	--grid-col-step: calc((100% - 2 * var(--grid-margin, 96px) - (var(--grid-columns, 12) - 1) * var(--grid-gutter, 32px)) / var(--grid-columns, 12) + var(--grid-gutter, 32px));
	--grid-col2-inset: calc(var(--grid-margin, 96px) + var(--grid-col-step));
	/* Same shape, three steps in — outer edge of column 4 (mirrored:
	   column 9). The delivery & returns info page centres its content
	   between these (middle six columns). */
	--grid-col4-inset: calc(var(--grid-margin, 96px) + 3 * var(--grid-col-step));
	/* Same shape, four steps in — outer edge of column 5 (mirrored:
	   column 8). The checkout confirmation page centres its content
	   between these. */
	--grid-col5-inset: calc(var(--grid-margin, 96px) + 4 * var(--grid-col-step));
	/* Same shape, five steps in — left edge of column 6. The contacts
	   page starts its info column on this line. */
	--grid-col6-inset: calc(var(--grid-margin, 96px) + 5 * var(--grid-col-step));
	/* Same shape, seven steps in — left edge of column 8, i.e. the 5th
	   column counting from the right. Every side drawer (cart, combo,
	   PLP filter, address book) reaches left to this line, which lands
	   them one column wider than the PDP summary panel below. */
	--grid-col8-inset: calc(var(--grid-margin, 96px) + 7 * var(--grid-col-step));
	/* Same shape, eight steps in — left edge of column 9. Unlike the
	   tokens above this one is NOT a mirrored pair: the PDP's
	   gallery/summary split is asymmetric, and this is the line its
	   summary content starts on (the panel's own edge sits one gutter
	   back, on column 8's right edge). */
	--grid-col9-inset: calc(var(--grid-margin, 96px) + 8 * var(--grid-col-step));
	/* One column's width on a `100vw` basis — for sizing elements DEEP
	   in the flow (no viewport-wide containing block, so the %-based
	   tokens above can't apply). Caveat vs the % tokens: 100vw includes
	   a classic scrollbar's width, so on scrollbar-reserving platforms
	   grid-derived sizes drift by that amount; overlay-scrollbar
	   platforms (macOS default) and the design frame are exact. Same
	   trade-off as the footer's 100vw-based `--brand-h`. */
	--grid-col-w: calc((100vw - 2 * var(--grid-margin, 96px) - (var(--grid-columns, 12) - 1) * var(--grid-gutter, 32px)) / var(--grid-columns, 12));
}

.ap-grid-overlay {
	position: fixed;
	inset: 0;
	pointer-events: none;
	z-index: 9999;
	display: grid;
	grid-template-columns: repeat(var(--grid-columns), 1fr);
	grid-template-rows: 100%; /* one full-viewport row, else the empty column spans collapse to 0 height */
	column-gap: var(--grid-gutter);
	padding-inline: var(--grid-margin);
	box-sizing: border-box;
}
.ap-grid-overlay > span {
	background: rgba(255, 0, 0, 0.1); /* Figma layout-grid: #FF0000 @ 10% */
}

/* ─── Ported from main on merge (2026-08-11) ────────────────────────────
   main carried these in the inline chrome-styles <style>; this branch's
   chrome is the extracted product-page-chrome.css (0017 unification), so
   they land here. The checkout sandboxes' sticky summary panel DEPENDS on
   .ap-pp__sticky-rail; the input-state + size-chip rules are the brandbook
   Input-catalog states (additive to the base input rule above). */
.ap-pp__sticky-rail {
	position: absolute;
	top: 64px;
	bottom: 64px;
	pointer-events: none;
}
.ap-pp__sticky-rail > * {
	position: sticky;
	top: calc(72px + 64px); /* sticky header + page top inset */
	pointer-events: auto;
}
@media (max-width: 768px) {
	.ap-pp__sticky-rail {
		display: contents;
	}
	.ap-pp__sticky-rail > * {
		position: static;
	}
}
.ap-sandbox .ap-pp__personalize__field-input:hover {
	background: var(--input-background-enabled, #FFF);
	color: var(--input-text-filled, #191919);
	border-color: var(--input-border-hover, #A3A3A3);
	outline: none;
	box-shadow: none;
}
.ap-sandbox .ap-pp__personalize__field-input:disabled,
.ap-sandbox .ap-pp__personalize__field-input:disabled:hover,
.ap-sandbox .ap-pp__personalize__field-input:disabled:focus,
.ap-sandbox .ap-pp__personalize__field-input:disabled:focus-visible,
.ap-sandbox .ap-pp__personalize__field-input:disabled:active {
	background: var(--input-background-disabled, #F0F0F0);
	border: 1px solid var(--input-border-disabled, #E4E4E4);
	color: var(--input-text-disabled, #D4D4D4);
	outline: none;
	box-shadow: none;
}
.ap-sandbox .ap-pp__personalize__field-input:disabled::placeholder {
	color: var(--input-text-disabled, #D4D4D4);
}
.ap-sandbox .ap-pp__personalize__field-input:-webkit-autofill,
.ap-sandbox .ap-pp__personalize__field-input:-webkit-autofill:hover,
.ap-sandbox .ap-pp__personalize__field-input:-webkit-autofill:focus {
	-webkit-text-fill-color: var(--input-text-filled, #191919);
	-webkit-box-shadow: 0 0 0 1000px var(--input-background-enabled, #FFF) inset;
	box-shadow: 0 0 0 1000px var(--input-background-enabled, #FFF) inset;
}
.ap-sandbox .ap-pp__personalize__field.is-invalid .ap-pp__personalize__field-input,
.ap-sandbox .ap-pp__personalize__field.is-invalid .ap-pp__personalize__field-input:hover,
.ap-sandbox .ap-pp__personalize__field.is-invalid .ap-pp__personalize__field-input:focus,
.ap-sandbox .ap-pp__personalize__field.is-invalid .ap-pp__personalize__field-input:focus-visible,
.ap-sandbox .ap-pp__personalize__field.is-invalid .ap-pp__personalize__field-input:active {
	border: 2px solid var(--input-border-error, #FF0014);
}
.ap-pp__size-option:not([aria-pressed="true"]):not(:disabled):hover,
.ap-pp__size-option:not([aria-pressed="true"]):not(:disabled):focus,
.ap-pp__size-option:not([aria-pressed="true"]):not(:disabled):focus-visible,
.ap-pp__size-option:not([aria-pressed="true"]):not(:disabled):active {
	background-color: transparent;
	border-color: transparent;
	color: var(--text-primary, #191919);
	outline: none;
	box-shadow: inset 0 0 0 1px var(--size-selector-outline-hover, #A3A3A3);
}
